Commit Graph
118 Commits
Author SHA1 Message Date
yellowcooln 127b0a4df1 fix(sensors): normalize Victron power precision and dependency setup 2026-10-12 15:02:09 -04:00
yellowcooln 17aa44bf17 feat(sensors): add read-only Victron VE.Direct telemetry 2026-10-11 22:29:25 -04:00
agessaman 737fda2628 feat(acl): admit a blank password as guest when the repeater has no guest password
Firmware simple_repeater compares a blank password from a sender not in the
ACL with guest_password, which defaults to empty, so anyone may log in as a
guest and fetch base telemetry. The repeater refused it unless
allow_read_only was on. Room servers are unchanged.
2026-10-02 15:48:13 -07:00
Treehouse-00 91ebdd0b36 docs(presets): correct the Waev preset's routing notes
The Waev preset's header described a router constant and a 1500 ms
broker-A-then-B failover that Waev's router no longer has, and called
mqtt-a.waev.app "force primary, no failover". Waev routes each
connection to one of two broker pools and fails over to the other; the
pool hostnames only choose which is tried first.

Describe that without naming Waev's internals. The override example in
config.yaml.example named a broker, waev-b, that the preset does not
have, so it did nothing; it now prefers pool A by overriding the host.
The two test docstrings that repeated the old claims follow.
2026-10-02 10:57:51 +01:00
agessaman d5e4bd754c fix(config): name the radios[] entry with a stray sync word
build_radio_stack pops _radio_id before building each radio, so the
warning never named the entry on a real node. A _radio_label survives the
pop, and the warning now points at radios[<id>].radio, saying the value is
inherited only when it is.

config.yaml.example documents sync_word.
2026-10-02 10:57:24 +01:00
agessaman 28e0f87a7e fix(acl): let an admin grant evict the least recently seen admin
A table full of admins refused every newcomer, and now that entries outlive
a restart that lockout was permanent; the upgrade placeholder made it likely
with max_clients: 1. The placeholder is now 32 (firmware MAX_CLIENTS), and
an admin password login or setperm to an admin role evicts the admin seen
least recently, by its stored last login after a restart. Blank-password and
guest newcomers are still refused, so they cannot strip admins one by one.

An eviction now waits for the newcomer: a refused login (a replay) puts the
evicted entry back, and its stored row is deleted only once the newcomer's
grant is stored, or once that grant's next write succeeds after a failure.
Evicted clients' replay watermarks are kept (capped, in RAM) so an evicted
admin's captured login cannot be replayed when it comes back.

unregister_identity also detaches an ACL listed only by name, after a hash
collision displaced it from the hash index.
2026-10-02 10:57:24 +01:00
agessaman d5541aca3d fix(acl): let a room's hot reload apply; make an interrupted key change retryable
Fourth review round (opencode; codex hit its usage limit):
- update_identity's hot reload was always refused: the identity manager
  rejects a name or hash already registered, which the running room's own
  entry always is. The ACL name index then lagged the config, so a renamed
  room was unmanageable by name, and a later rekey left a second live,
  writable ACL. The old entry is now unregistered first and restored if
  the new registration is refused (IdentityManager.unregister_identity),
  and a move falls back to the store key when the name misses.
- A key change whose save and cleanup both failed left a copy at the new
  key that blocked every retry. A copy under the room's own label is
  replaced on retry; another identity's entries still refuse the move.
- A registration now detaches the ACL it replaces before loading, warns
  when it displaces another identity sharing its hash byte, and the no-store
  guard for a key change lives in LoginHelper.move_room_acl.
- /acl_stats is documented as it has always answered; store_error and
  store_errors say they clear once a later read succeeds; the example
  config says room servers keep their own security settings.
2026-10-02 10:48:10 +01:00
agessaman e0e7cbd9e3 fix(acl): address review of persistent ACLs; add ACL management API
Review (codex, opencode) found:
- A room renamed and rekeyed in one update lost its ACL, and a room later
  created under the old name could adopt it. Room servers are no longer
  adopted by label; update_identity moves their rows to the new key and
  name in one transaction and points the live ACL at them. Only the
  repeater, which is unique, still adopts by label after a key change.
- A removal racing a grant could leave the grant stored but not listed.
  Each ACL change and its write now happen under one lock.
- A failed write was reported as OK. The store now raises; setperm answers
  "Err - failed to save" and leaves the table as it was, and the API
  returns an error. A login still succeeds, as its session is valid.
- Re-registering an identity (a rename) built a new ACL, zeroing every
  session's activity and replay watermark, so the room sync loop stopped
  pushing to logged-in clients. The live ACL is now reused.
- The repeater's max_clients default rises to firmware's 32: admins are
  never evicted and now persist, so 5 or 10 filled permanently. Stored
  entries past max_clients all load, with a warning.
- atoi takes ASCII digits only; non-repeater identity types get their own
  label; the sync-loop skip logs at debug; `get acl` has help.

API for the web UI:
- POST /api/acl_set_permissions sets an entry by identity name.
- /api/acl_clients lists entries by identity name rather than the 1-byte
  hash two identities can share, with permissions_value, persisted and
  identity_pubkey. /api/acl_info adds acl_entries and stored_entries, and
  authenticated_clients counts entries that have been active.
- /api/acl_remove_client reads client_pubkey, as its schema always said
  (the UI sent it, and the endpoint read only public_key), and takes
  identity_name.
2026-10-02 10:48:10 +01:00
yellowcooln f0a6a1d6c6 feat(sensors): discover bounded modem diagnostic metrics 2026-10-02 10:48:10 +01:00
agessaman 6a7a4ba117 fix(config): overlay per-radio config sections on global defaults
Multi-radio entries currently replace radio, sx1262, and transport
configuration sections wholesale. Partial sx1262 entries therefore discard
board settings supplied by the top-level hardware preset, including TCXO
control, DIO2 RF switching, and radio enable pins.

Merge mapping sections key-by-key instead, treating the top-level sections as
defaults and radios[] values as per-radio overrides. Keep radio_type as a
scalar override so heterogeneous fabrics continue to work.

Overlay makes two spellings of one setting reachable together, where the
entry's could lose to an inherited default it never wrote: en_pins beats
en_pin in SX1262Radio, and address/serial_number beat device_address/serial
in get_radio_for_board. An entry naming either spelling now displaces the
other, so the failure mode is not a radio that silently never powers up.

This also corrects duty-cycle metering. It builds its air profiles through
_merge_radio_entry, so a radios[] entry that set only its frequency was
metered on library defaults -- 62.5 kHz / SF8 / CR8 -- rather than on the
node's own top-level radio block, and reported no tx_power at all.
_inherited_air_profile's docstring already described the overlay semantics
this commit implements.

Inheritance reaches the MQTT radio map the same way, and there it needed a
guard. The map is rebuilt from config on every status publish, so once a
partial entry inherits the top-level radio block, saving the default radio's
bandwidth moved the reported bandwidth of every radio that omits one --
while only the default radio is retuned, the rest keeping their band until a
restart. Observers join that map against rx_radio_id to decide which band
carried a packet, so it would have attributed those radios' packets to a band
they were never on. build_radio_status_entries now takes the map built when
the radios were last really configured and keeps every non-default entry from
it, which is the line AirtimeBudgets._effective_air already holds for
metering; MeshCoreToMqttPusher seeds it on its first build.

Log each resolved radio config at debug level, redacting modem_tcp.token via
redact_modem_tokens_in_place, since what an entry inherited is otherwise
invisible and a wrong answer surfaces only as a radio that will not transmit.

Add regression coverage for TCXO/RF-switch inheritance, explicit false
overrides, partial radio air settings, CH341 device selection, per-radio
radio_type overrides, alias displacement, metering inheritance, token
redaction, and a live default-radio save leaving other radios' published
air settings alone.

Fixes #485
2026-09-21 15:56:04 -07:00
agessaman 043128e150 feat(mqtt): report each radio's TX power in the status radio map
A radio id resolved to its frequency and modulation, which is enough to say
which band a packet was on but not how hard it was pushed. Two radios on one
node routinely run different power -- a wide local radio turned down to keep
off its neighbour, a narrow backhaul at full -- and nothing in the air settings
says so, which leaves an observer comparing their coverage with a term missing.

