1. C++, Swift, Python, Javascript Development

Here’s a look at our APIs in Python and Javascript that wrap around our C++ library.

1.1. Connect

First create a MblMwBtleConnection and then call mbl_mw_metawearboard_create to instantiate a MblMwMetaWearBoard struct.

After you have instantiated a MblMwMetaWearBoard, you then need to initialize its internal state by calling mbl_mw_metawearboard_initialize. Initializing can take a few seconds and will asynchronously alert the caller when it is finished.

If initialization succeeded, MBL_MW_STATUS_OK will be passed to the callback function, otherwise a non-zero value will be returned instead signalling the task failed. You can check if a MblMwMetaWearBoard has been initialized by calling mbl_mw_metawearboard_is_initialized.

1.1.1. Python and Javascript

When you use our Python or Javascript API, MetaWear is the central class of the MetaWear API. It contains methods for connecting, disconnecting, saving and restoring state. We have wrapped around all these C++ calls so that all you have to do is import MetaWear and call the connect() or connectAndSetUp() functions respectively:

1.1.2. Swift

When using our Swift APIs, MetaWear is the central class of the MetaWear API. It contains methods for connecting, disconnecting, saving and restoring state. Before using any API features, you must first connect to the board with connectAsync. The returned task will finish when a connection has been established and the MetaWear state has been initialized.

#include "metawear/core/connection.h"
#include "metawear/core/metawearboard.h"

int main(int argc, char **argv) {
    MblMwBtleConnection btle_conn = { nullptr, write_gatt_char, read_gatt_char, enable_char_notify };
    MblMwMetaWearBoard* board = mbl_mw_metawearboard_create(&btle_conn);
}

