Skip to content

Runstar R6: derive body composition from the reported impedance - #1535

Merged
oliexdev merged 1 commit into
oliexdev:masterfrom
ldoench:feat/runstar-r6-body-composition
Sep 21, 2026
Merged

oliexdev merged 1 commit into
oliexdev:masterfrom
ldoench:feat/runstar-r6-body-composition

Conversation

@ldoench

@ldoench ldoench commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

The Runstar R6 reports whole-body impedance in its 0xA3 result frame, but
the handler added in #1432 only stored the raw value. Since the IMPEDANCE
measurement type is internal and disabled, and DerivedValuesCalculator
does not read it, users saw weight, BMI and heart rate and nothing else.

This feeds the impedance into StandardImpedanceLib to derive body fat,
water, skeletal muscle, bone mass, LBM and BMR, following the pattern
VitafitVT701Handler and EtekcityESF551Handler already use for their
weight-plus-impedance-only scales.

Measured impedance on a physical R6 is 533-605 Ohm across eight weigh-ins,
within the range StandardImpedanceLib documents its formulas for. Readings
outside 200-1200 Ohm, and users with no body height set, are skipped with
a warning rather than producing made-up values; the raw impedance is
stored regardless.

Also fixes two robustness gaps in the same handler:

  • 0xA4 history timestamps were used unchecked, so an unset scale clock
    produced measurements dated 1970. Now guarded like AfuB1Handler and
    YunmaiHandler, falling back to the current time rather than dropping
    the entry — acking retires it on the scale, so a rejected reading
    cannot be recovered.
  • The documented 0xA3 status byte was never read. A non-zero status is
    now logged rather than used to discard the weigh-in; its values have
    never been observed in any capture.

Verification:

  • New RunstarR6HandlerTest covers the 0xA3 decoder and the checksum
    against hand-built frames, including malformed, wrong-type,
    wrong-length and non-zero-status input.
  • One test decodes a frame captured verbatim from a physical R6
    (F3 08 00 A3 00 01 03 2E 4B 02 45 ... -> 66.35 kg, 75 bpm, 581 Ohm).
  • Verified end-to-end on the device: body composition now appears in the
    app after a weigh-in.

🤖 Generated with Claude Code

The R6 reports whole-body impedance in its 0xA3 result frame, but the
handler only stored the raw value, so openScale showed weight, BMI and
heart rate and nothing else. Feed the impedance into StandardImpedanceLib
to derive fat, water, skeletal muscle, bone mass, LBM and BMR, the same
way VitafitVT701Handler and EtekcityESF551Handler do for their
weight-plus-impedance-only scales.

Readings outside 200-1200 Ohm, and users with no body height set, are
skipped with a warning instead of being turned into fabricated numbers.
The raw impedance is stored either way, so a later recompute stays
possible.

Two robustness gaps in the same frame handlers, for consistency with the
rest of the package:

- 0xA4 history timestamps came off the scale's own clock and were used
  unchecked, so an unset clock produced a measurement dated 1970. Guard
  them like AfuB1Handler and YunmaiHandler do, falling back to the
  current time. Rejecting the entry outright, as MiScaleHandler does, is
  wrong here: acking retires it on the scale, so a rejected reading is
  lost for good.
- The 0xA3 status byte was documented but never read. Log a non-zero
  status instead of discarding the weigh-in, since its values have never
  been observed and guessing at them could drop valid measurements.

The 0xA3 field extraction moves into a pure decodeFinalResult() in the
companion object so it can be unit tested without a BLE stack, mirroring
VitafitVT701Handler.parseStableWeightRaw. Also drops the write-only
lastLockedWeightKg field.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@oliexdev
oliexdev merged commit d9e3ec1 into oliexdev:master Sep 21, 2026
2 of 3 checks passed
@oliexdev

Copy link
Copy Markdown
Owner

Thanks for your PR 👍

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.

2 participants