From 15c7072d81548214a76e13ed08efefe5e9c19569 Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 16 May 2026 13:01:09 -0600 Subject: [PATCH] Add repo scaffolding for Colorado-Mesh publish Adds the project metadata and CI/release workflow needed to publish this repo under Colorado-Mesh/meshcore-bot-firmware. - LICENSE: MIT with attribution to upstream MeshCore (also MIT) - README: rewritten with badges, supported boards, install paths (web flasher / PlatformIO / esptool), bot command table, layout map, development quickstart - CONTRIBUTING: submodule + patch-queue workflow, what-goes-where, patch hygiene rules, PR expectations - RELEASE: cmesh-bot-vX.Y.Z tag scheme + procedure - CHANGELOG: Keep-a-Changelog format, seeded with the hop-coordination fix and patch-5 utility commands - Issue templates: bug_report (with bot stats prompt), feature_request, config.yml (no blank issues, link to upstream + community) - Pull request template: layer checklist, verification gates, prefs-schema impact prompt - .github/workflows/firmware-build.yml: keep PR check on representative envs (Heltec V3, RAK 4631 USB+BLE), add concurrency cancellation for PRs, scope cache key to PIO config hash - .github/workflows/release.yml: NEW; triggers on cmesh-bot-v* tag or manual dispatch, enumerates all 133 *_companion_radio_(usb|ble) envs from vendor/MeshCore/variants/, shards across 8 parallel matrix jobs, aggregates artifacts into a single draft release - scripts/verify.sh: respect MESHCORE_SKIP_APPLY_PATCHES like build-representative.sh does, so verify works when vendor already has patches applied as commits - .gitignore: add .forge.bak.*/ for forge backup directories --- .github/ISSUE_TEMPLATE/bug_report.yml | 61 ++++++ .github/ISSUE_TEMPLATE/config.yml | 8 + .github/ISSUE_TEMPLATE/feature_request.yml | 28 +++ .github/PULL_REQUEST_TEMPLATE.md | 28 +++ .github/workflows/firmware-build.yml | 12 +- .github/workflows/release.yml | 205 +++++++++++++++++++++ .gitignore | 1 + CHANGELOG.md | 35 ++++ CONTRIBUTING.md | 103 +++++++++++ LICENSE | 26 +++ README.md | 152 +++++++++++++-- RELEASE.md | 81 ++++++++ scripts/verify.sh | 4 +- 13 files changed, 727 insertions(+), 17 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/release.yml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 RELEASE.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..ad2c440 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,61 @@ +name: Bug report +description: Something's broken in the firmware or tooling +labels: [bug] +body: + - type: markdown + attributes: + value: | + Thanks for the report. The more concrete, the faster we can chase it down. + - type: input + id: firmware_version + attributes: + label: Firmware version + description: From `bot` (no args) CLI command, the `version` chat command, or the filename you flashed. + placeholder: cmesh-bot-v1.2.3-abc1234 + validations: + required: true + - type: dropdown + id: board + attributes: + label: Board + description: Which PlatformIO env / hardware? + options: + - Heltec V3 (USB) + - Heltec V3 (BLE) + - RAK 4631 (USB) + - RAK 4631 (BLE) + - LilyGo T-Echo + - LilyGo T-Deck + - Xiao S3 WIO + - Other (specify below) + validations: + required: true + - type: textarea + id: symptom + attributes: + label: What happened? + description: What did you observe? What did you expect instead? + validations: + required: true + - type: textarea + id: repro + attributes: + label: Reproduction steps + description: The shortest sequence of actions that reproduces the bug. + placeholder: | + 1. Flash cmesh-bot-vX.Y.Z to Heltec V3 + 2. From another node 3 hops away, send `ping` on #bot + 3. Observe: no reply within 30s + validations: + required: true + - type: textarea + id: bot_stats + attributes: + label: bot stats output (if available) + description: Output of `bot` and `bot stats` from the USB CLI rescue console, before and after the trigger. + render: shell + - type: textarea + id: other + attributes: + label: Anything else? + description: Logs, screenshots, hop diagrams, peer node names, etc. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..43bd93a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: MeshCore upstream + url: https://github.com/meshcore-dev/MeshCore/issues + about: Bugs that affect upstream MeshCore (not specific to the Colorado bot patches) should go upstream. + - name: Colorado Mesh community + url: https://github.com/Colorado-Mesh + about: General questions about Colorado Mesh — coverage, joining, hardware recommendations. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..6455683 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,28 @@ +name: Feature request +description: Suggest a new bot command, board, or capability +labels: [enhancement] +body: + - type: textarea + id: motivation + attributes: + label: What problem are you trying to solve? + description: Start with the use case, not the proposed solution. + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposal + description: What command, behavior, or board support would address the problem? + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives you've considered + description: Other commands, workflow changes, off-device tools, etc. + - type: textarea + id: scope + attributes: + label: Anything specific about scope or implementation? + description: e.g. should this be guarded by a CLI toggle? Does it need new bot prefs? Cross-bot coordination? diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..dbf148b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,28 @@ + + +## Summary + + + +## Affected layer + +- [ ] MeshCore submodule (`vendor/MeshCore/`) — patches re-exported +- [ ] Wrapper tooling (`scripts/`, `tests/`, `.github/`) +- [ ] Colorado overlay (`colorado/`) +- [ ] Docs only + +## Verification + +- [ ] `MESHCORE_SKIP_APPLY_PATCHES=1 bash scripts/verify.sh --no-build` passes locally +- [ ] (if firmware changed) Built a representative env and confirmed size impact in `out/size/summary.json` +- [ ] (if behavior changed on the radio) Flashed and tested on real hardware — note the board and what you tried: + > _e.g. Heltec V3, sent `ping`/`trace`/`path` at 0, 2, 4, 6 hops from another node on #bot_ + +## Bot-prefs schema impact + +- [ ] No change to `BotPrefs` shape or default values +- [ ] Schema changed → `BOT_PREFS_VERSION` bumped accordingly +- [ ] N/A diff --git a/.github/workflows/firmware-build.yml b/.github/workflows/firmware-build.yml index b3d3639..5ef3168 100644 --- a/.github/workflows/firmware-build.yml +++ b/.github/workflows/firmware-build.yml @@ -7,9 +7,15 @@ on: - main workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: representative-build: + name: Representative build (Heltec V3, RAK 4631) runs-on: ubuntu-latest + timeout-minutes: 30 steps: - name: Check out repository uses: actions/checkout@v4 @@ -22,7 +28,9 @@ jobs: path: | ~/.cache/pip ~/.platformio/.cache - key: ${{ runner.os }}-pio + key: ${{ runner.os }}-pio-${{ hashFiles('vendor/MeshCore/platformio.ini', 'vendor/MeshCore/variants/**/platformio.ini') }} + restore-keys: | + ${{ runner.os }}-pio- - name: Install Python uses: actions/setup-python@v5 @@ -32,7 +40,7 @@ jobs: - name: Install PlatformIO run: python3 -m pip install --upgrade platformio - - name: Run host and safety checks + - name: Run host tests and safety checks run: bash scripts/verify.sh --no-build - name: Build representative firmware diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..606cec1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,205 @@ +name: Release + +on: + push: + tags: + - 'cmesh-bot-v*' + workflow_dispatch: + inputs: + version: + description: 'Version label to embed (e.g. v1.2.3 or v1.2.3-rc.1). Required for manual runs.' + required: true + type: string + +permissions: + contents: write + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + enumerate: + name: Enumerate companion targets + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + version: ${{ steps.version.outputs.version }} + shards: ${{ steps.shards.outputs.shards }} + env_count: ${{ steps.shards.outputs.env_count }} + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Resolve release version + id: version + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + version="${{ inputs.version }}" + else + tag="${GITHUB_REF#refs/tags/}" + # cmesh-bot-vX.Y.Z[-...] -> vX.Y.Z[-...] + version="${tag#cmesh-bot-}" + fi + if [ -z "$version" ]; then + echo "Could not derive a version label." >&2 + exit 1 + fi + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "Release version: $version" + + - name: Enumerate companion USB+BLE environments and shard + id: shards + run: | + envs=$(grep -rhE '^\[env:' vendor/MeshCore/variants/ \ + | sort -u \ + | grep -E '_companion_radio_(usb|ble)\]$' \ + | sed -e 's/\[env://' -e 's/\]//') + count=$(printf '%s\n' "$envs" | wc -l | tr -d ' ') + echo "env_count=$count" >> "$GITHUB_OUTPUT" + echo "Found $count companion USB+BLE environments." + shards=$(jq -nc '[range(0;8)]') + echo "shards=$shards" >> "$GITHUB_OUTPUT" + + build: + name: Build shard ${{ matrix.shard }}/8 + needs: enumerate + runs-on: ubuntu-latest + timeout-minutes: 180 + strategy: + fail-fast: false + matrix: + shard: ${{ fromJSON(needs.enumerate.outputs.shards) }} + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Cache PlatformIO + uses: actions/cache@v4 + with: + path: | + ~/.cache/pip + ~/.platformio/.cache + ~/.platformio/packages + key: ${{ runner.os }}-pio-release-${{ hashFiles('vendor/MeshCore/platformio.ini', 'vendor/MeshCore/variants/**/platformio.ini') }} + restore-keys: | + ${{ runner.os }}-pio-release- + ${{ runner.os }}-pio- + + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install PlatformIO + run: python3 -m pip install --upgrade platformio + + - name: Apply patches + run: bash scripts/apply-patches.sh + + - name: Select envs for this shard + id: select + run: | + envs=$(grep -rhE '^\[env:' vendor/MeshCore/variants/ \ + | sort -u \ + | grep -E '_companion_radio_(usb|ble)\]$' \ + | sed -e 's/\[env://' -e 's/\]//') + shard_envs=$(printf '%s\n' "$envs" \ + | awk -v shard=${{ matrix.shard }} -v total=8 'NR%total==shard') + printf 'envs<> "$GITHUB_OUTPUT" + shard_count=$(printf '%s\n' "$shard_envs" | grep -c . || true) + echo "Shard ${{ matrix.shard }}: building $shard_count envs" + printf '%s\n' "$shard_envs" + + - name: Build firmware + env: + FIRMWARE_VERSION: ${{ needs.enumerate.outputs.version }} + run: | + set -e + mkdir -p out/firmware out/logs + cd vendor/MeshCore + while IFS= read -r env; do + [ -z "$env" ] && continue + log="../../out/logs/$env.log" + echo "::group::Building $env" + if /usr/bin/env bash build.sh build-firmware "$env" 2>&1 | tee "$log"; then + echo "::endgroup::" + if compgen -G "out/$env-*" >/dev/null; then + cp out/"$env"-* ../../out/firmware/ + fi + else + echo "::endgroup::" + echo "::error::Build failed for $env (see logs artifact)" + exit 1 + fi + done <<< "${{ steps.select.outputs.envs }}" + + - name: Upload firmware shard + if: always() + uses: actions/upload-artifact@v4 + with: + name: firmware-shard-${{ matrix.shard }} + path: out/firmware + if-no-files-found: warn + retention-days: 14 + + - name: Upload build logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: build-logs-shard-${{ matrix.shard }} + path: out/logs + if-no-files-found: warn + retention-days: 14 + + release: + name: Publish draft release + needs: [enumerate, build] + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Download all firmware shards + uses: actions/download-artifact@v4 + with: + pattern: firmware-shard-* + merge-multiple: true + path: release-firmware + + - name: List release artifacts + run: | + count=$(find release-firmware -maxdepth 1 -type f | wc -l | tr -d ' ') + echo "Aggregated $count release artifacts:" + ls -la release-firmware/ + + - name: Create draft release + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') + with: + name: Colorado Mesh Bot Firmware ${{ needs.enumerate.outputs.version }} + tag_name: ${{ github.ref_name }} + draft: true + prerelease: ${{ contains(needs.enumerate.outputs.version, '-') }} + generate_release_notes: true + body: | + Companion-radio firmware for the Colorado Mesh. + + **Built environments:** ${{ needs.enumerate.outputs.env_count }} companion USB+BLE targets across ESP32, NRF52, RP2040, and STM32 platforms. + + See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md) for changes in this version. + + ### Install + + Easiest: flash via [flasher.meshcore.io](https://flasher.meshcore.io) and drag in the right artifact for your board. + + - ESP32 boards: `-merged.bin` + - NRF52 boards: `.uf2` + + See the [README](https://github.com/${{ github.repository }}#install) for other install methods. + files: release-firmware/* diff --git a/.gitignore b/.gitignore index aea94dc..a199c45 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .forge/ +.forge.bak.*/ out/ .venv/ __pycache__/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4d85398 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,35 @@ +# Changelog + +All notable changes to this firmware are documented here. Format follows +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), versioning +follows the `cmesh-bot-vX.Y.Z` scheme described in [RELEASE.md](RELEASE.md). + +## [Unreleased] + +### Added + +- Hop-aware response coordination: bot delays its reply proportional to + the sender's hop count so the closest bot wins; far bots suppress. +- Utility commands: `time`, `lora`, `id`, `neighbors`. +- Neighbor tracking: bot remembers nodes heard directly within the last + hour for the `neighbors` command. +- `received_at_timestamp` field on bot messages; personalized ack target + in command context. +- Comprehensive repository scaffolding: README, CONTRIBUTING, LICENSE, + CHANGELOG, RELEASE doc, issue/PR templates, release workflow building + all 133 companion USB+BLE environments. + +### Changed + +- `BOT_HOP_STEP_MILLIS_DEFAULT` lowered from 5000 to 1500 ms per hop. +- Added `BOT_HOP_BIAS_MAX_MILLIS` cap of 8000 ms on the total hop bias. +- `BOT_RESPONSE_PENDING_TTL_MILLIS` raised from 15000 to 45000 ms. +- `BOT_PREFS_VERSION` bumped 2 → 3 to force re-default of in-the-wild + bots that saved the broken 5000 ms hop-step value. + +### Fixed + +- Multi-hop senders on `#bot` no longer get silently ignored. Previously + 3+ hop responses scheduled past the 15 s TTL and were dropped by the + coordinator's `poll()` as `READY_EXPIRED`. Verified on a Heltec V3 + bench bot replying at 2, 4, and 6 hops. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2a8f0eb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,103 @@ +# Contributing to Colorado Mesh Bot Firmware + +Thanks for your interest. This repo is a thin wrapper around upstream +MeshCore: most firmware code lives in `vendor/MeshCore/` (a git submodule) +and is applied via the patch queue in `patches/meshcore/`. Colorado-only +overlay files, tests, and tooling live at the wrapper level. + +## Quick reference + +| Task | Command | +|---|---| +| Bootstrap | `git submodule update --init --recursive && bash scripts/apply-patches.sh` | +| Run host tests + safety checks | `MESHCORE_SKIP_APPLY_PATCHES=1 bash scripts/verify.sh --no-build` | +| Build representative firmwares | `bash scripts/build-representative.sh --baseline` | +| Export patch queue after edits | `bash scripts/export-patches.sh origin/main` | + +## Development workflow + +The submodule is a real git repo. Iterate inside it like normal: + +1. **Bootstrap once** (clone with `--recurse-submodules`, or run + `git submodule update --init --recursive`). +2. **Apply patches** with `bash scripts/apply-patches.sh`. This commits the + patch queue on top of `origin/main` in the submodule, leaving you with + a clean tree pinned to a known state. +3. **Make changes inside `vendor/MeshCore/`** — edit files, run + `pio run -e ` to compile, commit your changes in the submodule + with descriptive messages. +4. **Re-export the patch queue** with + `bash scripts/export-patches.sh origin/main`. This deletes + `patches/meshcore/*.patch` and regenerates it from the submodule + commits since `origin/main`. Each patch corresponds to one submodule + commit, so keep commits small and focused. +5. **Run host tests** (`python3 tests/firmware_bot/run_tests.py`) — fast, + no hardware needed. +6. **Run safety checks** (`bash scripts/check-bot-safety.sh`) — verifies + the firmware doesn't expose private keys, network bridges, or other + debug-only features in release builds. +7. **(Optional) Build representative envs** with + `bash scripts/build-representative.sh --baseline` to confirm the + binaries still link and to inspect size impact in `out/size/summary.json`. +8. **Commit at the wrapper level** with the regenerated patches, any + test/script changes, and the updated submodule pointer. PR-ready. + +## What goes where + +- **Firmware/library code that MUST live inside MeshCore** for PlatformIO + builds — develop inside `vendor/MeshCore/`, export as a patch. +- **Test code (host-side C++ tests)** — `tests/firmware_bot/` at the + wrapper level. They `#include` MeshCore headers via the script's `-I` + flag. +- **Tooling, build scripts, CI** — `scripts/` and `.github/` at the + wrapper level. +- **Colorado-specific docs, fixtures, board notes** — `colorado/`. + +## Patch hygiene + +- One conceptual change per patch / submodule commit. +- Prefer additive changes (`#if CMESH_BOT_ENABLED` guards, new files in + `examples/companion_radio/`) over edits to broadly-used upstream code. + This keeps merges with upstream MeshCore manageable. +- When changing a stored format (e.g., bot prefs), bump + `BOT_PREFS_VERSION` so deployed bots reset to the new defaults + instead of loading broken legacy state. +- Add a host test for new behavior whenever it's testable without + hardware. The bot has 48+ host tests covering the registry, parser, + policy, coordinator, and command output strings. + +## CI + +PRs run `.github/workflows/firmware-build.yml`, which: + +1. Runs host tests + safety checks (`scripts/verify.sh --no-build`). +2. Builds the four representative companion environments (Heltec V3 and + RAK 4631, USB + BLE). +3. Uploads firmware + size reports as workflow artifacts. + +Releases run `.github/workflows/release.yml` on `cmesh-bot-v*` tags and +build all 133 companion USB+BLE environments — see [RELEASE.md](RELEASE.md). + +## Pull request expectations + +- One PR per logical change. Bundle related patches; keep unrelated work + in separate PRs. +- Describe the **why** as well as the what. +- If you touched response timing, coordination, or anything that could + silently drop messages, include a host test that exercises the + failure mode and explain how you verified on hardware (which boards, + what commands, what hop counts). +- CI must be green. +- Don't commit `.forge/` workflow artifacts, the `.venv/` directory, or + PlatformIO build output. They're gitignored. + +## Reporting bugs + +[Open an issue](https://github.com/Colorado-Mesh/meshcore-bot-firmware/issues/new/choose). +For firmware bugs, include the output of `bot stats` (over USB CLI rescue), +the firmware version, board, and a minimal reproduction. + +## Code of conduct + +Be respectful, be patient, assume good faith. We're all volunteers building +infrastructure for a mesh community. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c900720 --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +MIT License + +Copyright (c) 2026 Colorado Mesh contributors + +Portions of this repository are derived from MeshCore (https://github.com/meshcore-dev/MeshCore), +Copyright (c) 2025 Scott Powell / rippleradios.com, used under the MIT License. The +upstream MeshCore source is included as a git submodule under vendor/MeshCore and +retains its original license notice in vendor/MeshCore/license.txt. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 8ae2f20..98d5a3f 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,123 @@ -# Colorado MeshCore Bot Firmware +# Colorado Mesh Bot Firmware -Wrapper repository for Colorado Mesh firmware-only bot work on top of upstream MeshCore companion firmware. +[![Firmware Build](https://github.com/Colorado-Mesh/meshcore-bot-firmware/actions/workflows/firmware-build.yml/badge.svg)](https://github.com/Colorado-Mesh/meshcore-bot-firmware/actions/workflows/firmware-build.yml) +[![Latest Release](https://img.shields.io/github/v/release/Colorado-Mesh/meshcore-bot-firmware?include_prereleases&sort=semver)](https://github.com/Colorado-Mesh/meshcore-bot-firmware/releases) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) + +Companion-radio firmware for the Colorado Mesh community, built on top of +[MeshCore](https://github.com/meshcore-dev/MeshCore). It adds an in-firmware +"firmware bot" that responds to chat commands like `ping`, `trace`, `path`, +`status`, `neighbors`, and `magic8`, with multi-bot response coordination so +nearby Colorado bots don't all reply at once. + +The bot runs entirely on-device — no internet, no companion-app bridge, no +cloud. Useful as a low-friction probe / utility node on the Colorado mesh. + +## What's different from upstream MeshCore + +The release-built firmware applies a patch queue (`patches/meshcore/`) on top +of a pinned MeshCore commit. The patches add: + +- A bot command registry (`help`, `cmd`, `ping`, `test`, `hello`, `about`, + `roll`, `dice`, `status`, `channels`, `version`, `stats`, `magic8`, `path`, + `trace`, `tracer`, `prefix`, `time`, `lora`, `id`, `neighbors`) +- Channel-aware response policy (`#bot`, `#testing` for normal commands; + `#emergency` for emergency forwards to `Public`; all other channels ignored) +- Multi-bot response coordinator with hop-aware delay, bounded TTL, and + fingerprint-based suppression so duplicates are avoided across the mesh +- Direct-message (DM) prefixless command parsing +- Two-byte path hash default for compact traces +- Private-key import/export disabled in release builds +- Host-side bot prefs (CLI tunable channels, delay, advert intervals, + known-bot list) + +See `patches/meshcore/` for the full set; each patch is a standalone commit +re-exported from the submodule. + +## Supported boards + +The release workflow builds **every** `*_companion_radio_usb` and +`*_companion_radio_ble` PlatformIO environment exposed by upstream MeshCore +— 133 boards across ESP32, NRF52, RP2040, and STM32 platforms (Heltec V3, +RAK 4631, LilyGo T-Echo, T-Beam, T-Deck, Xiao S3, Nano G2, ThinkNode, +Meshtiny, Pico W, and many more). See the +[latest release assets](https://github.com/Colorado-Mesh/meshcore-bot-firmware/releases/latest) +for the full list. + +PR CI builds a smaller "representative" matrix (Heltec V3 USB+BLE, RAK 4631 +USB+BLE) on every push, so the most common boards get fast feedback. + +## Install + +Three ways to flash: + +### MeshCore web flasher (easiest) + +1. Open [flasher.meshcore.io](https://flasher.meshcore.io) in Chrome/Edge. +2. Pick "Custom firmware" and drag in the `*-merged.bin` (ESP32) or `*.uf2` + (NRF52) file from the + [latest release](https://github.com/Colorado-Mesh/meshcore-bot-firmware/releases/latest) + for your board. +3. Plug the board in, click "Connect", and flash. + +### PlatformIO (from source) + +```sh +git clone --recurse-submodules https://github.com/Colorado-Mesh/meshcore-bot-firmware.git +cd meshcore-bot-firmware +python3 -m venv .venv && .venv/bin/python -m pip install --upgrade platformio +bash scripts/apply-patches.sh +cd vendor/MeshCore +pio run -e Heltec_v3_companion_radio_usb -t upload --upload-port /dev/cu.usbserial-0001 +``` + +Swap the env name for your board (see +`grep -rE '^\[env:' vendor/MeshCore/variants/`). + +### esptool / nrfutil (manual) + +ESP32 boards: `esptool.py write_flash 0x0 -merged.bin` +NRF52 boards: drag the `.uf2` onto the bootloader mass-storage volume, or +use `adafruit-nrfutil dfu serial -pkg .zip -p `. + +## Using the bot + +Once flashed, the bot joins the channels configured in its prefs. By default +those are `#bot`, `#testing`, `#emergency`, and `Public`. Send commands on +`#bot` (or DM the bot directly) without any prefix — for example: + +| Command | Response | +|---|---| +| `ping` | `Pong` | +| `hello` | `Hello @[], from ` | +| `path` | Compact route summary back to you, e.g. `Path 3h@2B SNR -6.25 \| 2751 -> ea4d -> 430d` | +| `trace` | Active trace request along your reverse path | +| `status` | Bot uptime, battery, storage, send counters | +| `neighbors` | Nodes heard directly within the last hour | +| `version` | Firmware version + build date | +| `magic8 ` | Classic 8-ball answer | +| `help` | List all commands | + +In any other channel the bot stays silent (except `#emergency`, which it +re-forwards to `Public`). + +If you're testing alongside other Colorado bots, the response coordinator +adds a hop-aware delay (~1.5 s per hop, capped at 8 s) so the closest bot +wins the race and others suppress. This avoids spam on the channel. ## Layout - `vendor/MeshCore/` — pinned upstream MeshCore submodule. - `patches/meshcore/` — ordered patch queue applied to the submodule. - `colorado/` — Colorado Mesh overlay files, fixtures, and notes. -- `scripts/` — wrapper scripts for patch and build workflows. +- `scripts/` — wrapper scripts for patch, build, verify, and size-report + workflows. +- `tests/firmware_bot/` — host-side C++ unit tests for the bot code + (compile and run on your machine, no hardware required). +- `.github/workflows/` — PR CI (`firmware-build.yml`) and tag-driven release + (`release.yml`). -## Patch workflow +## Local development Initialize the submodule and apply patches: @@ -18,25 +126,41 @@ git submodule update --init --recursive bash scripts/apply-patches.sh ``` -Develop firmware changes in `vendor/MeshCore`, commit them in that submodule worktree, then export the patch queue: +Make firmware changes inside `vendor/MeshCore`, commit them in the submodule +worktree, then export the patch queue: ```sh bash scripts/export-patches.sh origin/main ``` -## Representative builds - -Build the release-gate companion environments and write size reports: +Build the four representative companion environments and write size reports: ```sh bash scripts/build-representative.sh --baseline ``` -The build writes PlatformIO logs and `summary.json` to `out/size/`, and copied firmware artifacts to `out/firmware/`. +Run host tests + safety checks: -Representative companion build environments: +```sh +python3 tests/firmware_bot/run_tests.py +bash scripts/check-bot-safety.sh +# Or all in one go: +MESHCORE_SKIP_APPLY_PATCHES=1 bash scripts/verify.sh --no-build +``` -- `Heltec_v3_companion_radio_usb` -- `Heltec_v3_companion_radio_ble` -- `RAK_4631_companion_radio_usb` -- `RAK_4631_companion_radio_ble` +## Releases + +Releases are cut by pushing a `cmesh-bot-vX.Y.Z` tag. See +[RELEASE.md](RELEASE.md) for the full procedure. + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md). The short version: edit inside +`vendor/MeshCore`, re-export the patch queue, run `verify.sh`, open a PR. +Patches stay in `patches/meshcore/` so reviewers can see exactly what +deviates from upstream. + +## License + +MIT, including upstream MeshCore. See [LICENSE](LICENSE) and +[vendor/MeshCore/license.txt](vendor/MeshCore/license.txt). diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..a16d919 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,81 @@ +# Cutting a release + +Releases are produced by pushing a `cmesh-bot-vX.Y.Z` tag. The +`.github/workflows/release.yml` workflow then: + +1. Builds **every** companion USB+BLE environment exposed by the pinned + MeshCore submodule (133 boards as of this writing). +2. Splits the build across 8 parallel matrix shards for wall-time. +3. Embeds the tag version (`vX.Y.Z`) into the firmware via + `FIRMWARE_VERSION`. +4. Uploads each shard's artifacts. +5. Aggregates everything into a single GitHub Release in draft state, + ready for you to add release notes and publish. + +## Prerequisites + +- All PRs for the release are merged to `main`. +- CI on `main` is green (host tests + representative builds). +- You've updated `CHANGELOG.md` with notes for the new version. +- Your local branch is up to date with `origin/main`. + +## Steps + +```sh +git checkout main +git pull origin main + +# Bump CHANGELOG.md, commit, push. +$EDITOR CHANGELOG.md +git commit -am "docs: prep cmesh-bot-vX.Y.Z release notes" +git push origin main + +# Tag and push. +git tag -a cmesh-bot-vX.Y.Z -m "Colorado Mesh Bot Firmware vX.Y.Z" +git push origin cmesh-bot-vX.Y.Z +``` + +The `release.yml` workflow will start automatically. Track progress at +. + +When the workflow finishes, a **draft** release will appear at +. +Review it, paste in the CHANGELOG entry as the description, and publish. + +## Tag scheme + +- `cmesh-bot-vX.Y.Z` — production release. +- `cmesh-bot-vX.Y.Z-rc.N` — release candidate (still publishes as draft). +- `cmesh-bot-vX.Y.Z-alpha.N` / `-beta.N` — pre-release, mark as + pre-release when publishing. + +The leading `cmesh-bot-` namespace leaves room for future variants +(repeater, room-server) without colliding. + +## Versioning + +Semantic-ish: bump major for breaking pref/protocol changes, minor for +new commands or boards, patch for fixes. Bump `BOT_PREFS_VERSION` in +`vendor/MeshCore/examples/companion_radio/BotTypes.h` whenever an +existing pref field's meaning or default changes — that forces deployed +bots to reset prefs on first boot. + +## Hotfix / unpublish + +If a published release turns out to be broken: + +1. Mark it as a pre-release (don't delete — flashed devices may still + reference the URL). +2. Tag and publish `cmesh-bot-vX.Y.(Z+1)` immediately. +3. Edit the broken release's notes to point at the fix. + +If you tagged the wrong commit and the workflow hasn't finished: + +```sh +git tag -d cmesh-bot-vX.Y.Z +git push --delete origin cmesh-bot-vX.Y.Z +# fix, re-tag, re-push +``` + +Avoid deleting tags after the workflow has published artifacts — users +may have already downloaded them. diff --git a/scripts/verify.sh b/scripts/verify.sh index b6b7ed8..e9b23f2 100644 --- a/scripts/verify.sh +++ b/scripts/verify.sh @@ -42,7 +42,9 @@ if [ ! -d "${MESHCORE_DIR}/.git" ] && [ ! -f "${MESHCORE_DIR}/.git" ]; then exit 1 fi -if ! git -C "${MESHCORE_DIR}" diff --quiet --cached || ! git -C "${MESHCORE_DIR}" diff --quiet || [ -n "$(git -C "${MESHCORE_DIR}" ls-files --others --exclude-standard)" ]; then +if [ "${MESHCORE_SKIP_APPLY_PATCHES:-0}" = "1" ]; then + echo "Skipping MeshCore patch application; using current vendor/MeshCore tree." +elif ! git -C "${MESHCORE_DIR}" diff --quiet --cached || ! git -C "${MESHCORE_DIR}" diff --quiet || [ -n "$(git -C "${MESHCORE_DIR}" ls-files --others --exclude-standard)" ]; then echo "MeshCore submodule is dirty; verify existing patched tree without applying patches." else "${MESHCORE_FW_ROOT}/scripts/apply-patches.sh"