github.com/simpleiot/simpleiot@v0.18.3/docs/ref/serial.md (about)

     1  # Serial Devices
     2  
     3  **Contents**
     4  
     5  <!-- toc -->
     6  
     7  (see also [user documentation](../user/mcu.md) and
     8  [SIOT Firmware](https://github.com/simpleiot/firmware/tree/master/Arduino))
     9  
    10  It is common in embedded systems architectures for a MPU (Linux-based running
    11  SIOT) to be connected via a serial link (RS232, RS485, CAN, USB serial) to an
    12  MCU.
    13  
    14  ![mcu connection](../user/images/mcu.png)
    15  
    16  See
    17  [this article](http://bec-systems.com/site/1540/microcontroller-mcu-or-microprocessor-mpu)
    18  for a discussion on the differences between a MPU and MCU. These devices are not
    19  connected via a network interface, so can't use the [SIOT NATS API](api.md#nats)
    20  directly, thus we need to define a proxy between the serial interface and NATS
    21  for the MCU to interact with the SIOT system.
    22  
    23  State/config data in both the MCU and MPU systems are represented as nodes and
    24  points. An example of nodes and points is shown below. These can be arranged in
    25  any structure that makes sense and is convenient. Simple devices may only have a
    26  single node with a handful of points.
    27  
    28  ![nodes/points](images/mcu-nodes.png)
    29  
    30  SIOT does not differentiate between state (ex: sensor values) and config (ex:
    31  pump turn-on delay) -- it is all points. This simplifies the transport and
    32  allows changes to be made in multiple places. It also allows for the granular
    33  transmission and synchronization of data -- we don't need to send the entire
    34  state/config anytime something changes.
    35  
    36  SIOT has the ability to log points to InfluxDB, so this mechanism can also be
    37  used to log messages, events, state changes, whatever -- simply use an existing
    38  point type or define a new one, and send it upstream.
    39  
    40  ## Data Synchronization
    41  
    42  By default, the serial client synchronizes any extra points written to the
    43  serial node. The serial UI displays the extra points as shown below:
    44  
    45  <img src="./assets/image-20231031115204490.png" alt="image-20231031115204490" style="zoom:50%;" />
    46  
    47  Alternatively, there is an option for the serial client to sync its parent's
    48  points to the serial device. When this is selected, any points received from the
    49  serial device are posted to the parent node, and any points posted to the parent
    50  node that were not sent by the serial device are forwarded to the serial client.
    51  
    52  ## Protocol
    53  
    54  The SIOT serial protocol mirrors the NATS
    55  [PUB message](https://docs.nats.io/reference/reference-protocols/nats-protocol#pub)
    56  with a few assumptions:
    57  
    58  - we don't have mirrored nodes inside the MCU device
    59  - the number of nodes and points in a MCU is relatively small
    60  - the payload is always an array of points
    61  - only the following [SIOT NATS API](api.md#nats) subjects are supported:
    62    - blank (assumes ID of Serial MCU client node
    63    - `p.<id>` (used to send node points)
    64    - `p.<id>.<parent>` (used to send edge points)
    65    - `phr` (specifies high-rate payload)
    66  - we don't support NATS subscriptions or requests -- on startup, we send the
    67    entire dataset for the MCU device in both directions (see On connection
    68    section), merge the contents, and then assume any changes will get sent and
    69    received after that.
    70  
    71  `subject` can be left blank when sending/receiving points for the MCU root node.
    72  This saves some data in the serial messages.
    73  
    74  The point type `nodeType` is used to create new nodes and to send the node type
    75  on connection.
    76  
    77  All packets are ack'd (in both directions) by an empty packet with the same
    78  sequence number and subject set to 'ack'. If an ack is not received in X amount
    79  of time, the packet is retried up to 3 times, and then the other device is
    80  considered "offline".
    81  
    82  ### Encoding
    83  
    84  #### Packet Frame
    85  
    86  All packets between the SIOT and serial MCU systems are framed as follows:
    87  
    88  ```
    89  sequence (1 byte, rolls over)
    90  subject (16 bytes)
    91  payload (Protobuf Point array or HR repeated point payload)
    92  crc (2 bytes) (Currently using CRC-16/KERMIT) (not included on log messages)
    93  ```
    94  
    95  Protocols like RS232 and USB serial do not have any inherent framing; therefore,
    96  this needs to be done at the application level. SIOT encodes each packet using
    97  [COBS (Consistent Overhead Byte Stuffing)](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing).
    98  
    99  #### Log payload
   100  
   101  The log message is specified with `log` in the packet frame subject. The payload
   102  is ASCII characters and CRC not included.
   103  
   104  #### Protobuf payload
   105  
   106  The `serial` protobuf type is used to transfer these messages:
   107  
   108  ```
   109  message SerialPoint {
   110    string type = 2;
   111    float value = 4;
   112    int64 time = 16;
   113    float index = 13;
   114    string text = 8;
   115    string key = 11;
   116    int32 tombstone = 12;
   117  }
   118  ```
   119  
   120  Protobuf can be used for low-rate samples, config, state, etc.
   121  
   122  Protobuf is used to encode the data on the wire. Find protobuf files
   123  [here](https://github.com/simpleiot/simpleiot/tree/master/internal/pb).
   124  [nanopb](https://github.com/nanopb/nanopb) can be used to generate C-based
   125  protobuf bindings that are suitable for use in most MCU environments.
   126  
   127  #### High-rate payload
   128  
   129  A simple payload encoding for high-rate data can be used to avoid the overhead
   130  of protobuf encoding and is specified with `phr` in the packet frame subject.
   131  
   132  ```
   133  type (16 bytes) point type
   134  key (16 bytes) point key
   135  starttime (uint64) starting time of samples in ns since Unix Epoch
   136  sampleperiod (uint32) time between samples in ns
   137  data (variable, remainder of packet), packed 32-bit floating point samples
   138  ```
   139  
   140  This data bypasses most of the processing in SIOT and is sent to a special
   141  [`phr` NATS subject](api.md). Clients that are interested in high-rate data
   142  (like the InfluxDB client) can listen to these subjects.
   143  
   144  #### File payload
   145  
   146  This payload type is for transferring files in blocks. These files may be used
   147  for firmware updates or other transfers where large amounts of data need to be
   148  transferred. An empty block with index set to -1 is sent at the end of the
   149  transfer.
   150  
   151  ```
   152  name (16 bytes) filename
   153  index (4 bytes) file block index
   154  data (variable, remainder of packet)
   155  ```
   156  
   157  ### On connection
   158  
   159  On initial connection between a serial device and SIOT, the following steps are
   160  done:
   161  
   162  - the MCU sends the SIOT system an empty packet with its root node ID
   163  - the SIOT systems sends the current time to the MCU (point type `currentTime`)
   164  - the MCU updates any "offline" points with the current time (see offline
   165    section).
   166  - the SIOT acks the current time packet.
   167  - all of the node and edge points are sent from the SIOT system to the MCU, and
   168    from the MCU to the SIOT system. Each system compares point time stamps and
   169    updates any points that are newer. Relationships between nodes are defined by
   170    edge points (point type `tombstone`).
   171  
   172  ### Timestamps
   173  
   174  The Simple IoT uses a 64-bit ns since Unit epoch value for all timestamps.
   175  
   176  ### Fault handling
   177  
   178  Any communication medium has the potential to be disrupted (unplugged/damaged
   179  wires, one side off, etc). Devices should continue to operate and when
   180  re-connected, do the right thing.
   181  
   182  If a MCU has a valid time (RTC, sync from SIOT, etc), it will continue
   183  operating, and when reconnected, it will send all its points to re-sync.
   184  
   185  If a MCU powers up and has no time, it will set the time to 1970 and start
   186  operating. When it receives a valid time from the SIOT system, it will compute
   187  the time offset from the SIOT time and its own 1970 based time, index through
   188  all points, add the offset to any points with time less than 2020, and then send
   189  all points to SIOT.
   190  
   191  When the MCU syncs time with SIOT, if the MCU time is ahead of the SIOT system,
   192  then it set its time, and look for any points with a time after present, and
   193  reset these timestamps to the present.
   194  
   195  ## RS485
   196  
   197  Status: Idea
   198  
   199  RS485 is a half duplex, prompt response transport. SIOT periodically prompts MCU
   200  devices for new data at some configurable rate. Data is still COBS encoded so
   201  that is simple to tell where packets start/stop without needing to rely on dead
   202  space on the line.
   203  
   204  Simple IoT also supports modbus, but the native SIOT protocol is more capable --
   205  especially for structured data.
   206  
   207  Addressing: TODO
   208  
   209  ## CAN
   210  
   211  Status: Idea
   212  
   213  CAN messages are limited to 8 bytes. The J1939 Transport Protocol can be used to
   214  assemble multiple messages into a larger packet for transferring up to 1785
   215  bytes.
   216  
   217  ## Implementation notes
   218  
   219  Both the SIOT and MCU side need to store the common set of nodes and points
   220  between the systems. This is critical as the point merge algorithm only uses an
   221  incoming point if the incoming point is newer than the one currently stored on
   222  the device. For SIOT NATS clients, we use the NodeEdge data structure:
   223  
   224  ```go
   225  type NodeEdge struct {
   226          ID         string
   227          Type       string
   228          Parent     string
   229          Points     Points
   230          EdgePoints Points
   231  	Origin     string
   232  }
   233  ```
   234  
   235  Something similar could be done on the MCU.
   236  
   237  If new nodes are created on the MCU, the ID must be a UUID, so that it does not
   238  conflict with any of the node IDs in the upstream SIOT system(s).
   239  
   240  On the SIOT side, we keep a list of Nodes on the MCU and periodically check if
   241  any new Nodes have been created. If so, we send the new Nodes to the MCU.
   242  Subscriptions are set up for points and edges of all nodes, and any new points
   243  are sent to the MCU. Any points received from the MCU simply forwarded to the
   244  SIOT NATS bus.
   245  
   246  ## DFU
   247  
   248  Status: Idea
   249  
   250  For devices that support USB Device Firmware Upgrade (DFU), SIOT provides a
   251  mechanism to do these updates. A node that specifies USB ID and file configures
   252  the process.
   253  
   254  - [DFU Specification](https://www.usb.org/sites/default/files/DFU_1.1.pdf)
   255  - [Windows Implementation](https://docs.microsoft.com/en-us/windows-hardware/drivers/stream/device-firmware-update-for-usb-devices-without-using-a-co-installer)