Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/sphinx/drv.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
118 changes: 118 additions & 0 deletions drv/frame.h
Original file line number Diff line number Diff line change
@@ -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 <geovane.fedrecheski@inria.fr>
*
* @copyright Inria, 2026
* @}
*/

#include <stdint.h>
#include <string.h>
#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
22 changes: 13 additions & 9 deletions drv/protocol/protocol.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@
#include <string.h>
#include <stdbool.h>
#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 = {
Expand All @@ -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);
Expand Down
Loading