Skip to content

MiScaleHandler: derive the v1/v2 variant from the GATT table instead of trusting the scan-time name heuristic - #1490

Open
l9i wants to merge 4 commits into
oliexdev:masterfrom
l9i:miscale-variant-detection
Open

l9i wants to merge 4 commits into
oliexdev:masterfrom
l9i:miscale-variant-detection

Conversation

@l9i

@l9i l9i commented Aug 26, 2026

Copy link
Copy Markdown

The weight-only Mi Smart Scale 2 (XMTZC04HM) advertises "MI SCALE2" and even carries the 0x1530 vendor service, so the scan-time heuristic classifies it as v2. Its GATT table, however, only serves the Weight Scale service (0x181D) — there is no Body Composition service (0x181B). The v2 init sequence then subscribes/writes against a non-existent service and the app hangs forever in "waiting for measurement".

Changes:

  • Connect-time variant detection. After service discovery the variant is derived from the actual GATT table, probing for the Mi history characteristic (vendor 0x2A2F) under 0x181B (→ v2) and 0x181D (→ v1). This corrects the misclassified XMTZC04HM to v1 and it inits against 0x181D.
  • Clones keep working. Devices that omit the vendor history characteristic but still serve a standard service fall back to service-presence detection (their live weight arrives via the standard 0x2A9D characteristic). Only a device exposing neither 0x181B nor 0x181D aborts with a user-visible error and a disconnect, instead of hanging.
  • supportFor() is now side-effect-free. The handler is a shared singleton and the UI re-queries device support while a session is live; previously such a query could reset the GATT-corrected variant mid-session and silently drop live v1 frames.
  • Detected variant is remembered. The GATT-detected variant is persisted per device address and consulted by scan-time support queries, so a downgraded device stops advertising v2-only capabilities (body composition, unit config) it cannot deliver.
  • Tests. MiScaleHandlerVariantTest covers scan-time classification, the XMTZC04HM downgrade, genuine v2, the clone fallback (a live 10-byte weight frame is parsed end-to-end), a mid-session supportFor() query not clobbering the session variant, the persisted-variant override, and the neither-service abort. Timer-dependent tests run on runTest virtual time instead of racing the real 1-second history-fallback timer.
    65 test classes • 544 tests • 0 failures • 0 errors • 0 skipped
      └ MiScaleHandlerVariantTest: 7 tests, all passed
    

Fixes #1489.

l9i and others added 2 commits August 26, 2026 09:02
Re-detect the Mi scale variant on connect by probing for the history
characteristic under 0x181B (V2) / 0x181D (V1), correcting the ambiguous
scan-time name heuristic. The weight-only Mi Smart Scale 2 (XMTZC04HM)
advertises "MI SCALE2" and carries the 0x1530 vendor service but serves
only 0x181D, so V2 mode subscribed/wrote against a non-existent service
and hung in "waiting for measurement" forever.

Fail fast with a user-visible error + disconnect when the history
characteristic exists under neither service. Adds MiScaleHandlerVariantTest.

Fixes oliexdev#1489

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to e77aa89, fixing the issues found in review:

- supportFor() no longer mutates the session variant field. The handler
  is a shared singleton and the UI re-queries device support at any time
  (savedDeviceSupport, scan results), which could silently reset a
  GATT-corrected variant mid-session and drop live v1 frames.
- detectVariantFromGatt() no longer hard-rejects clones that serve a
  standard service without the vendor history characteristic (live
  weight via 0x2A9D worked for them before the fail-fast). Service
  presence acts as a fallback tier; only a device exposing neither
  0x181B nor 0x181D aborts, which is also exactly what the user-facing
  error string claims.
- The GATT-detected variant is persisted per device address so later
  scan-time support queries stop promising v2 capabilities (body
  composition, unit config) a downgraded device cannot deliver.
- Variant tests now run on runTest virtual time instead of racing the
  real 1-second history-fallback timer, and cancel it via
  handleDisconnected(); new regression tests cover the clone fallback,
  the mid-session supportFor() query, and the persisted override.

All changes stay within MiScaleHandler and its test, per the
how-to-support-a-new-scale ground rules (no shared infrastructure
changes).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HkiN1TxugYUaxWx2HbzhbD
@l9i l9i changed the title MiScaleHandler: detect v1/v2 variant from GATT table, fail fast when neither primary service exists MiScaleHandler: derive the v1/v2 variant from the GATT table instead of trusting the scan-time name heuristic Aug 26, 2026
- Refine variant determination to rely only on GATT table reads during connection, discarding scan-time heuristics
- Add warning to the UI when a device advertises v2 capabilities but is detected as v1 (weight only)
- Remove persisted variant setting/retrieval logic for cleaner state management
- Update resource strings to reflect the warning about degraded variant capability
@oliexdev

Copy link
Copy Markdown
Owner

Not merging yet — please retest on hardware first. I made these changes on top:

Removed the persisted variant. It doesn't work as intended: settings is lateinit and only attached in ModernScaleAdapter.init, i.e. after supportFor has already returned, so the first scan of a session always hits the runCatching and reads nothing. From the second session on it puts a runBlocking DataStore read with a 300 ms timeout into supportFor, which runs per scan result. Dropped persistedVariantFor, rememberDetectedVariant and the corresponding test.

Replaced it with a warning on connect. The device list keeps showing the scan-time guess, so a downgraded scale is still listed as v2. Instead of correcting the list, onConnected now says it once when the advertised name promises v2 but the GATT table is v1 — new string bt_warn_mi_scale_variant_downgraded, as userWarn since the measurement still works.

Shortened bt_error_mi_scale_service_missing to match bt_error_omron_service_missing. The UUIDs stay in the log, they don't belong in a user-facing dialog.

Kept as is: the GATT-table detection, the clone fallback tier, the fail-fast, and the side-effect-free supportFor.

Please verify with the XMTZC04HM (weight arrives, warning shown once) then I'll merge.

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.

Mi Scale v2 driver hangs in "waiting for notification" on weight-only Mi Smart Scale 2 (XMTZC04HM)

3 participants