Whitney Industries

Simple Pebble Accelerometer Data Acquisition

(All source available here)

I not so recently bought a Pebble watch and had been planning for a long time to learn how to program it. One of the more interesting programmatic interfaces on the watch is access to the accelerometer data. I do not have a clear goal in mind yet what I want to do with this data, but whatever I end up doing, it seems likely that it will be useful to have a corpus of data on a server somewhere to test ideas about inference/prediction/etc. Also, since I already do quite a bit of data analysis for my day job using a Python stack (numpy/pandas/statsmodels), I figured it would also be good to have my own project on which I could experiment with alternate toolsets (R, julia).

Below is the process I have gone through so far to collect raw pebble accelerometer readings. It is broken down into Pebble, Android and server-side application components. Ideally, there will be more follow up posts detailing my findings with the data I capture with this setup.

Accel reading dataflow

AccelData + Data Logging on Pebble

The Pebble API provides an interface to register a handler to receive elements that specify an X, Y, and Z-axis accelerometer measurement. I wrote a simple watchapp that calls accel_data_service_subscribe to receive AccelData elements. I also specified sampling of the accelerometer at the lowest rate for now, 10Hz, until I see the need to collect more data.

New to the 2.0 SDK, Pebble has a handy feature to stream data to your Android or iOS app via the Data Logging API. The nice thing about this is that you can push data to a logging session and it will eventually find its way to a phone app that has registered a handler to receive data from your watchapp.

The one caveat for sending stuff into your Data Logging session is that sending too many messages tends to clog the pipe at some point and message receipt will start to fail on the Android side (see the discussion here). For this reason, I do not send every AccelData instance individually but instead send a whole batch in a single log message. Since AccelData instances are delivered in batches to the accel_data_handler anyway, it works out quite well to just forward all the samples from a single accel_data_handler callback to the log session. These batches can be logged using the DATA_LOGGING_BYTE_ARRAY data type.

One small problem with this strategy is that a particular log session does not allow for dynamically sized byte array lengths. It turns out that this is not such a problem, in current Pebble firmware versions (2.0.2, for example), I have measured the number of AccelData samples per callback to (almost?) always be 18. Thus we can create a data logging session with array length: sizeof(AccelData)*18 and then just forward the whole block of AccelData samples in a callback directly to the log session. This looks like this:

static void accel_data_handler(AccelData *data, uint32_t num_samples) {
    if (num_samples >= 18) {
        DataLoggingResult r = data_logging_log(accel_log, data, 1);
    }
}

Any block of fewer than 18 samples get dropped, as well as any samples over the initial 18 in a block. At some point, I will quantify how many samples are dropped in practice in a day, but I suspect the number is pretty small.

Receiving Data in Android

So now we have the watch periodically sending packets of 18 accelerometer readings to whatever subscribes to that Data Logging feed. I will now describe the stubby Android app I wrote to receive that data.

At its simplest, one just instantiates and registers a PebbleKit.PebbleDataLogReceiver with a receiveData callback:

mDataLogReceiver = new PebbleKit.PebbleDataLogReceiver(APP_UUID) {
        @Override
        public void receiveData(Context context, UUID logUuid, UnsignedInteger timestamp, UnsignedInteger tag,
                                byte [] data) {
            // misaligned data, just drop it
            if (data.length % 15 != 0 || data.length < 15) {
                return;
            }

            // deserialize readings from byte array
            for (AccelData reading : AccelData.fromDataArray(data)) {
                // send reading to server or store it locally or something
            }
        }
    };
PebbleKit.registerDataLogReceiver(this, mDataLogReceiver);
PebbleKit.requestDataLogsForApp(this, APP_UUID);

In this example, AccelData is a class I implemented in my Android app that mirrors the C struct on the Pebble. AccelData.fromDataArray just breaks up an array of multiple readings into a list of objects. We should then do something with the readings, like post them to a web server or something.

With this snippet, we could just fire this up in an Activity and start handling data, but we have 2 additional goals:

  1. We shouldn’t need an Activity view lying around just to collect data
  2. We don’t want to collect data too often and run down the battery

To address these, we start an IntentService which registers a new Data Logging session, waits for some readings to come in, and then ends the session. This IntentService can be periodically stated by the AlarmManager as detailed here.

This works because AccelData readings can be buffered both on the Pebble and in the PebbleKit app that mediates communication between our app and the watch. We choose the shortest “inexact repeating” interval that AlarmManager supports: AlarmManager.INTERVAL_FIFTEEN_MINUTES. So far, my experience has been that waking up at this interval yields most of the accelerometer readings, but I still need to quantify this.

Finally, in the actual receiveData handler, we serialize readings back into a JSON array and just post it to a data collection server. So to put it all together, the Android side has a periodically running Service that starts a Data Logging session, drains all the readings from the PebbleKit app, then unregisters and goes back to sleep for some time.

Server Side Data Processing

This is the least developed component right now. It consists of an unsecured flask app that dumps a received bundle of readings into MongoDB. These timestamped readings can be loaded into a timeseries data structure like a pandas DataFrame.

Once enough data is collected, here are a few possible interesting applications:

  • Physical activity tracking
  • Sleep monitoring
  • Personal identification/fingerprinting
  • Typing performance (??)

Source

Source code for all these components is available on github:

pebble-accel-analyzer