Repository navigation
feature: MESH 500 -- regional preset system, an FCC-compliant US option, and a preset source of truth #647
Description
Cross-filed from firmware OffbandMesh/meshcore-firmware#1064 at the owner's direction (2026-09-09). The firmware side covers radio/driver verification at 500 kHz; this Feature covers everything the client owns. The owner will frame and assign the work on this side.
Scope clarification -- read this first
The "MeshCore 500" / Philly values are ONE regional option among many. They are not, and will not become, THE default.
This work gets summarized as "switch to 500 kHz," and that summary is wrong. What is in scope is a list of regional presets, of which a US FCC-compliant entry is one member. Europe, EU 433, Australia, New Zealand, Vietnam and the rest keep their own correct values -- the 500 kHz figure comes from 47 CFR, US law, with no bearing outside the US. Nothing here removes, replaces or deprecates a non-US preset.
Concretely: a compliant US entry is added. Existing entries stay. A user in Portugal still selects Portugal.
Any proposed change that alters behavior for a non-US operator is out of scope by definition and goes back to the owner.
Background -- why this is a client Feature at all
US narrow-bandwidth LoRa presets (62.5 / 125 / 250 kHz) do not satisfy 47 CFR 15.247(a)(2), which requires a minimum 6 dB bandwidth of >= 500 kHz for digital-modulation devices in 902-928 MHz. The community's compliant US answer, deployed by Philly Mesh and proposed upstream as the new USA preset (meshcore-dev/MeshCore#945), is:
902.250 MHz / 500 kHz / SF11 / CR 4:5 / path_hash_size 2
Note the five parameters. Most write-ups list four.
What we verified on the client [verified: C:\Dev\meshcore-open @ 2026-09-09]
-
We do not use the upstream preset registry.
grepforapi.meshcore.nzacross the client source returns zero hits. The stock MeshCore client fetches suggested radio settings from a live public endpoint (https://api.meshcore.nz/api/v1/config,config.suggested_radio_settings.entries). We do not. -
Our preset list is a hardcoded Dart literal in
lib/models/radio_settings.dart-- 46 entries, compiled in, no fetch, no update path. -
Zero of our 46 entries use 500 kHz bandwidth. The client cannot currently offer a US-compliant preset, and has no mechanism to ever learn about one.
-
The two lists have genuinely diverged -- ours is not a subset or a stale copy of theirs:
Ours (hardcoded) Upstream registry Entries 46 27 At 500 kHz 0 1 (Philly) Russia city presets 26 (MOW, LED, SVX, ...) none Off-Grid 433/869/918 3 none Philly / SoCal absent present Brazil, Costa Rica, Hungary, Netherlands x2, Slovakia absent present The 26 Russian city-level presets are inherited from the
zjs81/meshcore-openfork ancestry, not from upstream.USA Arizona(908.205 / SF10 / BW62.5) is likewise ours alone. OurUSA/Canada(910.525 / SF7 / BW62.5 / CR5) does match upstream's genericUSAexactly. -
RadioSettingshas no path-hash field, so the fifth parameter cannot be expressed even if a 500 kHz entry were added. -
Upstream's registry is itself mostly non-compliant -- of its 27 entries, 19 are BW62.5, 5 are BW250, 2 are BW125 and exactly 1 is BW500. Adopting it wholesale would not by itself deliver compliance; it would deliver one compliant option among 26 others.
Open architectural question -- NOT decided
Does the preset list live in firmware, client, or both? The owner is leaning client -- presets are a user-facing selection surface. But firmware has to be aware and coordinate: it holds compile-time defaults, validates and clamps what it is given, and actually drives the radio. A client-only answer firmware does not cooperate with yields a node whose displayed config and on-air behavior disagree.
This is the owner's call and it is open. Do not assume an answer or start implementation that presupposes one.
Scope
In scope: where preset definitions come from and how they update; the data model needed to express a full preset (including path hash); adding a compliant US option; making sure selecting a preset applies every parameter correctly to a connected radio.
Out of scope: changing any non-US preset's values; the firmware-side radio/driver verification (firmware #1064 / #1065); the regulatory analysis itself, which is settled and recorded on the firmware Feature; legal advice to operators.
Children
Filed as sub-issues below -- deliberately scoped as findings and one design decision, not as an implementation plan, since the owner is framing this.
A gotcha worth carrying into every child
Upstream publishes path_hash_size: 2 meaning 2 bytes. The firmware CLI value is zero-based: mode 0 = 1 byte, mode 1 = 2 bytes, mode 2 = 3 bytes (sendFlood(..., _prefs.path_hash_mode + 1)). Matching Philly requires path.hash.mode 1. Anything that maps the published number straight through will silently misconfigure the node off the mesh.
Agent: WhiteFalcon (session cb12cdd2) -- firmware-side, cross-filing only. Not claiming client work.
Activity
diffwireauto commentedon Sep 23, 2026
Research for the owner's preset-catalog direction (see #650, 2026-09-23)
Agent: MagentaHawk (session 5a8922d6). Findings only. No design is approved yet.
1. "Liam's" source is the stock-app registry.
[verified: docs.meshcore.io/faq, 2026-09-23]Regional preset updates go "to let Liam Cottle know" via the #meshcore-app Discord channel. The stock app reads them fromhttps://api.meshcore.nz/api/v1/configatconfig.suggested_radio_settings. Each entry hastitle,description,frequency,bandwidth,spreading_factor,coding_rate, and optionalnetwork_settings.path_hash_size(bytes). The document has no version or updated-at field. The same document also carriesconnect_screen,onboardingandremote_management, which we would ignore.2. We already have the pattern.
lib/services/config_source_service.dart+lib/models/config_catalog.dartfetch a curatedprofiles.jsonfromOffbandMesh/config-profiles, plus any region's self-hosted catalog. The design comment says it is shared by observer, repeater and companion. It uses a resilient parse: bad entries are skipped and counted, and a newer manifest version is rejected loudly.3. Every apply path we'd need is stock firmware
[verified: meshcore-dev/MeshCore upstream/main 0679dbef, 2026-08-24]:CMD_SET_RADIO_PARAMS11,CMD_SET_PATH_HASH_MODE61, TX power 12.CommonCLI):set radio f,bw,sf,cr,tempradio f,bw,sf,cr,mins,set path.hash.mode N,set tx N.4. Traps to carry into the design:
tempradio's revert timer is2000 + mins*60*1000inint(simple_repeater/MyMesh.cpp:1033), which overflows past ~35,791 minutes. The client should cap the duration.tempradioretunes the node 2 s after it replies (MyMesh.cpp:1027). Anything sent to that node after it in the same save goes out on the old frequency, so it must be sent last.5. Nothing is removed. Our current 46 entries (26 Russia cities, Off-Grid, USA Arizona) aren't in Liam's list. Under an overlay model they become Offband overlay entries, so no user loses a preset.
diffwireauto commentedon Sep 23, 2026
Licensing check on Liam's registry (owner asked before any further step)
Agent: MagentaHawk (session 5a8922d6), 2026-09-23. Findings only, not legal advice.
api.meshcore.nz/api/v1/configstates no license and no terms[verified: raw fetch 2026-09-23 04:47 UTC]:suggested_radio_settings.info_message: "These presets are suggested by the community."No licensed copy of the data found
[verified: gh search code]:927.875, "USA - Southern California") in themeshcore-devorliamcottleorgs.meshcore-dev/MeshCoreandmeshcore-dev/map.meshcore.ioare MIT, but map.meshcore.io only fetches the list live (src/map.js). The MIT grant covers their code, not this served data.How others use it
[verified: gh search code]:meshcore-dev/map.meshcore.io(official org),meshcore-ninja,kybl/meshcore-signal-tester,mikecarper/meshfirmware.openhop-dev/openhop_repeater(radio-presets.json).Widespread reuse suggests tolerance, but it is not a grant.
Also verified in the same fetch: 26 entries, none at 500 kHz, no Philly entry. The earlier 27-with-Philly count (2026-09-09) no longer holds. A US FCC-compliant option will have to come from our overlay, not from Liam's list.
Where that leaves the mirror decision: without a stated license, a direct-commit mirror republishes unlicensed data from our repo. Options for the owner:
diffwireauto commentedon Sep 28, 2026
Liam's list vs stock firmware (owner question, 2026-09-28)
Agent: MagentaHawk (session 5a8922d6).
Stock firmware has no preset list.
[verified: meshcore-dev/MeshCore upstream/main e9412598, 2026-09-20]git grep -i presethits onlydocs/faq.md.platformio.ini:29LORA_FREQ=869.618,LORA_BW=62.5,LORA_SF=8, withLORA_CR 5andLORA_TX_POWER 20from the example headers (e.g.companion_radio/MyMesh.h:49,52). The examples' own fallback isLORA_FREQ 915.0, butplatformio.inisets it for every build.docs/faq.md:192) names one example, "USA/Canada (Recommended)" 910.525 / SF7 / BW62.5 / CR5, as prose. It is not data the firmware uses.So all 26 entries in Liam's list are absent from stock firmware. None of them even matches the firmware default exactly: 869.618 / 62.5 / SF8 is Liam's "EU/UK (Narrow)", "Switzerland" and "Netherlands (Limburg)", but those use CR8, and the firmware default is CR5.
Liam's list is unchanged since 2026-09-23
[verified: re-fetch 2026-09-28, field-by-field diff empty]: 26 entries, 0 at 500 kHz, no Philly.Design implication for #650 Q2: presets exist only in the client layer today (Liam's registry and our app). Firmware knows only its single build default and whatever values it's sent. A client-owned catalog matches how stock already works, with no firmware coordination needed beyond the stock set commands.
diffwireauto commentedon Sep 29, 2026
Liam's list vs the stock MeshCore app (owner question, 2026-09-29)
Agent: MagentaHawk (session 5a8922d6).
Correction first: on 2026-09-28 I told the owner "the stock app pulls Liam's list live". That came from the parent issue's text, not my own check. It's verified now, for the web build only.
The stock web app is Liam's registry, with nothing bundled
[verified: app.meshcore.nz main.dart.js, Flutter engine 5a2a6a42, fetched 2026-09-29]:https://api.meshcore.nz/api/, and the app callsGET /v1/config.connect_screen,onboarding,remote_management,suggested_radio_settings{info_message, entries[title, description, frequency, spreading_factor, bandwidth, coding_rate, network_settings.path_hash_size]}.config_suggested_radio_settings_entries.62.5hits are consistent with a bandwidth option list[hypothesis: not individually inspected].So the stock app's picker is whatever Liam's registry served at its last fetch: today, the same 26 entries.
[hypothesis: untested]A fresh install that has never reached the API has no presets at all, since nothing is bundled.Not verified: the Android/iOS stock app. It's likely the same Flutter codebase, but I haven't inspected a mobile build. Checking one would mean pulling the stock APK (from the owner's phone if installed, or from Play), which needs the owner's OK.
diffwireauto commentedon Sep 29, 2026
Stock app presets before Liam's registry (owner question, 2026-09-29)
Agent: MagentaHawk (session 5a8922d6). Script and full output:
cmp_presets.py/cmp_presets.log(session scratchpad).1. The stock web app has used the registry for as long as it's archived.
[verified: Wayback, oldest archived app.meshcore.nz/main.dart.js 2025-07-14]That build already calls/v1/configand contains no hardcoded preset values. So the hardcoded era, if there was one, predates July 2025 and isn't in the web archive. The archived API snapshots (which would show the registry's earliest contents) couldn't be read: the Wayback index returned 503 twice.2. The closest "stock list" snapshot we have is zjs81/meshcore-open#72 (2026-01-21), "Current global presets". It has 15 entries with frequency / SF / BW and no CR. The requester's note: "Coding Rate (CR) is independent and can all be 5 by default". They also merged "Australia SA, WA, QLD" because "only CR was different". zjs81
947fafbb(2026-02-17) turned that table into code with every CR = 5, plus Arizona and Off-Grid. Our list descends from that commit.3. Liam's registry today vs that Jan 2026 snapshot:
4. Our list carries CR values that differ from Liam's current values for same-named entries
[verified: script]:That traces to the #72 "all CR 5" simplification.
[hypothesis: untested]Whether a CR mismatch breaks reception depends on header mode; not established.5. zjs81 upstream has moved on from our fork: it added
USA Philly(902.25 / 500 / SF11 / CR5, 2026-09-03) andAustralia (Mid), and changedUSA Arizonato SF9/CR8 (2026-08-09). Ours still has Arizona at SF10/CR5 and neither new entry.Still not seen: a stock mobile build from before the registry. That would need an old stock APK (owner's OK required to download one).
diffwireauto commentedon Sep 29, 2026
Registry history from openhop's committed copies (owner question, 2026-09-29)
Agent: MagentaHawk (session 5a8922d6). Source:
openhop-dev/openhop_repeaterradio-presets.json(MIT repo, created 2025-10-24), read at commits1065949f,004723d7,5fcb457f,547c1782.Origin: not visible. openhop was created three months after the registry was already live (the stock web app used
/v1/configby 2025-07-14). Its first copy is a verbatim API response from 2025-10-27. The commit is "implement API fetch with fallback to local presets", and the wrapper is identical to the API's, minusonboarding, which Liam added later. So it gives a dated baseline, not the beginning.The registry on 2025-10-27 (15 entries): Australia; Australia: Victoria (916.575/62.5/SF7/CR8); EU/UK (Narrow) CR8; EU/UK (Long Range); EU/UK (Medium Range); Czech Republic (Narrow) at 869.525; EU 433MHz (Long Range); New Zealand; New Zealand (Narrow); Portugal 433 CR6; Portugal 868 CR6; Switzerland CR8; USA/Canada (Recommended) 910.525/62.5/SF7/CR5; USA/Canada (Alternate) 910.525/250/SF11/CR5; Vietnam. No path hash on any entry.
Key finding: our CR divergence was wrong from the start. The Oct 2025 registry already carried CR8 for EU/UK (Narrow) and Switzerland, CR6 for Portugal 433, and CR8 for the 916.575 Australia entry. zjs81#72 (Jan 2026) dropped the CR column ("can all be 5"), and zjs81
947fafbbhardcoded CR5. Our fork inherited that. It is not a later change by Liam.Changes Oct 2025 to Sep 8 2026 (openhop's "synchronize with stock" commit):
Changes Sep 8 2026 to live (2026-09-28): USA/Canada (Recommended) split into USA and Canada (hash 3); USA - Southern California added (hash 3); Netherlands (Limburg) added; Australia (Narrow) CR8 became CR7; Czech gained hash 2.
openhop's own additions (not from the registry): SoCal (2026-05-13, before Liam had it), Philadelphia (2026-09-08), and a pre-release board preset. They were then replaced by the sync. So openhop ran a local-overlay model too, then chose to match stock one-to-one.
diffwireauto commentedon Oct 1, 2026
The plan, consolidated (2026-10-01): proposed, awaiting owner approval
Agent: MagentaHawk (session 5a8922d6). This replaces the scattered research comments above as the single reference. Decisions are logged on #650.
Goal: one preset list in the app with regional defaults plus local meshes (Arizona, SoCal, OKI, Philly, ...). Pick a preset and apply it to Companion, Observer, Repeater, Room Server or Sensor using stock firmware commands only.
Decided (owner, logged on #650):
api.meshcore.nz/api/v1/config) is the base.Work, as an Epic under #647 with these Tasks:
set radio, ortempradiowith the Temporary checkbox (this absorbs task: repeater preset picker with a Temporary (tempradio) checkbox #662), andset path.hash.mode.Folded in:
Open for the owner:
OffbandMesh/config-profiles?2 remaining items
diffwireauto commentedon Oct 1, 2026
Structure filed (owner: "File it. Go.", 2026-10-01)
Base capabilities first. Each Epic's verification task blocks the next Epic.
tempradio, #735set path.hash.mode, #736 verificationCitadel: Feature
mli; Epicsx7r/keb/yvn/hua. Every task depends on its Epic entry; the chain is A4 → B → B6 → C → C4 → D. #650 closed with the decisions.Still open for the owner:
OffbandMesh/config-profiles?Agent: MagentaHawk (session 5a8922d6)
docs(#740): record the owner's choices in the #647 plan
docs(#740): add the Feature #647 plan of record
docs(#740): record the owner's choices in the #647 plan
diffwireauto commentedon Oct 1, 2026
Plan amendment and owner requirement (2026-10-01)
mainand that check could not pass. The owner minted the grant; config-profiles#3 merged before D1 (2026-10-01 22:24 UTC). Both files return 200 onmain, and the mirror workflow was dispatched once. App PRs (epic(#726): in-app radio preset catalog and picker (Companion/Observer) #744, epic(#733): apply radio presets to repeaters, room servers and sensors #746, Epic D) still merge after D1. PLAN.md gets this amendment in the Epic D PR.tempradioduration is valid. Commits 04025cb (epic(#726): in-app radio preset catalog and picker (Companion/Observer) #744) and 9c17f8a (epic(#733): apply radio presets to repeaters, room servers and sensors #746).MagentaHawk (session 5a8922d6)
diffwireauto commentedon Oct 2, 2026
Owner directed closure 2026-10-01. All four Epics closed; outcomes at docs/initiatives/standalone/647-mesh500-presets/OUTCOMES.md. MagentaHawk (session 5a8922d6)