Skip to content

Instantly share code, notes, and snippets.

@NeoCat
Created May 16, 2026 11:04
Show Gist options
  • Select an option

  • Save NeoCat/ddaa47dcd33c4ed57349e34d65071bab to your computer and use it in GitHub Desktop.

Select an option

Save NeoCat/ddaa47dcd33c4ed57349e34d65071bab to your computer and use it in GitHub Desktop.
Send text message to mestastic primary channel from Arduino MKR WAN 1310 LoRa using meshtastic-lite library
// AES from Crypto library (https://github.com/rweather/arduinolibs)
#include <AES.h>
// arduino-LoRa library (https://github.com/sandeepmistry/arduino-LoRa)
// Firmware must be written using MKRWANFWUpdate_standalone example in https://github.com/arduino-libraries/MKRWAN, not v2!)
#include <LoRa.h>
// meshtastic-lite library (https://github.com/jstockdale/meshtastic-lite)
// A fix may be required to send messages longer than 12B (https://github.com/jstockdale/meshtastic-lite/pull/1)
#include <meshtastic.h>
#include <string.h>
static AES128 g_aes;
static uint8_t g_cached_key[16];
static bool g_key_set = false;
extern "C" void mesh_aes_block_encrypt(const uint8_t *key, int key_bits,
const uint8_t in[16], uint8_t out[16])
{
(void)key_bits;
if (!g_key_set || memcmp(key, g_cached_key, 16) != 0) {
g_aes.setKey(key, 16);
memcpy(g_cached_key, key, 16);
g_key_set = true;
}
g_aes.encryptBlock(out, in);
}
// ─── Configuration ──────────────────────────────────────────────────────────
static const char TX_TEXT[] = "Test message from Arduino MKR1310 (meshtastic-lite)";
static const uint32_t TX_INTERVAL_MS = 30000UL; // 30 s between transmissions
// ─── Globals ────────────────────────────────────────────────────────────────
static MeshSession session;
static uint8_t tx_frame[255];
static unsigned long last_tx_ms;
// ─── SAMD21 node number from unique serial ──────────────────────────────────
// SAMD21 has a 128-bit serial spread across four non-contiguous NVM words:
// word 0 @ 0x0080A00C, word 3 @ 0x0080A048
// XOR gives a stable 32-bit value suitable as a Meshtastic node number.
static uint32_t getNodeNum() {
const uint32_t w0 = *reinterpret_cast<const volatile uint32_t *>(0x0080A00Cu);
const uint32_t w3 = *reinterpret_cast<const volatile uint32_t *>(0x0080A048u);
return w0 ^ w3;
}
// ─── Send one Meshtastic text broadcast ─────────────────────────────────────
static void sendMessage() {
size_t len = session.buildTextTx(
0, MESH_ADDR_BROADCAST, TX_TEXT, false, false, tx_frame);
if (len == 0) { Serial.println("buildTextTx failed"); return; }
if (!LoRa.beginPacket()) { Serial.println("beginPacket failed"); return; }
LoRa.write(tx_frame, len);
LoRa.endPacket();
Serial.print("TX "); Serial.print(len); Serial.println(" bytes OK");
}
// ─── Setup ──────────────────────────────────────────────────────────────────
void setup() {
Serial.begin(115200);
while (!Serial && millis() < 3000); // wait up to 3 s for USB console
// Derive node number from SAMD21 hardware unique serial
uint32_t node_num = getNodeNum();
Serial.print("Node num : 0x"); Serial.println(node_num, HEX);
// Seed packet-ID generator with hardware entropy
srand(node_num ^ (uint32_t)analogRead(A0));
// Initialise Meshtastic session — JP LongFast, default PSK channel (AES-128)
session.init(REGION_JP, MODEM_LONG_FAST, ROLE_CLIENT, node_num);
session.channels.addDefaultChannel();
// Derive all radio parameters from the session (frequency, SF, BW, CR, …)
MeshRadioConfig rc = session.radioConfig();
Serial.print("Frequency : "); Serial.print(rc.frequency_mhz, 3); Serial.println(" MHz");
Serial.print("SF / BW : "); Serial.print(rc.spreading_factor);
Serial.print(" / "); Serial.print((int)rc.bandwidth_khz); Serial.println(" kHz");
Serial.print("TX power : "); Serial.print(rc.tx_power_dbm); Serial.println(" dBm");
// Initialise LoRa radio
if (!LoRa.begin((long)(rc.frequency_mhz * 1.0e6f))) {
Serial.println("LoRa init FAILED — check antenna/module");
while (1);
}
LoRa.setSpreadingFactor(rc.spreading_factor); // 11
LoRa.setSignalBandwidth((long)(rc.bandwidth_khz * 1000.0f)); // 250 000 Hz
LoRa.setCodingRate4(rc.coding_rate); // 5 → 4/5
LoRa.setSyncWord(rc.sync_word); // 0x2B (Meshtastic private)
LoRa.setPreambleLength(rc.preamble_length); // 16 symbols
LoRa.setTxPower(rc.tx_power_dbm); // 13 dBm (JP limit)
LoRa.enableCrc();
Serial.println("LoRa ready — sending first packet now");
sendMessage();
last_tx_ms = millis();
}
// ─── Loop ───────────────────────────────────────────────────────────────────
void loop() {
if (millis() - last_tx_ms >= TX_INTERVAL_MS) {
last_tx_ms = millis();
sendMessage();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment