Commit Graph
31 Commits
Author SHA1 Message Date
agessaman 7debb92e9e feat(template): add a hops_min filter for gating on route length
{firstlast_distance|prefix_if_nonempty: | F/L Dist: } prints "| F/L Dist: N/A"
on a direct message: the distance helpers return the literal "N/A" when there
is no path to measure, prefix_if_nonempty only asks whether the value is
non-empty, and "N/A" is. Suppressing that needed a gate, and pathbytes_min was
the only one available—so it was being used as a stand-in for "did this
message take any hops", which is not what it asks. It asks how the path is
encoded, so pathbytes_min:2 also discards a one-byte multi-hop path whose
distance is real and measurable.

hops_min:N asks about the route itself. hops_min:1 drops a clause on a direct
message and nothing else:

    hops_min:1   pathbytes_min:2
    direct         cleared      cleared
    1-byte 2 hops  kept         cleared   <- the difference
    2-byte 2 hops  kept         kept
    unknown        cleared      cleared

An unknown hop count clears the value: a gate that cannot confirm the route
should suppress rather than guess, matching pathbytes_min.

The hop-count logic moves to utils.message_hop_count so the filter and
BaseCommand.get_hops_display_values share one implementation instead of two.
It returns None for unknown rather than 0, since a gate must not read
"cannot tell" as "direct".
2026-08-25 10:03:30 -07:00
agessaman fc63039a48 fix: address Codex review of tonight's work
Correctness:

- {path_distance} was always blank in production. The resolution code that
  builds repeater_info dropped latitude/longitude in every branch, so the
  calculator could never find a coordinate. My tests passed hand-built
  dicts straight to the calculator and never exercised the builder, which
  is why they stayed green. Coordinates are now carried through all four
  construction sites, and the new tests drive _lookup_repeater_names via
  its lookup_func hook so the real builder runs.

- The #80 route guard was defeated two ways in the channel handler. When
  the RF data was an uncorrelated fallback, control fell through to the
  raw-hex and routing_info fallbacks below, which took the route from the
  unrelated packet anyway; the guard had actually made that path
  reachable. message.routing_info was also assigned unconditionally, and
  the path command reads it. "Not attributable" is now a terminal branch
  and the routing_info hand-off checks provenance.

- Same fix was incomplete for DMs: routing_info was captured and turned
  into path_info before the provenance check ran, so the later check only
  declined to overwrite an already-wrong value. Guarded at the source.

- Rendering could transmit for real. Capture only intercepts
  send_response, but advert calls send_advert() directly and
  send_response_chunked never checked capture_sink. Chunked sends are now
  captured, and rendering is opt-in via BaseCommand.render_safe (default
  False) instead of a denylist that cannot be complete. This also closes
  the DM-only leak: schedule is not marked safe, so {cmd:schedule} can no
  longer broadcast configuration to a channel.

- Multi-part rendered output was rejoined into one oversized send.
  Scheduled messages are now split to the RF body budget and sent through
  send_channel_messages_chunked, on character boundaries so multi-byte
  text is not corrupted.

- _last_path_distance_km is instance state that was only set on success,
  so an invalid path request could show the previous request's distance.
  Reset at the start of every execute().

- Stale-contact retries were only counted on non-OK results, so timeouts
  and exceptions left a contact eligible forever and could recreate the
  storm. All failed attempts count now.

Web viewer:

- A failed config reload was reported as a successful save. The API and
  UI now distinguish "saved and active" from "saved, restart needed".

- Preview count was unbounded; clamped to 1-20.

- update_ini_values is a read-modify-replace with no locking, so two
  concurrent viewer saves could lose one. Serialised behind a lock.
