Commit Graph
10 Commits
Author SHA1 Message Date
Jean-Pierre MartineauandClaude Fable 5 ce3c2d1e2e Background adverts on a per-device cadence, one send per pass
MeshTerm now announces the connected node by itself: a zero-hop advert
every 1/2/3/4 hours (default hourly) and a flood advert every
3/6/12/24/48/168 hours (default daily), each switchable off. The
cadences are staged and applied from a new Background adverts section
in the device-configuration editor, and persist per device (keyed by
public key) in adverts.json alongside the last-sent mark for each type
— so the countdown survives restarts, and a manual send from any
advert flow resets the same clock the scheduler reads.

The scheduler stays transmission-shy: at most one advert per 30 s pass
(a flood due together with a direct waits a pass), and a never-sent
device is armed to count from now rather than advertised immediately.
It holds no device subscription — each pass just skips when nothing is
connected — so reconnects need no special handling, and scripted CLI
runs never start it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 03:12:49 -04:00
Jean-Pierre MartineauandClaude Fable 5 a65ce0ccf7 Exit in bounded time, connect BLE first try: teardown and startup hardening
Two long-standing flakes, one root cause each:

Quit with a serial companion often froze until the exit watchdog killed
the process. The meshcore dispatcher's stop() awaits queue.join(), but
its processor task exits after draining at most one event once stopped —
so a routine advert/RX-log burst (two+ events queued at that instant)
deadlocks the join forever (repro confirmed against the installed
library). MeshCoreDevice.disconnect now gives the graceful teardown two
seconds, then abandons it: the dispatcher task is cancelled synchronously
and the raw transport closed directly, so the port is still released and
even the forced path finishes well under the watchdog.

BLE startup often failed until the user re-selected the device. Connecting
by bare address makes bleak re-discover the peripheral with a fresh
internal scan, which on Windows intermittently misses a slow-advertising
companion. The discovery scan's live BLEDevice now rides along
(DiscoveredDevice -> probe/context -> create_ble) so the connect opens the
peripheral directly, and a transport-level ConnectionError is retried once
before surfacing — the manual re-select was never anything but a retry.
PIN/bond rejections still surface on the first attempt, unlooped.

Verified against hardware: BLE info read off the Waymarker connects and
exits cleanly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 22:16:36 -04:00
Jean-Pierre MartineauandClaude Fable 5 0f49cf0a2d Passive monitoring is always on; CLI monitor becomes a timed capture
The on/off switch never made sense for a pure listener: recording is
now unconditional, so the toggle submenu, the persisted preference
(MonitorStore/monitor.json), and the header's ON/OFF framing are gone.
The monitor registers its hub subscription at startup — a device-free
step that cannot fail — and its run row opens lazily on the first
observation, so recording follows the hub wherever it opens (eagerly,
or deferred by connect_on_start) and silent sessions leave no empty
runs. The menu entry is now just the read side, "Heard nodes", and the
header shows live heard counters instead of a switch state.

meshterm monitor on the CLI drops --on/--off for a bounded foreground
capture (--seconds N, 0 = until Ctrl-C, mirroring chat listen): it
tails each overheard packet live and summarizes the window's heard
nodes when it ends, while persisting history exactly like a session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 02:07:23 -04:00
Jean-Pierre MartineauandClaude Opus 4.8 99643f52d9 Overhaul the config editor into a modal-dialog experience
Rework the device-configuration screen from a flat list with an
advert-laden "danger zone" into one grouped menu whose every prompt is
a real floating modal popup over a persistent backdrop, matching the
quit dialog. The main menu now stays pushed on the session stack for
the whole editor session, so sub-prompts float over it rather than
replacing the screen.

Settings are staged and applied together (with a dirty-cancel
confirmation); device actions — adverts, reboot, backup/restore,
identity key, factory reset — run immediately behind their own
dialogs. Adverts get a dedicated menu (zero-hop, flood, or share this
node as a QR/URI contact card). Destructive actions gate behind a new
TypedConfirmDialog that requires typing RESET / IMPORT. Reboot hands
off to the reconnect dialog and waits for the companion to return.
Location can be set by pointing at the full-screen map (new
LocationPickScreen) or by typing coordinates.

Make every dialog consistent: a short title in the border, the
instruction as an in-body prompt, and a width sized to its content
instead of a fixed wide cap. TextScreen/SelectScreen/AutocompleteScreen
gain an optional in-body prompt, and those plus ConfirmScreen gain a
dialog_width; the prompt is threaded through the surface and session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 22:28:42 -04:00
Jean-Pierre MartineauandClaude Opus 4.8 12577a1538 Add "Unpair & quit" to drop a BLE bond on exit
The quit dialog grows a third button, shown only when the session is on a
Bluetooth link that Windows actually holds a bond for (never serial, an open
PIN-less companion, or a non-Windows platform). Choosing it disconnects the
link and then drops the OS-level pairing during teardown, so the next launch
re-runs the PIN ceremony. MeshTerm's remembered-device record is deliberately
kept — unpairing forgets the credential, not the device identity.

