New info example, overall testing helpers. Improved readme

This commit is contained in:
Scott Penrose
2026-06-08 13:20:25 +10:00
parent ce3e5cad3f
commit 372c177479
12 changed files with 935 additions and 331 deletions
+182
View File
@@ -21,6 +21,21 @@ static size_t make_inbound(uint8_t *out, const uint8_t *payload, size_t plen) {
return plen + 3;
}
/* little-endian writers for building test payloads */
static void le16(uint8_t *p, uint16_t v) { p[0] = (uint8_t)v; p[1] = (uint8_t)(v >> 8); }
static void le32(uint8_t *p, uint32_t v) {
p[0] = (uint8_t)v; p[1] = (uint8_t)(v >> 8); p[2] = (uint8_t)(v >> 16); p[3] = (uint8_t)(v >> 24);
}
/* Parse a freshly-built payload through the rx assembler; returns mc_parse result. */
static int feed_parse(mc_rx_t *rx, uint8_t *frame, uint8_t *scratch, size_t scap,
const uint8_t *payload, size_t plen, mc_event_t *ev) {
size_t flen = make_inbound(frame, payload, plen), olen = 0;
mc_rx_feed(rx, frame, flen);
if (!mc_rx_poll(rx, scratch, scap, &olen)) return 0;
return mc_parse(scratch, olen, ev);
}
int main(void) {
uint8_t scratch[512], frame[512], payload[300];
size_t plen, flen, olen;
@@ -110,6 +125,173 @@ int main(void) {
ev.u.channel_data.data_len == 4 && ev.u.channel_data.data[0] == 0xDE &&
ev.u.channel_data.data[3] == 0xEF, "channel_data payload");
printf("== parse: MSG_SENT ==\n");
{
size_t j = 0;
payload[j++] = MC_RESP_SENT;
payload[j++] = 1; /* type */
le32(payload + j, 0x11223344); j += 4; /* expected_ack */
le32(payload + j, 5000); j += 4; /* suggested_timeout */
CHECK(feed_parse(&rx, frame, scratch, sizeof scratch, payload, j, &ev) == 1 &&
ev.code == MC_RESP_SENT, "parse msg_sent");
CHECK(ev.u.msg_sent.type == 1 && ev.u.msg_sent.expected_ack == 0x11223344u &&
ev.u.msg_sent.suggested_timeout == 5000u, "msg_sent fields");
}
printf("== parse: STATS (core/radio/packets) ==\n");
{
size_t j = 0;
payload[j++] = MC_RESP_STATS; payload[j++] = MC_STATS_CORE;
le16(payload + j, 4200); j += 2; /* battery_mv */
le32(payload + j, 86400); j += 4; /* uptime_secs */
le16(payload + j, 3); j += 2; /* errors */
payload[j++] = 7; /* queue_len */
CHECK(feed_parse(&rx, frame, scratch, sizeof scratch, payload, j, &ev) == 1 &&
ev.u.stats.subtype == MC_STATS_CORE && ev.u.stats.u.core.battery_mv == 4200 &&
ev.u.stats.u.core.uptime_secs == 86400 && ev.u.stats.u.core.errors == 3 &&
ev.u.stats.u.core.queue_len == 7, "stats core");
j = 0;
payload[j++] = MC_RESP_STATS; payload[j++] = MC_STATS_RADIO;
le16(payload + j, (uint16_t)(int16_t)-120); j += 2; /* noise_floor i16 */
payload[j++] = (uint8_t)(int8_t)-90; /* last_rssi i8 */
payload[j++] = 40; /* last_snr_q4 */
le32(payload + j, 1000); j += 4; /* tx_air_secs */
le32(payload + j, 2000); j += 4; /* rx_air_secs */
CHECK(feed_parse(&rx, frame, scratch, sizeof scratch, payload, j, &ev) == 1 &&
ev.u.stats.subtype == MC_STATS_RADIO && ev.u.stats.u.radio.noise_floor == -120 &&
ev.u.stats.u.radio.last_rssi == -90 && ev.u.stats.u.radio.last_snr_q4 == 40 &&
ev.u.stats.u.radio.tx_air_secs == 1000 && ev.u.stats.u.radio.rx_air_secs == 2000,
"stats radio (signed fields)");
j = 0;
payload[j++] = MC_RESP_STATS; payload[j++] = MC_STATS_PACKETS;
le32(payload + j, 10); j += 4; le32(payload + j, 20); j += 4;
le32(payload + j, 1); j += 4; le32(payload + j, 2); j += 4;
le32(payload + j, 3); j += 4; le32(payload + j, 4); j += 4;
le32(payload + j, 5); j += 4; /* recv_errors */
CHECK(feed_parse(&rx, frame, scratch, sizeof scratch, payload, j, &ev) == 1 &&
ev.u.stats.u.packets.recv == 10 && ev.u.stats.u.packets.direct_rx == 4 &&
ev.u.stats.has_recv_errors && ev.u.stats.u.packets.recv_errors == 5,
"stats packets (+recv_errors)");
}
printf("== parse: SELF_INFO extended fields ==\n");
{
size_t j = 0;
payload[j++] = MC_RESP_SELF_INFO;
payload[j++] = 1; payload[j++] = 22; payload[j++] = 30; /* type, txp, maxtxp */
for (int i = 0; i < 32; i++) payload[j++] = (uint8_t)i; /* public_key */
le32(payload + j, (uint32_t)(int32_t)-37000000); j += 4; /* adv_lat */
le32(payload + j, (uint32_t)(int32_t)145000000); j += 4; /* adv_lon */
payload[j++] = 1; /* multi_acks */
payload[j++] = 2; /* adv_loc_policy */
payload[j++] = 0x36; /* telemetry_mode: base=2 loc=1 env=3 */
payload[j++] = 1; /* manual_add_contacts */
le32(payload + j, 915000); j += 4; /* radio_freq */
le32(payload + j, 250000); j += 4; /* radio_bw */
payload[j++] = 11; /* radio_sf */
payload[j++] = 5; /* radio_cr */
memcpy(payload + j, "node", 4); j += 4;
CHECK(feed_parse(&rx, frame, scratch, sizeof scratch, payload, j, &ev) == 1 &&
ev.code == MC_RESP_SELF_INFO, "parse self_info");
CHECK(ev.u.self_info.multi_acks == 1 && ev.u.self_info.adv_loc_policy == 2 &&
ev.u.self_info.telemetry_mode == 0x36 && ev.u.self_info.tm_base == 2 &&
ev.u.self_info.tm_loc == 1 && ev.u.self_info.tm_env == 3,
"self_info multi_acks/loc_policy/telemetry split");
CHECK(ev.u.self_info.adv_lat == -37000000 && ev.u.self_info.adv_lon == 145000000 &&
ev.u.self_info.manual_add_contacts == 1 && ev.u.self_info.radio_freq == 915000 &&
ev.u.self_info.radio_bw == 250000 && ev.u.self_info.radio_sf == 11 &&
ev.u.self_info.radio_cr == 5 && strcmp(ev.u.self_info.name, "node") == 0,
"self_info numeric + name");
}
printf("== parse: CHANNEL_MSG_RECV_V3 (SNR) + base sentinel ==\n");
{
size_t j = 0;
payload[j++] = MC_RESP_CHANNEL_MSG_RECV_V3;
payload[j++] = 40; /* SNR q4 */
payload[j++] = 0; payload[j++] = 0; /* reserved */
payload[j++] = 2; /* channel_idx */
payload[j++] = MC_PATH_DIRECT; /* path_len */
payload[j++] = MC_TXT_PLAIN; /* txt_type */
le32(payload + j, 1700000000u); j += 4; /* sender_ts */
memcpy(payload + j, "Alice: hi", 9); j += 9;
CHECK(feed_parse(&rx, frame, scratch, sizeof scratch, payload, j, &ev) == 1 &&
ev.code == MC_RESP_CHANNEL_MSG_RECV_V3 && ev.u.channel_msg.snr_q4 == 40 &&
ev.u.channel_msg.channel_idx == 2 && ev.u.channel_msg.path_len == MC_PATH_DIRECT &&
ev.u.channel_msg.sender_ts == 1700000000u &&
strcmp(ev.u.channel_msg.text, "Alice: hi") == 0, "channel_msg_v3 fields + SNR");
j = 0;
payload[j++] = MC_RESP_CHANNEL_MSG_RECV;
payload[j++] = 0; /* channel_idx */
payload[j++] = 0; /* path_len */
payload[j++] = MC_TXT_PLAIN; /* txt_type */
le32(payload + j, 1); j += 4; memcpy(payload + j, "B: yo", 5); j += 5;
CHECK(feed_parse(&rx, frame, scratch, sizeof scratch, payload, j, &ev) == 1 &&
ev.u.channel_msg.snr_q4 == MC_SNR_NONE && strcmp(ev.u.channel_msg.text, "B: yo") == 0,
"channel_msg base has MC_SNR_NONE");
}
printf("== parse: CONTACT_MSG_RECV_V3 + signature skip ==\n");
{
size_t j = 0;
payload[j++] = MC_RESP_CONTACT_MSG_RECV_V3;
payload[j++] = (uint8_t)(int8_t)-8; /* SNR q4 */
payload[j++] = 0; payload[j++] = 0; /* reserved */
for (int i = 0; i < 6; i++) payload[j++] = (uint8_t)(0xC0 + i); /* pubkey_prefix */
payload[j++] = 1; /* path_len */
payload[j++] = MC_TXT_SIGNED_PLAIN; /* txt_type=2 */
le32(payload + j, 1700000001u); j += 4; /* sender_ts */
payload[j++]=0xAA; payload[j++]=0xBB; payload[j++]=0xCC; payload[j++]=0xDD; /* signature */
memcpy(payload + j, "signed!", 7); j += 7;
CHECK(feed_parse(&rx, frame, scratch, sizeof scratch, payload, j, &ev) == 1 &&
ev.code == MC_RESP_CONTACT_MSG_RECV_V3 && ev.u.contact_msg.snr_q4 == -8 &&
ev.u.contact_msg.txt_type == MC_TXT_SIGNED_PLAIN && ev.u.contact_msg.has_signature &&
ev.u.contact_msg.signature[0] == 0xAA && ev.u.contact_msg.signature[3] == 0xDD &&
ev.u.contact_msg.pubkey_prefix[0] == 0xC0 &&
strcmp(ev.u.contact_msg.text, "signed!") == 0,
"contact_msg_v3 signed: SNR/sig/text");
}
printf("== parse: DEVICE_INFO with ver/repeat/path_hash (fw=10) ==\n");
{
size_t j = 0;
payload[j++] = MC_RESP_DEVICE_INFO;
payload[j++] = 10; /* fw_ver */
payload[j++] = 50; /* max_contacts/2 */
payload[j++] = 8; /* max_channels */
le32(payload + j, 123456); j += 4; /* ble_pin */
memset(payload + j, 0, 12); memcpy(payload + j, "1 Jan 2026", 10); j += 12;
memset(payload + j, 0, 40); memcpy(payload + j, "Heltec V3", 9); j += 40;
memset(payload + j, 0, 20); memcpy(payload + j, "v1.7.0", 6); j += 20;
payload[j++] = 1; /* repeat (fw>=9) */
payload[j++] = 2; /* path_hash_mode (fw>=10) */
CHECK(feed_parse(&rx, frame, scratch, sizeof scratch, payload, j, &ev) == 1 &&
ev.u.device_info.fw_ver == 10 && ev.u.device_info.max_contacts == 100 &&
strcmp(ev.u.device_info.model, "Heltec V3") == 0 &&
strcmp(ev.u.device_info.ver, "v1.7.0") == 0 &&
ev.u.device_info.have_repeat && ev.u.device_info.repeat == 1 &&
ev.u.device_info.have_path_hash && ev.u.device_info.path_hash_mode == 2,
"device_info ver/repeat/path_hash");
}
printf("== build: send_txt_msg / send_cmd ==\n");
{
uint8_t dst[6] = { 1, 2, 3, 4, 5, 6 };
plen = mc_cmd_send_txt_msg(scratch, sizeof scratch, MC_TXT_PLAIN, 0, 1700000000u, dst, 6, "hi");
CHECK(plen == 1 + 1 + 1 + 4 + 6 + 2 && scratch[0] == MC_CMD_SEND_TXT_MSG &&
scratch[1] == MC_TXT_PLAIN && scratch[2] == 0, "send_txt_msg header");
CHECK(scratch[3] == 0x00 && scratch[4] == 0xF1 && scratch[5] == 0x53 && scratch[6] == 0x65 &&
scratch[7] == 1 && scratch[12] == 6 && scratch[13] == 'h' && scratch[14] == 'i',
"send_txt_msg ts LE + dst + body");
plen = mc_cmd_send_cmd(scratch, sizeof scratch, 1700000000u, dst, 6, "reboot");
CHECK(plen == 1 + 1 + 1 + 4 + 6 + 6 && scratch[0] == MC_CMD_SEND_TXT_MSG &&
scratch[1] == MC_TXT_CLI_DATA && scratch[2] == 0 && scratch[13] == 'r',
"send_cmd uses CLI txt_type");
}
printf("== resync: garbage before a valid frame ==\n");
uint8_t junk[3] = { 0x00, 0x99, 0x01 };
mc_rx_feed(&rx, junk, 3);