Skip to content

feature: MESH 500 -- regional preset system, an FCC-compliant US option, and a preset source of truth #647

Closed
7 of 7 issues completed

Description

@Strycher

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]

  1. We do not use the upstream preset registry. grep for api.meshcore.nz across 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.

  2. Our preset list is a hardcoded Dart literal in lib/models/radio_settings.dart -- 46 entries, compiled in, no fetch, no update path.

  3. 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.

  4. 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-open fork ancestry, not from upstream. USA Arizona (908.205 / SF10 / BW62.5) is likewise ours alone. Our USA/Canada (910.525 / SF7 / BW62.5 / CR5) does match upstream's generic USA exactly.

  5. RadioSettings has no path-hash field, so the fifth parameter cannot be expressed even if a 500 kHz entry were added.

  6. 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

  1. diffwireauto commented on Sep 23, 2026

    @diffwireauto
    Member

    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 from https://api.meshcore.nz/api/v1/config at config.suggested_radio_settings. Each entry has title, description, frequency, bandwidth, spreading_factor, coding_rate, and optional network_settings.path_hash_size (bytes). The document has no version or updated-at field. The same document also carries connect_screen, onboarding and remote_management, which we would ignore.

    • A fetch today (via a summarizing tool, so the content is to be re-read raw before we build on it) listed 26 entries, including SoCal (927.875/62.5/SF7/CR5, hash 3) and Canada (hash 3). Philly Mesh 500 kHz did not appear. WhiteFalcon's 2026-09-09 count was 27 with Philly. That needs a raw re-check, not a conclusion.

    2. We already have the pattern. lib/services/config_source_service.dart + lib/models/config_catalog.dart fetch a curated profiles.json from OffbandMesh/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]:

    • Companion (and Observer, which uses the companion Radio Settings path): CMD_SET_RADIO_PARAMS 11, CMD_SET_PATH_HASH_MODE 61, TX power 12.
    • Repeater / Room Server / Sensor (remote admin CLI, CommonCLI): set radio f,bw,sf,cr, tempradio f,bw,sf,cr,mins, set path.hash.mode N, set tx N.
    • No Offband-specific code is needed for any of it.

    4. Traps to carry into the design:

    • Path hash is published in bytes, but firmware takes a zero-based mode (2 bytes = mode 1). Needs a test.
    • tempradio's revert timer is 2000 + mins*60*1000 in int (simple_repeater/MyMesh.cpp:1033), which overflows past ~35,791 minutes. The client should cap the duration.
    • tempradio retunes 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.
    • Room Server and Sensor settings screens: I have not yet confirmed whether the app has a radio editor for them.

    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.

  2. diffwireauto commented on Sep 23, 2026

    @diffwireauto
    Member

    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/config states no license and no terms [verified: raw fetch 2026-09-23 04:47 UTC]:

    • Nothing in the body or the response headers matches license/copyright/terms/attribution.
    • The only provenance text is suggested_radio_settings.info_message: "These presets are suggested by the community."

    No licensed copy of the data found [verified: gh search code]:

    • No hits for the entry values (927.875, "USA - Southern California") in the meshcore-dev or liamcottle orgs.
    • meshcore-dev/MeshCore and meshcore-dev/map.meshcore.io are 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]:

    • Live fetch: meshcore-dev/map.meshcore.io (official org), meshcore-ninja, kybl/meshcore-signal-tester, mikecarper/meshfirmware.
    • A copy committed into their own repo: 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:

    1. Ask Liam for explicit permission, and ideally a license statement, before building the mirror.
    2. Change the plan: clients fetch Liam's list live (as the official map does) instead of us republishing it.
    3. Ship our own overlay only for now (it works standalone) and add Liam's layer once 1 or 2 is settled.
  3. diffwireauto commented on Sep 28, 2026

    @diffwireauto
    Member

    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 preset hits only docs/faq.md.
    • The only radio values are one compile-time default: platformio.ini:29 LORA_FREQ=869.618, LORA_BW=62.5, LORA_SF=8, with LORA_CR 5 and LORA_TX_POWER 20 from the example headers (e.g. companion_radio/MyMesh.h:49,52). The examples' own fallback is LORA_FREQ 915.0, but platformio.ini sets it for every build.
    • The FAQ (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.

  4. diffwireauto commented on Sep 29, 2026

    @diffwireauto
    Member

    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]:

    • The HTTP client base URL is https://api.meshcore.nz/api/, and the app calls GET /v1/config.
    • It parses exactly the registry schema: connect_screen, onboarding, remote_management, suggested_radio_settings{info_message, entries[title, description, frequency, spreading_factor, bandwidth, coding_rate, network_settings.path_hash_size]}.
    • It caches the last fetch in local settings under config_suggested_radio_settings_entries.
    • No preset values are compiled in. Zero hits for 910.525, 927.875, 869.618, 915.8 or 433.650, and none for "Southern California", "Arizona" or "Philly". The seven 62.5 hits 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.

  5. diffwireauto commented on Sep 29, 2026

    @diffwireauto
    Member

    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/config and 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:

    • Added: Australia (Mid), Brazil, Canada (split from USA/Canada, hash 3), Costa Rica, EU 433MHz (Narrow), Hungary, Netherlands, Netherlands (Limburg), Slovakia, USA - Southern California, Vietnam (Narrow).
    • Split: "Australia SA, WA, QLD" became "Australia: SA, WA" (CR8) and "Australia: QLD" (CR5).
    • Renamed: EU/UK (Long Range) became EU/UK (Deprecated); EU 433MHz became EU 433MHz (Long Range); New Zealand (250 kHz) became New Zealand (Gisborne, hash 1); Portugal 869 became Portugal 868 (CR6); Vietnam became Vietnam (Deprecated); USA/Canada became USA + Canada.
    • Dropped: EU/UK (Medium Range) 869.525 / 250 / SF10.
    • Path hash added to 8 entries (none carried it in Jan 2026).

    4. Our list carries CR values that differ from Liam's current values for same-named entries [verified: script]:

    Entry Ours Liam
    Australia (Narrow) CR5 CR7
    EU/UK (Narrow) CR5 CR8
    Switzerland CR5 CR8
    Portugal 433 CR5 CR6

    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) and Australia (Mid), and changed USA Arizona to 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).

  6. diffwireauto commented on Sep 29, 2026

    @diffwireauto
    Member

    Registry history from openhop's committed copies (owner question, 2026-09-29)

    Agent: MagentaHawk (session 5a8922d6). Source: openhop-dev/openhop_repeater radio-presets.json (MIT repo, created 2025-10-24), read at commits 1065949f, 004723d7, 5fcb457f, 547c1782.

    Origin: not visible. openhop was created three months after the registry was already live (the stock web app used /v1/config by 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, minus onboarding, 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 947fafbb hardcoded 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):

    • Added: Australia (Mid), Australia: SA, WA, Australia: QLD, Brazil, Costa Rica, EU 433MHz (Narrow), Hungary, Netherlands, Slovakia, Vietnam (Narrow).
    • Renamed or retuned: Victoria became Australia (Narrow); EU/UK (Long Range) became (Deprecated); New Zealand became (Gisborne); Vietnam became (Deprecated); Czech moved 869.525 to 869.432.
    • Dropped: EU/UK (Medium Range), USA/Canada (Alternate).
    • Path hash appears for the first time (Hungary, NZ Narrow, Gisborne, Slovakia).

    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.

  7. diffwireauto commented on Oct 1, 2026

    @diffwireauto
    Member

    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):

    • Liam's public preset list (api.meshcore.nz/api/v1/config) is the base.
    • Our own small file adds what Liam lacks, and wins on conflict.
    • Liam's list is mirrored into our config repo by a scheduled CI job that commits directly.
    • The app bundles a snapshot of both lists, so it works offline. The network only refreshes.
    • Liam and MeshCore are credited.

    Work, as an Epic under #647 with these Tasks:

    1. Preset model: frequency, bandwidth, SF, CR, plus optional path hash, TX power, region and source.
    2. Config repo: our overlay file (OKI 500 kHz test, Philly, Russia cities, Arizona, Off-Grid), the mirror job for Liam's list, and credit in the README.
    3. App loading: bundled snapshot, then refresh, then merge (ours over Liam's), keeping the last good copy.
    4. Picker: grouped by region, each entry showing its source.
    5. Apply to each node type, stock commands only:
    6. Credits on the About screen.
    7. Epic verification on hardware.

    Folded in:

    • Our CR values become Liam's correct ones; ours came from zjs81's "all CR 5" copy.
    • The Arizona values need a pick: ours are SF10/CR5, zjs81 now has SF9/CR8.

    Open for the owner:

  8. 2 remaining items

  9. diffwireauto commented on Oct 1, 2026

    @diffwireauto
    Member

    Structure filed (owner: "File it. Go.", 2026-10-01)

    Base capabilities first. Each Epic's verification task blocks the next Epic.

    Epic Tasks
    A #721: preset sources in config-profiles #722 overlay + schema, #723 CI mirror of Liam's list, #724 README credit, #725 verification
    B #726: in-app catalog + picker, Companion/Observer #727 model, #649 path hash, #728 bundled snapshots + refresh/merge, #648 (closes with #728), #729 grouped picker, #730 apply (cmd 11 + 61), #731 About credit, #732 verification
    C #733: remote nodes via stock CLI #734 picker on remote settings, #662 Temporary/tempradio, #735 set path.hash.mode, #736 verification
    D #737: integration testing #738 every node type end to end, #739 OUTCOMES.md

    Citadel: Feature mli; Epics x7r / 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:

    1. task: add the OKI-Mesh 500kHz Test preset entry #661 stopgap (built, tested, uncommitted): ship now, or fold into task: Offband overlay preset file + schema in config-profiles #722.
    2. Overlay home: OffbandMesh/config-profiles?
    3. Arizona values: SF10/CR5 (ours) or SF9/CR8 (zjs81 latest)?

    Agent: MagentaHawk (session 5a8922d6)

  10. added a commit that references this issue on Oct 1, 2026
  11. added 2 commits that reference this issue on Oct 1, 2026
  12. diffwireauto commented on Oct 1, 2026

    @diffwireauto
    Member

    Plan amendment and owner requirement (2026-10-01)

    MagentaHawk (session 5a8922d6)

  13. diffwireauto commented on Oct 2, 2026

    @diffwireauto
    Member

    Owner directed closure 2026-10-01. All four Epics closed; outcomes at docs/initiatives/standalone/647-mesh500-presets/OUTCOMES.md. MagentaHawk (session 5a8922d6)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions