get_messages_payload() built each item dict with sender, text, timestamp, hops and path_hashes, but silently dropped the already-resolved sender_pubkey and path_names fields that BleEventHandler writes to every archived message (see _resolve_path_names() in ble/events.py and the archive schema in services/message_archive.py lines 135–137).
Downstream consumers were therefore forced to re-resolve path hashes themselves using only the 1-byte path-hash prefix — a lookup that collides heavily in networks with more than ~256 nodes and yields the wrong repeater name, type and coordinates on nearly every hop.
The sender_pubkey omission had a similar effect on the sender column: clients could only match on display-name, which is ambiguous when two nodes share a name stem (e.g. NL-OV-ZWO-LGH-PD5WB vs the mobile variant NL-OV-ZWO-LGH-PD5WB-MOB).
Fix: added "sender_pubkey" and "path_names" to the item dict. Both fields are read straight from the archive — no new resolution logic is introduced, so there is no additional cost on the hot path. The response schema change is additive: existing clients that ignore unknown keys continue to work unchanged.
- Replace global _last_reply float with _last_reply_per_sender dict.
A reply to one node no longer blocks all other senders for 5 s.
LRU eviction keeps the dict bounded at 200 entries.
- _get_active_channels() now falls back to BotConfig defaults when
the stored channel set is empty (user never saved a selection).
Bot was silently deaf on first run despite the panel showing all
channels pre-checked.
Closes: bot only replies to first sender in multi-node #test session.
The README had fallen significantly behind the current state of the
codebase. This commit brings it up to date across the board.
Content changes:
- Rewrite title, subtitle and opening line to reflect the all-in-one
platform nature of the project instead of focusing on the bridge alone
- Rewrite section 1 (Why This Project Exists) to cover the full
operator workflow: connect, monitor, manage, archive, automate, BBS,
bridge, publish, headless
- Merge the two overlapping platform/testing notes into one
- Update features list: add BBS as a first-class feature, merge
Dynamic Channel Discovery and Add/Delete Channel into Channel
Management, rename Contact Maintenance to Contact Management, remove
Threaded Architecture (implementation detail), add Headless /
Multi-instance as explicit feature
- Move BBS section from top-level §12 into §9 Functionality as §9.14,
adjusting heading levels accordingly
- Add §9.2 note clarifying that contacts and nodes are the same concept
- Add new §8.1 Data Directory with a complete overview of all files and
subdirectories under ~/.meshcore-gui/, including maintenance commands
- Update §10 architecture diagram and component list: add BBSPanel,
BotPanel, REST API block, ChannelService, BbsService,
PublicApiService and MapSnapshotService
- Update §15.2 project structure: add meshcore_gui/api/, all missing
panels and services, and reflect install_scripts/ as a subdirectory
- Fix all install script paths from root level to install_scripts/
- Document install_scripts/install_venv.sh as the first setup step
- Document install_scripts/install_observer.sh in project structure
- Update Observer mode in roadmap: installer present, daemon in
development
- Renumber sections 13–20 to 12–19 after BBS was absorbed into §9
- Update all cross-references and table of contents accordingly
fix(packet_decoder): brute-force channel resolution when hash lookup fails
ChannelCrypto.calculate_channel_hash() and the MeshCore firmware compute
different channel identifiers for the same secret, causing _hash_to_idx to
return None for all hashtag-channel messages. Fallback: try each registered
key individually via a single-key keystore. First valid decryption wins.
Result cached in _hash_to_idx for O(1) resolution on subsequent packets.
fix(events): channel-agnostic sentinel prevents cross-channel duplicates
on_rx_log now marks '*' in DualDeduplicator after storing a message.
on_channel_msg checks this sentinel and suppresses storage regardless of
channel_idx or message_hash differences between the two library systems.
Fixes #mc-radar messages appearing in #weather and vice versa.
fix(events): secondary path-cache keyed by content for hash-mismatch
_path_cache keyed by meshcoredecoder hash; CHANNEL_MSG_RECV carries meshcore
hash (different value). Added _path_cache_by_content ("sender:text[:100]")
as fallback so path_hashes are recovered when the two hashes disagree.
fix(commands,cache): remove stale channel key after del_channel reindex
Cache entry for old_idx not removed after slot move, causing same channel
to appear twice. New DeviceCache.remove_channel_key(idx) called after each
move. asyncio.sleep(0.5) added before re-discovery to let device settle.
feat(channel_panel,commands,dashboard): channel edit — move/reindex support
First channel-edit capability in the GUI. New '↕️ Move / Reindex' mode in
Channel Manager dialog. Source channel selected from dropdown, target index
in number field. ↕ button inline with 🗑 in Messages and Archive submenus.
_cmd_move_channel reads secret from cache or device, writes new slot, clears
old slot, updates cache atomically, triggers re-discovery with settle delay.
### FIXED
- **Multibyte path hash support** (`ble/events.py`, `core/shared_data.py`):
corrected docstrings in both `_resolve_path_names` methods that incorrectly
described path hashes as "2-char hex strings". The actual contact lookup
uses `startswith` matching, which is hash-size agnostic and correctly
handles 1-byte (2 hex chars), 2-byte (4 hex chars) and 3-byte (6 hex chars)
path hashes as introduced in MeshCore firmware v1.14.0. No functional code
was changed — only the documentation was incorrect.
- **MariaDB schema** (`meshcore_schema.sql`): `meshcore_messages.path_hashes`
column widened from `VARCHAR(128)` to `VARCHAR(255)`. The old limit caused
silent truncation for paths longer than ~40 hops in 1-byte mode or ~25 hops
in 2-byte mode. Migration is backward-compatible; existing data is unchanged.
### CHANGED
- `config.py`: version bump `1.17.0 → 1.17.1`.
### RATIONALE
- MeshCore firmware v1.14.0 (2026-03-06) introduced configurable path hash
sizes (1-, 2- or 3-byte per repeater). Verification confirmed that
`meshcoredecoder 0.3.2` already returns correctly sized hex strings via
`_decode_path_len_byte`. The GUI path-resolution logic was already
forward-compatible; only the docstrings and the MariaDB column width required
correction.
### IMPACT
- No BLE handler, GUI panel, service or API endpoint modified.
- `meshcoredecoder` library unchanged; no pip update required.
- MariaDB migration: single `ALTER TABLE` statement, no downtime, no data loss.
WHAT: Four read-only GET endpoints under /api/v1/ registered on the
NiceGUI/FastAPI instance via register_routes(_shared) in __main__.py.
Controlled by API_ENABLED in config.py (default: True).
WHY: Enables the domca.nl PHP collector to pull live mesh data over HTTP
without direct access to SQLite or SharedData internals.
NOTES: Private channel messages are unconditionally excluded in
public_api_service.py — filtering is server-side hard, no auth needed.
Channel rule: idx==0 (Public) or name.startswith('#') (Hashtag) = expose.
WHAT: New + Add Channel button in the Messages submenu opens a dialog
supporting three modes — Hashtag (key derived from name), Private New
(random key + QR export), Private Existing (paste hex key).
WHY: Channels could only be added via firmware/external tools. The new
dialog covers all user scenarios without changing the BLE worker or
existing panels.
NOTES: Requires `qrcode[pil]` for QR rendering (graceful degradation if
absent). Channel re-discovery is triggered automatically on success.
WHAT: New BotPanel replaces the BOT checkbox in ActionsPanel. Interactive
channel checkboxes (from live device channel list) replace the hardcoded
BOT_CHANNELS constant. Private mode restricts replies to pinned contacts only.
BotConfigStore persists settings per device to ~/.meshcore-gui/bot/.
WHY: Bot configuration was scattered (toggle in Actions, channels in code).
A dedicated panel and config store aligns with the BBS panel/BbsConfigStore
pattern and enables private mode without architectural changes.
NOTES: ActionsPanel.__init__ signature simplified (set_bot_enabled removed).
create_worker accepts pin_store kwarg (backwards compatible, defaults to None).
_abbrev_table used a list comprehension inline inside a generator
expression filter. In Python 3, list comprehensions have their own
scope, so the loop variable 'cu' was not visible to the outer 'if'
condition — causing a NameError on every !h / !help DM command.
Extract the comprehension to a local variable 'cats_upper' so both
the iteration and the filter operate on the same pre-built list.
Adds an offline BBS accessible via Direct Message to the node's own key.
Access is channel-based: anyone seen on a configured BBS channel is
automatically whitelisted for DM access. Channels stay clean.
- Multi-channel configuration: any combination of device channels can be
selected; senders on any of them are auto-whitelisted
- Short syntax: !p <cat> <text> and !r [cat] alongside full !bbs syntax
- Category abbreviations computed automatically (shortest unique prefix)
- handle_channel_msg: bootstrap reply on channel + auto-whitelist sender
- handle_dm: DM entry point, checks whitelist, routes to post/read/help
- DM reply routed back to sender via command_sink
- SQLite message store with WAL mode and configurable retention
Adds an offline BBS accessible via Direct Message to the node's own key.
Access is channel-based: anyone seen on a configured BBS channel is
automatically whitelisted for DM access. Channels stay clean.
- Multi-channel configuration: any combination of device channels can be
selected; senders on any of them are auto-whitelisted
- Short syntax: !p <cat> <text> and !r [cat] alongside full !bbs syntax
- Category abbreviations computed automatically (shortest unique prefix)
- handle_channel_msg: bootstrap reply on channel + auto-whitelist sender
- handle_dm: DM entry point, checks whitelist, routes to post/read/help
- DM reply routed back to sender via command_sink
- SQLite message store with WAL mode and configurable retention
Adds an offline BBS accessible via Direct Message to the node's own key.
Access is channel-based: anyone seen on a configured BBS channel is
automatically whitelisted for DM access. Channels stay clean.
- Multi-channel configuration: any combination of device channels can be
selected; senders on any of them are auto-whitelisted
- Short syntax: !p <cat> <text> and !r [cat] alongside full !bbs syntax
- Category abbreviations computed automatically (shortest unique prefix)
- handle_channel_msg: bootstrap reply on channel + auto-whitelist sender
- handle_dm: DM entry point, checks whitelist, routes to post/read/help
- DM reply routed back to sender via command_sink
- SQLite message store with WAL mode and configurable retention
Adds an offline BBS accessible via Direct Message to the node's own key.
Access is channel-based: anyone seen on a configured BBS channel is
automatically whitelisted for DM access. Channels stay clean.
- Multi-channel configuration: any combination of device channels can be
selected; senders on any of them are auto-whitelisted
- Short syntax: !p <cat> <text> and !r [cat] alongside full !bbs syntax
- Category abbreviations computed automatically (shortest unique prefix)
- handle_channel_msg: bootstrap reply on channel + auto-whitelist sender
- handle_dm: DM entry point, checks whitelist, routes to post/read/help
- DM reply routed back to sender via command_sink
- SQLite message store with WAL mode and configurable retention
Adds an offline Bulletin Board System accessible via Direct Message to
the node's own key. All BBS commands (!p, !r, !bbs) are handled directly
in EventHandler.on_contact_msg, independent of MeshBot.
- One node = one board; settings reduced to a single channel selector
- Short syntax: !p <cat> <text> and !r [cat] alongside full !bbs syntax
- Category abbreviations computed automatically (shortest unique prefix)
- !r and !bbs help always include the abbreviation table in the reply
- DM reply routed back to sender via command_sink
- SQLite message store with WAL mode and configurable retention
Implements a fully offline Bulletin Board System for emergency mesh
communication (NoodNet Zwolle, NoodNet OV, Dalfsen and similar
organisations).
New files:
- services/bbs_config_store.py: Manages ~/.meshcore-gui/bbs/bbs_config.json.
Thread-safe, atomic writes. Created on first run. Channels are
enabled and configured at runtime via the GUI — no code changes
required.
- services/bbs_service.py: SQLite persistence at
~/.meshcore-gui/bbs/bbs_messages.db. WAL-mode enabled so multiple
simultaneous instances (e.g. 800 MHz + 433 MHz) share the same
bulletin board safely. BbsCommandHandler parses !bbs post/read/help
mesh commands with live config from BbsConfigStore. Whitelist
enforcement via sender public key (silent drop on unknown key).
- gui/panels/bbs_panel.py: Dashboard panel with channel selector,
region/category filters, scrollable message list and post form.
Settings section lists all active device channels; per channel:
enable toggle, categories, regions, retention and key whitelist.
Changes take effect immediately without restart.
Modified files:
- services/bot.py: MeshBot accepts optional bbs_handler; !bbs commands
are routed to BbsCommandHandler before keyword matching.
- config.py: BBS_CHANNELS removed (config now lives in bbs_config.json).
Version bumped to 1.14.0.
- gui/dashboard.py: BbsConfigStore and BbsService instantiated and
shared across handler and panel. BBS drawer menu item added.
- gui/panels/__init__.py: BbsPanel re-exported.
Storage layout:
~/.meshcore-gui/bbs/bbs_config.json — channel configuration
~/.meshcore-gui/bbs/bbs_messages.db — SQLite message store
No new external dependencies (SQLite is stdlib).
Implements a fully offline Bulletin Board System for use on MeshCore
mesh networks, designed for emergency communication organisations
(NoodNet Zwolle, NoodNet OV, Dalfsen).
New files:
- services/bbs_service.py: SQLite-backed persistence layer with
BbsMessage dataclass, BbsService (post/read/purge) and
BbsCommandHandler (!bbs post/read/help mesh command parser).
Whitelist enforcement via sender public key (silent drop on
unknown sender). Per-channel configurable regions, categories
and retention period.
- gui/panels/bbs_panel.py: Dashboard panel with channel selector,
region/category filters, scrollable message list and post form.
Region filter is conditionally visible based on channel config.
Modified files:
- config.py: BBS_CHANNELS configuration block added (ch 2/3/4).
Version bumped to 1.14.0.
- services/bot.py: MeshBot accepts optional bbs_handler parameter.
Incoming !bbs commands are routed to BbsCommandHandler before
keyword matching; no changes to existing bot behaviour.
- gui/dashboard.py: BbsPanel registered as standalone panel with
📋 BBS drawer menu item.
- gui/panels/__init__.py: BbsPanel re-exported.
Storage: ~/.meshcore-gui/bbs/bbs_messages.db (SQLite, stdlib only).
No new external dependencies.
Implements a fully offline Bulletin Board System for use on MeshCore
mesh networks, designed for emergency communication organisations
(NoodNet Zwolle, NoodNet OV, Dalfsen).
New files:
- services/bbs_service.py: SQLite-backed persistence layer with
BbsMessage dataclass, BbsService (post/read/purge) and
BbsCommandHandler (!bbs post/read/help mesh command parser).
Whitelist enforcement via sender public key (silent drop on
unknown sender). Per-channel configurable regions, categories
and retention period.
- gui/panels/bbs_panel.py: Dashboard panel with channel selector,
region/category filters, scrollable message list and post form.
Region filter is conditionally visible based on channel config.
Modified files:
- config.py: BBS_CHANNELS configuration block added (ch 2/3/4).
Version bumped to 1.14.0.
- services/bot.py: MeshBot accepts optional bbs_handler parameter.
Incoming !bbs commands are routed to BbsCommandHandler before
keyword matching; no changes to existing bot behaviour.
- gui/dashboard.py: BbsPanel registered as standalone panel with
📋 BBS drawer menu item.
- gui/panels/__init__.py: BbsPanel re-exported.
Storage: ~/.meshcore-gui/bbs/bbs_messages.db (SQLite, stdlib only).
No new external dependencies.
Implements a fully offline Bulletin Board System for use on MeshCore
mesh networks, designed for emergency communication organisations
(NoodNet Zwolle, NoodNet OV, Dalfsen).
New files:
- services/bbs_service.py: SQLite-backed persistence layer with
BbsMessage dataclass, BbsService (post/read/purge) and
BbsCommandHandler (!bbs post/read/help mesh command parser).
Whitelist enforcement via sender public key (silent drop on
unknown sender). Per-channel configurable regions, categories
and retention period.
- gui/panels/bbs_panel.py: Dashboard panel with channel selector,
region/category filters, scrollable message list and post form.
Region filter is conditionally visible based on channel config.
Modified files:
- config.py: BBS_CHANNELS configuration block added (ch 2/3/4).
Version bumped to 1.14.0.
- services/bot.py: MeshBot accepts optional bbs_handler parameter.
Incoming !bbs commands are routed to BbsCommandHandler before
keyword matching; no changes to existing bot behaviour.
- gui/dashboard.py: BbsPanel registered as standalone panel with
📋 BBS drawer menu item.
- gui/panels/__init__.py: BbsPanel re-exported.
Storage: ~/.meshcore-gui/bbs/bbs_messages.db (SQLite, stdlib only).
No new external dependencies.
Implements a fully offline Bulletin Board System for emergency mesh
communication (NoodNet Zwolle, NoodNet OV, Dalfsen and similar
organisations).
New files:
- services/bbs_config_store.py: Manages ~/.meshcore-gui/bbs/bbs_config.json.
Thread-safe, atomic writes. Created on first run. Channels are
enabled and configured at runtime via the GUI — no code changes
required.
- services/bbs_service.py: SQLite persistence at
~/.meshcore-gui/bbs/bbs_messages.db. WAL-mode enabled so multiple
simultaneous instances (e.g. 800 MHz + 433 MHz) share the same
bulletin board safely. BbsCommandHandler parses !bbs post/read/help
mesh commands with live config from BbsConfigStore. Whitelist
enforcement via sender public key (silent drop on unknown key).
- gui/panels/bbs_panel.py: Dashboard panel with channel selector,
region/category filters, scrollable message list and post form.
Settings section lists all active device channels; per channel:
enable toggle, categories, regions, retention and key whitelist.
Changes take effect immediately without restart.
Modified files:
- services/bot.py: MeshBot accepts optional bbs_handler; !bbs commands
are routed to BbsCommandHandler before keyword matching.
- config.py: BBS_CHANNELS removed (config now lives in bbs_config.json).
Version bumped to 1.14.0.
- gui/dashboard.py: BbsConfigStore and BbsService instantiated and
shared across handler and panel. BBS drawer menu item added.
- gui/panels/__init__.py: BbsPanel re-exported.
Storage layout:
~/.meshcore-gui/bbs/bbs_config.json — channel configuration
~/.meshcore-gui/bbs/bbs_messages.db — SQLite message store
No new external dependencies (SQLite is stdlib).
Implements a fully offline Bulletin Board System for emergency mesh
communication (NoodNet Zwolle, NoodNet OV, Dalfsen and similar
organisations).
New files:
- services/bbs_config_store.py: Manages ~/.meshcore-gui/bbs/bbs_config.json.
Thread-safe, atomic writes. Created on first run. Channels are
enabled and configured at runtime via the GUI — no code changes
required.
- services/bbs_service.py: SQLite persistence at
~/.meshcore-gui/bbs/bbs_messages.db. WAL-mode enabled so multiple
simultaneous instances (e.g. 800 MHz + 433 MHz) share the same
bulletin board safely. BbsCommandHandler parses !bbs post/read/help
mesh commands with live config from BbsConfigStore. Whitelist
enforcement via sender public key (silent drop on unknown key).
- gui/panels/bbs_panel.py: Dashboard panel with channel selector,
region/category filters, scrollable message list and post form.
Settings section lists all active device channels; per channel:
enable toggle, categories, regions, retention and key whitelist.
Changes take effect immediately without restart.
Modified files:
- services/bot.py: MeshBot accepts optional bbs_handler; !bbs commands
are routed to BbsCommandHandler before keyword matching.
- config.py: BBS_CHANNELS removed (config now lives in bbs_config.json).
Version bumped to 1.14.0.
- gui/dashboard.py: BbsConfigStore and BbsService instantiated and
shared across handler and panel. BBS drawer menu item added.
- gui/panels/__init__.py: BbsPanel re-exported.
Storage layout:
~/.meshcore-gui/bbs/bbs_config.json — channel configuration
~/.meshcore-gui/bbs/bbs_messages.db — SQLite message store
No new external dependencies (SQLite is stdlib).
Implements a fully offline Bulletin Board System for emergency mesh
communication (NoodNet Zwolle, NoodNet OV, Dalfsen and similar
organisations).
New files:
- services/bbs_config_store.py: Manages ~/.meshcore-gui/bbs/bbs_config.json.
Thread-safe, atomic writes. Created on first run. Channels are
enabled and configured at runtime via the GUI — no code changes
required.
- services/bbs_service.py: SQLite persistence at
~/.meshcore-gui/bbs/bbs_messages.db. WAL-mode enabled so multiple
simultaneous instances (e.g. 800 MHz + 433 MHz) share the same
bulletin board safely. BbsCommandHandler parses !bbs post/read/help
mesh commands with live config from BbsConfigStore. Whitelist
enforcement via sender public key (silent drop on unknown key).
- gui/panels/bbs_panel.py: Dashboard panel with channel selector,
region/category filters, scrollable message list and post form.
Settings section lists all active device channels; per channel:
enable toggle, categories, regions, retention and key whitelist.
Changes take effect immediately without restart.
Modified files:
- services/bot.py: MeshBot accepts optional bbs_handler; !bbs commands
are routed to BbsCommandHandler before keyword matching.
- config.py: BBS_CHANNELS removed (config now lives in bbs_config.json).
Version bumped to 1.14.0.
- gui/dashboard.py: BbsConfigStore and BbsService instantiated and
shared across handler and panel. BBS drawer menu item added.
- gui/panels/__init__.py: BbsPanel re-exported.
Storage layout:
~/.meshcore-gui/bbs/bbs_config.json — channel configuration
~/.meshcore-gui/bbs/bbs_messages.db — SQLite message store
No new external dependencies (SQLite is stdlib).
Implements a fully offline Bulletin Board System for use on MeshCore
mesh networks, designed for emergency communication organisations
(NoodNet Zwolle, NoodNet OV, Dalfsen).
New files:
- services/bbs_service.py: SQLite-backed persistence layer with
BbsMessage dataclass, BbsService (post/read/purge) and
BbsCommandHandler (!bbs post/read/help mesh command parser).
Whitelist enforcement via sender public key (silent drop on
unknown sender). Per-channel configurable regions, categories
and retention period.
- gui/panels/bbs_panel.py: Dashboard panel with channel selector,
region/category filters, scrollable message list and post form.
Region filter is conditionally visible based on channel config.
Modified files:
- config.py: BBS_CHANNELS configuration block added (ch 2/3/4).
Version bumped to 1.14.0.
- services/bot.py: MeshBot accepts optional bbs_handler parameter.
Incoming !bbs commands are routed to BbsCommandHandler before
keyword matching; no changes to existing bot behaviour.
- gui/dashboard.py: BbsPanel registered as standalone panel with
📋 BBS drawer menu item.
- gui/panels/__init__.py: BbsPanel re-exported.
Storage: ~/.meshcore-gui/bbs/bbs_messages.db (SQLite, stdlib only).
No new external dependencies.
- Replace two fixed-destination back-buttons on the route page with a
single arrow_back button using window.history.back(), so navigation
always returns to the calling screen (Messages or Archive).
- Guard setIcon() and setPopupContent() in applyDevice/applyContacts
behind isPopupOpen() to prevent popup flickering on the 500 ms
update tick.
- Set fadeAnimation: false and markerZoomAnimation: false on both
Leaflet map instances (main map and route map) to eliminate popup
flash on first click, particularly noticeable on Raspberry Pi.