Each entry in the status `radios` map gains `tx_power`, in dBm.

The value is read through build_radio_profiles, so it inherits the same merge
and per-radio override rules as the rest of the profile rather than a second
reading of the config. get_radio_for_board applies a different default per
radio type -- 14 for kiss, 22 for the modems, and none at all for sx1262, which
requires the key -- so those defaults now live beside the other air defaults and
a config that is silent reports what is actually on the air.

Where the power cannot be known it is omitted rather than guessed, and unlike
the air settings it never voids the map: power does not decide which band a
packet was on, so an entry without it still attributes the packet correctly.
Single-radio nodes publish no map at all, as before.

Everything the map reports is what the node is configured with, not a reading
from the hardware, and the docstring now says so: a driver may clamp what it was
given (an SX1262 holds -9..22 dBm), and editing a radios[] entry does not
reconfigure that radio live, because config_manager only applies the top-level
radio section. That gap predates this field -- it applies to the frequency and
modulation already in the map -- but a power figure invites the comparison, so
it is written down rather than left to be discovered.

A boolean reads as unknown rather than as int(True)'s plausible-looking 1 dBm,
and an unreadable optional field logs at debug: this runs once per status
publish, so a warning there would repeat for the life of the node.
2026-09-13 18:39:44 -07:00
agessaman 1046952266 feat(mqtt): attribute published packets to the radio that carried them
A two-radio bridge is one MeshCore node with one pubkey, and the path
byte it appends on repeat is the same whichever radio carried the
packet. Nothing on the air says which band a hop used, and an observer
receiving on one side can only infer the other by already knowing its
neighbour's frequency. The node itself does know, so say it.

Two halves, both needed:

- Packets gain rx_radio_id (the radio that heard it) and tx_radio_ids
  (every radio that retransmitted it - a bridge repeating on both sides
  lists both). tx_radio_ids is always a list, so a consumer never has to
  branch on the JSON type, and is absent rather than empty when the
  packet was not repeated.
- Status gains radios: [{id, radio}], mapping each id to its
  MHz,kHz,SF,CR settings. Without it a radio id is just an
  operator-chosen label. Ids are unique only within a node, so consumers
  key on (origin_id, id). Rebuilt per publish, since air settings can
  change from the web UI while the node runs.

Single-radio nodes publish neither and their payload stays byte-for-byte
what it was: the ids would say nothing the status message doesn't
already. A node whose radio settings cannot all be read publishes no map
at all rather than a partial one that would place a packet on the wrong
band.

On a Fabric node the top-level status radio field now reports the
default radio rather than the top-level radio: block, which is the base
entries inherit from and can describe a band no radio is on.
2026-09-13 18:39:43 -07:00
agessaman d4aca403a5 fix(duty-cycle): meter each radio against its own channel
A duty cycle is a limit on a channel, not on a node, and one AirtimeManager
charging every radio at the top-level radio block's modulation is wrong twice
over on a bridge. The same packet occupies a 62.5 kHz channel about eight times
longer than a 500 kHz one, so whichever bandwidth that block names, the other
radio is mis-charged by that ratio; and the two share one allowance, so a busy
local radio spends the backhaul's airtime on a band it never transmits on.

There is now one budget per channel, built from each radio's own profile and
its own band's limit -- 868.0-868.6 MHz allows 1% where 869.4-869.65 allows
10%, and one number cannot describe both. Two radios tuned alike share a
budget, because they contend for the same spectrum. duty_cycle.shared_budget
restores a single node-wide allowance for anyone who wants the conservative
reading, and a single-radio node is metered exactly as it was.

Metering a channel means knowing which radio will carry a packet, and the
engine used to work that out by restating openhop_core's dispatch rules. It
asks now. openhop_core passes a packet's ingress radio to the TX policy, so the
choice is a function of the packet rather than of whatever arrived most
recently; the repeater asks before scheduling and gets the answer the send will
reach. That also closes a race that predates this: sticky and bridge read the
node's last RX at send time, which after a retransmit delay on a busy node is
another packet's arrival, and moves a reply onto the wrong band.

The core change is optional. The keyword in resolve_tx_radio_id's signature is
the version check, and against a core without it the tx_mode selectors are
registered in their original one-argument form and behave exactly as they did.
What changes there is the gate: with no way to know which radio will transmit,
every radio the fabric could pick is held to its own budget, so a node cannot
transmit past a legal limit by guessing the quiet channel. Which mode is in
force is logged at startup.

Also fixed, each reproduced by an adversarial review of the earlier attempt:

- A node with one radios[] entry had its modulation and limit overwritten by
  the stale top-level block on every radio save from the web UI. The refresh
  path now branches on whether profiles were read, not on how many radios
  there are.
- build_radio_profiles is all-or-nothing by design -- it feeds the UI -- so one
  radio_type: none collapsed metering onto the top-level block for the radio
  still transmitting. Metering has its own builder that degrades per radio.
- Carrying the whole node's spend onto an unrecognised channel compounded over
  repeated rebuilds (100 -> 400 -> 1600 ms) and wedged transmission for the
  rest of the window. Carry-over is keyed by channel, which makes an unchanged
  rebuild idempotent, and an unrecognised one starts from the busiest single
  channel rather than the sum of them.
- A rebuild worked the previous default manager out from the config again,
  which by then already named the new default -- preserving the wrong object
  and orphaning the one NeighborScopeHelper holds for the life of the process.
  It is remembered rather than recomputed.
- Every caller that reports one airtime figure was reporting the default
  radio's channel: the MeshCore wire field total_air_time_secs, companion
  stats, the RRD and SQLite history, and /stats. They read a node total again
  -- totals summed across channels, utilisation the highest of them, because a
  node at its limit on one channel is at a limit however quiet the other is.

tests/test_fabric_fanout.py's rig never bound a tx_mode selector, so all 49 of
those tests ran against a fabric with no TX policy at all. It binds the real
one now; that gap is why an on-air change went unnoticed through two reviews.
2026-09-13 10:45:58 -07:00
agessaman 071196cb2b refactor(fabric): rename local_tx_mode to origin_tx
openhop-dev/openhop_core#123 settled on fabric.origin_tx: default | all
as the name for sending locally originated packets out on every radio,
and openhop-dev/openhop_core#124 with openhop-dev/openhop_repeater#412
implement that knob in core. Use the same key here so a Fabric config
means one thing whichever implementation lands.

local_tx_mode is still read, so a config written against the first
version of this branch keeps its behaviour. If both keys are set to
different modes, startup rejects the config before any hardware is
opened, since there is no way to tell which one the operator meant.
While running, the engine reads the option through the same helper and
lets origin_tx win. Validation errors name the key the operator actually
wrote.

The radio stack meta and the startup log report origin_tx.
2026-09-12 15:06:47 -07:00
agessaman f510aedf78 feat(fabric): repeat on both sides of a two-radio bridge
Add two Fabric options for an exactly-two-radio node:

- fabric.repeat_on_ingress (tx_mode: bridge): a relayed packet is sent
  on the other radio first, then back out on the radio that heard it.
- fabric.local_tx_mode: all: locally originated packets (companions,
  adverts, room/protocol replies) go out once on every radio, default
  radio first.

MeshCore processing (policy, dedupe, path mutation) still runs once per
logical packet; only the physical TX is duplicated, each egress with its
own Packet, serialised through the TX lock and gated independently for
duty cycle and link health. One successful egress is a successful
forward. Multi-ack extras share the egress set.

inject_packet registers its single ACK waiter before the first TX so an
ACK heard mid-fan-out outlives the dispatcher's ACK cache. Fan-out fixes
flood scope and path-hash mode once before copying and metering, so all
radios carry identical bytes and the duty-cycle gates see them.

