Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ not a side effect. Grouped by what pulls them in:
build / release build.sh, platformio.ini
packet filter examples/simple_repeater/MyMesh.{cpp,h}, docs/cli_commands.md
companion filter examples/companion_radio/MyMesh.{cpp,h}, docs/cli_commands.md
companion net examples/companion_radio/{MyMesh.{cpp,h},main.cpp}, build.sh,
variants/*/platformio.ini (SNMP lib_deps), docs/cli_commands.md
region gating src/Dispatcher.{cpp,h}, src/helpers/RegionMap.{cpp,h},
src/helpers/CommonCLI.{cpp,h}, test/mocks/{Arduino,SHA256,Stream}.h
duty-cycle auto src/helpers/CommonRadioPrefs.{cpp,h}, src/helpers/CommonCLI.{cpp,h},
Expand Down
25 changes: 25 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ disable_debug_flags() {

# build firmware for the provided pio env in $1
build_firmware() {
# Per-env -D flags (version, OTA variant) are appended below; restore the caller's
# flags on return so they do not leak into the next env of a multi-env build.
local SAVED_PIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS}"

# get env platform for post build actions
ENV_PLATFORM=($(get_platform_for_env $1))

Expand Down Expand Up @@ -170,6 +174,19 @@ build_firmware() {
# add firmware version info to end of existing platformio build flags in environment vars
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DFIRMWARE_BUILD_DATE='\"${FIRMWARE_BUILD_DATE}\"' -DFIRMWARE_VERSION='\"${EMBEDDED_VERSION_STRING}\"'"

# DMC companion pull OTA (`ota check` / `ota update` / `ota branch`): the device reads
# <base>/<env>.json from the DutchMeshCore OTA server, which mirrors the dmc-companion-*
# releases. Both channel bases are baked in for `ota branch`; the build's own channel
# (dev for DMC_CHANNEL=dev) is the default. Only Wi-Fi capable ESP32 builds use them.
if [[ "$1" =~ _companion_radio_(wifi|ble)$ ]]; then
local OTA_URL="${COMPANION_OTA_URL:-https://ota.dutchmeshcore.nl/companion}"
local OTA_NATIVE="${OTA_URL}/v"
if [ "${DMC_CHANNEL:-}" == "dev" ]; then
OTA_NATIVE="${OTA_URL}/dev/v"
fi
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DCOMPANION_OTA_VARIANT='\"$1\"' -DCOMPANION_OTA_BASE='\"${OTA_NATIVE}\"' -DCOMPANION_OTA_BASE_STABLE='\"${OTA_URL}/v\"' -DCOMPANION_OTA_BASE_DEV='\"${OTA_URL}/dev/v\"'"
fi

# disable debug flags if requested
disable_debug_flags

Expand All @@ -181,6 +198,13 @@ build_firmware() {
pio run -t mergebin -e $1
cp .pio/build/$1/firmware.bin out/${FIRMWARE_FILENAME}.bin 2>/dev/null || true
cp .pio/build/$1/firmware-merged.bin out/${FIRMWARE_FILENAME}-merged.bin 2>/dev/null || true
# Partition-table signature for the OTA server's manifest (partSig): `ota update`
# refuses an image built for a different partition layout than the device has.
if [[ "$1" =~ _companion_radio_(wifi|ble)$ ]] && [ -f ".pio/build/$1/partitions.bin" ]; then
if ! python3 scripts/partition_signature.py ".pio/build/$1/partitions.bin" > "out/$1.partsig"; then
rm -f "out/$1.partsig"
fi
fi
fi

# build .uf2 for nrf52 boards, copy .uf2 and .zip to out folder (e.g: RAK_4631_Repeater-v1.0.0-SHA.uf2)
Expand All @@ -202,6 +226,7 @@ build_firmware() {
cp .pio/build/$1/firmware.uf2 out/${FIRMWARE_FILENAME}.uf2 2>/dev/null || true
fi

export PLATFORMIO_BUILD_FLAGS="${SAVED_PIO_BUILD_FLAGS}"
}

# firmwares containing $1 will be built
Expand Down
14 changes: 14 additions & 0 deletions dmc-docs/custom-cli.de.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ App aus. Wird über die Befehlskonsole der App, USB-Seriell oder Remote-CLI gese
Mit aktiviertem Repeat werden malformed/age/path-Nachrichten auch nicht weitergeleitet;
sender/text-Regeln blenden eine Nachricht nur für dich aus, sie wird für andere weiterhin weitergeleitet.

## Companion SNMP, OTA und MAC

Auf **Companion**-Builds, die sich mit WLAN verbinden können (ESP32 `*_companion_radio_wifi` und
die ESP32-`*_companion_radio_ble`-Boards mit WLAN). Gespeichert in `/cnet.json`. Siehe
[`docs/companion_snmp_ota_reference.md`](https://github.com/Dutch-MeshCore/MeshCore/blob/dmc-dev/docs/companion_snmp_ota_reference.md).

- `get snmp` / `set snmp on|off` (standardmäßig **aus**; UDP 161, nur lesend, OID-Layout des Observers)
- `get snmp.status` / `get snmp.community` / `set snmp.community <Text>` (Standard `public`)
- `ota check` / `ota update` / `ota status` (DutchMeshCore-OTA-Server, HTTPS)
- `ota branch` / `ota branch stable|dev|default`
- `get mac` / `get wifi.mac` / `get ble.mac` (jeder ESP32-Build)

Kein OTA auf Boards mit 4 MB Flash (ThinkNode M2/M5, Xiao C3, LilyGo T-LoRa V2.1). Eine neue Partitionstabelle oder ein Board ohne zweiten App-Slot braucht weiterhin einen USB-Flash.

## Duty-Cycle-Region-Gating

`dc.gate.*`. Standardmäßig **aus**. Wirft Inter-Regions-Flood-Verkehr ab, wenn der
Expand Down
14 changes: 14 additions & 0 deletions dmc-docs/custom-cli.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ messages are never filtered. Saved to `/cfilter.json`. See
With repeat enabled, malformed/age/path messages are not repeated either; sender/text rules only
hide a message from you and it is still repeated for others.

## Companion SNMP, OTA and MAC

On **companion** builds that can join Wi-Fi (ESP32 `*_companion_radio_wifi`, and the ESP32
`*_companion_radio_ble` boards with Wi-Fi). Saved to `/cnet.json`. See
[`docs/companion_snmp_ota_reference.md`](https://github.com/Dutch-MeshCore/MeshCore/blob/dmc-dev/docs/companion_snmp_ota_reference.md).

- `get snmp` / `set snmp on|off` (**off** by default; UDP 161, read-only, observer OID layout)
- `get snmp.status` / `get snmp.community` / `set snmp.community <text>` (default `public`)
- `ota check` / `ota update` / `ota status` (DutchMeshCore OTA server, HTTPS)
- `ota branch` / `ota branch stable|dev|default`
- `get mac` / `get wifi.mac` / `get ble.mac` (every ESP32 build)

No OTA on 4 MB flash boards (ThinkNode M2/M5, Xiao C3, LilyGo T-LoRa V2.1). A new partition table, or a board without a second app slot, still needs a USB flash.

## Duty-cycle region gating

`dc.gate.*`. **Off** by default. Sheds inter-region flood traffic when the repeater has
Expand Down
14 changes: 14 additions & 0 deletions dmc-docs/custom-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ directe berichten worden nooit gefilterd. Opgeslagen in `/cfilter.json`. Zie
Met repeat aan worden malformed/age/path-berichten ook niet doorgestuurd; sender/text-regels
verbergen een bericht alleen voor jou en het wordt nog steeds doorgestuurd voor anderen.

## Companion SNMP, OTA en MAC

Op **companion**-builds die met wifi kunnen verbinden (ESP32 `*_companion_radio_wifi`, en de
ESP32 `*_companion_radio_ble`-boards met wifi). Opgeslagen in `/cnet.json`. Zie
[`docs/companion_snmp_ota_reference.md`](https://github.com/Dutch-MeshCore/MeshCore/blob/dmc-dev/docs/companion_snmp_ota_reference.md).

- `get snmp` / `set snmp on|off` (standaard **uit**; UDP 161, alleen lezen, OID-indeling van de observer)
- `get snmp.status` / `get snmp.community` / `set snmp.community <tekst>` (standaard `public`)
- `ota check` / `ota update` / `ota status` (DutchMeshCore OTA-server, HTTPS)
- `ota branch` / `ota branch stable|dev|default`
- `get mac` / `get wifi.mac` / `get ble.mac` (elke ESP32-build)

Geen OTA op boards met 4 MB flash (ThinkNode M2/M5, Xiao C3, LilyGo T-LoRa V2.1). Een nieuwe partitietabel, of een board zonder tweede app-slot, heeft nog steeds een USB-flash nodig.

## Duty-cycle region gating

`dc.gate.*`. Standaard **uit**. Werpt inter-regio-floodverkeer af wanneer de repeater
Expand Down
21 changes: 21 additions & 0 deletions docs/cli_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore
- [Information](#info)
- [Packet Filter](#packet-filter-repeater-only)
- [Companion Receive Filter](#companion-receive-filter-companion-only)
- [Companion SNMP, OTA and MAC](#companion-snmp-ota-and-mac-companion-only)
- [Configuration](#configuration)
- [Radio](#radio)
- [System](#system)
Expand Down Expand Up @@ -346,6 +347,26 @@ Find more information in [companion_filter_reference.md](companion_filter_refere

---

## Companion SNMP, OTA and MAC (Companion Only)

DMC companion builds that can join Wi-Fi (ESP32 `*_companion_radio_wifi`, and the ESP32 `*_companion_radio_ble` boards with Wi-Fi) have an SNMP agent and pull OTA from the DutchMeshCore OTA server. Sent through the app's command console, USB serial or remote CLI. Settings are persisted to `/cnet.json`.

Find more information in [companion_snmp_ota_reference.md](companion_snmp_ota_reference.md)

**Usage:**
- `get snmp` / `set snmp <on|off>` — read-only SNMPv2c agent on UDP 161, off by default; same OID layout as the observer firmware
- `get snmp.status` — `off`, `on, waiting for Wi-Fi` or `running on udp/161`
- `get snmp.community` / `set snmp.community <text>` — default `public`; not readable over remote CLI
- `ota check` — is a newer build published for this board?
- `ota update` — check, then download, flash and reboot (HTTPS, verified)
- `ota status` — `idle`, `starting` or the last failure
- `ota branch` / `ota branch <stable|dev|default>` — the OTA channel
- `get mac` / `get wifi.mac` / `get ble.mac` — factory MAC addresses (every ESP32 build; `n/a` elsewhere)

**Note:** An update with a different partition table, or on a board without a second app slot, needs a USB flash. Boards with 4 MB flash (ThinkNode M2/M5, Xiao C3, LilyGo T-LoRa V2.1) have no OTA.

---

## Configuration

### Radio
Expand Down
146 changes: 146 additions & 0 deletions docs/companion_snmp_ota_reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# MeshCore Companion SNMP, OTA and MAC

## Overview

DMC-only network extras in the companion firmware:

* **SNMP**: a read-only SNMPv2c agent on UDP 161, with the same OID layout as the
DMC observer firmware, so one poller template (LibreNMS, Zabbix, RTFM-EV) reads
companions and observers alike.
* **OTA**: `ota check` / `ota update` pull the published build for this board from
the DutchMeshCore OTA server (`ota.dutchmeshcore.nl/companion`), the same server
the observer firmware updates from.
* **MAC**: `get mac`, `get wifi.mac`, `get ble.mac` show the factory MAC addresses,
for DHCP reservations and MAC filtering.

Commands are sent through the app's command console (`CMD_RUN_CLI_COMMAND`), USB
serial, or remote CLI. Settings are saved to `/cnet.json`.

---

# Which builds have what

SNMP and OTA need a Wi-Fi network, so they exist on ESP32 builds that can join one:

| Build | SNMP | OTA | MAC |
| --- | --- | --- | --- |
| ESP32 `*_companion_radio_wifi` | yes | yes | yes |
| ESP32 `*_companion_radio_wifi` with 4 MB flash (ThinkNode M2, ThinkNode M5, Xiao C3, LilyGo T-LoRa V2.1) | yes | no | yes |
| ESP32 `*_companion_radio_ble` with Wi-Fi (Heltec V3, WSL3, V4, V4 R8, Wireless Paper, RAK3112, ThinkNode M7, Xiao S3, Xiao S3 WIO) | yes | yes | yes |
| Other ESP32 `*_companion_radio_ble` | no | no | yes |
| nRF52, RP2040 | no | no | `n/a` |

OTA needs a TLS stack (~180 KB of flash) that the 4 MB boards have no room for, so
they set `DMC_COMPANION_NO_OTA` and are updated over USB.

On the BLE builds with Wi-Fi, Wi-Fi is off until it is set up:
`set wifi.ssid <name>`, `set wifi.pwd <password>`, `set wifi.enabled 1` (a number:
`on` reads as 0 and turns Wi-Fi off), then reboot.

---

# SNMP

Off by default. Until `set snmp on` the agent is never started, so it costs no RAM.
When on, it runs whenever Wi-Fi has an address and stops while the link is down.

| Command | |
| --- | --- |
| `get snmp` | `on` or `off` |
| `set snmp on\|off` | start or stop the agent (applies live) |
| `get snmp.status` | `off`, `on, waiting for Wi-Fi` or `running on udp/161` |
| `get snmp.community` | the community string (default `public`); not over remote CLI |
| `set snmp.community <text>` | 1 to 32 characters, applies to the next request |

The community string is the only access control: change it from `public` on any
network you do not fully trust. The agent is read-only.

## OIDs

Base `.1.3.6.1.4.1.99999` (the same temporary enterprise number as the observer).
Shared numbers keep the observer's meaning, type and unit. Anything the companion
has no data for is not registered and reads `noSuchObject`, never a fake 0.

| OID | Type | Value |
| --- | --- | --- |
| `.1.1.0` | Integer | uptime (s) |
| `.1.2.0` | String | firmware version |
| `.1.3.0` | String | node name |
| `.1.4.0` | Integer | battery (mV), read every 30 s |
| `.1.5.0` | Integer | MCU temperature (°C), `-127` when the board has no sensor |
| `.1.6.0` | Integer | reset reason code |
| `.1.7.0` | String | reset reason |
| `.2.1.0` - `.2.3.0` | Integer | packets received, sent, receive errors |
| `.2.4.0` | Integer | noise floor (dBm) |
| `.2.5.0` | Integer | last RSSI (dBm) |
| `.2.6.0` | Integer | last SNR × 4 |
| `.2.7.0` - `.2.10.0` | Integer | sent flood, sent direct, received flood, received direct |
| `.2.11.0` | Integer | TX air time (s) |
| `.2.12.0` | Integer | TX queue length |
| `.2.13.0`, `.2.14.0` | Counter32 | flood, direct duplicates |
| `.2.15.0` | Counter32 | RX air time (s) |
| `.2.17.0` | Integer | error flags (1 queue full, 2 CAD timeout, 4 start RX timeout) |
| `.4.1.0` - `.4.5.0` | Integer | free heap, largest block, free internal, free PSRAM, lowest free heap |
| `.5.1.0` | Integer | Wi-Fi RSSI (dBm), `-127` when not connected |
| `.9.1.0` | Integer | app connected over Wi-Fi (0/1) |
| `.9.3.0` | Integer | contacts |
| `.9.4.0` | Integer | channels |
| `.10.1.0` | Integer | receive filter on (0/1) |
| `.10.2.0` - `.10.6.0` | Counter32 | receive filter: malformed, age, path, sender, text |

Not registered: `.2.16` (TX duty cycle), `.3` (MQTT and NTP), `.6` (duty gate),
`.7` (repeater packet filter), `.8` (neighbours), `.9.2` (meshcomod WebSocket clients).

```
snmpwalk -v2c -c public <companion-ip> .1.3.6.1.4.1.99999
```

---

# OTA

| Command | |
| --- | --- |
| `ota check` | is a newer build published? |
| `ota update` | check, then download, flash and reboot |
| `ota status` | `idle`, `starting`, or the last failure |
| `ota branch` | the channel in use |
| `ota branch stable\|dev\|default` | pull from the stable or dev channel; `default` = the channel the build was made for |

`ota update` answers straight away, then starts the download a few seconds later so
the reply still reaches the app. Progress is printed on USB serial; the device
reboots into the new build when it is written. A failure leaves the running build
in place, and `ota status` shows why.

How it works:

1. The device reads `<base>/<env>.json`, where `<base>` is
`https://ota.dutchmeshcore.nl/companion/v` (stable) or `.../companion/dev/v`
(dev) and `<env>` is the PlatformIO env name baked in by `build.sh`. The server
builds these manifests from the `dmc-companion-*` releases.
2. Versions compare numerically (`v1.18.0` < `v1.18.1`). The same version from
another commit counts as an update (a newer dev build). It never downgrades.
3. `ota check` reads the manifest over plain HTTP. `ota update` reads it again over
HTTPS and downloads the image over HTTPS. Both are verified against a short list
of root CAs in `CompanionOtaCerts.h` (regenerate with
`scripts/gen_companion_ota_certs.py`).
4. The image must sit under the same product's `fw/` path on the server.
5. The update is refused, and needs a USB flash, when the new build has a different
partition table (the manifest's `partSig` against the flashed table) or the
board has no second app slot.

A build made without `build.sh` has no server bases and answers
`OTA not configured`.

---

# MAC

| Command | Reply |
| --- | --- |
| `get mac` | `> wifi AA:BB:CC:DD:EE:FF, ble AA:BB:CC:DD:EE:01` |
| `get wifi.mac` | Wi-Fi station MAC |
| `get ble.mac` | Bluetooth MAC |

Read from the chip's eFuse, so they answer with Wi-Fi or BLE off. Non-ESP32 builds
reply `n/a`.
Loading
Loading