From ffd846069b52e876be541b80b6e5115e462bd1ca Mon Sep 17 00:00:00 2001 From: David Riedel <48388362+Riedel-David@users.noreply.github.com> Date: Thu, 22 Oct 2026 00:20:36 +0300 Subject: [PATCH 1/3] Mi Body Composition Scale 2: pin the real 7-byte history count reply The XMTZC05HM answers the history request with 7 bytes, not 3. The composition history test now sends that reply, so it fails on a protocol that cannot read it. Mark the composition scale as verified on hardware; the Mi Smart Scale 2 stays experimental. Needs the openstrap_protocol pin bumped to a commit containing OpenStrap/protocol#81. --- lib/ble/adapters/_registry.dart | 4 ++-- lib/ble/adapters/miscale.dart | 4 +++- test/adapters/miscale_test.dart | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/ble/adapters/_registry.dart b/lib/ble/adapters/_registry.dart index beeef00f0..b7f7c3e29 100644 --- a/lib/ble/adapters/_registry.dart +++ b/lib/ble/adapters/_registry.dart @@ -979,8 +979,8 @@ bool _looksLikeThermometer(String lowercaseName) => /// OS-level service filter does not match, so it is also named there /// ([BandEntry.scanServiceData]). /// -/// EXPERIMENTAL (ASSUMPTIONS R6). Weight becomes a `weight` observation and -/// impedance a vendor observation. With its developer flag on (default off) +/// Verified on a physical Mi Body Composition Scale 2 (XMTZC05HM). Weight +/// becomes a `weight` observation and impedance a vendor observation. With its developer flag on (default off) /// the newest weight within 10% of the profile's becomes it /// (`newestWeighing`); impedance /// stays a measurement only. diff --git a/lib/ble/adapters/miscale.dart b/lib/ble/adapters/miscale.dart index 74e19e1fa..201475457 100644 --- a/lib/ble/adapters/miscale.dart +++ b/lib/ble/adapters/miscale.dart @@ -23,7 +23,9 @@ // impedance are shown attributed; with the scale's flag on, a weighing near // the profile's weight becomes it (`measurement_inputs.dart`). // -// EXPERIMENTAL (ASSUMPTIONS R6): nobody on this project owns one. +// The Mi Body Composition Scale 2 (XMTZC05HM) is verified on a physical unit: +// pairing, stored history and live weight with impedance. The Mi Smart Scale 2 +// is still EXPERIMENTAL (ASSUMPTIONS R6): nobody on this project owns one. import 'dart:async'; import 'dart:typed_data'; diff --git a/test/adapters/miscale_test.dart b/test/adapters/miscale_test.dart index 9ef504b39..1df7cab6a 100644 --- a/test/adapters/miscale_test.dart +++ b/test/adapters/miscale_test.dart @@ -151,7 +151,9 @@ void main() { final (rows, link) = await _drive( kMiScaleComposition, reply: (u, v) => switch ((u == kMiScaleHistoryChar, v.first)) { - (true, 0x01) => [(kMiScaleHistoryChar, [0x01, 2, 0])], + // The XMTZC05HM's real count reply is 7 bytes (captured on hardware); + // weights and stamps below are made up. + (true, 0x01) => [(kMiScaleHistoryChar, [0x01, 2, 0, 1, 0, 0, 0])], (true, 0x02) => [ (kMiScaleHistoryChar, [ ..._bc(0x22, 480, 14000, DateTime.utc(2026, 10, 2, 7)), From 876d6b5e9f61f5e87f90fdab70d7c88e7d1ff959 Mon Sep 17 00:00:00 2001 From: David Riedel <48388362+Riedel-David@users.noreply.github.com> Date: Sun, 1 Nov 2026 13:43:25 +0200 Subject: [PATCH 2/3] Pin openstrap_protocol to the #81 merge commit --- pubspec.lock | 4 ++-- pubspec.yaml | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index eee7705f5..ea031e64d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -946,8 +946,8 @@ packages: dependency: "direct main" description: path: "." - ref: "89080bcfac7767b3a871fd3a7d9307f5f30dad26" - resolved-ref: "89080bcfac7767b3a871fd3a7d9307f5f30dad26" + ref: "b4fde6ab5589e53154b808f8b2bbb4d01d9c15b5" + resolved-ref: "b4fde6ab5589e53154b808f8b2bbb4d01d9c15b5" url: "https://github.com/OpenStrap/protocol.git" source: git version: "1.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index c20440bfd..ce731c1f6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -262,7 +262,13 @@ dependencies: # REPIN: protocol feat/multidevice-verified-decoders head (multi-device # decoders; WHOOP decode untouched, pinned by whoop_freeze_golden_test). # Re-point at the merge commit once it lands on protocol main. - ref: 89080bcfac7767b3a871fd3a7d9307f5f30dad26 + # REPIN: protocol main @ b4fde6a (#81: Mi Body Composition Scale 2 answers + # the history request with a 7-byte count). The range from 89080bc is 41 + # commits: #81, the AGPL license/docs (#80), and #82 decoder-hardening + # (R10 gated on packet type, hello/battery parsing, reassembler + # truncation semantics, removal of deprecated gen5 APIs). #81 sits on top + # of #82, so it cannot be pinned alone. + ref: b4fde6ab5589e53154b808f8b2bbb4d01d9c15b5 openstrap_analytics: git: url: https://github.com/OpenStrap/analytics.git From b4c3a2fe8bdadd74caceb1239e74b755907a38e3 Mon Sep 17 00:00:00 2001 From: Mohammad Abdul Sahil <127765312+abdulsaheel@users.noreply.github.com> Date: Mon, 2 Nov 2026 19:28:17 +0530 Subject: [PATCH 3/3] kProtocolPin follows the b4fde6a repin --- lib/compute/derivation_engine.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/compute/derivation_engine.dart b/lib/compute/derivation_engine.dart index 2610e7726..a304e800f 100644 --- a/lib/compute/derivation_engine.dart +++ b/lib/compute/derivation_engine.dart @@ -2077,7 +2077,10 @@ const String kAnalyticsPin = '3a7ca34d30233ff11e6c2877083f319a907ccb31'; // main merge commit. Re-pointed at that branch's Huami gap fix (minutes() // empty after a counter gap); NO kAlgoVersion bump: the adapter already // decoded only gap-free transfers, so no output changes. -const String kProtocolPin = '89080bcfac7767b3a871fd3a7d9307f5f30dad26'; +// REPIN: protocol main @ b4fde6a (#81 Mi scale 7-byte history count, on top +// of #82 decoder hardening). NO kAlgoVersion bump: decode runs at ingest, so a +// bump re-derives nothing differently; WHOOP output unchanged (freeze goldens). +const String kProtocolPin = 'b4fde6ab5589e53154b808f8b2bbb4d01d9c15b5'; // Fold idempotency, the minimum-nights warm-up, and legacy-payload handling // all live in SleepProfilePolicy (pure, unit-tested) — see