Packet records gain tx_radio_ids (JSON column + migration); tx_radio_id
remains the primary successful egress. Invalid option combinations are
rejected before hardware is opened. Defaults preserve existing behaviour.
2026-09-10 17:16:27 -07:00
Rightup c9fddbfd4b fix: update file handling to use UTF-8 encoding for YAML operations 2026-09-06 20:32:43 +01:00
yellowcooln 3e008cac26 fix(plugins): use hosted catalogue endpoint 2026-09-04 11:00:03 -04:00
Rightup 26560fd50a Add comprehensive tests for plugin management and IPC functionality
- Introduced tests for the plugin catalogue installation and update flows in `test_plugin_catalogue_install.py`.
- Added unit tests for the GitHub Releases plugin client in `test_plugin_github_releases.py`.
- Implemented IPC tests for the plugin manager Unix socket protocol in `test_plugin_ipc.py`.
- Created lifecycle and installation tests for the plugin manager in `test_plugin_manager_lifecycle.py`.
- Added tests for plugin manifest parsing in `test_plugin_manifest.py`.
- Developed tests for plugin filesystem layout and state management in `test_plugin_storage.py`.
2026-09-03 22:16:38 +01:00
yellowcooln af3d128176 docs: add CARTO basemap key example 2026-08-27 10:32:35 -04:00
Rightup a6a75f84ef fix: bump default value for max_clients 2026-08-25 17:02:37 +01:00
yellowcooln 76e9a9c745 feat: migrate openHop modem sensor 2026-08-20 12:17:26 -04:00
yellowcooln 9511a8477f feat: migrate openHop modem transport names 2026-08-20 09:40:09 -04:00
Rightup e840c10ed9 Merge branch 'pr-392' into dev 2026-08-18 08:33:11 +01:00
Rightup 2f6dcf3c69 feat(advert): add direct advert interval configuration and scheduling 2026-08-17 14:25:19 +01:00
ViezeVingertjes fd4e364fa3 Install the USB modem udev rule and document the Docker device path limitation 2026-08-15 10:38:47 +02:00
Rightup 51e16efcbb Merge branch 'pr-387' into intergration 2026-08-13 16:06:08 +01:00
Étienne Fesser 22acdff6d3 feat(sensors): add BME280 temperature/humidity/pressure plug-in 2026-08-12 17:24:38 +02:00
Rightup 4c2a3af30a feat: add multi-radio stack support and related tests
- Added tests for multi-radio stack functionality, including merging radio entries and building radio stacks.
- Implemented policy context tests to ensure correct handling of radio IDs in multi-radio scenarios.
2026-08-11 16:14:36 +01:00
agessaman 4fa25893ad feat(mqtt): neighbours trigger endpoint, payload counters, persistent schedule
Follow-up work on the neighbours feature, plus one crash found while reviewing it.

The crash: `mqtt_brokers.neighbors` is a settings block, but the per-broker key of
the same name is a boolean and config.yaml.example documents both, so
`neighbors: true` under mqtt_brokers is an easy hand-edit to make. Every reader
called .get() on it directly, raising AttributeError inside
NeighborScopeHelper.refresh_config -- which is built during daemon init, so the
daemon would not start at all. A shared neighbors_config_block() accessor now
ignores a non-mapping (with one startup warning instead of per-tick noise) and
the API save rewrites it as a proper block.

Payload counters, mirroring firmware buildNeighborsMessage:

- total_neighbors and queried_neighbors. The latter cannot be derived from
  `status`: a neighbour the sweep never reached reports `timeout` exactly like one
  that was asked and stayed silent. ScopeResult therefore carries a `transmitted`
  flag, set only past the point where the injector confirms the request is on air.
- Firmware's third field, `truncated`, is deliberately not emitted -- it reports a
  fixed PSRAM JSON buffer overflowing, which openhop does not have. total_neighbors
  here consequently always equals the published row count.
- self.default_scope: the region this node stamps on outgoing floods, or `*` when
  unset. Read from live config so `region default <name>` applies without a
  restart, and `#`-stripped like the scopes string beside it. Firmware tracks this
  internally but does not publish it, so this field is openhop-only.

