feat: Meshtastic TX — device announces as a named node (messageable)
The device now transmits a NodeInfo (User protobuf: id/long_name/short_name) on the LongFast primary channel every 60s, so it appears in other Meshtastic clients as 'Reticutasticore' / 'RTC'. NodeNum derived from the MAC (!4359d2d0). No public key advertised, so DMs to us fall back to channel encryption — which the existing RX path already decodes and displays. Confirmed on hardware: announce tx=0 (success) on the first T-window. Also spec the planned message-received animation (upside-down spinning smiley + lightning bolts -> Meshtastic-style modal) in the README. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a284c91848
commit
d731bfd7fa
@ -53,6 +53,13 @@ pio run -t upload --upload-port /dev/cu.usbserial-0001 # flash (adjust port)
|
||||
pio device monitor -b 115200 # watch the scan
|
||||
```
|
||||
|
||||
## Planned: message-received animation
|
||||
|
||||
When a text message arrives, play a short intro before showing it: the striped
|
||||
smiley spins **upside-down / rotated**, little lightning bolts flash, then a
|
||||
Meshtastic-style **message modal** slides in with the sender + text. (Spec'd,
|
||||
not yet built.)
|
||||
|
||||
## Roadmap from here
|
||||
|
||||
1. **RX decode per protocol** — turn logged raw packets into `{sender, text}`
|
||||
|
||||
@ -24,6 +24,8 @@
|
||||
#include <Wire.h>
|
||||
#include <math.h>
|
||||
#include "mbedtls/aes.h"
|
||||
#include "esp_mac.h"
|
||||
#include "esp_random.h"
|
||||
|
||||
// --- Heltec V3 SX1262 pin map (from the Heltec V3 schematic) ----------------
|
||||
static const int PIN_LORA_NSS = 8;
|
||||
@ -98,6 +100,12 @@ static char lastMsg[40] = {0};
|
||||
static char lastMsgNet = ' ';
|
||||
static uint32_t lastMsgMs = 0;
|
||||
|
||||
// Meshtastic node identity (this device's presence on the mesh).
|
||||
static uint32_t nodeNum = 0;
|
||||
static char nodeId[12] = {0}; // "!xxxxxxxx"
|
||||
static uint32_t lastAnnounceMs = 0;
|
||||
static const uint32_t ANNOUNCE_INTERVAL_MS = 60000; // re-announce every 60s
|
||||
|
||||
// --- RX interrupt -----------------------------------------------------------
|
||||
static volatile bool rxFlag = false;
|
||||
ICACHE_RAM_ATTR static void onDio1() { rxFlag = true; }
|
||||
@ -186,6 +194,57 @@ static bool decodeMeshtastic(const uint8_t* pkt, size_t len, char* out, size_t o
|
||||
return false;
|
||||
}
|
||||
|
||||
// AES-128-CTR (symmetric, so same call encrypts) with the Meshtastic default key.
|
||||
static void mtCryptCtr(uint32_t sender, uint32_t pid, const uint8_t* in, uint8_t* out, size_t n) {
|
||||
uint8_t nonce[16]; memset(nonce, 0, 16);
|
||||
memcpy(nonce + 0, &pid, 4);
|
||||
memcpy(nonce + 8, &sender, 4);
|
||||
mbedtls_aes_context aes; mbedtls_aes_init(&aes);
|
||||
mbedtls_aes_setkey_enc(&aes, MT_KEY, 128);
|
||||
size_t nc_off = 0; uint8_t stream[16]; memset(stream, 0, 16);
|
||||
mbedtls_aes_crypt_ctr(&aes, n, &nc_off, nonce, stream, in, out);
|
||||
mbedtls_aes_free(&aes);
|
||||
}
|
||||
|
||||
// Announce this device as a Meshtastic node named DEVICE_NAME. Broadcasts a
|
||||
// NodeInfo (User protobuf) on the LongFast primary channel (hash 0x08) so it
|
||||
// appears in other Meshtastic clients — and, because we advertise no public
|
||||
// key, DMs to us fall back to channel encryption, which we can already decode.
|
||||
static void announceMeshtastic() {
|
||||
// User protobuf: field1 id, field2 long_name, field3 short_name
|
||||
uint8_t user[64]; size_t u = 0;
|
||||
size_t idl = strlen(nodeId), lnl = strlen(DEVICE_NAME), snl = strlen(DEVICE_SHORT);
|
||||
user[u++] = 0x0a; user[u++] = idl; memcpy(user + u, nodeId, idl); u += idl;
|
||||
user[u++] = 0x12; user[u++] = lnl; memcpy(user + u, DEVICE_NAME, lnl); u += lnl;
|
||||
user[u++] = 0x1a; user[u++] = snl; memcpy(user + u, DEVICE_SHORT, snl); u += snl;
|
||||
|
||||
// Data protobuf: portnum=4 (NODEINFO_APP), payload=User
|
||||
uint8_t data[96]; size_t d = 0;
|
||||
data[d++] = 0x08; data[d++] = 0x04; // portnum = 4
|
||||
data[d++] = 0x12; data[d++] = (uint8_t)u; memcpy(data + d, user, u); d += u;
|
||||
|
||||
uint32_t pid = esp_random();
|
||||
uint8_t enc[96];
|
||||
mtCryptCtr(nodeNum, pid, data, enc, d);
|
||||
|
||||
// 16-byte header + ciphertext
|
||||
uint8_t pkt[128]; uint32_t dest = 0xffffffff;
|
||||
memcpy(pkt + 0, &dest, 4);
|
||||
memcpy(pkt + 4, &nodeNum, 4);
|
||||
memcpy(pkt + 8, &pid, 4);
|
||||
pkt[12] = 0x63; // flags: hop_limit=3, hop_start=3
|
||||
pkt[13] = 0x08; // channel hash (LongFast primary)
|
||||
pkt[14] = 0x00; // next_hop
|
||||
pkt[15] = 0x00; // relay_node
|
||||
memcpy(pkt + 16, enc, d);
|
||||
|
||||
radio.standby();
|
||||
int st = radio.transmit(pkt, 16 + d);
|
||||
Serial.printf(" [T] announce '%s' (%s) id=0x%08x -> tx=%d\n",
|
||||
DEVICE_NAME, nodeId, pid, st);
|
||||
radio.startReceive();
|
||||
}
|
||||
|
||||
static void drainPacket() {
|
||||
size_t len = radio.getPacketLength();
|
||||
uint8_t buf[256];
|
||||
@ -380,6 +439,14 @@ void setup() {
|
||||
}
|
||||
radio.setDio2AsRfSwitch(true);
|
||||
radio.setDio1Action(onDio1);
|
||||
radio.setOutputPower(14); // EU868-safe TX power for announces
|
||||
|
||||
// Derive this device's Meshtastic NodeNum from the last 4 bytes of the MAC.
|
||||
uint8_t mac[6]; esp_read_mac(mac, ESP_MAC_WIFI_STA);
|
||||
nodeNum = ((uint32_t)mac[2] << 24) | ((uint32_t)mac[3] << 16) |
|
||||
((uint32_t)mac[4] << 8) | mac[5];
|
||||
snprintf(nodeId, sizeof(nodeId), "!%08x", nodeNum);
|
||||
Serial.printf("Meshtastic node: %s (%s)\n", DEVICE_NAME, nodeId);
|
||||
|
||||
activeIdx = 0;
|
||||
applyConfig(CONFIGS[0]);
|
||||
@ -399,6 +466,12 @@ void loop() {
|
||||
drainPacket();
|
||||
radio.startReceive();
|
||||
}
|
||||
// Announce ourselves as a Meshtastic node while tuned to that PHY.
|
||||
if (CONFIGS[activeIdx].letter == 'T' &&
|
||||
(lastAnnounceMs == 0 || millis() - lastAnnounceMs > ANNOUNCE_INTERVAL_MS)) {
|
||||
announceMeshtastic();
|
||||
lastAnnounceMs = millis();
|
||||
}
|
||||
drawUI();
|
||||
frame++;
|
||||
delay(45); // ~20 fps
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user