Skip to content

Adds support for the Beurer BF1000 Super Precision. - #1481

Closed
Lidara wants to merge 3 commits into
oliexdev:masterfrom
Lidara:Beurer-BF1000
Closed

Adds support for the Beurer BF1000 Super Precision.#1481
Lidara wants to merge 3 commits into
oliexdev:masterfrom
Lidara:Beurer-BF1000

Conversation

@Lidara

@Lidara Lidara commented Aug 21, 2026

Copy link
Copy Markdown

Adds support for the Beurer BF1000 Super Precision.

The BF1000 uses the existing Beurer/Sanitas connection flow, but reports additional segmental body composition values. This PR adds those measurement types, wires them through persistence, and decodes the BF1000 measurement packets.

Changes:
Add measurement types for segmental body fat and muscle:

  • left arm
  • right arm
  • torso
  • left leg
  • right leg

Add migration/default rows for the new measurement types.
Persist segmental values from Bluetooth scale measurements.
Add BF1000 model detection to the Beurer/Sanitas handler.
Decode BF1000 standard weight/body-composition packets and private segmental packets.
Add captured-payload tests for the BF1000 parser.
Add BF1000 to the scale catalog and README list.

A few parts may be worth a closer look:
The BF1000 sends BMR in the body-composition packet. This PR stores that value instead of letting openScale calculate BMR for that measurement. If the preferred behavior is to ignore scale-provided BMR and keep openScale’s derived value, I can change that.
Bone mass is not sent as a direct field in the packet. The scale does send body weight, body fat, and soft lean mass; using those values gives the bone mass shown on the scale display in the captures. If this should not be stored unless it is sent directly, I can remove it.
The segmental values are added as normal measurement types and are enabled by default. If these should be disabled by default, internal, or grouped differently, I’m happy to adjust that.
The BF1000 private characteristics used here are based on captures from the device: 0006, 0009, and 000A. No official app traffic was used. If there is a preferred way to document these mappings, I can add that.

Open to changing any of the above if there is a better fit for openScale’s data model or review preferences.

Thanks for the App.

Cheers, Lidara

Lidara added 3 commits August 21, 2026 23:18
Add segmental fat and muscle MeasurementType keys, labels, defaults, and migration rows so fresh and upgraded installs can store BF1000 segmental readings.

Extend demo data, broad percentage plausibility checks, schema export, and migration coverage for the new types.
Add segmental fat and muscle fields to ScaleMeasurement, merge them across multi-packet readings, persist them with the existing BleConnector conversion path, and map stored values back into ScaleMeasurement.
Detect the BF1000 in the existing Beurer/Sanitas handler, subscribe only to the observed BF1000 private measurement characteristics, and aggregate standard plus segmental packets before publishing.

Add a pure BF1000 parser with captured-payload tests for weight, body composition, segmental fat, segmental muscle, and the displayed bone-mass derivation.
@Lidara
Lidara marked this pull request as draft August 22, 2026 07:31
@Lidara

Lidara commented Aug 22, 2026

Copy link
Copy Markdown
Author

Actually, I noticed some flaky behavior with the “Choose a user on the scale” popup when connecting to the scale for the first time. I’ll leave this PR as a draft and investigate further.
In the meantime, I’d appreciate any feedback on the rest of the changes required to fully support the scale and all of its measurements.
Thank you.
Cheers, Lidara.

@oliexdev

Copy link
Copy Markdown
Owner

Thanks for the great PR 🥇 !

The segmental measurements (left/right arm, torso, left/right leg for fat/muscle) add 10 new MeasurementType values plus migrations, demo data, plausibility checks, and schema export changes. That's a big core footprint for one scale model, and we already had a similar attempt reverted for the S400 (#1367 / #1375).

Could you scope this PR down: merge the segmental values in the BF1000 parser (e.g. average left/right, or just use the total) and write only into the existing fields (fat %, muscle %, water %, bone)? That keeps the PR to the BF1000 handler, parser, and tests, no core changes needed.

Longer term, I am currently thinking about extending our generic measurement system so BLE handlers can write arbitrary values too — that'll be a separate change I'll drive myself, and the full BF1000 segmental data could go through that later. No need to wait on it now.

Quick notes on your other questions:

  • BMR: I would prefer to keep the calculation by openScale.
  • Bone mass (derived): fine if it matches what the scale displays.
  • Private characteristics (0006/0009/000A): if you want you can document that in a comment in the handler itself.

Happy to review the rest once the segmental part is simplified. Thanks for sticking with the user-popup bug too!

@Lidara

Lidara commented Aug 22, 2026

Copy link
Copy Markdown
Author

Thanks for the quick review, and for the context around the S400 revert.

I understand the concern about adding new core MeasurementType values, migrations, schema changes, demo data, etc. for one scale. I’ll scope this PR down to basic BF1000 support and keep it limited to the handler/parser/tests/catalog side as requested.

