diff --git a/doc/sphinx/drv.md b/doc/sphinx/drv.md index de0ac33..ce3e0e9 100644 --- a/doc/sphinx/drv.md +++ b/doc/sphinx/drv.md @@ -12,6 +12,7 @@ Libraries _api/drv_as5048b _api/drv_battery _api/drv_control_loop +_api/drv_frame _api/drv_hdlc _api/drv_imu _api/drv_ism330 diff --git a/drv/frame.h b/drv/frame.h new file mode 100644 index 0000000..4d5711b --- /dev/null +++ b/drv/frame.h @@ -0,0 +1,118 @@ +#ifndef __FRAME_H +#define __FRAME_H + +/** + * @defgroup drv_frame DotBot app frame header + * @ingroup drv + * @brief Mari-compatible wire header for bare-radio DotBot apps + * + * Bare-radio DotBot applications (apps that don't run over the Mari TSCH + * link layer) prepend this header to each radio payload. The byte layout + * is a deliberate copy of Mari's `mr_packet_header_t` (see + * `mari/firmware/mari/models.h`) so that a bare-radio gateway can wrap + * received frames as `MARI_EDGE_DATA` UART events and PyDotBot's existing + * MarilibEdge adapter — which dispatches by `next_proto` — handles them + * with no special case. + * + * Bare apps always set `type = DATA` and `next_proto = DOTBOT_APP`. The + * radio uses a BLE access address distinct from Mari's so the two stacks + * ignore each other at the peripheral filter. + * + * @{ + * @file + * @author Geovane Fedrecheski + * + * @copyright Inria, 2026 + * @} + */ + +#include +#include +#include "device.h" + +//=========================== defines ========================================== + +/// Mari-compatible packet type for DATA frames (mirrors `MARI_PACKET_DATA`). +#define DB_FRAME_TYPE_DATA (16) + +/// Mari-compatible next_proto value for the DotBot application protocol +/// (mirrors `MARI_NEXT_PROTO_DOTBOT_APP`). The host's MarilibEdge adapter +/// dispatches frames carrying this value to the DotBot pipeline. +#define DB_FRAME_NEXT_PROTO (0x11) + +/// Header version. Matches Mari's current `MARI_PROTOCOL_VERSION` so the +/// host parses a bare-radio frame identically to a Mari frame. +#define DB_FRAME_VERSION (3) + +/// Network ID for bare-radio DotBot apps. Reserved at the Crystalfree +/// Mari network-ID registry — distinct from any Mari deployment's +/// `net_id` to keep bare-radio traffic in its own namespace. +#define DB_FRAME_NETWORK_ID (0xD0B0) + +/// BLE access address for bare radio. Distinct from Mari's `0x12345678` +/// so a Mari node and a bare-radio node ignore each other at the radio +/// peripheral filter. Also distinct from the BLE advertising access +/// address (`0x8E89BED6`). +#define DB_FRAME_ACCESS_ADDR (0xDB12DB12UL) + +/// Default radio frequency offset for bare-radio DotBot apps. The radio +/// driver computes `2400 + freq` MHz from this, so 8 → 2408 MHz — same +/// value bare DotBot apps used pre-TDMA. +#define DB_FRAME_DEFAULT_FREQ (8U) + +/// Broadcast destination value. +#define DB_FRAME_DST_BROADCAST (0xFFFFFFFFFFFFFFFFULL) + +/// Wire size of the header in bytes — must match Mari's `mr_packet_header_t`. +#define DB_FRAME_HEADER_SIZE (21) + +//=========================== types ============================================ + +/** + * @brief Wire header for bare-radio DotBot frames. + * + * Byte-for-byte mirror of Mari's `mr_packet_header_t`. Mari stores + * `type` and `next_proto` as enums; here they're `uint8_t` so the + * layout doesn't depend on the compiler's enum width. The numeric + * values are pinned by Mari's enum. + */ +typedef struct __attribute__((packed)) { + uint8_t version; ///< Protocol version + uint8_t type; ///< Packet type — always DATA for bare radio + uint16_t network_id; ///< Network identifier + uint64_t dst; ///< Destination device id (`DB_FRAME_DST_BROADCAST` for broadcast) + uint64_t src; ///< Source device id + uint8_t next_proto; ///< Upper-layer protocol — always DOTBOT_APP for bare radio +} db_frame_header_t; + +/* Hard guarantee: layout matches Mari's mr_packet_header_t exactly. */ +_Static_assert(sizeof(db_frame_header_t) == DB_FRAME_HEADER_SIZE, + "db_frame_header_t must be 21 bytes (Mari mr_packet_header_t layout)"); + +//=========================== public =========================================== + +/** + * @brief Write a DotBot app frame header to a buffer. + * + * Fills the canonical DATA + DOTBOT_APP header. The sender's device id + * is read from `db_device_id()`. + * + * @param[out] buffer Destination buffer (must hold at least 21 bytes) + * @param[in] dst Destination device id (`DB_FRAME_DST_BROADCAST` for broadcast) + * + * @return Number of bytes written (always `DB_FRAME_HEADER_SIZE`). + */ +static inline size_t db_frame_header_to_buffer(uint8_t *buffer, uint64_t dst) { + db_frame_header_t header = { + .version = DB_FRAME_VERSION, + .type = DB_FRAME_TYPE_DATA, + .network_id = DB_FRAME_NETWORK_ID, + .dst = dst, + .src = db_device_id(), + .next_proto = DB_FRAME_NEXT_PROTO, + }; + memcpy(buffer, &header, sizeof(header)); + return sizeof(header); +} + +#endif // __FRAME_H diff --git a/drv/protocol/protocol.c b/drv/protocol/protocol.c index 631191b..bfcd592 100644 --- a/drv/protocol/protocol.c +++ b/drv/protocol/protocol.c @@ -13,11 +13,15 @@ #include #include #include "device.h" +#include "frame.h" #include "protocol.h" //=========================== public =========================================== -static size_t _protocol_header_to_buffer(uint8_t *buffer, uint64_t dst, packet_type_t packet_type) { +// Legacy 18-byte header writer. Kept around for the deprecated TDMA helpers +// below; the public non-TDMA writers route through `db_frame_header_to_buffer` +// (mari-shaped 21-byte header) so apps end up with one wire vocabulary. +static size_t _legacy_header_to_buffer(uint8_t *buffer, uint64_t dst, packet_type_t packet_type) { uint64_t src = db_device_id(); protocol_header_t header = { @@ -31,48 +35,48 @@ static size_t _protocol_header_to_buffer(uint8_t *buffer, uint64_t dst, packet_t } size_t db_protocol_header_to_buffer(uint8_t *buffer, uint64_t dst) { - return _protocol_header_to_buffer(buffer, dst, DB_PACKET_DATA); + return db_frame_header_to_buffer(buffer, dst); } size_t db_protocol_tdma_keep_alive_to_buffer(uint8_t *buffer, uint64_t dst) { - return _protocol_header_to_buffer(buffer, dst, DB_PACKET_TDMA_KEEP_ALIVE); + return _legacy_header_to_buffer(buffer, dst, DB_PACKET_TDMA_KEEP_ALIVE); } size_t db_protocol_tdma_table_update_to_buffer(uint8_t *buffer, uint64_t dst, protocol_tdma_table_t *tdma_table) { - size_t header_length = _protocol_header_to_buffer(buffer, dst, DB_PACKET_TDMA_UPDATE_TABLE); + size_t header_length = _legacy_header_to_buffer(buffer, dst, DB_PACKET_TDMA_UPDATE_TABLE); memcpy(buffer + sizeof(protocol_header_t), tdma_table, sizeof(protocol_tdma_table_t)); return header_length + sizeof(protocol_tdma_table_t); } size_t db_protocol_tdma_sync_frame_to_buffer(uint8_t *buffer, uint64_t dst, protocol_sync_frame_t *sync_frame) { - size_t header_length = _protocol_header_to_buffer(buffer, dst, DB_PACKET_TDMA_SYNC_FRAME); + size_t header_length = _legacy_header_to_buffer(buffer, dst, DB_PACKET_TDMA_SYNC_FRAME); memcpy(buffer + sizeof(protocol_header_t), sync_frame, sizeof(protocol_sync_frame_t)); return header_length + sizeof(protocol_sync_frame_t); } size_t db_protocol_advertizement_to_buffer(uint8_t *buffer, uint64_t dst, application_type_t application) { - size_t header_length = _protocol_header_to_buffer(buffer, dst, DB_PACKET_DATA); + size_t header_length = db_frame_header_to_buffer(buffer, dst); *(buffer + header_length) = DB_PROTOCOL_ADVERTISEMENT; *(buffer + header_length + sizeof(uint8_t)) = application; return header_length + sizeof(uint8_t) + sizeof(uint8_t); } size_t db_protocol_dotbot_advertizement_to_buffer(uint8_t *buffer, uint64_t dst, bool calibrated) { - size_t header_length = _protocol_header_to_buffer(buffer, dst, DB_PACKET_DATA); + size_t header_length = db_frame_header_to_buffer(buffer, dst); *(buffer + header_length) = DB_PROTOCOL_DOTBOT_ADVERTISEMENT; *(buffer + header_length + sizeof(uint8_t)) = calibrated; return header_length + sizeof(uint8_t) + sizeof(uint8_t); } size_t db_protocol_cmd_move_raw_to_buffer(uint8_t *buffer, uint64_t dst, protocol_move_raw_command_t *command) { - size_t header_length = _protocol_header_to_buffer(buffer, dst, DB_PACKET_DATA); + size_t header_length = db_frame_header_to_buffer(buffer, dst); *(buffer + header_length) = DB_PROTOCOL_CMD_MOVE_RAW; memcpy(buffer + header_length + sizeof(uint8_t), command, sizeof(protocol_move_raw_command_t)); return header_length + sizeof(uint8_t) + sizeof(protocol_move_raw_command_t); } size_t db_protocol_cmd_rgbled_to_buffer(uint8_t *buffer, uint64_t dst, protocol_rgbled_command_t *command) { - size_t header_length = _protocol_header_to_buffer(buffer, dst, DB_PACKET_DATA); + size_t header_length = db_frame_header_to_buffer(buffer, dst); *(buffer + header_length) = DB_PROTOCOL_CMD_RGB_LED; memcpy(buffer + header_length + sizeof(uint8_t), command, sizeof(protocol_rgbled_command_t)); return header_length + sizeof(uint8_t) + sizeof(protocol_rgbled_command_t);