Commit Graph
69 Commits
Author SHA1 Message Date
agessaman f994171818 feat(weather): [Weather] always_show_location names the place in every wx/gwx reply
wx and gwx name the place only when it adds information: a city outside default_state or default_country, or a position. always_show_location (default false) names every place a lookup finds instead, including cities in the bot's own state and ZIP codes.

For a ZIP code, gwx now does a reverse lookup when the option is on (it skipped that since the name was never shown), and wx reuses the reverse lookup it already did for a companion position. A failed lookup leaves the reply unnamed rather than repeating the ZIP code. The prefix comes out of the message budget as before. The option is in the wx settings schema for the web viewer, the example config, the command reference and the changelog.
2026-10-02 07:55:18 -07:00
agessaman 9959cc33fc fix(gwx): moon for clear night hours in the hourly forecast
The hourly forecast still showed ☀️ for clear hours after dark. I now request Open-Meteo's hourly is_day with the hourly forecast and pick each hour's emoji from it, falling back to 06:00–18:00 local when it's absent, as the current conditions do.
2026-10-02 07:12:38 -07:00
agessaman 17a93104cc fix(gwx): label current conditions by is_day, moon at night, name tomorrow's weather
At 5:58 am wx 98258 (Open-Meteo provider) replied "Tonight: ☀️Clear … | H:68°F L:50°F | Tomorrow: 🌫️ H:67°F L:51°F". Any hour before 06:00 was labeled "Tonight", which next to today's high/low and a "Tomorrow" for the following day read like last night's forecast, and code 0 always showed a sun.

I now request Open-Meteo's is_day and use it for both the label and the emoji, falling back to 06:00–18:00 local when it's absent. Night before noon is "Overnight" (the NWS name for that period, new key in every catalog), night after noon stays "Tonight". Clear and mostly clear at night show 🌙. Tomorrow's segment adds its description when it fits and keeps the emoji-only form otherwise.

Goldens change for the new request field, Overnight on the fixtures captured after midnight, 🌙 on clear night fixtures, and tomorrow's description.
2026-10-02 07:04:52 -07:00
agessaman 9c055030c7 fix(weather): alerts as an option alone, option-named sources, alert list send status
A codex re-review of the last two commits confirmed their fixes and found three gaps. wx alerts (and gwx alerts) with no location looked up a place called "alerts"; the option now applies to the fallback location like the others. A custom source named like an option (custom.wxsim.hourly or custom.mqtt_weather.hourly) was treated as the option and answered from the default source; a configured name now wins. The full alert list (wx <place> alerts) kept sending after a refused first message and reported success; it now stops and reports the refusal.
2026-10-02 01:31:37 -07:00
agessaman 1536f2a634 fix(weather): codex review of the whole branch
- A scheduled {cmd:gwx ...} or {cmd:wx ...} with a forecast and an alert slept out the TX pacing between them during capture; with bot_tx_rate_limit_seconds = 30 that passed the 30 s render timeout and the whole reply was lost. Pacing is skipped when the message has a capture_sink.
- gwx tomorrow ignored its byte budget (a Russian Reykjavik forecast was 99 bytes against 89). It now drops the precipitation amount, gusts, wind, the precipitation chance and the description, in that order. The Open-Meteo goldens give tomorrow runs a roomy budget, since their key-name translator makes the text much longer than real; trimming is tested with real catalogs in test_gwx_tomorrow_budget.
- Custom default sources (bare wx/gwx) still returned True after a refused send.
- An option without a location (wx hourly, gwx tomorrow, wx 5d) showed usage, since the fallback location was resolved before the option was parsed; the option now applies to the fallback, custom default sources included. The docs say how far each provider's forecast reaches.
- wx read its unit settings through get_config_value, whose [Weather] fallback logged a notice to move them into [Wx_Command] on every lookup (14 per reply); _unit_setting reads [Wx_Command], then [Weather], without it.
- NOAA forecasts that mention thunderstorms after sun ("Sunny then Slight Chance Showers And Thunderstorms") still got ☀️; thunderstorms now outrank sun.
- The catalog tests passed with keys removed, since a missing key differs from English; they now require each key and every wind point, with en-GB and Polish allowed to use the English letters.
2026-10-02 01:10:39 -07:00
agessaman 0853739f30 fix(weather): review follow-ups: warning units, wx localization, help text
A space bunny review of this branch's commits found:
- gwx's high-wind warning converted the speed to mph for every catalog, so on a km/h bot it read 49 next to a reply saying 80, and Polish (whose template said km/h) regressed in the merge. The threshold stays 30 mph; the warning shows the speed in the configured unit with a {unit} placeholder in every catalog.
- tomorrow_not_available, tomorrow_error, multiday_not_available and multiday_error were still English in seven catalogs; they are translated, and the catalog test now fails on any weather reply left in English.
- wx kept NOAA's English wind letters and English weekday letters while gwx's are translated, so one German bot showed NE and NO for the same wind. wx now translates both through the catalog (commands.wx.day_abbrev added); English output is byte-for-byte unchanged, which the goldens show (their stub translator gives English for these short labels, via tests.helpers.key_name_translate).
- Polish feels_like was ungrammatical (odczuwalna -> odczuwalnie), Russian's used a nonstandard abbreviation, and Russian gwx's MQTT subscriber error lacked wx's hint.
- The help text (commands.wx.description/usage, commands.gwx.usage) described wx as ZIP-only; it now lists cities, coordinates and the options, under 130 bytes in every language.
- The generated default config, the docs' default_state wording, _fit_tomorrow_parts' docstring and the CHANGELOG's stats claim were inaccurate.
2026-10-02 00:48:31 -07:00
agessaman 5b50229223 feat(weather): accept knots as the wind unit in wx, gwx and WXSIM
The weather service took [Weather] wind_speed_unit = kn, while wx and gwx rejected it with a warning and used mph, so the same setting gave knots in one and mph in the other. Knots now work in all of them: Open-Meteo is asked for kn, NOAA's mph and km/h values and station gusts are converted, gwx's high-wind warning converts back to mph for its threshold, and the WXSIM parser converts its km/h. The web config's wind unit choices gain m/s and knots.
2026-10-01 23:35:48 -07:00
agessaman dbe4ac7028 feat(gwx): name the place only when it adds information, as wx does
gwx prefixed every reply with the resolved place. It now follows wx's policy: a city is named when it resolved to another country than [Weather] default_country or, in the US, to another state than default_state (or no default_state is set); coordinates are named when the reverse lookup found a place; a ZIP code is not named. A failed reverse lookup for coordinates used to turn the input into a label like "47.60620, -122.33210" (the longitude standing in for a country); such replies now have no label. The ZIP path drops its reverse-geocoding request, whose only use was the label.
2026-10-01 23:23:14 -07:00
agessaman a2f0d4c22c fix(weather): a refused first message ends the reply
Follow-up parts of a wx or gwx reply skip the per-user rate limit, since the limiter already let the first part through. When it had not (rate-limited, or the send failed), the alert after the forecast and the later parts of a multi-day forecast still went out on their own. Now a refused first part ends the reply. The multi-day sender also sent a forecast that fits one message only after dropping blank lines as a continuation, skipping the limit; the first message now always goes through it. execute returns whether the reply was sent, which the stats and web viewer record.
2026-10-01 23:12:47 -07:00
agessaman 43c3006072 Merge remote-tracking branch 'origin/refactor/weather-port' into feat/wx-gwx-convergence 2026-10-01 23:00:39 -07:00
agessaman 66176a173b refactor(weather): keep wx and gwx's companion-location DEBUG lines
The codex review of this port also found that the shared companion lookup dropped the three DEBUG lines wx and gwx logged on dev (no sender key, no row for the key, the position found). get_companion_lat_lon takes a trace flag that wx and gwx pass; rain and aurora, which never logged these, stay quiet. With trace, a bot without a db_manager logs the lookup error at the caller's level again instead of returning silently.
2026-10-01 23:00:14 -07:00
agessaman 45f2512acb Merge remote-tracking branch 'origin/feat/gwx-render-safe' into feat/wx-gwx-convergence
# Conflicts:
#	CHANGELOG.md
2026-10-01 22:53:29 -07:00
agessaman 4895da3f77 Merge remote-tracking branch 'origin/fix/weather-wind-emoji' into feat/wx-gwx-convergence
Goldens conflicted; I regenerated them, and against this branch they differ only in NOAA emojis, gwx wind labels and the Phoenix width budgets the merged branch corrected. gwx's new hourly test expected the old truncating conversion (202.5° as SW); with nearest-point directions it is SSW.
2026-10-01 22:50:34 -07:00
agessaman e9122b2dfd fix(weather): let rain and snow outrank cloud in NOAA emojis; skip non-finite wind degrees
A review of this branch found three things. Moving the partly/mostly cloudy check ahead of rain gave forecasts like "Chance Rain Showers then Mostly Cloudy" (ten of the 79 forecasts it sampled from live NWS points) a sun-behind-cloud emoji; rain and snow now come before every cloud check, so they get 🌦️ or ❄️. A NaN or infinite wind direction made gwx's new nearest-point conversion raise; it is now left out like a null one. The Phoenix daily-width test's budgets were written for a reply one byte shorter (SW is now WSW), so they no longer pinned <= at equality; they are 330/343 now and the regenerated golden shows each segment fitting exactly at its budget.
2026-10-01 22:46:47 -07:00
agessaman 37aabe5641 Merge remote-tracking branch 'origin/feat/gwx-hourly' into feat/wx-gwx-convergence
Conflicts: NOAA hourly wind keeps this branch's full 16-point direction and fix/noaa-configured-units' speed conversion (a test pins both); gwx's hourly forecast returns (reply, {}) like the other forecast types since fix/gwx-warnings-structured, and _open_meteo_current keeps fix/gwx-missing-current's Optional return.
2026-10-01 22:42:38 -07:00
agessaman 4ab2a9ae48 Merge remote-tracking branch 'origin/fix/gwx-warnings-structured' into feat/wx-gwx-convergence
fix/gwx-missing-current returns the fetch error as a bare string when the current temperature is missing, and this branch moved that code into a method whose callers unpack (reply, current conditions). Git merged it without a conflict; the merged early return now returns the pair, so the reply is the fetch error again instead of an unpacking error.
2026-10-01 22:32:56 -07:00
agessaman f4aaa7556f Merge remote-tracking branch 'origin/fix/gwx-missing-current' into feat/wx-gwx-convergence
The distorted-London golden conflicted; I regenerated it, and it differs from this branch only in the no_current and null_temp keys the merged change means to move.
2026-10-01 22:29:46 -07:00
agessaman fcb10eaa7e Merge remote-tracking branch 'origin/fix/weather-multiday-days' into feat/wx-gwx-convergence
# Conflicts:
#	CHANGELOG.md
2026-10-01 22:26:08 -07:00
agessaman 988c0caeb3 Merge remote-tracking branch 'origin/feat/gwx-keep-coordinates' into feat/wx-gwx-convergence
# Conflicts:
#	CHANGELOG.md
2026-10-01 22:22:34 -07:00
agessaman 9b9a0a0605 fix(weather): recognize Open-Meteo's heavy-thunderstorm code 97
A review pointed out that Open-Meteo documents WMO code 97 as a heavy thunderstorm, and neither gwx nor the weather service knew it: the reply showed an unknown condition with the generic emoji and gwx gave no thunderstorm warning. It now has a description in every language, the ⛈️ emoji and the thunderstorm warning.
2026-10-01 22:21:48 -07:00
agessaman 2260ac0353 fix(gwx): leave out an absent wind direction instead of reporting north
A review found that an Open-Meteo response with no wind_direction_10m key still defaulted the direction to 0°, so the reply read ⬆️N; a null direction was already left out. Both now give the wind speed with no direction.
2026-10-01 22:20:07 -07:00
agessaman f893c7f00d feat(gwx): allow gwx in scheduled {cmd:...} messages
gwx was not render_safe, so {cmd:gwx ...} expanded to nothing, while {cmd:wx ...} already ran gwx through delegation when weather_provider = openmeteo. gwx sends every reply, including the two-part warning, through send_response, which is what render_safe requires. Tests render gwx (plain and two-part replies) through the real CommandManager capture and check nothing is transmitted.
2026-10-01 22:13:06 -07:00
agessaman 0e8ba9bb61 fix(weather): nearest-point wind directions in gwx, and NOAA emoji precedence
gwx's _degrees_to_direction picked the 8-point sector starting below the angle (22.5° → NE, 241° → SW, negative angles → N); it now picks the nearest of the 16 points the catalog already translates, with the arrow wx uses, via _COMPASS_16/_ARROWS_8 moved to weather_common so both commands share them. wx's get_weather_emoji tested 'cloudy' before 'partly cloudy'/'mostly cloudy' and rain before thunderstorms, so those got ☁️ and 🌦️; thunderstorms (and 't-storm') now come right after heavy rain, and the partly/mostly cloudy check before plain cloudy. Golden moves: NOAA emojis for thunderstorm and partly/mostly cloudy periods (Seattle hourly fits one more hour, ⛅ being shorter than ☁️), and gwx wind labels where the nearest point differs.
2026-10-01 22:08:37 -07:00
agessaman 65eb3492fa feat(gwx): hourly forecasts; custom sources say when an option is unavailable
gwx advertised hourly but parsed it as part of the place name. It now requests Open-Meteo's hourly precipitation probability alongside the hourly fields it already got and formats the hours after Open-Meteo's location-local current time in wx's compact one-reply style, sharing the hour label, description shortening and byte packing through WeatherCommandMixin. Hourly lines drop the wind arrow (both commands) so the arrow's six bytes do not push an hour out of the reply; wx's NOAA hourly now keeps the full 16-point letters instead of the first two. WXSIM sources answer hourly and alerts with source_option_not_available instead of their default conditions; wx no longer skips an MQTT alias for alerts (the alias then got geocoded as a city); gwx answers 'alerts' the same way. WXSIM highs and lows of exactly 0° are no longer treated as missing, and gwx warns about a stale WXSIM forecast as wx does. Only noaa_wx_hourly_seattle moved (WNW/NNW instead of WN/NN).
2026-10-01 21:51:34 -07:00
agessaman 71f7f93e26 fix(gwx): judge extreme-weather warnings from Open-Meteo's numbers, not the reply text
_check_extreme_conditions regex-searched the finished, translated reply: it only saw temperatures followed by °F (so -10°F read as 10 and Celsius replies never warned), took the number after a direction label as the wind speed and called it mph whatever the unit, and lost it when the direction letters were translated. The check now gets the current conditions from the same Open-Meteo request (via _get_open_meteo_weather_with_conditions; get_open_meteo_weather keeps its signature) and uses the weather codes the description match stood for (65/82 heavy rain, 95/96/99 thunderstorms, 75/85/86 snow), and the temperature and sustained wind converted to °F and mph for the unchanged thresholds. Warning text and the multi_message shape are unchanged.
2026-10-01 21:46:41 -07:00
agessaman a4513d17c9 fix(gwx): leave out missing current fields instead of inventing values
_open_meteo_current defaulted every missing current field to 0, so a response without current conditions read as clear skies, 0° and 0% humidity, and called int() on present-but-null fields, which failed the whole reply. A missing current temperature now returns None (the caller replies with error_fetching and logs a warning); null or absent humidity, wind, gusts, feels-like and weather code are each left out. The no_current and null_temp distortion goldens move accordingly; nothing else does.
2026-10-01 21:42:44 -07:00
agessaman 70972d1d4d refactor(weather): keep solarforecast's enabled check and the names local plugins may use
A codex review of this port found solarforecast relying on the enabled_attr hook, which dev's BaseCommand does not have, so [Solarforecast_Command] enabled = false stopped working; its can_execute override is back as on dev. It also found names an earlier refactor commit had dropped as unused, which local plugins could still read: WxCommand.forecast_duration, num_wx_alerts, use_metric, zulu_time and geolocator, geolocator on gwx and solarforecast, the WXSIM_PARSER_AVAILABLE and WX_INTERNATIONAL_AVAILABLE flags, and the _mqtt_weather_error_key hook (now a WeatherCommandMixin method that _mqtt_weather_line calls through self, so an override takes effect again). tests/unit/test_weather_compat_surface.py pins all of them.
2026-10-01 21:27:56 -07:00
agessaman 8874c54a67 fix(weather): date forecasts by the location's own times, not the bot's clock
wx took "today" from the bot's clock: on a server running UTC, or answering for a city in another zone, tomorrow and multi-day started a day late, and hourly compared NOAA's local wall-clock times with the bot's after dropping their offsets, keeping hours that had passed. wx now takes today from the forecast's first period date and compares hourly times as absolute times; multi-day groups NOAA periods by their start date, which also keeps holiday-named periods ("Columbus Day") the weekday-name grouping skipped. Name-based grouping remains the fallback when periods have no startTime. gwx chose Today/Tonight and the multi-day weekday labels from the bot's clock; it now uses Open-Meteo's location-local current time and daily dates. gwx also asked for N days counting today and then skipped today, so 7d showed six days; it now asks for N+1 (capped at Open-Meteo's 16). The NOAA characterization clock gains its time zone, so aware comparisons do not depend on the machine running the tests; the regenerated goldens move only where these fixes apply (drop-period variants whose first period is a later day, Miami's already-started 6 PM hour, gwx Today/Tonight and weekday labels, and gwx's forecast_days request parameter).
2026-10-01 21:24:03 -07:00
agessaman 94e6387eba feat(weather): forecast the sender's own point in gwx, and accept zero coordinates
gwx with no location reverse-geocoded the companion's (or bot's) coordinates to a town name and then forecast that name: three Nominatim lookups, and a forecast for wherever the geocoder put the town rather than for the sender. It now passes the coordinates through, as wx does; the reply's label still comes from the one reverse lookup in geocode_location. Both commands write those coordinates in fixed decimals (`_coordinates_query`), since str() of a small float gives "1e-05", which the coordinate pattern does not match. The companion and repeater position queries in modules.location skipped any zero latitude or longitude; only 0,0 is the no-position sentinel, so they now exclude just that. wx's city lookup used truthiness and rejected a geocoded zero coordinate.
2026-10-01 21:17:16 -07:00
agessaman 5601a126dd refactor(weather): port the behavior-neutral weather refactor from refactor/maintainability
This brings the weather slices of my long-running refactor branch to dev ahead of converging wx and gwx, so the convergence PRs build on one copy of the code instead of rewriting it twice.