For the simplified version I’ll:
remove the segmental measurement types and related core/database changes
store only the existing openScale fields
use the total fat/muscle values where the scale sends them, rather than averaging segmental values
leave BMR to openScale’s existing calculation
keep the derived bone mass, since it matches the scale display
add a short handler comment for the private BF1000 characteristics

That said, I want to be transparent: the segmental readings are the main reason I bought the BF1000, so losing those values in openScale is pretty disappointing for my own use case. I understand why you don’t want that core footprint in this PR, but averaging or dropping the segmental values defeats the point of this scale for me.

So I’ll get this PR into the shape you asked for, and I may maintain a (private) fork with the full BF1000 segmental support until there’s a generic measurement extension point upstream. No pressure intended there, just setting expectations.

One question before I update it: since the BF1000 already sends total fat/muscle values, I assume you’d prefer using those directly over averaging the left/right/torso/leg values?

Thank you.

Cheers, Lidara.

@oliexdev

Copy link
Copy Markdown
Owner

Thanks for understanding, and for being upfront about the trade-off — that's fair, and I get why it's disappointing given that's the reason you bought the scale.

To your question: yes, please use the total fat/muscle values directly from the scale rather than averaging the segmental ones.

One suggestion so your parsing work isn't wasted: feel free to have the handler still parse all 10 segmental values internally (just don't persist or expose them for now, e.g. keep them as fields on the parser/response object or log them). Once I add a BLE generic/custom measurement mechanism, we can wire those existing parsed values straight in without redoing the BLE parsing.

Fair warning though — I likely won't get to the generic measurement work done soon, so it'll take a bit of time. No need to wait on it for this PR.

Thanks again for adjusting the scope!

@Lidara

Lidara commented Aug 22, 2026

Copy link
Copy Markdown
Author

Thanks, that sounds good.

I’ll keep the parser decoding the full BF1000 payload, including the segmental fat/muscle values, and document the private characteristics/mapping in the handler or parser. For this PR I’ll just drop those decoded segmental values before persistence and only write the existing openScale fields, using the total fat/muscle values sent by the scale.

And just to be clear: I don’t intend to put any pressure on you about the generic measurement work. I understand you’re maintaining this voluntarily, and I appreciate the time you’re already spending reviewing this. I’ll use a private fork with the full segmental fields for now, and if/when generic/custom BLE measurements become available upstream, we can wire the already-decoded BF1000 values into that. If it takes a while or doesn’t happen, that’s completely fair too.

I’ll update the PR accordingly.

Thank you.

Cheers, Lidara.

@Lidara

Lidara commented Aug 22, 2026

Copy link
Copy Markdown
Author

I’ve adjusted the implementation to the reduced scope you suggested.

The branch now only adds BF1000 support through the existing Beurer/Sanitas handler path, writes only the existing openScale fields, and keeps the BF1000 segmental/private characteristic parsing as internal documentation for now. From my side, the scale-support part is ready for review.

Would you prefer that I update the existing PR with the scoped-down version, or open a fresh PR so the diff is easier to review?

I also dug further into the flaky user-selection popup on the BF1000. From the logs, the scale does not always send the user list in one clean format. It can send normal records like:

[00, slot, initials..., birthdate, height, gender, activity]

but it also sometimes sends records like:

[slot-like, 00, initials..., birthdate, height, gender, activity]

Those second records seem to contain valid profile data, but the slot byte is not always reliable. If they are treated the same as normal records, the popup can show missing users, duplicated users, or duplicate IDs.

There is also a one-byte [02] packet. The existing handler treated that as “no users on scale”, but the BF1000 logs show [02] can also arrive after valid user records, where it appears to act as an end-of-list/status packet. So the safer interpretation seems to be: if the collected list is empty when [02] arrives, present the create-only path; otherwise finalize and present the collected users.

The fix I’m currently testing does this:
clears the temporary user list before requesting users, so stale entries from previous attempts cannot leak into the popup
treats [00, slot, ...] records as authoritative
defers [slot-like, 00, ...] records until the list is complete, then only uses them to fill a missing slot if they do not duplicate an already-seen profile

With that, the popup has been stable in my latest tests, including after adding a new user through openScale.

Would you prefer this user-list handling to stay guarded for the BF1000 only, or should it be applied generally in StandardBeurerSanitasHandler? I do not know yet whether other Beurer/Sanitas scales can emit the same mixed user-list format, so I’m happy to scope it either way.

Thank you.

Cheers,
Lidara

@oliexdev

Copy link
Copy Markdown
Owner

You can just update this PR or open a new PR what is better for you.
I think it would be better to stay you user handling in BF1000 handler only as we don't know if it's break the existing code.

@Lidara

Lidara commented Aug 23, 2026

Copy link
Copy Markdown
Author

closed in favor of #1484

@Lidara Lidara closed this Aug 23, 2026
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