Two things hardware turned up.
The whole terminal came back red. The endpoint decided a command had succeeded
by testing its reply for an "OK" prefix — the convention the config batch relies
on, and a safe one there because every allowlisted setter uses it. The CLI
reaches the whole surface, where success has no single shape: setters answer
"OK...", getters answer "> value", `erase` answers "File system erase: OK". Only
failure is uniform ("Err", "ERR:", "Error:"), so that is what the CLI now tests
for.
Colour was the visible half. The other half was worse: _batch_all_ok went false
the moment a sequence contained a `get`, so a script ending in `reboot` was told
some commands had failed and the reboot was withheld.
Replies are green now and red means the node said no, which is what red should
have meant all along. The "> " a getter prefixes its value with is dropped for
display — on the serial console it sets the value apart, here it collides with
the prompt glyph that means "you typed this". The mock emits that marker too;
had it done so from the start, this would have shown up before the flash.
Second: discover.neighbors and discover.scopes did not autocomplete, because
MyMesh::handleCommand intercepts a few commands before delegating to CommonCLI
and the table was built by reading CommonCLI alone. setperm, `get acl` and
`shutdown` were missing for the same reason.
The audit could not have caught that: it drove every command the table offered
and checked the mock answered, which only finds gaps in one direction. It now
also reads the command literals the firmware dispatches on — across CommonCLI,
CommonCLI_Observer and MyMesh — and fails on any the table does not offer. That
check found `shutdown` immediately.
122 commands, all answered, none missing. 22 batch + 14 keys tests pass.
The terminal has been driving the mock since it was built. This is the firmware
side, so it works on hardware.
Same 202 + reqid + poll contract as a config save, for the same reason:
CommonCLI touches prefs, the radio and the filesystem, none of which may be
reached from the async_tcp task. Commands go into the deferred slot and tick()
drains them on the loop task. Unlike a save this is not allowlisted — reaching
what the serial console reaches is the point, and execCommand() already passes
sender_timestamp 0, so the terminal gets exactly the serial console's
privilege. Authentication is the boundary, as it is there.
The CLI shares the config batch's slot rather than owning a second MAX_BATCH
array: both drain on the loop task, both are single-slot, and a duplicate would
cost ~8 KB of permanently resident RAM. Sharing also makes a save and a CLI run
mutually exclusive, which they must be. Each reader checks the kind, so neither
can serve the other's results.
Three things the mock could not have taught us:
- Board::reboot() does not return, so a drained `reboot` would take the node
down before the client read a single result. It is answered rather than
executed, and the batch arms the existing deferred-reboot path once the
results have been read — withheld if any command failed, exactly as a save
withholds one. clkreboot/poweroff/ota update do real work on the way down
and cannot be faked, so they still drop the connection; the UI warns first.
- `password <new>` echoes the new password in its reply. The config path
already scrubbed that by key; a CLI entry has no key, so it is matched on
the command. CLI commands are also kept out of the serial log entirely —
the browser session and the serial console are different audiences.
- MAX_BATCH is 24, not the 64 the page assumed. It is reported as
status.max_cmds instead of hardcoded, so the cap cannot drift.
Results stream and page (kCliResultPage = 8), and "done" means the client has
been handed every result, not merely that execution finished — otherwise a
client that stops polling at "done" loses the last page. Commands are never
echoed back: they may carry a secret, and the client matches by index.
New decisions live in WebConfigBatch.h with the rest, covered by three host
tests. Builds clean for heltec_v4_repeater_observer_mqtt; 22 batch + 14 keys
tests pass; the CLI audit reports 119/119 against the updated mock.
A bare 404 carries no body, so r.json() rejected and the parse failure escaped
with no HTTP status attached. Every caller then had to treat "this route does
not exist" as an ambiguous network failure — for the CLI that meant ~14 seconds
of polling before reporting a lost connection, which is the wrong diagnosis and
the wrong wait.
api() now substitutes an empty object when an *error* response has no readable
JSON, so the status survives onto the error. Successful responses must still
parse, or a captive portal's HTML would sail through as valid config.
The CLI names the case outright: firmware without /api/cli says so in 100ms
instead of retrying a route that will never exist.
`get radio.fem.rxgain` returned "unknown config key" from the mock, which reads
as the terminal offering a command that does not exist. It does exist: CommonCLI
implements get and set for it, gated at runtime by Board::canControlLoRaFemLna()
rather than compiled out, so the command is present in every build and the board
answers for itself — "Error: unsupported" where there is no front-end module.
Auditing the whole table found 31 of 70 config keys unanswered, all the ones no
portal form drives: alert.*, bridge.*, owner.info, path.hash.mode, dutycycle and
the rest. Plus 14 verbs (gps, powersaving, sensor, region, clock sync) with no
handler at all. They now live in a "cli" section of the mock config, typed
through the existing lookup tables and stripped from /api/config, which does not
carry them.
Two real bugs behind that:
- the `set` path gated on whether a key was *readable*, so write-only and
computed keys (prv.key, dutycycle, radio.fem.rxgain) were rejected as
unknown. apply_set now owns that decision alone.
- apply_set accepted anything it did not recognise and replied OK. That
leniency is what let the gap hide: a CLI `set` on an unknown key looked
like it worked. It is strict now — verified against every key in
WC_ALLOWED_SET_KEYS so the form batch is unaffected.
Also mqtt.neighbors / mqtt.neighbors.interval, which the MQTT tab binds but the
mock's config never carried, so that toggle could not round-trip.
webconfig_cli_audit.py keeps the two honest: it drives every command the
autocomplete table offers through /api/cli and fails on anything unanswered.
119 commands, all answered.
"meshcore@<node>" borrowed the user@host convention without the referents:
there is no user concept here, and the node name is already in the page header
directly above, larger. It was decoration duplicating what was on screen — and
it crowded the header enough that `help` had to be hidden below 430px.
Removing it fits all three buttons on a 375px phone, so that media query goes
with it.
Operators who already have a prepared config want to paste it, not tap through
four wizard steps. A quiet `>_` chip in the setup-mode header drops straight
into the terminal; everyone else still sees only the wizard.
That makes the CLI reachable in setup mode, which the previous commit had
deliberately blocked. Setup mode authenticates by proximity to the AP rather
than by password — but the wizard already sets the admin password and rewrites
the node's radio config from there, so the trust boundary is the AP either way,
and refusing the console would only push these operators back to serial.
Onboarding by paste does skip the one thing the wizard makes mandatory: the
admin password, which /api/config enforces before it will arm a reboot. Nothing
in CommonCLI enforces it, so the terminal says so on entry, and confirming a
reboot without a `password` command having run warns again. That is a client-
side reminder, not a gate; wiring the real gate belongs with /api/cli on-device.
The console is a one-way door out of the wizard otherwise, so its header grows
a "← setup" button that goes back.
The generator gzipped webui/index.html verbatim, so the page's comments — and
this file is commented heavily by house style — were paying flash rent. A
line-based pass now drops comments, indentation and blank lines before
compressing. The source stays as readable as it was.
Conservative on purpose: only a comment that starts its own line is removed, so
a `//` inside a URL or a `/*` inside a regex can never be mistaken for one.
Line breaks survive, which leaves JS statement boundaries (and the space a
newline contributes between HTML inline elements) exactly as written.
This ships to thousands of devices, so it is not taken on trust:
- check_stripped() fails the build if the page's structure changed or the
output shrank implausibly
- the pass lives in its own module, shared with the mock backend's new
--minify flag, so the bytes exercised in a browser are the bytes that get
embedded rather than a second implementation that could drift
- webconfig_minify.py joins the generator in the freshness hash, so editing
the stripper forces a regenerate
Today's page: 22,678 -> 17,671 bytes gzipped.
Design prototype, driven entirely by the mock backend — nothing here runs
on-device yet.
The portal's form batch is deliberately allowlisted (WebConfigKeys.h), which
leaves everything the serial console can do unreachable from a browser. This
adds a fifth tab holding a real terminal: monospace white-on-black in either
colour scheme, autocomplete over the full ~270-command surface, in-session
history, and a confirmation step for pasted command sequences.
Autocomplete goes past the flasher's <datalist>: rows carry descriptions, Tab
extends to the longest shared prefix before committing to a match, and once
`set <key> ` is complete it switches to completing the VALUE — enums from the
command table, broker presets from /api/presets, packet-type names per CSV
segment. The table is generated from a key list rather than written out per
slot, so mqttN.* tracks active_slots instead of being duplicated six times.
Pasting several lines never mangles the prompt: the lines are parsed (comments,
blank lines and pasted `>` prompts stripped), listed back numbered, and run only
after an explicit confirm. Commands that restart, erase, reflash or move the
node off its network get the same confirmation singly. History is memory-only —
`set wifi.pwd` and `password` pass through it.
/api/cli mirrors the config-save contract (202 + reqid, poll for results) for
the same reason: commands run on the node's main loop, not in the request. The
one difference is that results stream, so a long sequence fills the window as
it executes rather than landing all at once.
The tab is hidden in setup mode, where the portal authenticates by proximity
and no admin password exists yet.
13 commits, 6 files, zero conflicts. Records the byte-identical smoke-build
baseline, the LR1110 RX-timeout fix and the global nRF52 CC310 crypto switch,
and the fact that the ESP32-only smoke pair could not see either.
Three fork-introduced dependencies had leaked into non-ESP32 builds, breaking
every nRF52 and RP2040 target since 7e4f75c9 (2026-04-10):
- The `memory` CLI command called ESP.getFreeHeap()/heap_caps_* unguarded from
shared CommonCLI.cpp. ESP32 output is unchanged; other platforms now report
newlib arena stats, omitting min-ever-free and largest-free-block rather than
substituting numbers that mean something different.
- The vendored PsychicMqttClient (ESP-IDF esp-mqtt) was pulled in by the LDF on
nRF52; nrf52_base now lib_ignores it, matching how RP2040 variants ignore BLE.
- JWTHelper.cpp and MQTTMessageBuilder.cpp are excluded in arduino_base, but 22
variants re-glob helpers/*.cpp after inheriting it, which undoes the exclusion.
Guarding the file contents on WITH_MQTT_BRIDGE is robust against any variant's
filter, and matches helpers/esp32/WebConfigServer.cpp.
Verified: RAK4631 repeater + room server, Heltec T114, T1000-E, Wio WM1110,
Xiao nRF52 and ThinkNode M1 all build. ESP32 observer builds are byte-identical
to before (RAM and flash), and the native suite stays at 267/267.
RP2040 still fails separately: four boards declare the pre-force_ap
startOTAUpdate signature. Not addressed here.
The starvation needs ArduinoJson v7's fixed 4096-byte pool blocks plus dev's
custom budget-capped allocator. Production is still on v6 (MQTTMessageBuilder
uses createNestedArray, removed in 7.0), where DynamicJsonDocument(10240) is a
real compact slot pool needing ~3.8 KB for 50 entries, so prod is unaffected
and needs no hand-port.
Also notes that prod pins no ArduinoJson version at all.
Enables neighbors publication on the ESP32-S3 non-PSRAM observer envs via a
per-variant MQTT_NEIGHBORS_WITHOUT_PSRAM opt-in, and fixes a pre-existing JSON
pool-budget bug that silently dropped the publish on PSRAM boards with roughly
40+ neighbours.
Bench-verified 2026-08-03 at the 2-wss-slot non-PSRAM maximum.
Bench run 2026-08-03 on a non-PSRAM ESP32-S3 observer at the 2-wss-slot
non-PSRAM maximum: periodic publish succeeded, min-ever free internal heap
53776 B, payload 1252 B of the 4096 B buffer.
Also records the pre-existing PSRAM pool-budget bug found while verifying,
and the two scenarios still untested on hardware: the >20-neighbour
truncation path, and a publish coinciding with a slot reconnect.
Neighbors publication was gated on BOARD_HAS_PSRAM. Removing that gate alone
was not enough: the feature built but was inert without PSRAM, because three
allocation sites asked for MALLOC_CAP_SPIRAM (which returns null with no
PSRAM), the bridge's persistent buffer was allocated inside a BOARD_HAS_PSRAM
block, and neighborDiscoverReady() rejected every pass at runtime on
psramFound(). The entry table also did not fit: finishNeighborDiscover put
pubkey_hex[50][65] plus entries[50] on the stack, a 4752-byte frame against
the mesh loop task's 8 KB.
- Gate on MAX_NEIGHBOURS plus PSRAM or an explicit per-variant
MQTT_NEIGHBORS_WITHOUT_PSRAM opt-in.
- Move the entry table and its hex strings into one heap block sized to the
pass; the frame drops from 4752 to 304 bytes.
- Prefer PSRAM and fall back to internal DRAM in the mesh-side allocations
and the ArduinoJson pool; hoist the bridge's persistent buffer out of the
BOARD_HAS_PSRAM block (psram_malloc already falls back).
- Keep the runtime psramFound() check only where the buffers are sized for
PSRAM, so a board whose PSRAM failed to init still refuses.
- Size for internal DRAM without PSRAM: 4 KB text buffer and 20 entries per
publish, keeping the pool to a single block and the peak near 13 KB rather
than ~35 KB. Oversized tables truncate and report total_neighbors as before.
Enabled on the ESP32-S3 observer envs (Heltec V3/WSL3, RAK3112, Heltec
Tracker v1.1/v2). Left off for the classic ESP32 T-LoRa V2.1-1.6, which is
already limited to one active TLS slot.
Costs ~7.4 KB static DRAM on repeaters and ~9.6 KB on room servers. The
prefs layout is unchanged, so this is neutral for existing devices.
ArduinoJson v7 hands out document-pool blocks in fixed 4096-byte chunks, so
the pool is not bounded by the size of the text it serialises to. Budgeting
it at NEIGHBORS_JSON_BUFFER_SIZE starved it once the table grew: a 50-entry
table needs 12541 B of pool against the 10240 B cap, and a starved allocator
sets doc.overflowed(), which makes buildNeighborsMessage return 0 and drop
the entire publish rather than truncating the tail.
Repeaters with roughly 40 or more neighbours therefore published no neighbors
message at all, silently, while smaller tables published normally.
Give the pool its own NEIGHBORS_DOC_POOL_BUDGET and add an explicit
NEIGHBORS_MAX_PUBLISH_ENTRIES cap alongside the existing text-size check.
Board manifest for esp32-c6-devkitm-1 defaults build.flash_mode to qio,
which this module's flash chip does not support -- causes a boot
crash-loop (repeated USB-Serial-JTAG reconnects) on real hardware.
Override with board_build.flash_mode = dio in the common M5Stack_Unit_C6L
section so it applies to all envs (ble/usb/repeater/room_server).
Adds ThinkNode_M7_repeater_observer_mqtt and
ThinkNode_M7_room_server_observer_mqtt, following the Station G3 observer
overlay: adafruit-full cert bundle, MQTT bridge + SNMP, pinned observer
lib_deps, and the quieter default logging profile.
WiFi-only for now. The M7 has an onboard CH390 Ethernet controller (used by
ThinkNode_M7_companion_radio_ethernet, and an lwIP netif so the MQTT data path
would work over it), but the bridge's link management is bound to the WiFi
station API, so Ethernet cannot carry MQTT yet.
The board has PSRAM, so MAX_NEIGHBOURS=50 enables WITH_MQTT_NEIGHBORS —
verified present in both images. Builds use 46.5% of the 3.19 MB app slot at
13.7% RAM, and build.sh emits the full artifact set (.bin, -merged.bin,
.partsig). The partition table is inherited from [ThinkNode_M7] unchanged, so
no merged first flash is needed; its signature is byte-identical to the T-Beam
Supreme observer builds (both default_8MB.csv).
Also fixes two pre-existing build failures on this board:
- [ThinkNode_M7] re-added a blanket helpers/*.cpp after arduino_base excludes
the MQTT-only sources, so every M7 env failed on a missing Timezone.h. The
glob was otherwise fully redundant with the base, so it is dropped rather
than re-excluding the two files.
- ThinkNode_M7_companion_radio_ethernet sets DISPLAY_CLASS=NullDisplayDriver
but never compiled NullDisplayDriver.cpp, which defines UIColor::window_bkg,
so it failed at link. The ble and wifi envs both already list it.
All nine M7 envs now build. Observer env discovery picks up both new envs, and
the ArduinoJson pin check covers both new sections.
A neighbour heard before the clock is set carries the firmware's unset-clock
default (1715770351, 15 May 2024). Subtracting that from an NTP-synced clock
published ages of ~806 days for neighbours that had just answered a live scope
query, and a backwards clock step reported 0, i.e. "heard just now".
- finishNeighborDiscover() reports the age as unknown ("heard_secs_ago": null)
when the stored stamp and the current clock come from different epochs, or
when the clock has stepped backwards
- handleNeighborDiscoverResponse() re-stamps heard_timestamp on a zero-hop scope
reply, in both the snapshot and the live table, so entries heal once per
discovery cycle instead of waiting for the neighbour's next advert
- publish ordering places usable ages ahead of unknown ones so a poisoned stamp
cannot displace fresh entries when the JSON buffer truncates
- document the null case, and the always-present total_neighbors /
queried_neighbors / truncated fields the payload sample omitted
- add UPSTREAM_BUGS.md, tracking the monotonic-uptime fix to propose upstream
plus the unclamped subtraction in the companion and CLI readouts
Containment only: upstream still stamps neighbours from the wall clock at
packet-reception time, which on a cold boot always precedes NTP.
The [Xiao_S3_WIO] base block lost its sensor wiring when upstream
3dd6dc02 ("xiao_s3: use environment sensor manager and add sensor
role") was resolved to our side in a later merge. Only the .ini half
was dropped -- target.h/target.cpp kept the EnvironmentSensorManager,
so builds still compiled helpers/sensors with an empty SENSOR_TABLE:
no ENV_INCLUDE_* macros meant the table held only its sentinel, so
begin() scanned the I2C bus and initialized nothing and querySensors()
emitted nothing above channel 1.
Reported as an INA226 missing from channel 2 on a MQTT Observer build,
working on stock firmware. Affected every Xiao_S3_WIO env, including
the dedicated Xiao_S3_WIO_sensor role.
Restores sensor_base build_flags/lib_deps, -UENV_INCLUDE_GPS and the
PIN_BOARD_SDA/SCL defines, making the base block identical to upstream
again. Also picks up the commented rs232 pin relocation from the same
upstream commit, since 5,6 is now the I2C bus.
Flash on Xiao_S3_WIO_repeater_observer_mqtt: 42.7% -> 44.3%.
Adds Station_G3_ESP32_repeater_observer_mqtt and
Station_G3_ESP32_room_server_observer_mqtt, mirroring the known-good
Station G2 observer envs. The two boards are the same family — ESP32-S3,
qio_opi, PSRAM, 16 MB flash, SH1106 display — so the overlay ports across
unchanged: default_16MB.csv partitions, adafruit-full cert bundle,
MQTT bridge + SNMP, and the same lib_deps pinning.
Verified the G3 partition signature is byte-identical to the G2 observer's,
so the OTA partition-compatibility gate treats them the same. Both envs
build clean (1.67 MB of the 6.25 MB app slot, 28% RAM) and produce the full
CI artifact set via build.sh: .bin, -merged.bin and .partsig. Observer env
discovery now finds 32 envs (was 30) and the ArduinoJson pin check covers
both new sections.
Docs: added the G3 build commands and the two partition-table rows to
MQTT_IMPLEMENTATION.md.
Absorbs 106 upstream commits. Seven files conflicted; the substantive one
was upstream's new JSON ConfigSerializer (PR #2982), which replaces the
binary /com_prefs layout with /prefs.json and makes NodePrefs a
ConfigSerializer subclass.
Prefs migration
- Adopt upstream's ConfigSerializer. writeCommonPrefsImage() and its
documented offsets (0-294) are deleted, along with the now-unreachable
saveCommonPrefsImageAtomically()/CommonPrefsFileStore atomic rename path.
- Load order is /prefs.json, then /com_prefs, then /node_prefs. Upstream
dropped the /node_prefs fallback; it is restored here so devices that
never advanced past that filename keep their config.
- Legacy files are never removed, so migration cannot destroy its own
source and a deferred or failed save simply retries next boot.
- /com_prefs is treated as a format migration only, not an "upgrade", so
it does not trip the bridge.source tx->rx flip on existing nodes.
- The MQTTPrefsAtomicStore legacy gate is retained: the observer tail
recovered from an old-format file still commits to /mqtt_prefs before
/prefs.json is written.
- MQTTPrefs and /mqtt_prefs are untouched; savePrefs keeps its save_mqtt
parameter and now returns upstream's bool.
Fixes to upstream code
- RadioPrefs::structure() bound both "rxgain" and "fem_rxgain" to
rx_boosted_gain, so radio_fem_rxgain was never persisted. Bound to the
correct field.
- discovery_mod_timestamp was dropped from structure(); it gates
'since'-filtered DISCOVER replies and is set on every config change, so
losing it would silently stop discovery responses after a reboot. Added
as "disc_mod".
Merge artifacts repaired
- Restored bblanchon/ArduinoJson to the native test env; a clean but wrong
auto-merge at the lib_deps block boundary dropped it and broke all 19
host test suites.
- Migrated the fork's WebConfig UITask screens off the removed
DisplayDriver::Color enum to upstream's UIColor element types.
- Removed duplicate getCADEnabled() definitions in companion MyMesh.cpp
and simple_sensor SensorMesh.cpp that both sides had added.
- Dropped memset(&_prefs, 0, ...) in the four example meshes; NodePrefs now
has a vtable. guard gains an initializer that memset used to provide.
Other resolutions
- simple_room_server keeps both the fork's discover.* commands and
upstream's new room.post.
- docs/payloads.md taken from upstream, undoing content earlier merges had
reverted (Control data section, split login tables).
Verified: 273/273 host tests pass across native and native_kiss_modem;
Heltec v3 repeater, repeater_observer_mqtt, room_server_observer_mqtt,
sensor, and companion_radio_ble all build clean.
Port the observer doc cleanup from observer-firmware (4beb5472) onto dev,
keeping dev-specific content intact.
- move internal mechanics into MQTT_INTERNALS.md: deferred construction,
runtime slot memory, backoff/circuit breaker, message building, command
namespacing, and the legacy prefs key mapping
- drop the duplicated First-Time Setup, Command Architecture, and SNMP
Monitoring sections, folding their unique steps into Quick Start
- condense the partition/NVS prose and merge the five custom-broker
examples into one Custom Brokers section
- promote the preset list to a top-level Broker Presets section with an
Extra setup column, and document the presets the table was missing:
meshcore-fi, corecomms, mesh-chaun14, wcmesh, and meshtexas
- fix the coloradomesh port (443, not 1883)
- point flasher and changelog links at observer.gessaman.com
- restore the nbr: status field, which the code still emits, and correct
the neighbors topic to QoS 0 retained per allow_retain
Dev-specific content is preserved as-is: per-slot packet filters, the Web
Configuration Portal, local testing without hardware, MeshRank's raw
exclusion, and neighbors default_scope.
CustomLR1110::startReceive() passed RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED
(1<<4 = 16) as RadioLib's first argument, which is the RX *timeout*, not an
IRQ mask. At the LR11x0's 30.52us tick that armed the receiver for ~488us, so
it dropped out of RX before any packet could arrive and the node received
nothing at all -- while transmitting normally.
Symptoms on a SenseCAP T1000-E: tx_air_secs rising, rx_air_secs stuck at 0,
recv_errors 0, and the noise floor pinned at the -120 clamp because
getCurrentRSSI() never sampled a live receiver.
Pass RADIOLIB_LR11X0_RX_TIMEOUT_INF (continuous RX), which is what
LR11x0::startReceive() itself uses, keeping the PREAMBLE_DETECTED flag in the
reported IRQ flags as intended.
Introduced in ea5d7c8b ("LR1110: add PREAMBLE_DETECTED to reported irq flags").
Verified on two T1000-E units: with only the repeater fixed it began receiving
(last_rssi -29, SNR 17.0) while the unfixed companion stayed deaf; fixing both
brought up the link in each direction.
MeshRank slots previously took packets only. The broker tolerates status and
neighbors (and its maintainer intends to look at using the neighbors data), so
those now publish under meshrank/uplink/{token}/{device}/{type}, using the same
type suffixes as the MeshCore layout. Raw stays excluded: it is the
highest-volume topic and the broker does not consume it, so `set mqtt.raw on`
has no effect on a MeshRank slot.
All MeshRank gating funnels through the topic router, so relaxing that single
guard was sufficient - publishStatusToSlot, publishStatus, publishRaw,
publishNeighbors, and eligiblePacketSlots already skip slots that cannot form a
topic. A per-slot token is still required. Because eligiblePacketSlots resolves
topic support before serialising, raw JSON is never built for a MeshRank slot
rather than built and discarded.
observer-firmware sends raw to MeshRank; this supersedes that decision, so raw
must stay excluded when the branches merge. MeshRankTakesEveryTypeExceptRaw
fails if a merge re-enables it.
Enhance the neighbor discovery JSON structure by introducing a
default_scope field, which indicates the region name this node
floods to by default. This change improves clarity in the
neighbor discovery process and aligns with the unscoped flood
behavior when no default region is set. Updates include
modifications to the MyMesh class and related message building
functions to accommodate the new field.