- `modules/weather_common.py` (WeatherCommandMixin): code wx and gwx shared by copy (MQTT and WXSIM source lookup, multi-day sending, high/low formatting, the forecast-option parser, the Open-Meteo model loader).
- `modules/nws_alerts.py`: one NWS alert entry parser for wx and the weather service, keeping each caller's own Special Weather Statement rules.
- `modules/http_retry.py`: one retrying session builder for the weather HTTP clients.
- wx: get_noaa_weather, the hourly, tomorrow and multi-day formatters, _add_period_details and the extract_* readers broken up or made table-driven; NOAA fetches go through `_noaa_fetch`.
- gwx: one fetch path; `_open_meteo_current` and `_open_meteo_daily_tail` split out of get_open_meteo_weather.
- solarforecast: `_format_forecast` built from one timestamp parser and one day builder (418 → 155 lines).
- aurora, rain, wx, gwx: companion and bot location lookups through `modules.location` (which gains optional logging arguments).

The changes outside these files that the refactor branch made (the base command's enabled_attr hook, the service base's run_periodic and _cancel_tasks) stay out: aurora and rain keep their can_execute overrides and the weather service its own loops, exactly as on dev.

Tests: characterization goldens over real captured NOAA, NWS, Open-Meteo and forecast.solar responses (tests/characterization, fixtures in tests/fixtures), 50 tests and several hundred cases, all of which also pass against dev's unmodified weather code. Each refactor step was additionally checked on the branch with an old-vs-new differential run (tens of thousands of generated inputs, including log calls and exceptions) and reviewed by codex.
2026-10-01 21:11:23 -07:00
agessaman 9b04791dff fix(gwx): end multi-day forecasts at Open-Meteo's null days instead of failing
Open-Meteo always returns the requested number of days but fills the ones past the model's horizon with nulls: the last one or two with the default model, nine of sixteen with icon_seamless. format_multiday_forecast called int() on them, so the whole reply became the multi-day error (gwx 15d for London and Reykjavik on 2026-10-01; gwx 7d under icon_seamless). It now stops at the first day without a high and low. The tomorrow forecast and the default reply compared a null precipitation probability with 30, which raised too; a null probability now just leaves the precipitation out, and a tomorrow with null temperatures reports that it is not available.
2026-10-01 19:27:49 -07:00
agessaman 1d749d3847 fix(send): stop dropping follow-up reply parts, and space concurrent transmissions
A user who raised bot_tx_rate_limit_seconds to 5 noticed wx's weather-then-alerts reply did not seem to honor it. The gap between those two messages was in fact longer than the limit (every send already waits on the bot TX limiter in _check_rate_limits, and wx sleeps limit + 1 s on top), but looking at it turned up two real bugs.

