Saturday, July 18, 2009

Searching for a Calorie Graph

Kenneth did some academic paper research and found that our EE column = Energy Expenditure which he thought was the crude calorie calculation on the device.

I've plotted EE for an ~12 hr period in the top half of the graph above, and in the lower half is the calorie graph from the GoWearFit web service. I'd say Kenneth is right!

The web service obviously does slightly different processing. I'm also not sure of the units for EE - the values range from ~113 - 1050, whereas the calorie chart range from ~1.5 - 7.5 calories-per-minute. Maybe it is just a divide-by-100 and the more sophisticated web service processing is simply that much more accurate, but I'm not sure. A summation over EE/100 gives a much larger calorie count than the web service.

Thursday, July 16, 2009

Memory Clear Ability

I was surprised to hear that if you have a wristband display for your GoWearFit armband, and don't subscribe to the web service, that you can no longer use the armband once its memory is full.

If true, this seems rather unfair. Also poor business practice - the person in question wanted to buy a second device+wristband to replace a lower quality competitor unit, but was unwilling to pay for two unused web subscriptions. I seem to be driving multiple sales here...

I tracked down the command to clear the device memory of sensor data the same way the BodyMedia app does it.

A new bmhack update (bmhack-z717a) is available with a command line '--clear' command.
See the updated "release post" for the download link.

This feature, like everything else, is highly experimental. I have not yet received a single end user report - please provide them if you've used bmhack.

UPDATE 7/17: New version z717a. TESTED UNDER WINDOWS. More robust serial I/O.

UPDATE 7/18: New version z718a. New data record type added, thanks to Freak. Enhanced device memory dump capabilities for improved debugging.

UPDATE 7/18: New version z718b. Fixed limited memory bug (thanks again Roy!). Software can theoretically now handle a completely full device.

Wednesday, July 15, 2009

A Trivial Activity Classifier (that works!)

With low expectations, I thought I'd throw a simple classifier at the data records to see if it could differentiate between sleep, mundane activity, and strenuous activity.

The results surprised me!

Top: GoWearFit activity graph, Middle: k-means classifier, Bottom, GoWearFit Sleep Graph. Click graphic for full resolution.

I used a simple k-means clustering function out of the Open Source "R" statistical package (via the Python RPy interface). I did _NO_ normalization on the data at all. I just read in the tab-delimited data, stripped off the date fields, and fed it to the classifier.

Each 1-minute data record became a point in 27-dimensional space, and I told the algorithm to divide them into 3 classes (hoping they would end up being "sleep", "vigorous", and "moderate"). And it simply worked!

The black-grey-and-white graph above is the output of the classifier for each minute in a ~18 hour period. The blue graphs are the graphs provided by the GoWearFit web report, aligned manually.
The peaks at 10:45am and Noon were bike rides/walks. I took an afternoon nap, and went to sleep around 11:30pm.

Monday, July 13, 2009

Getting the Sensor Data Right

Over the past couple days I've made considerable progress on the GoWearFit command line extraction tool. Be sure to get the latest version in my edited release post below.

The current version (z712a) 100% parses the device data memory, and there are only a few fields I still don't understand. It also adds extracts date and time for the samples, and exports everything into one nice tab-delimited spreadsheet table.

I added an example tab-delimited output file into the release post as well.

Finally, I discovered that FileDropper hosting is not quite as free and forever as they claim. So a lot of earlier data postings have disappeared. Most are obsolete, but I thought I'd mention it.

This is still very untested. Let me know if works or not!

Saturday, July 11, 2009

Command Line Extraction Tool

PRE-ALPHA: bmhack is a command line extraction tool which will extract and parse data from your (new model) BodyMedia ArmBand device into a variety of formats, including a tab-delimited spreadsheet-compatible file, raw packets in ASCII HEX, and a full Python cPickle binary dump.

THIS TOOL WILL NOT HELP YOU if you are looking for something to give you calorie counts or activity graphs. It is in a very early state, and is only useful for technical people who wish to attempt to experiment with and analyze the raw data or learn more about their BodyMedia device.

You must install Python 2.6, and the Python numpy libraries.
Windows: Python 2.6.2 from http://www.python.org/download/releases/
Windows: Numpy for 2.6.2 from http://sourceforge.net/projects/numpy/files/

Download bmhack VERSION z718b [June 18 update] from:
http://www.mediafire.com/?sharekey=26cf7e726ccc34f061d4646c62b381cbfe370e2ca4df676ac95965eaa7bc68bc

