Files
meshcore_c/README.md
T
2026-06-08 13:37:27 +10:00

179 lines
7.6 KiB
Markdown

# MeshCoreCompanion
A client library for the **MeshCore Companion Radio** serial protocol, for when
the radio lives on one MCU and your application (display, sensor hub, gateway)
lives on another. Talk to a separate companion radio — e.g. a Seeed XIAO +
Wio-SX1262 — over a UART/USB serial link and exchange channel messages, set
channel PSKs, and read link metadata.
This is the inverse of the phone/web app: your host MCU plays the *client*, the
companion radio plays the *server*.
## Design: portable C core + Arduino wrapper
Two layers, so the protocol is reusable and testable far beyond Arduino:
- **`meshcore_companion.h/.c`** — a portable **C99** core. No I/O, no `malloc`,
no Arduino. It assembles inbound frames from a byte stream, builds outbound
command frames into buffers you own, and parses payloads into plain structs.
It compiles and runs on a desktop for unit testing (see `test/`), and drops
into ESP-IDF, bare nRF52/STM32, or a host bridge unchanged.
- **`MeshCoreCompanion.h/.cpp`** — a thin C++ wrapper. Inject any `Stream`
(`Serial1` on a Grove UART, USB CDC, `SoftwareSerial`), call `loop()` often,
register lambda callbacks. It also **auto-drains** the radio's queue: on the
`MsgWaiting` push it loops `SyncNextMessage` until empty, so you just receive
`onChannelMessage(...)`.
Transport is injected; the core never touches a port. "Physical serial only" is
simply which `Stream` you hand the wrapper.
XXX Add a why - because I want control from other embedded systems, talk about how I do compile meshcore directly in and juse use a SX??? lora modem, that however uses a lot of resources and memroy and some hardware doesn't have access to enough pins to do SPI. Also I write code that runs on low level embedded linux SBC or SOC that are dependent on C.
## Wire protocol
`frame = [type:u8][len:u16 LE][payload:len bytes]`, type `0x3C` for app→radio,
`0x3E` for radio→app. `payload[0]` is the command/response/push code. Derived
from the MeshCore companion protocol and the `meshcore.js` reference client.
## Install (PlatformIO)
Drop this folder into your project's `lib/`, or add to `platformio.ini`:
```ini
lib_deps = symlink:///path/to/MeshCoreCompanion
```
The companion radio must be flashed with the **serial** companion variant
(`companion_radio_usb`) and have its serial interface bound to the UART you wire
to — not BLE, and not the WiFi/TCP variant. Companions compile in one interface
at a time. If you are using an OS (e.g. Linux) you can use USB variant as the drivers will present a /dev/ttyX interface that looks same as serial. If wanting to use bare metal evices such as AVR/ESP32/nRF52/STM serial, then see notes below about changes to radio.
## Quick start
```cpp
#include "MeshCoreCompanion.h"
MeshCoreCompanion mc(Serial1);
void setup() {
Serial1.begin(115200, SERIAL_8N1, /*RX*/16, /*TX*/17);
mc.onDeviceInfo([](const mc_device_info_t& d){
mc.setChannelHexSecret(2, "sensors", "000102...0f"); // 32 hex chars
mc.getDeviceTime();
});
mc.onChannelMessage([](const mc_channel_msg_t& m){
// m.text is "SenderName: body"
});
mc.onChannelData([](const mc_channel_data_t& d){
// d.data / d.data_len, plus MC_SNR_DB(d.snr_q4), d.path_len
});
mc.begin(); // AppStart + DeviceQuery
}
void loop() {
mc.loop(); // pump + auto-drain
// mc.sendChannelText(2, "hello");
}
```
## Linux / host build (CMake)
The portable C core needs no Arduino and no hardware — it builds and runs on any
desktop. A `CMakeLists.txt` at the repo root builds the core, the host unit test,
and the Linux examples (the Arduino C++ wrapper is skipped here — it needs
`<Arduino.h>`):
```sh
cmake -B build && cmake --build build
ctest --test-dir build --output-on-failure # run the codec unit test
```
### Linux examples (`examples-linux/`)
Both talk to a companion radio over **any tty** — a USB-CDC device
(`/dev/ttyACM0`), a USB-serial adapter (`/dev/ttyUSB0`), or a raw UART
(`/dev/serial0`):
- **`tty_bridge`** — live: receive/echo channel traffic and send messages.
```sh
./build/meshcore_tty /dev/ttyACM0
```
- **`info`** — one-shot: dump everything the radio reports (model, firmware,
radio params, device time, channels + PSKs, stats), then exit.
```sh
./build/meshcore_info /dev/ttyACM0
```
No CMake? Compile a single example directly:
```sh
cc -std=c99 -Wall -Wextra -Isrc \
examples-linux/info/meshcore_info.c src/meshcore_companion.c -o meshcore_info
```
The same C core also ships starter examples for other toolchains in
`examples-esp-idf/` (ESP-IDF UART driver) and `examples-stm32/` (STM32 HAL).
## What's covered
| Need | API |
|------|-----|
| Handshake / identity | `begin()`, `appStart()`, `deviceQuery()` → `onDeviceInfo`, `onSelfInfo` |
| Receive channel text | `onChannelMessage` (auto-drained) |
| Receive channel data + metadata | `onChannelData` (SNR, path length, data type) |
| Send on a channel | `sendChannelText(idx, text)` |
| Set / read channel PSK | `setChannel`, `setChannelHexSecret`, `getChannel` → `onChannelInfo` |
| Device time | `getDeviceTime`, `setDeviceTime`, `deviceEpochNow()` |
| Adverts | `sendSelfAdvert(flood)` |
| Radio params / stats | `getStats`, plus `mc_cmd_set_radio_params` in the core |
The C core also exposes raw command builders and `mc_parse` for codes the
wrapper doesn't surface yet (contacts, battery, send-confirmed, etc.) — adding a
new command is one builder plus one `case`.
## Testing the core (no hardware)
Run everything at once with `./testall.sh` — it runs the CMake/ctest, PlatformIO
native, and Arduino-compile suites, and auto-skips whichever tools aren't
installed (so it works on a cmake-only or PlatformIO-only box):
```sh
./testall.sh
```
Or run a suite individually:
```sh
ctest --test-dir build --output-on-failure # via CMake (see above)
pio test -e native # via PlatformIO's native env
cd test && cc -std=c99 -Wall -Wextra -I../src test_codec.c ../src/meshcore_companion.c -o t && ./t
```
The unit test exercises command encoding, frame reassembly across split reads,
every parser, and resync past line garbage.
## Raw Serial Port
For embedded linux, windows etc, there is USB drivvers that expose a serial port we can access from the raw c. But from a bare metal (or FreeRTOS etc) embedded CPU we need TTL serial port access. To do this is relatively simple on most of the MeshCore hardware supported for Companion mode, however can't be done from online flasher.
XXX How to build example, lets start with a XIAO ESP32S3 with SX1262. Minimum steps here to show somone how to do it and work out what pins can be used on that board for RX/TX
And add a Arduino example (basic ESP32S3 dev) that also programs it, e.g. Name, Network to Australia Narrow band and to add a channel. Basically a zero config just plug it in and the host device sets it up how it needs. For completeness this examle code then loops watching the channel programmed and responding to any hello sent with a response.
## Notes
- **Timestamps:** channel messages carry an epoch-seconds sender timestamp.
Call `getDeviceTime()` (or `setDeviceTime()`) once so `sendChannelText` can
stamp outgoing messages; otherwise it sends `0`.
- **PSK derivation:** `setChannel` takes a raw 16-byte secret. If you need to
derive one from a passphrase, do it host-side (e.g. SHA-256 prefix, as
`meshcore.js`'s transport-key util shows) — kept out of the core to stay
dependency-free.
- **Buffer sizes:** override `MC_MAX_PAYLOAD`, `MC_MAX_TEXT`, `MC_MAX_DATA`
before including the header if your traffic needs more.
## License
MIT. Protocol derived from MeshCore and `meshcore.js` (MIT, © Liam Cottle).