1

a friend has a body scale with Bluetooth Le. To save his weight measurements he has to open the Manufactures App on his phone. So we try to use his raspberry pi for the Job. We find out, that the scale sends the data over advertisments. The scale is never connectable, and you can use different phones at the same time to receive the data. During the measurement the app(s) shows the same values as the display of the scale does. You know, if you stand on the scale the value will go up and down until the measurement is finished. This 'going up and down' is also visible on the phone, or phones if you use more than one at the same time. So i think it is a fact, that the data is transmitted over BLe advertisments. They can only be in the manufacturer data, what for example looks like this:

starts always with:
0f ff ac a0 db 58 e2 53 91 a0

and then:

88,30kg
20 c9 f8 42 0d b0
20 c9 f8 78 0d a6

88,35kg
20 c9 f9 b4 0d a3
20 c9 f9 88 0d b7

88,40kg
20 c9 f9 e6 0d b5
20 c9 f9 e6 0d b5

2,15kg
a0 c8 ab 40 0d a0

0,00kg
a0 c8 a0 a0 0d b5

But no matter how i have tried, i can't calculate this data to the given weight. I tried it in big endian, little endian, other untis like pound, ounces or even stone. I can not find a formula to calculate the data to every known weight. Interesting is, that zero is "a0 c8 a0 a0 0d b5". The first byte is 20 during the measurement, after finishing it, the scale sends many advertisment-packages with a0 in the first byte. I think this will say "final value". The fifth byte is always 0d, no matter what. The second byte changes from c8 to c9 on higher weights. I think at 50 or 60 kg. Because i can't find the solution for this i tried something else. I decompiled the android app an look for the part, where the advertisment-package is parsed. Unfortunately the app loads a native library to do this. Looks like the Manufacture really wanna keep it`s secret. I tried to use their library for my one purpose. But it looks like i need a Header-File to use the library in my own Java-Application. So my last chance is to disassemble the Library, what i have done. I used objdump. I know from the Java-Code, the function that i need is

private native List<Map<String, Object>> decode(byte[] bArr, int i, int i2, Object obj);

I can find this function/subroutine in the dump file.. but i can not understand how to work with it. I watched a video tutorial series for assembler, what was really interesting, but it doesn't really help me with this. I assumed you can somehow see where the data comes in (parameters for decode), how they be processed and returned (saved to memory for example). I also found a "BleBroadcastScaleProtocol16decodeWeightData"-Function but have still the same problem. My questions is now: Is there a real chance, that this can be done this way? The dump file has 10MB of text. Do someone know how to do this, or have done something like this before? I'm working for months on this and ever step forward brings me two steps back.

I put the dump file on my Cloud drive, maybe someone will have a look.

Dump File

Edit: I use my Raspberry Pi to send advertisements to the Manufactures App. I use the example-advertisement script from bluez. If i send adverts with the Service-ID FFB0 and "aca0" on the first two bytes of the manufacturer data the app recognize the pi as an "Insmart" scale. I'm also able to send weight data to the app. If i send "(ac a0 db 58 e2 53 91 a0) a0 c8 a0 a0 0d b5" the app show 0. I played around and find out the last four bits are a checksum, calculated by adding all hex-values together and take the last four bits of the summary. The weight data must be somewhere in the 2. to 4. bit (c8 - second a0). I wrote a script to take 16 bits from an offset and print out the hex value. As input data i use c8a0a0 (=0). With an offset of 1 (take 16 bits, starting from bit 2) it prints out "0x9141". In a second script i can input a hex value and it will merge it with the default value of "c8a0a0". So i can enter 9142 and put it together to data i can send as manufacuter data. 9142 is c8a120, what does nothing but 9143 is c8a1a0. This value is 0,25kg. When i increase the first value (9143) by 2 it will add 0,25kg in the app. Sometimes it adds 0,3kg. This works to 917f / c8bfa0 / 7,95kg. If i go to 9181 / c8c0a0 it jumps to 24,55kg. The i can continue in 0,25kg steps to 9195 / c8caa0 / 27,15kg. On 9197 / c8cba0 it changes unit and shows 4:4.4st:lb. If i count up by 1 the app crashs most of the time with the other values (9144, 9146, 9148...) Does anyone recognize a pattern in it? I'm also starting to try different offsets, but it doesn't look very promising.

Edit2: 4:4.4st:lb is equal to 27.397kg. Next step is 4:5.0st:lb what is 27,67kg. So it is linear at this point. But the range 8 - 24kg is missing

Here are the scripts that i use

Christian
  • 23
  • 8
  • I am assuming this is linked to the other question you have asked with very similar advertising data. That advert does appear to have a GATT service with the UUID of "FFB0". I have also found instructions for the scale that says "scale can only connect to one device at one time,please do not open the app on other devices at the same time". I wonder if the reason you are seeing it as non-connectable is because it is already connected elsewhere? – ukBaz Sep 06 '20 at 21:22
  • I find the generic tool of the nRF Connect App is very good at analyzing devices https://www.nordicsemi.com/Software-and-tools/Development-Tools/nRF-Connect-for-mobile On the Raspberry Pi, with the chromium browser you can use the URL `chrome://bluetooth-internals/#devices` to scan and analyze devices. You might also be interest in the following article where they discuss reverse engineer the protocol https://medium.com/@urish/reverse-engineering-a-bluetooth-lightbulb-56580fcb7546 – ukBaz Sep 06 '20 at 21:23
  • Yes, this question is linked to my other question. The scale does work with multiple devices at the same time. In the Android-Sourcecode is a File "BleHandle.java" witch import "ICBleProtocol.java". In this file is a function called "onDiscoverPeripheral". When i work through this Function manually, with my Advertising Data i get "ICDeviceCommunicationTypeBroadcast;". CommunicationTypeConnected also exists, but seems to be for other devices. We also tried to turn all phones off (not just disabled Bluetooth), but the scale is still never in connectable state. – Christian Sep 06 '20 at 23:36
  • It looks to me that the manufacturer use the UUID "FFB0" to identify the device type or maybe they use the same firmware for different devices and just customize it a little bit. The Sourcecode for the Android-App is also very wired, there are typos in function names and if/else statements that do just nothing. And it just don't look like the manufacture does use any standards. But i will have a look on your links an try to find out something more. Thank you so far – Christian Sep 06 '20 at 23:45
  • I know now for sure, that the weight data is transmitted over an advertisement. I was able to register my raspberry pi as scale in the app and send fake measurements to the app. It looks like the last byte is a checksum. If i add 1 to any byte after the mac address i have to add 1 to the last byte too. Shouldn't be that hard to figure out how the checksum is calculated. – Christian Sep 07 '20 at 10:43
  • Well, just as i say. Add all bytes together and take the last two "characters" ob the hexadecimal value – Christian Sep 07 '20 at 10:51
  • Is there other data in the advert also? Is it sending BMI data or something? – ukBaz Sep 07 '20 at 10:54
  • The BMI must be calculated in the app, because you can not enter your gender, size or age in the scale. The scale itself shows only the weight, but after the measurement the app shows more data like fat%, water%, muscle%. But i think this data is transmitted in other adverts. After the final weight data is transmittet the scale send some other values with a2 instead of a0 or 20 – Christian Sep 09 '20 at 08:13

2 Answers2

1

We finally figured out how to get the weight data. As i suggested the weight data is in the second to fourth byte, like this: c9 f8 42. But to get the right data you have to do something weird: You have to replace the third and fifth 'character' of the hex string like this:

0 1 2 3 4 5 6 7 8 9 a b c d e f Normal
a b 8 9 e f c d 2 3 0 1 6 7 4 5 Icomon

For example, c8a0a0 (=0,00kg) will become 0xC80000. Convert this to an integer and substract 0xC80000, divide by 1000 and there you have it.In this example its just 0, but i works with every values we tried.

I really don't know if this have anything to do with Big/little Endian or MSB/LSB shenanigans or if it is just a very simple 'encrypten' but it totally works.

Thanks anyone for helping us, we got some new ideas here what led us to the answer

Here is our python-file to decode the data:

#!/usr/bin/env python3
import sys
# 0 1 2 3 4 5 6 7 8 9 a b c d e f normal people
# a b 8 9 e f c d 2 3 0 1 6 7 4 5 Icomon
def main():
  hex_code = sys.argv[1]
  ic = ['a','b','8','9','e','f','c','d','2','3','0','1','6','7','4','5']

  i=0
  out=''
  for byte in hex_code:
    if i in [2, 4]:
      index = int(f'0x{byte}', 16)
      out = out+ic[index]
    else: out=out+byte  
    i=i+1
  value = round((int(f'0x{out}', 16)-0xc80000)/1000,2)
  value=round05(value)
  print(value)

