diff --git a/.github/actions/setup-build-environment/action.yml b/.github/actions/setup-build-environment/action.yml index 3354c260..01c79f2e 100644 --- a/.github/actions/setup-build-environment/action.yml +++ b/.github/actions/setup-build-environment/action.yml @@ -44,6 +44,7 @@ runs: # companion-wifi-v1.15.0 # repeater-bridge-espnow-v1.15.0 # observer-eastmesh-bridge-espnow-v2026.5.1 + # observer-eastmesh-bridge-mqtt-v2026.7.0 # observer-eastmesh-v2026.5.1 # with OFFICIAL_MESHCORE_VERSION=v1.15.0 configured as a GitHub variable. # @@ -85,6 +86,12 @@ runs: exit 1 fi EASTMESH_VERSION="${BASH_REMATCH[1]}" + elif [[ "$GIT_TAG_NAME" =~ ^observer-eastmesh-bridge-mqtt-(v[[:alnum:]._-]+)$ ]]; then + if [[ -z "$STATIC_OFFICIAL_VERSION" ]]; then + echo "OFFICIAL_MESHCORE_VERSION must be set for EastMesh release tags" >&2 + exit 1 + fi + EASTMESH_VERSION="${BASH_REMATCH[1]}" elif [[ "$GIT_TAG_NAME" =~ ^companion-wifi-(v[[:alnum:]._-]+)$ ]]; then FIRMWARE_VERSION="${BASH_REMATCH[1]}" elif [[ "$GIT_TAG_NAME" =~ ^repeater-bridge-espnow-(v[[:alnum:]._-]+)$ ]]; then diff --git a/.github/workflows/eastmesh-build-observer-mqtt-bridge-firmwares.yml b/.github/workflows/eastmesh-build-observer-mqtt-bridge-firmwares.yml new file mode 100644 index 00000000..3b47c631 --- /dev/null +++ b/.github/workflows/eastmesh-build-observer-mqtt-bridge-firmwares.yml @@ -0,0 +1,41 @@ +name: EastMesh Build Observer MQTT Bridge Firmwares + +permissions: + contents: write + +on: + workflow_dispatch: + push: + tags: + - "observer-eastmesh-bridge-mqtt-v*" + +jobs: + build: + runs-on: ubuntu-latest + env: + OFFICIAL_MESHCORE_VERSION: ${{ vars.OFFICIAL_MESHCORE_VERSION }} + PLATFORMIO_BUILD_FLAGS: -UMQTT_DEBUG + steps: + - name: Clone Repo + uses: actions/checkout@v6 + + - name: Setup Build Environment + uses: ./.github/actions/setup-build-environment + + - name: Build Firmwares + run: /usr/bin/env bash eastmesh-build.sh build-observer-mqtt-bridge-firmwares + + - name: Upload Workflow Artifacts + uses: actions/upload-artifact@v7 + with: + name: observer-bridge-mqtt-firmwares + path: out + + - name: Create Release + uses: softprops/action-gh-release@v3 + if: startsWith(github.ref, 'refs/tags/') + with: + name: Observer EastMesh Firmware with MQTT Bridge ${{ env.RELEASE_VERSION }} + body: "" + draft: true + files: out/* diff --git a/AGENTS.md b/AGENTS.md index 56908eaf..c902f7d1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -114,7 +114,7 @@ uv run pio run -e T_Beam_S3_Supreme_SX1262_repeater_observer -t upload --upload- - `eastmesh-build.sh` — EastMesh build wrapper - `build.sh` — upstream MeshCore build wrapper retained for merge hygiene - `platformio.ini` -- `variants/eastmesh_mqtt/platformio.ini` +- `variants/eastmesh/platformio.ini` - `examples/simple_repeater/MyMesh.cpp` - `src/helpers/mqtt/MQTTUplink.cpp` - `eastmesh-docs/*.md` @@ -172,6 +172,7 @@ Current tag formats: git tag companion-wifi-v1.15.0 git tag repeater-bridge-espnow-v1.15.0 git tag observer-eastmesh-bridge-espnow-v2026.5.1 +git tag observer-eastmesh-bridge-mqtt-v2026.7.0 git tag observer-eastmesh-v2026.5.1 ``` @@ -180,8 +181,9 @@ Rules: - `companion-wifi` tags use the upstream MeshCore version directly - `repeater-bridge-espnow` tags use the upstream MeshCore version directly - `observer-eastmesh-bridge-espnow` tags use the EastMesh release version in the tag +- `observer-eastmesh-bridge-mqtt` tags use the EastMesh release version in the tag - `observer-eastmesh` tags use the EastMesh release version in the tag -- GitHub Actions variable `OFFICIAL_MESHCORE_VERSION` supplies the upstream base version for Observer EastMesh and Observer ESP-NOW EastMesh release builds +- GitHub Actions variable `OFFICIAL_MESHCORE_VERSION` supplies the upstream base version for Observer EastMesh, Observer ESP-NOW EastMesh, and Observer MQTT Bridge EastMesh release builds - if the upstream MeshCore release version changes, update `OFFICIAL_MESHCORE_VERSION` in GitHub before cutting release tags Typical release flow: diff --git a/README.md b/README.md index dcadc9a3..635007a8 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ CoreScope () offers visibility into the network, inclu - native WiFi - MQTT over WSS with JWT auth - optional local HTTPS config panel on supported ESP32 targets +- `*_repeater_observer_mqtt_bridge` firmware targets that add a **bidirectional MQTT mesh bridge** to a peer broker (separate from MQTT uplink) +- `*_repeater_observer_espnow` firmware targets that add a local ESP-NOW mesh bridge - `*_companion_radio_wifi` firmware targets for WiFi-connected companion devices - EastMesh-specific release workflows and versioning on top of upstream MeshCore releases - docs and release guidance for EastMesh users instead of the full upstream MeshCore docs set @@ -123,10 +125,12 @@ uv run --group docs zensical build - Python tooling and docs dependencies - [`platformio.ini`](./platformio.ini) - root PlatformIO config and ESP32 helper scripts -- [`variants/eastmesh_mqtt/platformio.ini`](./variants/eastmesh_mqtt/platformio.ini) +- [`variants/eastmesh/platformio.ini`](./variants/eastmesh/platformio.ini) - shared EastMesh observer env definitions - [`examples/simple_repeater/MyMesh.cpp`](./examples/simple_repeater/MyMesh.cpp) - repeater CLI wiring, MQTT command surface, and web allowlist integration +- [`src/helpers/bridges/MQTTBridge.cpp`](./src/helpers/bridges/MQTTBridge.cpp) + - bidirectional MQTT mesh bridge (peer broker TCP, topic `meshcore/bridge/packets`) - [`src/helpers/mqtt/MQTTUplink.cpp`](./src/helpers/mqtt/MQTTUplink.cpp) - MQTT uplink implementation, HTTPS web panel, WSS/JWT handling, and repeater WiFi control - [`examples/companion_radio`](./examples/companion_radio) @@ -159,6 +163,121 @@ uv run --group docs zensical build - owner public key and email - local web panel enablement +### MQTT Mesh Bridge (Observer) + +The `observer-eastmesh-bridge-mqtt` release track (`*_repeater_observer_mqtt_bridge`) bridges **raw mesh packets** between repeaters through a **peer MQTT broker you run** (for example Mosquitto on a LAN PC). This is **not** the same as MQTT uplink to EastMesh/MeshMapper: + +| | MQTT uplink (observer) | MQTT mesh bridge | +| --- | --- | --- | +| Purpose | Publish JSON telemetry to curated brokers | Forward encrypted mesh packets between radios | +| Brokers | `eastmesh-au`, `meshmapper`, custom WSS | Your peer broker (`host:port`, TCP) | +| Topic | `meshcore//...` (per IATA) | `meshcore/bridge/packets` (fixed) | +| Default on bridge builds | Off by default (enable in web panel / CLI) | On when `bridge.enabled` is on | + +**First supported target:** `Xiao_S3_WIO_repeater_observer_mqtt_bridge` + +**Related track:** `observer-eastmesh-bridge-espnow` (`*_repeater_observer_espnow`) uses ESP-NOW instead of MQTT for local bridging. + +Build example: + +```bash +uv run pio run -e Xiao_S3_WIO_repeater_observer_mqtt_bridge +``` + +Flash (update): + +```bash +uv run pio run -e Xiao_S3_WIO_repeater_observer_mqtt_bridge -t upload --upload-port COM10 +``` + +After a **full flash erase**, flash the merged image at `0x0` instead of only `firmware.bin`: + +```bash +uv run pio run -e Xiao_S3_WIO_repeater_observer_mqtt_bridge -t mergebin +uv run pio pkg exec -p tool-esptoolpy -- esptool.py --chip esp32s3 --port COM10 write_flash 0x0 .pio/build/Xiao_S3_WIO_repeater_observer_mqtt_bridge/firmware-merged.bin +``` + +#### Peer Mosquitto broker (LAN) + +Each bridge node connects to the **same** peer broker with the **same** credentials and `bridge.secret`. + +1. Install [Mosquitto](https://mosquitto.org/download/) on a machine reachable from both repeaters (for example `192.168.1.145`). +2. Edit `mosquitto.conf` (Windows service install: `C:\Program Files\Mosquitto\mosquitto.conf`) and add: + +```conf +listener 1883 0.0.0.0 +allow_anonymous false +password_file C:\Program Files\Mosquitto\passwd +``` + +For lab/testing only, `allow_anonymous true` works without a password file. + +3. Create a user (admin CMD): + +```cmd +cd "C:\Program Files\Mosquitto" +mosquitto_passwd -c passwd bridgeuser +``` + +4. Restart the **service** (do not run a second `mosquitto -v` while the service owns port 1883): + +```powershell +Restart-Service mosquitto +``` + +5. Confirm LAN listen and open the firewall: + +```cmd +netstat -ano | findstr ":1883" +``` + +Expect `0.0.0.0:1883`, not only `127.0.0.1:1883`. + +6. Test from another machine: + +```cmd +mosquitto_pub -h 192.168.1.145 -p 1883 -u bridgeuser -P your-password -t test -m hello +mosquitto_sub -h 192.168.1.145 -p 1883 -u bridgeuser -P your-password -t meshcore/bridge/packets -v +``` + +Binary garbage on `meshcore/bridge/packets` is normal — payloads are XOR-encrypted mesh frames, not text. + +#### Repeater configuration + +Configure **both** bridge nodes identically for broker access; use the same `bridge.secret` on every node in the bridge group. + +Serial or web panel CLI: + +```text +set wifi.ssid YourNetwork +set wifi.pwd YourWiFiPassword +set bridge.peer.host 192.168.1.145 +set bridge.peer.port 1883 +set bridge.peer.username bridgeuser +set bridge.peer.password your-password +set bridge.secret your-shared-bridge-secret +``` + +Web panel: **MQTT Settings** → **Mesh bridge peer MQTT** (`host:port`, username, password). The section appears when `get bridge.type` returns `mqtt`. + +Useful checks: + +```text +get bridge.type +get bridge.peer.host +get bridge.enabled +``` + +Default admin password on dev builds is usually `password` unless you changed it. + +Packets use magic `0xC03E`, a Fletcher checksum, XOR encryption with `bridge.secret`, then publish/subscribe on `meshcore/bridge/packets`. Duplicate detection limits loops when both nodes see the same traffic. + +More detail: + +- [Custom CLI — MQTT bridge settings](./eastmesh-docs/custom-cli.md) +- [Web panel — MQTT settings and bridge peer fields](./eastmesh-docs/web-panel.md) +- [Releases — track comparison](./eastmesh-docs/releases.md) + ### Local Web Panel On supported `*_repeater_observer` ESP32 targets, the repeater can expose a local HTTPS config panel over WiFi. @@ -198,6 +317,7 @@ These rescue commands are only available after entering `CLI Rescue`: - `.github/workflows/eastmesh-build-observer-firmwares.yml` - `.github/workflows/eastmesh-build-repeater-bridge-espnow-firmwares.yml` - `.github/workflows/eastmesh-build-observer-espnow-firmwares.yml` +- `.github/workflows/eastmesh-build-observer-mqtt-bridge-firmwares.yml` - `.github/workflows/eastmesh-pr-build-check.yml` - `.github/workflows/eastmesh-push-build-check.yml` - `.github/workflows/eastmesh-github-pages.yml` @@ -210,6 +330,7 @@ The current release workflows intentionally focus only on: - `repeater-bridge-espnow` - `observer-eastmesh` - `observer-eastmesh-bridge-espnow` +- `observer-eastmesh-bridge-mqtt` ## Release Tags @@ -220,6 +341,7 @@ git tag companion-wifi-v1.14.1 git tag repeater-bridge-espnow-v1.15.0 git tag observer-eastmesh-v2026.5.1 git tag observer-eastmesh-bridge-espnow-v2026.5.1 +git tag observer-eastmesh-bridge-mqtt-v2026.7.0 ``` Companion WiFi uses the upstream MeshCore version in the tag. @@ -243,3 +365,4 @@ Current docs pages: - [Download and Flash Releases](./eastmesh-docs/releases.md) - [Build Locally With uv](./eastmesh-docs/local-builds.md) - [Custom CLI Commands](./eastmesh-docs/custom-cli.md) +- [Web Panel](./eastmesh-docs/web-panel.md) diff --git a/RELEASE.md b/RELEASE.md index cec3c0e1..2adab788 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -19,11 +19,13 @@ Push one or more of the following tag formats to trigger the matching firmware r - `companion-wifi-v1.15.0` - `repeater-bridge-espnow-v1.15.0` - `observer-eastmesh-bridge-espnow-v2026.5.1` +- `observer-eastmesh-bridge-mqtt-v2026.7.0` - `observer-eastmesh-v2026.5.1` Use the upstream MeshCore version in `companion-wifi-v1.15.0`. Use the upstream MeshCore version in `repeater-bridge-espnow-v1.15.0`. Use the EastMesh release version in `observer-eastmesh-bridge-espnow-v2026.5.1`. +Use the EastMesh release version in `observer-eastmesh-bridge-mqtt-v2026.7.0`. Use the EastMesh release version in `observer-eastmesh-v2026.5.1`. Each tag triggers a separate workflow: @@ -31,6 +33,7 @@ Each tag triggers a separate workflow: - `companion-wifi-v*` builds companion WiFi firmware - `repeater-bridge-espnow-v*` builds repeater ESP-NOW bridge firmware - `observer-eastmesh-bridge-espnow-v*` builds Observer ESP-NOW firmware +- `observer-eastmesh-bridge-mqtt-v*` builds Observer MQTT bridge firmware - `observer-eastmesh-v*` builds Observer firmware You can push one, or more tags on the same commit, and they will all build separately. @@ -42,6 +45,7 @@ During the GitHub Actions build: - `companion-wifi` uses the version in the tag as `FIRMWARE_VERSION` - `repeater-bridge-espnow` uses the version in the tag as `FIRMWARE_VERSION` - `observer-eastmesh-bridge-espnow` uses `OFFICIAL_MESHCORE_VERSION` as `FIRMWARE_VERSION` and the EastMesh version from the tag as `EASTMESH_VERSION` +- `observer-eastmesh-bridge-mqtt` uses `OFFICIAL_MESHCORE_VERSION` as `FIRMWARE_VERSION` and the EastMesh version from the tag as `EASTMESH_VERSION` - `observer-eastmesh` uses `OFFICIAL_MESHCORE_VERSION` as `FIRMWARE_VERSION` and the EastMesh version from the tag as `EASTMESH_VERSION` The resulting version string depends on the workflow: @@ -49,6 +53,7 @@ The resulting version string depends on the workflow: - `companion-wifi`: `v1.15.0-` - `repeater-bridge-espnow`: `v1.15.0-` - `observer-eastmesh-bridge-espnow`: `v1.15.0-eastmesh-v2026.5.1-` +- `observer-eastmesh-bridge-mqtt`: `v1.15.0-eastmesh-v2026.7.0-` - `observer-eastmesh`: `v1.15.0-eastmesh-v2026.5.1-` Example: @@ -76,8 +81,9 @@ Example: git tag companion-wifi-v1.15.0 git tag repeater-bridge-espnow-v1.15.0 git tag observer-eastmesh-bridge-espnow-v2026.5.1 +git tag observer-eastmesh-bridge-mqtt-v2026.7.0 git tag observer-eastmesh-v2026.5.1 -git push origin companion-wifi-v1.15.0 repeater-bridge-espnow-v1.15.0 observer-eastmesh-bridge-espnow-v2026.5.1 observer-eastmesh-v2026.5.1 +git push origin companion-wifi-v1.15.0 repeater-bridge-espnow-v1.15.0 observer-eastmesh-bridge-espnow-v2026.5.1 observer-eastmesh-bridge-mqtt-v2026.7.0 observer-eastmesh-v2026.5.1 ``` ## Supported Tags @@ -85,4 +91,5 @@ git push origin companion-wifi-v1.15.0 repeater-bridge-espnow-v1.15.0 observer-e - `companion-wifi-v1.15.0` - `repeater-bridge-espnow-v1.15.0` - `observer-eastmesh-bridge-espnow-v2026.5.1` +- `observer-eastmesh-bridge-mqtt-v2026.7.0` - `observer-eastmesh-v2026.5.1` diff --git a/eastmesh-build.sh b/eastmesh-build.sh index 48955a21..b27002c6 100755 --- a/eastmesh-build.sh +++ b/eastmesh-build.sh @@ -37,6 +37,7 @@ Commands: build-repeater-bridge-espnow-firmwares: Build all repeater ESP-NOW bridge firmwares for all build targets. build-observer-firmwares: Build all observer firmwares for all build targets. build-observer-espnow-firmwares: Build all observer ESP-NOW firmwares for all build targets. + build-observer-mqtt-bridge-firmwares: Build all observer MQTT bridge firmwares for all build targets. build-room-server-firmwares: Build all chat room server firmwares for all build targets. Examples: @@ -64,6 +65,9 @@ $ sh eastmesh-build.sh build-observer-firmwares Build all observer ESP-NOW firmwares $ sh eastmesh-build.sh build-observer-espnow-firmwares +Build all observer MQTT bridge firmwares +$ sh eastmesh-build.sh build-observer-mqtt-bridge-firmwares + Build all chat room server firmwares $ sh eastmesh-build.sh build-room-server-firmwares @@ -327,6 +331,12 @@ build_repeater_observer_espnow_firmwares() { } +build_repeater_observer_mqtt_bridge_firmwares() { + + build_all_firmwares_by_suffix "_repeater_observer_mqtt_bridge" + +} + build_room_server_firmwares() { # # build specific room server firmwares @@ -380,6 +390,8 @@ elif [[ $1 == "build-observer-firmwares" ]]; then build_repeater_observer_firmwares elif [[ $1 == "build-observer-espnow-firmwares" ]]; then build_repeater_observer_espnow_firmwares +elif [[ $1 == "build-observer-mqtt-bridge-firmwares" ]]; then + build_repeater_observer_mqtt_bridge_firmwares elif [[ $1 == "build-room-server-firmwares" ]]; then build_room_server_firmwares fi diff --git a/eastmesh-docs/boards.md b/eastmesh-docs/boards.md index 9fff8bc8..a23b3319 100644 --- a/eastmesh-docs/boards.md +++ b/eastmesh-docs/boards.md @@ -26,7 +26,8 @@ For bridge repeaters, think local radio linking between nearby repeaters: - use bridge firmware when two local repeaters need to exchange traffic across different MeshCore radio configs, such as `Australia (Narrow)` and `Australia (Mid)` - pure ESP-NOW bridge repeaters do not need MQTT, the EastMesh web panel, or a screen - prioritise stable power, suitable antennas, and placement over display features -- use `observer-eastmesh-bridge-espnow` only when the same repeater also needs MQTT uplink; otherwise `repeater-bridge-espnow` keeps the role simpler +- use `observer-eastmesh-bridge-espnow` only when the same repeater also needs MQTT uplink and a local ESP-NOW bridge; otherwise `repeater-bridge-espnow` keeps the role simpler +- use `observer-eastmesh-bridge-mqtt` when the same repeater needs MQTT uplink and bidirectional MQTT mesh bridging through a peer broker Other notes: @@ -52,6 +53,7 @@ The tables below are built from the repo's PlatformIO board metadata and variant - `companion-wifi` boards are for app-connected companion devices. - `observer-eastmesh` boards are for Wi-Fi repeaters that publish to MQTT. - `repeater-bridge-espnow` and `observer-eastmesh-bridge-espnow` boards are for local ESP-NOW bridge use between nearby repeaters on different MeshCore radio configs. +- `observer-eastmesh-bridge-mqtt` boards bridge mesh packets through a shared MQTT topic at a peer broker you configure. Bridge firmware is not MQTT-over-WAN or VPN bridging. Use it when two local repeaters need to exchange traffic across radio configs such as `Australia (Narrow)` and `Australia (Mid)`. @@ -107,7 +109,7 @@ If you are deciding between otherwise similar boards, these target-level setting ## `repeater_observer` Boards -This table includes all observer targets currently defined in `variants/eastmesh_mqtt/platformio.ini`. +This table includes all observer targets currently defined in `variants/eastmesh/platformio.ini`. | Target | MCU | RAM | PSRAM | Flash | LoRa | Display | GPS | SD | | ------------------------ | ------- | ------ | ----- | ----- | ------ | --------------- | --- | --- | diff --git a/eastmesh-docs/custom-cli.md b/eastmesh-docs/custom-cli.md index 3a6d1242..dee72e8a 100644 --- a/eastmesh-docs/custom-cli.md +++ b/eastmesh-docs/custom-cli.md @@ -129,7 +129,7 @@ Default servers are `au.pool.ntp.org`, `time.google.com`, and `time.cloudflare.c ### ESP-NOW Bridge Settings For Observer ESP-NOW Builds -These commands are available on `*_repeater_observer_espnow` firmware targets. +These commands are available on `*_repeater_observer_espnow` firmware targets that use the ESP-NOW bridge transport. Bridge commands are for local ESP-NOW bridge use between nearby repeaters, such as linking repeaters on `Australia (Narrow)` and `Australia (Mid)`. They are not MQTT-over-WAN, VPN, or internet bridge controls. @@ -159,6 +159,40 @@ Example: OK ``` +### MQTT Bridge Settings For Observer MQTT Bridge Builds + +These commands are available on `*_repeater_observer_mqtt_bridge` firmware targets (release track `observer-eastmesh-bridge-mqtt`). + +The MQTT bridge forwards raw mesh packets over a shared topic at a peer MQTT broker. It is separate from MQTT uplink publishing to EastMesh or MeshMapper brokers. + +- `get bridge.type`: returns `mqtt` on MQTT bridge builds. +- `get bridge.peer.host`: shows the configured peer MQTT broker host. +- `set bridge.peer.host `: sets the peer MQTT broker host and restarts the bridge. +- `get bridge.peer.port`: shows the configured peer MQTT broker port (defaults to `1883` when unset). +- `set bridge.peer.port `: sets the peer MQTT broker port and restarts the bridge. +- `get bridge.peer.username` / `set bridge.peer.username `: optional peer MQTT broker username. +- `get bridge.peer.password` / `set bridge.peer.password `: optional peer MQTT broker password (`get` returns `set` or `-`, not the stored value). +- `get bridge.secret` / `set bridge.secret `: shared XOR key used by all bridge nodes on the same bridge network. + +Both bridge nodes must use the same peer broker address, port, credentials, and `bridge.secret`. + +On `*_repeater_observer_mqtt_bridge` builds, MQTT uplink brokers are **disabled by default** so the mesh bridge client is not competing with EastMesh/MeshMapper WSS uplink at boot. Enable uplink brokers manually in MQTT Settings when needed. + +Example: + +```text +> set bridge.peer.host 192.168.1.10 +OK +> set bridge.peer.port 1883 +OK +> set bridge.peer.username meshbridge +OK +> set bridge.peer.password your-mqtt-password +OK +> set bridge.secret my-shared-secret +OK +``` + ### Web Panel Controls - `get web` diff --git a/eastmesh-docs/index.md b/eastmesh-docs/index.md index 70c40711..51c91753 100644 --- a/eastmesh-docs/index.md +++ b/eastmesh-docs/index.md @@ -1,17 +1,20 @@ # MeshCore EastMesh Docs -MeshCore-EastMesh keeps the upstream MeshCore firmware intact and publishes four firmware tracks, depending on how the device needs to connect: +MeshCore-EastMesh keeps the upstream MeshCore firmware intact and publishes five firmware tracks, depending on how the device needs to connect: - `companion-wifi`: use this for Wi-Fi-connected companion devices. It stays closest to upstream MeshCore and adds the EastMesh Wi-Fi rescue/configuration commands. - `repeater-bridge-espnow`: use this when you need a plain upstream-style repeater ESP-NOW bridge without MQTT uplink or the EastMesh web panel. - `observer-eastmesh`: use this for a Wi-Fi repeater that should publish to an MQTT broker and, on supported ESP32 boards, offer the optional local web panel for setup and troubleshooting. - `observer-eastmesh-bridge-espnow`: use this when one repeater needs both MQTT uplink and ESP-NOW bridge duties, including bridge channel/secret controls for keeping the bridge aligned with Wi-Fi. +- `observer-eastmesh-bridge-mqtt`: use this when one repeater needs both MQTT uplink and bidirectional MQTT mesh bridging through a peer broker you configure in the web panel or CLI. !!! note "Bridge tracks are local radio bridges" - The bridge tracks are for bridging two nearby repeaters that operate on different MeshCore radio configs, for example one repeater on `Australia (Narrow)` and another on `Australia (Mid)`. + The ESP-NOW bridge tracks are for bridging two nearby repeaters that operate on different MeshCore radio configs, for example one repeater on `Australia (Narrow)` and another on `Australia (Mid)`. - They are not MQTT-over-WAN, VPN, or internet bridge releases. MQTT is still the uplink/visibility path for MQTT firmware; it is not used to tunnel mesh traffic between distant sites. + The MQTT bridge track uses a shared topic at a peer MQTT broker for mesh packet bridging. It is separate from MQTT uplink publishing to EastMesh or MeshMapper. + + Bridge tracks are not MQTT-over-WAN, VPN, or internet tunnel releases. If you want guidance first, start with: diff --git a/eastmesh-docs/local-builds.md b/eastmesh-docs/local-builds.md index b988f1e2..1acd29af 100644 --- a/eastmesh-docs/local-builds.md +++ b/eastmesh-docs/local-builds.md @@ -29,6 +29,7 @@ uv run pio run -e heltec_v4_repeater_observer uv run pio run -e heltec_v4_companion_radio_wifi uv run pio run -e heltec_v4_repeater_bridge_espnow uv run pio run -e heltec_v4_repeater_observer_espnow +uv run pio run -e Xiao_S3_WIO_repeater_observer_mqtt_bridge ``` Flash a target: @@ -82,7 +83,7 @@ This produces versioned artifacts in `out/`. Versioning rule: - `companion-wifi` and `repeater-bridge-espnow` use the upstream MeshCore version as `FIRMWARE_VERSION` -- `observer-eastmesh` and `observer-eastmesh-bridge-espnow` use the upstream MeshCore version as `FIRMWARE_VERSION` plus the EastMesh release version as `EASTMESH_VERSION` +- `observer-eastmesh`, `observer-eastmesh-bridge-espnow`, and `observer-eastmesh-bridge-mqtt` use the upstream MeshCore version as `FIRMWARE_VERSION` plus the EastMesh release version as `EASTMESH_VERSION` ## Supported `repeater_observer` Boards @@ -143,6 +144,20 @@ T_Beam_S3_Supreme_SX1262_repeater_bridge_espnow T_Beam_S3_Supreme_SX1262_repeater_observer_espnow ``` +## Supported `repeater_observer_mqtt_bridge` Boards + +At present only the Xiao S3 WIO observer MQTT bridge target is defined: + +```text +Xiao_S3_WIO_repeater_observer_mqtt_bridge +``` + +List all observer MQTT bridge targets: + +```bash +bash eastmesh-build.sh list | grep '_repeater_observer_mqtt_bridge' +``` + Bridge firmware is for local ESP-NOW bridge use between nearby repeaters. It is not MQTT-over-WAN or VPN bridging. ## Supported `companion_radio_wifi` Boards diff --git a/eastmesh-docs/releases.md b/eastmesh-docs/releases.md index 2c33e1d5..174de088 100644 --- a/eastmesh-docs/releases.md +++ b/eastmesh-docs/releases.md @@ -22,7 +22,7 @@ If you are not sure which track you need, start with `companion-wifi` for app-co ## Pick Your Track -EastMesh publishes four release tracks: +EastMesh publishes five release tracks: | Track | Use it when | Firmware filename suffix | | ----- | ----------- | ------------------------ | @@ -30,12 +30,15 @@ EastMesh publishes four release tracks: | `observer-eastmesh` | You want a repeater with Wi-Fi and MQTT uplink, usually feeding broker visibility such as EastMesh/CoreScope. | `*_repeater_observer` | | `repeater-bridge-espnow` | You want a local ESP-NOW bridge between nearby repeaters, without MQTT uplink or the EastMesh web panel. | `*_repeater_bridge_espnow` | | `observer-eastmesh-bridge-espnow` | You want one repeater to provide both MQTT uplink and local ESP-NOW bridge duties. | `*_repeater_observer_espnow` | +| `observer-eastmesh-bridge-mqtt` | You want one repeater to provide both MQTT uplink and bidirectional MQTT mesh bridging to a peer broker. | `*_repeater_observer_mqtt_bridge` | !!! note "Bridge firmware is not a WAN bridge" - Bridge tracks are for bridging two nearby repeaters that operate on different MeshCore radio configs, for example `Australia (Narrow)` and `Australia (Mid)`. + ESP-NOW bridge tracks are for bridging two nearby repeaters that operate on different MeshCore radio configs, for example `Australia (Narrow)` and `Australia (Mid)`. - They do not use MQTT to tunnel mesh traffic over the internet, WAN links, or VPNs. + The MQTT bridge track forwards mesh packets through a shared topic at a peer MQTT broker you configure. It is separate from MQTT uplink publishing to EastMesh or MeshMapper. + + Bridge tracks do not use MQTT uplink brokers to tunnel mesh traffic over the internet, WAN links, or VPNs. ## Pick The Right Asset @@ -48,6 +51,7 @@ Examples: - `heltec_v4_repeater_observer-v1.15.0-eastmesh-v2026.5.1-abcdef-merged.bin` - `heltec_v4_repeater_bridge_espnow-v1.15.0-abcdef.bin` - `heltec_v4_repeater_observer_espnow-v1.15.0-eastmesh-v2026.5.1-abcdef.bin` +- `Xiao_S3_WIO_repeater_observer_mqtt_bridge-v1.15.0-eastmesh-v2026.7.0-abcdef.bin` The important part is the board/env prefix: @@ -55,6 +59,7 @@ The important part is the board/env prefix: - `*_repeater_observer` - `*_repeater_bridge_espnow` - `*_repeater_observer_espnow` +- `*_repeater_observer_mqtt_bridge` ## Which File To Flash @@ -187,6 +192,19 @@ Typical first steps after flashing: - set `bridge.channel` to match that Wi-Fi channel - set the same `bridge.secret` on every local ESP-NOW bridge node that should talk together +### Observer MQTT Bridge + +`repeater_observer_mqtt_bridge` builds combine the observer role with bidirectional MQTT mesh bridging. + +Typical first steps after flashing: + +- set `wifi.ssid` +- set `wifi.pwd` +- set `mqtt.iata` +- confirm `get mqtt.status` +- set `bridge.peer.host` and `bridge.peer.port` to your peer MQTT broker +- set the same `bridge.secret` on every MQTT bridge node that should talk together + ### Repeater ESP-NOW Bridge `repeater_bridge_espnow` builds are for local ESP-NOW bridge nodes without MQTT uplink. diff --git a/eastmesh-docs/web-panel.md b/eastmesh-docs/web-panel.md index dce014b8..77f84f0f 100644 --- a/eastmesh-docs/web-panel.md +++ b/eastmesh-docs/web-panel.md @@ -255,6 +255,7 @@ This section includes: - `mqtt.email`: owner contact email. - MQTT brokers: **Primary MQTT** and **Secondary MQTT** dropdowns, each selecting one of `eastmesh-au`, `meshmapper`, `Custom`, the retired `letsmesh-eu`/`letsmesh-us`, or `None`. The two slots enforce the two-broker maximum, and a broker chosen in one slot is disabled in the other. - custom MQTT `host:port`, TCP/WSS transport, username, and password fields, shown when `Custom` is selected in either slot. +- mesh bridge peer MQTT `host:port`, optional username and password, shown on MQTT bridge builds (`get bridge.type` returns `mqtt`). This is separate from MQTT uplink brokers and points at the shared peer broker used for bidirectional mesh packet bridging. `UNSET - To be configured` is the default for new observer installs until a real saved value exists. @@ -269,6 +270,7 @@ Notes: - turning off a connected MQTT server publishes retained offline status before the client disconnects - changing `mqtt.iata` away from a configured value publishes retained offline status to the old status topic, restarts connected broker clients, and reconnects under the new topic path - at most two MQTT brokers can be enabled at once +- on MQTT bridge builds, both bridge nodes must use the same peer broker host, port, credentials, and `bridge.secret` ## `/stats` Overview diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index f3e15e37..397fd127 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -533,6 +533,8 @@ uint8_t MyMesh::handleAnonClockReq(const mesh::Identity& sender, uint32_t sender reply_data[8] |= 0x01; // is bridge, type UART #elif WITH_ESPNOW_BRIDGE reply_data[8] |= 0x03; // is bridge, type ESP-NOW +#elif WITH_MQTT_BRIDGE + reply_data[8] |= 0x04; // is bridge, type MQTT #endif if (_prefs.disable_fwd) { // is this repeater currently disabled reply_data[8] |= 0x80; // is disabled @@ -1198,6 +1200,9 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc #if defined(WITH_ESPNOW_BRIDGE) , bridge(&_prefs, _mgr, &rtc) #endif +#if defined(WITH_MQTT_BRIDGE) + , bridge(&_prefs, _mgr, &rtc) +#endif #if defined(WITH_MQTT_UPLINK) , mqtt(rtc, self_id) #endif @@ -1254,6 +1259,9 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.bridge_channel = 1; // channel 1 StrHelper::strncpy(_prefs.bridge_secret, "LVSITANOS", sizeof(_prefs.bridge_secret)); +#if defined(WITH_MQTT_BRIDGE) + _prefs.bridge_peer_port = 1883; +#endif // GPS defaults _prefs.gps_enabled = 0; @@ -3007,6 +3015,8 @@ void MyMesh::loop() { mqtt_status.tx_air_secs = static_cast(getTotalAirTime() / 1000); mqtt_status.rx_air_secs = static_cast(getReceiveAirTime() / 1000); mqtt_status.recv_errors = radio_driver.getPacketsRecvErrors(); + mqtt_status.packets_sent = radio_driver.getPacketsSent(); + mqtt_status.packets_received = radio_driver.getPacketsRecv(); mqtt_status.radio_freq = _prefs.freq; mqtt_status.radio_bw = _prefs.bw; mqtt_status.radio_sf = _prefs.sf; diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 192b937b..b8bd5657 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -23,6 +23,11 @@ #define WITH_BRIDGE #endif +#ifdef WITH_MQTT_BRIDGE +#include "helpers/bridges/MQTTBridge.h" +#define WITH_BRIDGE +#endif + #ifdef WITH_MQTT_UPLINK #include #endif @@ -170,6 +175,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks, public WebPanelComm RS232Bridge bridge; #elif defined(WITH_ESPNOW_BRIDGE) ESPNowBridge bridge; +#elif defined(WITH_MQTT_BRIDGE) + MQTTBridge bridge; #endif #ifdef WITH_MQTT_UPLINK MQTTUplink mqtt; diff --git a/release-notes.yml b/release-notes.yml index eb9654f6..aff3a130 100644 --- a/release-notes.yml +++ b/release-notes.yml @@ -4,6 +4,7 @@ tracks: - repeater-bridge-espnow - observer-eastmesh - observer-eastmesh-bridge-espnow + - observer-eastmesh-bridge-mqtt generated_from: "Adjacent git tag comparisons plus non-merge commit subjects." releases: @@ -265,6 +266,18 @@ releases: text: "Documented the Waev broker in the README and custom CLI guides." breaking_changes: [] + - track: observer-eastmesh + version: "2026.6.6" + tag: "observer-eastmesh-v2026.6.6" + date: "2026-06-27" + previous_version: "2026.6.5" + summary: "Adds cumulative packet counters to the MQTT status payload so brokers receive radio TX/RX totals." + changes: + - type: added + area: mqtt + text: "Added `packets_sent` and `packets_received` counters to the MQTT `status` payload `stats` object, reporting cumulative radio TX/RX packet totals to align with the Waev/MeshMapper status schema." + breaking_changes: [] + - track: observer-eastmesh-bridge-espnow version: "2026.5.1" tag: "observer-eastmesh-bridge-espnow-v2026.5.1" @@ -441,3 +454,39 @@ releases: area: docs text: "Documented the Waev broker in the README and custom CLI guides." breaking_changes: [] + + - track: observer-eastmesh-bridge-espnow + version: "2026.6.6" + tag: "observer-eastmesh-bridge-espnow-v2026.6.6" + date: "2026-06-27" + previous_version: "2026.6.5" + summary: "Adds cumulative packet counters to the MQTT status payload so brokers receive radio TX/RX totals." + changes: + - type: added + area: mqtt + text: "Added `packets_sent` and `packets_received` counters to the MQTT `status` payload `stats` object, reporting cumulative radio TX/RX packet totals to align with the Waev/MeshMapper status schema." + breaking_changes: [] + + - track: observer-eastmesh-bridge-mqtt + version: "2026.7.0" + tag: "observer-eastmesh-bridge-mqtt-v2026.7.0" + date: "2026-07-01" + previous_version: null + summary: "Introduces the observer-eastmesh-bridge-mqtt release track with bidirectional MQTT mesh bridging and web-panel peer broker configuration." + changes: + - type: added + area: mqtt + text: "Added `WITH_MQTT_BRIDGE` and `MQTTBridge` for bidirectional mesh packet transport over a shared MQTT topic at a peer broker." + - type: added + area: cli + text: "Added `get/set bridge.peer.host` and `get/set bridge.peer.port` for configuring the peer MQTT broker used by the mesh bridge." + - type: added + area: web + text: "Added mesh bridge peer MQTT host:port controls to the web panel on MQTT bridge builds." + - type: added + area: release + text: "Added the `observer-eastmesh-bridge-mqtt-v*` release tag format, PlatformIO `*_repeater_observer_mqtt_bridge` environments, and CI build workflow." + - type: added + area: board + text: "Added `Xiao_S3_WIO_repeater_observer_mqtt_bridge` as the first supported observer MQTT bridge target." + breaking_changes: [] diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index e5ed752b..91c0ebf0 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -103,7 +103,19 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { if (file.available() >= (int)sizeof(_prefs->flood_max_advert)) { file.read((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 295 } - // next: 296 + if (file.available() >= (int)sizeof(_prefs->bridge_peer_host)) { + file.read((uint8_t *)&_prefs->bridge_peer_host, sizeof(_prefs->bridge_peer_host)); // 296 + } + if (file.available() >= (int)sizeof(_prefs->bridge_peer_port)) { + file.read((uint8_t *)&_prefs->bridge_peer_port, sizeof(_prefs->bridge_peer_port)); // 360 + } + if (file.available() >= (int)sizeof(_prefs->bridge_peer_username)) { + file.read((uint8_t *)&_prefs->bridge_peer_username, sizeof(_prefs->bridge_peer_username)); // 362 + } + if (file.available() >= (int)sizeof(_prefs->bridge_peer_password)) { + file.read((uint8_t *)&_prefs->bridge_peer_password, sizeof(_prefs->bridge_peer_password)); // 427 + } + // next: 523 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -200,7 +212,11 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->fan_mode, sizeof(_prefs->fan_mode)); // 292 file.write((uint8_t *)&_prefs->fan_timeout_secs, sizeof(_prefs->fan_timeout_secs)); // 293 file.write((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 295 - // next: 296 + file.write((uint8_t *)&_prefs->bridge_peer_host, sizeof(_prefs->bridge_peer_host)); // 296 + file.write((uint8_t *)&_prefs->bridge_peer_port, sizeof(_prefs->bridge_peer_port)); // 360 + file.write((uint8_t *)&_prefs->bridge_peer_username, sizeof(_prefs->bridge_peer_username)); // 362 + file.write((uint8_t *)&_prefs->bridge_peer_password, sizeof(_prefs->bridge_peer_password)); // 427 + // next: 523 file.close(); } @@ -770,6 +786,52 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep _callbacks->restartBridge(); savePrefs(); strcpy(reply, "OK"); +#endif +#ifdef WITH_MQTT_BRIDGE + } else if (memcmp(config, "bridge.peer.host ", 17) == 0) { + const char* host = &config[17]; + size_t oi = 0; + char cleaned[sizeof(_prefs->bridge_peer_host)]; + memset(cleaned, 0, sizeof(cleaned)); + for (size_t i = 0; host[i] != 0 && oi + 1 < sizeof(cleaned); ++i) { + unsigned char c = static_cast(host[i]); + if (c <= ' ' || c == '/' || c == ':' || c == '\\') { + strcpy(reply, "Error: invalid host"); + return; + } + cleaned[oi++] = static_cast(c); + } + cleaned[oi] = 0; + StrHelper::strncpy(_prefs->bridge_peer_host, cleaned, sizeof(_prefs->bridge_peer_host)); + _callbacks->restartBridge(); + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "bridge.peer.port ", 17) == 0) { + char* end = nullptr; + unsigned long parsed = strtoul(&config[17], &end, 10); + if (end == &config[17] || *end != 0 || parsed == 0 || parsed > 65535UL) { + strcpy(reply, "Error: port must be 1-65535"); + return; + } + _prefs->bridge_peer_port = static_cast(parsed); + _callbacks->restartBridge(); + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "bridge.peer.username ", 21) == 0) { + StrHelper::strncpy(_prefs->bridge_peer_username, &config[21], sizeof(_prefs->bridge_peer_username)); + _callbacks->restartBridge(); + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "bridge.peer.password ", 21) == 0) { + StrHelper::strncpy(_prefs->bridge_peer_password, &config[21], sizeof(_prefs->bridge_peer_password)); + _callbacks->restartBridge(); + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "bridge.secret ", 14) == 0) { + StrHelper::strncpy(_prefs->bridge_secret, &config[14], sizeof(_prefs->bridge_secret)); + _callbacks->restartBridge(); + savePrefs(); + strcpy(reply, "OK"); #endif } else if (memcmp(config, "adc.multiplier ", 15) == 0) { _prefs->adc_multiplier = atof(&config[15]); @@ -884,6 +946,8 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep "rs232" #elif WITH_ESPNOW_BRIDGE "espnow" +#elif WITH_MQTT_BRIDGE + "mqtt" #else "none" #endif @@ -905,6 +969,18 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep sprintf(reply, "> %d", (uint32_t)_prefs->bridge_channel); } else if (memcmp(config, "bridge.secret", 13) == 0) { sprintf(reply, "> %s", _prefs->bridge_secret); +#endif +#ifdef WITH_MQTT_BRIDGE + } else if (memcmp(config, "bridge.peer.host", 16) == 0) { + sprintf(reply, "> %s", _prefs->bridge_peer_host); + } else if (memcmp(config, "bridge.peer.port", 16) == 0) { + sprintf(reply, "> %u", _prefs->bridge_peer_port != 0 ? _prefs->bridge_peer_port : 1883); + } else if (memcmp(config, "bridge.peer.username", 20) == 0) { + sprintf(reply, "> %s", _prefs->bridge_peer_username[0] ? _prefs->bridge_peer_username : "-"); + } else if (memcmp(config, "bridge.peer.password", 20) == 0) { + sprintf(reply, "> %s", _prefs->bridge_peer_password[0] ? "set" : "-"); + } else if (memcmp(config, "bridge.secret", 13) == 0) { + sprintf(reply, "> %s", _prefs->bridge_secret); #endif } else if (memcmp(config, "bootloader.ver", 14) == 0) { #ifdef NRF52_PLATFORM diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 7310ac78..fe092e85 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -6,7 +6,7 @@ #include #include -#if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE) +#if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE) || defined(WITH_MQTT_BRIDGE) #define WITH_BRIDGE #endif @@ -50,7 +50,7 @@ struct NodePrefs { // persisted to file uint8_t bridge_pkt_src; // 0 = logTx, 1 = logRx (default logTx) uint32_t bridge_baud; // 9600, 19200, 38400, 57600, 115200 (default 115200) uint8_t bridge_channel; // 1-14 (ESP-NOW only) - char bridge_secret[16]; // for XOR encryption of bridge packets (ESP-NOW only) + char bridge_secret[16]; // for XOR encryption of bridge packets (ESP-NOW / MQTT) // Power setting uint8_t powersaving_enabled; // boolean // Gps settings @@ -65,6 +65,10 @@ struct NodePrefs { // persisted to file uint8_t loop_detect; uint8_t fan_mode; uint16_t fan_timeout_secs; + char bridge_peer_host[64]; // peer MQTT broker host (MQTT bridge only) + uint16_t bridge_peer_port; // peer MQTT broker port (MQTT bridge only, default 1883) + char bridge_peer_username[65]; // peer MQTT broker username (MQTT bridge only) + char bridge_peer_password[96]; // peer MQTT broker password (MQTT bridge only) }; class CommonCLICallbacks { diff --git a/src/helpers/bridges/MQTTBridge.cpp b/src/helpers/bridges/MQTTBridge.cpp new file mode 100644 index 00000000..49577f5f --- /dev/null +++ b/src/helpers/bridges/MQTTBridge.cpp @@ -0,0 +1,335 @@ +#include "MQTTBridge.h" + +#ifdef WITH_MQTT_BRIDGE + +#if defined(ESP_PLATFORM) + +#include +#include +#include +#include + +#ifndef BRIDGE_DEBUG + #define BRIDGE_DEBUG 0 +#endif + +#if BRIDGE_DEBUG + #define BRIDGE_DEBUG_PRINTLN(...) Serial.printf(__VA_ARGS__) +#else + #define BRIDGE_DEBUG_PRINTLN(...) do { } while (0) +#endif + +namespace { +constexpr unsigned long kConnectRetryBaseMillis = 10000; +constexpr unsigned long kConnectRetryMaxMillis = 120000; + +unsigned long connectRetryDelayMillis(uint8_t failures) { + unsigned long delay_ms = kConnectRetryBaseMillis; + if (failures > 0) { + uint8_t shifts = min(failures - 1, 3); + delay_ms <<= shifts; + } + if (delay_ms > kConnectRetryMaxMillis) { + delay_ms = kConnectRetryMaxMillis; + } + return delay_ms; +} + +bool peerHostConfigured(const NodePrefs *prefs) { + return prefs != nullptr && prefs->bridge_peer_host[0] != 0; +} + +uint16_t peerPort(const NodePrefs *prefs) { + return prefs->bridge_peer_port != 0 ? prefs->bridge_peer_port : 1883; +} +} // namespace + +MQTTBridge *MQTTBridge::_instance = nullptr; +const char *MQTTBridge::kBridgeTopic = "meshcore/bridge/packets"; + +MQTTBridge::MQTTBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc) + : BridgeBase(prefs, mgr, rtc), _client(nullptr), _connected(false), _started(false), + _pending_destroy(false), _next_connect_attempt(0), _reconnect_failures(0) { + _instance = this; + _client_id[0] = 0; +} + +void MQTTBridge::xorCrypt(uint8_t *data, size_t len) { + size_t keyLen = strlen(_prefs->bridge_secret); + if (keyLen == 0) { + return; + } + for (size_t i = 0; i < len; i++) { + data[i] ^= _prefs->bridge_secret[i % keyLen]; + } +} + +void MQTTBridge::destroyClient() { + if (_client != nullptr) { + esp_mqtt_client_stop(_client); + esp_mqtt_client_destroy(_client); + _client = nullptr; + } + _connected = false; + _started = false; + _pending_destroy = false; +} + +void MQTTBridge::scheduleClientDestroy() { + _connected = false; + _started = false; + _pending_destroy = true; +} + +void MQTTBridge::begin() { + BRIDGE_DEBUG_PRINTLN("MQTT bridge initializing\n"); + if (!peerHostConfigured(_prefs)) { + BRIDGE_DEBUG_PRINTLN("MQTT bridge peer host not configured\n"); + return; + } + + uint8_t mac[6]; + WiFi.macAddress(mac); + snprintf(_client_id, sizeof(_client_id), "mc-br-%02x%02x%02x", mac[3], mac[4], mac[5]); + + _initialized = true; + _next_connect_attempt = 0; + _reconnect_failures = 0; +} + +void MQTTBridge::end() { + BRIDGE_DEBUG_PRINTLN("MQTT bridge stopping\n"); + destroyClient(); + _initialized = false; +} + +void MQTTBridge::onMqttConnected() { + _connected = true; + _reconnect_failures = 0; + _next_connect_attempt = 0; + if (_client != nullptr) { + esp_mqtt_client_subscribe(_client, kBridgeTopic, 0); + } + BRIDGE_DEBUG_PRINTLN("MQTT bridge connected to %s:%u\n", _prefs->bridge_peer_host, + static_cast(peerPort(_prefs))); +} + +void MQTTBridge::onMqttDisconnected() { + scheduleClientDestroy(); + _next_connect_attempt = millis() + connectRetryDelayMillis(_reconnect_failures); + if (_reconnect_failures < 255) { + ++_reconnect_failures; + } +} + +void MQTTBridge::mqttEventHandler(void *handler_args, esp_event_base_t, int32_t event_id, void *event_data) { + auto *bridge = static_cast(handler_args); + if (bridge == nullptr) { + return; + } + + switch (event_id) { + case MQTT_EVENT_CONNECTED: + bridge->onMqttConnected(); + break; + case MQTT_EVENT_DISCONNECTED: + bridge->onMqttDisconnected(); + break; + case MQTT_EVENT_ERROR: + bridge->onMqttDisconnected(); + break; + case MQTT_EVENT_DATA: { + auto *event = static_cast(event_data); + if (event != nullptr && event->data_len > 0) { + bridge->handleMqttData(reinterpret_cast(event->data), + static_cast(event->data_len)); + } + break; + } + default: + break; + } +} + +bool MQTTBridge::ensureClient() { + if (_client != nullptr) { + return true; + } + if (!peerHostConfigured(_prefs)) { + return false; + } + if (WiFi.status() != WL_CONNECTED) { + return false; + } + + esp_mqtt_client_config_t cfg = {}; +#if ESP_IDF_VERSION_MAJOR >= 5 + cfg.broker.address.hostname = _prefs->bridge_peer_host; + cfg.broker.address.port = peerPort(_prefs); + cfg.broker.address.transport = MQTT_TRANSPORT_OVER_TCP; + cfg.credentials.client_id = _client_id; + if (_prefs->bridge_peer_username[0] != 0) { + cfg.credentials.username = _prefs->bridge_peer_username; + } + if (_prefs->bridge_peer_password[0] != 0) { + cfg.credentials.authentication.password = _prefs->bridge_peer_password; + } + cfg.session.keepalive = 30; + cfg.network.reconnect_timeout_ms = 10000; + cfg.network.timeout_ms = 10000; + cfg.network.disable_auto_reconnect = true; + cfg.buffer.size = MAX_MQTT_PACKET_SIZE; + cfg.buffer.out_size = MAX_MQTT_PACKET_SIZE; +#else + cfg.host = _prefs->bridge_peer_host; + cfg.port = peerPort(_prefs); + cfg.transport = MQTT_TRANSPORT_OVER_TCP; + cfg.client_id = _client_id; + if (_prefs->bridge_peer_username[0] != 0) { + cfg.username = _prefs->bridge_peer_username; + } + if (_prefs->bridge_peer_password[0] != 0) { + cfg.password = _prefs->bridge_peer_password; + } + cfg.keepalive = 30; + cfg.buffer_size = MAX_MQTT_PACKET_SIZE; + cfg.out_buffer_size = MAX_MQTT_PACKET_SIZE; + cfg.reconnect_timeout_ms = 10000; + cfg.network_timeout_ms = 10000; + cfg.disable_auto_reconnect = true; +#endif + + _client = esp_mqtt_client_init(&cfg); + if (_client == nullptr) { + BRIDGE_DEBUG_PRINTLN("MQTT bridge client init failed\n"); + return false; + } + + esp_mqtt_client_register_event(_client, MQTT_EVENT_ANY, &MQTTBridge::mqttEventHandler, this); + if (esp_mqtt_client_start(_client) != ESP_OK) { + BRIDGE_DEBUG_PRINTLN("MQTT bridge client start failed\n"); + destroyClient(); + return false; + } + + _started = true; + return true; +} + +void MQTTBridge::loop() { + if (!_initialized) { + return; + } + + if (_pending_destroy) { + destroyClient(); + return; + } + + if (WiFi.status() != WL_CONNECTED) { + if (_client != nullptr) { + destroyClient(); + } + return; + } + + if (_client != nullptr) { + return; + } + + if (_next_connect_attempt != 0 && millis() < _next_connect_attempt) { + return; + } + ensureClient(); +} + +void MQTTBridge::handleMqttData(const uint8_t *data, size_t len) { + if (len < (BRIDGE_MAGIC_SIZE + BRIDGE_CHECKSUM_SIZE)) { + BRIDGE_DEBUG_PRINTLN("MQTT RX packet too small, len=%u\n", static_cast(len)); + return; + } + + if (len > MAX_MQTT_PACKET_SIZE) { + BRIDGE_DEBUG_PRINTLN("MQTT RX packet too large, len=%u\n", static_cast(len)); + return; + } + + uint16_t received_magic = (data[0] << 8) | data[1]; + if (received_magic != BRIDGE_PACKET_MAGIC) { + BRIDGE_DEBUG_PRINTLN("MQTT RX invalid magic 0x%04X\n", received_magic); + return; + } + + uint8_t decrypted[MAX_MQTT_PACKET_SIZE]; + const size_t encryptedDataLen = len - BRIDGE_MAGIC_SIZE; + memcpy(decrypted, data + BRIDGE_MAGIC_SIZE, encryptedDataLen); + + xorCrypt(decrypted, encryptedDataLen); + + uint16_t received_checksum = (decrypted[0] << 8) | decrypted[1]; + const size_t payloadLen = encryptedDataLen - BRIDGE_CHECKSUM_SIZE; + + if (!validateChecksum(decrypted + BRIDGE_CHECKSUM_SIZE, payloadLen, received_checksum)) { + BRIDGE_DEBUG_PRINTLN("MQTT RX checksum mismatch, rcv=0x%04X\n", received_checksum); + return; + } + + mesh::Packet *pkt = _mgr->allocNew(); + if (!pkt) { + return; + } + + if (pkt->readFrom(decrypted + BRIDGE_CHECKSUM_SIZE, payloadLen)) { + onPacketReceived(pkt); + } else { + _mgr->free(pkt); + } +} + +void MQTTBridge::sendPacket(mesh::Packet *packet) { + if (!_initialized || !_connected || _client == nullptr || packet == nullptr) { + return; + } + + if (_seen_packets.hasSeen(packet)) { + return; + } + + uint8_t sizingBuffer[MAX_PAYLOAD_SIZE]; + uint16_t meshPacketLen = packet->writeTo(sizingBuffer); + if (meshPacketLen > MAX_PAYLOAD_SIZE) { + BRIDGE_DEBUG_PRINTLN("MQTT TX packet too large (payload=%u, max=%u)\n", meshPacketLen, + static_cast(MAX_PAYLOAD_SIZE)); + return; + } + + uint8_t buffer[MAX_MQTT_PACKET_SIZE]; + buffer[0] = (BRIDGE_PACKET_MAGIC >> 8) & 0xFF; + buffer[1] = BRIDGE_PACKET_MAGIC & 0xFF; + + const size_t packetOffset = BRIDGE_MAGIC_SIZE + BRIDGE_CHECKSUM_SIZE; + memcpy(buffer + packetOffset, sizingBuffer, meshPacketLen); + + uint16_t checksum = fletcher16(buffer + packetOffset, meshPacketLen); + buffer[2] = (checksum >> 8) & 0xFF; + buffer[3] = checksum & 0xFF; + + xorCrypt(buffer + BRIDGE_MAGIC_SIZE, meshPacketLen + BRIDGE_CHECKSUM_SIZE); + + const size_t totalPacketSize = BRIDGE_MAGIC_SIZE + BRIDGE_CHECKSUM_SIZE + meshPacketLen; + int msg_id = esp_mqtt_client_publish(_client, kBridgeTopic, reinterpret_cast(buffer), + static_cast(totalPacketSize), 0, 0); + if (msg_id >= 0) { + BRIDGE_DEBUG_PRINTLN("MQTT TX, len=%u\n", meshPacketLen); + } else { + BRIDGE_DEBUG_PRINTLN("MQTT TX FAILED\n"); + } +} + +void MQTTBridge::onPacketReceived(mesh::Packet *packet) { + handleReceivedPacket(packet); +} + +#endif // ESP_PLATFORM + +#endif // WITH_MQTT_BRIDGE diff --git a/src/helpers/bridges/MQTTBridge.h b/src/helpers/bridges/MQTTBridge.h new file mode 100644 index 00000000..d9dcf276 --- /dev/null +++ b/src/helpers/bridges/MQTTBridge.h @@ -0,0 +1,54 @@ +#pragma once + +#include "helpers/bridges/BridgeBase.h" + +#ifdef WITH_MQTT_BRIDGE + +#if defined(ESP_PLATFORM) +#include +#endif + +/** + * @brief Bridge implementation using MQTT for bidirectional mesh packet transport + * + * Publishes and subscribes on a shared topic at a peer MQTT broker. Uses the same + * binary framing and XOR encryption as other bridge types for network isolation. + */ +class MQTTBridge : public BridgeBase { +private: + static MQTTBridge *_instance; + + static void mqttEventHandler(void *handler_args, esp_event_base_t base, int32_t event_id, + void *event_data); + + static const char *kBridgeTopic; + static const size_t MAX_MQTT_PACKET_SIZE = 512; + static const size_t MAX_PAYLOAD_SIZE = MAX_MQTT_PACKET_SIZE - (BRIDGE_MAGIC_SIZE + BRIDGE_CHECKSUM_SIZE); + + esp_mqtt_client_handle_t _client; + bool _connected; + bool _started; + bool _pending_destroy; + unsigned long _next_connect_attempt; + uint8_t _reconnect_failures; + char _client_id[24]; + + void xorCrypt(uint8_t *data, size_t len); + void destroyClient(); + void scheduleClientDestroy(); + bool ensureClient(); + void handleMqttData(const uint8_t *data, size_t len); + void onMqttConnected(); + void onMqttDisconnected(); + +public: + MQTTBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc); + + void begin() override; + void end() override; + void loop() override; + void onPacketReceived(mesh::Packet *packet) override; + void sendPacket(mesh::Packet *packet) override; +}; + +#endif diff --git a/src/helpers/mqtt/MQTTPrefs.cpp b/src/helpers/mqtt/MQTTPrefs.cpp index 372e25fc..4bc7e282 100644 --- a/src/helpers/mqtt/MQTTPrefs.cpp +++ b/src/helpers/mqtt/MQTTPrefs.cpp @@ -13,7 +13,12 @@ constexpr uint32_t kFixedStatusIntervalMs = 300000; void MQTTPrefsStore::setDefaults(MQTTPrefs& prefs) { memset(&prefs, 0, sizeof(prefs)); prefs.magic = kMagic; +#if defined(WITH_MQTT_BRIDGE) + prefs.enabled_mask = 0x00; + prefs.mqtt_bridge_uplink_migrated = 1; +#else prefs.enabled_mask = 0x01; +#endif prefs.packets_enabled = 1; prefs.raw_enabled = 0; prefs.status_enabled = 1; @@ -83,6 +88,13 @@ bool MQTTPrefsStore::load(FILESYSTEM* fs, MQTTPrefs& prefs) { prefs.brokers_migrated = 1; save(fs, prefs); } +#if defined(WITH_MQTT_BRIDGE) + if (!prefs.mqtt_bridge_uplink_migrated) { + prefs.enabled_mask = 0; + prefs.mqtt_bridge_uplink_migrated = 1; + save(fs, prefs); + } +#endif return true; } diff --git a/src/helpers/mqtt/MQTTPrefs.h b/src/helpers/mqtt/MQTTPrefs.h index 337ad5af..1112fd18 100644 --- a/src/helpers/mqtt/MQTTPrefs.h +++ b/src/helpers/mqtt/MQTTPrefs.h @@ -37,6 +37,7 @@ struct MQTTPrefs { // Appended fields must stay at the end: older prefs files are shorter and // read back as zero here, which drives one-time migrations (see load()). uint8_t brokers_migrated; + uint8_t mqtt_bridge_uplink_migrated; }; class MQTTPrefsStore { diff --git a/src/helpers/mqtt/MQTTUplink.cpp b/src/helpers/mqtt/MQTTUplink.cpp index 74c60438..5a1ef954 100644 --- a/src/helpers/mqtt/MQTTUplink.cpp +++ b/src/helpers/mqtt/MQTTUplink.cpp @@ -669,13 +669,16 @@ int MQTTUplink::buildStatusJson(char* buffer, size_t buffer_size, bool online) c "{\"status\":\"%s\",\"timestamp\":\"%s\",\"origin\":\"%s\",\"origin_id\":\"%s\"," "\"model\":\"%s\",\"firmware_version\":\"%s\",\"radio\":\"%s\",\"client_version\":\"%s\"," "\"repeat\":\"%s\",\"stats\":{\"battery_mv\":%d,\"uptime_secs\":%lu,\"errors\":%u,\"queue_len\":%u," - "\"noise_floor\":%d,\"tx_air_secs\":%lu,\"rx_air_secs\":%lu,\"recv_errors\":%lu}}", + "\"noise_floor\":%d,\"tx_air_secs\":%lu,\"rx_air_secs\":%lu,\"recv_errors\":%lu," + "\"packets_sent\":%lu,\"packets_received\":%lu}}", online ? "online" : "offline", ts, origin, _device_id, model, FIRMWARE_VERSION, radio_info, client_version, _last_status.repeat_enabled ? "on" : "off", _last_status.battery_mv, static_cast(_last_status.uptime_secs), _last_status.error_flags, _last_status.queue_len, _last_status.noise_floor, static_cast(_last_status.tx_air_secs), static_cast(_last_status.rx_air_secs), - static_cast(_last_status.recv_errors)); + static_cast(_last_status.recv_errors), + static_cast(_last_status.packets_sent), + static_cast(_last_status.packets_received)); } int MQTTUplink::buildPacketJson(char* buffer, size_t buffer_size, const mesh::Packet& packet, bool is_tx, int rssi, diff --git a/src/helpers/mqtt/MQTTUplink.h b/src/helpers/mqtt/MQTTUplink.h index 82f421fb..960d71e3 100644 --- a/src/helpers/mqtt/MQTTUplink.h +++ b/src/helpers/mqtt/MQTTUplink.h @@ -22,6 +22,8 @@ struct MQTTStatusSnapshot { uint32_t tx_air_secs; uint32_t rx_air_secs; uint32_t recv_errors; + uint32_t packets_sent; + uint32_t packets_received; float radio_freq; float radio_bw; uint8_t radio_sf; diff --git a/src/helpers/web/WebPanelServer.cpp b/src/helpers/web/WebPanelServer.cpp index 2e22029f..1b8bef7b 100644 --- a/src/helpers/web/WebPanelServer.cpp +++ b/src/helpers/web/WebPanelServer.cpp @@ -1027,6 +1027,32 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
A maximum of two MQTT brokers can be enabled at once.
+ @@ -1222,6 +1248,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML( function parseClientEnv(clientEnv) { const env = String(clientEnv || "").trim(); const suffixes = [ + { suffix:"_repeater_observer_mqtt_bridge", firmware:"repeater_observer_mqtt_bridge" }, { suffix:"_repeater_observer_espnow", firmware:"repeater_observer_espnow" }, { suffix:"_repeater_observer", firmware:"repeater_observer" }, { suffix:"_repeater_bridge_espnow", firmware:"repeater_bridge_espnow" }, @@ -2835,6 +2862,40 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML( if (!portResult.ok) return; input.value = `${parsed.host}:${parsed.port}`; } + async function loadBridgePeerEndpoint(options = {}) { + const config = document.getElementById("bridgePeerConfig"); + if (!config) return; + const typeResult = await runCommand("get bridge.type", options); + if (!typeResult.ok || parseReplyValue(typeResult.text) !== "mqtt") { + config.style.display = "none"; + return; + } + config.style.display = ""; + const hostResult = await runCommand("get bridge.peer.host", options); + if (!hostResult.ok) return; + const portResult = await runCommand("get bridge.peer.port", options); + if (!portResult.ok) return; + const host = parseReplyValue(hostResult.text); + const port = parseReplyValue(portResult.text); + const input = document.getElementById("bridgePeerEndpoint"); + if (!input) return; + input.value = host ? `${host}:${port}` : ""; + await loadField("get bridge.peer.username", "bridgePeerUsername", null, options); + } + async function saveBridgePeerEndpoint() { + const input = document.getElementById("bridgePeerEndpoint"); + if (!input) return; + const parsed = parseCustomEndpoint(input.value); + if (!parsed) { + statusEl.textContent = "Use host:port, for example 192.168.1.10:1883"; + return; + } + const hostResult = await runCommand("set bridge.peer.host " + parsed.host); + if (!hostResult.ok) return; + const portResult = await runCommand("set bridge.peer.port " + parsed.port); + if (!portResult.ok) return; + input.value = `${parsed.host}:${parsed.port}`; + } async function loadRadioConfig(options = {}) { const result = await runCommand("get radio", options); if (!result.ok) { @@ -3017,6 +3078,14 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML( }); document.getElementById("refreshCustomEndpointBtn").onclick = () => loadCustomEndpoint(); document.getElementById("saveCustomEndpointBtn").onclick = () => saveCustomEndpoint(); + const refreshBridgePeerEndpointBtn = document.getElementById("refreshBridgePeerEndpointBtn"); + if (refreshBridgePeerEndpointBtn) { + refreshBridgePeerEndpointBtn.onclick = () => loadBridgePeerEndpoint(); + } + const saveBridgePeerEndpointBtn = document.getElementById("saveBridgePeerEndpointBtn"); + if (saveBridgePeerEndpointBtn) { + saveBridgePeerEndpointBtn.onclick = () => saveBridgePeerEndpoint(); + } const customTransportSlider = document.getElementById("mqttCustomTransport"); if (customTransportSlider) { customTransportSlider.addEventListener("input", () => { @@ -3200,7 +3269,8 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML( () => loadBrokerState("get mqtt.custom", "mqttCustom", quiet), () => loadCustomEndpoint(quiet), () => loadCustomTransport(quiet), - () => loadField("get mqtt.custom.username", "mqttCustomUsername", null, quiet) + () => loadField("get mqtt.custom.username", "mqttCustomUsername", null, quiet), + () => loadBridgePeerEndpoint(quiet) ]); if (!isCurrentPageLoad(generation)) return; statusEl.textContent = "Ready"; diff --git a/variants/eastmesh_mqtt/platformio.ini b/variants/eastmesh/platformio.ini similarity index 74% rename from variants/eastmesh_mqtt/platformio.ini rename to variants/eastmesh/platformio.ini index e64f4aac..9bf79b18 100644 --- a/variants/eastmesh_mqtt/platformio.ini +++ b/variants/eastmesh/platformio.ini @@ -1,3 +1,5 @@ +# ===== Ebyte ===== + [env:Ebyte_EoRa-S3_Repeater_observer] extends = Ebyte_EoRa-S3 board_build.partitions = min_spiffs.csv @@ -24,6 +26,25 @@ lib_deps = ${Ebyte_EoRa-S3.lib_deps} ${esp32_ota.lib_deps} +[env:Ebyte_EoRa-S3_Repeater_observer_espnow] +extends = env:Ebyte_EoRa-S3_Repeater_observer +build_flags = + ${env:Ebyte_EoRa-S3_Repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:Ebyte_EoRa-S3_Repeater_observer.build_src_filter} + + + +[env:Ebyte_EoRa-S3_Repeater_observer_mqtt_bridge] +extends = env:Ebyte_EoRa-S3_Repeater_observer +build_flags = + ${env:Ebyte_EoRa-S3_Repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Ebyte_EoRa-S3_Repeater_observer.build_src_filter} + + + +# ===== Generic ===== + [env:Generic_E22_sx1262_repeater_observer] extends = Generic_E22 board_build.partitions = min_spiffs.csv ; get around 4mb flash limit @@ -51,6 +72,23 @@ lib_deps = ${Generic_E22.lib_deps} ${esp32_ota.lib_deps} +[env:Generic_E22_sx1262_repeater_observer_espnow] +extends = env:Generic_E22_sx1262_repeater_observer +build_flags = + ${env:Generic_E22_sx1262_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:Generic_E22_sx1262_repeater_observer.build_src_filter} + + + +[env:Generic_E22_sx1262_repeater_observer_mqtt_bridge] +extends = env:Generic_E22_sx1262_repeater_observer +build_flags = + ${env:Generic_E22_sx1262_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Generic_E22_sx1262_repeater_observer.build_src_filter} + + + [env:Generic_E22_sx1268_repeater_observer] extends = Generic_E22 board_build.partitions = min_spiffs.csv ; get around 4mb flash limit @@ -78,6 +116,25 @@ lib_deps = ${Generic_E22.lib_deps} ${esp32_ota.lib_deps} +[env:Generic_E22_sx1268_repeater_observer_espnow] +extends = env:Generic_E22_sx1268_repeater_observer +build_flags = + ${env:Generic_E22_sx1268_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:Generic_E22_sx1268_repeater_observer.build_src_filter} + + + +[env:Generic_E22_sx1268_repeater_observer_mqtt_bridge] +extends = env:Generic_E22_sx1268_repeater_observer +build_flags = + ${env:Generic_E22_sx1268_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Generic_E22_sx1268_repeater_observer.build_src_filter} + + + +# ===== Heltec ===== + [env:Heltec_E213_repeater_observer] extends = Heltec_E213_base build_flags = @@ -104,6 +161,23 @@ lib_deps = ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 +[env:Heltec_E213_repeater_observer_espnow] +extends = env:Heltec_E213_repeater_observer +build_flags = + ${env:Heltec_E213_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:Heltec_E213_repeater_observer.build_src_filter} + + + +[env:Heltec_E213_repeater_observer_mqtt_bridge] +extends = env:Heltec_E213_repeater_observer +build_flags = + ${env:Heltec_E213_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Heltec_E213_repeater_observer.build_src_filter} + + + [env:Heltec_E290_repeater_observer] extends = Heltec_E290_base build_flags = @@ -130,6 +204,23 @@ lib_deps = ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 +[env:Heltec_E290_repeater_observer_espnow] +extends = env:Heltec_E290_repeater_observer +build_flags = + ${env:Heltec_E290_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:Heltec_E290_repeater_observer.build_src_filter} + + + +[env:Heltec_E290_repeater_observer_mqtt_bridge] +extends = env:Heltec_E290_repeater_observer +build_flags = + ${env:Heltec_E290_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Heltec_E290_repeater_observer.build_src_filter} + + + [env:Heltec_T190_repeater_observer] extends = Heltec_T190_base build_flags = @@ -153,6 +244,23 @@ lib_deps = ${Heltec_T190_base.lib_deps} ${esp32_ota.lib_deps} +[env:Heltec_T190_repeater_observer_espnow] +extends = env:Heltec_T190_repeater_observer +build_flags = + ${env:Heltec_T190_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:Heltec_T190_repeater_observer.build_src_filter} + + + +[env:Heltec_T190_repeater_observer_mqtt_bridge] +extends = env:Heltec_T190_repeater_observer +build_flags = + ${env:Heltec_T190_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Heltec_T190_repeater_observer.build_src_filter} + + + [env:heltec_tracker_v2_repeater_observer] extends = Heltec_tracker_v2 build_flags = @@ -179,6 +287,23 @@ lib_deps = ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 +[env:heltec_tracker_v2_repeater_observer_espnow] +extends = env:heltec_tracker_v2_repeater_observer +build_flags = + ${env:heltec_tracker_v2_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:heltec_tracker_v2_repeater_observer.build_src_filter} + + + +[env:heltec_tracker_v2_repeater_observer_mqtt_bridge] +extends = env:heltec_tracker_v2_repeater_observer +build_flags = + ${env:heltec_tracker_v2_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:heltec_tracker_v2_repeater_observer.build_src_filter} + + + [env:Heltec_v2_repeater_observer] extends = Heltec_lora32_v2 build_flags = @@ -205,6 +330,23 @@ lib_deps = ${Heltec_lora32_v2.lib_deps} ${esp32_ota.lib_deps} +[env:Heltec_v2_repeater_observer_espnow] +extends = env:Heltec_v2_repeater_observer +build_flags = + ${env:Heltec_v2_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:Heltec_v2_repeater_observer.build_src_filter} + + + +[env:Heltec_v2_repeater_observer_mqtt_bridge] +extends = env:Heltec_v2_repeater_observer +build_flags = + ${env:Heltec_v2_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Heltec_v2_repeater_observer.build_src_filter} + + + [env:Heltec_v3_repeater_observer] extends = Heltec_lora32_v3 build_flags = @@ -231,6 +373,23 @@ lib_deps = ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 +[env:Heltec_v3_repeater_observer_espnow] +extends = env:Heltec_v3_repeater_observer +build_flags = + ${env:Heltec_v3_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:Heltec_v3_repeater_observer.build_src_filter} + + + +[env:Heltec_v3_repeater_observer_mqtt_bridge] +extends = env:Heltec_v3_repeater_observer +build_flags = + ${env:Heltec_v3_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Heltec_v3_repeater_observer.build_src_filter} + + + [env:heltec_v4_repeater_observer] extends = heltec_v4_oled build_flags = @@ -258,6 +417,23 @@ lib_deps = ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 +[env:heltec_v4_repeater_observer_espnow] +extends = env:heltec_v4_repeater_observer +build_flags = + ${env:heltec_v4_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:heltec_v4_repeater_observer.build_src_filter} + + + +[env:heltec_v4_repeater_observer_mqtt_bridge] +extends = env:heltec_v4_repeater_observer +build_flags = + ${env:heltec_v4_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:heltec_v4_repeater_observer.build_src_filter} + + + [env:heltec_v4_tft_repeater_observer] extends = heltec_v4_tft build_flags = @@ -284,6 +460,23 @@ lib_deps = ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 +[env:heltec_v4_tft_repeater_observer_espnow] +extends = env:heltec_v4_tft_repeater_observer +build_flags = + ${env:heltec_v4_tft_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:heltec_v4_tft_repeater_observer.build_src_filter} + + + +[env:heltec_v4_tft_repeater_observer_mqtt_bridge] +extends = env:heltec_v4_tft_repeater_observer +build_flags = + ${env:heltec_v4_tft_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:heltec_v4_tft_repeater_observer.build_src_filter} + + + [env:Heltec_Wireless_Paper_repeater_observer] extends = Heltec_Wireless_Paper_base build_flags = @@ -310,6 +503,23 @@ lib_deps = ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 +[env:Heltec_Wireless_Paper_repeater_observer_espnow] +extends = env:Heltec_Wireless_Paper_repeater_observer +build_flags = + ${env:Heltec_Wireless_Paper_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:Heltec_Wireless_Paper_repeater_observer.build_src_filter} + + + +[env:Heltec_Wireless_Paper_repeater_observer_mqtt_bridge] +extends = env:Heltec_Wireless_Paper_repeater_observer +build_flags = + ${env:Heltec_Wireless_Paper_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Heltec_Wireless_Paper_repeater_observer.build_src_filter} + + + [env:Heltec_Wireless_Tracker_repeater_observer] extends = Heltec_tracker_base build_flags = @@ -336,6 +546,23 @@ lib_deps = ${Heltec_tracker_base.lib_deps} ${esp32_ota.lib_deps} +[env:Heltec_Wireless_Tracker_repeater_observer_espnow] +extends = env:Heltec_Wireless_Tracker_repeater_observer +build_flags = + ${env:Heltec_Wireless_Tracker_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:Heltec_Wireless_Tracker_repeater_observer.build_src_filter} + + + +[env:Heltec_Wireless_Tracker_repeater_observer_mqtt_bridge] +extends = env:Heltec_Wireless_Tracker_repeater_observer +build_flags = + ${env:Heltec_Wireless_Tracker_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Heltec_Wireless_Tracker_repeater_observer.build_src_filter} + + + [env:Heltec_WSL3_repeater_observer] extends = Heltec_lora32_v3 build_flags = @@ -360,6 +587,25 @@ lib_deps = ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 +[env:Heltec_WSL3_repeater_observer_espnow] +extends = env:Heltec_WSL3_repeater_observer +build_flags = + ${env:Heltec_WSL3_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:Heltec_WSL3_repeater_observer.build_src_filter} + + + +[env:Heltec_WSL3_repeater_observer_mqtt_bridge] +extends = env:Heltec_WSL3_repeater_observer +build_flags = + ${env:Heltec_WSL3_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Heltec_WSL3_repeater_observer.build_src_filter} + + + +# ===== LilyGo ===== + [env:LilyGo_T3S3_sx1262_repeater_observer] extends = LilyGo_T3S3_sx1262 board_build.partitions = min_spiffs.csv ; get around 4mb flash limit @@ -386,6 +632,23 @@ lib_deps = ${LilyGo_T3S3_sx1262.lib_deps} ${esp32_ota.lib_deps} +[env:LilyGo_T3S3_sx1262_repeater_observer_espnow] +extends = env:LilyGo_T3S3_sx1262_repeater_observer +build_flags = + ${env:LilyGo_T3S3_sx1262_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:LilyGo_T3S3_sx1262_repeater_observer.build_src_filter} + + + +[env:LilyGo_T3S3_sx1262_repeater_observer_mqtt_bridge] +extends = env:LilyGo_T3S3_sx1262_repeater_observer +build_flags = + ${env:LilyGo_T3S3_sx1262_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:LilyGo_T3S3_sx1262_repeater_observer.build_src_filter} + + + [env:LilyGo_T3S3_sx1276_repeater_observer] extends = LilyGo_T3S3_sx1276 board_build.partitions = min_spiffs.csv ; get around 4mb flash limit @@ -412,6 +675,23 @@ lib_deps = ${LilyGo_T3S3_sx1276.lib_deps} ${esp32_ota.lib_deps} +[env:LilyGo_T3S3_sx1276_repeater_observer_espnow] +extends = env:LilyGo_T3S3_sx1276_repeater_observer +build_flags = + ${env:LilyGo_T3S3_sx1276_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:LilyGo_T3S3_sx1276_repeater_observer.build_src_filter} + + + +[env:LilyGo_T3S3_sx1276_repeater_observer_mqtt_bridge] +extends = env:LilyGo_T3S3_sx1276_repeater_observer +build_flags = + ${env:LilyGo_T3S3_sx1276_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:LilyGo_T3S3_sx1276_repeater_observer.build_src_filter} + + + [env:LilyGo_TBeam_1W_repeater_observer] extends = LilyGo_TBeam_1W build_flags = @@ -437,6 +717,23 @@ lib_deps = ${LilyGo_TBeam_1W.lib_deps} ${esp32_ota.lib_deps} +[env:LilyGo_TBeam_1W_repeater_observer_espnow] +extends = env:LilyGo_TBeam_1W_repeater_observer +build_flags = + ${env:LilyGo_TBeam_1W_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:LilyGo_TBeam_1W_repeater_observer.build_src_filter} + + + +[env:LilyGo_TBeam_1W_repeater_observer_mqtt_bridge] +extends = env:LilyGo_TBeam_1W_repeater_observer +build_flags = + ${env:LilyGo_TBeam_1W_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:LilyGo_TBeam_1W_repeater_observer.build_src_filter} + + + [env:LilyGo_TDeck_repeater_observer] extends = LilyGo_TDeck build_flags = @@ -461,6 +758,23 @@ lib_deps = ${LilyGo_TDeck.lib_deps} ${esp32_ota.lib_deps} +[env:LilyGo_TDeck_repeater_observer_espnow] +extends = env:LilyGo_TDeck_repeater_observer +build_flags = + ${env:LilyGo_TDeck_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:LilyGo_TDeck_repeater_observer.build_src_filter} + + + +[env:LilyGo_TDeck_repeater_observer_mqtt_bridge] +extends = env:LilyGo_TDeck_repeater_observer +build_flags = + ${env:LilyGo_TDeck_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:LilyGo_TDeck_repeater_observer.build_src_filter} + + + [env:LilyGo_Tlora_C6_repeater_observer] extends = tlora_c6 build_src_filter = ${tlora_c6.build_src_filter} @@ -483,6 +797,25 @@ build_flags = lib_deps = ${tlora_c6.lib_deps} +[env:LilyGo_Tlora_C6_repeater_observer_espnow] +extends = env:LilyGo_Tlora_C6_repeater_observer +build_flags = + ${env:LilyGo_Tlora_C6_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:LilyGo_Tlora_C6_repeater_observer.build_src_filter} + + + +[env:LilyGo_Tlora_C6_repeater_observer_mqtt_bridge] +extends = env:LilyGo_Tlora_C6_repeater_observer +build_flags = + ${env:LilyGo_Tlora_C6_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:LilyGo_Tlora_C6_repeater_observer.build_src_filter} + + + +# ===== M5Stack ===== + [env:M5Stack_Unit_C6L_repeater_observer] extends = M5Stack_Unit_C6L build_src_filter = ${M5Stack_Unit_C6L.build_src_filter} @@ -505,6 +838,25 @@ build_flags = lib_deps = ${M5Stack_Unit_C6L.lib_deps} +[env:M5Stack_Unit_C6L_repeater_observer_espnow] +extends = env:M5Stack_Unit_C6L_repeater_observer +build_flags = + ${env:M5Stack_Unit_C6L_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:M5Stack_Unit_C6L_repeater_observer.build_src_filter} + + + +[env:M5Stack_Unit_C6L_repeater_observer_mqtt_bridge] +extends = env:M5Stack_Unit_C6L_repeater_observer +build_flags = + ${env:M5Stack_Unit_C6L_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:M5Stack_Unit_C6L_repeater_observer.build_src_filter} + + + +# ===== Meshadventurer ===== + [env:Meshadventurer_sx1262_repeater_observer] extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} @@ -532,6 +884,23 @@ lib_deps = ${Meshadventurer.lib_deps} ${esp32_ota.lib_deps} +[env:Meshadventurer_sx1262_repeater_observer_espnow] +extends = env:Meshadventurer_sx1262_repeater_observer +build_flags = + ${env:Meshadventurer_sx1262_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:Meshadventurer_sx1262_repeater_observer.build_src_filter} + + + +[env:Meshadventurer_sx1262_repeater_observer_mqtt_bridge] +extends = env:Meshadventurer_sx1262_repeater_observer +build_flags = + ${env:Meshadventurer_sx1262_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Meshadventurer_sx1262_repeater_observer.build_src_filter} + + + [env:Meshadventurer_sx1268_repeater_observer] extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} @@ -559,6 +928,25 @@ lib_deps = ${Meshadventurer.lib_deps} ${esp32_ota.lib_deps} +[env:Meshadventurer_sx1268_repeater_observer_espnow] +extends = env:Meshadventurer_sx1268_repeater_observer +build_flags = + ${env:Meshadventurer_sx1268_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:Meshadventurer_sx1268_repeater_observer.build_src_filter} + + + +[env:Meshadventurer_sx1268_repeater_observer_mqtt_bridge] +extends = env:Meshadventurer_sx1268_repeater_observer +build_flags = + ${env:Meshadventurer_sx1268_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Meshadventurer_sx1268_repeater_observer.build_src_filter} + + + +# ===== Meshimi ===== + [env:Meshimi_repeater_observer] extends = Meshimi build_src_filter = ${Meshimi.build_src_filter} @@ -581,6 +969,17 @@ build_flags = lib_deps = ${Meshimi.lib_deps} +[env:Meshimi_repeater_observer_mqtt_bridge] +extends = env:Meshimi_repeater_observer +build_flags = + ${env:Meshimi_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Meshimi_repeater_observer.build_src_filter} + + + +# ===== Nibble ===== + [env:nibble_screen_connect_repeater_observer] extends = nibble_screen_connect_base board_build.partitions = min_spiffs.csv ; get around 4mb flash limit @@ -607,6 +1006,25 @@ lib_deps = ${nibble_screen_connect_base.lib_deps} ${esp32_ota.lib_deps} +[env:nibble_screen_connect_repeater_observer_espnow] +extends = env:nibble_screen_connect_repeater_observer +build_flags = + ${env:nibble_screen_connect_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:nibble_screen_connect_repeater_observer.build_src_filter} + + + +[env:nibble_screen_connect_repeater_observer_mqtt_bridge] +extends = env:nibble_screen_connect_repeater_observer +build_flags = + ${env:nibble_screen_connect_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:nibble_screen_connect_repeater_observer.build_src_filter} + + + +# ===== RAK ===== + [env:RAK_3112_repeater_observer] extends = rak3112 build_flags = @@ -631,6 +1049,25 @@ lib_deps = ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 +[env:RAK_3112_repeater_observer_espnow] +extends = env:RAK_3112_repeater_observer +build_flags = + ${env:RAK_3112_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:RAK_3112_repeater_observer.build_src_filter} + + + +[env:RAK_3112_repeater_observer_mqtt_bridge] +extends = env:RAK_3112_repeater_observer +build_flags = + ${env:RAK_3112_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:RAK_3112_repeater_observer.build_src_filter} + + + +# ===== Station ===== + [env:Station_G2_repeater_observer] extends = Station_G2 build_flags = @@ -654,6 +1091,23 @@ lib_deps = ${Station_G2.lib_deps} ${esp32_ota.lib_deps} +[env:Station_G2_repeater_observer_espnow] +extends = env:Station_G2_repeater_observer +build_flags = + ${env:Station_G2_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:Station_G2_repeater_observer.build_src_filter} + + + +[env:Station_G2_repeater_observer_mqtt_bridge] +extends = env:Station_G2_repeater_observer +build_flags = + ${env:Station_G2_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Station_G2_repeater_observer.build_src_filter} + + + [env:Station_G2_logging_repeater_observer] extends = Station_G2 build_flags = @@ -679,6 +1133,23 @@ lib_deps = ${Station_G2.lib_deps} ${esp32_ota.lib_deps} +[env:Station_G2_logging_repeater_observer_espnow] +extends = env:Station_G2_logging_repeater_observer +build_flags = + ${env:Station_G2_logging_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:Station_G2_logging_repeater_observer.build_src_filter} + + + +[env:Station_G2_logging_repeater_observer_mqtt_bridge] +extends = env:Station_G2_logging_repeater_observer +build_flags = + ${env:Station_G2_logging_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Station_G2_logging_repeater_observer.build_src_filter} + + + [env:Station_G3_ESP32_repeater_observer] extends = Station_G3_ESP32 board_build.partitions = min_spiffs.csv ; get around default app partition limit @@ -703,6 +1174,25 @@ lib_deps = ${Station_G3_ESP32.lib_deps} ${esp32_ota.lib_deps} +[env:Station_G3_ESP32_repeater_observer_espnow] +extends = env:Station_G3_ESP32_repeater_observer +build_flags = + ${env:Station_G3_ESP32_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:Station_G3_ESP32_repeater_observer.build_src_filter} + + + +[env:Station_G3_ESP32_repeater_observer_mqtt_bridge] +extends = env:Station_G3_ESP32_repeater_observer +build_flags = + ${env:Station_G3_ESP32_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Station_G3_ESP32_repeater_observer.build_src_filter} + + + +# ===== T-Beam ===== + [env:T_Beam_S3_Supreme_SX1262_repeater_observer] extends = T_Beam_S3_Supreme_SX1262 build_flags = @@ -726,6 +1216,23 @@ lib_deps = ${T_Beam_S3_Supreme_SX1262.lib_deps} ${esp32_ota.lib_deps} +[env:T_Beam_S3_Supreme_SX1262_repeater_observer_espnow] +extends = env:T_Beam_S3_Supreme_SX1262_repeater_observer +build_flags = + ${env:T_Beam_S3_Supreme_SX1262_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:T_Beam_S3_Supreme_SX1262_repeater_observer.build_src_filter} + + + +[env:T_Beam_S3_Supreme_SX1262_repeater_observer_mqtt_bridge] +extends = env:T_Beam_S3_Supreme_SX1262_repeater_observer +build_flags = + ${env:T_Beam_S3_Supreme_SX1262_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:T_Beam_S3_Supreme_SX1262_repeater_observer.build_src_filter} + + + [env:Tbeam_SX1262_repeater_observer] extends = LilyGo_TBeam_SX1262 build_flags = @@ -749,6 +1256,23 @@ lib_deps = ${LilyGo_TBeam_SX1262.lib_deps} ${esp32_ota.lib_deps} +[env:Tbeam_SX1262_repeater_observer_espnow] +extends = env:Tbeam_SX1262_repeater_observer +build_flags = + ${env:Tbeam_SX1262_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:Tbeam_SX1262_repeater_observer.build_src_filter} + + + +[env:Tbeam_SX1262_repeater_observer_mqtt_bridge] +extends = env:Tbeam_SX1262_repeater_observer +build_flags = + ${env:Tbeam_SX1262_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Tbeam_SX1262_repeater_observer.build_src_filter} + + + [env:Tbeam_SX1276_repeater_observer] extends = LilyGo_TBeam_SX1276 build_flags = @@ -773,6 +1297,25 @@ lib_deps = ${LilyGo_TBeam_SX1276.lib_deps} ${esp32_ota.lib_deps} +[env:Tbeam_SX1276_repeater_observer_espnow] +extends = env:Tbeam_SX1276_repeater_observer +build_flags = + ${env:Tbeam_SX1276_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:Tbeam_SX1276_repeater_observer.build_src_filter} + + + +[env:Tbeam_SX1276_repeater_observer_mqtt_bridge] +extends = env:Tbeam_SX1276_repeater_observer +build_flags = + ${env:Tbeam_SX1276_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Tbeam_SX1276_repeater_observer.build_src_filter} + + + +# ===== ThinkNode ===== + [env:ThinkNode_M2_Repeater_observer] extends = ThinkNode_M2 board_build.partitions = min_spiffs.csv ; get around 4mb flash limit @@ -797,6 +1340,23 @@ lib_deps = ${ThinkNode_M2.lib_deps} ${esp32_ota.lib_deps} +[env:ThinkNode_M2_Repeater_observer_espnow] +extends = env:ThinkNode_M2_Repeater_observer +build_flags = + ${env:ThinkNode_M2_Repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:ThinkNode_M2_Repeater_observer.build_src_filter} + + + +[env:ThinkNode_M2_Repeater_observer_mqtt_bridge] +extends = env:ThinkNode_M2_Repeater_observer +build_flags = + ${env:ThinkNode_M2_Repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:ThinkNode_M2_Repeater_observer.build_src_filter} + + + [env:ThinkNode_M5_Repeater_observer] extends = ThinkNode_M5 board_build.partitions = min_spiffs.csv ; get around 4mb flash limit @@ -821,6 +1381,25 @@ lib_deps = ${ThinkNode_M5.lib_deps} ${esp32_ota.lib_deps} +[env:ThinkNode_M5_Repeater_observer_espnow] +extends = env:ThinkNode_M5_Repeater_observer +build_flags = + ${env:ThinkNode_M5_Repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:ThinkNode_M5_Repeater_observer.build_src_filter} + + + +[env:ThinkNode_M5_Repeater_observer_mqtt_bridge] +extends = env:ThinkNode_M5_Repeater_observer +build_flags = + ${env:ThinkNode_M5_Repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:ThinkNode_M5_Repeater_observer.build_src_filter} + + + +# ===== WHY2025 ===== + [env:WHY2025_badge_repeater_observer] extends = WHY2025_badge build_src_filter = ${WHY2025_badge.build_src_filter} @@ -843,6 +1422,25 @@ build_flags = lib_deps = ${WHY2025_badge.lib_deps} +[env:WHY2025_badge_repeater_observer_espnow] +extends = env:WHY2025_badge_repeater_observer +build_flags = + ${env:WHY2025_badge_repeater_observer.build_flags} + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${env:WHY2025_badge_repeater_observer.build_src_filter} + + + +[env:WHY2025_badge_repeater_observer_mqtt_bridge] +extends = env:WHY2025_badge_repeater_observer +build_flags = + ${env:WHY2025_badge_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:WHY2025_badge_repeater_observer.build_src_filter} + + + +# ===== Xiao ===== + [env:Xiao_C6_repeater_observer] extends = Xiao_C6 build_src_filter = ${Xiao_C6.build_src_filter} @@ -865,6 +1463,15 @@ build_flags = lib_deps = ${Xiao_C6.lib_deps} +[env:Xiao_C6_repeater_observer_mqtt_bridge] +extends = env:Xiao_C6_repeater_observer +build_flags = + ${env:Xiao_C6_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Xiao_C6_repeater_observer.build_src_filter} + + + [env:Xiao_S3_WIO_repeater_observer] extends = Xiao_S3_WIO build_src_filter = ${Xiao_S3_WIO.build_src_filter} @@ -888,270 +1495,6 @@ lib_deps = ${Xiao_S3_WIO.lib_deps} ${esp32_ota.lib_deps} -[env:Ebyte_EoRa-S3_Repeater_observer_espnow] -extends = env:Ebyte_EoRa-S3_Repeater_observer -build_flags = - ${env:Ebyte_EoRa-S3_Repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:Ebyte_EoRa-S3_Repeater_observer.build_src_filter} - + - -[env:Generic_E22_sx1262_repeater_observer_espnow] -extends = env:Generic_E22_sx1262_repeater_observer -build_flags = - ${env:Generic_E22_sx1262_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:Generic_E22_sx1262_repeater_observer.build_src_filter} - + - -[env:Generic_E22_sx1268_repeater_observer_espnow] -extends = env:Generic_E22_sx1268_repeater_observer -build_flags = - ${env:Generic_E22_sx1268_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:Generic_E22_sx1268_repeater_observer.build_src_filter} - + - -[env:Heltec_E213_repeater_observer_espnow] -extends = env:Heltec_E213_repeater_observer -build_flags = - ${env:Heltec_E213_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:Heltec_E213_repeater_observer.build_src_filter} - + - -[env:Heltec_E290_repeater_observer_espnow] -extends = env:Heltec_E290_repeater_observer -build_flags = - ${env:Heltec_E290_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:Heltec_E290_repeater_observer.build_src_filter} - + - -[env:Heltec_T190_repeater_observer_espnow] -extends = env:Heltec_T190_repeater_observer -build_flags = - ${env:Heltec_T190_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:Heltec_T190_repeater_observer.build_src_filter} - + - -[env:heltec_tracker_v2_repeater_observer_espnow] -extends = env:heltec_tracker_v2_repeater_observer -build_flags = - ${env:heltec_tracker_v2_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:heltec_tracker_v2_repeater_observer.build_src_filter} - + - -[env:Heltec_v2_repeater_observer_espnow] -extends = env:Heltec_v2_repeater_observer -build_flags = - ${env:Heltec_v2_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:Heltec_v2_repeater_observer.build_src_filter} - + - -[env:Heltec_v3_repeater_observer_espnow] -extends = env:Heltec_v3_repeater_observer -build_flags = - ${env:Heltec_v3_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:Heltec_v3_repeater_observer.build_src_filter} - + - -[env:heltec_v4_repeater_observer_espnow] -extends = env:heltec_v4_repeater_observer -build_flags = - ${env:heltec_v4_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:heltec_v4_repeater_observer.build_src_filter} - + - -[env:heltec_v4_tft_repeater_observer_espnow] -extends = env:heltec_v4_tft_repeater_observer -build_flags = - ${env:heltec_v4_tft_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:heltec_v4_tft_repeater_observer.build_src_filter} - + - -[env:Heltec_Wireless_Paper_repeater_observer_espnow] -extends = env:Heltec_Wireless_Paper_repeater_observer -build_flags = - ${env:Heltec_Wireless_Paper_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:Heltec_Wireless_Paper_repeater_observer.build_src_filter} - + - -[env:Heltec_Wireless_Tracker_repeater_observer_espnow] -extends = env:Heltec_Wireless_Tracker_repeater_observer -build_flags = - ${env:Heltec_Wireless_Tracker_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:Heltec_Wireless_Tracker_repeater_observer.build_src_filter} - + - -[env:Heltec_WSL3_repeater_observer_espnow] -extends = env:Heltec_WSL3_repeater_observer -build_flags = - ${env:Heltec_WSL3_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:Heltec_WSL3_repeater_observer.build_src_filter} - + - -[env:LilyGo_T3S3_sx1262_repeater_observer_espnow] -extends = env:LilyGo_T3S3_sx1262_repeater_observer -build_flags = - ${env:LilyGo_T3S3_sx1262_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:LilyGo_T3S3_sx1262_repeater_observer.build_src_filter} - + - -[env:LilyGo_T3S3_sx1276_repeater_observer_espnow] -extends = env:LilyGo_T3S3_sx1276_repeater_observer -build_flags = - ${env:LilyGo_T3S3_sx1276_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:LilyGo_T3S3_sx1276_repeater_observer.build_src_filter} - + - -[env:LilyGo_TBeam_1W_repeater_observer_espnow] -extends = env:LilyGo_TBeam_1W_repeater_observer -build_flags = - ${env:LilyGo_TBeam_1W_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:LilyGo_TBeam_1W_repeater_observer.build_src_filter} - + - -[env:LilyGo_TDeck_repeater_observer_espnow] -extends = env:LilyGo_TDeck_repeater_observer -build_flags = - ${env:LilyGo_TDeck_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:LilyGo_TDeck_repeater_observer.build_src_filter} - + - -[env:LilyGo_Tlora_C6_repeater_observer_espnow] -extends = env:LilyGo_Tlora_C6_repeater_observer -build_flags = - ${env:LilyGo_Tlora_C6_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:LilyGo_Tlora_C6_repeater_observer.build_src_filter} - + - -[env:M5Stack_Unit_C6L_repeater_observer_espnow] -extends = env:M5Stack_Unit_C6L_repeater_observer -build_flags = - ${env:M5Stack_Unit_C6L_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:M5Stack_Unit_C6L_repeater_observer.build_src_filter} - + - -[env:Meshadventurer_sx1262_repeater_observer_espnow] -extends = env:Meshadventurer_sx1262_repeater_observer -build_flags = - ${env:Meshadventurer_sx1262_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:Meshadventurer_sx1262_repeater_observer.build_src_filter} - + - -[env:Meshadventurer_sx1268_repeater_observer_espnow] -extends = env:Meshadventurer_sx1268_repeater_observer -build_flags = - ${env:Meshadventurer_sx1268_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:Meshadventurer_sx1268_repeater_observer.build_src_filter} - + - -[env:nibble_screen_connect_repeater_observer_espnow] -extends = env:nibble_screen_connect_repeater_observer -build_flags = - ${env:nibble_screen_connect_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:nibble_screen_connect_repeater_observer.build_src_filter} - + - -[env:RAK_3112_repeater_observer_espnow] -extends = env:RAK_3112_repeater_observer -build_flags = - ${env:RAK_3112_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:RAK_3112_repeater_observer.build_src_filter} - + - -[env:Station_G2_repeater_observer_espnow] -extends = env:Station_G2_repeater_observer -build_flags = - ${env:Station_G2_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:Station_G2_repeater_observer.build_src_filter} - + - -[env:Station_G2_logging_repeater_observer_espnow] -extends = env:Station_G2_logging_repeater_observer -build_flags = - ${env:Station_G2_logging_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:Station_G2_logging_repeater_observer.build_src_filter} - + - -[env:Station_G3_ESP32_repeater_observer_espnow] -extends = env:Station_G3_ESP32_repeater_observer -build_flags = - ${env:Station_G3_ESP32_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:Station_G3_ESP32_repeater_observer.build_src_filter} - + - -[env:T_Beam_S3_Supreme_SX1262_repeater_observer_espnow] -extends = env:T_Beam_S3_Supreme_SX1262_repeater_observer -build_flags = - ${env:T_Beam_S3_Supreme_SX1262_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:T_Beam_S3_Supreme_SX1262_repeater_observer.build_src_filter} - + - -[env:Tbeam_SX1262_repeater_observer_espnow] -extends = env:Tbeam_SX1262_repeater_observer -build_flags = - ${env:Tbeam_SX1262_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:Tbeam_SX1262_repeater_observer.build_src_filter} - + - -[env:Tbeam_SX1276_repeater_observer_espnow] -extends = env:Tbeam_SX1276_repeater_observer -build_flags = - ${env:Tbeam_SX1276_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:Tbeam_SX1276_repeater_observer.build_src_filter} - + - -[env:ThinkNode_M2_Repeater_observer_espnow] -extends = env:ThinkNode_M2_Repeater_observer -build_flags = - ${env:ThinkNode_M2_Repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:ThinkNode_M2_Repeater_observer.build_src_filter} - + - -[env:ThinkNode_M5_Repeater_observer_espnow] -extends = env:ThinkNode_M5_Repeater_observer -build_flags = - ${env:ThinkNode_M5_Repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:ThinkNode_M5_Repeater_observer.build_src_filter} - + - -[env:WHY2025_badge_repeater_observer_espnow] -extends = env:WHY2025_badge_repeater_observer -build_flags = - ${env:WHY2025_badge_repeater_observer.build_flags} - -D WITH_ESPNOW_BRIDGE=1 -build_src_filter = ${env:WHY2025_badge_repeater_observer.build_src_filter} - + - [env:Xiao_S3_WIO_repeater_observer_espnow] extends = env:Xiao_S3_WIO_repeater_observer build_flags = @@ -1159,3 +1502,12 @@ build_flags = -D WITH_ESPNOW_BRIDGE=1 build_src_filter = ${env:Xiao_S3_WIO_repeater_observer.build_src_filter} + + +[env:Xiao_S3_WIO_repeater_observer_mqtt_bridge] +extends = env:Xiao_S3_WIO_repeater_observer +build_flags = + ${env:Xiao_S3_WIO_repeater_observer.build_flags} + -D WITH_MQTT_BRIDGE=1 + -UMQTT_DEBUG +build_src_filter = ${env:Xiao_S3_WIO_repeater_observer.build_src_filter} + +