Windows: Launch bmhack with C:\python26\python.exe bmhack.py

There is also an example of several days of CSV output in that MediaFire directory, filename z705a.csv

It is verified to work under Linux, and should work under Windows (let me know?).

If you want to experiment with the less understood parts of the protocol, download the known_requests.pickle from the same site and move into your current directory when you run bmhack so you can use the --fromSerialFull command.

MISSING
-[DONE VERSION z713a] Timestamps - Pretty sure I know where time offsets live before each data table. I output them to CSV, but can't intepret them yet. I expect a global time field somewhere?

-"Register" data - user attributes like height, serial number, etc are probably stored outside of the main data memory area. I haven't really looked.

-Other

In addition to being a command line program, you can also import bmhack into a Python interpreter and manipulate the data or the device directly. There are an array of analysis and display python functions.

The bmhack.py code and comments also contain my (incomplete) documentation of the ArmBand data structures. Look at ReadAllStruct() to start.

Linux: How to force the ftdi_sci driver to recognize the BodyMedia Vendor/Product IDs:

sudo modprobe ftdi_sio vendor=0x11f8 product=0x0007

Verify that a new '/dev/ttyUSB*' device has been created.


./bmhack.py -h
THIS CODE IS DECLARED BY THE AUTHOR TO BE IN THE PUBLIC DOMAIN.
NO WARRANTY EXPRESSED OR IMPLIED OF ANY KIND IS PROVIDED.
USAGE: ./bmhack.py [SOURCE] [TARGET] [TARGET] ...
Retrieve and convert data from a BodyMedia armband device
Convert from a packet source to one or more target formats
A packet source can be a live BodyMedia USB device, a cPickle
dump file of packets, or a capture file form a serial port sniffer.

SOURCES - specify only one
--fromSerial= Extract data by quering a live USB device on the specified serial port
--fromDump= Read packets from a cPickle dump file saved previously
--fromFSPM= Parse packets from a 'Free Serial Port Monitor' by HDD Software. is an export of the RequestView window.

TARGETS - specify one or more
--toDump= Write cPickle dump of all packet data
--toCsv= Write a Spreadsheet-compatible tab delimited file of most of the data
--toPackets= Write parsed packets in human-readable HEX format

NOTE: Specify '-' as a filename to toCsv and toPackets commands in order to write to stdout instead of a file

Friday, July 10, 2009

HTTP Sniffing

I've tried USB sniffing, serial snooping, but some how neglected network sniffing of the HTTP traffic. Back to my old friend Wireshark nee Ethereal.

VERY promising results which hopefully will help solve the record field alignment puzzle.

This is obviously the technique that Henry at BodyBuggHacks used, at least for his initial postings (his later partial Java code posting indicated direct use of the serial port or a BodyMedia Java API). The protocol has changed a bit - field names are different etc, but the structure is the same.

