.env.example still described the #896 model: the ingestor owning an RNS
identity and deriving its !xxxxxxxx from "the transport identity already
in RETICULUM_CONFIG_DIR". #897 shipped the opposite. RE8 excludes the
transport identity from the pick, because RNS generates it as an
independent keypair that matches none of the operator's announced
destinations; the id is the discovered primary identity, the one
announcing the most destinations on the machine. README says this
correctly and .env.example contradicted it, pointing operators at the
hash rnstatus prints as "Transport Instance".
The strands-the-old-row caveat #897 added to README and CHANGELOG is
carried over too, since .env.example is where an operator is standing
when they set the variable that moves the id.
Two guards in the deployment-surface class, both negative-tested against
main, where they correctly fail.
pytest 1443 passed; black clean.
Answers the two questions left open on #888 after #893.
INGESTOR_NODE_ID was required for PROTOCOL=reticulum because a passive
announce listener has no handshake revealing "our" node id. The premise
holds; the conclusion did not follow. The ingestor now owns an RNS
identity, generated on first run and stored 0600 as potato_mesh_identity
inside the app-owned RETICULUM_CONFIG_DIR, and derives its !xxxxxxxx
through the same identity-hash mapping applied to every peer, so its own
row is shaped like any other Reticulum row. A plain PROTOCOL=reticulum
deployment brings up the heartbeat, and so the reticulum packets/hour
scope, unaided; the variable still overrides, which also means no key is
generated for a deployment that does not need one. The identity is local
and never announced, so nothing new transmits. Persist-or-nothing is
deliberate: an unstorable identity would be regenerated per start and
each generation is a different node id, filing one orphan ingestor row
per restart, so the provider reports no id and warns instead. An existing
but unreadable key file is never overwritten. The startup warning #890
emitted for an unset INGESTOR_NODE_ID is removed.
CONNECTION and RETICULUM_CONFIG_DIR/RETICULUM_INTERFACES are disjoint
rather than in conflict: CONNECTION names one serial, TCP, or BLE
endpoint and an RNS stack has no such thing, which is why connect() has
always ignored it. What was wrong was the silence, since the image sets a
serial default for every protocol. connect() now says once at info that
the value is dropped and names the two variables that do apply. The image
default stays: removing it would change how meshtastic and meshcore
containers resolve their port, and a Reticulum follow-up is the wrong
change to carry that.
SPEC RN9/RN10, ACCEPTANCE RN-A9/RN-A10; CONTRACTS, README and
.env.example reconciled. 100% line and branch coverage on the provider;
pytest 1442 passed, rspec 1717 examples 0 failures.
* fix reticulum protocol loose ends from #888 review
* data: fix quoted Compose defaults blacking out every channel
* data,web: fix four defects found reviewing this PR
The periodic activity announcement was gated only by RX_ONLY, which
defaults to off, so a stock ingestor deployed to feed a map starts
broadcasting on its community's default channel without the operator
having chosen to. The only opt-out, RX_ONLY=1, also silences the
MeshCore telemetry polls, so "ingest but stay quiet" was not expressible.
Add an ANNOUNCE boolean (default 0) as an opt-in gate on top of RX_ONLY,
which keeps its meaning as the global TX kill switch and overrides
ANNOUNCE=1. Amend SPEC MA7 (which had explicitly ruled out a separate
enable env) and MA-A7 with the reasoning, document both flags in the
README env table — the RX_ONLY row did not mention the announcement at
all — and add gate unit tests for all four combinations plus the
missing-attribute fail-closed path.
Adds an optional passive UDP transport (TRANSPORT=udp) that reads the node's
"Mesh via UDP" LAN multicast instead of holding the radio's single API/serial
slot, filters to the PRIMARY channel by channel-hash (fail-closed), decrypts
with the default PSK, and enriches payloads to match the API-path packet shape
so the collector receives identical records. Implemented as a MeshProtocol
provider; 100% unit coverage on new modules; real-capture fixtures included
(node IDs/GPS anonymized). See PR description for details.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Fix regression where Meshcore chat senders show as Meshtastic
* Address review feedback for protocol misclassification fix
- ingest.rb: exclude wrapper ``protocol`` key from /api/nodes batch-limit
count so the documented 1000-node maximum still applies after the
Python ingestor started stamping protocol at the wrapper level.
- Drop plan-file references from production and test comments per the
repo guidelines; the why is already explained inline.
* Address protocol-fallback review feedback
- Neighbor placeholder now inherits the source node's protocol from the
surrounding /api/neighbors entry, so the badge tracks the radio the
peer lives on instead of collapsing to the neutral "Unknown" label
(review item #1).
- resolve_record_protocol logs one warn_log line when an explicit
protocol stamp is rejected as malformed, making misbehaving custom
protocol adapters visible in the operator log instead of silently
falling back (review item #3).
* Extract buildNodePlaceholder helper for testability
The neighbor placeholder logic in main.js lives inside an untested
closure, so codecov reported the protocol-propagation lines as
uncovered. Extract the small placeholder builder into long-link-router
so it can be unit tested directly; the closure-internal call site stays
trivial (one factory call + one fallback call).
* data: refactor 4/7 interfaces
* data: address PR #775 review feedback
Fix the two CI test regressions caused by the package split:
- ``factory._load_ble_interface`` no longer keeps a stale module-level
``BLEInterface`` cache that survived ``monkeypatch`` teardown across
tests. The package-level attribute is now the single cache; the
``factory.py`` global was removed. This unblocks
``test_load_ble_interface_sets_global``.
- ``interfaces/__init__.py`` re-resolves ``SerialInterface`` and
``TCPInterface`` from ``meshtastic.*`` at package-load time so that a
test that pops ``data.mesh_ingestor.interfaces`` from ``sys.modules``
and re-imports picks up the freshly registered classes rather than
whatever a cached ``factory.py`` first resolved. This unblocks
``test_interfaces_patch_handles_preimported_serial``.
Restore 100% patch coverage on the interfaces subpackage by:
- Adding tests for previously uncovered, testable paths:
``_extract_host_node_id(None)``, ``_ensure_channel_metadata``,
``_normalise_nodeinfo_packet`` (None input + dict-conversion fallback),
``_resolve_lora_message`` (radio_section paths), ``_modem_preset``
(preset attr fallback + unparseable value), ``_camelcase_enum_name``
separator-only input, ``_region_frequency`` no-digit enum name,
``_ensure_radio_metadata`` unresolvable-message path, plus the
unknown-section recursive branch of ``_candidate_node_id``.
- Marking genuinely unreachable defensive branches with
``pragma: no cover`` (BLE receive loop body, upstream API regression
guards, patch re-entry guard, unreachable ``NoAvailableMeshInterface``
fallback).
* data: refactor 3/7 protocols
* data: address PR #774 review feedback
- Rewrite the parents[4] path comment in protocols/meshcore/debug_log.py
to clearly explain why the index changed from parents[3] (the original
pre-split index) without contradicting the code.
- Add tests covering the six lines flagged uncovered by codecov:
* _process_self_info host-position branch (handlers.py:78)
* on_contact_msg early-return for missing text/sender_ts (handlers.py:278)
* close() RuntimeError swallow when loop closes mid-call (interface.py:155-156)
* _run_meshcore wrapper around _ensure_channel_names failure (runner.py:131-132)
Restores 100% patch coverage on the meshcore package.
* web: reference meshcore nodes in chat
* data: add adv_name to messages
* web: address review comments
* derive actual companion from name string
* derive actual companion from name string
* derive actual companion from name string
* web: address review comments
* web: address review comments
* chore: bump version to 0.6.0 and remove deprecated env var aliases
BREAKING CHANGES:
- POTATOMESH_INSTANCE removed — use INSTANCE_DOMAIN
- PROVIDER removed — use PROTOCOL
- MESH_SERIAL removed — use CONNECTION
- PORT config alias removed — use CONNECTION
The _ConfigModule proxy class (which kept PROTOCOL/PROVIDER and
CONNECTION/PORT in sync) is deleted. docker-compose.yml now defaults
INSTANCE_DOMAIN to http://web:41447 so deployments without an explicit
value continue to work.
* tests: run black
* address review comments
Replace the hardcoded max_idx=8 parameter on _ensure_channel_names with
a DEVICE_INFO query (send_device_query → max_channels) so the full range
of configured channels is always probed regardless of firmware variant.
Falls back to _CHANNEL_PROBE_FALLBACK_MAX (32) when the query fails or
the device returns an older firmware that omits max_channels.
Also removes always=True from the warning-severity channel failure log
(redundant — only debug-severity is gated behind the DEBUG flag) and adds
a deferred-import comment in _ensure_channel_names.
* data: register meshcore channel mappings
* fix: use mc.commands.get_channel for MeshCore channel name probing
MeshCore exposes device commands via the commands sub-object
(CommandHandler), not directly on MeshCore instances.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: probe all channel indices regardless of ERROR responses
Removed the consecutive-error early-stop heuristic from
_ensure_channel_names so sparse channel configurations (e.g. slots 0
and 5 configured with slots 1–4 empty) are fully probed. Only a hard
exception aborts the loop early.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* data: provide frequency and modem preset for meshcore
* data: provide frequency and modem preset for meshcore
* ingestor: address review comments
* fix: remove duplicate _mark_packet_seen entry from handlers __all__
* ci: install meshcore in Python workflow
protocols/meshcore.py now imports meshcore at module level (required to
fix a self-referential import failure after the providers/ → protocols/
rename). test_provider_unit.py imports that module unconditionally, so
meshcore must be present in the test environment.
* data: run black