draft: mobile app daemon integration (dev/mobile-app-integration)#1070
Draft
tfrere wants to merge 38 commits into
Draft
draft: mobile app daemon integration (dev/mobile-app-integration)#1070tfrere wants to merge 38 commits into
tfrere wants to merge 38 commits into
Conversation
…rerData Allow BLE scanners to discover the robot's IP address(es) without establishing a GATT connection. The advertisement payload encodes each non-loopback IPv4 as 5 bytes (1 flag byte + 4 IP bytes), refreshed every 10 seconds alongside the existing GATT network characteristic. This enables passive robot discovery via BLE scan, which is useful as a fallback when mDNS/WiFi discovery is unreliable. Made-with: Cursor
Expose four new BLE commands so a mobile client can provision WiFi
over Bluetooth, even before the robot is on the network:
WIFI_STATUS -> {"mode","connected","known","error"}
WIFI_SCAN -> ["SSID1", "SSID2", ...]
WIFI_CONNECT {"ssid","psk"} -> "OK: Connecting to <ssid>"
WIFI_FORGET <ssid> -> "OK: Forgotten <ssid>"
All four commands simply proxy to the existing /wifi/* FastAPI routes
over localhost HTTP. This reuses the daemon's `busy_lock`, threading
and automatic hotspot fallback instead of duplicating `nmcli` logic in
the Bluetooth service.
Mutating commands (SCAN, CONNECT, FORGET) require prior PIN auth; like
today for CMD_*. Unlike CMD_* we do NOT reset `self.connected` after
each WIFI_* command so a client can chain scan -> connect -> poll in a
single provisioning session.
WIFI_STATUS is readable without authentication (same rationale as the
public NETWORK_STATUS characteristic) so the UI can surface the
current state during the first-time setup flow.
Implementation notes:
- Uses `urllib` from the standard library: the Bluetooth service runs
out of the system Python (not the daemon venv).
- Responses are kept compact (short JSON keys, SSID list capped at 12)
so a single BLE MTU is enough in practice.
- WIFI_CONNECT is fire-and-forget; clients poll WIFI_STATUS to observe
the outcome (which also reports nmcli errors via the /wifi/error
relay).
- WIFI_STATUS is excluded from the command/response log filter since
clients are expected to poll it.
Made-with: Cursor
Adds `GET /api/hf-auth/token` so a remote client (mobile app WebView) can retrieve the stored HF OAuth token and seed it into a sandboxed iframe or the ReachyMini JS SDK that cannot go through the HF OAuth flow itself (blocked by `X-Frame-Options: SAMEORIGIN` on `huggingface.co/login`). The daemon's HTTP API is already unauthenticated on the local network, so any client that can reach this endpoint can also start/stop apps at will. Exposing the token here does not widen the attack surface. We deliberately keep the endpoint separate from `/status` so the desktop frontend, which never needs the raw token, is not tempted to consume it. Made with Cursor. Made-with: Cursor
Adds `POST /api/hf-auth/refresh-relay` to force the central signaling relay to drop its SSE channel and re-register with the currently stored HF token, via the existing `notify_token_change` path. Recovers from a desynchronised state where `/relay-status` claims `connected` but `/central-robot-status` returns `robots: []`, which means the relay is still holding an SSE channel open with central but is no longer registered as a producer for the authenticated user. From the outside this shows up as "the robot is online but no one can call it" until someone restarts the daemon. Before this endpoint, the only recovery was SSH + `systemctl restart`. The mobile app can now detect the desync on startup and call this endpoint to self-heal. Implementation piggy-backs on `notify_token_change`, the same path the login flow uses, so the relay's reconnect logic is exercised in a well-trodden way. Works with any token currently stored (raw user tokens or OAuth access tokens) because we do not re-validate the token shape here. Made with Cursor. Made-with: Cursor
notify_token_change short-circuits with "Token unchanged, no action needed" whenever old_token == new_token, which is exactly the case we ship POST /refresh-relay for: the user is still signed in with the same token, but the relay's SSE has desynced from central. Adds CentralSignalingRelay.force_reconnect() + notify_force_reconnect() that always tear down the current connection and re-register with the stored token. Factored the close + set-token-updated sequence into _reconnect_now() so update_token and force_reconnect stay in lockstep. Before: POST /refresh-relay on a logged-in daemon was a silent no-op, leaving the mobile app's auto-heal spinner spinning and "waiting for reachy" stuck forever. After: the relay drops its SSE and re-registers within ~1s, central lists the robot again, and the conversation engine starts cleanly. Made with Cursor. Made-with: Cursor
When `force_reconnect` (or `update_token`) tears down connections via `_close_connections()`, aiohttp can propagate a CancelledError up through an in-flight `session.get(...)` - typically because it was wedged in DNS resolution (`_resolve_host`) at the time of cancellation. That CancelledError bubbled up to `_run_loop`'s inner handler, which unconditionally re-raised it, which killed the entire relay thread. Every subsequent `POST /api/hf-auth/refresh-relay` then just flipped the reported state without actually doing anything, because there was no loop left to service the reconnect. Now distinguish legitimate shutdown (`self._running == False`, from `stop()`) from in-flight cancellation triggered by a reconnect request: the former still exits the thread as before, the latter transitions into RECONNECTING and lets the loop retry. Reproduces reliably when the daemon boots before the robot has DNS (WiFi still negotiating) and the mobile app's auto-heal fires at the same time. Made-with: Cursor
When the daemon's relay receives `welcome` from central, it used to:
1. set state -> CONNECTED ("Remote access enabled as ...")
2. await _send_to_central(setPeerStatus producer)
If step 2 was cancelled mid-flight (force_reconnect, token rotation,
DNS hiccup, transient HTTP error swallowed silently), observers
already saw `relay-status: {state: connected}` while central had
never registered the relay as a producer for the authenticated user.
From the outside this looked like
- /relay-status -> {"state":"connected","is_connected":true}
- /central-robot-status -> {"available":true,"robots":[]}
Reorder so producer registration happens BEFORE the state flip, so
the CONNECTED announcement only fires once central has acknowledged
the producer role.
Also make _send_to_central observable: log INFO on success, WARNING
with the response body on non-200, ERROR with the exception on
network failure, and a WARNING when the call is skipped (no
session / no token). Without these logs we had no way to tell from
the journal whether setPeerStatus had reached central, and the same
zombie-relay condition documented on /refresh-relay was only ever
diagnosable by SSHing in and tcpdumping.
Reproduces by calling /refresh-relay (or rotating the token) while
the previous _send_to_central is still in flight - which is exactly
what the mobile app's auto-heal flow does on every login.
Made-with: Cursor
Made-with: Cursor
Adds a daemon-side logging foundation that mirrors the mobile app's
PR-A: every log line carries a 4-char trace-id, and that id travels
end-to-end via the `X-Trace-Id` HTTP header, so a single user action
on the phone can be grep'd across mobile DevTools and the daemon's
systemd journal.
What's added
- `logging_ctx.py`: ContextVar-based trace-id, `TraceIdFilter` so
`%(trace_id)s` in formatters always has a value, `redact_dict` /
`redact_token` for secret-safe payloads, and a `kv` / `kv_log`
helper for structured `event_name key=value` lines.
- `trace_middleware.py`: `TraceIdMiddleware` reads `X-Trace-Id` from
incoming requests (or mints one when absent), scopes it on the
ContextVar for the duration of the request, emits one structured
`http.request` line per non-noisy path, and echoes the id back as
a response header so clients can confirm correlation.
- `main.py`: installs the filter on the stderr handler and prepends
`[trace_id]` to the format string. Registers `TraceIdMiddleware`
AFTER CORSMiddleware so it runs first on the request side (Starlette
wraps in reverse registration order).
Instrumentation points
- hf_auth: `auth.save_token.{success,failure}`, `auth.delete_token.
{success,failure}`
- wifi_config: `wifi.connect.{request,busy,success,failure}`,
`wifi.forget.{success,failure}`
- central_signaling_relay: `central.relay.state` on every transition
(kept the existing human-readable line for journal compatibility)
Trace-id propagation contract
- Mobile sets `X-Trace-Id: abc1` on every daemon HTTP call (PR-A).
- The middleware copies that into the ContextVar, so any
`logger.info(...)` call inside the request handler is auto-tagged.
- WebRTC `http_proxy` will propagate the same header through the
`headers` field of the proxied request (already wired on the
mobile side; daemon-side proxy handler is on the integration
branch).
Behaviour change is additive: existing `logger.info(...)` calls keep
working, they just gain a `[trace]` prefix when called from inside a
request.
Made-with: Cursor
Adds a tiny, additive endpoint that returns the daemon's package version plus an `api_revision` marker. Mobile and remote clients use it during the handshake to detect feature mismatches (e.g. a phone shipping a new endpoint that the daemon hasn't grown yet) and warn the user rather than failing later with a cryptic 404. Why a separate `api_revision` field instead of relying on the package version: pip versions track whatever the maintainer wants (0.x bumps, marketing tags) and aren't a reliable signal for "this endpoint exists". Bumping `api_revision` whenever the HTTP surface changes gives clients a stable, monotonic boolean check. The endpoint requires no auth and no robot, so it's safe to call as the very first probe. Made-with: Cursor
Adding ManufacturerData to the advertisement on top of LocalName,
ServiceUUIDs (128-bit, 18B) and Appearance overflowed the 31-byte
legacy cap and the controller rejected the payload (`Failed to add
advertisement: <unknown status> (0xea)`), leaving the robot fully
non-discoverable over BLE.
Two changes to make the payload fit reliably:
1. Drop ServiceUUIDs and Appearance from the primary advertisement.
* ServiceUUIDs are not used client-side for filtering (the mobile
app matches on LocalName 'reachymini'); the full GATT service
tree remains discoverable once the client connects.
* Appearance was always 0x0000 ('Unknown') and adds no signal.
2. Cap manufacturer data to 2 IPv4 entries (5B each).
Two entries cover the common dual-homed case (eth0 + wlan0 or
wlan0 + hotspot). Clients that need every interface can still
query the GATT NETWORK_STATUS characteristic.
After: typical advert is Flags(3) + LocalName(12) + ManufData(14) =
~29B for two interfaces, safely under the legacy cap.
Made-with: Cursor
…, ble-wifi-provisioning, refresh-relay)
…Client can reach them The mobile app's unified RobotClient prefixes all calls with /api (so it can swap LAN HTTP for the WebRTC HTTP-proxy data channel transparently). The wifi_config router was only mounted at /wifi/* (legacy first-boot tooling contract), which made the mobile app's forgetCurrentNetwork / status calls 404. We now dual-mount it: /wifi/* stays for the BLE provisioning service, and /api/wifi/* is the canonical path for everything that talks to the daemon through RobotClient.
Adds two parallel transport channels on top of the existing WebRTC data-channel so the mobile app can talk to the daemon's FastAPI without a separate LAN HTTP/WebSocket session: - http_proxy: tunnels HTTP requests over the DC and forwards them to the daemon's loopback FastAPI port (set via set_loopback_http_port, wired from the FastAPI lifespan with --fastapi-port). - ws_proxy: multiplexes WebSocket subscriptions over the DC by stream_id (ws_open / ws_send / ws_close / ws_message / ws_closed / ws_error frames), so several long-lived subscriptions can run in parallel (e.g. /api/move/ws/updates + /api/state/ws/full). Also closes the cold-boot recovery gap for the central signaling relay: if the daemon boots without an HF token, the relay never came up. After save-token / refresh-relay we now ensure the relay is running via the new ensure_central_signaling_relay() helper, so the robot becomes visible on the central relay without a daemon restart. Made-with: Cursor
On macOS 26 ("Tahoe"), calling Gst.DeviceMonitor.stop() can segfault
inside gst_device_provider_stop -> avfdeviceprovider, which kills the
whole Python daemon with SIGSEGV before any Python traceback can be
emitted. The parent process only sees "exit code 1" with no clue.
The monitor is short-lived (one enumeration at boot per source type)
so skipping the explicit stop on darwin is acceptable: the underlying
providers are reclaimed when the Python object is garbage collected
and when the process exits.
Other platforms keep the original behavior, wrapped in a defensive
try/except so an unexpected stop() failure logs instead of propagating.
Tested on macOS 14 (Sonoma) and macOS 26 (Tahoe) with the Reachy Mini
daemon in USB mode.
Made-with: Cursor
…ating Add a first-class concept of "this robot's name" that is: - persisted to ~/.config/reachy_mini/daemon.json (XDG-compliant) so a user-chosen name survives restarts; - exposed through GET/POST /api/daemon/robot-name with server-side validation (1-32 ASCII printable chars, auto-trim) and an asyncio.Lock so concurrent renames cannot race; - live-updatable: rename propagates to the persisted config, the mDNS TXT record, and the HF central signaling relay (via setPeerStatus) without dropping any active WebRTC session. Also gate the central signaling relay on a non-default name. Until the user explicitly names their Reachy through the mobile-app onboarding (or POST /api/daemon/robot-name), the relay stays off so we don't clutter the HF fleet listing with anonymous "reachy_mini" duplicates that are impossible to tell apart. The relay starts the moment the default-to-custom rename happens, via Daemon.set_robot_name calling ensure_central_signaling_relay. Bump api_revision to "2" so the mobile app can detect this surface. Made-with: Cursor
Generate a UUID4 hex once per install, persist alongside robot_name in ~/.config/reachy_mini/daemon.json, and expose it on every discovery channel (mDNS TXT, BLE GATT, HF central setPeerStatus meta, and a new GET /api/daemon/identity endpoint). This gives the mobile app a stable reconciliation key to dedupe sightings of the same physical robot across BLE / loopback / central into a single listing row. Also drop the "robot still uses default name" gate that previously deferred central registration: with install_id, two unnamed reachy_mini instances are uniquely distinguishable on central, so the gate is no longer load-bearing and just delays the user's robot from showing up on their HF dashboard. Bumps api_revision to "3". Made-with: Cursor
The HF central server currently strips ``meta.install_id`` from the producer status it forwards, which broke client-side dedupe between a "this Mac" loopback row and the same robot's central listing. Until central propagates the full meta blob, fall back to the producer ``peerId`` central just handed back to us on the welcome frame: - ``CentralSignalingRelay`` exposes the assigned id via a new ``central_peer_id`` property + module helper ``get_central_peer_id``. - ``Daemon.status()`` refreshes ``DaemonStatus.central_peer_id`` on every read (cheap, the value rotates per relay reconnect). - ``GET /api/daemon/identity`` now returns ``central_peer_id`` next to ``install_id`` so mobile clients can dedupe central rows by either key with no extra round-trip. Made-with: Cursor
Default keeps pointing at the upstream cduss/reachy_mini_central Space, so unconfigured deployments keep working. Override the env var at service startup to redirect the SSE relay and the proxied /api/robot-status calls at a fork (test Space, staging, etc.) without patching the source. Single source of truth lives in central_signaling_relay (the relay already owned the constant); the hf_auth router imports it instead of duplicating the default URL. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…peer id
Replace the IPv4-list manufacturer data (under Pollen id 0xFFFF) with
a versioned TLV payload:
- byte 0: format version 0x02
- tag 0x01 (8 bytes): first half of install_id, stable per-install
- tag 0x02 (8 bytes): first half of central_peer_id, present only
while the relay is connected
Lets BLE-discovering clients (mobile app, desktop tray) dedupe a BLE
row against the same physical robot's loopback / central-listing row
without having to GATT-connect first. The central peer id TLV doubles
as a cheap "hotspot mode?" hint: a modern daemon advertising no peer
id is typically broadcasting its own AP and waiting for Wi-Fi setup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ive meta updates Lays down the daemon side of the lifecycle contract documented in docs/SIGNALING.md: the daemon now owns the verdict on whether meta.health is ok / degraded / error, and the central signaling relay forwards that verdict to listeners without reinterpreting it. New module daemon/peer_health.py: pure compute(DaemonStatus) -> PeerHealth so the truth table is exhaustively unit-tested (14 cases) and stays decoupled from the relay. no_backend / backend_not_ready / motor_comm / media / daemon_fatal form the stable error_code taxonomy clients branch on. CentralSignalingRelay grows three knobs all wired through start_central_relay: - health_provider callback consulted by _build_producer_meta() on every emission. Always-fresh, never cached past one setPeerStatus send. - kind (robot | tray), wireless_version, version, capabilities baked into the producer meta so clients can distinguish a tray-without- hardware from a real robot without parsing free-text. - schema_version field set to 1 - additive changes keep this at 1, bump only on breaking semantics. Two new primitives: - update_producer_meta() rebuilds the meta from the live health_provider, diffs against the last published payload, sends only on change. Cheap to call on every status tick (1 Hz today). Wrapped in synchronous notify_meta_change() for callers on the daemon's status thread. - withdraw() sends setPeerStatus(roles=[]) and keeps the WebSocket open. Distinct from stop() which closes everything. Used for transient unavailability without forcing a reconnect storm. Daemon hooks them via _publish_status: - Every status tick calls notify_meta_change() so health transitions reach central within ~1s without manual plumbing. - A _NO_HARDWARE_GRACE_SECONDS=30 watchdog evicts a desktop-tray daemon from the central listing once it has had no backend for half a minute; recovery flips it back automatically. - Graceful shutdown (_stop_central_signaling_relay) sends an explicit withdraw() before closing the relay so the mobile picker drops the row in real time instead of waiting for the central TTL. Made-with: Cursor
… field RobotBackendStatus.ready is initialised once at backend boot and never refreshed afterwards (it's a pydantic field on the static status model). The live readiness signal is the threading.Event on Backend.ready, which flips to set() once the backend has actually finished its async init. Without the override compute() always read the stale False from the model and reported error/backend_not_ready on a perfectly healthy robot, breaking the new health-aware client gating. We thread an Optional[bool] backend_ready_override through compute() and have Daemon._compute_peer_health() pass backend.ready.is_set(). The fallback to the model field stays in place so unit tests and third-party callers without a live Event keep working. Made-with: Cursor
Two complementary changes that together kill the "ghost robot in the
central listing" failure mode (most visible when forgetting an active
WiFi network: the producer used to linger for several minutes before
TTL eviction kicked in).
1. Heartbeat re-emission (central_signaling_relay.py)
`update_producer_meta()` now force-resends `setPeerStatus` once
`HEARTBEAT_INTERVAL_SECONDS` (default 20s, env-tunable) have
elapsed since the last send, even when the meta payload is byte-
for-byte identical. This is the only liveness signal central
accepts now; the matching central commit drops the unsound "touch
on SSE keepalive yield" path and depends on the daemon's
heartbeat-shaped POSTs to keep its lease alive.
- First send still triggers on the initial registration in the
`welcome` handler, which seeds `_last_published_at`.
- Meta deltas (health flips, name change) still propagate on the
next 1Hz tick, NOT gated on the heartbeat interval.
- `withdraw()` clears both `_last_published_meta` and
`_last_published_at` so a future re-registration fully resends.
2. Cooperative withdraw on `/wifi/forget*` (wifi_config.py)
When the user forgets the currently-active WiFi network, the
daemon will lose internet within a few seconds. We now call
`notify_withdraw(timeout=1.0)` BEFORE wiping the connection, while
we still have connectivity to central. Result: the ghost row
disappears from the mobile app's "Hugging Face central" list
instantly instead of after ~55s of TTL.
Best-effort: any failure (slow central, partial network) just
falls back on the new TTL flow.
Same logic added to `/wifi/forget_all` when at least one
non-Hotspot connection is currently active.
Tests:
- 7 new unit tests for the heartbeat behaviour
(`test_central_signaling_heartbeat.py`).
- `docs/SIGNALING.md` rewritten around the new model: explicit
heartbeat protocol, why server-pushed keepalives can't be liveness,
cooperative-withdraw subsection.
Made-with: Cursor
…advert * WIFI_PROBE: new public BLE command returning a JSON snapshot of wlan/gateway/dns/internet/daemon reachability for fast handshake diagnostics. Runs the 4 probes in parallel under a strict total budget so a stuck DNS resolver can't block the call. * network_mode TLV (tag 0x03, 1 byte enum OFFLINE/HOTSPOT/CONNECTED) is appended to the BLE advertisement on each refresh tick. Mobile scanners use it to render an authoritative "Setup pending" badge without false positives from transient relay flaps. Made-with: Cursor
Removes the three `wireless_version` gates that prevented a lite daemon (e.g. desktop tray piloting a USB Reachy Mini) from joining the central signaling relay. The relay code path is already lite-friendly (it tags the producer with `kind="tray"` vs `"robot"`) - the only blocker was these legacy "Coming soon to Lite version" checks in the HTTP layer: - save-token: ensure_central_signaling_relay() now runs after any successful token push, not only on wireless. - relay-status: delegates to the relay state machine instead of short-circuiting on lite. Real status (CONNECTED / WAITING_FOR_TOKEN / unavailable) is the source of truth. - refresh-relay: cold-boot recovery path no longer skips lite. This unlocks remote WebRTC access to a tray-piloted Reachy from a phone over the internet, matching the wireless robot's discovery flow. Made-with: Cursor
The default fork lives at https://tfrere-reachy-mini-central.hf.space. Daemons that booted with no explicit env override were silently publishing to the upstream cduss instance while the mobile app polled the tfrere fork; the two stores never converged so robots stayed invisible from the app. Aligning the daemon default and the JS SDK default removes the need for ad-hoc REACHY_CENTRAL_URL exports on every robot install, and keeps the SDK docs and runtime in sync. Production overrides via REACHY_CENTRAL_URL still take precedence. Made-with: Cursor
The daemon now reads ``recommended_heartbeat_interval_seconds`` from the ``welcome`` SSE frame and uses that as its actual heartbeat interval (clamped to [1.0s, 60.0s] as a defence against malformed welcomes). The module-level ``HEARTBEAT_INTERVAL_SECONDS`` becomes the fallback used only before the first welcome and against older central versions that do not advertise the field. Fully backwards compatible. Default lowered from 20s to 5s to pair with the central's new 15s lease (LEASE/3 ratio gives ~2 missed heartbeats of headroom). The combined effect shrinks the worst-case staleness window observed by remote clients (mobile/desktop too far for BLE) from ~55s to ~18s. Operationally: tuning ``LEASE_SECONDS`` server-side is now the only knob needed; every daemon auto-aligns on its next reconnect with zero coordinated redeploy. Observability: - ``get_relay_status()`` exposes the live ``heartbeat_interval_seconds`` and ``central_lease_seconds`` so operators can confirm the contract the daemon is currently honouring. - Negotiated values are logged on each welcome. Tests cover override, fallback, clamping, non-numeric defence, and end-to-end re-emit window driven by the negotiated interval. Docs: docs/SIGNALING.md updated with the negotiation flow and observability cheat sheet for client implementers. Made-with: Cursor
Every GATT characteristic exposed by the daemon (commands, responses, install_id, network status) is intentionally unencrypted because the information they carry is also broadcast in the BLE advertisement manufacturer data. There is therefore no security benefit in having the adapter accept SMP pairing requests, and the only user-visible side-effect of being pairable was an iOS / Android "Pair this accessory?" prompt the first time the mobile app connected. Drop the NoInputNoOutput Just Works agent registration and set the adapter to Pairable=False so the mobile OS no longer surfaces a pairing dialog. The NoInputAgent class is kept in the module for reference in case a future encrypted-write characteristic warrants re-enabling bonding. Also document the libnice session-reuse abort observed on the central / Wi-Fi WebRTC path under docs/known-issues/libnice-session-reuse-crash.md so the diagnostic work is not lost while we deal with it later. Made-with: Cursor
Contributor
Author
PR branch coverage vs
|
| PR | Branch | On PR not in dev | On dev not in PR | PR tip ancestor of dev? | Note |
|---|---|---|---|---|---|
| #1029 | feat/ble-advertise-network-ip |
0 | 79 | yes | Superseded by TLV advert + network_mode; safe to close. |
| #1038 | fix/macos-gst-device-monitor-segfault |
1 | 81 | no | Same fix as 35a188a8 on dev (duplicate SHA vs b7ce45df on PR branch). PR branch stale. |
| #1045 | feat/ble-wifi-provisioning |
19 | 35 | no | PR branch polluted with unrelated merges (main, JS, mobile-app-integration-light). Content is in dev; do not merge PR branch as-is. |
| #1046 | feat/hf-token-endpoint |
0 | 38 | yes | Fully contained; keep PR only as upstream slice or close as superseded by #1070. |
| #1047 | feat/hf-auth-refresh-relay |
0 | 34 | yes | Fully contained. |
| #1048 | feat/webrtc-http-proxy |
23 | 24 | no | PR branch has unrelated merges; http_proxy landed on dev via e5712012. Prefer #1070 or cherry-pick. |
| #1049 | integration/mobile-app-daemon |
0 | 13 | yes | Umbrella; dev adds 13 commits on top. |
| #1050 | feat/structured-logging |
0 | 36 | yes | Fully contained. |
| #1051 | feat/wifi-forget-http-endpoint |
1 | 38 | no | Tip diverges only because 252c7e06 is a merge commit not replayed on dev; symmetric diff shows dev ahead (forget + tests + relay). PR branch is stale; safe to close once #1070 lands. |
| #1052 | feat/daemon-version-endpoint |
0 | 37 | yes | Fully contained. |
Commands per open child PR (copy-paste)
git fetch origin
DEV=dev/mobile-app-integration
for b in feat/ble-advertise-network-ip fix/macos-gst-device-monitor-segfault feat/ble-wifi-provisioning \
feat/hf-token-endpoint feat/hf-auth-refresh-relay feat/webrtc-http-proxy \
integration/mobile-app-daemon feat/structured-logging feat/wifi-forget-http-endpoint \
feat/daemon-version-endpoint; do
echo "=== $b ==="
git rev-list --count "$DEV..origin/$b"
git rev-list --count "origin/$b..$DEV"
git merge-base --is-ancestor "origin/$b" "$DEV" && echo ancestor || echo diverged
done
This was referenced May 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Draft integration branch for the Reachy Mini mobile app daemon work. This is the line we use day-to-day locally and on robots; it supersedes the older umbrella branch
integration/mobile-app-daemonin breadth (extra signaling, health, BLE TLV, central URL default, tray-on-central, etc.).Not intended for merge as-is without review split — this PR exists so Pollen can see the full diff, run CI, and decide how to land it (cherry-picks vs smaller PRs).
Scope (high level)
http_proxy/ws_proxyfor unified transportWIFI_PROBE, TLV advert (install_id,central_peer_id,network_mode), pairing disabled on adapterREACHY_CENTRAL_URLalignmentinstall_id,/api/daemon/identity, macOS GStreamer DeviceMonitor guarddocs/known-issues/libnice-session-reuse-crash.mdRelated
integration/mobile-app-daemon) — child PRs there are subsets of this line.Notes
dev/mobile-app-integration(pushed onorigin).mainwill be needed before any non-draft merge (branch is behindmainby some commits).Made with Cursor