Skip to content

Claude/cli bluetooth raspberry pi f8571a - #3

Merged
Ktechen merged 10 commits into
masterfrom
claude/cli-bluetooth-raspberry-pi-f8571a
Jul 25, 2026
Merged

Claude/cli bluetooth raspberry pi f8571a#3
Ktechen merged 10 commits into
masterfrom
claude/cli-bluetooth-raspberry-pi-f8571a

Conversation

@Ktechen

@Ktechen Ktechen commented Jul 25, 2026

Copy link
Copy Markdown
Owner

No description provided.

kevin and others added 2 commits July 25, 2026 14:14
The Pi (BlueZ) transport found and connected nothing because it did not
do what the mobile transport does. Bring it to parity:

- Power the radio on first: check the adapter's Powered property and call
  SetPoweredAsync(true); throw an actionable message (rfkill/bluetoothctl)
  when it cannot come up, instead of silently scanning nothing.
- Scan on the LE transport (SetDiscoveryFilter Transport=le) so BLE-only
  hubs and their manufacturer data surface; BlueZ's default auto mode
  routinely misses them.
- Also enumerate GetDevicesAsync() at scan start, since a fresh
  StartDiscovery never re-fires DeviceFound for already-cached devices.
- Wait for ServicesResolved=true (not just Connected) before GATT lookups,
  which otherwise race and return null.
- Cold connect: GetDeviceAsync does a brief LE scan when the hub is not in
  BlueZ's cache yet, so auto/connect work after a reboot with no discover.
- Surface radio errors: DiscoverAsync awaits an EnsureReadyAsync preflight
  (Discover() is fire-and-forget and swallows exceptions) and
  DiscoverCommand prints the message cleanly. Add diagnostic log events.

The preflight is why BlueZLegoService takes the concrete adapter, wired via
a forwarding singleton so the SharpBrick host and the service share one
radio instance.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
A long-running loop for unattended operation on the Pi (systemd/Docker).
Every --interval seconds (default 60) it re-reads the saved trains from
SQLite and applies each one's saved config — connect, set the hub LED,
drive at its saved speed — reconnecting any hub that has dropped. On
Ctrl+C / SIGINT it stops every motor and disconnects cleanly.

- SetSpeed doubles as a health check: a dropped link throws, the train is
  disconnected, and the next sweep reconnects it fresh (self-healing).
- The store is re-read each cycle, so new/edited trains are picked up live.
- A failed sweep is caught and retried next interval so the daemon never
  dies; --all includes inactive trains.
- Documented in ReadMe (EN + DE) incl. the systemd unit; added to the
  dashboard cheat-sheet and command help.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@Ktechen Ktechen self-assigned this Jul 25, 2026
@Ktechen Ktechen added the enhancement New feature or request label Jul 25, 2026
kevin and others added 6 commits July 25, 2026 14:33
Ubuntu Server images ship without BlueZ, so trackify could not reach
bluetoothd over D-Bus (bluetoothctl/bluetooth.service missing) even though
the kernel BT stack was fine. Add first-class setup + diagnostics:

- scripts/setup-bluez.sh: idempotent installer — apt install bluez+rfkill,
  enable/start bluetoothd, rfkill unblock, power on the adapter, add the
  user to the bluetooth group.
- scripts/pi-bt-info.sh: read-only diagnostics — radio/rfkill/service/
  permissions/adapter state + a live LE scan that flags LEGO (0x0397)
  manufacturer data.
- ReadMe (EN + DE): new "install BlueZ" prerequisites section referencing
  both scripts and the manual apt steps.

