Since a few month I own an electrical toothbrush from Phillips called Sonicare HX992B. It connects to an mobile App using Bluetooth that tells you how well you brushed your teeth, shows the orientation in real-time, and also notifies you when to change the brush for a new one.
I was curious how this exactly works. Since the application uses Bluetooth Low Energy (BLE), I used the nRF Connect app for Android to get a list of advertised services.
Now I had a list of 12 BLE services of which only four are standard services described on bluetooth.com. In addition to well-known services like Generic Access, Generic Attribute, Device Information and Battery Service, also eight custom services exist, that were created by the manufacturer. Every one of the first seven service UUIDs starts with 477ea600-a260-11e4-ae37-0002a5d5000
and ends with a number from 1 to 8 with number 3 missing. An additional eight service has a completely different prefix than the others.
UUID | Service |
---|---|
0x1800 | Generic Access |
0x1801 | Generic Attribute |
0x180a | Device Information |
0x180f | Battery Service |
477ea600-a260-11e4-ae37-0002a5d50001 | Unknown Service 1 |
477ea600-a260-11e4-ae37-0002a5d50002 | Unknown Service 2 |
477ea600-a260-11e4-ae37-0002a5d50004 | Unknown Service 4 |
477ea600-a260-11e4-ae37-0002a5d50005 | Unknown Service 5 |
477ea600-a260-11e4-ae37-0002a5d50006 | Unknown Service 6 |
477ea600-a260-11e4-ae37-0002a5d50007 | Unknown Service 7 |
477ea600-a260-11e4-ae37-0002a5d50008 | Unknown Service 8 |
a651fff1-4074-4131-bce9-56d4261bc7b1 | Unknown Other Service |
By switching the device on and off, changing the strength and mode and also by changing the brush back and forth, I was able to identify some of the characteristics of the six services. By reverse engineering the code of the Android app using jadx I found information about the session storage and many other things.
Service | Suffix | Actions | Description |
---|---|---|---|
Handle (1) | 4010 | RWN | Handle State: Off(0), Standby(1), Run(2),Charge(3), Shutdown(4) Validate(6), LightsOut(7) |
4020 | R– | ? | |
4022 | R– | ? | |
4030 | R-N | ? | |
4040 | RW- | ? | |
4050 | RW- | Current Time (Unix Timestamp) | |
Brushing (2) | 4070 | R-N | Current session id |
4080 | R– | Mode (0x0=Clean, 0x2=White+, …) | |
4082 | RWN | State (0=Ready, 1=Active, 2=Resume) | |
4090 | R-N | Active time in seconds | |
4091 | R-N | Mode 2 (0x78=Clean, 0xa0=White+, 0xc8=Gum Health, 0xb4=Deep Clean+) | |
40a0 | R-N | ? | |
40b0 | R-N | Strength (0,1,2) | |
40c0 | R-N | ? | |
Session Storage (4) | 40d0 | R-N | Last session id (e.g. 0x2c05 = 1324) |
40d2 | R– | ? | |
40d5 | RW- | Session Type | |
40e0 | -WN | Request session (write session id, write 0x00 to 4110, get notification with data here and at 4100) | |
4100 | –N | ? | |
4110 | -WN | Session Access Control Point | |
Orientation (5) | 4120 | RW- | ? |
4130 | –N | Gyroscope Data | |
4140 | -WN | ? | |
Brush (6) | 4210 | R– | NFC Tag Version |
4220 | R– | ? INT8 | |
4230 | R-N | Brush Serial | |
4240 | R– | Brush Manufacturing Date? | |
4250 | R– | ? | |
4254 | R– | ? | |
4260 | R– | ? | |
4270 | R– | ? | |
4280 | R– | Brush Lifetime (Seconds) | |
4290 | R– | Brush Usage (Seconds) | |
42a0 | R– | Brush Type (None, Adaptive Clean, Adaptive White, Tongue Care, Adaptive Gums) | |
42a2 | R– | ? | |
42a4 | R– | ? | |
42a6 | RWN | ? | |
42b0 | R– | NFC Payload (always phillips.com) | |
42c0 | R– | ? INT16 | |
7 | 4310 | R– | ? INT16 |
4320 | R– | ? INT16 | |
4330 | R– | ? | |
4360 | R– | ? (Only uses second byte) | |
8 | 4410 | RW- | ? INT16 |
4420 | RW- | ? INT32 |
According to the application sources, the last unknown service is called “Moonshine Streaming” and is used to update the firmware. I agreed with myself to not touch this one, since I could damage the firmware and would need to brush my teeth by hand again.
Since I could see a very course orientation displayed in the app during a cleaning session, I expected orientation data somewhere in the characteristics. I found them in the service 5
. Once you enable notifications for the 4130
service, you get a data stream to byte arrays. It turns out, these are indeed the values of the gyroscope.
I created a python library to interface with the toothbrush in Python. Feel free to experiment, find the meaning of the missing services and submit pull requests: https://github.com/joushx/python-sonicare