def round05(number):
  return (round(number * 20) / 20)

main()

Edit: Well, now i know the 'shenanigans' are just XOR.

Christian
  • 23
  • 8
  • I only just revisited this now. haha. I almost got it. My suggestion was (value - 0xC8A0A0) / 1000 as answered above. It turns out that the right way is to XOR the value: (value XOR 0xC8A0A0) / 1000 – d_air Aug 13 '21 at 06:15
0

Update: Basing on the data that you provided, I think that you already found a pattern that can lead to finding the weight value. It looks like the weight value can be extracted from the 2nd to the 4th byte with C8A0A0 as the starting value(0kg).

To get the weight value, the 2nd-4th byte values can be substracted by C8A0A0. Then divide the answer by 1000.

The examples below are based on your data. The one inside the parenthesis is your observed kg value.

(0.25kg) C8A1A0 - C8A0A0 = 100HEX => 256DEC => 0.256kg

(7.95kg) C8BFA0 - C8A0A0 = 1F00HEX => 7936DEC => 7.936kg

(88.3kg) C9F842 - C8A0A0 = 157A2HEX => 87970DEC => 87.97kg C9F878 - C8A0A0 = 158D8HEX => 88280DEC => 88.28kg

(88.35kg) C9F9B4 - C8A0A0 = 15914HEX => 88340DEC => 88.34kg C9F988 - C8A0A0 = 158E8HEX => 88296DEC => 88.296kg

(88.40kg) C9F9E6 - C8A0A0 = 15946HEX => 88390DEC => 88.39kg

(2.15kg) C8AB40 - C8A0A0 = AA0HEX => 2720DEC => 2.72kg

Please try more data like simulating to send C9B210 and see if you can get a weight value that is closer to 70kg.

Previous answer: Perhaps the weight data is not in the advertisement. It is not clear to me in your description that you attempted to connect to the bluetooth le scale in a standard way. You can do it using the nRF or the Lightblue app. This is the general procedure:

  1. Open the app. Let it detect the bluetooth scale.
  2. Connect to the device. Make sure that the scale is not connected to other apps(such as it's own app).
  3. You will see the UUIDs for the services and characterics.
  4. Find the uuids that has a 'notify' and 'indicate' characteristics and subscribe to all that has that characteristics.
  5. Look for the logs of those characteristics while the value in the scale is changing. It is most probably where you see the weight data.

Depending on the functionality of your scale, it may be required that you'll be doing all these steps while the scale is active and have not yet settled on the final value so the bluetooth communication is ongoing. You can do this by constantly stepping in/out to the scale while operating the app.

d_air
  • 631
  • 1
  • 4
  • 12
  • I'm sure the weight data is in the advertisement. I can send advertisements on my own to the app and "emulate" the scale. I have edit my question, so there are more information. Yes, in the past we tried to connect to the scale in various ways. We create hcidumps to see if you have so send something to the scale to make it connectable, turned any phones/devices off. But the scale was never connectable. – Christian Sep 09 '20 at 08:55
  • I seem to find a pattern after you edit your question and showed more data. I updated my answer. – d_air Sep 09 '20 at 20:13
  • Looks promising. But it looks like it does not work with every value. I send some data to the app and get this values: [pastebin](https://pastebin.com/53a7tRP8) – Christian Sep 10 '20 at 10:48