Skip to content

Boards broadcast their MAC — remembered devices resolve across hosts without connecting#11

Merged
lkasso merged 4 commits into
mainfrom
mac-self-identification
Jul 7, 2026
Merged

Boards broadcast their MAC — remembered devices resolve across hosts without connecting#11
lkasso merged 4 commits into
mainfrom
mac-self-identification

Conversation

@lkasso

@lkasso lkasso commented Jul 6, 2026

Copy link
Copy Markdown
Member

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: company 0x626D + MAC LSB-first), recorded as an on-boot macro and applied live. Rides the existing SetScanResponse chunking + MWMacro machinery.
  • MetaWearScanner.advertisedMACs — passive parsing during scanning.
  • App — one-time configuration on connect behind a unit-tested gate (never stacks duplicate macros); 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). refreshRememberedDevices now 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 DeviceFreshness rule (8 s) now filters Nearby AND drives Remembered status, evaluated inside a 1 s TimelineView so time itself re-evaluates presence.

Validation

  • 1040/1040 SPM tests; app suite green (22 new tests across payload/parser byte vectors — including a real captured MAC — configuration gate, dedupe, freshness boundaries).
  • Hardware test target gained MACAdvertisementHardwareTests (configure → verify advertised MAC over the air → clean up).
  • Two-device field test (iPhone + Mac, real MMS/MMR): all phases pass — passive cross-host recognition with no Mac-side connect, one row per physical board, silent boards drop within ~8 s.

🤖 Generated with Claude Code

lkasso and others added 4 commits July 5, 2026 22:03
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>
@lkasso lkasso merged commit ec9f5f6 into main Jul 7, 2026
5 checks passed
@lkasso lkasso deleted the mac-self-identification branch July 7, 2026 00:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant