Skip to content

feat: add support for Taylor 5331891 #1393

Open
chropic wants to merge 2 commits into
oliexdev:masterfrom
chropic:master
Open

feat: add support for Taylor 5331891 #1393
chropic wants to merge 2 commits into
oliexdev:masterfrom
chropic:master

Conversation

@chropic

@chropic chropic commented Jun 9, 2026

Copy link
Copy Markdown

Add support for Taylor 5331891 BIA Scale

Scale

Taylor Body Composition Bathroom Scale (model 5331891) — shows up on Bluetooth as 5331891 BIA Scale.

Summary

This PR adds a dedicated TaylorBIAHandler. Because this scale uses the same Bluetooth service (0xFFB0) as the MGB scale family, our app used to accidentally let the MGBHandler claim it. That caused the connection to time out without getting a measurement, as Taylor's data format is entirely different.

What works: Live and stable weight (tested against real measurements).
Not implemented: Body composition (explained below).

Protocol (reverse-engineered)

  • Communicates over service 0xFFB0.
  • Requires a quick initial handshake to tell the scale to start streaming data.
  • Data arrives in 20-byte frames. The weight is extracted using this formula:
    weight_kg = (((chan & 0x01) << 16) | (hi << 8) | lo) / 1000.0
  • Verified decodes: 80 8D ... → 68.200 kg = 150.4 lb; 80 8D ... → 69.500 kg = 153.2 lb (which perfectly matches the official app's readout).

Publishing the final reading

Most of the time, the scale doesn't send a specific "done" signal, it just continuously streams live weight values until the user stands still and the numbers plateau. To handle this, I added a stability check: once the weight stays exactly the same for 4 readings in a row (about 1 second), it's locked and published. The handler will still honor explicit "stable" signals if they ever arrive, and there's a fallback timer just in case the weight never fully settles.

No body composition

The scale only ever sends weight, there is no body impedance data sent over BLE. The official Precision Hub app "calculates" body fat, water, and muscle by itself, which probably means that the stats are bullshit anyway. Since there's no access to those formulas or the raw impedance data over Bluetooth, body comp cannot be reproduced here.

Changes

  • Added a new handler: core/bluetooth/scales/TaylorBIAHandler.kt.
  • Updated ScaleFactory.kt to check for the Taylor scale before the MGB scale, ensuring the right handler picks it up first.
  • Added unit tests in TaylorBIAHandlerTest.kt to verify the weight decoding logic (including idle states and bit-boundary cases).

Testing

  • All unit tests pass locally.
  • Tested on a physical device (Pixel 10 Pro, Android 16): The app now correctly identifies the scale (instead of calling it "SWAN / Icomon (MGB)"), connects smoothly, and locks in the weight using the stability check just like the official app.

AI Disclaimer

  • I used generative AI (Claude) to help write this solution.

chropic and others added 2 commits June 8, 2026 16:40
Adds TaylorBIAHandler for the Taylor Body Composition Bathroom Scale
(advertised as "5331891 BIA Scale").

The scale exposes service 0xFFB0 (FFB1 write, FFB2 notify), the same GATT
layout as the MGB family, so MGBHandler previously mis-claimed it and the
session timed out with no measurement because the notification format differs.

Protocol (reverse-engineered from btsnoop HCI logs):
- 20-byte NOTIFY frames "AC 27 <flag> <chan> <hi> <lo> ... 24 D5 <cksum>".
- weight_kg = (((chan & 0x01) << 16) | (hi << 8) | lo) / 1000.0, where the
  channel byte's low bit is weight bit 16 (0x8C < 65.536 kg, 0x8D >= 65.536 kg).
- flag 0x80 = stable, 0x01 = summary record. Verified against two captures
  (171.2 lb and 173.0 lb readings).

The scale transmits weight only; body composition is computed app-side by the
vendor and is not exposed over BLE, so only LIVE_WEIGHT_STREAM is implemented.

TaylorBIAHandler is registered ahead of MGBHandler in ScaleFactory (first
non-null supportFor wins). Includes unit tests for the weight decode.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Under openScale's minimal init the scale never sends an explicit stable
(0x80) or summary (0x01) frame; it streams live values that plateau. The
previous code published only via a flat fallback timer, which could capture
a still-settling value.

Publish instead when the same weight repeats STABLE_FRAMES (4) times in a
row (~1s at the scale's ~4Hz stream) — the settled value repeats verbatim
while step-on ramp values are all distinct. Explicit stable/summary frames
are still honored, and a last-resort timer (FALLBACK_DELAY_MS) covers weight
that never settles. Verified on device: records 77.65 kg = 171.2 lb.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chropic

chropic commented Jun 9, 2026

Copy link
Copy Markdown
Author

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