Here is an edited sampling of the response to an HTTP POST during a sync:
....sr.6com.bodymedia.common.shared.armband.comm.UploadRequestO...r.0....L..seri
alPortBeant.,Lcom/bodymedia/device/serial/SerialPortBean;xr.,com.bodymedia.commo
n.shared.comm.DataRequest...).z.n...L..handlert..Ljava/lang/String;L..mySessionI
Dq.~..xpppsr.*com.bodymedia.device.serial.SerialPortBean.\.qBI.K...Z..cradle_mod
eL..ageq.~..L..batteryq.~..L..birthdateq.~..L..boardq.~..L..boardSeriesq.~..L..c
alibrationq.~..L..channelListq.~..[..channelst..[BL..epochq.~..L..gsrThresholdq.
~..L..handq.~..L..heartTargetq.~..L..heightq.~..L..memoryq.~..L..productCodeq.~.
.L..recordsq.~..L..serialq.~..L..sexq.~..L..smokerq.~..L..subjectq.~..L.
systemTimeq.~..L..uptimeq.~..L..versionq.~..L..volumeq.~..L..weightq.~..xp.t..Ag
e: 99 years ^M
t.;Battery voltage: 4.160 Max voltage: 4.200 Charged to 94%^M
t..Series: 16^M
t./ 0: RAWACCTR INUSE input: 698 output: 1763^M
1: RAWACCLO INUSE input: 707 output: 2343^M
2: RAWACCTR INUSE input: 2094 output: 1212^M
3: RAWACCLO INUSE input:
2095 output: 2930^M
4: RAWACCFW INUSE input: 988 output: 2410^M
5: RAWACCFW INUSE input: 1964 output: 2818^M
6: RAWGSR INUSE input: 0 output: 1366^M
7: RAWON INUSE input: 0 output: 1367^M
8: RAWTSKIN INUSE input: 40000 output: 1416^M
9: RAWTCOV INUSE input: 40000 output: 1416^M
10: RAWTSKIN INUSE input: 25002 output: 2048^M
11: RAWTCOV
INUSE input: 25002 output: 2048^M
t..Chan# Name ^M
----- ---- ^M
0 RAWACCFW^M
1 RAWTSKIN^M
2 RAWGSR^M
3 RAWACCTR^M
4 RAWACCLO^M
5 RAWVBAT^M
6 RAWTCOV^M
7 RAWON^M
8 EE^M
9 MOVTSKIN^M
10 MOVGSR^M
11 MOVACCTR^M
12 MOVACCLO^M
13 MOVVBAT^M
14 MOVTCOV^M
15 MOVON^M
16 MADACCTR^M
17 MADACCLO^M
18 F0CROSS^M
19 HRATE^M
20 PEDO3^M
21 PLATEAU^M
22 MADECG^M
23 TRPEAKS^M
24 MOVTHETA^M
25 FWPEAKS^M
26 FCOUNT^M
27 MOVACCFW^M
28 MADACCFW^M
29 TCOUNT^M
30 LCOUNT^M
31 PEDO3TOE^M
32 TIMESTMP^M
33 HEARTBT^M
34 T0CROSS^M
35 L0CROSS^M
36 RAWECG^M
37 LOGSWEEP^M
38 MADTHETA^M
39 LOPEAKS^M
40 COMPGSR^M
41 RAWCGSR^M

[...SNIP...]

SESSION-BEGIN0502Firefly2_00000000af1bc00e04a54d7ca00000000090c0000046500200780MOV
TSKIN^M
93494295095895d96396696996c96f97898098498598798998798598899199d9a29a099f9a29a29a
19a39a29a39a39a099d99b99c99f9a19a49a69a59a59a79a99ab9ad9b09b09b19b39b69b89b99b89
b99bb9bd9bf9c09bd9bd9c09bf9be9be9be9be9bf9c19c39c59c79c89ca9ca9ca9ca9ca9c89c69c5
9c59c69c79c99c99c99c99c99c69c39c09c19c59c89cc9ce9d09d29d39d29d29d39d49d79db9dd9d
d9df9de9dd9de9dc9d99d89d99da9de9e09db9d29ca9c59be9b99b59b19ae9ab9aa9aa9aa9ae9b29
b39b49b49b39b39b29b29b29b39b39b39b49b49b49b49b59b59b69b69b79b89b99ba9bb9bb9bb9bb
9bc9bc9bd9bd9bd9bf9bf9c09c09c19c29c19c19c29c29c39c39c49c59c59c29be9bc9bb9ba9bb9b
b9bb9ba9ba9ba9ba9bb9bb9bc9bd9bd9be9be9be9c09c29c49c59c69c59c49c59c69c69c69c79c79
c69c69c69c69c69c59c59c59c59c49c49c39c39c39c29c29c29c39c49c59c69c79c89c99ca9ca9ca
9cb9cc9cc9cc9cc9cd9cd9cd9cc9cc9ca9c79c59c49c59c
79c79c89c89c99ca9cb9cb9cb9cb9cb9cb9cb9cb9cb9cb9cb9ca9c99c79c69c59c69c89ca9cc9cd9
cd9cd9cd9cd9cd9cd9cc9cc9cc9cc9cb9cb9cb9cb9cb9cb9ca9ca9ca9ca9ca9ca9ca9ca9ca9cb9cb
9cc9cc9cd9cd9ce9ce9cf9cf9d19d29d49d59d79d89d89d99da9da9db9dc9dd9de9df9df9df9dd9d
c9dc9dd9dd9de9de9df9df9e09e09e09e19e09dd9dc9dd9de9df9e09e09e19e19e19e09e09e19e29
e39e59e69e69e79e89e99e99ea9e99e99e79

[...SNIP....]

t.DThere have been 1247284976 seconds since the epoch. Time zone -9.^M
t.&gthresh offbody: 1376 onbody: 1371^M
t..Handed: Left ^M
t..heartrat
e targets lo: 0 hi: 0t..Height: 67 inches^M
t..FILE: 78339 bytes of 1048576, 7 percent used^M
t..Product code: 173^M
t..# Type Name Div Channels Bytes^M
- ---- -------- --- ------------------------------- -----^M
0 16 V6RES1 1920 9 11 12 27 14 16 17 40 13^M
1 17 V6RES2 1920 20 21 23
24 38 29 37 254 12^M
2 18 V6RES3 1920 30 34 35 31 39 13 28 254 12^M
3 19 V6RES4 1920 26 18 25 10 8 254 254 254 9^M
4 20 UNUSED 0 254 254 254 254 254 254 254 254 0^M
I am very excited by that table of record types (16-19) to channel numbers. I had been parsing that table, but couldn't interpret the numbers before. I believe this gives me the numbered field name to record mapping I've needed.

I am willing to send you a full sample if you request via e-mail centibenzo at gmail.

EDIT (7/11): BREAKTHROUGH:
1. The device stores data as 12-bit packed fields, not byte-aligned.
2. The "table headers" with apparent column labels do NOT correlate with the columns (baffling).
3. The actual record layout is obtained from the record type table (seen above in HTTP, but also in device mem) indexed into ANOTHER set of field names retrieved by individual request packets. Weird.

I should have full data extracts soon.

Monday, July 6, 2009



BodyMedia Reverse Engineering

centibenzo @t gmail d.t com

What

I am documenting this work in the hopes that it spurs BodyMedia to create a
Linux driver, or a third party to develop a similar Open Source Linux application.

A partial reverse engineering of the BodyMedia ArmBand Mini device protocol was accomplished.

A Python library was written to communicate with the device, and to parse the data.
This library is compatible with both Linux and Windows (barely tested on Windows).

There is a lot of work to do before this system becomes useful.

Accomplished



One night of RAW graphed data records, left-to-right, showing brief
activity before sleep, quiet, and more activity after waking.


  • Correct Linux driver and modprobe line discovered to make device function as a USB Serial port.

  • Basic structure of serial protocol decoded.

  • Memory dumps from device can be obtained by serial protocol.

  • Most data structures in memory, including data tables, can be parsed.

  • Crude "activity graph" can be made from the raw data tables which does show activity over time
To do

  • Find and interpret data timestamps (presumably in each data table header)

  • Identification of type and size of individual data record fields

  • Identification of actual MEANING of individual data record fields

  • Development of heuristics to translate data into indentification of sleep, active, etc (Ph.D thesis)

Discoveries

  • Device uses an FTDI USB<->Serial chip, compatible with Linux ftdi_sci driver.

  • Under Windows, system uses a stock FTDI driver with changed Vendor and Product
    ID's to present a virtual com port to Java(?) software integrated with Web
    Browser.

  • Protocol is far more complex than the USB/Serial application would
    require. It is geared for the wireless RF link (sync bytes, etc). I suspect
    it derives from some RF vendor protocol.

  • ??? Device logs a data record only once per minute (?). A little disappointing - perhaps the clinical models log more. Or maybe it is variable rate???

  • ??? Device has 200 kByte of memory for data logging???



Platform

  • Device:

  • Ubuntu Linux 9.04

  • Windows XP

Usage

Python Code: SEE NEWER VERSION ABOVE
Command List: SEE NEWER VERSION ABOVE
(Command list must be renamed to known_requests.cpickle for FullSerialDump() function.)

Linux: How to force the ftdi_sci driver to recognize the BodyMedia Vendor/Product IDs:


sudo modprobe ftdi_sio vendor=0x11f8 product=0x0007


Verify that a new '/dev/ttyUSB*' device has been created.

Windows: You don't need to install an FTDI driver if you already have the
GoWearSense driver installed. When you plug in the device, you will see a new
COM14 (or similar) port appear which you can use within Python.

Examples of how to use the python library.


import bodylib as bl
import cPickle

# Open, query, and close the device
packets=bl.FullSerialDump(serialName="/dev/ttyUSB0")

# Let me print all the packets sent and received
bl.PrintPacket2(packets)

# Parse packets and print all data in appropriate format in text tab-delimited format to file
# This is a good human and spreadsheet readable dump of most of the device state.
bl.SaveStructTabDelim(packets, "/tmp/foobar.txt")

# If filename not specified, outputs to stdout for your inspection
bl.SaveStructTabDelim(packets)

# I usually save my device dumps in cPickle format for future reference
cPickle.dump(packets,open("mydump.cpickle","w"))

# Assemble the received data packets into a unified memory buffer
mem=bl.AssembleDataFromPackets(packets)

# Pretty Print the memory buffer. We use 46 bytes/line to be periodic with the most common data record
bl.HexPrintMod(mem, 46)

# Parse memory data structures, including data records
tables=bl.ReadAllStruct(mem)

# Create image from data table
bl.ToImage(tables.s4).show()



Protocol Structure

Serial baud rate must be set to 921,600 bps.

The protocol is packet based, with packets of length 66 bytes including headers
and sync bytes.

The computer sends a Request packet ("Req" in my code), to which the device
responds with one or more answer ("Ans") packets.

All request types expect only a single "Ans" answer packet, except for requests
to read from the data memory. Data memory requests specify a starting memory
offset and a length, and receive as many 66-byte packets as required in
response. I call this "Burst" mode in the code.

All packets have a simple modulus 256 checksum byte. This is a simple sum and
modulus of all bytes in the packet, excluding sync bytes (leading or tailing
"AB" or "BA").

The device appears to ignore the checksums sent to it (I don't bother computing
it). However, the device always gives me correct checksums.

The packet begins with a leading sync byte 0xAB. Then a rather elaborate header
almost all of which is unused or invariant (RF protocol related?). Most
importantly, a "command" byte defines the type of request. There is also some
form of sequence number byte on Req packets, but not device-generated Ans
packets. The sequence number appears to be ignored by the device.

The "command" byte specifies the requestion type, and is the key element of the
header. Note that in my code, I call this the "Bank" byte, because I initially
thought it was a device selection address.

After the command and (sequence number if Req), the packet payload contains
command-specific data.

The command byte to read from the main data memory is 0x82. The command byte
for the Answer packets from the device is 0x02 (in my code I stip the high bit
from the command/bank for clarity, so you may see 0x02 Requests).

The payload to read from the main data memory consists of a 4-byte LSB offset
uint32 and a 2-byte LSB length uint16.

In addition to the block of main data memory, there are a number of other
values which can be queried using other command types. These usually return
short strings or single binary values. Examine the packet lists to find some
examples.

Tools

"Free Serial Port Monitor" by HDD Software is GREAT - you can use this to snoop
on the virtual serial port under Windows, and export the conversation to a file.

Under Python I've created a parser for the "Free Serial Port Monitor" text
export of the "RequestView" window. See bodylib.ParseFile().

Debug FS is very useful for monitoring USB traffic under Linux.

sudo mount -t debugfs none_debugs /sys/kernel/debug
cd /sys/kernel/debug/usbmon/
cat 0u


I use ipython, Python 2.6.2, numpy, PIL python libraries. If you install on
Windows you'll have to fetch each of them, or comment them out of the bodylib library.

See Also http://bodybugghacks.blogspot.com/
(apparently an attack at the Java API level, but the author is not forthcoming on details)

Units of Measure - from http://bodybugghacks.blogspot.com/2008/07/code.html?showComment=1225132980000#c1498831675573385775

Useful to determine figure out field structure?

Accelerometers - The SenseWear® WMS Armband (2-axis) - The SenseWear® WMS Armband Mini (3-axis) Calibrated range is +/- 2.0g
The minimum resolution is 0.01g
Two-standard-deviation accuracy of +/-0.05g (longitudinal axis) and +/-0.06g (transverse & forward axis), up to 1.0g
Two-standard-deviation accuracy of +/-12.0% of expected value otherwise
Heat Flux
Calibrated Range is 0.0 W/m2 to 300.0W/m2
A minimum resolution of 1.0W/m2
Two-standard-deviation of +/-10.0W/m2 at heat flux less than 50.0W/m2
Two-standard-deviation of +/-35.0% of expected value otherwise
Galvanic Skin Response
Calibrated range is 56.0KΩ to 20.0MΩ (50.0 nSiemens – 17.00 uSiemens)
Two-standard-deviation accuracy of +/- 7.0 nSiemens up to 233.34 nSiemens reading
Two-standard-deviation accuracy of +/- 3.0% of expected value otherwise
Skin Temperature
Calibrated range is 20.0ºC to 40.0ºC
A minimum resolution of 0.05ºC
Two-standard deviation accuracy of +/- 0.80°C
Classification of the device, as per 93/42 directives: IIa (rule 10)
Certification procedure : 93/42/EEC, Annex VI, VII.
Transmit Power Class 8 - Less than 10mW output power
Duty Cycle Class 4 - permitted to operate at 100% duty cycle
Receiver Class 3 - Standard reliable SRD communication media

----