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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .github/workflows/radio-presets.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Radio presets: validate on every change, and mirror MeshCore's upstream list daily.
#
# The mirror job fetches https://api.meshcore.nz/api/v1/config (MeshCore's preset list,
# maintained by Liam Cottle), validates it together with the overlay, and commits
# radio-presets/meshcore-upstream.json straight to main only when the presets changed. A
# fetch failure, a shape change or a title clash fails the run and commits nothing, so
# the last good mirror stays in place.
name: Radio presets

on:
schedule:
- cron: "17 6 * * *"
workflow_dispatch:
push:
branches: [main]
paths: ["radio-presets/**", "scripts/*radio_presets*.py", "scripts/mirror_meshcore_presets.py", ".github/workflows/radio-presets.yml"]
pull_request:
paths: ["radio-presets/**", "scripts/*radio_presets*.py", "scripts/mirror_meshcore_presets.py", ".github/workflows/radio-presets.yml"]

permissions:
contents: read

concurrency:
group: radio-presets-${{ github.ref }}
cancel-in-progress: false

jobs:
validate:
if: github.event_name == 'push' || github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate both preset files
run: python3 scripts/validate_radio_presets.py

mirror:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: main
- name: Fetch and validate the upstream list
run: python3 scripts/mirror_meshcore_presets.py
- name: Check for changes
id: diff
run: |
git add radio-presets/meshcore-upstream.json
if git diff --cached --quiet; then
echo "Mirror unchanged; nothing to commit."
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
# Before the commit, so main always passes its own validation. If a new upstream
# title clashes with one in offband.json, the run fails and nothing is committed;
# fix it in offband.json (set overrides_upstream, or retire ours) and rerun.
- name: Validate the overlay against the new mirror
if: steps.diff.outputs.changed == 'true'
run: python3 scripts/validate_radio_presets.py
- name: Commit the mirror
if: steps.diff.outputs.changed == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git commit -m "chore: mirror MeshCore radio presets from api.meshcore.nz"
git push origin HEAD:main
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,35 @@ raw.githubusercontent, S3, your own box). Give users the directory URL (or the
[`profiles.json.example`](profiles.json.example) as a starting point. A region
that would rather not depend on Offband can run entirely on its own catalog.

## Radio presets

The app's regional radio presets (frequency, bandwidth, spreading factor, coding
rate) live in [`radio-presets/`](radio-presets/), not in the app's code, so they can
change without an app release. The app ships with a copy and refreshes from here.

- [`meshcore-upstream.json`](radio-presets/meshcore-upstream.json): MeshCore's
suggested presets, the same list the stock MeshCore app shows. Mirrored daily and
unmodified by the [Radio presets](.github/workflows/radio-presets.yml) workflow.
Don't edit it by hand.
- [`offband.json`](radio-presets/offband.json): Offband's additions, for meshes the
upstream list doesn't cover yet (OKI-Mesh, Philly Mesh, Arizona, Russian cities,
Off-Grid). Where a title matches an upstream entry, ours wins.

To add or change a preset, edit `offband.json`, run
`python scripts/validate_radio_presets.py`, and open a PR. Field rules are in
[SCHEMA.md](SCHEMA.md#radio-presets-radio-presets).

### Credit

The upstream presets are maintained by **Liam Cottle** for
[MeshCore](https://github.com/meshcore-dev/MeshCore) and published at
`https://api.meshcore.nz/api/v1/config`, where MeshCore apps and tools read them.
They are community-suggested values; regional updates go to Liam through the
MeshCore Discord (see the [MeshCore FAQ](https://docs.meshcore.io/faq/)). Offband
mirrors them unchanged, with thanks.

## License

MIT — see [LICENSE](LICENSE). Profiles you contribute are shared under the same.
The mirrored MeshCore presets in `radio-presets/meshcore-upstream.json` are
community-suggested radio parameters, credited above.
77 changes: 77 additions & 0 deletions SCHEMA.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,80 @@ A user can point the app at any HTTP source. The tail of the URL decides how it'
There is **no** reliance on HTTP directory listing — a directory URL resolves to
`<dir>/profiles.json`. Host `profiles.json` + your YAMLs on any HTTP server and
your catalog behaves exactly like this one.

## Radio presets: `radio-presets/`

The app's regional radio preset list (frequency, bandwidth, spreading factor, coding
rate) comes from two files. The app ships with a copy of both, so it works offline,
and refreshes them from here. **An edit here reaches users on their next refresh,
with no app release** (GitHub's raw-file cache can delay it by about five minutes).

| File | What it is | Who edits it |
|---|---|---|
| `meshcore-upstream.json` | MeshCore's suggested presets, maintained by Liam Cottle, mirrored unmodified from `https://api.meshcore.nz/api/v1/config` | Nobody by hand. A scheduled workflow rewrites it when the upstream list changes |
| `offband.json` | Offband's overlay: presets the upstream list lacks | People, by PR |

**Precedence:** the app shows both lists. When an overlay entry has the same `title`
as an upstream entry, the overlay entry wins. That must be deliberate, so the entry
must also set `"overrides_upstream": true`; the validator rejects an unmarked clash.

Check your change before opening a PR:

```
python scripts/validate_radio_presets.py
```

### Overlay file: `offband.json`

```json
{
"format": "offband-radio-presets",
"format_version": 1,
"description": "...",
"presets": [
{
"id": "usa-philly-mesh",
"title": "USA - Philly Mesh",
"region": "USA",
"frequency": 919.5,
"bandwidth": 500,
"spreading_factor": 10,
"coding_rate": 5,
"tx_power": 20,
"path_hash_size": 2,
"status": "published",
"notes": "Where the values come from, and when they were checked."
}
]
}
```

| Field | Required | Rule |
|---|---|---|
| `id` | yes | Stable key: lowercase letters, digits, single hyphens. Never reuse or change it |
| `title` | yes | Shown in the picker; unique in this file |
| `region` | yes | Picker group, e.g. `USA`, `Russia`, `Off-Grid`. Use the same word the upstream titles start with, so both lists land in one group |
| `frequency` | yes | MHz, a JSON number, 150 to 2500 |
| `bandwidth` | yes | kHz, one of 7.8, 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125, 250, 500 |
| `spreading_factor` | yes | 5 to 12 |
| `coding_rate` | yes | The denominator of 4/x: 5 to 8 (4/5 is `5`) |
| `tx_power` | no | dBm, whole number, -9 to 30. Omit to leave the radio's power alone |
| `path_hash_size` | no | **Bytes**, 1 to 3. The app converts to the firmware's zero-based mode (2 bytes = mode 1). Omit to leave the radio's setting alone |
| `off_grid` | no | `true` marks a client-repeat (off-grid) frequency |
| `overrides_upstream` | no | `true` when this entry deliberately replaces an upstream entry with the same title |
| `status` | yes | `published` (shown) or `retired` (kept for history, hidden) |
| `notes` | no | Source and date of the values. Not shown in the app |

Numbers must be JSON numbers, not strings. Unknown keys are rejected.

**To add** a preset, append an entry with a new `id`. **To change** one, edit its
values in place and update `notes`. **To retire** one, set `status` to `"retired"`;
don't delete it, and never reuse its `id`.

### Upstream mirror: `meshcore-upstream.json`

`suggested_radio_settings` is copied byte-for-byte from the API (whose numbers are
strings), wrapped with `format`, `format_version`, `source`, `credit` and
`updated_at` (when the upstream content last changed). The workflow validates the
fetched list before writing; if the API is unreachable or its shape changes, the run
fails and the last good mirror stays in place.
Loading
Loading