Routing Sensor Data

Interacting with sensor data is done through the creation of data routes. Routes are constructed via a Java DSL that enables users to express how they want to process the data rather than manage callback functions and store IDs. Route construction can be broken down in to three main steps:

  1. Select a data source
  2. Attach processors or monitors
  3. Select an endpoint

To showcase the features and power of the DSL, the first four sub-sections will refactor the Led controller from the event handling blog post and the last two sub-sections will demonstrate how to setup a live stream of accelerometer data.

Data Source

Data sources are the starting point for a route; with data originating from the sensors. To select our switch data source, we first need a reference to the Switch module. If the getModule call is sucessful, we begin the route by calling Switch.routeData and choosing “fromSensor”. We will also bundle the call to get an Led module in the same try-catch block.

try {
    final Switch switchModule= mwBoard.getModule(Switch.class);
    final Led ledModule= mwBoard.getModule(Led.class);

    switchModule.routeData().fromSensor();
} catch (UnsupportedModuleException e) {
    Log.e("MainActivity", "Module not present", e);
}

Processor

Data processors are route components that can transform or filter data on-board. They are added to a route by calling process and configured by the various classes under the processor package or via an URI string. Processors can be combined to form more complex processing chains. You can optionally assign an identifying key to the processor to allow you to create a feedback loop with an activity monitor. Data Processors are covered more extensively in Data Processor section.

Let’s attach an accumulator to the switch data to transform the raw switch data (1 = pressed, 0 = released) into a count of how many presses have been made. We will also chain a math processor to the accumulator to compute “sum mod 2”. Note that the try-catch block has been omitted for brevity.

switchModule.routeData().fromSensor()
    .process(new Accumulator())
    .process(new Maths(Maths.Operation.MODULUS, 2));

Splitter

Splitters creates forks in a data route, allowing you to chain different sets of components to the same piece of data. To signify a split in a route, call the split method on your desired signal. Then, call branch to signify that a new processing chain is to be attached to the signal. Call the end method to signal that you are done attaching branches. Control will be returned to the most recent data signal that has a splitter attached to it. If no such signal exists, you must commit the branch after calling end.

We will now attach a splitter to the math transformer to handle both cases of the mod 2 operation.

switchModule.routeData().fromSensor()
    .process(new Accumulator())
    .process(new Maths(Maths.Operation.MODULUS, 2))
    .split()
        .branch().process(new Comparison(Comparison.Operation.EQ, 1))
        .branch().process(new Comparison(Comparison.Operation.EQ, 0))
    .end()
.commit();

Monitor

A data monitor is a component whose sole purpose is to monitor a signal for activity i.e. when data is emitted from a creator. Upon detecting activity, a monitor will execute a user configured set of MetaWear commands in reponse. In the onSignalActive method, you can access any data processors in the current route or previously committed routes that were added with an identifying key and use a token representing the signal data to create feedback/feedforward loops.

We now use the activity monitor to program the board to turn on/off the LED depending on which comparison filter emits data.

switchModule.routeData().fromSensor()
    .process(new Accumulator())
    .process(new Maths(Maths.Operation.MODULUS, 2))
    .split()
        .branch().process(new Comparison(Comparison.Operation.EQ, 1))
            .monitor(new ActivityHandler() {
                @Override
                public void onSignalActive(Map<String, DataProcessor> processors,
                        DataToken token) {
                    ledModule.configureColorChannel(Led.ColorChannel.BLUE)
                        .setRiseTime((short) 0)
                        .setPulseDuration((short) 1000).setRepeatCount((byte) -1)
                        .setHighTime((short) 500).setHighIntensity((byte) 16)
                        .setLowIntensity((byte) 16).commit();
                    ledModule.play(false);
                }
            })
        .branch().process(new Comparison(Comparison.Operation.EQ, 0))
            .monitor(new ActivityHandler() {
                @Override
                public void onSignalActive(Map<String, DataProcessor> processors,
                        DataToken token) {
                    ledModule.stop(true);
                }
            })
    .end()
.commit();

Streaming and Logging

The final two components you can add in your route are streamers and loggers. As the names suggest, these components respectively stream data to your device and log data to the on-board flash memory. When calling the stream or log methods, you will need to pass in a unique identifying key, which will be later used to attached a MessageHandler.

We will now switch gears and work on setting up an accelerometer data stream.

try {
    mwBoard.getModule(Accelerometer.class).routeData().fromAxes().stream("accel_stream_key");
} catch (UnsupportedModuleException e) {
    Log.e("MainActivity", "No accelerometer present on this board", e);
}

Route Manager

Committing routes to your board is done with a call to commit. The commit method returns an AsyncOperation wrapping a RouteManager result, which will be ready to use when the route has been comitted to the board. The RouteManager class interacts with a committed data route, providing methods for you to attach message handlers and performing cleanup.

We will finish setting up our route by subscribing to the accelerometer stream. If we had instead been logging the data, we would call setLogMessageHandler rather than subscribe.

try {
    mwBoard.getModule(Accelerometer.class).routeData().fromAxes().stream("accel_stream_key")
        .commit().onComplete(new CompletionHandler<RouteManager>() {
            @Override
            public void success(RouteManager result) {
                result.subscribe("accel_stream_key", new MessageHandler() {
                    @Override
                    public void process(Message msg) {
                        CartesianFloat axes= msg.getData(CartesianFloat.class);
                        Log.i("MainActivity", axes.toString());
                    }
                });
            }
        });
} catch (UnsupportedModuleException e) {
    Log.e("MainActivity", "No accelerometer present on this board", e);
}

Silent Reads

Some sensors require an explicit read call to receive data from the board e.g. temperature and gpio. Prior to firmware v1.1.0, these read calls will always send the data to your Android device regardless of whether or not your data route was streaming from said sensor. In firmware v1.1.0, the read system was revamped such that the user can silence these reads if they do not need the data from the sensor. Functions that have can utilize the silent read will have a parameter named “silent” where passing true will not send the read data and “false” will retain the old behavior of always sending data when connected.

When using silent mode, you will need to tell the data route the data source is a silent read. For example, if you are using gpio data as part of a data processing chain, then you may not want the gpio data also sent to your Android device. Therefore, calls to readDigitalIn or readAnalogIn should set silent mode to true and building data route with fromDigitalIn and fromAnalogIn must also have silent mode set to true.

final Gpio gpioModule= mwBoard.getModule(Gpio.class);

boolean silent= true;
// Feeding gpio data into a processor so don't want
// to send the raw gpio data to the mobile device
gpioModule.routeData().fromDigitalIn((byte) 0, silent).process(new Counter()).commit();

// Read digital input with silent mode enabled
gpioModule.readDigitalIn((byte) 0, silent);