mbl_mw_metawearboard_initialize(board, nullptr, [](void* context, MblMwMetaWearBoard* board, int32_t status) -> void {
    if (!status) {
        printf("Error initializing board: %d\n", status);
    } else {
        printf("Board initialized\n");
    }
);
from mbientlab.metawear import MetaWear, libmetawear
from mbientlab.metawear.cbindings import *
import sys

device = MetaWear('cb:7d:c5:b0:20:8f') //where 'cb:7d:c5:b0:20:8f' = MAC address of MetaSensor
device.connect()
var MetaWear = require('metawear');

//MetaWear.discoverByAddress('cb:7d:c5:b0:20:8f', function(device) { ... } //this will connect to the MetaSensor with the specific MAC address
MetaWear.discover(function (device) { //this will connect to the closest MetaSensor
    console.log('discovered ' + device.address); //where address is MAC address of MetaSensor
    device.connectAndSetUp(function (error) {
        if (error) {
            console.log(error);
            process.exit(1);
        }
        console.log('connected ' + device.address);
    });
});
import MetaWear
import MetaWearCpp

MetaWearScanner.shared.startScan(allowDuplicates: true) { (device) in
    // We found a MetaWear board, see if it is close
    if device.rssi.intValue > -50 {
        // Hooray! We found a MetaWear board, so stop scanning for more
        MetaWearScanner.shared.stopScan()
        // Connect to the board we found
        device.connectAndSetup().continueWith { t in
            if let error = t.error {
                // Sorry we couldn't connect
                print(error)
            } else {
                // Hooray! We connected to a MetaWear board, so flash its LED!
                var pattern = MblMwLedPattern()
                mbl_mw_led_load_preset_pattern(&pattern, MBL_MW_LED_PRESET_PULSE)
                mbl_mw_led_stop_and_clear(device.board)
                mbl_mw_led_write_pattern(device.board, &pattern, MBL_MW_LED_COLOR_GREEN)
                mbl_mw_led_play(device.board)
            }
        }
    }
}

1.2. Disconnect

In the C++ library, you are responsible for telling the SDK that a bluetooth connection is lost (depending you which Bluetooth library you use).

The C++ APIs use the on_disconnect field to pass a function pointer to the Bluetooth LE wrapper for handling disconnect events.

1.2.1. Python and Javascript

In Python, we use PyWarble as the Bluetooth library and for Javascript, we use Noble as the Bluetooth library. We have created the function disconnect() which can be called to disconnect the board.

The C++ callback function on_disconnect() has been handled for you and can be used to manage disconnection events.

1.2.2. Swift

Swift uses the Apple Bluetooth libraries called Core Bluetooth. We have created the function cancelConnection() which can be called to disconnect the board.

We give you an error on function connectAndSetup() if a connection is lost.

static unordered_map<const void*, MblMwFnVoidVoidPtrInt> dc_handlers;

static void on_disconnect(void* context, const void* caller, MblMwFnVoidVoidPtrInt handler) {
    // call this handler everytime connection is lost, use 0 for 'value' parameter
    // handler(context, caller, 0)
    dc_handlers.insert({ caller, handler });
}
from mbientlab.metawear import MetaWear, libmetawear
from mbientlab.metawear.cbindings import *
import sys

device = MetaWear(address) //where address is MAC address of MetaSensor
...
device.on_disconnect = lambda status: print "we are disconnected!"
device.disconnect()
var MetaWear = require('metawear');

MetaWear.discover(function (device) {
    console.log('discovered ' + device.address);
    // you can be notified of disconnects
    device.on('disconnect', function () {
        console.log('we got disconnected!');
    });
    // connect and disconnect 1000 ms later
    device.connectAndSetUp(function (error) {
        console.log('we are connected!');
        setTimeout(function () {
            device.disconnect(function (error) {
                console.log('disconnect call finished');
            });
        }, 1000);
    });
});
device.cancelConnection().continueWith { t in
    print("Disconnected")
}

device.connectAndSetup().continueWith { t in
    t.result?.continueWith { t in
        print("Lost connection")
    }
}

1.3. Reset

If your board is stuck in a bad state or needs to be reset, there are a few steps and functions you should call to throughly reset your board:

// Stops data logging
METAWEAR_API void mbl_mw_logging_stop(const MblMwMetaWearBoard* board);
// Clear the logger of saved entries
METAWEAR_API void mbl_mw_logging_clear_entries(const MblMwMetaWearBoard* board);
// Remove all macros on the flash memory
METAWEAR_API void mbl_mw_macro_erase_all (MblMwMetaWearBoard *board)
// Restarts the board after performing garbage collection
METAWEAR_API void mbl_mw_debug_reset_after_gc (const MblMwMetaWearBoard *board)
# usage: python full_reset.py [mac]
from __future__ import print_function
import sys
from mbientlab.metawear import MetaWear, libmetawear
from mbientlab.metawear.cbindings import *

device = MetaWear(sys.argv[1]) // where argv[1] is the MAC address of the board
device.connect()
print("Connected")

libmetawear.mbl_mw_logging_stop(device.board)
libmetawear.mbl_mw_logging_clear_entries(device.board)
libmetawear.mbl_mw_macro_erase_all(device.board)
libmetawear.mbl_mw_debug_reset_after_gc(device.board)
print("Erase logger, state, and macros")

libmetawear.mbl_mw_debug_disconnect(device.board)

device.disconnect()
print("Disconnected")
var MetaWear = require('metawear');
var MetaWear = require('../index') // require('metawear');
var ref = require('ref');

MetaWear.discover(function (device) {
    console.log('discovered ' + device.address);
    device.connectAndSetUp(function (error) {
        if (error) {
            console.log(error);
            process.exit(1);
        }
        console.log('connected ' + device.address);
        ...
        MetaWear.mbl_mw_logging_stop(device.board);
        MetaWear.mbl_mw_logging_clear_entries(device.board)
        MetaWear.mbl_mw_macro_erase_all(device.board);
        MetaWear.mbl_mw_debug_reset(device.board);
        device.disconnect(function (error) {
            console.log('disconnect call finished');
        });
    });
});
device.connectAndSetup().continueWith(.mainThread) { t in
    if t.error != nil {
        self.showAlertTitle("Error", message: t.error!.localizedDescription)
    } else {
        device.clearAndReset() // this handles everything for you
    }
}

You can take time to look at what each call does in the C++ documentation yourself.

If you are still having trouble, go to https://mbientlab.com/troubleshooting.

1.4. Data

Data signals are an abstract representation of data producers. The API treats them as an event that contains data and represents them with the MblMwDataSignal struct. They can be safely typecasted to an MblMwEvent.

Signal data is encapsulated by the MblMwData struct. The struct contains a:

  • Timestamp of when the data was created

  • Pointer to the data

  • Data type id indicating how to cast the pointer

An enumeration of data types is defined by the MblMwDataTypeId enum and structs wrapping non basic data types are defined in the types.h header file.

#include "metawear/core/data.h"
#include <chrono>
#include <iomanip>

using std::chrono::duration_cast;
using std::chrono::milliseconds;
using std::chrono::system_clock;
using std::chrono::time_point;

void data_printer(void* context, const MblMwData* data) {
    // Print data as 2 digit hex values
    uint8_t* data_bytes = (uint8_t*) data->value;
    string bytes_str("[");
    char buffer[5];
    for (uint8_t i = 0; i < data->length; i++) {
        if (i) {
            bytes_str += ", ";
        }
        sprintf(buffer, "0x%02x", data_bytes[i]);
        bytes_str += buffer;
    }
    bytes_str += "]";

    // Format time as YYYYMMDD-HH:MM:SS.LLL
    time_point<system_clock> then(milliseconds(data->epoch));
    auto time_c = system_clock::to_time_t(then);
    auto rem_ms= data->epoch % 1000;

    cout << "{timestamp: " << put_time(localtime(&time_c), "%Y%m%d-%T") << "." << rem_ms << ", "
        << "type_id: " << data->type_id << ", "
        << "bytes: " << bytes_str.c_str() << "}"
        << endl;
}

Let’s take a look at the most common MblMwCartesianFloat data type. Angular velocity from the Gyropscope is represented by the MblMwCartesianFloat struct and is in units of degrees per second. The x, y, and z fields contain the angular velocity of the spin around that axis.

For the Python and Javascript Apis, you cast the type when you subscribe to the signal. The type FnVoid_VoidP_DataP represents the MblMwCartesianFloat type.

Don’t bother too much with the rest of the code, we will look into it in more details in the rest of this tutorial. Concentrate on the different data types that can come from our sensors and how we cast them:

#include "metawear/sensor/gyro_bmi160.h"

void gyro_rot_rate_stream(MblMwMetaWearBoard* board) {
    static auto rot_handler = [](void* context, const MblMwData* data) {
        // Cast value to MblMwCartesianFloat*
        auto rot_rate = (MblMwCartesianFloat*)data->value;
        printf("(%.3fdps, %.3fdps, %.3fdps)\n", rot_rate->x, rot_rate->y, rot_rate->z);
    };
    auto state_signal = mbl_mw_gyro_bmi160_get_rotation_data_signal(board);
    mbl_mw_datasignal_subscribe(state_signal, nullptr, rot_handler);

    mbl_mw_gyro_bmi160_enable_rotation_sampling(board);
    mbl_mw_gyro_bmi160_start(board);
}
callback = FnVoid_VoidP_DataP(self.data_handler)

def data_handler(self, ctx, data):
    print("%s -> %s" % (self.device.address, parse_value(data)))
    self.samples+= 1

signal = libmetawear.mbl_mw_gyro_bmi160_get_rotation_data_signal(device.board)
libmetawear.mbl_mw_datasignal_subscribe(signal, None, callback)

libmetawear.mbl_gyro_bmi160_enable_rotation_sampling(device.board)
libmetawear.mbl_mw_gyro_bmi160_start(device.board)

sleep(30.0)

libmetawear.mbl_mw_gyro_bmi160_stop(device.board)
libmetawear.mbl_mw_gyro_bmi160_disable_rotation_sampling(device.board)

libmetawear.mbl_mw_datasignal_unsubscribe(signal)
libmetawear.mbl_mw_debug_disconnect(device.board)
function startAccelStream(device) {
  device.connectAndSetUp(function (error) {
    var rotSignal = MetaWear..mbl_mw_gyro_bmi160_get_rotation_data_signal(device.board);
    MetaWear.mbl_mw_datasignal_subscribe(rotSignal, ref.NULL, MetaWear.FnVoid_VoidP_DataP.toPointer(function gotTimer(context, dataPtr) {
      var data = dataPtr.deref();
      var pt = data.parseValue();
      console.log(pt.x, pt.y, pt.z);
    }));
    MetaWear.mbl_gyro_bmi160_enable_rotation_sampling(device.board);
    MetaWear.mbl_mw_gyro_bmi160_start(device.board);

    // Stop after 5 seconds
    setTimeout(function () {
      // Stop the stream
      MetaWear.mbl_mw_gyro_bmi160_stop(device.board);
      MetaWear.mbl_mw_gyro_bmi160_disable_rotation_sampling(device.board);
      MetaWear.mbl_mw_datasignal_unsubscribe(accSignal);
      MetaWear.mbl_mw_debug_disconnect(device.board);
    }, 5000);
  });
}
func gyroBMI160StartStream(_ sender: Any) {
    let signal = mbl_mw_gyro_bmi160_get_rotation_data_signal(device.board)!
    mbl_mw_datasignal_subscribe(signal, bridge(obj: self)) { (context, obj) in
        let gyroscope: MblMwCartesianFloat = obj!.pointee.valueAs()
        let _self: DeviceDetailViewController = bridge(ptr: context!)
        print(obj!.pointee.epoch, gyroscope, gyroscope.x, gyroscope.y, gyroscope.z)
    }
    mbl_mw_gyro_bmi160_enable_rotation_sampling(device.board)
    mbl_mw_gyro_bmi160_start(device.board)

    streamingCleanup[signal] = {
        mbl_mw_gyro_bmi160_stop(self.device.board)
        mbl_mw_gyro_bmi160_disable_rotation_sampling(self.device.board)
        mbl_mw_datasignal_unsubscribe(signal)
    }
}

Another example would be FnVoid_VoidP_VoidP_Int, which casts the incoming data as an integer, DataTypeId.INT32. You can take a look at our swift, javascript and python binding files for all the available data types.

1.5. Bindings

The binding files that bridge the C++ to the Swift, Javascript, and Python APIs are particularly important. They describe all the different data types, sensor classes, settings and more:

Language

Bindings File

Python:

cbindings.py

Javascript:

cbindings.js

Swift:

cbindings.swift

The bindings are very useful and you should have them handy.

1.6. Streaming

You can stream sensor data continuously from any of the digital sensors on board including the magnetomer, gyroscope, accelerometer, pressure, humidity and light sensor.

When you do so, you need to setup the sensor with the frequency interval of the desired data (sampling frequency) among a few other things. You need to lookup the capabilities of that particular sensor and set it up accordingly. For example, the accelerometer might be set at the +/-4g mode and sample at 25Hz.

To stream data live from your MetaSensor board, call mbl_mw_datasignal_subscribe with the desired data signal and include a callback function for handling the received data.

Terminating the live stream is done by calling mbl_mw_datasignal_unsubscribe . Don’t forget to turn of the sensor as well!

Here is an example to stream accelerometer data:

#include "metawear/core/datasignal.h"
#include "metawear/core/types.h"
#include "metawear/sensor/accelerometer.h"

void enable_acc_sampling(MblMwMetaWearBoard* board) {
    // Set ODR to 25Hz or closest valid frequency
    mbl_mw_acc_set_odr(board, 25.f);

    // Set range to +/-4g or closest valid range
    mbl_mw_acc_set_range(board, 4.f);

    // Write the config to the sensor
    mbl_mw_acc_write_acceleration_config(board);

    auto data_handler = [](void* context, const MblMwData* data) -> void {
        // Cast value to MblMwCartesianFloat*
        auto acceleration = (MblMwCartesianFloat*) data->value;
        printf("(%.3fg, %.3fg, %.3fg)\n", acceleration->x, acceleration->y, acceleration->z);
    };

    auto acc_signal= mbl_mw_acc_get_acceleration_data_signal(board);
    mbl_mw_datasignal_subscribe(acc_signal, nullptr, data_handler);

    mbl_mw_acc_enable_acceleration_sampling(board);
    mbl_mw_acc_start(board);
}

void disable_acc_sampling(MblMwMetaWearBoard* board) {
    auto acc_signal= mbl_mw_acc_get_acceleration_data_signal(board);
    mbl_mw_datasignal_unsubscribe(acc_signal);

    mbl_mw_acc_disable_acceleration_sampling(board);
    mbl_mw_acc_stop(board);
}
callback = FnVoid_VoidP_DataP(self.data_handler)

def data_handler(self, ctx, data):
    print("%s -> %s" % (self.device.address, parse_value(data)))

libmetawear.mbl_mw_settings_set_connection_parameters(device.board, 7.5, 7.5, 0, 6000)
libmetawear.mbl_mw_acc_set_odr(device.board, 100.0)
libmetawear.mbl_mw_acc_set_range(device.board, 16.0)
libmetawear.mbl_mw_acc_write_acceleration_config(device.board)

signal = libmetawear.mbl_mw_acc_get_acceleration_data_signal(device.board)
libmetawear.mbl_mw_datasignal_subscribe(signal, None, callback)

libmetawear.mbl_mw_acc_enable_acceleration_sampling(device.board)
libmetawear.mbl_mw_acc_start(device.board)

...

libmetawear.mbl_mw_acc_stop(device.board)
libmetawear.mbl_mw_acc_disable_acceleration_sampling(device.board)

signal = libmetawear.mbl_mw_acc_get_acceleration_data_signal(device.board)
libmetawear.mbl_mw_datasignal_unsubscribe(signal)
libmetawear.mbl_mw_debug_disconnect(device.board)
function startAccelStream(device) {
    device.connectAndSetUp(function (error) {
        if (error) {
            console.error(error);
            process.exit(1);
        }

        // Set the max range of the accelerometer
        MetaWear.mbl_mw_acc_set_range(device.board, 8.0);
        MetaWear.mbl_mw_acc_write_acceleration_config(device.board);
        var accSignal = MetaWear.mbl_mw_acc_get_acceleration_data_signal(device.board);
        MetaWear.mbl_mw_datasignal_subscribe(accSignal, ref.NULL, MetaWear.FnVoid_VoidP_DataP.toPointer(function gotTimer(context, dataPtr) {
            var data = dataPtr.deref();
            var pt = data.parseValue();
            console.log(pt.x, pt.y, pt.z);
        }));
        MetaWear.mbl_mw_acc_enable_acceleration_sampling(device.board);
        MetaWear.mbl_mw_acc_start(device.board);

        // Stop after 5 seconds
        setTimeout(function () {
            // Stop the stream
            MetaWear.mbl_mw_acc_stop(device.board);
            MetaWear.mbl_mw_acc_disable_acceleration_sampling(device.board);
            MetaWear.mbl_mw_datasignal_unsubscribe(accSignal);
            MetaWear.mbl_mw_debug_disconnect(device.board);
        }, 5000);
    });
}
func accelerometerBMI160StartStreamPressed(_ sender: Any) {
     mbl_mw_acc_bosch_set_range(device.board, MBL_MW_ACC_BOSCH_RANGE_2G)
     mbl_mw_acc_set_odr(device.board, 25.f)
     mbl_mw_acc_bosch_write_acceleration_config(device.board)
     let signal = mbl_mw_acc_bosch_get_acceleration_data_signal(device.board)!
     mbl_mw_datasignal_subscribe(signal, bridge(obj: self)) { (context, obj) in
         let acceleration: MblMwCartesianFloat = obj!.pointee.valueAs()
         let _self: DeviceDetailViewController = bridge(ptr: context!)
         DispatchQueue.main.async {
             _self.accelerometerBMI160Graph.addX(Double(acceleration.x), y: Double(acceleration.y), z: Double(acceleration.z))
         }
         // Add data to data array for saving
         _self.accelerometerBMI160Data.append((obj!.pointee.epoch, acceleration))
     }
     mbl_mw_acc_enable_acceleration_sampling(device.board)
     mbl_mw_acc_start(device.board)

     streamingCleanup[signal] = {
         mbl_mw_acc_stop(self.device.board)
         mbl_mw_acc_disable_acceleration_sampling(self.device.board)
         mbl_mw_datasignal_unsubscribe(signal)
     }
 }

1.7. Logging

Alternatively, data can be logged and retrieved at a later time. This applies to the same sensors including the magnetomer, gyroscope, accelerometer, pressure, humidity and light sensor.

This takes a bit more effort because your code will need to know what data is being logged so you are responsible for saving logger IDs and retrieving them when you download the sensor data from the MetaSensor internal memory. You also need to handle the callbacks as the data is downloaded from the MetaSensor memory.

Warning

MetaSensors have a finite memory and when you run out of memory, new entries will over-write old entries. You can actually change this default setting in the mbl_mw_logging_start call.

Note

Please see our calculator function to see how many sensor data entries and at what frequency will the memory of the MetaSensor become full.

Loggers record data from a data signal and are represented by the MblMwDataLogger struct. You create an MblMwDataLogger object by calling mbl_mw_datasignal_log with the data signal you want to log. If successful, the callback function will be executed with a MblMwDataLogger* pointer and a null pointer if it failed.

MblMwDataLogger objects only interact with the specific data signal, they do not control the logging features.

MblMwDataLogger objects are identified by a numerical id; you can retrieve the id by calling mbl_mw_logger_get_id. The id is used to retrieve existing loggers from the API with the mbl_mw_logger_lookup_id function and is important when you have multiple loggers running.

Like a data signal, you can subscribe to an MblMwDataLogger to process the downloaded data. Call mbl_mw_logger_subscribe to attach a callback function to the MblMwDataLogger which handles all received data.

After you have setup the signal loggers, start the logger by calling mbl_mw_logging_start.

When you are ready to retrieve the data, execute mbl_mw_logging_download . You will need to pass in a MblMwLogDownloadHandler struct to handle notifications from the logger.

We have updated our streaming accelerometer data example to log data this time:

#include "metawear/core/datasignal.h"
#include "metawear/core/logging_fwd.h"
#include "metawear/sensor/accelerometer.h"

void enable_acc_logging(MblMwMetaWearBoard* board) {
    mbl_mw_acc_set_odr(board, 25.f);
    mbl_mw_acc_set_range(board, 4.f);
    mbl_mw_acc_write_acceleration_config(board);

    auto acc_signal= mbl_mw_acc_get_acceleration_data_signal(board);
    mbl_mw_datasignal_log(acc_signal, nullptr, [](void* context, MblMwDataLogger* logger) -> void {
        if (logger != nullptr) {
            // save logger as acc_logger for logger_subscribe function
            printf("logger ready\n");
        } else {
            printf("Failed to create the logger\n");
        }
    });

    mbl_mw_logging_start(board, 0)
    mbl_mw_acc_enable_acceleration_sampling(board);
    mbl_mw_acc_start(board);
}

void logger_subscribe(MblMwDataLogger* acc_logger) {
    mbl_mw_logger_subscribe(acc_logger, context, [](void* context, const MblMwData* data) -> void {
        // Cast value to MblMwCartesianFloat*
        auto acceleration = (MblMwCartesianFloat*) data->value;
        printf("(%.3fg, %.3fg, %.3fg)\n", acceleration->x, acceleration->y, acceleration->z);
    });
}
e = Event()
print("Configuring device")
signal = libmetawear.mbl_mw_acc_get_acceleration_data_signal(d.board)
logger = create_voidp(lambda fn: libmetawear.mbl_mw_datasignal_log(signal, None, fn), resource = "acc_logger")

libmetawear.mbl_mw_logging_start(d.board, 0)
libmetawear.mbl_mw_acc_enable_acceleration_sampling(d.board)
libmetawear.mbl_mw_acc_start(d.board)

print("Logging data for 5s")
sleep(5.0)

libmetawear.mbl_mw_acc_stop(d.board)
libmetawear.mbl_mw_acc_disable_acceleration_sampling(d.board)
libmetawear.mbl_mw_logging_stop(d.board)

print("Downloading data")
def progress_update_handler(context, entries_left, total_entries):
    if (entries_left == 0):
        e.set()

fn_wrapper = FnVoid_VoidP_UInt_UInt(progress_update_handler)
download_handler = LogDownloadHandler(context = None, received_progress_update = fn_wrapper, received_unknown_entry = cast(None, FnVoid_VoidP_UByte_Long_UByteP_UByte), received_unhandled_entry = cast(None, FnVoid_VoidP_DataP))

callback = FnVoid_VoidP_DataP(lambda ctx, p: print("{epoch: %d, value: %s}" % (p.contents.epoch, parse_value(p))))
libmetawear.mbl_mw_logger_subscribe(logger, None, callback)
libmetawear.mbl_mw_logging_download(d.board, 0, byref(download_handler))
e.wait()
function startLogging(device, callback) {
    MetaWear.mbl_mw_acc_set_odr(device.board, 50.0);
    MetaWear.mbl_mw_acc_set_range(device.board, 16.0);
    MetaWear.mbl_mw_acc_write_acceleration_config(device.board);

    // See if we already created a logger
    var accSignal = MetaWear.mbl_mw_acc_get_acceleration_data_signal(device.board);
    MetaWear.mbl_mw_datasignal_log(accSignal, ref.NULL, MetaWear.FnVoid_VoidP_DataLoggerP.toPointer(function (context, logger) {
        accelLogger = logger;
        callback(logger.address() ? null : new Error('failed to start logging accel'));
    }));

    MetaWear.mbl_mw_logging_start(device.board, 0);
    MetaWear.mbl_mw_acc_enable_acceleration_sampling(device.board);
    MetaWear.mbl_mw_acc_start(device.board);
}

function downloadLog(device, callback) {
    // Shutdown accel
    MetaWear.mbl_mw_acc_stop(device.board);
    MetaWear.mbl_mw_acc_disable_acceleration_sampling(device.board);

    // Shutdown log
    MetaWear.mbl_mw_logging_stop(device.board);
    // Setup handler for accel data points
    MetaWear.mbl_mw_logger_subscribe(accelLogger, ref.NULL, MetaWear.FnVoid_VoidP_DataP.toPointer(function onSignal(context, dataPtr) {
        var data = dataPtr.deref();
        var pt = data.parseValue();
        console.log(data.epoch + ' ' + pt.x + ',' + pt.y + ',' + pt.z);
    }));
    // Setup the handlers for events during the download
    var downloadHandler = new MetaWear.LogDownloadHandler();
    downloadHandler.received_progress_update = MetaWear.FnVoid_VoidP_UInt_UInt.toPointer(function onSignal(context, entriesLeft, totalEntries) {
        console.log('received_progress_update entriesLeft:' + entriesLeft + ' totalEntries:' + totalEntries);
    });
    downloadHandler.received_unknown_entry = MetaWear.FnVoid_VoidP_UByte_Long_UByteP_UByte.toPointer(function onSignal(context, id, epoch, data, length) {
        console.log('received_unknown_entry');
    });
    downloadHandler.received_unhandled_entry = MetaWear.FnVoid_VoidP_DataP.toPointer(function onSignal(context, dataPtr) {
        var data = dataPtr.deref();
        var dataPoint = data.parseValue();
        console.log('received_unhandled_entry: ' + dataPoint);
    });
    // Actually start the log download, this will cause all the handlers we setup to be invoked
    MetaWear.mbl_mw_logging_download(device.board, 20, downloadHandler.ref());
}
func accelerometerBMI160StartLogPressed(_ sender: Any) {
    let signal = mbl_mw_acc_bosch_get_acceleration_data_signal(device.board)!
    mbl_mw_datasignal_log(signal, bridge(obj: self)) { (context, logger) in
        let _self: DeviceDetailViewController = bridge(ptr: context!)
        let cString = mbl_mw_logger_generate_identifier(logger)!
        let identifier = String(cString: cString)
        _self.loggers[identifier] = logger!
    }
    mbl_mw_logging_start(device.board, 0)
    mbl_mw_acc_enable_acceleration_sampling(device.board)
    mbl_mw_acc_start(device.board)
}

func accelerometerBMI160StopLogPressed(_ sender: Any) {
    guard let logger = loggers.removeValue(forKey: "acceleration") else {
        return
    }
    mbl_mw_acc_stop(device.board)
    mbl_mw_acc_disable_acceleration_sampling(device.board)
    mbl_mw_logger_subscribe(logger, bridge(obj: self)) { (context, obj) in
        let acceleration: MblMwCartesianFloat = obj!.pointee.valueAs()
        let _self: DeviceDetailViewController = bridge(ptr: context!)
        // Add data to data array for saving
        _self.accelerometerBMI160Data.append((obj!.pointee.epoch, acceleration))
    }
    var handlers = MblMwLogDownloadHandler()
    handlers.context = bridgeRetained(obj: self)
    handlers.received_progress_update = { (context, remainingEntries, totalEntries) in
        let _self: DeviceDetailViewController = bridge(ptr: context!)
        let progress = Double(totalEntries - remainingEntries) / Double(totalEntries)
        if remainingEntries == 0 {
            _self.logCleanup { error in
                DispatchQueue.main.async {
                    if error != nil {
                        _self.deviceConnected()
                    }
                }
            }
        }
    }
    handlers.received_unknown_entry = { (context, id, epoch, data, length) in
        print("received_unknown_entry")
    }
    handlers.received_unhandled_entry = { (context, data) in
        print("received_unhandled_entry")
    }
    mbl_mw_logging_download(device.board, 100, &handlers)
}

1.8. Reading

Analog sensors such as the temperature sensor or the on-board push button cannot be read continuously; they will only send data when they receive a command to do so. You can either read them once OR use a timer loop to read them multiple times.

You can also read the pressure, light, and humidity sensors once if you wish.

Data signals that represent this type of data source are called readable signals. You can check if a data signal is readable by calling mbl_mw_datasignal_is_readable.

The read command is issued by calling mbl_mw_datasignal_read or mbl_mw_datasignal_read_with_parameters.

When using readable signals, you must decide up front if the data will be streamed or logged before interacting with it. That is, you should either have subscribed to or setup a logger for a readable signal before reading it.

Note

This is a great way to get very low sampling rates for certain sensors.

We will look at an example for the temperature sensor. Temperature reads are manually triggered by calling mbl_mw_datasignal_read. The data is represented as a float and is in units of Celsius.

Let’s do a one time read:

#include "metawear/core/datasignal.h"
#include "metawear/sensor/multichanneltemperature.h"

void read_temperature(MblMwMetaWearBoard* board) {
    static auto temp_handler = ;

    // Retrieve data signal for on board thermistor source
    auto temp_signal = mbl_mw_multi_chnl_temp_get_temperature_data_signal(board,
            MBL_MW_METAWEAR_RPRO_CHANNEL_ON_BOARD_THERMISTOR);
    mbl_mw_datasignal_subscribe(temp_signal, nullptr, [](void* context, const MblMwData* data) {
        // cast to float*
        printf("%.3fC\n", *((float*) data->value));
    });
    mbl_mw_datasignal_read(temp_signal);
}
def sensorDataHandler(self, context, data):
    data_ptr= cast(data.contents.value, POINTER(c_float))
    self.data_float= c_float()
    self.data_float.value= data_ptr.contents.value
    self.data = self.data_float

self.sensor_data_handler= FnVoid_VoidP_DataP(self.sensorDataHandler)

temp_signal= self.libmetawear.mbl_mw_multi_chnl_temp_get_temperature_data_signal(self.board, MetaWearRProChannel.ON_BOARD_THERMISTOR)
libmetawear.mbl_mw_datasignal_subscribe(temp_signal, None, self.sensor_data_handler)
libmetawear.mbl_mw_datasignal_read(temp_signal)
// TO DO
var tempSignal = MetaWear.mbl_mw_multi_chnl_temp_get_temperature_data_signal(device.board, MetaWearRProChannel.ON_BOARD_THERMISTOR)
MetaWear.mbl_mw_datasignal_subscribe(tempSignal, ref.NULL, MetaWear.FnVoid_VoidP_DataP.toPointer(function gotTemp(context, dataPtr) {
    var data = dataPtr.deref();
    var pt = data.parseValue();
    console.log(pt);
}));
MetaWear.mbl_mw_datasignal_read(temp_signal)
func readTempraturePressed(_ sender: Any) {
    let source = mbl_mw_multi_chnl_temp_get_source(device.board, UInt8(MBL_MW_TEMPERATURE_SOURCE_PRESET_THERM.rawValue))
    let selected = mbl_mw_multi_chnl_temp_get_temperature_data_signal(device.board, UInt8(MBL_MW_TEMPERATURE_SOURCE_PRESET_THERM.rawValue))!
    selected.read().continueOnSuccessWith(.mainThread) { obj in
        print(String(format: "%.1f°C", (obj.valueAs() as Float)))
    }
}

1.9. Writing

Sometimes you want to send commands to the board. You can turn on the LED, make the board vibrate if it has a coin vibration motor on it, or send GPIO, I2C or SPI commands.

Let’s take a look at the LED since all boards come with an on-board RGD LED.

An led pattern is represented by the MblMwLedPattern struct. Users can configure every part of the pulse or load one of the preset patterns using the mbl_mw_led_load_preset_pattern function. Patterns are written to the board with the mbl_mw_led_write_pattern function.

After writing patterns to the board, you can playback the pattern, similar to playing a music track, using mbl_mw_led_play, mbl_mw_led_pause, and mbl_mw_led_stop.

To remove patterns, call mbl_mw_led_stop_and_clear; this will also stop pattern playback.

#include "metawear/peripheral/led.h"

void set_led_pattern(MblMwMetaWearBoard* board) {
    MblMwLedPattern pattern;

    // Load the blink pattern
    mbl_mw_led_load_preset_pattern(&pattern, MBL_MW_LED_PRESET_BLINK);
    // Write the blink pattern to the blue channel
    mbl_mw_led_write_pattern(board, &pattern, MBL_MW_LED_COLOR_BLUE);
    // Start playing the programmed patterns
    mbl_mw_led_play(board);
    ...
    // Stop
    mbl_mw_led_stop_and_clear(board)
}
pattern= LedPattern(repeat_count= Const.LED_REPEAT_INDEFINITELY)
libmetawear.mbl_mw_led_load_preset_pattern(byref(pattern), LedPreset.SOLID)
libmetawear.mbl_mw_led_write_pattern(device.board, byref(pattern), LedColor.GREEN)
libmetawear.mbl_mw_led_play(device.board)
...
libmetawear.mbl_mw_led_stop_and_clear(device.board)
var pattern = new MetaWear.LedPattern();
MetaWear.mbl_mw_led_load_preset_pattern(pattern.ref(), MetaWear.LedPreset.BLINK);
MetaWear.mbl_mw_led_write_pattern(device.board, pattern.ref(), MetaWear.LedColor.GREEN);
MetaWear.mbl_mw_led_play(device.board);
...
MetaWear.mbl_mw_led_stop_and_clear(device.board)
MetaWearScanner.shared.startScan(allowDuplicates: true) { (device) in
    // We found a MetaWear board, see if it is close
    if device.rssi.intValue > -50 {
        // Hooray! We found a MetaWear board, so stop scanning for more
        MetaWearScanner.shared.stopScan()
        // Connect to the board we found
        device.connectAndSetup().continueWith { t in
            if let error = t.error {
                // Sorry we couldn't connect
                print(error)
            } else {
                // Hooray! We connected to a MetaWear board, so flash its LED!
                var pattern = MblMwLedPattern()
                mbl_mw_led_load_preset_pattern(&pattern, MBL_MW_LED_PRESET_PULSE)
                mbl_mw_led_stop_and_clear(device.board)
                mbl_mw_led_write_pattern(device.board, &pattern, MBL_MW_LED_COLOR_GREEN)
                mbl_mw_led_play(device.board)
            }
        }
    }
}

1.10. Events

An event is an asynchronous notification from the MetaWear board represented in the C++ API by the MblMwEvent struct.

The board can be programmed to execute MetaWear commands in response to an event firing. To start recording commands, call mbl_mw_event_record_commands. While in a recording state, all MetaWear functions called will instead be recorded on the board and executed when the event is fired.

To stop recording, call mbl_mw_event_end_record. This function is asynchronous and will alert the caller when it is completed via a callback function. If recording events was successful, MBL_MW_STATUS_OK will be passed into the callback function, otherwise MBL_MW_STATUS_ERROR_TIMEOUT is used.

#include "metawear/core/event.h"
#include "metawear/core/settings.h"

void setup_dc_event(MblMwMetaWearBoard* board) {
    MblMwLedPattern pattern;
    mbl_mw_led_load_preset_pattern(&pattern, MBL_MW_LED_PRESET_BLINK);
    pattern.repeat_count = 10;

    // Setup disconnect event to blink the blue led 10x when fired
    MblMwEvent* dc_event = mbl_mw_settings_get_disconnect_event(board);
    mbl_mw_event_record_commands(dc_event);
    mbl_mw_led_write_pattern(board, &pattern, MBL_MW_LED_COLOR_BLUE);
    mbl_mw_led_play(board);
    mbl_mw_event_end_record(dc_event, nullptr, [](void* context, MblMwEvent* event, int32_t status) {
        if (!status) {
            cout << "Error recording commands, status= " << status << endl;
        } else {
            cout << "Commands recorded" << endl;
        }
    });
}
def commandsRecorded(self, context, event, status):
    self.event_status.append(status)
    self.events["event"].set()
    self.recorded= True

self.commands_recorded_fn= FnVoid_VoidP_VoidP_Int(self.commandsRecorded)

pattern= LedPattern(high_time_ms = 50, pulse_duration_ms = 500, high_intensity = 31, repeat_count = 10)
event= self.libmetawear.mbl_mw_settings_get_disconnect_event(self.board)

self.libmetawear.mbl_mw_event_record_commands(event)
self.libmetawear.mbl_mw_led_write_pattern(self.board, byref(pattern), LedColor.BLUE)
self.libmetawear.mbl_mw_led_play(self.board)
self.libmetawear.mbl_mw_event_end_record(event, None, self.commands_recorded_fn)
self.events["event"].wait()
MetaWear.discover(function (device) {
  device.connectAndSetUp(function (error) {
    event= MetaWear.mbl_mw_settings_get_disconnect_event(device.board);
    let pattern = new MetaWear.LedPattern();
    pattern.repeat_count = 5

    MetaWear.mbl_mw_event_record_commands(event);
    MetaWear.mbl_mw_led_write_pattern(device.board, pattern.ref(), MetaWear.LedColor.GREEN);
    MetaWear.mbl_mw_led_play(device.board);
    MetaWear.mbl_mw_event_end_record(timer, ref.NULL, MetaWear.FnVoid_VoidP_VoidP_Int.toPointer((ctx, ptr, status) => {
        resolve(null);
    }))
  });
});
event= mbl_mw_settings_get_disconnect_event(device.board);
var pattern = MblMwLedPattern(high_intensity: 31, low_intensity: 31, rise_time_ms: 0, high_time_ms: 2000, fall_time_ms: 0, pulse_duration_ms: 2000, delay_time_ms: 0, repeat_count: 0xFF)

mbl_mw_event_record_commands(event)
mbl_mw_led_write_pattern(device.board, &pattern, color)
mbl_mw_led_play(device.board)

event.eventEndRecord()

1.11. Data Processing

Data signals can be fed through the on-board data processors to filter and/or transform the data in the firmware.

By performing computations on the MetaWear side, you can reduce the amount of data that is sent over the radio and the amount of postprocessing that is done on your mobile device.

Data processors can also be chained together to perform more complex tasks, such as using the rss, average, and threshold processors to determine if the board is in freefall based on the XYZ acceleration data.

Here is an example of the rss processor. RSS which stands for root sum square is a statistical method of dealing with a series of values where each value is squared, the sum of these squares is calculated and the square root of that sum is then taken:

#include "metawear/core/dataprocessor_fwd.h"
#include "metawear/processor/rss.h"
#include "metawear/sensor/accelerometer.h"

void rss_accelerometer(MblMwMetaWearBoard* board) {
    static auto data_handler = [](void* context, const MblMwData* data) -> void {
        printf("acc rss= %.3fg\n", (float*) data->value);
    };
    static auto rss_ready = [](void* context, MblMwDataProcessor* processor) -> void {
        // subscribe to the rss processor
        mbl_mw_datasignal_subscribe((MblMwDataSignal*) processor, context, data_handler);
    };

    // Create an rss processor to transform the XYZ values into vector magnitude
    // Do not need to compute rss on your device and less data is transmitted
    auto acc_signal = mbl_mw_acc_get_acceleration_data_signal(board);
    mbl_mw_dataprocessor_rss_create(acc_signal, nullptr, rss_ready);
}
accel_signal= libmetawear.mbl_mw_acc_get_acceleration_data_signal(device.board)

rss = create_voidp(lambda fn: libmetawear.mbl_mw_dataprocessor_rss_create(accel_signal, None, fn), resource = "rss", event = e)

libmetawear.mbl_mw_acc_enable_acceleration_sampling(device.board)
libmetawear.mbl_mw_acc_start(device.board)
let acc = MetaWear.mbl_mw_acc_get_acceleration_data_signal(device.board)
let rss = await new Promise((resolve, reject) => {
    MetaWear.mbl_mw_dataprocessor_rss_create(acc, ref.NULL, MetaWear.FnVoid_VoidP_DataProcessorP.toPointer((ctx, pointer) => {
        if (!pointer) {
            reject("Failed to create rss")
        } else {
            resolve(pointer);
        }
    }))
})
rssCreate()

Data processors are functions in the firmware that filter or transform sensor data and are represented by the MblMwDataProcessor struct.

In the C++ API, data processors can be thought of as a data signal whose data is produced by the on-board data processor. As such, a MblMwDataProcessor can be safely typecasted to a MblMwDataSignal.

This section will focus on the MblMwDataProcessor struct. Data processors are identified by a unique numerical ID; you can retrieve this id by calling mbl_mw_dataprocessor_get_id. The data processor ID is used to lookup a previously created MblMwDataProcessor object with the mbl_mw_dataprocessor_lookup_id function.

Some processors have an internal state that can be read and modified; the internal state is treated as a readable MblMwDataSignal.

#include "metawear/core/datasignal.h"
#include "metawear/processor/dataprocessor.h"

// Assume input processor is a Buffer processor
void subscribe_state_signal(MblMwDataProcessor* buffer_processor) {
    auto state_signal = mbl_mw_dataprocessor_get_state_data_signal(buffer_processor);
    mbl_mw_datasignal_subscribe(state_signal, nullptr, data_printer);
    mbl_mw_datasignal_read(state_signal);
}

Removing a processor is handled by calling mbl_mw_dataprocessor_remove. When a processor is removed, all processors that consume its output will also be removed.

#include "metawear/core/datasignal_fwd.h"
#include "metawear/processor/dataprocessor.h"
#include "metawear/processor/math.h"

void remove_processors(MblMwDataSignal* temp_signal) {
    static MblMwDataProcessor* first_processor= nullptr;

    static auto add_32_created = [](void* context, MblMwDataProcessor* processor) -> void {
        // Removes all 3 processors in the chain
        mbl_mw_dataprocessor_remove(first_processor);
    };
    static auto div_10_created = [](void* context, MblMwDataProcessor* processor) -> void {
        mbl_mw_dataprocessor_math_create((MblMwDataSignal*) processor, MBL_MW_MATH_OP_ADD, 32.f,
                context, add_32_created);
    };
    static auto mul_18_created = [](void* context, MblMwDataProcessor* processor) -> void {
        first_processor = processor;
        mbl_mw_dataprocessor_math_create((MblMwDataSignal*) processor, MBL_MW_MATH_OP_DIVIDE,
                10.f, context, div_10_created);
    };
    mbl_mw_dataprocessor_math_create(temp_signal, MBL_MW_MATH_OP_MULTIPLY, 18.f, nullptr, mul_18_created);
}

There are many more processors which are detailed here: https://mbientlab.com/cppdocs/latest/dataprocessor.html

1.12. Timer

A MetaWear timer can be thought of as an event that is fired at fixed intervals. These timers are represented by the MblMwTimer struct and can be safely typcased to a MblMwEvent struct. Timers can be used to schedule periodic tasks or setup a delayed task execution.

MblMwTimer objects are identified by a numerical id; you can retrieve the id by calling mbl_mw_timer_get_id. The id is used to retrieve existing timers from the API with the mbl_mw_timer_lookup_id function.

Before you can schedule tasks, you first need to create a timer, by calling either mbl_mw_timer_create or mbl_mw_timer_create_indefinite. These functions are asynchronous and will pass a pointer to the caller when the timer is created. When you have a valid MblMwTimer, you can use the command recording system outlined in Events section to program the board to respond to the periodic events. Upon recording timer task commands, call mbl_mw_timer_start to start the timer.

When you are done using a timer, you can remove it with mbl_mw_timer_remove.

#include "metawear/core/event.h"
#include "metawear/core/timer.h"

#include "metawear/sensor/gpio.h"

void timer_setup(MblMwMetaWearBoard* board) {
    static auto cmds_recorded = [](void* context, MblMwEvent* event, int32_t status) {
        printf("timer task setup\n");
    };
    static auto timer_created = [](void* context, MblMwTimer* timer) {
        auto owner = mbl_mw_event_get_owner((MblMwEvent*) timer);

        auto adc_signal= mbl_mw_gpio_get_analog_input_data_signal(board, 0,
                MBL_MW_GPIO_ANALOG_READ_MODE_ADC);
        // read gpio adc data every time the timer fires an event
        mbl_mw_event_record_commands((MblMwEvent*) timer);
        mbl_mw_datasignal_read(adc_signal);
        mbl_mw_event_end_record((MblMwEvent*) timer, context, cmds_recorded);

        mbl_mw_timer_start(timer);
    };

    // create a timer that indefinitely fires events every 500ms
    mbl_mw_timer_create_indefinite(board, 500, 0, nullptr, timer_created);
}

1.13. Component Signals

Some signals, such as the acceleration datasignal, are composed of multiple values. While you can interact with them as a whole, sometimes it is more convenient to only use individual values.

To access the component values, call mbl_mw_datasignal_get_component with the signal and an index represnting which component to retrieve. If a signal is single valued, the function will return null.

#include "metawear/sensor/accelerometer.h"

void component_demo(MblMwMetaWearBoard* board) {
    auto acc_root = mbl_mw_acc_get_acceleration_data_signal(board);
    // get z axis signal
    auto acc_z = mbl_mw_datasignal_get_component(acc_root, MBL_MW_ACC_ACCEL_Z_AXIS_INDEX);

    mbl_mw_datasignal_subscribe(acc_z, [](MblMwData* data) -> void {
        //combined xyz data is MblMwCartesianFloat, individual axis is float
        printf("z-axis: %.3f\n", *((float*) data->value));
    });
}

1.14. Sensor Fusion

The sensor_fusion.h header file interfaces with the sensor fusion algorithm running on MetaMotion boards. When using the sensor fusion algorithm, it is important that you do not simultaneously use the Accelerometer, Gyro, and Magnetometer modules; the algorithm configures those sensors internally based on the selected fusion mode.

To activate the sensor fusion algorithm, first set the fusion mode and data ranges, then subscribe to and enable the desired output data, and finally, call mbl_mw_sensor_fusion_start.

The sensor fusion algorithm has 4 fusion modes, listed in the below table:

Mode

Description

NDoF

Calculates absolute orientation from accelerometer, gyro, and magnetometer

IMUPlus

Calculates relative orientation in space from accelerometer and gyro data

Compass

Determines geographic direction from th Earth’s magnetic field

M4G

Similar to IMUPlus except rotation is detected with the magnetometer

The mode is set with mbl_mw_sensor_fusion_set_mode and written to the board by calling mbl_mw_sensor_fusion_write_config. Before writing the configuration, you can also set the acceleration and rotation ranges of the accelerometer and gyroscope respectively.

#include "metawear/sensor/sensor_fusion.h"

void configure_sensor_fusion(MblMwMetaWearBoard* board) {
    // set fusion mode to ndof (n degress of freedom)
    mbl_mw_sensor_fusion_set_mode(board, MBL_MW_SENSOR_FUSION_MODE_NDOF);
    // set acceleration rangen to +/-8G, note accelerometer is configured here
    mbl_mw_sensor_fusion_set_acc_range(board, MBL_MW_SENSOR_FUSION_ACC_RANGE_8G);
    // write changes to the board
    mbl_mw_sensor_fusion_write_config(board);
}

Learn more here: https://mbientlab.com/cppdocs/latest/sensor_fusion.html

1.15. Next Steps

To access the higher functionality of your MetaSensor, see the C++ docs: https://mbientlab.com/cppdocs/latest