Skip to content

Commit

Permalink
Add event record service
Browse files Browse the repository at this point in the history
Along with the IOT2050 SM variant, some IOT2050 device specific events
are recognized as being important for field applications, including:

- Device power up
- Device power loss
- Device case open (uncover)
- Device tilting
- Device watchdog reset
- External module events

Although it's possible to collect all these events from variant source,
such as syslog, or by customized coding to get the sensor event, it's
valuable to provide an unified method or portal to collect all these
predefined events from one place.

Then this service is added to serve the above purpose, it reads the
power up, power loss, eio, tilt, uncover, and (possibly) the watchdog
reset events, then writes them into syslog and makes them be readable
from gRPC interface.

This service could be used directly, or as a base/reference for customization.

Signed-off-by: Li Hua Qian <[email protected]>
  • Loading branch information
huaqianli authored and BaochengSu committed Feb 1, 2024
1 parent 6d433d3 commit 5550c24
Show file tree
Hide file tree
Showing 16 changed files with 831 additions and 0 deletions.
94 changes: 94 additions & 0 deletions recipes-app/iot2050-event-record/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# IOT2050 Event Record

IOT2050 Event Record is using for reading and recording events, such as
power up, power loss, tilted, uncovered, watchdog reset and eio events.

The core is a RPC service implemented with the help of gPRC.

## Event record services

The `iot2050-event-record.service` and `iot2050-event-serve.service` are systemd
services, they could be managed by `systemctl`. The `iot2050-event-record` collects events from various source then consume the API exposed by `iot2050-event-serve`
to wrap the collected events as `IOT2050-EventRecord` events and save them to
syslog. Then these wrapped events could be read by `journalctl` or by gRPC APIs.

## Predefined events

### Power events and Extended IO(EIO) events

Power events and EIO events are injected to `journal(syslog)` by default.

To check them on IOT2050:

```sh
root@iot2050-debian:~# journalctl SYSLOG_IDENTIFIER=IOT2050-EventRecord
Oct 23 22:36:12 iot2050-debian IOT2050-EventRecord[323]: IOT2050_EVENTS.power: 2023-10-23 22:36:00 the device is powered up
Oct 23 22:40:21 iot2050-debian IOT2050-EventRecord[323]: IOT2050_EVENTS.power: 2023-10-23 22:34:4 [2] power loss
Oct 23 22:40:21 iot2050-debian IOT2050-EventRecord[323]: IOT2050_EVENTS.eio: 2023-10-23 22:12:54 [11] slot1 lost
```

### Sensor events

By default, the sensor events, i.e. tilted and uncovered events, are disabled.
To enable them, please create a systemd drop-in for `iot2050-event-record.service`,
as follows:

```sh
cp /usr/lib/iot2050/event/iot2050-event-record.conf /etc/systemd/system/iot2050-event-record.service.d/
```

### Watchdog events

If watchdog event recording is expected, please refer to [WATCHDOG.md](./WATCHDOG.md).

## Development

### How to inject a new event?

First, please link the `EventInterface` to the customized application.
```sh
# In IOT DUT
ln -s /usr/lib/iot2050/event/gRPC/EventInterface /path/to/customized-app/gRPC/EventInterface

# In Source code
ln -s recipes-app/iot2050-event-record/files/gRPC/EventInterface /path/to/customized-app/gRPC/EventInterface
```

Then, use the `Write` and `Read` functions to communicate with
`iot2050-event-serve.service`, as follows:

```python
import grpc
from gRPC.EventInterface.iot2050_event_pb2 import (
WriteRequest,
ReadRequest
)
from gRPC.EventInterface.iot2050_event_pb2_grpc import EventRecordStub


def write_event(event_type, event):
with grpc.insecure_channel(iot2050_event_api_server) as channel:
stub = EventRecordStub(channel)
response = stub.Write(WriteRequest(event_type=event_type, event=event))

if response.status:
print(f'Event Record writes event result: {response.status}')
print(f'Event Record writes event message: {response.message}')

def read_event(event_type):
with grpc.insecure_channel(iot2050_event_api_server) as channel:
stub = EventRecordStub(channel)
response = stub.Read(ReadRequest(event_type=event_type))
return response.event
```

And, please find the api definition in `gRPC/EventInterface/iot2050-event.proto`.

### Regenerate the gRPC python modules if proto file changed

If the `proto` file needs to be changed when customizing a new application
to inject event, please update the gRPC in the original path as follows.

```sh
python3 -m grpc_tools.protoc -I. --python_out=. --pyi_out=. --grpc_python_out=. gRPC/EventInterface/iot2050-event.proto
```
38 changes: 38 additions & 0 deletions recipes-app/iot2050-event-record/WATCHDOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
This README file explains how to get the watchdog reset status and how to
inject it into `iot2050-event-record` service.

# How to get the watchdog reset status?

The `wdt_example.py` below shows how to get the watchdog reset status.

```py
import psutil
import time
from datetime import datetime

WDIOF_CARDRESET = "32"
WDT_PATH = "/sys/class/watchdog/watchdog0/bootstatus"

EVENT_STRINGS = {
"wdt": "{} watchdog reset is detected",
"no-wdt": "{} watchdog reset isn't detected"
}

def record_wdt_events():
status = ""
with open(WDT_PATH, "r") as f:
status = f.read().strip()

boot_time = datetime.fromtimestamp(psutil.boot_time())
if WDIOF_CARDRESET == status:
print(EVENT_STRINGS["wdt"].format(boot_time))
else:
print(EVENT_STRINGS["no-wdt"].format(boot_time))

if __name__ == "__main__":
record_wdt_events()
```

# How to inject it into iot2050-event-record?

