Please respect the license under which this work is made available. (See terms and conditions at the end of the page)
It's January, a time when we reflect on the excesses of Christmas and decide to get fit. To this end we have an EtekCity scale. It comes with a Bluetooth facility for wirelessly reading the measurements and this is accessed through a large Android application called VeSync.
The aims of this project are:
Build a convenient, automatic, app-less logger for the scale that works like this :
The server will be built in a part 2.
The protocol spoken by the ESF24 scale is proprietary and undocumented so to understand it we need to do a little protocol analysis.
The application that talks to the scale is called VeSync and runs on Android. Android has the ability to log bluetooth communications so we'll use that to log and then inspect the protocol to see if we can reverse engineer the relevant details.
There are a number of guides on the Internet describing how to enable Bluetooth data logging on Android. The exact guide you choose will depend on your version of Android. These steps are for version 12 :
The btsnoop_hci.log log file can be viewed in Wireshark. The log contains a lot of noise (especially if you have other Bluetooth devices connected) so it's helpful to identify the MAC address of your scale and then set a Wireshark filter as follows :
bluetooth.addr == 04:ac:44:01:02:03
The first part of the communications is the standard BLE GATT service discovery where the app is asking the scales what services it supports.
0000180f-0000-1000-8000-00805f9b34fb (Battery service)
00002A19-0000-1000-8000-00805f9b34fb (Battery characteristic)
0000fff0-0000-1000-8000-00805f9b34fb (Unknown service)
0000fff1-0000-1000-8000-00805f9b34fb (Notify characteristic)
0000fff2-0000-1000-8000-00805f9b34fb (Write characteristic)
Communications with the scale are performed through the "Unknown service" by :
Having identified the communications channel, we can further filter the Wireshark log to show only these comms as follows :
bluetooth.addr == 04:ac:44:01:02:03 && (btatt.opcode == 0x52 || btatt.opcode == 0x1b)
And then we export them as JSON and process them using this throwaway script :
import json
a = json.loads(open("a.json").read())
for doc in a:
btatt = doc["_source"]["layers"]["btatt"]
d = int(btatt["btatt.opcode"],16)
val = btatt["btatt.value"].replace(":","")
print("<" if d==0x1b else ">", val)
The script prefixes each line with a < for data received from the scale and a > for data transmitted to the scale.
The edited (identifying information removed) output is below :
< 120f150xxxxxxxREDACTED # Hello from scale message.
> 1309150110be340034 # Configures the scale.
< 140b150000010000000035
> 2008151fd86929c6 # Send the current time to the scales.
< 210515013c
< 100b150000000000000030 # Weight message
> 2204153b
< 100b150000000000000030 # Weight message
< 231415000000000000000000000000000000004c
< 100b150bea000000000025 # Weight message
< 100b1525ad000000000002 # Weight message
... lots more of these all changing as scale was stepped on
< 100b1528e60101f801afe8 # Contains the final weight + resistance measurements. Fat bastard.
> 1f05151049 # Stops sending the final measurement.
Examining the above trace we can deduce the likely message structure as follows :
Other observations are described in the test implementation.
Using the above analysis we built a test implementation and used it to talk to the scales.
This was a success and we can reliably get measurements from it.
The code for this is here :
Install it into a Python virtual environment and run like this :
python -metekcity_scale.test 04:AC:44:01:02:03
Example output:
Battery bytearray(b'd')
Found scales service
UnknownPacket(type=35, raw_value=bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'))
2.15 0 0 0
19.7 0 0 0
19.7 1 434 363