Skip to content
Chris Lenk edited this page Apr 27, 2016 · 8 revisions

Data routes are the new mechanism for manipulating sensor data. They replace the old callback driven setup with a Java DSL for expressing what to do with sensor data. You will be using these routes to stream or log data, performing on board data processing, and programming the board to react to sensor activity.

Routes are created by calling the routeData method from one of the sensor classes. From there, you use the SourceSelector class to choose the origin of your data source. After selecting your data source, you can then attach various signal components to your route to manipulate the data flow before finally committing the route to the board.

This guide will cover the basics of data routes and will provide you with the foundation needed to build simple routes. More advanced features and complex examples will be covered in later guides.

Streaming Data

For starters, lets break down the switch example from the MetaWearBoard Class page.

try {
    Switch switchModule= mwBoard.getModuleController(Switch.class);
    switchModule.routeData().fromSensor().stream(SWITCH_STREAM_KEY).commit()
} catch (UnsupportedModuleException e) {
    
}

First off, we will need to retrieve a reference. Attaching a subscriber to the route is done by calling subscribe. The subscribe function takes in a MessageProcessor object which processes each message received from your data source. In a similar vein, you can replace the subscriber with a logger to instead store data to the board's flash memory and retrieve it later.

mwBoard.routeData().fromSwitch().log(new DataSignal.MessageProcessor() {
    @Override
    public void process(final Message msg) {
        Log.i("ExampleActivity", String.format(
                "%tY%<tm%<td-%<tH:%<tM:%<tS.%<tL: Switch %s" ,
                msg.getTimestamp(),
                msg.getData(Boolean.class) ? "Pressed" : "Released"));
    }
}).commit();

Messages

In the MessageProcessor class that you override, the process function has one parameter of type Message. The Message class is a generic container that wraps the raw sensor data and also attaches a few attributes to the data. Extracting data is done by calling Message.getData and passing in the type that the data should be interpreted as.

In the above switch example, switch sensor data can be interpreted as both a boolean or a byte thus the valid parameters for the getData function are either Boolean.class or Byte.class. More specifics about the different Message classes are provided here.

Data Processor

Another signal component you can add to the route is a data processor. Data processors manipulate data on-board before passing the output to the next route component. There are two different types of data processors: filters and transformers. Both are added to the route via the process function and configured either with a ProcessorConfig object or string URI. Specifics on the URI format are provided here.

Filter

Filters act like gates, controlling the flow of data in your route by only allowing data through when certain conditions are met. Let's further expand on our switch example by adding a comparison filter to remove button releases from our subscriber.

import com.mbientlab.metawear.processor.Comparison;

mwBoard.routeData().fromSwitch()
    ///< Button presses correspond to a 1 in the sensor data
    .process(new Comparison(Comparison.Operation.EQ, 1))
    .subscribe(new DataSignal.MessageProcessor() {
        @Override
        public void process(final Message msg) {
            ///< Add this here to ensure the subscriber only receives press events
            if (!msg.getData(Boolean.class)) {
                Log.e("ExampleActivity", "This log call should never be executed");
            }
            Log.i("ExampleActivity", "Button pressed");
        }
    })
.commit();

As mentioned above, the process function could have been called using a string URI to configure the filter. Below is the string URI equivalent of the Comparison object passed in:

.process("comparison?operation=eq&reference=1")

Transformer

Unlike filters, transformers modify the incoming data, possibly converting it into different forms. Instead of filtering out button releases, lets transform the button data into a count of how many times the button was pressed.

import com.mbientlab.metawear.processor.Accumulator;

mwBoard.routeData().fromSwitch()
    .process(new Accumulator())
    .subscribe(new DataSignal.MessageProcessor() {
        @Override
        public void process(final Message msg) {
            Log.i("ExampleActivity", String.format("Button press #%d", 
                    msg.getData(Byte.class)));
        }
    })
.commit();

You may have noticed, that the Message.getData method is now called with Byte.class instead of Boolean.class. This is because interpreting the accumulated data as a boolean is nonsensical given our goal of counting button presses. In this context, interpreting the data as a byte makes more sense. You will need to think about what you expect your data to look like when adding transformers in your route to make the appropriate type casting on the received data.