Skip to content

Bugfix: Fixed support for Beurer BF450 scale#1399

Merged
oliexdev merged 7 commits into
oliexdev:masterfrom
VulcanoHex:master
Jun 16, 2026
Merged

Bugfix: Fixed support for Beurer BF450 scale#1399
oliexdev merged 7 commits into
oliexdev:masterfrom
VulcanoHex:master

Conversation

@VulcanoHex

Copy link
Copy Markdown
Contributor

The merge request i made yesterday was buggy, since i just added the support to an existing handler, that would only take the first measurement for the user. That was a issue in testing on my part.

So i made a custom handler for the Beurer BF450, updated the scale factory and removed the changes in the previous commit.
The custom BF450Handler currently supports:

  • Creating a user (i couldn't get the sync to an existing user in the scale to work)
  • Adding measurement while connected
  • Syncing with the scale on connection

Still sorry for the previous commit, have a nice day.

when (val s = data[0].toInt() and 0xFF) {
0x00 -> logD("FFF4: misura in corso…")
0x01 -> logD("FFF4: misura completata")
else -> logD("FFF4: status sconosciuto 0x${s.toString(16)}")

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you please translate that into English

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, it slipped in my proof reading. Should be fixed in the relevant commit

if (pendingBodyComposition) {
transformed.fat = lastFatPct
transformed.muscle = lastMusclePct
transformed.water = lastBodyWater

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lastBodyWater in kg would overwrite the transformed water in % here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed it!

val impl = setOf(
DeviceCapability.BODY_COMPOSITION,
DeviceCapability.LIVE_WEIGHT_STREAM,
DeviceCapability.HISTORY_READ,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the history read really implemented in this handler?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My interpretation of history read is that:
When the scale assigns a measurement to a user, in the scale's memory, while the scale isn't connected the measurement is stored in the scale. Later when the scales connects it receives all past measurements.
Is my interpretation wrong? I didn't found the definition for history_read so that's what i assumed and tested for.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also i encountered a bug in which sometimes when the scale is disconnected it doesn't recognize the user even if the weight is close to one. Do you have any idea on what could be the issue? If it happens again i can log it and send it.
Also i read in the FAQ that logs are welcome and valuable to the project, i have one but i don't know where to post it, is here ok or there is a specific place? Sorry for stupid question i'm new to contributing in an open source project.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HISTORY_READ: your interpretation is correct. Does it work like that?

The scale-slot → openScale-user mapping is missing. Normally StandardWeightProfileHandler saves it for you in its UDS consent flow (onUserInteractionFeedback), but your handler talks to the scale over the custom 0xFFF1 flow instead, so that base path never runs — and your FFF1 code only ever clears the mapping with saveUserIdForScaleIndex(idx, -1).

Good news: you don't need anything new. The helper functions are already inherited from StandardWeightProfileHandler and are protected, so you can call them directly:

  • saveUserIdForScaleIndex(scaleSlot, appUserId) — persist the mapping
  • saveConsentForScaleIndex(scaleSlot, consent) — persist the consent (if used)
  • loadUserIdForScaleIndex(scaleSlot) — the base parser already calls this to resolve the user index in 0x2A9C/0x2A9D packets

So in your FFF1 user-pick (where you currently clear it), just save the real mapping instead: saveUserIdForScaleIndex(scaleSlot, currentAppUser().id). Once that's stored, the base class will automatically attribute incoming measurements to the right user.

Logs are very welcome! Just attach the file or include it in the PR/issue. The log format is already in a markdown format so you can just copy & paste it here.

Solid first PR, no need to apologize.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think i fixed it?
I tried implementing it as you said, and it worked, but sometimes the scale itself doesn't assign a measurement to any user ( it says U ?? ).
Then when it's connected again, it gives the unknown measurement to the last user that is connected. It's fixable with the option, in bluetooth settings, in the scale manager "Smart user assigment" however idk if it is intended behavior.

@VulcanoHex

Copy link
Copy Markdown
Contributor Author

Here are some logs, taken with frida, because i couldn't get bluetooth hci snoop logs to work.

  • In this log should be present user creation and setup newinstall.txt
  • Here there should be some test measurments, already parsed in the .txt, with the provided ble2.js in frida parsed.txt
  • Also the ble2.js creates a .pcap version of parsed.txt. Uploading in .zip since github doesn't let me upload .pcap ble_try.zip

The quick bluetooth parser for frida i used: ble2.js

@oliexdev

Copy link
Copy Markdown
Owner

I pushed a small commit on top to fix the "U ??" / wrong-user issue.

The FFF1 handler pre-wrote a guessed slot + hard-coded consent 1, which collided with the real UDS consent. Your logs show the scale assigns the slot itself (20 01 01 03 → slot 3, consent 0x0DA8), so on reconnect the fake slot 1 / consent 1 won → USER_NOT_AUTHORIZED → "U ??".

FFF1 is now discovery-only; mapping + consent come solely from the standard UDS flow (which persists the real slot/consent on register). Also clear the user list before re-enumerating.

Please try it out.

@VulcanoHex

Copy link
Copy Markdown
Contributor Author

Tried it, but unfortunately the unknown measurement are still assigned to the user in the app at the moment of syncing.
I saved the logs hopefully you can gain more insight from them than me:

@oliexdev

Copy link
Copy Markdown
Owner

The scale recognises users automatically by weight/impedance; if the scale can't recognise anyone, it produces an unknown measurement.

openScale will assign unknown/unrecognised measurement to the current openScale user.

So:

  • The consent fix is correct and complete — registration/consent/reconnect now work reliably for multiple users.
  • The "107.3 kg went to user 2" case is the unknown-measurement default: you were connected as user 2, the scale couldn't identify the body, so it went to user 2.
  • To test real multi-user: use actual, distinct registered people (so the scale's recognition works) and connect as the user you're weighing. Maybe hard reset the scale and do a fresh openScale installation, so the user assignment will be fresh (backup your measurements via CSV files for each user and do a openScale backup in advance)

@oliexdev oliexdev merged commit e429372 into oliexdev:master Jun 16, 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