Files
mongoose/src/l2_mesh.c
T
2026-11-05 22:23:06 +00:00

617 lines
23 KiB
C

// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "l2_mesh.h"
#include "log.h"
#include "printf.h"
#include "util.h"
#if MG_ENABLE_TCPIP
#define MGI_ETH_MAX 1514
#define MGI_TX_DST_LEN 2
// Mesh header, after the radio header: type, then burst: the fragment's
// index << 4 | the last fragment's index. Common to every radio
#define MG_MESH_HDR_LEN 2
#define MG_MESH_FRAME_HDR_LEN (MG_802154_HEADER_LEN + MG_MESH_HDR_LEN)
// MAC header, 9 bytes: frame control 0x8841, a data frame, PAN ID
// compression, short destination and source addresses; sequence number, 0,
// as nothing uses it yet; PAN ID; destination; source. Little endian, as
// the standard says
static void mgi_802154_print_hdr(const struct mg_radio *radio, uint8_t *buf,
uint16_t dst, uint16_t src) {
buf[0] = 0x41, buf[1] = 0x88, buf[2] = 0;
buf[3] = (uint8_t) MG_802154_PAN_ID;
buf[4] = (uint8_t) (MG_802154_PAN_ID >> 8);
buf[5] = (uint8_t) dst, buf[6] = (uint8_t) (dst >> 8);
buf[7] = (uint8_t) src, buf[8] = (uint8_t) (src >> 8);
(void) radio;
}
static size_t mgi_802154_parse_hdr(const struct mg_radio *radio,
const uint8_t *buf, size_t len,
uint16_t *dst, uint16_t *src) {
(void) radio;
if (len < MG_802154_HEADER_LEN || buf[0] != 0x41 || buf[1] != 0x88 ||
buf[3] != (uint8_t) MG_802154_PAN_ID ||
buf[4] != (uint8_t) (MG_802154_PAN_ID >> 8)) {
return 0;
}
*dst = (uint16_t) (buf[5] | buf[6] << 8);
*src = (uint16_t) (buf[7] | buf[8] << 8);
return MG_802154_HEADER_LEN;
}
const struct mg_radio mg_radio_802154 = {
"802.15.4", // name
125, // max_frame_size: 127 minus FCS
250000, // bitrate
8, // overhead
320, // slot_us
128, // cca_us
100, // turnaround_us
640, // ifs_us
3, // backoff_min
5, // backoff_max
true, // csma
MG_802154_HEADER_LEN, // header_len
mgi_802154_print_hdr, // print_hdr
mgi_802154_parse_hdr // parse_hdr
};
static uint64_t mgi_mesh_b_airtime(size_t len) {
const struct mg_radio *radio = &mg_radio_802154;
return ((uint64_t) (len + radio->overhead) * 8000000 + radio->bitrate - 1) /
radio->bitrate;
}
// Each queue record holds a destination and a complete burst.
static size_t mgi_mesh_b_queue(struct mg_mesh_node *node, struct mg_queue *q,
const void *buf, size_t len, uint16_t dst,
uint8_t type) {
const struct mg_radio *radio = &mg_radio_802154;
const uint8_t *eth = (const uint8_t *) buf;
size_t hdr = MG_MESH_FRAME_HDR_LEN, cap = radio->max_frame_size - hdr;
size_t frames, size, i;
char *record;
uint8_t *out;
if (len < 14 || len > MGI_ETH_MAX) return 0;
frames = (len + cap - 1) / cap;
size = MGI_TX_DST_LEN + len + frames * (1 + hdr);
if (mg_queue_next(q, NULL) == 0) mg_queue_init(q, q->buf, q->size);
if (mg_queue_book(q, &record, size) < size) return 0;
out = (uint8_t *) record;
*out++ = (uint8_t) (dst >> 8);
*out++ = (uint8_t) dst;
for (i = 0; i < frames; i++) {
size_t chunk = len - i * cap;
if (chunk > cap) chunk = cap;
*out++ = (uint8_t) (hdr + chunk);
radio->print_hdr(radio, out, dst, node->radio_address);
out[radio->header_len] = type;
out[radio->header_len + 1] = (uint8_t) (i << 4 | (frames - 1));
memcpy(out + hdr, eth + i * cap, chunk);
out += hdr + chunk;
}
mg_queue_add(q, size);
return len;
}
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#define MGI_JOIN_WINDOW_US 3000
#define MGI_JOIN_TURN_US 192
#define MGI_JOIN_JITTER_US 640
#define MGI_DATA_QUEUE_SIZE 2048
#define MGI_TX_RETRY_US 32
// Our frames: the radio header, with the radio addresses, then the mesh
// header MG_MESH_HDR_LEN, then the payload
#define MGI_JOIN_UP_LEN \
(MG_MESH_FRAME_HDR_LEN + 6) // MGI_JOIN_UP's payload: MAC addr
#define MGI_MAX_FRAGMENTS 14
enum { MGI_JOIN_DOWN = 1, MGI_JOIN_UP, MGI_DATA_DOWN, MGI_DATA_UP };
enum { MGI_TURN_IDLE, MGI_TURN_JOIN, MGI_TURN_DATA };
struct mgi_mesh_b_data {
uint8_t rx_buf[MGI_ETH_MAX]; // Ethernet frame being reassembled
uint8_t rx_expected; // Expected index | last index, 0xff if idle
struct mg_mesh_node *node; // Owner of the TCP/IP interface
struct mg_queue tx; // One record per burst of RF fragments
size_t tx_offset; // Next queued fragment; zero for an empty frame
uint64_t tx_at; // Next fragment or JOIN transmission time
uint64_t deadline; // End of the current JOIN or DATA turn
struct mgi_mesh_b_peer *fanout; // Next broadcast recipient
char tx_buf[MGI_DATA_QUEUE_SIZE]; // Transmit queue storage
uint32_t random; // PRNG state for join timing
struct mgi_mesh_b_peer *members; // Bridge membership list
struct mgi_mesh_b_peer *current; // Member whose turn is active or next
int8_t missed_rounds; // -1 after a grant; otherwise completed rounds missed
uint16_t coordinator; // Coordinator radio address
uint8_t phase; // MGI_TURN_IDLE, MGI_TURN_JOIN, or MGI_TURN_DATA
};
struct mgi_mesh_b_peer {
struct mgi_mesh_b_peer *next; // Next member in round-robin order
uint16_t radio_address; // Source address supplied in MGI_JOIN_UP
uint8_t mac[6]; // Ethernet MAC supplied in MGI_JOIN_UP
uint8_t misses; // Turns in a row it did not answer
};
static uint32_t mgi_mesh_b_random(struct mg_mesh_node *node,
struct mgi_mesh_b_data *data) {
uint32_t *random = &data->random;
if (*random == 0) {
size_t i;
*random = (uint32_t) mg_now();
for (i = 0; i < 6; i++) *random = *random * 33u + node->radio->mac[i];
if (*random == 0) *random = 1;
}
*random ^= *random << 13;
*random ^= *random >> 17;
*random ^= *random << 5;
return *random;
}
static const struct mg_radio *const s_mesh_default_radio = &mg_radio_802154;
// Read a frame's radio addresses to dst and src, see mg_radio::parse_hdr(), and
// return its type, from the mesh header. 0: not a frame of ours
static unsigned mgi_mesh_b_frame_parse(const uint8_t *frame, size_t len,
uint16_t *dst, uint16_t *src) {
if (len < MG_MESH_FRAME_HDR_LEN ||
s_mesh_default_radio->parse_hdr(s_mesh_default_radio, frame, len, dst,
src) !=
s_mesh_default_radio->header_len) {
return 0;
}
return frame[s_mesh_default_radio->header_len];
}
// Write a frame's headers: the radio's, to dst from src, then ours: type,
// and burst: high nibble is the index, low nibble is the last index
static void mgi_mesh_b_frame_header(uint8_t *frame, uint16_t dst, uint16_t src,
uint8_t type, uint8_t burst) {
size_t h = s_mesh_default_radio->header_len;
s_mesh_default_radio->print_hdr(s_mesh_default_radio, frame, dst, src);
frame[h] = type, frame[h + 1] = burst;
}
static bool mgi_mesh_b_control_send(struct mg_mesh_node *node, uint16_t dst,
uint8_t command) {
struct mg_tcpip_if *ifp = node->radio;
uint8_t frame[MGI_JOIN_UP_LEN] = {0};
size_t len = MG_MESH_FRAME_HDR_LEN;
mgi_mesh_b_frame_header(frame, dst, node->radio_address, command, 0);
if (command == MGI_JOIN_UP) {
memcpy(frame + MG_MESH_FRAME_HDR_LEN, node->radio->mac, 6);
len = MGI_JOIN_UP_LEN;
}
return node->now >= node->tx_at && node->tx(ifp, frame, len) == len;
}
static size_t mgi_mesh_b_fragment_capacity(void) {
return s_mesh_default_radio->max_frame_size - MG_MESH_FRAME_HDR_LEN;
}
static uint64_t mgi_mesh_b_burst_time(size_t len) {
size_t cap = mgi_mesh_b_fragment_capacity();
size_t frames = len == 0 ? 1 : (len + cap - 1) / cap;
return (frames - 1) *
(mgi_mesh_b_airtime(s_mesh_default_radio->max_frame_size) +
s_mesh_default_radio->ifs_us) +
mgi_mesh_b_airtime(MG_MESH_FRAME_HDR_LEN + len - (frames - 1) * cap);
}
static void mgi_mesh_b_tx_finish(struct mgi_mesh_b_data *data) {
if (data->tx_offset != 0) {
if (data->fanout != NULL) data->fanout = data->fanout->next;
if (data->fanout == NULL) {
size_t len = mg_queue_next(&data->tx, NULL);
mg_queue_del(&data->tx, len);
}
}
data->tx_offset = 0;
data->tx_at = (uint64_t) -1;
}
static void mgi_mesh_b_tx_step(struct mgi_mesh_b_data *data, uint64_t now) {
struct mg_mesh_node *node = data->node;
struct mg_tcpip_if *radio = node->radio;
uint8_t empty[MG_MESH_FRAME_HDR_LEN], *frame = empty;
size_t len = MG_MESH_FRAME_HDR_LEN, record_len = 0;
uint16_t dst;
uint8_t command;
if (now < data->tx_at) return;
if (now >= data->deadline) {
mgi_mesh_b_tx_finish(data);
return;
}
dst = node->lan == NULL ? data->coordinator : data->current->radio_address;
command = node->lan == NULL ? MGI_DATA_UP : MGI_DATA_DOWN;
if (data->tx_offset != 0) {
char *record;
uint8_t *part;
record_len = mg_queue_next(&data->tx, &record);
part = (uint8_t *) record + data->tx_offset;
len = part[0];
frame = part + 1;
s_mesh_default_radio->print_hdr(s_mesh_default_radio, frame, dst,
node->radio_address);
} else {
mgi_mesh_b_frame_header(frame, dst, node->radio_address, command, 0);
}
if (now < data->node->tx_at || data->node->tx(radio, frame, len) != len) {
data->tx_at = now + MGI_TX_RETRY_US;
return;
}
if (data->tx_offset == 0 || (data->tx_offset += len + 1) == record_len) {
mgi_mesh_b_tx_finish(data);
} else {
data->tx_at = now + mgi_mesh_b_airtime(len) + s_mesh_default_radio->ifs_us;
}
}
static void mgi_mesh_b_tx_begin(struct mgi_mesh_b_data *data, bool burst,
uint64_t now, uint64_t deadline) {
data->phase = MGI_TURN_DATA;
data->tx_offset = burst ? MGI_TX_DST_LEN : 0;
data->tx_at = now;
data->deadline = deadline;
}
// Return the assembled length, zero for an empty frame, or -1 if incomplete
// or invalid. Full non-final fragments make the write offset derivable.
static int mgi_mesh_b_data_receive(struct mgi_mesh_b_data *data,
const uint8_t *frame, size_t len,
uint64_t now) {
unsigned burst = frame[MG_MESH_FRAME_HDR_LEN - 1], index = burst >> 4,
last = burst & 15;
size_t chunk = len - MG_MESH_FRAME_HDR_LEN,
cap = mgi_mesh_b_fragment_capacity();
size_t offset = index * cap;
if (index == 0) {
data->rx_expected = (uint8_t) burst;
if (data->node->lan == NULL)
data->deadline = now + mgi_mesh_b_burst_time(MGI_ETH_MAX) +
2 * s_mesh_default_radio->ifs_us + MGI_JOIN_TURN_US;
}
if (last >= MGI_MAX_FRAGMENTS || index > last || burst != data->rx_expected ||
chunk > cap || now > data->deadline || (index < last && chunk != cap) ||
(last != 0 && chunk == 0) || offset + chunk > sizeof(data->rx_buf)) {
data->rx_expected = 0xff;
return -1;
}
memcpy(data->rx_buf + offset, frame + MG_MESH_FRAME_HDR_LEN, chunk);
if (index < last) {
data->rx_expected = (uint8_t) (burst + 0x10);
return -1;
}
data->rx_expected = 0xff;
return offset + chunk == 0 || offset + chunk >= 14 ? (int) (offset + chunk)
: -1;
}
// Bridge scheduling and Ethernet forwarding.
#define MGI_MAX_MISSES 3 // Turns in a row without an answer: remove the member
static uint16_t mgi_mesh_b_join_field(const uint8_t *p) {
return (uint16_t) ((unsigned) p[0] * 256 + p[1]);
}
// Return chaddr for an Ethernet DHCP server reply, even when broadcast.
static const uint8_t *mgi_mesh_b_dhcp_client(const uint8_t *frame, size_t len) {
size_t ip_len, ihl, udp_len;
const uint8_t *udp, *dhcp;
if (len < 34 || mgi_mesh_b_join_field(frame + 12) != 0x0800 ||
(frame[14] >> 4) != 4 || frame[23] != 17 ||
(mgi_mesh_b_join_field(frame + 20) & 0x3fff) != 0)
return NULL;
ip_len = mgi_mesh_b_join_field(frame + 16);
ihl = (size_t) (frame[14] & 15) * 4;
if (ihl < 20 || ip_len > len - 14 || ip_len < ihl + 8 + 240) return NULL;
udp = frame + 14 + ihl;
udp_len = mgi_mesh_b_join_field(udp + 4);
if (mgi_mesh_b_join_field(udp) != 67 ||
mgi_mesh_b_join_field(udp + 2) != 68 || udp_len < 8 + 240 ||
udp_len > ip_len - ihl)
return NULL;
dhcp = udp + 8;
if (dhcp[0] != 2 || dhcp[1] != 1 || dhcp[2] != 6 ||
memcmp(dhcp + 236, "\x63\x82\x53\x63", 4) != 0)
return NULL;
return dhcp + 28;
}
static size_t mgi_mesh_b_enqueue(struct mg_mesh_node *node, const void *buf,
size_t len) {
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) node->data;
const uint8_t *frame = (const uint8_t *) buf;
if (node->lan == NULL)
return mgi_mesh_b_queue(node, &data->tx, buf, len, data->coordinator,
MGI_DATA_UP);
if (len >= 14 && len <= MGI_ETH_MAX) {
const uint8_t *client = mgi_mesh_b_dhcp_client(frame, len);
const uint8_t *dst = client == NULL ? frame : client;
bool multicast = client == NULL && (frame[0] & 1) != 0;
struct mgi_mesh_b_peer *peer;
for (peer = data->members; peer != NULL; peer = peer->next) {
if (multicast || memcmp(dst, peer->mac, 6) == 0) {
uint16_t addr = multicast ? 0xffffu : peer->radio_address;
return mgi_mesh_b_queue(node, &data->tx, buf, len, addr, MGI_DATA_DOWN);
}
}
}
return 0;
}
static void mgi_mesh_b_bridge_queue_lan(struct mg_mesh_node *bridge) {
struct mg_queue *rx = &bridge->lan->recv_queue;
char *buf;
size_t len;
while ((len = mg_queue_next(rx, &buf)) > 0) {
bridge->strategy->tx(bridge, buf, len);
mg_queue_del(rx, len);
}
}
static void mgi_mesh_b_bridge_rx(uint64_t now, struct mg_mesh_node *b,
const uint8_t *frame, size_t len) {
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) b->data;
struct mgi_mesh_b_peer *peer;
uint16_t dst = 0, src = 0;
unsigned command = mgi_mesh_b_frame_parse(frame, len, &dst, &src);
if (command == MGI_DATA_UP && dst == b->radio_address &&
data->phase == MGI_TURN_DATA && data->current != NULL &&
src == data->current->radio_address && now <= data->deadline) {
int received = mgi_mesh_b_data_receive(data, frame, len, now);
if (received < 0) return;
if (received != 0) {
b->lan->driver->tx(data->rx_buf, (size_t) received, b->lan);
}
data->current->misses = 0;
data->current = data->current->next;
data->phase = MGI_TURN_IDLE;
data->deadline = now;
return;
}
if (len != MGI_JOIN_UP_LEN || command != MGI_JOIN_UP ||
dst != b->radio_address || data->phase != MGI_TURN_JOIN ||
now >= data->deadline) {
return;
}
if (src == 0 || src == 0xffffu) return;
for (peer = data->members; peer != NULL; peer = peer->next) {
if (peer->radio_address == src) break;
}
if (peer == NULL) {
peer = (struct mgi_mesh_b_peer *) calloc(1, sizeof(*peer));
if (peer == NULL) return;
peer->radio_address = src;
peer->next = data->members;
data->members = peer;
}
memcpy(peer->mac, frame + MG_MESH_FRAME_HDR_LEN, sizeof(peer->mac));
}
// Member peer did not answer its turn. After MGI_MAX_MISSES in a row, it is
// gone: remove it, and its queued downlink frames
static void mgi_mesh_b_member_missed(struct mgi_mesh_b_data *data,
struct mgi_mesh_b_peer *peer) {
if (++peer->misses < MGI_MAX_MISSES) return;
MG_INFO(("Member %M removed: no answer %d times", mg_print_mac, peer->mac,
MGI_MAX_MISSES));
if (data->fanout == peer) {
data->fanout = peer->next;
if (data->fanout == NULL) {
size_t len = mg_queue_next(&data->tx, NULL);
mg_queue_del(&data->tx, len);
}
}
LIST_DELETE(struct mgi_mesh_b_peer, &data->members, peer);
free(peer);
}
// A broadcast occupies one record and is sent once to each member in list
// order.
static size_t mgi_mesh_b_bridge_burst(struct mgi_mesh_b_data *data,
struct mgi_mesh_b_peer *peer) {
char *record;
size_t len;
while ((len = mg_queue_next(&data->tx, &record)) != 0) {
uint16_t dst = mgi_mesh_b_join_field((const uint8_t *) record);
struct mgi_mesh_b_peer *member;
if (dst == 0xffffu) {
if (data->fanout == NULL) data->fanout = data->members;
return data->fanout == peer ? len : 0;
}
if (dst == peer->radio_address) return len;
for (member = data->members; member != NULL; member = member->next) {
if (member->radio_address == dst) return 0;
}
mg_queue_del(&data->tx, len); // Its destination has left.
}
return 0;
}
static uint64_t mgi_mesh_b_queued_burst_time(struct mgi_mesh_b_data *data,
size_t len) {
char *record;
size_t frames;
if (len == 0) return mgi_mesh_b_burst_time(0);
mg_queue_next(&data->tx, &record);
// Fragment zero carries the final index. Count overhead and IFS per frame.
frames =
((uint8_t) record[MGI_TX_DST_LEN + MG_MESH_FRAME_HDR_LEN] & 15u) + 1u;
return mgi_mesh_b_airtime(len - MGI_TX_DST_LEN - frames +
(frames - 1) * s_mesh_default_radio->overhead) +
(frames - 1) * s_mesh_default_radio->ifs_us;
}
static void mgi_mesh_b_bridge_io(uint64_t now, struct mg_mesh_node *b) {
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) b->data;
mgi_mesh_b_bridge_queue_lan(b);
if (now >= data->deadline) {
if (data->tx_at != (uint64_t) -1) mgi_mesh_b_tx_finish(data);
if (data->phase == MGI_TURN_DATA && data->current != NULL) {
struct mgi_mesh_b_peer *peer = data->current;
data->current = peer->next;
mgi_mesh_b_member_missed(data, peer);
} else if (data->phase == MGI_TURN_JOIN) {
data->current = data->members;
}
data->phase = MGI_TURN_IDLE;
if (data->current != NULL) {
size_t burst = mgi_mesh_b_bridge_burst(data, data->current);
uint64_t down = mgi_mesh_b_queued_burst_time(data, burst);
data->rx_expected = 0xff;
mgi_mesh_b_tx_begin(data, burst != 0, now,
now + down + mgi_mesh_b_burst_time(MGI_ETH_MAX) +
2 * s_mesh_default_radio->ifs_us +
MGI_JOIN_TURN_US);
} else if (mgi_mesh_b_control_send(b, 0xffffu, MGI_JOIN_DOWN)) {
data->phase = MGI_TURN_JOIN;
data->deadline = now + MGI_JOIN_WINDOW_US;
}
if (data->deadline <= now) data->deadline = now + MGI_JOIN_TURN_US;
}
}
static void mgi_mesh_b_bridge_free(struct mgi_mesh_b_data *data) {
while (data->members != NULL) {
struct mgi_mesh_b_peer *peer = data->members;
data->members = peer->next;
free(peer);
}
}
static void mgi_mesh_b_node_rx(struct mg_mesh_node *n, uint64_t now,
uint8_t *frame, size_t len) {
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) n->data;
uint16_t dst = 0, src = 0;
unsigned command = mgi_mesh_b_frame_parse(frame, len, &dst, &src);
if (command == MGI_JOIN_DOWN && len == MG_MESH_FRAME_HDR_LEN &&
dst == 0xffffu) {
if (n->joined && src == data->coordinator &&
++data->missed_rounds >= MGI_MAX_MISSES) {
n->joined = false;
data->phase = MGI_TURN_IDLE;
data->tx_at = (uint64_t) -1;
data->tx_offset = 0;
data->rx_expected = 0xff;
}
if (!n->joined && (mgi_mesh_b_random(n, data) % 64) == 0) {
// Spread contenders across windows as well as within the reply window.
data->coordinator = src;
data->phase = MGI_TURN_JOIN;
data->tx_at = now + MGI_JOIN_TURN_US +
mgi_mesh_b_random(n, data) % MGI_JOIN_JITTER_US;
}
} else if (command == MGI_DATA_DOWN && data->coordinator != 0 &&
dst == n->radio_address && src == data->coordinator) {
int received = mgi_mesh_b_data_receive(data, frame, len, now);
if (received < 0) return;
n->joined = true;
data->missed_rounds =
-1; // This round had a grant; do not count it as missed.
if (received != 0) {
struct mg_tcpip_if *ifp = n->radio;
if (n->rx_len != 0 || (size_t) received > ifp->recv_queue.size) {
ifp->ndrop++;
} else {
memcpy(ifp->recv_queue.buf, data->rx_buf, (size_t) received);
n->rx_len = (size_t) received;
}
}
if (data->tx_at != (uint64_t) -1) mgi_mesh_b_tx_finish(data);
mgi_mesh_b_tx_begin(data, mg_queue_next(&data->tx, NULL) != 0, now,
now + mgi_mesh_b_burst_time(MGI_ETH_MAX) +
2 * s_mesh_default_radio->ifs_us +
MGI_JOIN_TURN_US);
}
}
static void mgi_mesh_b_input(struct mg_mesh_node *node, void *buf, size_t len) {
uint64_t ready = node->now + node->strategy->radio->turnaround_us;
if (node->tx_at < ready) node->tx_at = ready;
node->next_tick = node->now;
if (node->lan != NULL)
mgi_mesh_b_bridge_rx(node->now, node, (const uint8_t *) buf, len);
else
mgi_mesh_b_node_rx(node, node->now, (uint8_t *) buf, len);
}
static void mgi_mesh_b_tick(struct mg_mesh_node *node, uint64_t now) {
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) node->data;
node->now = now;
if (node->lan != NULL) {
mgi_mesh_b_bridge_io(now, node);
mgi_mesh_b_tx_step(data, now);
node->next_tick = data->deadline;
if (data->tx_at < node->next_tick) node->next_tick = data->tx_at;
return;
}
if (data->phase == MGI_TURN_JOIN) {
if (data->tx_at <= now) {
if (now - data->tx_at <= MGI_JOIN_TURN_US && !node->joined)
mgi_mesh_b_control_send(node, data->coordinator, MGI_JOIN_UP);
data->tx_at = (uint64_t) -1;
data->phase = MGI_TURN_IDLE;
}
} else {
mgi_mesh_b_tx_step(data, now);
}
node->next_tick = data->tx_at;
}
static bool mgi_mesh_b_init(struct mg_mesh_node *node,
struct mg_tcpip_if *radio, struct mg_tcpip_if *lan,
uint16_t radio_address) {
struct mgi_mesh_b_data *data =
(struct mgi_mesh_b_data *) calloc(1, sizeof(*data));
memset(node, 0, sizeof(*node));
node->radio = radio;
node->lan = lan;
node->radio_address = radio_address;
node->next_tick = (uint64_t) -1;
node->ram_used = node->ram_peak = sizeof(*node);
if (data == NULL) return false;
node->ram_used += sizeof(*data);
node->ram_peak = node->ram_used;
data->rx_expected = 0xff;
node->data = data;
data->node = node;
data->tx_at = (uint64_t) -1;
mg_queue_init(&data->tx, data->tx_buf, sizeof(data->tx_buf));
if (lan != NULL) node->next_tick = 0;
return true;
}
static void mgi_mesh_b_free(struct mg_mesh_node *node) {
struct mgi_mesh_b_data *data = (struct mgi_mesh_b_data *) node->data;
if (data == NULL) return;
mgi_mesh_b_bridge_free(data);
free(data);
node->data = NULL;
}
const struct mg_mesh mg_mesh_default = {
"default",
mgi_mesh_b_init,
mgi_mesh_b_tick,
mgi_mesh_b_free,
mgi_mesh_b_enqueue,
mgi_mesh_b_input,
true,
true,
&mg_radio_802154,
};
#endif