Boards broadcast their MAC — remembered devices resolve across hosts without connecting#11
Merged
Conversation
A remembered board synced through iCloud was a stranger on the user's other Apple devices until connected once per host: peripheral UUIDs are generated per host, the MAC is only readable over a connection, and — verified over the air on fw 1.7.x — stock firmware does NOT broadcast the MAC despite the API docs suggesting otherwise. So the app teaches boards to identify themselves: - MWMACAdvertisement (new): assembles the scan-response payload — a Complete Local Name AD structure (keeps the board discoverable; the scanner filters on the "MetaWear" prefix) plus a manufacturer-data AD structure under MbientLab's company identifier 0x626D carrying the 6 MAC bytes LSB-first — and parses the same structure back out of CBAdvertisementDataManufacturerDataKey bytes. - MetaWearDevice.enableMACAdvertisement(): records the scan response as an on-boot macro (persists across power cycles) AND applies it live, since macro recording stores commands without executing them. Builds on the existing SetScanResponse chunking and MWMacro machinery. - MetaWearScanner.advertisedMACs: parsed MACs per peripheral, populated passively during scanning. - App: rememberDevice configures each board once (pure decision gate, unit-tested: only when a FRESH advertisement was observed WITHOUT a MAC and this host hasn't configured it — on-boot macros consume finite flash slots, so configuration must never stack duplicates). localPeripheralUUID(for:) now resolves remembered records against live advertisements, so a board configured by the iPhone matches its Nearby twin on the Mac with no connection ever made there. Known limitation: the broadcast name is frozen at configuration time; a later rename won't update it until reconfiguration (documented on enableMACAdvertisement). Includes a hardware test (MetaWearHardwareTests) that configures a real board, then verifies the advertised MAC over the air matches the connection-read MAC and the name survived — cleaning up its macro so the board reverts on next power cycle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Field report from the Mac: "the ID 0665D988… occurs multiple times
within the collection" — two RememberedDevice rows with the same
peripheralUUID. CloudKit forbids unique constraints, and its sync-state
resets ("Change Token Expired" recovery, observed in the same log after
tonight's Development-database surgery) can re-import server rows next
to their local twins. The connect-time reconciler folds pairs it
touches, but nothing swept duplicates that already existed — and the
scan list's ForEach was keyed on peripheralUUID, which is undefined
under duplicates.
- refreshRememberedDevices now dedupes on every pass: rows sharing a
MAC address or a peripheralUUID fold into one keeper (newest
lastConnected, deterministic tie-break so every host deletes the SAME
losers and the fold converges), merging fields the keeper lacks.
Deletions sync, so one host's sweep cleans every host.
- The Remembered list is keyed by SwiftData identity so rendering stays
defined in the window before a sweep runs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A board that powers off, dies, or connects to another phone stops advertising — but the scanner's discovery cache is append-only and the Nearby list never re-evaluated silence, so the board kept rendering as present and connectable with a frozen RSSI. Worse, silence produces no observation updates, so even a freshness check at render time would never re-run once advertisements stopped. - DeviceFreshness: the shared 8 s advertisement-freshness rule, now used by BOTH the Remembered rows' status and the Nearby filter (previously only Remembered applied it). - The scan screen renders inside a 1 s TimelineView so the passage of time itself re-evaluates freshness: silent boards drop out of Nearby within the window, and a remembered board that dies flips to Offline instead of freezing on "available". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
App Store Connect requires a unique build number per upload; 10.0 (1) is already on TestFlight. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Fixes the remaining cross-device UX gaps found during two-device field testing. Three commits:
1. Boards broadcast their MAC (
f4a1944)Empirically established first: stock fw 1.7.x advertisements carry no MAC (only name + service UUID), despite docs suggesting otherwise — which is why no MbientLab SDK ever parsed one. The firmware capability is the Settings scan-response registers; the app now uses them:
MetaWearDevice.enableMACAdvertisement()— scan-response payload (name AD + manufacturer AD: company0x626D+ MAC LSB-first), recorded as an on-boot macro and applied live. Rides the existingSetScanResponsechunking +MWMacromachinery.MetaWearScanner.advertisedMACs— passive parsing during scanning.localPeripheralUUID(for:)resolves remembered records against live advertisements. A board connected once anywhere is recognized everywhere — no per-host connect.Known limitation: the broadcast name freezes at configuration; renames need reconfiguration (documented).
2. Duplicate remembered rows folded (
0f171c2)Field log showed
ForEach: ID … occurs multiple times— CloudKit sync-state resets ("Change Token Expired") resurrect server rows next to local twins (no unique constraints possible).refreshRememberedDevicesnow dedupes every pass with a deterministic keeper (all hosts delete the same losers → converges), merging missing fields; the list is keyed by SwiftData identity so rendering stays defined pre-sweep.3. Silent boards stop looking connectable (
0bb0ce9)A powered-off / connected-elsewhere board kept rendering in Nearby with frozen RSSI: the discovery cache is append-only, and silence produces no observation updates to re-render on. Shared
DeviceFreshnessrule (8 s) now filters Nearby AND drives Remembered status, evaluated inside a 1 sTimelineViewso time itself re-evaluates presence.Validation
MACAdvertisementHardwareTests(configure → verify advertised MAC over the air → clean up).🤖 Generated with Claude Code