Using C Forth to reverse engineer the Milo Champions Band fitness tracker protocol

| quozl@us.netrek.org | up |

2nd October 2016

The Milo Champions Band fitness tracker is only supported with Android and iOS, yet it reveals itself to scans over Bluetooth Low Energy (BLE) on Linux;

% sudo hcitool lescan
LE Scan ...
xx:xx:xx:xx:xx:xx MILO 000000
Perhaps it will work with Linux? Let's find out.


gatttool

The device provides a few standard handles and characteristics, but the activity data is not described by them. Instead, a custom serial protocol is used on handles fff6 and fff7. Perhaps the pedometer and display controller are on separate silicon to the BLE?

gatttool --device XX:XX:XX:XX:XX:XX -I
[   ][XX:XX:XX:XX:XX:XX][LE]> connect 
[CON][XX:XX:XX:XX:XX:XX][LE]> primary
[CON][XX:XX:XX:XX:XX:XX][LE]> 
attr handle: 0x0001, end grp handle: 0x0007 uuid: 00001800-0000-1000-8000-00805f9b34fb
attr handle: 0x0008, end grp handle: 0x000b uuid: 00001801-0000-1000-8000-00805f9b34fb
attr handle: 0x000c, end grp handle: 0xffff uuid: 0000fff0-0000-1000-8000-00805f9b34fb
[CON][XX:XX:XX:XX:XX:XX][LE]> characteristics
[CON][XX:XX:XX:XX:XX:XX][LE]> 
handle: 0x0002, char properties: 0x0a, char value handle: 0x0003, uuid: 00002a00-0000-1000-8000-00805f9b34fb
handle: 0x0004, char properties: 0x02, char value handle: 0x0005, uuid: 00002a01-0000-1000-8000-00805f9b34fb
handle: 0x0006, char properties: 0x02, char value handle: 0x0007, uuid: 00002a04-0000-1000-8000-00805f9b34fb
handle: 0x0009, char properties: 0x20, char value handle: 0x000a, uuid: 00002a05-0000-1000-8000-00805f9b34fb
handle: 0x000d, char properties: 0x10, char value handle: 0x000e, uuid: 0000fff7-0000-1000-8000-00805f9b34fb
handle: 0x0010, char properties: 0x0e, char value handle: 0x0011, uuid: 0000fff6-0000-1000-8000-00805f9b34fb
[CON][XX:XX:XX:XX:XX:XX][LE]> char-desc
[CON][XX:XX:XX:XX:XX:XX][LE]> 
handle: 0x0001, uuid: 2800
handle: 0x0002, uuid: 2803
handle: 0x0003, uuid: 2a00
handle: 0x0004, uuid: 2803
handle: 0x0005, uuid: 2a01
[CON][XX:XX:XX:XX:XX:XX][LE]> 
Note the fff7 UUID has NOTIFY support in properties. So it is likely to be serial receive data. The other UUID fff6 is therefore likely serial transmit data.

Let's set up notifications for receiving on 0x000e, and then transmit on handle 0x0011.


C Forth

C Forth is an implementation of Forth by Mitch Bradley. See git.

One of the build targets of C Forth is bluez64, a host-based Forth with support for Bluetooth on the Linux kernel. It runs as a Linux process.

Building the target on 64-bit Linux needed a couple of trivial fixes, see my bluez64 branch.

git clone -b bluez64 https://github.com/quozl/cforth-1
cd cforth-1/build/bluez64
make
./forth
Result is;
C Forth  Copyright (c) 2008 FirmWorks
ok █
That's C Forth running. See Forth Lessons for a quick introduction to Forth.


Bluetooth Words

Mitch's words for using Bluetooth are in his source file: Here's some of them documented:

WordStack effectMeaning
bdaddr>binary( adr -- bdaddr )convert a hexadecimal string to a bluetooth address
(connect)( bdaddr -- )connect to a bluetooth device
.primaries( -- )print primaries
.chars( -- )print characteristics
.devname( -- )print device name
.notifications( -- )print characteristics that support notifications and whether notifications are on or off
notify-on( handle -- )turn on notifications for a characteristic handle
wait-notify( timeout -- true | adr len false )wait up to timeout milliseconds for a notification, returning either true on timeout, or false with a pointer and length of the reply
byte-write-handle( byte handle -- )write a byte to a handle
write-handle( adr len handle -- )write a buffer to a handle


Connecting

ok " xxxxxxxxxxxx" drop bdaddr>binary (connect)
ok █
Where xxxxxxxxxxxx is the address shown by lescan without colons.

Information

General information queries;
ok .primaries
SIG 1800  S: 0001 E: 0007 
SIG 1801  S: 0008 E: 000b 
SIG fff0  S: 000c E: ffff 
ok .chars
SIG 2a00  H: 0002 P: 0a VH: 0003 
SIG 2a01  H: 0004 P: 02 VH: 0005 
SIG 2a04  H: 0006 P: 02 VH: 0007 
SIG 2a05  H: 0009 P: 20 VH: 000a 
SIG fff7  H: 000d P: 10 VH: 000e 
SIG fff6  H: 0010 P: 0e VH: 0011 
ok .devname
MILO 000000
ok █

Enable Notifications

ok .notifications
SIG fff7 H: 000d OFF
ok e notify-on
ok .notifications 
SIG fff7 H: 000d ON
ok █

Sending Single Bytes

Need a test harness for sending single byte commands and printing the response.

\ print a byte as two digits in hexadecimal
: .2   (s n -- )   u>d <#   # #   #>   type   space   ;

\ print a buffer as hexadecimal
: cdump.2  ( adr len -- )  bounds  ?do  i c@ .2  loop  ;

\ wait 100ms for a response from the device and if there is one dump it
: .response  ( -- )  #100 wait-notify  0=  if  cdump.2  then  ;

\ send a one-byte command and print a response if any
: cmd  ( n -- )  dup .2 ." --> "  11 byte-write-handle  .response cr  ;
Now to use it;

ok 00 cmd
00 --> 
ok 01 cmd
01 --> 81 00 00 00 00 00 00 00 00 00 00 00 00 00 00 81 
ok █
So there was no reply to 00, but a 16-byte reply to 01.

Let's try another few bytes.

00 --> 
01 --> 81 00 00 00 00 00 00 00 00 00 00 00 00 00 00 81 
02 --> 82 00 00 00 00 00 00 00 00 00 00 00 00 00 00 82 
04 --> 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 
08 --> 88 00 00 00 00 00 00 00 00 00 00 00 00 00 00 88 
10 --> 
20 --> a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a0 
40 --> 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 
80 --> 
For some bytes, there was no response. Otherwise, there was a 16-byte packet, with the last byte equal to the first.

Let's try all 256 possibilities.

ok 100 0 do i cmd loop
00 --> 
01 --> 81 00 00 00 00 00 00 00 00 00 00 00 00 00 00 81 
02 --> 82 00 00 00 00 00 00 00 00 00 00 00 00 00 00 82 
03 --> 
04 --> 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 
05 --> 85 00 00 00 00 00 00 00 00 00 00 00 00 00 00 85 
06 --> 
07 --> 87 00 00 00 00 00 00 00 00 00 00 00 00 00 00 87 
08 --> 88 00 00 00 00 00 00 00 00 00 00 00 00 00 00 88 
09 --> 89 00 00 00 00 00 00 00 00 00 00 00 00 00 00 89 
0a --> 8a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 8a 
0b --> 8b 00 00 00 00 00 00 00 00 00 00 00 00 00 00 8b 
0c --> 
0d --> 
0e --> 
0f --> 8f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 8f 
10 --> 
11 --> 
12 --> 92 00 00 00 00 00 00 00 00 00 00 00 00 00 00 92 
13 --> 93 00 00 00 00 00 00 00 00 00 00 00 00 00 00 93 
14 --> 
15 --> 
16 --> 
17 --> 
18 --> 
19 --> 
1a --> 
1b --> 
1c --> 
1d --> 
1e --> 
1f --> 
20 --> a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a0 
21 --> 
22 --> a2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a2 
23 --> a3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a3 
24 --> a4 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a4 
25 --> a5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a5 
26 --> a6 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a6 
27 --> a7 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a7 
28 --> 
29 --> 
2a --> 
2b --> 
2c --> 
2d --> 
2e --> ae 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ae 
2f --> 
30 --> 
31 --> 
32 --> 
33 --> 
34 --> 
35 --> 
36 --> 
37 --> b7 00 00 00 00 00 00 00 00 00 00 00 00 00 00 b7 
38 --> b8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 b8 
39 --> 
3a --> 
3b --> 
3c --> 7c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 7c 
3d --> bd 00 00 00 00 00 00 00 00 00 00 00 00 00 00 bd 
3e --> be 00 00 00 00 00 00 00 00 00 00 00 00 00 00 be 
3f --> 9b 04 07 00 00 00 00 00 00 00 00 00 00 00 00 a6 
40 --> 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 
41 --> 41 16 09 11 16 14 04 00 00 00 00 00 00 00 00 9f 
42 --> c2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 82 
43 --> c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c3 
44 --> 
45 --> 
46 --> 
47 --> 
48 --> c8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c8 
49 --> c9 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c9 
4a --> ca 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ca 
4b --> cb 00 00 00 00 00 00 00 00 00 00 00 00 00 00 cb 
4c --> cc 00 00 00 00 00 00 00 00 00 00 00 00 00 00 cc 
4d --> cd 00 00 00 00 00 00 00 00 00 00 00 00 00 00 cd 
4e --> 
4f --> cf 00 00 00 00 00 00 00 00 00 00 00 00 00 00 cf 
50 --> 
51 --> 
52 --> 
53 --> 
54 --> 
55 --> 
56 --> 
57 --> 
58 --> 
59 --> 
5a --> 
5b --> 
5c --> 
5d --> 
5e --> 
5f --> 
60 --> 
61 --> 
62 --> 
63 --> 
64 --> 
65 --> 
66 --> 
67 --> 
68 --> 
69 --> 
6a --> 
6b --> 
6c --> 
6d --> 
6e --> 
6f --> 
70 --> e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 e0 
71 --> e9 00 00 00 00 00 00 00 00 00 00 00 00 00 00 e9 
72 --> 
73 --> e3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 e3 
74 --> 
75 --> 
76 --> 
77 --> 
78 --> 
79 --> 
7a --> 
7b --> 
7c --> 
7d --> 
7e --> 
7f --> 
Everything from 80 and above returned nothing.