The second part of wx's and gwx's two-part reply, and every part after the first of 'alert all', went through the reply rate limiter (rate_limit_seconds, 10 s by default) a few seconds after the first part had used it, so it was refused: with default settings the alert message was never sent. These now pass skip_user_rate_limit=True, as the other multi-part senders already did. The repeater command's two-part branch gets the same change for consistency.

BotTxRateLimiter.wait_for_tx only checked the time since the last recorded transmission, and record_tx runs after the radio confirms, so concurrent senders (two replies, a reply and a scheduled message) all passed the wait before any of them recorded and transmitted back to back. wait_for_tx now claims the slot under the lock when it returns. A coverage test that monkeypatched can_tx to drive the old loop is rewritten against the new one.
2026-10-01 15:01:29 -07:00
CoderNemesis 5175558dd7 fix(send): split oversized channel text before it stalls the radio (#299)
An oversized webhook or channel body was handed to the firmware whole. The device dropped it without an ack, retries stalled the transport, and the radio looked dead. Split to the frame budget, keep links intact, and tag multi-part sends so they can be reassembled.

Reserve the wx location prefix from that same budget, and let NOAA alert polling be turned off without disabling the rest of the weather service.
2026-09-22 16:21:54 -07:00
Adam Gessaman c9ff277fd2 Merge pull request #261 from custardpy/feat/weather-service-localization
feat(i18n): localize weather outputs and add Russian translations
2026-09-07 15:19:03 -07:00
agessaman 595f37a27b fix(i18n): address review findings on weather localization
- Extracted `modules/alert_format.py` as the single NWS alert formatter, replacing four copies of the event-type abbreviation table and two of the time compactor. `!wx alerts` and the proactive `WeatherService` broadcasts now localize from one code path, so a Russian bot no longer answers `!wx alerts` in English while its proactive alerts are Russian.
- Stopped leaking translation key paths into mesh broadcasts. `Translator.translate` returns the dotted key when a lookup misses in both the locale and the English fallback, which is right for development but reached the air in production: an unclassifiable NWS title rendered as `⚪Hazardous services.weather_service.event_types.Unknown`, an unmapped WMO code as `services.weather_service.weather_descriptions.4`, and an oddly-cased `wind_speed_unit` as `services.weather_service.wind_speed_units.KMH`. `alert_format.translate_or()` carries an English default at each site, and `WeatherService` now normalizes and validates its three `[Weather]` unit settings the way `GlobalWxCommand` already did.
- Fixed alert expiry rendering in every locale. The formatter rendered a timestamp to a string and re-parsed its own output with `(\d+)(AM|PM)` against a hardcoded English month list, so translated months took the wrong branch and truncated mid-string. Times now carry parsed parts and render through a per-locale `common.alerts.time_12h` template — the space before AM/PM was correct (Russian writes "6 дня", not "6дня"); the downstream regex was the bug.
- Restored month abbreviation. `_compact_time` iterated over abbreviations and replaced them in the string instead of mapping full names, so English stopped shortening "June 28" and Russian replaced the "Jun" inside "June", leaving a stray Latin "e" (`июнe 28`). Reuses the existing `common.date_time.month_abbreviations` rather than the duplicate `services.weather_service.months` block.
- Made `!gwx` display units follow `[Weather]` config instead of the response language. Visibility switched on `base_language != 'en'`, so `language = ru` with the default `temperature_unit = fahrenheit` printed Fahrenheit beside kilometers, and `en-GB` was forced to miles. Pressure is a locale convention rather than a metric/imperial split, so each catalog names its own via `commands.gwx.pressure_unit` — previously every non-English locale inherited mmHg from the English catalog, whose `pressure_mmhg` string contained Russian text, giving German and French users Cyrillic pressure units.
- Let localized `H`/`L` labels reach a standard install. `config.ini.example` shipped the three `temperature_*_format` keys uncommented with literal `H:`/`L:`, and a config value always beats the new locale-aware default, so a Russian bot built from the documented example still rendered `H:47°C L:33°C`. The example now uses the `{high_label}`/`{low_label}` placeholders, which were documented in the docstring but not in the file.
- Routed high/low labels through the reply's translator. `_format_high_low` passed `bot.translator`, so with `auto_detect_language` on, an English-default bot answering a Russian sender localized the rest of the line but not `H:`/`L:`. Added `BaseCommand.response_translator` for this, replacing `wx_international`'s reach into the private `_response_translator` ContextVar.
- Fixed a byte-budget overrun in `!gwx`. The guard on the extra conditions block compared a character count against a byte-derived budget while the rest of the function used `_count_display_width`; Cyrillic is two bytes per character, so the block was appended after the budget was spent.
- Reverted nine `commands.gwx` English rewordings that were not localization work, including the configuration hint in `mqtt_weather_no_subscriber` — dropping it left a mis-configured operator with no pointer to the two keys they need.
- Fixed the Russian `visibility` string, which said "км" on the miles key — the same locale/config conflation as the code bug, in the data. Shortened the Russian event-type abbreviations, which were full words consuming a quarter of the 130-byte budget at two bytes per character.
- Added `commands.wx.hourly_not_available`, missing from every catalog so `!wx hourly` printed the raw key path. Predates this branch; found while auditing every translation key the weather modules reference.
- Moved alert strings to `common.alerts.*` and wind directions to `common.wind_directions.*`, since a command and a service both read them.
2026-09-07 15:13:18 -07:00
agessaman 27d3f0847e feat: Enhance MeshMessage and command matching to preserve original content
- Addresses #267
- Added `original_content` attribute to `MeshMessage` to store the on-air body of messages, ensuring it remains unchanged during command processing.
- Updated command matching methods across various commands to utilize `_cleaned_content_matches`, which restores original content when necessary, preventing mention stripping from altering the message context.
- Implemented tests to verify that original content is preserved and correctly utilized in command execution and message handling.
2026-09-04 23:06:03 -07:00
custardpy 92441d0aef fix(i18n): show gwx pressure in mmHg for metric locales
Render GWX pressure in mm Hg (rounded hPa->mmHg conversion) for
non-English locales instead of hPa, matching the metric handling used for
visibility. Also fix format_temperature_high_low so custom [Weather]
templates can use the documented {high_label}/{low_label} placeholders,
and add tests for the translator param.
2026-08-30 08:14:55 +04:00
custardpy 558e5e78b8 fix(i18n): show gwx visibility in km for metric locales 2026-08-28 10:36:42 +04:00
custard 495e5cb5d7 feat(i18n): localize gwx output (wind, humidity, dew point, visibility, pressure, day names) 2026-08-28 10:30:13 +04:00
custardpy d0715936c7 feat(i18n): add translator support to format_temperature_high_low
- Add optional translator param to format_temperature_high_low()
- Translate temperature labels (H/L) via common.temp_high_label/low_label
- Translate wind directions via services.weather_service.wind_directions
- Fix wind format spacing (direction and speed were concatenated)
- Pass translator from Weather_Service, !wx, and !gwx callers
2026-08-26 13:54:50 +04:00
agessaman 9a24ed1bf2 chore: update documentation and improve configuration handling
- Added a tracked `LICENSE` file and updated `pyproject.toml` to include license metadata.
- Enhanced `CHANGELOG.md` with recent changes and clarifications.
- Updated `config.ini.example` and related documentation to reflect clamping behavior for numeric limits in `[Feed_Manager]`.
- Improved startup validation to suggest corrections for unknown and misspelled keys.
- Refactored geocoding and HTTP request handling to run off-thread, preventing event loop stalls.
- Added thread safety to cache management in geocoding functions to avoid race conditions.
2026-07-28 16:56:43 -07:00
agessaman d34ad24e9b feat(weather): add default city configuration for wx commands
- Introduced a new configuration option `default_city` in `config.ini.example` for fallback city names when no location is provided.
- Updated `wx_command.py` and `wx_international.py` to utilize the `default_city` for location disambiguation, enhancing user experience when companion location is unavailable.
- Improved logging to reflect the use of default city or bot location as fallback options.
2026-04-25 21:15:51 -07:00
agessaman 8bea10cdc3 Enhance mention handling in message processing
- Updated `config.ini.example` to introduce the `respond_to_mentions` setting, allowing configuration of how the bot responds to mentions in channel messages.
- Refactored `MessageHandler` to implement logic for handling mentions based on the new configuration, including stripping mentions when appropriate.
- Added `cleanup_message_for_matching` method in `BaseCommand` to streamline message processing and mention validation.
- Enhanced various command classes to utilize the new cleanup method for consistent mention handling.
- Introduced tests to validate the behavior of the new mention handling logic across different configurations.
2026-04-11 18:45:34 -07:00
agessaman 9d768a3cf7 Implement MQTT weather support in weather commands and configuration
- Added new configuration options in `config.ini.example` for enabling and configuring MQTT weather sources.
- Updated `WxCommand` and `GlobalWxCommand` classes to retrieve weather data from custom MQTT topics, enhancing flexibility in weather data sources.
- Introduced error handling for MQTT data retrieval, ensuring robust responses for various error scenarios.
- Expanded translation strings in `en.json` to support MQTT-related messages, improving user feedback.

These changes enhance the application's capability to utilize MQTT for weather data, providing users with more options for weather information retrieval.
2026-04-02 10:24:49 -07:00
agessaman 5b1bcac2a0 Implement temperature formatting in weather commands and services
- Added new configuration options in `config.ini.example` for customizing the display of daily high/low temperatures.
- Introduced `format_temperature_high_low` function in `utils.py` to format temperature strings based on user-defined templates.
- Updated `WxCommand`, `GlobalWxCommand`, and `WeatherService` classes to utilize the new formatting function, enhancing the presentation of temperature data in weather forecasts.
- Refactored existing high/low temperature handling to improve code clarity and maintainability.

These changes enhance the flexibility and readability of temperature displays in weather-related outputs.
2026-03-29 21:22:00 -07:00
agessaman 5f6eced45d Add Open-Meteo model selection support in weather configuration
- Introduced a new configuration option for selecting Open-Meteo models in `config.ini.example`, allowing users to specify a model or default to "best_match".
- Implemented `_load_weather_model` method in `GlobalWxCommand` and `WeatherService` classes to handle model selection logic, including validation and fallback mechanisms.
- Updated API request parameters to include the selected weather model when available, enhancing the flexibility of weather data retrieval.

These changes improve the customization of weather data requests and ensure robust handling of model selection in the application.
2026-03-29 20:58:26 -07:00
Chris Close e2725686ef Fix path message truncation for emoji (double-width) characters
Use UTF-8 byte length instead of character count or display width for message
truncation. This fixes an issue where emoji characters like 😀 (4 UTF-8 bytes,
1 character, 2 display units) caused messages to exceed the RF packet size.

Changes:
- Replace _count_display_width with byte-based length calculation
- Add _count_byte_length and _truncate_to_byte_length helper methods
- Update get_max_message_length to use 127 bytes (channel limit) not 150
- Add type hints to new methods
2026-03-29 13:33:58 -07:00
agessaman 206753a0c8 Implement bot location fallback in weather commands when no location is provided
- Added a new configuration option `use_bot_location_when_no_location` to allow the use of bot's configured coordinates if no companion location is available.
- Updated `wx_command.py` and `wx_international.py` to utilize the new configuration, enhancing the user experience by providing a fallback mechanism for location-based commands.
- Improved logging to reflect the usage of bot coordinates and handle cases where bot location is not set.
2026-03-21 20:44:19 -07:00
agessaman 3735f26971 Enhance weather command functionality to support extended multiday forecasts
- Introduced support for multiday forecasts up to 16 days, allowing users to specify days in various formats (e.g., Nd, 7day).
- Updated usage documentation and parameter descriptions to reflect new options for both US and international weather commands.
- Adjusted internal logic to validate and parse new forecast formats, ensuring compatibility with the extended range.
2026-03-21 20:44:07 -07:00
Stacy Olivas ce884cee87 fix: auth, db migrations, retry, chunking, socket race, trace, timezone, repeater, and ruff/mypy cleanup
BUG-001: web viewer login/session auth (in web viewer commit)
BUG-002: db_manager ALTER TABLE for missing channel_operations and
  feed_message_queue columns on startup
BUG-015: scheduler thread blocked on future.result(); replaced all
  blocking waits with add_done_callback (fire-and-forget)
BUG-016: reboot_radio sends meshcore.commands.reboot() before disconnect
BUG-017: radio disconnect uses asyncio.wait_for(timeout=10)
BUG-022: custom asyncio loop exception handler suppresses IndexError
  from meshcore parser at DEBUG level
BUG-024: last_db_backup_run updated after each run; 2-min startup
  window; last-run seeded from DB on restart
BUG-025: send_channel_message retries up to 2 times (2s delay) on
  no_event_received via _is_no_event_received() helper
BUG-026: split_text_into_chunks() and get_max_message_length() added
  to CommandManager; keyword dispatch uses send_response_chunked()
BUG-028: byte_data = b"" initialised before try block in
  decode_meshcore_packet to prevent UnboundLocalError in except handler
TraceCommand: path nodes reversed and return path truncated; fixed
format_elapsed_display: UTC normalisation before elapsed computation (#75)
RepeaterManager: auto_manage_contacts guard before any purge logic (#50)
Command aliases: [Aliases] config section injects shorthands at startup
JSON logging: _JsonFormatter; json_logging = true in [Logging]
Structured JSON logging compatible with Loki, Elasticsearch, Splunk
Discord bridge, Telegram bridge, and all service plugins updated
MeshGraph edge promotion logic corrected
Shutdown: scheduler and meshcore disconnect joined cleanly; log spam fixed
All modules: ruff and mypy cleanup applied (type annotations, imports)
2026-03-17 18:07:18 -07:00