Both scripts are marked executable.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Guarantees the Pi setup/diagnostics scripts keep Unix line endings even when
checked out or edited on Windows (CRLF breaks #!/usr/bin/env bash on Linux).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Hosts an ASP.NET Core backend over the same Domain/Application/Infrastructure
use-cases the CLI runs, so the Uno app can drive a Pi remotely:

- REST (one-shot): GET /api/trains, POST /api/discover, connect/disconnect by
  hubId, GET /api/state — routes shared via Application/Remote/ApiRoutes.
- SignalR hub /hubs/trains: real-time SetSpeed/SetLed/Stop + a SpeedChanged
  broadcast; method names shared via TrainHubMethods. Keyed by hubId so it maps
  1:1 onto ILegoService (the app's existing control seam).
- Composed with an AddTrackifyServer() DI extension; handlers resolve their
  use-cases from DI. `server` is a normal Spectre command (--urls override).
- Config is externalised to appsettings.json (Serilog levels/sinks, Urls,
  DiscoverTimeoutSeconds) via Serilog.Settings.Configuration — nothing hardcoded.
- Global exception handling: Spectre SetExceptionHandler (CLI) + UseExceptionHandler
  middleware (serve), both logging; new Info/Error log events.
- ASP.NET arrives via a Microsoft.AspNetCore.App FrameworkReference (dropping the
  now-redundant M.E.DI/Logging package refs that tripped NU1510).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
The app gains an optional Server mode alongside its own Bluetooth (Direct mode);
both are interchangeable ILegoService implementations, so the UI is unchanged:

- RemoteLegoService : ILegoService — one-shot actions over REST (Refit
  ITrackifyApi) and real-time speed/LED over SignalR; raises SpeedChanged so the
  UI can show live speed.
- RemoteTrainSync — pulls the backend's trains into the local SQLite store,
  de-duplicated by hub identity (HubId → BleAddress → Name), upserting in place.
- AddTrackifyRemote(baseUrl) wires the Refit client + SignalR transport; when
  AppConfig.ServerUrl is set the app registers it (wins as ILegoService) and
  enables the sync — otherwise it stays in Direct mode (unchanged behaviour).
- Global exception handling (ASP.NET-style): App hooks UnhandledException,
  AppDomain.UnhandledException and TaskScheduler.UnobservedTaskException, all
  logged via Serilog — no silent swallowing.

The UI mode switch + IP entry is the remaining piece (wired via config for now).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
`trackify discover --save` stores each discovered hub in the train list,
de-duplicated by hub identity (HubId then MAC). Saving goes through a new
Application use-case (ITrainService.SaveDiscoveredAsync) so the CLI stays
DTO-only (the entity boundary stays behind Application). Per-hub failures are
reported, never swallowed.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
- TrainSaveDiscoveredTests: new-train creation, dedup by HubId and by MAC, and
  the unnamed-hub name fallback (Application, via FakeTrainRepository).
- TrainStateStoreTests: latest-speed-per-hub tracking + empty snapshot (CLI).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Comment thread Source/Trackify.Cli/Server/TrackifyServer.cs Fixed
Comment thread Source/Trackify.Cli/Server/TrackifyServer.cs Fixed
kevin and others added 2 commits July 25, 2026 15:32
MainPage gains a Connection panel (header toggle → modal overlay): a
Direct/Server switch + backend URL field + Apply, bound to a persisted
ConnectionState. Switching is live (no restart):

- SwitchingLegoService routes each ILegoService call to the local Bluetooth
  transport or a remote one, chosen per-call from ConnectionState; the remote
  transport is (re)created when the URL changes. Registered by wrapping the
  platform transport (ResolveLocal) so the two don't compete for resolution.
- ConnectionState persists mode + URL in local app settings (seeded from
  AppConfig.ServerUrl); TrackifyApiFactory builds the Refit client per URL.
- Apply persists the choice and, in Server mode, runs RemoteTrainSync (which
  now reads the URL from ConnectionState) to pull trains into local SQLite.
- Quieted framework INFO noise (Uno appsettings probes, Microsoft host) so the
  log overview stays clean; new Info/Warning log events for mode/sync/settings.
- Replaces the earlier config-only remote wiring (AddTrackifyRemote removed).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Addresses the code-scanning findings on the server:
- Log forging (CWE-117): sanitize user-controlled Request.Method/Path (strip
  CR/LF) before logging in the global exception handler.
- Information disclosure (CWE-209): the 500 response now returns a generic
  "Internal server error" instead of echoing the exception message to clients;
  the detail is still logged server-side.
- Over-permissive CORS (CWE-942): drop AllowCredentials so we no longer combine
  any-origin with credentials; the backend uses no cookies/auth.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@Ktechen
Ktechen merged commit 2f0380c into master Jul 25, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants