Skip to content

Conversation

@Nezisi
Copy link

@Nezisi Nezisi commented Jan 3, 2026

Summary

Fixes Beurer BF500 scale support by:

  • Fixing consent workflow (scales don't support UDS LIST_ALL_USERS)
  • Separating parsing from transformation (water/muscle/bone/LBM now correct)
  • Storing impedance and BMR (previously lost)

Closes #1034

BF 500: What works

Initial pairing requires:

  1. Scale enters pairing mode when bonding starts
  2. User must step on the scale's set button to confirm PIN

Measurement flow:

  1. After pairing: reference weight is determined
  2. Scale shows progress during impedance measurement (squares on display)
    • If user steps off the scale, impedance / body fat / water mass will be 0
  3. When complete: all values shown on scale display
  4. Icon changes: bluetooth → bluetooth+phone (15-20 seconds)
    • This indicates measurement was sent to app
    • User must be patient!
  5. If successful, OpenScale should work "forever" (no need to reset)

Issues Fixed

Body Composition Values Incorrect

Problem:

  • Water stored as mass (49.9 kg) instead of percentage (38.8%)
    • from the previous code and variable naming, I suspect that "waterMass" means "water in weightUnit" and OpenScale expects water in percent for ScaleMeasurement
  • Bone/LBM calculated before packets merged or calculation with "0" (user stepped off the scale)
  • Impedance & BMR parsed but never stored

Root Cause:
Transformations happened during parsing, before packets were merged. Weight arrived in a separate packet, so conversions failed.

Solution:
Separated transformMeasurement from parseWeightToMeasurement, so one has in transformMeasurement always the weight present for calculations.

Issues left

Problem:

  • If consent saved in app and scale is reset → consent doesn't "reset"
  • Scale shows "USE APP" after weighing
  • Only fix: reinstall app + reset scale

Log evidence:

BluetoothEvent received: DeviceMessage(message=Bestehende Zuordnung verwenden: App-Benutzer 1 ↔ Waagen-Slot 1., deviceAddress=D8:0B:CB:5E:50:BC) for BF500
Event: Message from BF500: Bestehende Zuordnung verwenden: App-Benutzer 1 ↔ Waagen-Slot 1.
...
UCP response received: reqOp=0x2 result=4 fullData=[20 02 04]
UDS CONSENT unhandled result=4 for appUserId=1
  • consent gets rejected by BF500 - but the code isn't implemented, so the app is "stuck" with the wrong user profile & consent

Disclaimer for transparency: I used AI (Claude) to aid in generating the descriptions, for debugging and for the code.
I refactored and reviewed the code, tested it and tested with the scale to the best of my knowledge.

@oliexdev
Copy link
Owner

oliexdev commented Jan 4, 2026

Thanks for your PR. I have fixed the conversation to % before publishing there was already a function transformBeforePublish so no need to add a new function transformMeasurement

But the only really BF500 different what you have made (which i can see) is that you registerScaleNewUser(user.id) on onAutoConsentFailed but that should be normally handled by the following code:

            UDS_CP_LIST_ALL_USERS -> {
                logD("UDS LIST_ALL_USERS response: ${value.toHex()} (result=$result)")
                if (result == UDS_CP_RESP_VALUE_SUCCESS) {
                    if (value.size >= 4) {
                        val count = value[3].toInt() and 0xFF
                        logD("LIST_ALL_USERS: user count=$count")
                        if (count > 0 && value.size >= 4 + count) {
                            val indices = mutableListOf<Int>()
                            for (i in 0 until count) {
                                indices += (value[4 + i].toInt() and 0xFF)
                            }
                            logD("Available user slots: $indices")
                            presentChooseFromIndices(indices)
                        } else {
                            logD("LIST_ALL_USERS success but no users found, defaulting to 'Create new'")
                            findKnownScaleIndexForAppUser(currentAppUser().id)?.let { idx ->
                                saveUserIdForScaleIndex(idx, -1)
                                logD("Cleared previous mapping for currentAppUserId at scaleIndex=$idx")
                            }
                            presentCreateOnlyChoice()
                        }
                    } else {
                        logW("LIST_ALL_USERS payload too short, using fallback")
                        presentCreateOnlyChoice()
                    }
                } else {
                    logW("LIST_ALL_USERS not supported or failed, switching to read-only mode")
                    userInfo(R.string.bt_info_read_only_mode_no_consent)
                    userInfo(R.string.bt_info_suggest_create_new_user)
                    presentCreateOnlyChoice()
                }
            }

so the question is what is BF500 reporting back after they receive the following writing

    private fun requestUdsListAllUsers() {
        val payload = byteArrayOf(UDS_CP_LIST_ALL_USERS.toByte())
        logD("→ UCP LIST_ALL_USERS")
        writeTo(SVC_USER_DATA, CHR_USER_CONTROL_POINT, payload)
    }

Could you provide the debug log with the latest dev version?

@Nezisi
Copy link
Author

Nezisi commented Jan 5, 2026

Good news, your changes to the GATT handling in the latest commits definitely fixed the user association, from an earlier log on 2026-01-02:

onSearchComplete() = address=XX:XX:XX:XX:50:BC status=0
Services discovered for D8:0B:CB:5E:50:BC
attach()
handleConnected(userId=1, height=175.0, age=36)
→ set notify on chr=00002a9d-0000-1000-8000-00805f9b34fb svc=0000181d-0000-1000-8000-00805f9b34fb
tryAutoConsent called for appUserId=1
No known scale index found for appUserId=1, auto-consent cannot proceed
→ UCP LIST_ALL_USERS
BF500 connected
Adapter isConnected: true for BF500 (Status: CONNECTING)
Successfully connected to BF500 via adapter's isConnected flow.

Now we've got at least the user set up working, the scale auto associates.
Problem seems to be the consent:

Yes, the problem is the consent (from the latest log):

UCP response received: reqOp=0x2 result=4 fullData=[20 02 04]
UDS CONSENT unhandled result=4 for appUserId=1
write response chr=00002a9f-0000-1000-8000-00805f9b34fb len=4 status=SUCCESS payload=[02 01 CC 17]
write to chr=0000fff2-0000-1000-8000-00805f9b34fb svc=0000ffff-0000-1000-8000-00805f9b34fb len=1 withResp=true payload=[05]
write response chr=0000fff2-0000-1000-8000-00805f9b34fb len=1 status=SUCCESS payload=[05]

This happened before, too - sorry for not being clear on this.

I've created a zip with 3 files:

  • the debug log (bit shortened, so only the most relevant part is shown, straight from logcat)
  • packets in JSON export from Wireshark
  • packets in PCAP format from Wireshark

I've cut the packet logs to the relevant portions, it is filtered for the MAC of the Scale.

If you need more data, feel free to ping, I'll try to answer as soon as I can.

Thanks for your efforts and patience.

openscale_bf500_417aa74d4dda006a4cb2dac178bf94f643745f7f.zip

@oliexdev
Copy link
Owner

oliexdev commented Jan 5, 2026

According to the Bluetooth User Data Service (UDS) specification, result code 0x04 means Operation Not Permitted.
This may indicate that consent is already present / already applied, rather than an actual failure.

Logically, the consent therefore succeeds, but the response is currently logged as unhandled instead of being treated as OK.

That said, it is also possible that the scale interprets this result differently. Further investigation on the device-specific behavior would be required to determine the exact issue why you don't get any measurement.

@oliexdev
Copy link
Owner

I will close this PR for now if you find out how the BF500 handles user responses please open a new PR. Thanks.

@oliexdev oliexdev closed this Jan 18, 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.

BF500 support

2 participants