Skip to content
Metawear edited this page Dec 2, 2016 · 4 revisions

Changes from api v2.0 to v3.0 here

API

  • AsyncOperation class replaced with Task class from the Bolts Framework
  • MetaWearBleService class renamed to BtleService and moved to android package
<service android:name="com.mbientlab.metawear.android.BtleService" />
  • Sensor data represented by AsyncDataProducer or ForcedDataProducer
  • getModule no longer throws checked exception, returns null if failed
    • Checked exception variant renamed to getModuleOrThrow

Data Route

  • MessageHandler renamed to Subscriber
    • Interacts with the outer variables through an environment parameter which is set by its managing Route
    • Need to use the environment to avoid references to unseriazable variables
    • process function renamed to apply
  • process function replaced with explicitly named data processing functions
  • monitor renamed to react
  • stream and log functions directly take a Subscriber object rather than a key
    • Instead of assigning a Subscriber to a key, you now set a Subscriber's environment
  • split component now breaks data into its component pieces, replacing functions such as fromXAxis and fromYAxis
  • Behavior of the old splitter now handled by the multicast component
  • name component assigns a user defined name to a data processor
  • Feedback & feedforward loops expressed by setting the desired value to the string representing the desired data output, either from another data processor or sensor

Macro

  • Use startRecord and endRecord to mark where to start and stop recording a macro instead of wrapping the code in an anonymous function

Examples

Data Stream

Using lambda expressions, streaming data is easily expressed in a compact statement:

AccelerationDataProducer accel = board.getModule(Accelerometer.class).acceleration();
ArrayList<CartesianFloat> received = new ArrayList<>();

accel.addRoute(source -> source.stream((msg, env) -> {
    received.add(msg.data(CartesianFloat.class));
    Log.i("test", msg.data(CartesianFloat.class).toString());
}));

If you are using anonymous classes, the code is written instead as follows:

final ArrayList<CartesianFloat> received = new ArrayList<>();
accel.addRoute(new RouteBuilder() {
    @Override
    public void configure(RouteElement source) {
        source.stream(new Subscriber() {
            @Override
            public void apply(Message msg, Object... env) {
                received.add(msg.data(CartesianFloat.class));
                Log.i("test", msg.data(CartesianFloat.class).toString());
            }
        });
    }
});

If you are planning on serializing a route, you will need to be mindful of the outer variables the Subscriber object refers to as they may not be serializable i.e. a GUI object. This is especially truth for developers using anonymous classes as all non-static anonymous classes refer to their enclosing class with a hidden this$0 variable. To work around the references issue, Subscribers have an environment parameter passed into the apply function along with the data object. This lets the Subscriber access outer variables without explicitly referring them resulting in their being ignored when serializing Subscribers. When deserializing routes, you do not need to reset the Subscriber however you will need to reset the environment.

To serialize the anonymous class version of the accelerometer stream, the code needs to be refactored such that the Subscriber is static and the owning route sets the Subscriber's environment.

private static Subscriber DATA_HANDLER = new Subscriber() {
    @Override
    public void apply(Message msg, Object... env) {
        ((ArrayList<CartesianFloat>) env[0]).add(msg.data(CartesianFloat.class));
        Log.i("test", msg.data(CartesianFloat.class).toString());
    }
};

final ArrayList<CartesianFloat> received = new ArrayList<>();
accel.addRoute(new RouteBuilder() {
    @Override
    public void configure(RouteElement source) {
        source.stream(DATA_HANDLER);
    }
}).continueWith(new Continuation<Route, Void>() {
    @Override
    public Void then(Task<Route> task) throws Exception {
        task.getResult().setEnvironment(0, received);
        return null;
    }
});

LED Controller

Consider the LED controller example code from the Android documentation which uses the counter, math, and comparison processors, and signal monitors. With API v3.0, the same code now looks as follows:

final Led led= mwBoard.getModule(Led.class);
mwBoard.getModule(Switch.class).addRoute(new RouteBuilder() {
    @Override
    public void configure(RouteElement source) {
        source.count().map(Function2.MODULUS, 2)
                .multicast()
                     .to().filter(Comparison.EQ, 1).react(new RouteElement.Action() {
                        @Override
                        public void execute(DataToken token) {
                            led.editPattern(Led.Color.BLUE)
                                    .highIntensity((byte) 16).lowIntensity((byte) 16)
                                    .pulseDuration((short) 1000)
                                    .highTime((short) 500)
                                    .repeatCount(Led.PATTERN_REPEAT_INDEFINITELY)
                                    .commit();
                            led.play();
                        }
                    })
                    .to().filter(Comparison.EQ, 0).react(new RouteElement.Action() {
                        @Override
                        public void execute(DataToken token) {
                            led.stop(true);
                        }
                    })
                .end();
    }
})

If you use the Jack compiler, you can compact the code by replacing all of the anonymous functions with lambda expressions:

final Led led= board.getModule(Led.class);
board.getModule(Switch.class).addRoute(source -> source.count().map(Function2.MODULUS, 2)
        .multicast()
            .to().filter(Comparison.EQ, 1).react(token -> {
                led.editPattern(Led.Color.BLUE)
                    .highIntensity((byte) 16).lowIntensity((byte) 16)
                    .pulseDuration((short) 1000)
                    .highTime((short) 500)
                    .repeatCount(Led.PATTERN_REPEAT_INDEFINITELY)
                    .commit();
                led.play();
            })
            .to().filter(Comparison.EQ, 0).react(token -> led.stop(true))
        .end());

Macro

To program the above led controller code with the macro, add calls to startRecord and endRecord to the code.

final Led led= mwBoard.getModule(Led.class);
final Macro macro= mwBoard.getModule(Macro.class);

macro.startRecord();
mwBoard.getModule(Switch.class).addRoute(new RouteBuilder() {
    @Override
    public void configure(RouteElement source) {
        source.count().map(Function1.MODULUS, 2)
                .multicast()
                     .to().filter(Comparison.EQ, 1).react(new RouteElement.Action() {
                        @Override
                        public void execute(DataToken token) {
                            led.editPattern(Led.Color.BLUE)
                                    .highIntensity((byte) 16).lowIntensity((byte) 16)
                                    .pulseDuration((short) 1000)
                                    .highTime((short) 500)
                                    .repeatCount(Led.PATTERN_REPEAT_INDEFINITELY)
                                    .commit();
                            led.play();
                        }
                    })
                    .to().filter(Comparison.EQ, 0).react(new RouteElement.Action() {
                        @Override
                        public void execute(DataToken token) {
                            led.stop(true);
                        }
                    })
                .end();
    }
}).continueWithTask(new Continuation<Route, Task<Byte>>() {
    @Override
    public Task<Byte> then(Task<Route> task) throws Exception {
        return macro.endRecord();
    }
}).continueWith(new Continuation<Byte, Void>() {
    @Override
    public Void then(Task<Byte> task) throws Exception {
        Log.i("test", "Macro id " + task.getResult());
    }        
});

With lambda support enabled:

final Led led= board.getModule(Led.class);
final Macro macro = board.getModule(Macro.class);

macro.startRecord();
board.getModule(Switch.class).addRoute(source ->
    source.accumulate().map(Function2.MODULUS, 2).multicast()
        .to().filter(Comparison.EQ, 1).react(token -> {
            led.editPattern(Led.Color.GREEN)
                    .highIntensity((byte) 16).lowIntensity((byte) 16)
                    .pulseDuration((short) 1000)
                    .highTime((short) 500)
                    .repeatCount(Led.PATTERN_REPEAT_INDEFINITELY)
                    .commit();
            led.play();
        })
        .to().filter(Comparison.EQ, 0).react(token -> led.stop(true))
).continueWithTask(ignored -> macro.endRecord())
.continueWith(task -> Log.i("test", "Macro id " + task.getResult()));