Time to dive into the (bluetooth) specifics of the BS440 scale. When starting this project i was lucky to bump into this post on StackOverflow where user Edmundo was actually trying to acomplish the same. He snooped the bluetooth traffic on his Android phone and thus discovered the commands to send to the scale to make it send the data we need.
Data is presented to us by use of Indications. Indications are messages conveying the data for certain characteristics the device supports. Characteristics have a long (16 byte globally unique UUID) identifier but also a two-byte shortcut for it: the handle. Writing the command 0200 to a handle tells the device you register to the indications of this characteristic. Writing 0000 will stop it. A data packet of this characteristic will report with a handle 1 less than the handle of the characteristic. The BS440 has 3 kinds of interesting characteristics:
Handle UUID Data 0x26 00008a82-0000-1000-8000-00805f9b34fb person, gender, age, size, activity 0x1c 00008a21-0000-1000-8000-00805f9b34fb weight, time, person 0x1f 00008a22-0000-1000-8000-00805f9b34fb time, person, kcal, fat, tbw, muscle, bone
Some notes: person ranges from 1~8 and corresponds with the user selected before stepping on the scale. Or: being a “smart scale” : if the weight roughly corresponds to a known user weight that user is automatically selected. And when multiple users with the same weight are found it even lets you select the appropriate user.
So what does the data look like?
The last 30 measurements per person will be stored in the scale and upon communication the history for this user will be dumped. So you will receive values like this
handle=0x25, value=0x845302800134b6e0000000000000000000000000 handle=0x1b, value=0x1d8c1e00fe6e0aa056451100ff020900000000 handle=0x1e, value=0x6f6e0aa05602440ab8f07ff26bf11ef0000000 handle=0x1b, value=0x1d961e00fe72fe9f56291100ff020900000000 handle=0x1e, value=0x6f72fe9f5602460ab8f07ff26bf11ef0000000 handle=0x1b, value=0x1d961e00fe2dfe9f569e1000ff020900000000 handle=0x1e, value=0x6f2dfe9f5602460ab8f080f26cf11ef0000000 handle=0x1b, value=0x1d8c1e00feb3fd9f56c21000ff020900000000 handle=0x1e, value=0x6fb3fd9f5602440ab8f080f26cf11ef0000000 handle=0x1b, value=0x1d8c1e00fe39fc9f56641000ff020900000000 handle=0x1e, value=0x6f39fc9f5602440ab8f080f26cf11ef0000000 handle=0x1b, value=0x1d8c1e00fe02fb9f56991000ff020900000000 handle=0x1e, value=0x6f02fb9f5602440ab8f080f26cf11ef0000000 handle=0x1b, value=0x1d8c1e00fe80fa9f561d1000ff020900000000 handle=0x1e, value=0x6f80fa9f5602440ab7f080f26cf11ef0000000 handle=0x1b, value=0x1d8c1e00fef7f89f56021100ff020900000000 handle=0x1e, value=0x6ff7f89f5602440ab8f080f26bf11ef0000000 handle=0x1b, value=0x1d8c1e00fe10f39f566a1000ff020900000000 handle=0x1e, value=0x6f10f39f5602440ab8f080f26cf11ef0000000 etc.
Nice, but what does it actually mean?
Now the puzzle starts where to find the data we need, but luckily Edmundo helps us out again:
All multibyte values represented in unsigned int, little endian (multi byte values start with least significant byte). First byte of string is byte 0. 0x1b: value fixed: byte: 0 [0x1d] weight: byte: 1 & 2 [kg*100] timestamp: byte 5-8 Unix timestamp person: byte 13 [1..8] 0x1e: value fixed: byte 0 [0x6f] timestamp: byte 1-4 Unix timestamp person: byte 5 [1..8] kcal: byte 6 & 7 first nibble = 0xf, [kcal] fat: byte 8 & 9 first nibble = 0xf, [fat*10] tbw: byte 10 & 11 first nibble = 0xf, [tbw*10] muscle: byte 12 & 13 first nibble = 0xf, [muscle*10] bone: byte 14 & 15 first nibble = 0xf, [bone*10] 0x25: value fixed: byte 0 [0x84] person: byte 2 [1..8] gender: byte 4 (1=male, 2=female) [1|2] age: byte 5 [year] size: byte 6 [cm] activity: byte 8 (0=normal, 3=high) [0|3]
In Python the datastrings can be interpreted simply using struct.unpack using the pattern ‘BxBxBBBxB’ for the 0x25 data, ‘<BHxxIxxxxB’ for the 0x1b data and ‘<BIBHHHHH’ for the 0x1e data. In my program BS440decode.py takes care of the decoding job.
These insights should get us going. Just one more thing. When does the scale start sending data? Data transfer starts after sending 0x02 followed by the unix timestamp to handle 0x23. This is also the way the scale resyncs it’s RTC. Note that this last write must be a write with response (i.e. “write-req” instead of a “write-cmd”).
By now you might start to understand why this project dragged over 3 months to complete. Part 4 will discuss the Python code.