Backing this: static WinRT helpers is_ble_paired / unpair_ble mirroring
_pair_ble_windows (Windows-gated, best-effort), an active_address on the
context, and _can_unpair / _unpair_on_exit in the menu.

Also close the WinRT BluetoothLEDevice handles in every helper. Each
from_bluetooth_address_async returns an IClosable that pins the OS ACL link
open; unpair removes the bond but not the link, so a leaked handle left the
device "unpaired but still connected" and refusing to re-pair until it was
power-cycled. Closing the handle in a finally is what finally tears the link
down.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 20:46:02 -04:00
Jean-Pierre MartineauandClaude Opus 4.8 d4fa056526 Identify companion hardware by its firmware model
Query MeshCore's device-info frame (send_device_query) on connect to learn the
board's own model string (e.g. "Seeed Tracker T1000-E") — the only reliable source,
since BLE companions advertise no manufacturer data, use randomized addresses with
no IEEE OUI, and expose no GATT Device Information Service.

The probe folds the model into the identity dict, so it's captured during the
connection that already happens, then remembered in the device store (preserved
across reconnects like the node name). The device list and splash picker rename
their "Vendor" column to "Hardware": it shows the firmware model for confirmed
devices and falls back to the USB vendor name for merely-attached serial ports.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 19:17:43 -04:00
Jean-Pierre MartineauandClaude Opus 4.8 7850587e2a Add Bluetooth LE companion support alongside serial
Companions can now be reached over Bluetooth as well as USB. Rather than
a parallel BLE path, this generalizes the existing serial abstractions so
a device is identified by a transport (serial | ble) plus a target (COM
port or Bluetooth address); every feature rides the same Device interface
and works over either link unchanged.

- discovery: DiscoveredDevice gains transport/address/name and
  transport-aware stable_id/label/confidence/target; discover_ble_devices
  scans for MeshCore* adverts via bleak, discover_all merges both.
- connection: MeshCoreDevice opens create_ble vs create_serial by
  transport; new link_present() liveness (OS port enumeration for serial,
  the BLE client's is_connected flag for Bluetooth); is_connection_lost
  recognizes Bleak errors and BLE transport callbacks; probe_device is the
  transport-aware smoke test.
- disconnect: the session watcher polls transport-neutral link_alive(), so
  a dropped BLE link raises the same reconnect dialog and auto-resume as an
  unplugged cable; reconnect rebuilds the same endpoint by address.
- selection/context/store/profiles: --ble address, remembered BLE default,
  and BLE device profiles all resolve to the right transport.
- UI/CLI: startup picker scans both transports behind a spinner and tags
  Bluetooth companions; `devices` lists them (--no-ble to skip); global
  --ble/--ble-pin flags; README and example config updated.

Validated on hardware: discovery, connect, reconnect, and channel
messaging (send + receive) over BLE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 18:44:39 -04:00
Jean-Pierre MartineauandClaude Opus 4.8 dd097e48e5 Prompt to reconnect or abort when the device drops mid-session
Watch the companion link while the menu runs; if it disconnects, float a
centered dialog with a spinner and a single Abort button while a
background task retries the connection. The dialog dismisses itself the
moment the device returns, so the user's only action is to give up:
Enter aborts, and Esc is deliberately inert so a stray keypress can't
drop the session while a replug may be a second away.

Give every popup dialog the same cyan reverse-highlight button. This
adds a "selected" theme style (and "err.reverse"): Rich silently renders
a style string that mixes a theme name with an attribute ("reverse
brand") as plain text, so the reverse is baked into a single-name theme
entry instead. Center the button chips with manual padding rather than
Text(justify="center"), which strips a styled span's trailing whitespace
and left the label hugging the fill's left edge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 00:07:45 -04:00
Jean-Pierre MartineauandClaude Opus 4.8 101b48bc71 Adopt the probed device and polish the startup splash
Rework the startup device picker into a self-contained splash flow:

- probe_meshcore (renamed from smoke_test_meshcore) now leaves the confirmed
  connection open and returns it, so AppContext.adopt_device reuses that live
  radio instead of reopening (many boards reset on each serial open). Restore
  the library's normal per-command timeout once the handshake succeeds.
- Extract the Braille animation into a reusable tui.Spinner; BusyScreen now
  delegates to it.
- Pin the wordmark to a fixed vertical anchor so it never shifts as the box
  swaps between the device list, spinner, and messages.
- Esc or a trailing Quit row leaves the splash and exits the program.
- Show the copyright only on the opening splash; retire it once a device is
  chosen, even across a failed smoke test and re-pick.
- Disable prompt_toolkit's loop exception handler and route asyncio (and the
  awaited spinner ticker) errors to the file log, so a stray background-task
  error can't dump a traceback over the full-screen UI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 18:09:27 -04:00
Jean-Pierre MartineauandClaude Opus 4.8 2ec18138a7 Rename project from MeshTools to MeshTerm
Rename the package directory, imports, brand strings, env-var prefixes,
console-script entry point, and the config/data dir (~/.config/meshterm).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 19:40:49 -04:00