unleashed-firmware/documentation/ExpansionModules.md
Georgii Surkov 95737958ad
[FL-3669] Expansion module protocol (#3250)
* ApiSymbols: add furi_record_destroy
* FuriHal: cleanup serial API, add logging configuration in RTC
* FuriHal: hide private part in _i header. Toolbox: cleanup value index. SystemSettings: logging device and baudrate.
* FuriHal: RTC logging method documentation
* Synchronize API Symbols
* Furi: mark HEAP_PRINT_DEBUG as broken
* FuriHal: furi_hal_serial, add custom IRQ func
* Fix PR review issues
* Implement basic external module detection and echo
* Update api symbols for f18
* Minimally working implementation (can create directory via rpc)
* Make expansion protocol parser a header-only library
* Rename a function
* Improve thread syncronisation
* Implement multi-packet transmissions
* Improve test application
* Clean up expansion worker code
* Send heartbeat when host is ready
* Update API symbols
* Add draft documentation
* Expansion worker: proper timeout and error handling
* Expansion worker: correct TX, do not disable expansion callback
* Expansion protocol: pc side test script
* PC side expansion test: trying to change baudrate
* Working comms between 2 flippers
* Cleaner exit from expansion worker thread
* Better checks
* Add debug logs
* Remove unneeded delays
* Use USART as default expansion port
* Refactor furi_hal_serial_control, fix crash
* Improve furi_hal abstraction, wait for stable rx pin
* Remove rogue include
* Set proper exit reason on RPC error
* Remove rogue comment
* Remove RX stability check as potentially problematic
* Improve expansion_test application
* Remove rogue define
* Give up on TODO
* Implement expansion protocol checksum support
* Update ExpansionModules.md
* RPC: reverse input
* Assets: sync protobuf
* Fix typos
* FuriHal: UART add reception DMA (#3220)
* FuriHal: add DMA serial rx mode
* usb_uart_bridge: switch to working with DMA
* Sync api symbol versions
* FuriHal: update serial docs and api
* FuriHal: Selial added similar API for simple reception mode as with DMA
* FuriHal: Update API target H18
* API: ver API H7
* FuriHal: Serial error processing
* FuriHal: fix furi_hal_serial set baudrate
* Sync api symbols
* FuriHal: cleanup serial isr and various flag handling procedures
* FuriHal: cleanup and simplify serial API
* Debug: update UART Echo serial related flags
* FuriHal: update serial API symbols naming
* Make expansion_test compile
* Remove unneeded file
* Make PVS-studio happy
* Optimise stack usage
* Optimise heap usage, improve api signature
* Fix typo
* Clean up code
* Update expansion_protocol.h
* Fix unit tests
* Add doxygen comments to expansion.h
* Update/add doxygen comments
* Update ExpansionModules.md
* Github: new global code owner
* FuriHal: naming in serial control
* Expansion: check mutex acquire return result

Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
Co-authored-by: hedger <hedger@users.noreply.github.com>
Co-authored-by: SkorP <skorpionm@yandex.ru>
Co-authored-by: SG <who.just.the.doctor@gmail.com>
Co-authored-by: Skorpionm <85568270+Skorpionm@users.noreply.github.com>
2024-01-16 18:18:56 +09:00

8 KiB

Expansion Module Protocol - Draft

Terms and definitions

  • Expansion Module: A third-party hardware unit meant for use with Flipper Zero by connecting it to its GPIO header.
  • Expansion Module Protocol: A serial-based, byte-oriented, synchronous communication protocol described in this document.
  • Host: Hardware unit tasked with serving requests. Used interchangeably with Flipper, Server, Host etc. throughout this document.
  • Device: Used interchangeably with Expansion Module, Module, Client, etc.
  • RPC: Remote Procedure Call, a protobuf-based communication protocol widely used by Flipper Zero companion applications.
  • Timeout Interval: Period of inactivity to be treated as a loss of connection, also denoted as Tto. Equals to 250 ms.
  • Baud Rate Switch Dead Time: Period of time after baud rate change during which no communication is allowed, also denoted Tdt. Equals to 25 ms.

Features

  • Automatic expansion module detection
  • Baud rate negotiation
  • Basic error detection
  • Request-response communication flow
  • Integration with Flipper RPC protocol

Hardware

Depending on the UART selected for communication, the following pins area available for the expansion modules to connect to:

UART Tx pin Rx pin
USART 13 14
LPUART 15 16

Frame structure

Each frame consists of a header (1 byte), contents (size depends of frame type) and checksum (1 byte) fields:

Header (1 byte) Contents (0 or more bytes) Checksum (1 byte)
Frame type Frame payload XOR checksum

Heartbeat frame

HEARTBEAT frames are used to maintain an idle connection. In the event of not receiving any frames within Tto, either side must cease all communications and be ready to initiate the connection again.

Header (1 byte) Checksum (1 byte)
0x01 XOR checksum

Note that the contents field is not present (0 bytes length).

Status frame

STATUS frames are used to report the status of a transaction. Every received frame MUST be confirmed by a matching STATUS response.

Header (1 byte) Contents (1 byte) Checksum (1 byte)
0x02 Error code XOR checksum

The Error code field SHALL have one of the following values:

Error code Meaning
0x00 OK (No error)
0x01 Unknown error
0x02 Baud rate not supported

Baud rate frame

BAUD RATE frames are used to negotiate communication speed. The initial connection SHALL always happen at 9600 baud. The first message sent by the module MUST be a BAUD RATE frame, even if a different speed is not required.

Header (1 byte) Contents (4 bytes) Checksum (1 byte)
0x03 Baud rate XOR checksum

If the requested baud rate is supported by the host, it SHALL respond with a STATUS frame with an OK error code, otherwise the error code SHALL be 0x02 (Baud rate not supported). Until the negotiation succeeds, the speed SHALL remain at 9600 baud. The module MAY send additional BAUD RATE frames with alternative speeds in case the initial request was refused. No other frames are allowed until the speed negotiation succeeds.

Control frame

CONTROL frames are used to control various aspects of the communication. As of now, the sole purpose of CONTROL frames is to start and stop the RPC session.

Header (1 byte) Contents (1 byte) Checksum (1 byte)
0x04 Command XOR checksum

The Command field SHALL have one of the followind values:

Command Meaning
0x00 Start RPC session
0x01 Stop RPC session

Data frame

DATA frames are used to transmit arbitrary data in either direction. Each DATA frame can hold up to 64 bytes. If an RPC session is curretly open, all received bytes are forwarded to it.

Header (1 byte) Contents (1 to 65 byte(s)) Checksum (1 byte)
0x05 Data XOR checksum

The Data field SHALL have the following structure:

Data size (1 byte) Data (0 to 64 bytes)
0x00 ... 0x40 Arbitrary data

Communication flow

In order for the host to be able to detect the module, the respective feature must be enabled first. This can be done via the GUI by going to Settings -> Expansion Modules and selecting the required Listen UART or programmatically by calling expansion_enable(). Likewise, disabling this feature via the same GUI or by calling expansion_disable() will result in ceasing all communications and not being able to detect any connected modules.

The communication is always initiated by the module by the means of shortly pulling the RX pin down. The host SHALL respond with a HEARTBEAT frame indicating that it is ready to receive requests. The module then MUST issue a BAUDRATE request within Tto. Failure to do so will result in the host dropping the connection and returning to its initial state.

        MODULE               |            FLIPPER
-----------------------------+---------------------------
                             |        (Start)
Pull down RX                -->
                            <--       Heartbeat
Baud Rate                   -->
                            <--       Status [OK | Error]
                             |
(Module changes baud rate    |        (Flipper changes 
 and waits for Tdt)          |         baud rate)
                             |
Control [Start RPC]         -->
                            <--       Status [OK | Error]
-----------------------------+--------------------------- (1)
Data [RPC Request]          -->
                            <--       Status [OK | Error]
                            <--       Data [RPC Response]
Status [OK | Error]         -->
-----------------------------+--------------------------- (2)
Data [RPC Request pt.1]     -->
                            <--       Status [OK | Error]
Data [RPC Request pt.2]     -->
                            <--       Status [OK | Error]
Data [RPC Request pt.3]     -->
                            <--       Status [OK | Error]
                            <--       Data [RPC Response]
Status [OK | Error]         -->
-----------------------------+--------------------------- (3)
Heartbeat                   -->
                            <--       Heartbeat
Heartbeat                   -->
                            <--       Heartbeat
-----------------------------+---------------------------
Control [Stop RPC]          -->
                            <--       Status [OK | Error]
(Module disconnected)        |
                             |        (No activity within Tto
                             |            return to start)

(1) The module MUST confirm all implicitly requested frames (e.g. DATA frames containing RPC responses) with a STATUS frame.
(2) RPC requests larger than 64 bytes are split into multiple frames. Every DATA frame MUST be confirmed with a STATUS frame.
(3) When the module has no data to send, it MUST send HEARTBEAT frames with a period < Tto in order to maintain the connection.
    The host SHALL respond with a HEARTBEAT frame each time.

Error detection

Error detection is implemented via adding an extra checksum byte to every frame (see above).

The checksum is calculated by bitwise XOR-ing every byte in the frame (excluding the checksum byte itself), with an initial value of 0.

Error recovery behaviour

In the event of a detected error, the concerned side MUST cease all communications and reset to initial state. The other side will then experience a communication timeout and the connection will be re-established automatically.