From this we can deduce a few more details.

Perhaps the device expects 16-byte packets from the host?

Sending 16-byte Packets

Need a test harness for sending 16-byte packets, with checksums, and printing the response.

\ print then send a buffer, and print the response
: prod  ( adr len -- )
   ." > tx " 2dup cdump.2  cr   ( adr len )
   11 write-handle              ( )
   ." < rx " .response cr       ( )
;

\ a 16-byte buffer
#16 buffer: mine

\ send a command as a 16-byte packet with checksum
: pcmd  ( n -- )
   mine #16 erase               ( n )
   dup mine c!                  ( n )  \ set the command
   mine #15 + c!                ( )    \ use as checksum
   mine #16 prod                ( )    \ send
;
Now to try it out;
ok 01 pcmd
> tx 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 
< rx 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 
ok 41 pcmd
> tx 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 41 
< rx 41 16 10 01 22 47 51 06 00 00 00 00 00 00 00 28 
ok █
While the 01 command didn't seem to do anything, the reply was 01 instead of 81, suggesting that the high bit in 81 means an error.

But the 41 command was very interesting, it gave the date and time in year, month, day, hour, minute, second and day of week form.

ok 12 pcmd
> tx 12 00 00 00 00 00 00 00 00 00 00 00 00 00 00 12 
ok █
At this point, hcidump showed a disconnect, the device powered down, and would not respond to touch. Bricked? No, it woke up in charger.

The 12 command did nothing previously, it was only in a packet that it worked.

From this we can deduce:


Commands

With some experimentation;

Alarms

With some further experimentation, an alarm clock feature was found, which runs the vibration motor at some future time;

Cross checking with gatttool

gatttool --device XX:XX:XX:XX:XX:XX -I
[   ][XX:XX:XX:XX:XX:XX][LE]> connect 
[CON][XX:XX:XX:XX:XX:XX][LE]> char-write-cmd 0xf 0300
[CON][XX:XX:XX:XX:XX:XX][LE]> char-write-cmd 11 07000000000000000000000000000007
Notification handle = 0x000e value: 07 00 00 15 05 01 00 00 00 00 00 00 00 00 00 22 
Notification handle = 0x000e value: 07 01 00 15 05 01 00 00 00 00 00 00 00 00 00 23 
[CON][XX:XX:XX:XX:XX:XX][LE]> 

Microscope photographs

DC input pins

DC charging bracket

Rear logo printing


| quozl@us.netrek.org | up |