- Add pre-restart config preflight to RestartModal with pass/warning/fail result panel and "Restart Anyway" confirmation for warnings
- Add Site Identification (site_name) config field shown as browser title and login page caption
- Add GET /api/validate_config and GET /api/site_info backend endpoints
- Sync document.title with site_name via system store watchEffect
- Fix authRegression.test.ts TS2367 type narrowing error
- add new radio hardware and radio settings tabs
The HAT (E) uses a dedicated BMS MCU (not an INA219) at I2C address 0x2D.
It exposes charge state, pack voltage/current, per-cell voltages, remaining
capacity in mAh, and time-to-empty/full estimates directly via I2C registers.
Tested on Raspberry Pi 4B with pyMC_Repeater running pymc-battery-writer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
nmea_gps.py reads NMEA 0183 sentences directly from a serial GPS receiver
(/dev/serial0 by default) and exposes fix status, position, motion, accuracy,
and satellite fields as sensor readings.
Parses GGA, RMC, and GSA sentence types using stdlib only (no pynmea2
dependency) — pyserial is already required by the repeater.
Designed for use when the repeater's built-in GPS service is disabled
(gps.enabled: false). Both cannot share the serial port simultaneously.
With gps.api_fallback_to_config_location: true the repeater continues
advertising the manually-configured location while the sensor plugin handles
raw GPS data.
Returns: fix_valid, fix_quality, fix_type, latitude, longitude, altitude_m,
speed_kmh, course_degrees, hdop, pdop, vdop, satellites_used, utc_datetime.
Position and motion fields are null when fix_valid is false to avoid
reporting config-fallback coordinates as real GPS data.
Tested on Raspberry Pi 3B+ (DietPi) with a u-blox GPS module on /dev/serial0.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- shtc3.py: SHTC3 temperature/humidity sensor (RAK1901 WisBlock module,
I2C 0x70). Uses smbus2 i2c_rdwr for raw I2C reads since SHTC3 requires
16-bit command words with no register-byte prefix. Returns temperature_c,
temperature_f, humidity_pct.
- waveshare_ups_d.py: Waveshare UPS HAT (D) battery monitor via INA219 at
I2C 0x43. Uses the HAT's actual shunt (0.01 Ω, CAL=26868) rather than the
generic INA219 defaults. Returns bus_voltage_v, shunt_voltage_mv,
current_ma, power_mw, battery_percent (piecewise-linear SoC for 21700
cell), and charge_state (charging/discharging/idle). Sign convention
matches Waveshare sample code: negative current = charging.
Both plug-ins tested on Raspberry Pi 3B+ (DietPi) with RAK1901 WisBlock
sensor and Waveshare UPS HAT (D).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Lets a fresh repeater install pick the pymc_usb (USB-CDC) or pymc_tcp
(Wi-Fi/Ethernet) external modem from the first-run /setup wizard
instead of requiring the user to hand-edit config.yaml after install.
radio-settings.json gets two new hardware entries; setup_wizard()
in api_endpoints.py handles them in dedicated branches that mirror
the existing KISS pattern (placeholders if the SPA doesn't yet send
modem-specific inputs, request body overrides if it does).
For pymc_tcp the wizard writes a sentinel host placeholder
('REPLACE_WITH_MODEM_HOST') so the YAML stays valid; on startup
get_radio_for_board() then errors with a clear pointer at
pymc_tcp.host (existing behavior from the PR #240 branch). pymc_usb
defaults to /dev/ttyACM0 at 921600 baud — matches the USB-CDC
device path documented in pymc_usb's README + pymc_driver.
Five new tests in tests/test_setup_wizard_pymc.py verify both
default and overridden code paths plus a KISS regression guard.
Adds a new read-only endpoint that serves the bundled `repeater/presets/*.yaml`
catalogue so the admin UI can render a network picker without bundling its own
copy of the broker dicts. The UI side of this is paired with
pyMC-dev/pyMC-RepeaterUI#TBD which retires src/assets/broker-templates.json
in favour of authClient.get('/api/broker_presets').
Why
The UI previously shipped a separate JSON snapshot of every supported MC2MQTT
network. The JSON and these YAML files drifted: the Waev entry on the UI side
pointed at mqtt-a.waev.app with audience mqtt.waev.app (single primary, no
failover) while the YAML side here listed two brokers (A + B). The result was
that operators picking 'Waev' from the dropdown silently lost the redundancy
this preset is meant to provide.
What changes
repeater/presets/*.yaml
- Add optional top-level `display_name` and `website` fields. The loader
treats them as advisory metadata for the UI; the runtime connection code
never reads them. `display_name` falls back to the titlecased filename
stem if absent so existing third-party presets keep rendering.
repeater/presets/waev.yaml
- Collapse from two broker entries (waev-a, waev-b) to a single broker on
`mqtt.waev.app`. The Waev edge Worker (see waev/src/router.ts:
MQTT_PRIMARY_FAILOVER_TIMEOUT_MS) already does server-side A/B failover on
the alias host with a 1500 ms timeout. Two independent client connections
would defeat the dedup-on-pubkey-hash contract on the waev ingest side.
Operators who want to pin to a specific container can edit host/audience
after import.
repeater/presets/meshmapper.yaml (new)
- Port of the historical MeshMapper entry from the UI's deprecated JSON.
Single broker on mqtt.meshmapper.cc, format: letsmesh (matches the
published wire contract; bump to a dedicated value if/when wire-level
differentiation lands).
repeater/web/api_endpoints.py
- New `broker_presets` CherryPy handler at `GET /api/broker_presets`.
Unauthenticated to match the existing `mqtt_status` precedent — the
response carries only public hostnames + TLS hints, no PII. Imports the
presets module lazily so a broken YAML never blocks process startup.
Response shape:
{
success: true,
data: [{ id, name, website?, brokers: [ ... raw YAML dicts ... ] }, …]
}
tests/test_presets.py
- Locks the new metadata fields (display_name, website) on all three presets.
- Locks the Waev single-alias-broker design with an explicit comment tying
the test to the waev Worker failover code.
- Adds MeshMapper coverage parallel to the other public-network presets.
- Adds a stub-instance test that drives the new `broker_presets` method on
an APIEndpoints stand-in (bypassing the heavyweight `__init__`) and
asserts the UI-ready response shape.
Verification
- New endpoint serves the expected three presets (letsmesh: 2 brokers,
meshmapper: 1, waev: 1) when exercised end-to-end against a local mock
that imports the real preset loader.
- Existing legacy-config migration tests (broker_index 0/1/-1 → preset +
overrides) still pass — the override pipeline is untouched.
Co-Authored-By: Oz <oz-agent@warp.dev>