Reverse Engineering the BodyBugg
I recently bought a BodyBuggSP after a lot of research into fitness measurement//recording devices which I wanted to use for recording some information about my sleeping habits.
The BodyBugg V3 and BodyBuggSP are both essentially rebranded flavors of devices designed and manufactured by BodyMedia. As far as I can tell all of the devices have more or less the same sensors although some come with bluetooth or RF communication built in for use with a smart phone or display.
The biggest drawback for me is that the way the devices are designed and the software to retrieve data from them was written you are required to pay for the online service used to store and interpret the data. You can view the data using bluetooth if you got a BodyBuggSP but this will only view. You must sync the device with the web service in order to erase the device's memory, which means if you no longer use the web service, your device will simply run out of memory and stop logging.
This sort of commercial douche-baggery really doesn't sit well with me and I immediately started looking around for any projects for reverse engineering the protocol used to communicate with the device and download//clear memory. There are as far as I can tell 3 people who have started projects like this.
One of these projects is a python script which took the approach of reverse engineering the protocol for communicating with the device. Since the device communicates with a computer via serial over USB you can pretty easily sniff the traffic on a serial port. This turned out to be a very messy endeavor as there are a lot of unknown fields and discovering the structure of requests and responses isn't very straight forward.
Another involved sniffing the HTTP traffic between the java applet used for downloading and clearing device memory. Unfortunately this method is only capable of recording data, not clearing device memory after the service period has elapsed.
The last project was a Java program which has basically no documentation or source available since it was distributed as a JAR file and lived on google docs. Once BodyMedia caught wind that class files from their Java applet were being distributed verbatim in this program DMCA's were filed with google to remove the files.
First thing I did was attempt to rewrite some of the python script in a more general fashion but quickly gave up since the protocol seems to have slight differences depending on the device used.
I eventually decided to look at the Java data upload applet used with the web service. Once the JAR's for the applet were downloaded I got to work disassembling the classes within. I found pretty soon that there was a class used strictly for serial communication and provides methods for writing commands to the device and retrieving the response which is exactly what I wanted. This class essentially wraps a dll in Java.
The bare minimum classes needed from the applet jars are shown below:
1 2 3 4 5 6 | import com.bodymedia.device.usb.Usb; import com.bodymedia.device.serial.SerialPort3; import com.bodymedia.common.applets.logger.Logger; import com.bodymedia.common.applets.CommandException; import com.bodymedia.common.applets.device.util.LibraryException; |
For ease of use the Usb class can discover serial ports any bodymedia devices are connected to. The code below simply finds ports the devices exist on and fails if there are no devices or more than one.
1 2 3 4 5 6 7 8 9 10 11 12 | Usb usb = new Usb("bmusbapex5", Usb.ANY); String[] ports = usb.getArmbandPorts(); if(ports.length < 1) { System.out.println("No BodyBuggs detected."); System.exit(1); } else if(ports.length > 1) { System.out.println("Multiple BodyBuggs detected, re-run with only one connected."); System.exit(1); } String serialPort = ports[0]; |
Once the port the device is connected on is discovered it's fairly simple to connect to it and communicate with it.
1 2 3 4 5 | SerialPort3 ser = new SerialPort3("bmcommapex5", serialPort); ser.setAddr(0x0000000E, 0xFFFFFFFF); ser.open(); ... ser.close(); |
The call to setAddr was the last key to getting communication to work. I'm not sure if the device address is constant on all devices or not. The values used were discovered by sniffing serial communication.
Dissassembling the class ArmbandCommand revealed several string constants which represent commands that can be issued to the device.
Executing a command on the device is as simple as:
1 2 |
Some testing revealed that the device responds much like a unix program by giving usage if the command is invalid (or a multi-part command). Using this I was able to compile as far as I can tell a complete list of commands the device understands. Two important commands are get and set.
| get
|
set
|
||
| age | modrunning | age | modthreshold |
| banner | modtarget | birth | modtoday |
| battery | modthreshold | bluetoothaddr | modyesterday |
| birth | modtoday | bluetoothname | offbodydelay |
| bluetoothaddr | modyesterday | boardnum | operationmode |
| bluetoothname | offbodydelay | boardseries | orientation |
| boardnum | operationmode | bootdiagnostic | password |
| boardseries | orientation | bt_link_data | personalized |
| bootdiagnostic | password | bt_local_port | productcode |
| bt_link_data | personalized | bt_mysterybyte | radiotimeout |
| bt_local_port | productcode | bt_remote_address | rrcollect |
| bt_mysterybyte | radiotimeout | deepsleeptimeout | sampletimeout |
| bt_remote_address | rrcollect | defaultconfig | secondactivity |
| capabilities | sampletimeout | dialstring | serialnum |
| charge | secondactivity | disable | sex |
| checksum | serialnum | display1224 | smoker |
| deepsleeptimeout | sex | eedebug | spewmode |
| defaultconfig | smoker | eegoalmsg | stepgoalmsg |
| dialstring | spewmode | eerunning | stepsrunning |
| disable | stepgoalmsg | eetarget | stepstoday |
| display1224 | stepsrunning | eetoday | stepsyesterday |
| eedebug | stepstoday | eeyesterday | steptarget |
| eegoalmsg | stepsyesterday | epoch | subject |
| eerunning | steptarget | gsrthresh | timezonechange |
| eetarget | subject | handed | usbspeed |
| eetoday | timezonechange | height | username |
| eeyesterday | usbspeed | hostname | viggoalmsg |
| epoch | username | hrtarget | vigrunning |
| gsrthresh | value | iapbundleseedid | vigtarget |
| handed | version | iapprotocol | vigthreshold |
| height | viggoalmsg | ipaddress | vigtoday |
| hostname | vigrunning | lastdataupdate | vigyesterday |
| hrtarget | vigtarget | led | volume |
| iapbundleseedid | vigthreshold | modgoalmsg | weight |
| iapprotocol | vigtoday | modrunning | welcomemsg |
| ipaddress | vigyesterday | modtarget | |
| lastdataupdate | volume | ||
| led | weight | ||
| memory | welcomemsg | ||
| modgoalmsg | |||
Commands available to deal with recorded data are as follows:
1 | file init | clear | size | msgerase |
I'm not sure what the exact difference between init and clear is, in my source I've been using file init which appears to only clear data memory, not configuration.
The possible types of data recorded by the device are shown using channel show:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | Chan# Name ----- ---- 0 RAWACCFW 1 RAWTSKIN 2 RAWGSR 3 RAWACCTR 4 RAWACCLO 5 RAWVBAT 6 RAWTCOV 7 RAWON 8 EE 9 MOVTSKIN 10 MOVGSR 11 MOVACCTR 12 MOVACCLO 13 MOVVBAT 14 MOVTCOV 15 MOVON 16 MADACCTR 17 MADACCLO 18 F0CROSS 19 HRATE 20 PEDO3 21 PLATEAU 22 MADECG 23 TRPEAKS 24 MOVTHETA 25 FWPEAKS 26 FCOUNT 27 MOVACCFW 28 MADACCFW 29 TCOUNT 30 LCOUNT 31 PEDO3TOE 32 TIMESTMP 33 HEARTBT 34 T0CROSS 35 L0CROSS 36 RAWECG 37 LOGSWEEP 38 MADTHETA 39 LOPEAKS 40 COMPGSR 41 RAWCGSR |
One channel listed here which is conditionally recorded is TIMESTMP. This channel records a timestamp when the button on the device is pressed while being worn. This is very useful for delimiting important events during use.
The information recorded per unit time can be modified using the record command.
1 2 3 | record show record define <index> <type> <name> <divisor> <channels[1..8]> <bytes> record commit |
The output of record show is shown below:
1 2 3 4 5 6 7 | # Type Name Div Channels Bytes - ---- -------- --- ------------------------------- ----- 0 16 V6RES1 1920 9 11 12 27 14 16 17 40 13 1 17 V6RES2 1920 20 21 23 24 38 29 37 254 12 2 18 V6RES3 1920 30 34 35 31 39 13 28 254 12 3 19 V6RES4 1920 26 18 25 10 8 254 254 254 9 4 20 UNUSED 0 254 254 254 254 254 254 254 254 0 |
Theoretically every channel available may be recorded. However to define a new record the bytes parameter must be calculated for each record index. The bytes parameter is calculated as follows; where n is the number of non-empty channels (maximum of 8 channels) in the record:
$$bytes = \left\lceil\frac{n\cdot 12}{8}\right\rceil + 1$$
The divisor is used to modify how often data is recorded for the particular record. The default value of 1920 produces records once per minute, halving this value produces records once every 30 seconds. Any given interval may be calculated using divisor of 32 = 1 second.
The transient command appears to be used for getting simple overview information about recent data for use with the mobile phone application. Two parameters are available: get and reset.
The last important command I've found is retrieve PDP, which will return information about each channel's recorded data including a starting timestamp. Example output is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf00000000080c0000000300200780EE fff0bf232 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf00000000090c00000005002003c0MOVTSKIN fff8a58b58c28cc SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf000000000a0c0000000300200780MOVGSR fff56857b SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf000000000b0c00000005002003c0MOVACCTR 8ad8a986d8ad885 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf000000000c0c00000005002003c0MOVACCLO 9139088f38ef913 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf000000000d0c0000000300200780MOVVBAT bf4bedbee SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf000000000e0c00000005002003c0MOVTCOV fff7967a87b87c6 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf00000000100c00000005002003c0MADACCTR 00003903d0420ec SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf00000000110c00000005002003c0MADACCLO 00002b02f0290d5 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf00000000120c0000000300200780F0CROSS 00001a06f SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf00000000140c0000000300200780PEDO3 000000000 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf00000000150c0000000300200780PLATEAU 000000000 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf00000000170c0000000300200780TRPEAKS 0000d3130 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf00000000180c0000000300200780MOVTHETA 8007f8823 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf00000000190c0000000300200780FWPEAKS 00009e0e2 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf000000001a0c0000000300200780FCOUNT 00004814a SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf000000001b0c00000005002003c0MOVACCFW 6cc6c86a56b7723 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf000000001c0c0000000300200780MADACCFW 00002a08f SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf000000001d0c0000000300200780TCOUNT 000083193 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf000000001e0c0000000300200780LCOUNT 000070148 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf000000001f0c0000000300200780PEDO3TOE 000000000 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf00000000220c0000000300200780T0CROSS 00003f092 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf00000000230c0000000300200780L0CROSS 000026091 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf00000000250c0000000300200780LOGSWEEP 0004e76c2 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf00000000260c0000000300200780MADTHETA 00136e3a3 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf00000000270c0000000300200780LOPEAKS 0000a0107 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf00000000280c00000005002003c0COMPGSR fff00a01301b026 SESSION-BEGIN0502Firefly2_00000000e00e3ec50b48bbf0000000068100000000100000002DIAGNSTC 1354009655 5 1 |
Each SESSION-BEGIN statement contains a hexadecimal string which contains at least the timestamp recording was started at, this is shown below delimited by pipes. I'm not sure what other information is recorded in the hexadecimal string:
1 | SESSION-BEGIN0502Firefly2_00000000e00e3ec|50b48bbf|00000000080c0000000300200780EE |
Each line following the SESSION-BEGIN statement contains a hexadecimal string which represents each data point recorded for the channel. Data points are represented as unaligned 12-bit integers.
There are several other commands which I've not tested yet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | bin show bin set <index> <value> bin reset log text <listen_timeout> <text> log data <listen_timeout> <mfg> <units> <data ...> a <audible-number> <count> wav <audible-number> <count> visual <visual-number> <count> word address [value] long address [value] remind N at hhmm remind N cancel remind list remind N gettext remind N settext <text> noop system reboot |
The remaining logic for the tool I've written for downloading data and clearing memory is shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | ser.writeCommand("get lastdataupdate"); Pattern lastUpdateRegex = Pattern.compile("Last Data Update: ([0-9]+)"); Matcher matcher = lastUpdateRegex.matcher(ser.readResponse().toString()); matcher.find(); String lastUpdate = matcher.group(1); String logPath = String.format("%s.log", lastUpdate); System.out.printf("Writing data to: %s\n", logPath); FileWriter out = new FileWriter(logPath); ser.writeCommand("retrieve PDP"); out.write(ser.readResponse().toString()); out.close(); System.out.println("Clearing device memory and updating timestamps."); ser.writeCommand("file init"); ser.writeCommand(String.format("set lastdataupdate %d", System.currentTimeMillis() / 1000L)); ser.writeCommand(String.format("set epoch %d", System.currentTimeMillis() / 1000L)); |
This will get the last timestamp data was cleared at to name the output file. Output of retrieve PDP is then written to this file. Once this is complete file init is executed to clear recorded data and finally the device's time and last data clear times are updated.
To avoid the same fate as FreeTheBugg, I am not packaging any of the jars this program needs to run, instead you'll need to download and place them in the directory bodybuggbypass_lib which must be in the same directory as the executable jar bodybuggbypass.jar:
- armband-applets-1.11.0-SNAPSHOT.jar
- common-applets-1.11.0-SNAPSHOT.jar
- common-shared-1.11.0-SNAPSHOT.jar
Full source presented here as well as sample data and a few other tools can be found at: https://github.com/bemasher/BodyBuggBypass


November 28th, 2012 - 15:36
Interesting. Not sure I can make it work, but I love the idea that you are opening this device.. I need a device such as this, if this “BodyBug” is the best one or most cost effective one I’ll figure out how to do this. be well.
November 28th, 2012 - 18:45
The link above:
common-applets-1.10.0-SNAPSHOT.jar, is not downloading a file, just taking me to the site login. I do have a username, psw put still no file.
November 28th, 2012 - 19:42
There is a double slash in the download link, just copy the download link and remove the double slash :)
Just a silly error :)
November 28th, 2012 - 21:44
I corrected the link. Grover was right, one too many slashes in the url.
November 28th, 2012 - 23:30
So as I understand it, what you’ve managed to do is to pull raw sensor data from the device, leaving you with a fairly useful data logger. Any plans to try and interpret that data into calories burned, or anything else? (IE duplicate their services functionality)
November 28th, 2012 - 23:37
Not currently. I’ve written a parser for the data and discovered a few of the divisors and offsets to convert recorded data to meaningful values (like temperature to degrees celcius or fahrenheit) but beyond that I have not written any classifiers or algorithms for determining activity from the data.
I’m primarily using this to record during sleep, so my classifiers will have a significantly different set of criteria than what their service offers.
November 29th, 2012 - 08:49
I have an old BodyBug with a bad batterie and I’m interested in using its sensors. Is there a way to read the live sensor data?
November 29th, 2012 - 19:12
Just a note, but presumably the gt and lt signs in your code, the ones with the ampersand and semicolon, need changing to their actual symbols. Of course I can’t just TYPE the symbols I mean, since I may well end up with the problem I’m telling you about. Stupid nested encoding!
Here, I mean your quoted code says “<” where it should be the “<" symbol. See if that works!
November 30th, 2012 - 00:59
Got it fixed now, forgot the syntax highlighting plugin takes care of that. The editor I wrote this in doesn’t handle any of that, so i used the html encodings.
November 29th, 2012 - 19:13
It didn’t work! Hah!
December 1st, 2012 - 15:21
hah it’s cool you were able to reverse engineer the sensor. i guess the website is another revenue stream for the company, be cool if you could open source a similar UI for people to run their own server.
February 2nd, 2013 - 22:08
Did you figure out which channel represents skin temperature and ambient temperature? I’m also interested in the heat transfer sensor data that should be somewhere in here.
Thanks
February 3rd, 2013 - 02:46
Either RAWTSKIN or MOVTSKIN appear to be the skin temperature, though from my logs it doesn’t appear as though MOVTSKIN is any different from RAWTSKIN. As far as ambient temperature goes I don’t believe it records this. For my purposes I’ve been using a maxim digital temperature sensor and a custom 1-wire to RS232 board I made (in the same room while sleeping and such).
February 13th, 2013 - 09:16
When I try to run it (using the 1.11 version of the jars, and “java -jar bodybuggbypass.jar”), I get the following error:
“Exception in thread “main” java.lang.NoClassDefFoundError: com/bodymedia/common/
applets/CommandException”
“Caused by: java.lang.ClassNotFoundException: com.bodymedia.common.applets.CommandException”
Is this due to the new jars? Or just me doing something wrong?
March 11th, 2013 - 21:27
I am receiving the same error.
April 14th, 2013 - 03:05
I’ve tried running it with the new version of the jars and I get the same error. I haven’t had a chance to debug things yet though. Might be that they’ve caught wind of this project and encrypted the jars so I can’t call the methods I’m using anymore.
April 10th, 2013 - 13:09
i got the error
Could not find the main class: com.bypass.BodyBuggBypass
April 14th, 2013 - 23:18
This is the error I get.
Exception in thread “main” java.lang.UnsupportedClassVersionError: com/bypass/BodyBuggBypass : Unsupported major.minor version 51.0
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)