2026-08-22 00:16:03 -07:00
agessaman 29a2e7b412 feat(web): manage scheduled messages from the viewer (#174)
Adds a Schedule page that lists every [Scheduled_Messages] entry with its
next run time and supports add, edit and delete. Writes go to config.ini
and queue a config reload, so schedules change without restarting the
bot, which was the actual request.

It edits the config section the bot already reads rather than
introducing a database table. reload_config() already re-runs
setup_scheduled_messages() with rollback, so there is nothing to keep in
sync and the schedule command lists exactly what the page shows.

Validation runs through the same parsers the scheduler uses, so the UI
cannot accept a schedule the bot would later reject. The builder
composes cron from plain-language options and previews the next five
runs; entries the bot cannot run are listed as "Not scheduled" with the
reason instead of being hidden, since a typo that silences a message is
what an operator most needs to see. The 15-minute floor for {cmd:...}
messages is enforced at save time too.

Also relabels the radio Disconnect button to "Stop Bot" behind a
confirmation (#240). It was never a radio-only disconnect: the main loop
runs while self.connected is true, so disconnecting exits the process.
That surprised an operator running under tmux with nothing to restart
it. disconnect_radio()'s docstring now says so as well.
2026-08-21 23:32:45 -07:00
agessaman a764186eb2 feat(scheduler): airtime guards for {cmd:...}, plus missed docs
Two guards on command placeholders in scheduled messages, neither
configurable, because both exist to protect a shared medium:

A 15-minute floor. A schedule containing {cmd:...} that fires more often
is rejected at startup with an error rather than quietly running slower.
The interval is sampled and measured by the tightest gap between
firings, so "0,1 * * * *" is correctly treated as every 60 seconds and
not as hourly. Schedules without a command placeholder are unaffected.

The command's own cooldown_seconds now applies to a render. Scheduling
is not a way around the rate a command was configured to run at. The
execution is recorded before the command runs, matching execute_commands,
so a slow or failing render cannot be retried straight past the cooldown.

Also fills documentation gaps from the preceding commits:

- --install-extras was only in the script's own --help; documented in
  service-installation.md (including alongside --update-venv) and
  upgrade.md.
- weather-service.md documented weather_alarm's once-a-day scheduling
  with no route to more than one forecast a day, which is exactly what
  people go there looking for. It now points at {cmd:wx ...} and notes
  that sunrise/sunset still belong to weather_alarm.
2026-08-21 21:33:54 -07:00
agessaman b0148506a6 feat(scheduler): broadcast any command's output with {cmd:...}
A scheduled message can now embed the reply of any bot command:

    [Scheduled_Messages]
    0 6,12,18 * * * = Public:{cmd:wx Seattle}

The schedule side was never the missing piece — [Scheduled_Messages]
keys have been 5-field cron with @presets for a while, so multiple
times, intervals and @hourly were all already expressible. What was
missing was any way to get a service's output into a scheduled message:
placeholders were limited to mesh info (contact counts). That gap is
why services were growing their own schedule parsers.

CommandManager.render_command_output runs a command for its text alone.
MeshMessage.capture_sink makes that safe: when set, send_response
collects the reply and transmits nothing, checked before the
_last_response bookkeeping so a background render cannot overwrite the
response captured for a real user's command.

Degrades quietly in every failure mode — unknown, disabled, admin-only,
non-renderable, timed out or silent commands expand to nothing, so raw
{cmd:...} text is never put on the air, and a message left empty is not
sent at all. Command output is not re-scanned, so a reply containing
{cmd:...} cannot recurse. Commands that transmit directly rather than
returning text (announcements) are refused outright, since rendering
them would broadcast for real.

Bounded by [Bot] scheduled_command_timeout_seconds (default 30).
2026-08-21 21:25:16 -07:00
agessaman 5bdd6302c1 fix(airplanes): switch Airplanes API to adsb.lol default 2026-08-21 09:57:23 -07:00
agessaman 035ec963b6 docs: clarify APScheduler cron usage and day-of-week numbering
- Updated `config.ini.example`, `command-reference.md`, and `configuration.md` to clarify the use of APScheduler for cron scheduling, emphasizing the difference in day-of-week numbering compared to Vixie cron.
- Added examples and notes to prevent confusion regarding the interpretation of cron expressions, particularly for users transitioning from Vixie cron conventions.
- Enhanced documentation to guide users in using the correct syntax for scheduling messages, ensuring better understanding and usability.
2026-08-04 20:33:26 -07:00
agessaman 717f9a8ef3 feat(i18n): opt-in auto-detect sender language for replies
Reworked implementation of the multilingual-response feature (issue #218)
as a reusable, framework-level capability instead of logic bolted onto
HelloCommand.

- modules/lang_detector.py: keyword-first detection (reliable for the short
  greetings typical on mesh) with optional langdetect for longer text;
  results are constrained to languages that actually have a translation file.
- core.py: cache Translator instances by language (get_translator) so
  per-message switching never re-reads files, and expose available_languages()
  derived from translations/*.json. Store translation_path; keep cache/path
  consistent on config reload.
- base_command.py: task-local translator override via a ContextVar (safe under
  asyncio concurrency, no global mutation), plus detect_response_language() and
  a respond_in_sender_language() context manager any command can opt into.
- hello_command.py: build the reply inside respond_in_sender_language(); the
  only awaited call (send_response) stays outside the override window.
- Gated by [Localization] auto_detect_language (default false); langdetect is
  an optional [lang] extra, not a hard dependency.

Tests: unit coverage for the detector and the base-command wiring.
2026-07-28 12:44:53 -07:00
agessaman c5bcfb3f26 feat(config): enhance connection configuration and documentation
- Updated `config.ini.example`, `config.ini.minimal-example`, and `config.ini.quickstart` to clarify connection type precedence and required keys for serial, BLE, and TCP connections.
- Added detailed comments and examples for each connection type, ensuring users understand how to configure their connection settings effectively.
- Introduced a new `[Service_Overrides]` section in `config.ini.example` for alternative service plugin implementations, improving extensibility.
- Enhanced documentation in `configuration.md` to reflect these changes and provide clearer guidance on connection options and templates.
2026-07-09 23:25:11 -07:00
agessaman 95b0601980 feat(command_prefix, config): enhance command prefix handling and documentation
- Updated `config.ini.example` to support single, multiple, and decorative command prefixes, improving flexibility in command invocation.
- Refactored `CommandManager` and `BaseCommand` to utilize new command prefix parsing logic, allowing for better handling of multiple prefixes and optional bare commands.
- Enhanced documentation to clarify command prefix configuration options and behavior, ensuring users understand how to set and use prefixes effectively.
- Added unit tests to verify the correct functionality of command prefix handling, including support for permissive and strict prefix modes.
2026-07-07 09:37:28 -07:00
agessaman f6352cdb7c feat(connection): implement automatic transport reconnect for serial, BLE, and TCP
- Added configuration options for reconnect behavior, including `reconnect_max_retries`, `reconnect_delay_seconds`, and `reconnect_max_delay_seconds`.
- Enhanced the bot's ability to handle transport disconnects by scheduling reconnect attempts with exponential backoff.
- Updated documentation to reflect new connection settings and behavior.
- Added tests to verify reconnect logic and ensure proper handling of transport errors.
2026-05-30 20:34:06 -07:00
agessaman cb54ca6a45 fix(config): clarify flood scope configuration and logging behavior
- Updated `config.ini.example` and `configuration.md` to enhance clarity on the `outgoing_flood_scope_override` and `flood_scopes` settings, detailing their behavior and interactions.
- Improved logging in `CommandManager` to provide better insights into scope resolution and potential issues during message sending.
- Added new methods in `MessageHandler` for improved RF data correlation, ensuring eligibility checks for flood scope matching.
- Enhanced unit tests to cover new behaviors and ensure robust handling of flood scope configurations.
2026-05-17 14:38:47 -07:00
agessaman 4a0b989f5a feat(scheduler): optional flood scope for scheduled channel messages
Parse channel:#scope:body in [Scheduled_Messages], pass scope to
send_channel_message; extend schedule list and docs.
2026-05-14 11:32:20 -07:00
agessaman 70b2256752 feat(test): Test_Command response_format with piped path filters
Add format_piped_template (pathbytes_min, prefix_if_nonempty), utils
bytes-per-hop helpers, and [Test_Command] format priority over Keywords.
2026-05-12 19:30:42 -07:00
agessaman a60b35f567 docs: update README and command reference with new features and services
- Removed caution section from README to streamline information.
- Added new services to README: Earthquake Service, Repeater Prefix Collision Service, and MQTT Weather Relay.
- Expanded command reference documentation with new commands: `version`, `status`, `aurora`, `magic8`, `catfact`, `reload`, `channelpause`, `channelresume`, `greeter`, `announcements`, and `schedule`.
- Updated getting started guide to reflect changes in Python version requirement and installation instructions.
- Enhanced service plugins documentation to include new services and clarify existing ones.
2026-04-25 19:21:23 -07:00
agessaman bf6bc14de7 release(v0.9.0): readiness — silent-subscribe tests, py310, RF correlation fix
- Rewrite test_subscribe_packets/messages_emits_status_ack to match the
  silent subscription UX from 1ee84f2.
- Reconcile Python version: requires-python>=3.10, ruff target py310, CI
  matrix adds 3.13, pyupgrade UP0xx ignored pending a separate typing-rewrite
  PR; fix two B905 zip(strict=...) lints.
- Issue #80 fix in find_recent_rf_data: return None when correlation_key is
  provided but unmatched; prefer the longest observed path among samples
  sharing a packet_hash; narrow the no-key fallback to a configurable
  rf_fallback_window (default 2s).
- Issue #161: lower shipped max_response_hops default 10 -> 7.
- Add CHANGELOG.md, restructure BUGS.md around a ## v0.9.0 Fixed Bugs
  table, prune crossed-out duplicate outstanding rows, and add a
  Deferred-from-v0.9.0 triage section to TODO.md.
- Untrack coverage.json and add it to .gitignore.

Made-with: Cursor
2026-04-17 13:53:13 -07:00
agessaman a4d5f5478b feat: add RandomLine command support for website generation
Introduced a new command structure for RandomLine entries in the website generation process, allowing for dynamic rendering of random lines based on configuration. Updated the configuration file to include a new category option for commands and enhanced documentation to reflect these changes. This addition improves the command reference organization and user experience on the website.
2026-04-15 09:40:39 -07:00
agessaman 90fdd0c77a feat: add cmd_reference_url option for Cmd_Command
Introduced a new configuration option `cmd_reference_url` in the Cmd_Command section, allowing users to override the default `cmd` output with a link to a full documentation page. Updated the CmdCommand class to utilize this new setting and modified related documentation and tests to ensure proper functionality.
2026-04-15 09:26:19 -07:00
agessaman 2ac1722014 Refactor flood scope configuration and enhance message handling
- Updated `config.ini.example` to clarify the usage of `outgoing_flood_scope_override` and `flood_scopes`, providing examples for better understanding.
- Modified `configuration.md` to reflect changes in flood scope handling, emphasizing the distinction between `outgoing_flood_scope_override` and `flood_scopes`.
- Refactored `CommandManager` to utilize the new `outgoing_flood_scope_override` for sending messages, ensuring consistent scope handling.
- Enhanced `MessageHandler` to prioritize library-provided scope fields for improved accuracy in flood scope matching.
- Added tests to validate the handling of flood scope fields from library payloads, ensuring robustness in message processing.
2026-04-10 16:29:52 -07:00
Adam Gessaman 4bf09291f5 Implement public channel guard and enhance configuration validation
- Updated `config.ini.example` to include a warning about running the bot on the Public channel and added an override key for intentional usage.
- Enhanced `config_validation.py` to implement a public channel guard that prevents the bot from starting if the Public channel is included in monitored channels without the override.
- Refactored `CommandManager` and `Core` to check for the Public channel key during channel loading and connection setup, ensuring compliance with the new guard.
- Improved documentation in `configuration.md` and `config-validation.md` to clarify the implications of using the Public channel and the necessary configuration changes.
2026-04-09 20:44:48 -07:00
agessaman b3d5054074 Merge branch 'integration/massive-pr' into dev
Resolve conflicts by combining v0.9 integration work with dev-only behavior:
- Keep channel_responses_enabled, greeter pause checks, and max_response_hops gating
- Retain TRACE/repeat handling, MQTT weather, temperature format helpers, and feed tooling
- Unify package-data globs, ruff/mypy/pytest config, Rate_Limits/Webhook in config example
- Web viewer: config panels + X-Requested-With on channel API; drop redundant DBManager import

Made-with: Cursor
2026-04-03 10:51:50 -07:00
Chris Barker 8966be89a6 Adds max_response_hops
Before this there was no way to prevent the bot to reply to random
channel noise from a temprorary strong link to another distant mesh.

This provides the ability for users in larger meshes to cap the bot
to reply to only messages sent likely from nearby repeaters. This works
well in conjunction with region scoping.

Default value is still 64, to not change behavior on existing installs,
but the example config.ini's all include a suggested start valu of 10.
2026-03-30 15:11:05 +01:00
agessaman 7a3bb56d70 Update admin commands to include channelpause functionality
- Added `channelpause` and `channelresume` commands to the admin commands list in configuration files, allowing admins to pause or resume bot responses on public channels via DM.
- Updated documentation to reflect the new command functionality and its implications for channel responses.
- Modified validation and message handling to incorporate the new channel response control feature.
2026-03-21 20:43:58 -07:00
agessaman 32fd416df8 Update admin commands to include channelpause functionality
- Added `channelpause` and `channelresume` commands to the admin commands list in configuration files, allowing admins to pause or resume bot responses on public channels via DM.
- Updated documentation to reflect the new command functionality and its implications for channel responses.
- Modified validation and message handling to incorporate the new channel response control feature.
2026-03-19 19:53:27 -07:00
agessaman 07a2db4ede Slim scheduler, add maintenance module
- Introduced a new `maintenance` module to handle data retention, log rotation, and nightly email tasks.
- Updated the `scheduler` to utilize the `MaintenanceRunner` for executing maintenance tasks, improving code organization and clarity.
- Enhanced documentation to reflect changes in logging configuration and data retention processes.
- Adjusted tests to accommodate the refactored scheduler methods and ensure proper functionality.
2026-03-19 19:40:33 -07:00
agessaman 219de10afd Refactor command alias handling to standardize command prefix use
- Updated configuration examples to remove command prefix from aliases in `config.ini.example` and documentation.
- Enhanced `CommandManager` to normalize command names and resolve aliases through both direct mapping and plugin keyword mappings.
- Introduced a method in `BaseCommand` to normalize aliases from configuration, ensuring consistency in keyword handling.
- Added tests to verify that aliases resolve correctly from both keyword mappings and runtime keywords without legacy prefixes.
2026-03-19 16:39:08 -07:00
agessaman fcfde7ea33 Add data retention configuration and cleanup functionality
- Introduced a new `[Data_Retention]` section in `config.ini.example` to manage retention periods for various database tables, including packet stream, daily stats, and observed paths.
- Updated `mkdocs.yml` and `configuration.md` to include documentation for the new data retention settings.
- Implemented data retention cleanup methods in `mesh_graph.py`, `repeater_manager.py`, and `scheduler.py` to enforce retention policies automatically.
- Enhanced the web viewer's data cleanup logic to utilize the new retention settings, ensuring efficient database management.
2026-02-24 22:01:49 -08:00
agessaman 084d2c66cb Add config validation section to documentation
- Introduced a new section on config validation in configuration.md, detailing how to validate section names and path writability before starting the bot.
- Updated mkdocs.yml to include a link to the new config validation documentation.
- Added a reference to config validation in index.md for improved navigation.
2026-02-16 17:22:57 -08:00
agessaman 652712eb0c Update documentation links and file references for consistency
- Renamed various documentation files to use lowercase for improved consistency, including `WEB_VIEWER.md`, `DOCKER.md`, and `PATH_COMMAND_CONFIG.md`.
- Updated references in `config.ini.example`, `docker-setup.sh`, `README.md`, and multiple documentation files to reflect the new file names.
- Removed obsolete documentation files related to the Discord Bridge, Map Uploader, Packet Capture, and Weather Service, streamlining the documentation structure.
2026-02-16 17:15:52 -08:00
Adam Gessaman ac2825a25d feat: Add channel keyword filtering to configuration
- Introduced `channel_keywords` option in the configuration files to limit bot responses in monitored channels to specified keywords, enhancing control over channel interactions.
- Updated relevant documentation and examples to reflect the new feature, ensuring users understand how to implement keyword restrictions.
- Modified the command manager to support the new keyword filtering logic, allowing for more efficient message handling in channels.
2026-02-09 11:56:43 -08:00
agessaman d15c58994e chore: Remove outdated documentation files and update references
- Deleted COMMANDS.md, DOCKER.md, WEB_VIEWER.md, and other related documentation files to streamline the project structure.
- Updated README.md and other documentation references to point to the new locations of the command and Docker instructions.
- Added optional dependencies for documentation generation in pyproject.toml.
2026-02-07 20:10:09 -08:00