POST /api/publish_neighbors runs a cycle immediately, the HTTP twin of the
`discover.scopes` mesh CLI command. Authenticated by default along with the rest
of /api, and it schedules the cycle on the event loop rather than holding the
request open for the minutes a cycle takes.

Schedule persistence: _next_publish_at is monotonic and _last_publish_at was
in-memory only, so every restart read as "due" and spent a discovery broadcast
plus one serialized scope query per neighbour. A generic daemon_state table
(migration 14) now records the last publish and start() resumes from it, never
sooner than a five-minute grace window and never later than one interval. Two
details this had to get right:

- The schedule keys off the last *successful* publish. A failed publish also sets
  _last_publish_at and reschedules on the short retry delay; persisting that would
  silently turn the retry into a full interval.
- _tick's disabled branch no longer clears the schedule before the feature has
  been enabled in this process. At boot the MQTT connections may not be up, so
  enabled() can briefly be false, and clearing there would discard the restored
  schedule and re-run the sweep anyway -- the exact thing this prevents.
2026-07-28 22:03:33 -07:00
agessaman eaf28e28b7 feat(mqtt): periodic neighbours publication with serialized scope discovery
Python port of the firmware's WITH_MQTT_NEIGHBORS feature (MeshCore PR #35 plus
the aba571ed pacing rework). Each cycle refreshes the zero-hop neighbour table
with a node-discovery broadcast, asks every neighbour for its region scopes, and
publishes the assembled table to the MQTT neighbors topic.

- NeighborScopeHelper: client side of the anon-regions request (the server side
  already existed in openhop_core). Queries are strictly serialized -- firing
  them as a burst makes the responses collide, which is what the firmware fix
  addressed. router.inject_packet resolves at the firmware's logTx/logTxFail
  boundary, so the response deadline is armed only once the request is on air
  and the firmware's QUEUED/PENDING state machine collapses into an await.
  The response window is sized from radio parameters, mirroring
  neighborDiscoverQueryTimeoutMs().
- NeighborsPublisher: owns the schedule, the immutable per-cycle snapshot, and
  the payload. The snapshot merges live discovery responses over the stored
  table because get_neighbors() serves a 60s cache that store_advert() does not
  invalidate.
- packet_router: offer PAYLOAD_TYPE_RESPONSE to the scope helper before the
  companion fan-out; it consumes only on an authenticated tag match.
- Per-broker `neighbors: true` opt-in (default off) plus a
  mqtt_brokers.neighbors.enabled master switch; interval is 12-336h as in
  firmware, rejected rather than clamped.
- `discover.scopes` mesh CLI command triggers one cycle immediately.