Please refer to [README.md](./README.md).
1 change: 1 addition & 0 deletions recipes-app/iot2050-event-record/files/gRPC/EIOManager
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright (c) Siemens AG, 2023
*
* Authors:
* Li Hua Qian <[email protected]>
*
* SPDX-License-Identifier: MIT
*/

syntax = "proto3";

package eventrecord;

service EventRecord {
rpc Write (WriteRequest) returns (WriteReply) {}
rpc Read (ReadRequest) returns (ReadReply) {}
}

/* ----------------- Write event ----------------- */
/* WriteRequest
* - event_type: a string to present event type
* - "IOT2050_EVENT.xxx" means IOT2050 standard events
* - "" or other strings mean customized events
* - event: the event content to write
*/
message WriteRequest {
string event_type = 1;
string event = 2;
}

/* WriteReply
* - status: 0 means successful
* others mean error
* - message: the detail write message
*/
message WriteReply {
int32 status = 1;
string message = 2;
}

/* ----------------- Read event ----------------- */
/* ReadRequest
* - event_type: a string to present event type
* - "IOT2050_EVENT.xxx" means IOT2050 standard events
* - "" means to all types of events
*/
message ReadRequest {
string event_type = 1;
}

/* ReadReply
* - status: 0 means successful
* others mean error
* - message: the detail write message
* - event: the read back event content
*/
message ReadReply {
int32 status = 1;
string message = 2;
string event = 3;
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Optional as _Optional

DESCRIPTOR: _descriptor.FileDescriptor

class WriteRequest(_message.Message):
__slots__ = ["event_type", "event"]
EVENT_TYPE_FIELD_NUMBER: _ClassVar[int]
EVENT_FIELD_NUMBER: _ClassVar[int]
event_type: str
event: str
def __init__(self, event_type: _Optional[str] = ..., event: _Optional[str] = ...) -> None: ...

class WriteReply(_message.Message):
__slots__ = ["status", "message"]
STATUS_FIELD_NUMBER: _ClassVar[int]
MESSAGE_FIELD_NUMBER: _ClassVar[int]
status: int
message: str
def __init__(self, status: _Optional[int] = ..., message: _Optional[str] = ...) -> None: ...

class ReadRequest(_message.Message):
__slots__ = ["event_type"]
EVENT_TYPE_FIELD_NUMBER: _ClassVar[int]
event_type: str
def __init__(self, event_type: _Optional[str] = ...) -> None: ...

class ReadReply(_message.Message):
__slots__ = ["status", "message", "event"]
STATUS_FIELD_NUMBER: _ClassVar[int]
MESSAGE_FIELD_NUMBER: _ClassVar[int]
EVENT_FIELD_NUMBER: _ClassVar[int]
status: int
message: str
event: str
def __init__(self, status: _Optional[int] = ..., message: _Optional[str] = ..., event: _Optional[str] = ...) -> None: ...
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc

from gRPC.EventInterface import iot2050_event_pb2 as gRPC_dot_EventInterface_dot_iot2050__event__pb2


class EventRecordStub(object):
"""Missing associated documentation comment in .proto file."""

def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.Write = channel.unary_unary(
'/eventrecord.EventRecord/Write',
request_serializer=gRPC_dot_EventInterface_dot_iot2050__event__pb2.WriteRequest.SerializeToString,
response_deserializer=gRPC_dot_EventInterface_dot_iot2050__event__pb2.WriteReply.FromString,
)
self.Read = channel.unary_unary(
'/eventrecord.EventRecord/Read',
request_serializer=gRPC_dot_EventInterface_dot_iot2050__event__pb2.ReadRequest.SerializeToString,
response_deserializer=gRPC_dot_EventInterface_dot_iot2050__event__pb2.ReadReply.FromString,
)


class EventRecordServicer(object):
"""Missing associated documentation comment in .proto file."""

def Write(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')

def Read(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')


def add_EventRecordServicer_to_server(servicer, server):
rpc_method_handlers = {
'Write': grpc.unary_unary_rpc_method_handler(
servicer.Write,
request_deserializer=gRPC_dot_EventInterface_dot_iot2050__event__pb2.WriteRequest.FromString,
response_serializer=gRPC_dot_EventInterface_dot_iot2050__event__pb2.WriteReply.SerializeToString,
),
'Read': grpc.unary_unary_rpc_method_handler(
servicer.Read,
request_deserializer=gRPC_dot_EventInterface_dot_iot2050__event__pb2.ReadRequest.FromString,
response_serializer=gRPC_dot_EventInterface_dot_iot2050__event__pb2.ReadReply.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'eventrecord.EventRecord', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))


# This class is part of an EXPERIMENTAL API.
class EventRecord(object):
"""Missing associated documentation comment in .proto file."""

@staticmethod
def Write(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(request, target, '/eventrecord.EventRecord/Write',
gRPC_dot_EventInterface_dot_iot2050__event__pb2.WriteRequest.SerializeToString,
gRPC_dot_EventInterface_dot_iot2050__event__pb2.WriteReply.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)

@staticmethod
def Read(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(request, target, '/eventrecord.EventRecord/Read',
gRPC_dot_EventInterface_dot_iot2050__event__pb2.ReadRequest.SerializeToString,
gRPC_dot_EventInterface_dot_iot2050__event__pb2.ReadReply.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# In IoT2050-SM, there are sensors for tilted and uncovered detection. If you
# want to enable logging of sensor events, i.e. tilted and uncovered events.
# Please copy this file to /etc/systemd/system/iot2050-event-record.service.d/.

[Service]
Environment="RECORD_SENSOR_EVENTS=True"
Loading

0 comments on commit 5550c24

Please sign in to comment.