The whole zero-hop table is published including neighbours that did not answer
(timeout / send_failed), matching the firmware payload. No 10 KB cap: that
existed for a fixed PSRAM buffer.
2026-07-28 21:53:22 -07:00
Rightup efb7e4a319 feat(metrics): implement metrics data retrieval with RRDtool fallback 2026-07-20 13:44:14 +01:00
Lloyd 7de15b135e Merge pull request #364 from agessaman/fix/all-the-things 2026-07-19 15:50:19 +01:00
agessaman 2432332f87 refactor(airtime): introduce refresh_radio_params method for dynamic modulation updates
Added a new method to the AirtimeManager class to refresh modulation parameters without clearing transmission history. Updated ConfigManager to call this method after applying live radio configurations. Enhanced tests to verify that modulation updates occur correctly during live updates.
2026-07-17 17:38:11 -07:00
Rightup f1f39c71f2 refactor(logging): enhance logging configuration and add TRACE level support 2026-07-17 08:48:01 +01:00
agessaman d886af0991 fix(engine): relay routed ACKs through their MeshCore branch
An ACK at an intermediate direct hop is now regenerated as a plain
DIRECT ACK (transport codes dropped, version bits cleared) and relayed
with zero retransmit delay, matching routeDirectRecvAcks, instead of
taking the generic delayed direct forward. The repeater.multi_acks
preference (already exposed by the mesh CLI) is now consumed by the
engine: when enabled, both ACK relay sites precede the plain ACK with
a MULTIPART-wrapped redundancy copy spaced by the direct retransmit
delay + 300 ms. The multipart relay seen-key now keeps the MULTIPART
payload type over the unwrapped payload, matching firmware hasSeen so
a redundancy pair survives each hop.
2026-07-16 10:48:52 -07:00
agessaman a620433312 Merge upstream/fix/all-the-things into fix/all-the-things
Keep both migration 12 (companion message signal/channel data) and
migration 13 (packet upstream hash for neighbour links), and retain
ACK/MULTIPART plus TRACE imports in engine tests.
2026-07-15 22:51:15 -07:00
agessaman f91a30afa1 docs(engine): state TX delay factor semantics at startup
The TX delay factors changed from absolute seconds to firmware-matching
airtime multipliers (random delay in [0, 5 * airtime * factor], per
MeshCore getRetransmitDelay), but deployed configs tuned under the old
seconds interpretation get no signal on upgrade. Log the interpretation
and both factors at startup, and fix the stale wording in
config.yaml.example and the web API docstring. Defaults are unchanged
(flood 1.0, direct 0.5; firmware ships 0.5/0.3).
2026-07-15 22:02:18 -07:00
Rightup 9688fe70e4 feat(neighbour-links): implement neighbour link tracking and history retrieval 2026-07-15 17:08:40 +01:00
Rightup 1906f576bb feat: add region/default-scope / cli commands update
standardize default region on mesh.default_region (remove legacy region_default_scope usage)
add/align CLI support for owner.info, path.hash.mode, and loop.detect with validation
wire UI terminal get/set + autocomplete/help for owner.info, path.hash.mode, loop.detect
extend update_radio_config to persist owner_info for UI set owner.info
add and use shared packet utility for advert creation + default-region transport scoping
refactor repeater and room-server advert paths to reuse shared packet logic
2026-07-08 17:25:35 +01:00
Rightup 43a112ce7c feat: add HTTP server configuration and control commands 2026-07-07 20:38:22 +01:00
Lloyd 2b67dea96b refactor:rename-project-to-openhop 2026-06-24 23:27:49 +01:00
Yellowcooln 875ea47192 docs: trim modem GPS example comment 2026-06-16 12:24:59 -04:00
Yellowcooln 1c186d7feb fix: throttle only pyMC modem sensor polling 2026-06-15 18:34:05 -04:00
Yellowcooln f2b7d254a7 fix: slow sensor polling to 60 seconds 2026-06-15 18:32:12 -04:00
Yellowcooln dde487bce3 fix: add generic PyMC modem stats sensor 2026-06-14 18:00:15 -04:00
agessaman 183650228e feat(config): add optional KISS CSMA tuning parameters
- Updated `config.yaml.example` to include optional KISS key-up and CSMA tuning parameters.
- Enhanced `get_radio_for_board` function in `config.py` to forward KISS tuning settings to the modem firmware when specified.
- Added tests to verify correct forwarding of KISS parameters and omission of unset values in `test_radio_config.py`.
2026-06-08 21:25:26 -07:00
Lloyd e9a9f21cab feat: update preamble_length to 32 for radio configurations 2026-06-05 16:00:23 +01:00
Rightup 14b4804c26 feat: Enhance logging system and introduce policy management endpoints
- Updated LogBuffer to support log entry IDs, enhanced log entry structure with additional metadata, and implemented subscriber management for real-time log streaming.
- Added OpenAPI specifications for new endpoints related to policy management, including retrieval, updating, validation, and group management for network policies.
- Implemented comprehensive tests for new policy endpoints, ensuring correct behavior for creating, updating, validating, and deleting policy groups and entries.
- Introduced policy evaluation tests to validate the functionality of the PolicyEngine, including various scenarios for action decisions based on defined rules.
- Enhanced packet routing tests to ensure proper handling of policy decisions in packet processing.
2026-06-04 15:53:17 +01:00
Lloyd b464fa8593 docs: update example configuration for Waveshare UPS D and E Hats 2026-05-21 12:42:22 +01:00
Lloyd 22adbd1a84 feat: add setup usb/tcp details on setup 2026-05-19 14:45:26 +01:00