1. C++, Swift, Python, Javascript¶
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
# Where 'cb:7d:c5:b0:20:8f' = MAC address of MetaSensor
device = MetaWear('cb:7d:c5:b0:20:8f')
device.connect()
var MetaWear = require('metawear');
// This will connect to the MetaSensor with the specific MAC address
//MetaWear.discoverByAddress('cb:7d:c5:b0:20:8f', function(device) { ... }
// This will connect to the closest MetaSensor
MetaWear.discover(function (device) {
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)
}
}
}
}
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.
For Javascript, we use Noble as the Bluetooth library.
We created the function disconnect()
which can be called to disconnect the board. The C++ callback function on_disconnect()
can be used to manage disconnect 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 *
...
# Where address is MAC address of MetaSensor
device = MetaWear(address)
...
device.on_disconnect = lambda status: print "we are disconnected!"
device.disconnect()
var MetaWear = require('metawear');
...
device.on('disconnect', function () {
console.log('we got disconnected!');
});
...
device.disconnect(function (error) {
console.log('disconnect call finished');
});
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)
device.connect()
# Stops data logging
libmetawear.mbl_mw_logging_stop(device.board)
# Clear the logger of saved entries
libmetawear.mbl_mw_logging_clear_entries(device.board)
# Remove all macros on the flash memory
libmetawear.mbl_mw_macro_erase_all(device.board)
# Restarts the board after performing garbage collection
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")
MetaWear.discover(function (device) {
console.log('discovered ' + device.address);
device.connectAndSetUp(function (error) {
console.log('connected ' + device.address);
// Stops data logging
MetaWear.mbl_mw_logging_stop(device.board);
// Clear the logger of saved entries
MetaWear.mbl_mw_logging_clear_entries(device.board)
// Remove all macros on the flash memory
MetaWear.mbl_mw_macro_erase_all(device.board);
// Restarts the board after performing garbage collection
MetaWear.mbl_mw_debug_reset(device.board);
device.disconnect(function (error) {
console.log('disconnect call finished');
});
});
});
device.connectAndSetup().continueWith(.mainThread) { t in
device.clearAndReset() // this handles everything for you
print("Clear and Reset the board")
}
You can take time to look at what each call does in the C++ documentation yourself.
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 function pointer
callback = FnVoid_VoidP_DataP(self.data_handler)
# Callback function to process/parse the gyroscope data
def data_handler(self, ctx, data):
print("%s -> %s" % (self.device.address, parse_value(data)))
self.samples+= 1
# Get the gyroscope data signal
signal = libmetawear.mbl_mw_gyro_bmi160_get_rotation_data_signal(device.board)
// Subscribe to it
libmetawear.mbl_mw_datasignal_subscribe(signal, None, callback)
# Enable the gyroscope
libmetawear.mbl_gyro_bmi160_enable_rotation_sampling(device.board)
libmetawear.mbl_mw_gyro_bmi160_start(device.board)
sleep(30.0)
# Disable the gyroscope
libmetawear.mbl_mw_gyro_bmi160_stop(device.board)
libmetawear.mbl_mw_gyro_bmi160_disable_rotation_sampling(device.board)
# Unsubscribe to it
libmetawear.mbl_mw_datasignal_unsubscribe(signal)
libmetawear.mbl_mw_debug_disconnect(device.board)
function startAccelStream(device) {
device.connectAndSetUp(function (error) {
// Get the gyroscope signal
var rotSignal = MetaWear..mbl_mw_gyro_bmi160_get_rotation_data_signal(device.board);
// Subscribe to it and create the callback
MetaWear.mbl_mw_datasignal_subscribe(rotSignal, ref.NULL, MetaWear.FnVoid_VoidP_DataP.toPointer(function gotTimer(context, dataPtr) {
// Callback function to parse the gyro data
var data = dataPtr.deref();
var pt = data.parseValue();
console.log(pt.x, pt.y, pt.z);
}));
// Enable the gyroscope
MetaWear.mbl_gyro_bmi160_enable_rotation_sampling(device.board);
MetaWear.mbl_mw_gyro_bmi160_start(device.board);
// Stop after 5 seconds
setTimeout(function () {
// Disable the gyroscope
MetaWear.mbl_mw_gyro_bmi160_stop(device.board);
MetaWear.mbl_mw_gyro_bmi160_disable_rotation_sampling(device.board);
// Unsubscribe to it
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()
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)
}
}
You can take a look at our swift, javascript and python binding files for all the available data types. The binding file locations are in the next section.
For example, the python to cpp bindings show the expected input types of libmetawear.mbl_mw_datasignal_subscribe
used in our example above:
>>> libmetawear.mbl_mw_datasignal_subscribe.restype = None
>>> libmetawear.mbl_mw_datasignal_subscribe.argtypes = [c_void_p, c_void_p, FnVoid_VoidP_DataP]
So back to our python code where we call the cpp subscribe method:
libmetawear.mbl_mw_datasignal_subscribe(signal, None, callback)
The argument, FnVoid_VoidP_DataP
, is the callback
method.
Now back to our bindings, let’s look at how we need to implement the callback:
>>> FnVoid_VoidP_DataP = CFUNCTYPE(None, c_void_p, POINTER(Data))
So our python callback function must handle the incoming data:
def data_handler(self, ctx, data):
And we need to parse the incoming data from the gyroscope:
print("%s -> %s" % (self.device.address, parse_value(data)))
This uses the parse_value
convenience function of our Python APIs:
def parse_value(pointer, **kwargs):
"""
Helper function to extract the value from a Data object.
"""
If you want to learn more, you can read the helper code.
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 in the language of your choice.
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).
You need to lookup the capabilities and setup requirements of that particular sensor and write your code accordingly. For example, the accelerometer might need the resolution to be set at +/-4g
and the sample rate 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 function pointer
callback = FnVoid_VoidP_DataP(self.data_handler)
# Callback function to process/parse the gyroscope data
def data_handler(self, ctx, data):
print("%s -> %s" % (self.device.address, parse_value(data)))
# Setup the accelerometer sample frequency and range
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)
# Get the accelerometer data signal
signal = libmetawear.mbl_mw_acc_get_acceleration_data_signal(device.board)
# Subscribe to it
libmetawear.mbl_mw_datasignal_subscribe(signal, None, callback)
# Enable the accelerometer
libmetawear.mbl_mw_acc_enable_acceleration_sampling(device.board)
libmetawear.mbl_mw_acc_start(device.board)
...
# Disable the accelerometer
libmetawear.mbl_mw_acc_stop(device.board)
libmetawear.mbl_mw_acc_disable_acceleration_sampling(device.board)
# Unsubscribe to it
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) {
// 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);
// Get the acc signal
var accSignal = MetaWear.mbl_mw_acc_get_acceleration_data_signal(device.board);
// Subscribe to it and create the callback function
MetaWear.mbl_mw_datasignal_subscribe(accSignal, ref.NULL, MetaWear.FnVoid_VoidP_DataP.toPointer(function gotTimer(context, dataPtr) {
// Callback function to parse the acc data
var data = dataPtr.deref();
var pt = data.parseValue();
console.log(pt.x, pt.y, pt.z);
}));
// Enable and start the acc
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 and disable the acc
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()
print(Double(acceleration.x), Double(acceleration.y), Double(acceleration.z))
}
}
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 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()
# Get the accelerometer data signal
signal = libmetawear.mbl_mw_acc_get_acceleration_data_signal(d.board)
# Create a logger
logger = create_voidp(lambda fn: libmetawear.mbl_mw_datasignal_log(signal, None, fn), resource = "acc_logger")
# Start logger
libmetawear.mbl_mw_logging_start(d.board, 0)
# Turn on the accelerometer
libmetawear.mbl_mw_acc_enable_acceleration_sampling(d.board)
libmetawear.mbl_mw_acc_start(d.board)
print("Logging data for 5s")
sleep(5.0)
# Turn off the accelerometer
libmetawear.mbl_mw_acc_stop(d.board)
libmetawear.mbl_mw_acc_disable_acceleration_sampling(d.board)
# Stop logging
libmetawear.mbl_mw_logging_stop(d.board)
print("Downloading data")
# Callback function to handle logger entries as we download them
def progress_update_handler(context, entries_left, total_entries):
if (entries_left == 0):
e.set()
# Function pointer and handlers for the logger download (LogDownloadHandler -> in the cbindings)
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))))
# Stop logger
libmetawear.mbl_mw_logger_subscribe(logger, None, callback)
# Download logger contents
libmetawear.mbl_mw_logging_download(d.board, 0, byref(download_handler))
e.wait()
var acceLogger = null;
function startLogging(device, callback) {
// Set up the acc functionality
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);
// Create 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'));
}));
// Start logging and enable the acc
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) {
// Disable accel
MetaWear.mbl_mw_acc_stop(device.board);
MetaWear.mbl_mw_acc_disable_acceleration_sampling(device.board);
// Stop logging
MetaWear.mbl_mw_logging_stop(device.board);
// Setup handler for accel data points and create callback to parse incoming logger data
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();
// Logger progress callback
downloadHandler.received_progress_update = MetaWear.FnVoid_VoidP_UInt_UInt.toPointer(function onSignal(context, entriesLeft, totalEntries) {
console.log('received_progress_update entriesLeft:' + entriesLeft + ' totalEntries:' + totalEntries);
});
// Logger unknown entry callback
downloadHandler.received_unknown_entry = MetaWear.FnVoid_VoidP_UByte_Long_UByteP_UByte.toPointer(function onSignal(context, id, epoch, data, length) {
console.log('received_unknown_entry');
});
// Logger bad entry callback
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 accelerometerBMI160StartLog(_ 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 accelerometerBMI160StopLog(_ 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()
}
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);
}
# Callback function
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
# Function pointer
self.sensor_data_handler= FnVoid_VoidP_DataP(self.sensorDataHandler)
# Get temperature signal
temp_signal= self.libmetawear.mbl_mw_multi_chnl_temp_get_temperature_data_signal(self.board, MetaWearRProChannel.ON_BOARD_THERMISTOR)
# Subscribe to it
libmetawear.mbl_mw_datasignal_subscribe(temp_signal, None, self.sensor_data_handler)
# Read it
libmetawear.mbl_mw_datasignal_read(temp_signal)
// Get temp signal
var tempSignal = MetaWear.mbl_mw_multi_chnl_temp_get_temperature_data_signal(device.board, MetaWearRProChannel.ON_BOARD_THERMISTOR)
// Subscribe to it
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);
}));
// Read it
MetaWear.mbl_mw_datasignal_read(temp_signal)
func readTemprature(_ 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)))
}
}
See the Timer section below to understand how we can do continuous reads of some of the analog sensors and peripherals.
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)
}
# Create a pattern
pattern= LedPattern(repeat_count= Const.LED_REPEAT_INDEFINITELY)
# Load the LED with the pattern
libmetawear.mbl_mw_led_load_preset_pattern(byref(pattern), LedPreset.SOLID)
libmetawear.mbl_mw_led_write_pattern(device.board, byref(pattern), LedColor.GREEN)
# Play the pattern
libmetawear.mbl_mw_led_play(device.board)
...
# Stop and clear the pattern from memory
libmetawear.mbl_mw_led_stop_and_clear(device.board)
// Create a pattern
var pattern = new MetaWear.LedPattern();
...
// Load the LED with the pattern
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);
// Play the pattern
MetaWear.mbl_mw_led_play(device.board);
...
MetaWear.mbl_mw_led_stop_and_clear(device.board)
device.connectAndSetup().continueWith { t in
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;
}
});
}
# Callback function
def commandsRecorded(self, context, event, status):
self.event_status.append(status)
self.events["event"].set()
self.recorded= True
# Function pointer
self.commands_recorded_fn= FnVoid_VoidP_VoidP_Int(self.commandsRecorded)
# Create LED pattern
pattern= LedPattern(high_time_ms = 50, pulse_duration_ms = 500, high_intensity = 31, repeat_count = 10)
# Create an event based on a disconnection
event= self.libmetawear.mbl_mw_settings_get_disconnect_event(self.board)
# Start recording commands:
self.libmetawear.mbl_mw_event_record_commands(event)
# The first command is to write the led pattern
self.libmetawear.mbl_mw_led_write_pattern(self.board, byref(pattern), LedColor.BLUE)
# The second command is to play the led pattern
self.libmetawear.mbl_mw_led_play(self.board)
# Stop recording commands (everything up until now is going into the MetaSensor memory)
self.libmetawear.mbl_mw_event_end_record(event, None, self.commands_recorded_fn)
# Wait until MetaSensor gives the OK
self.events["event"].wait()
MetaWear.discover(function (device) {
device.connectAndSetUp(function (error) {
// Create an event based on a disconnection
event= MetaWear.mbl_mw_settings_get_disconnect_event(device.board);
// Create LED pattern
let pattern = new MetaWear.LedPattern();
pattern.repeat_count = 5
// Start recording commands based on disconnect event:
MetaWear.mbl_mw_event_record_commands(event);
// The first command is to write the led pattern
MetaWear.mbl_mw_led_write_pattern(device.board, pattern.ref(), MetaWear.LedColor.GREEN);
// The second command is to play the led pattern
MetaWear.mbl_mw_led_play(device.board);
// Stop recording commands (everything up until now is going into the MetaSensor memory)
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. 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);
}
# Callback function and pointer in one
callback = FnVoid_VoidP_DataP(lambda ctx, p: print("{epoch: %d, value: %s}" % (p.contents.epoch, parse_value(p))))
# Get the temperature signal
signal = libmetawear.mbl_mw_multi_chnl_temp_get_temperature_data_signal(d.board, MetaWearRProChannel.ON_BOARD_THERMISTOR)
# Subscribe to it
libmetawear.mbl_mw_datasignal_subscribe(signal, None, callback)
# Create a timer that runs every 1000ms (1s)
timer = create_voidp(lambda fn: libmetawear.mbl_mw_timer_create_indefinite(d.board, 1000, 0, None, fn), resource = "timer", event = e)
# Start recording commands:
libmetawear.mbl_mw_event_record_commands(timer)
# The first command is to read the signal - temp data - based on the timer - every 1s
libmetawear.mbl_mw_datasignal_read(signal)
# Stop recording commands
create_voidp_int(lambda fn: libmetawear.mbl_mw_event_end_record(timer, None, fn), event = e)
# Start the timer
libmetawear.mbl_mw_timer_start(timer)
sleep(5.0)
# Delete the timer
libmetawear.mbl_mw_timer_remove(timer)
# Unsubscribe to the temp signal
libmetawear.mbl_mw_datasignal_unsubscribe(signal)
async function startLogging(device, callback) {
// Get temp signal
console.log('Get temp signal');
var tempSignal = MetaWear.mbl_mw_multi_chnl_temp_get_temperature_data_signal(device.board, 1);
// Create a timer
console.log('Create timer');
var promise = new Promise((resolve, reject) => {
var timer = MetaWear.mbl_mw_timer_create_indefinite(device.board, 1000, 0, ref.NULL, MetaWear.FnVoid_VoidP_TimerP.toPointer(function onSignal(context, timer) {
console.log(context);
console.log('Timer created');
console.log(timer);
resolve(timer);
}));
});
tempTimer = await promise;
console.log(tempTimer);
// Create event based on timer and record as a command
console.log('Record command');
MetaWear.mbl_mw_event_record_commands(tempTimer);
console.log('Command to read temp signal');
MetaWear.mbl_mw_datasignal_read(tempSignal);
console.log('End record command');
promise = new Promise((resolve, reject) => {
var rec = MetaWear.mbl_mw_event_end_record(tempTimer, ref.NULL, MetaWear.FnVoid_VoidP_EventP_Int.toPointer(function onSignal(context, dataPtr, lstatus) {
console.log('Command created');
resolve(lstatus);
}));
});
let rec = await promise;
// Start timer
MetaWear.mbl_mw_timer_start(tempTimer);
// Create a logger
MetaWear.mbl_mw_datasignal_log(tempSignal, ref.NULL, MetaWear.FnVoid_VoidP_DataLoggerP.toPointer(function (context, logger) {
tempLogger = logger;
callback(logger.address() ? null : new Error('failed to start logging temp'));
}));
// Start logger
MetaWear.mbl_mw_logging_start(device.board, 0);
}
let signal = mbl_mw_multi_chnl_temp_get_temperature_data_signal(device.board, 1)!
mbl_mw_datasignal_subscribe(signal, bridge(obj: self)) { (context, obj) in
let temp: UInt32 = obj!.pointee.valueAs()
print(format: "%d", temp)
}
// Create a timer to read every 700 ms
device.timerCreate(period: 700).continueOnSuccessWith { timer in
self.streamingCleanup[signal] = {
mbl_mw_timer_remove(timer)
mbl_mw_datasignal_unsubscribe(signal)
}
mbl_mw_event_record_commands(timer)
mbl_mw_datasignal_read(signal)
timer.eventEndRecord().continueOnSuccessWith {
mbl_mw_timer_start(timer)
}
}
1.12. 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));
});
}
# Get the accelerometer data signal
signal_acc = libmetawear.mbl_mw_acc_get_acceleration_data_signal(device.board)
# Get only the X part of the signal (no Y or Z)
signal_acc_x = libmetawear.mbl_mw_datasignal_get_component(signal_acc, Const.ACC_ACCEL_X_AXIS_INDEX)
# Subscribe to the Acc.X
libmetawear.mbl_mw_datasignal_subscribe(signal_acc_x, None, s.callback)
# Start the accelerometer
libmetawear.mbl_mw_acc_enable_acceleration_sampling(device.board)
libmetawear.mbl_mw_acc_start(device.board)
sleep(5.0)
# Stop the accelerometer
libmetawear.mbl_mw_acc_stop(s.device.board)
libmetawear.mbl_mw_acc_disable_acceleration_sampling(device.board)
# Unsubscribe
signal = libmetawear.mbl_mw_acc_get_acceleration_data_signal(device.board)
libmetawear.mbl_mw_datasignal_unsubscribe(signal_acc_x)
console.log('Set up acc signal.');
let acc = MetaWear.mbl_mw_acc_get_acceleration_data_signal(device.board);
console.log('Get x component of acc.');
let acc_x = MetaWear.mbl_mw_datasignal_get_component(acc, 0); //cbindings.Const.ACC_ACCEL_X_AXIS_INDEX);
console.log('Set up stream.');
MetaWear.mbl_mw_datasignal_subscribe(acc_x, ref.NULL, MetaWear.FnVoid_VoidP_DataP.toPointer((ctx, pointer) => {
var data = pointer.deref();
var value = data.parseValue();
console.log('epoch: ' + data.epoch + ' acc x: ' + value);
}))
let signal = mbl_mw_acc_bosch_get_acceleration_data_signal(device.board)!
let signal_x = mbl_mw_datasignal_get_component(signal, Const.ACC_ACCEL_X_AXIS_INDEX)!
mbl_mw_datasignal_subscribe(signal_x, bridge(obj: self)) { (context, obj) in
let accelerationx: MblMwCartesianFloat = obj!.pointee.valueAs()
print(Double(accelerationx))
}
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_x)
}
1.13. 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);
}
# Sensor fusion setup
libmetawear.mbl_mw_sensor_fusion_set_mode(s.device.board, SensorFusionMode.NDOF);
libmetawear.mbl_mw_sensor_fusion_set_acc_range(s.device.board, SensorFusionAccRange._8G);
libmetawear.mbl_mw_sensor_fusion_set_gyro_range(s.device.board, SensorFusionGyroRange._2000DPS);
libmetawear.mbl_mw_sensor_fusion_write_config(s.device.board);
# Subscribe to the quaternion signal
signal = libmetawear.mbl_mw_sensor_fusion_get_data_signal(s.device.board, SensorFusionData.QUATERNION);
libmetawear.mbl_mw_datasignal_subscribe(signal, None, s.callback);
# Start sensor fusion (acc + gyro + mag + on-board sensor fusion algo)
libmetawear.mbl_mw_sensor_fusion_enable_data(s.device.board, SensorFusionData.QUATERNION);
libmetawear.mbl_mw_sensor_fusion_start(s.device.board);
sleep(10.0)
# Stop sensor fusion
libmetawear.mbl_mw_sensor_fusion_stop(s.device.board);
# Unsubscribe
signal = libmetawear.mbl_mw_sensor_fusion_get_data_signal(s.device.board, SensorFusionData.QUATERNION);
libmetawear.mbl_mw_datasignal_unsubscribe(signal);
// Setup gyro, acc, and sensor fusion settings
MetaWear.mbl_mw_sensor_fusion_set_mode(device.board, 1); //SensorFusionMode.NDOF);
MetaWear.mbl_mw_sensor_fusion_set_acc_range(device.board, 2 ); //SensorFusionAccRange._8G)
MetaWear.mbl_mw_sensor_fusion_set_gyro_range(device.board, 0); //SensorFusionGyroRange._2000DPS)
MetaWear.mbl_mw_sensor_fusion_write_config(device.board);
console.log('Get quat signal.');
let signal = MetaWear.mbl_mw_sensor_fusion_get_data_signal(device.board, 3); //QUATERNION
console.log('Set up stream.');
MetaWear.mbl_mw_datasignal_subscribe(signal, ref.NULL, MetaWear.FnVoid_VoidP_DataP.toPointer((ctx, pointer) => {
var data = pointer.deref();
var value = data.parseValue();
console.log('epoch: ' + data.epoch + ' quat: ' + value.x + ' ' + value.y + ' ' + value.z);
}));
console.log('Start sensor fusion.');
MetaWear.mbl_mw_sensor_fusion_enable_data(device.board, 3); //QUATERNION
MetaWear.mbl_mw_sensor_fusion_start(device.board);
let signal = mbl_mw_sensor_fusion_get_data_signal(device.board, MBL_MW_SENSOR_FUSION_DATA_QUATERNION)!
mbl_mw_datasignal_subscribe(signal, bridge(obj: self)) { (context, obj) in
let quaternion: MblMwQuaternion = obj!.pointee.valueAs()
print(Double(quaternion.x))
print(Double(quaternion.y))
print(Double(quaternion.z))
print(Double(quaternion.w))
}
mbl_mw_sensor_fusion_clear_enabled_mask(device.board)
mbl_mw_sensor_fusion_enable_data(device.board, MBL_MW_SENSOR_FUSION_DATA_QUATERNION)
mbl_mw_sensor_fusion_write_config(device.board)
mbl_mw_sensor_fusion_start(device.board)
streamingCleanup[signal] = {
mbl_mw_sensor_fusion_stop(self.device.board)
mbl_mw_sensor_fusion_clear_enabled_mask(self.device.board)
mbl_mw_datasignal_unsubscribe(signal)
}
What’s important is that you look at all the capabilities and settings of each sensor.
For example, for the sensor fusion to work, you need to pick the MODE
, the OUTPUT TYPE
, the GYROSCOPE RANGE
, and the ACCELEROMETER RANGE
. These are things you need to think about before you start coding.
Learn more here: https://mbientlab.com/cppdocs/latest/sensor_fusion.html
1.14. 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);
}
# Get the accelerometer data signal
accel_signal= libmetawear.mbl_mw_acc_get_acceleration_data_signal(device.board)
# Past the accelerometer data through an RSS
rss = create_voidp(lambda fn: libmetawear.mbl_mw_dataprocessor_rss_create(accel_signal, None, fn), resource = "rss", event = e)
// start to setup rms->avg->thresh->log chain
console.log('Get acc signal');
var acc = MetaWear.mbl_mw_acc_get_acceleration_data_signal(device.board);
// create RSS
console.log('Create RSS');
let promise = new Promise((resolve, reject) => {
var rms = MetaWear.mbl_mw_dataprocessor_rss_create(acc, ref.NULL, MetaWear.FnVoid_VoidP_DataProcessorP.toPointer(function onSignal(context, dataPtr) {
console.log('RSS Created');
resolve(dataPtr);
}));
});
let rms = await promise;
let signal = mbl_mw_acc_bosch_get_acceleration_data_signal(device.board)
// Create RSS
signal!.rssCreate().continueOnSuccessWith { rss in
self.streamingCleanup[signal!] = {
mbl_mw_acc_stop(self.device.board)
mbl_mw_acc_disable_acceleration_sampling(self.device.board)
mbl_mw_datasignal_unsubscribe(rss)
}
mbl_mw_datasignal_subscribe(rss, bridge(obj: self)) { (context, obj) in
let acceleration_rss: MblMwCartesianFloat = obj!.pointee.valueAs()
print(acceleration_rss)
}
mbl_mw_acc_enable_acceleration_sampling(self.device.board)
mbl_mw_acc_start(self.device.board)
}
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.
1.15. Multiple sensors¶
#include "metawear/core/types.h"
#include "metawear/sensor/accelerometer.h"
#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);
}
void enable_acc_sampling(MblMwMetaWearBoard* 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);
}
# Get the accelerometer data signal and subscribe
acc = libmetawear.mbl_mw_acc_get_acceleration_data_signal(s.device.board)
libmetawear.mbl_mw_datasignal_subscribe(acc, None, s.callback)
# Get the gyroscope data signal and subscribe
gyro = libmetawear.mbl_mw_gyro_bmi160_get_rotation_data_signal(s.device.board)
libmetawear.mbl_mw_datasignal_subscribe(gyro, None, s.callback)
# Start the accelerometer
libmetawear.mbl_mw_acc_enable_acceleration_sampling(s.device.board)
libmetawear.mbl_mw_acc_start(s.device.board)
# Start the gyroscope
libmetawear.mbl_mw_gyro_bmi160_enable_rotation_sampling(s.device.board)
libmetawear.mbl_mw_gyro_bmi160_start(s.device.board)
# Stop everything after 10 seconds
sleep(10.0)
libmetawear.mbl_mw_acc_stop(s.device.board)
libmetawear.mbl_mw_acc_disable_acceleration_sampling(s.device.board)
libmetawear.mbl_mw_gyro_bmi160_stop(s.device.board)
libmetawear.mbl_mw_gyro_bmi160_disable_rotation_sampling(s.device.board)
acc = libmetawear.mbl_mw_acc_get_acceleration_data_signal(s.device.board)
libmetawear.mbl_mw_datasignal_unsubscribe(acc)
gyro = libmetawear.mbl_mw_gyro_bmi160_get_rotation_data_signal(s.device.board)
libmetawear.mbl_mw_datasignal_unsubscribe(gyro)
console.log('Get gyro and acc signal');
let acc = MetaWear.mbl_mw_acc_get_acceleration_data_signal(device.board)
let gyro = MetaWear.mbl_mw_gyro_bmi160_get_rotation_data_signal(device.board)
console.log('Set up stream.')
MetaWear.mbl_mw_datasignal_subscribe(acc, ref.NULL, MetaWear.FnVoid_VoidP_DataP.toPointer((ctx, pointer) => {
var data = pointer.deref();
var value = data.parseValue();
console.log('epoch: ' + data.epoch + ' acc: ' + value.x + ' ' + value.y + ' ' + value.z)
}))
MetaWear.mbl_mw_datasignal_subscribe(gyro, ref.NULL, MetaWear.FnVoid_VoidP_DataP.toPointer((ctx, pointer) => {
var data = pointer.deref();
var value = data.parseValue();
console.log('epoch: ' + data.epoch + ' gyro: ' + value.x + ' ' + value.y + ' ' + value.z)
}))
console.log('Start accelerometer.')
MetaWear.mbl_mw_acc_enable_acceleration_sampling(device.board)
MetaWear.mbl_mw_acc_start(device.board)
console.log('Start gyroscope.')
MetaWear.mbl_mw_gyro_bmi160_enable_rotation_sampling(device.board)
MetaWear.mbl_mw_gyro_bmi160_start(device.board)
@IBAction func accelerometerBMI160StartStreamPressed(_ sender: Any) {
accelerometerBMI160StartStream.isEnabled = false
accelerometerBMI160StopStream.isEnabled = true
accelerometerBMI160StartLog.isEnabled = false
accelerometerBMI160StopLog.isEnabled = false
updateAccelerometerBMI160Settings()
accelerometerBMI160Data.removeAll()
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)
}
}
@IBAction func accelerometerBMI160StopStreamPressed(_ sender: Any) {
accelerometerBMI160StartStream.isEnabled = true
accelerometerBMI160StopStream.isEnabled = false
accelerometerBMI160StartLog.isEnabled = true
let signal = mbl_mw_acc_bosch_get_acceleration_data_signal(device.board)!
streamingCleanup.removeValue(forKey: signal)?()
}
@IBAction func gyroBMI160StartStreamPressed(_ sender: Any) {
gyroBMI160StartStream.isEnabled = false
gyroBMI160StopStream.isEnabled = true
gyroBMI160StartLog.isEnabled = false
gyroBMI160StopLog.isEnabled = false
updateGyroBMI160Settings()
gyroBMI160Data.removeAll()
let signal = mbl_mw_gyro_bmi160_get_rotation_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 {
// TODO: Come up with a better graph interface, we need to scale value
// to show up right
_self.gyroBMI160Graph.addX(Double(acceleration.x * 0.008), y: Double(acceleration.y * 0.008), z: Double(acceleration.z * 0.008))
}
// Add data to data array for saving
_self.gyroBMI160Data.append((obj!.pointee.epoch, acceleration))
}
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)
}
}
@IBAction func gyroBMI160StopStreamPressed(_ sender: Any) {
gyroBMI160StartStream.isEnabled = true
gyroBMI160StopStream.isEnabled = false
gyroBMI160StartLog.isEnabled = true
let signal = mbl_mw_gyro_bmi160_get_rotation_data_signal(device.board)!
streamingCleanup.removeValue(forKey: signal)?()
}
1.16. Multiple sensors with Fuser¶
The fuser is one of our data processors.
The fuser processor combines data from multiple sensors into 1 message. When fusing multiple data sources, ensure that they are sampling at the same frequency, or at the very least, integer multiples of the fastest frequency. Data sources sampling at the lower frequencies will repeat the last received value.
Unlike the other data sources, fuser data is represented as an MblMwData
array, which is indexed based on the order of the data signals passed into mbl_mw_dataprocessor_fuser_create
.
#include "metawear/processor/fuser.h"
#include "metawear/sensor/accelerometer.h"
#include "metawear/sensor/gyro_bmi160.h"
void setup_adc_delta(MblMwMetaWearBoard* board) {
static auto proc_created = [](void* context, MblMwDataProcessor* fuser) {
mbl_mw_datasignal_subscribe((MblMwDataSignal*)fuser, context, [](void* context, const MblMwData* data) {
auto fused = (MblMwData**)data->value;
// 0 - acc data
// 1 - gyro data
auto acc = (MblMwCartesianFloat*) fused[0]->value;
auto gyro = (MblMwCartesianFloat*) fused[1]->value;
printf("acc = (%.3f, %.3f, %.3f), gyro = (%.3f, %.3f, %.3f)\n",
acc->x, acc->y, acc->z,gyro->x, gyro->y, gyro->z);
});
};
auto acc_signal = mbl_mw_acc_get_acceleration_data_signal(board);
auto gyro_signal = mbl_mw_gyro_bmi160_get_rotation_data_signal(board);
mbl_mw_dataprocessor_fuser_create(acc_signal, &gyro_signal, 1, nullptr, proc_created);
}
e = Event()
# Callback function
def processor_created(context, pointer):
self.processor = pointer
e.set()
# Function pointer
fn_wrapper = cbindings.FnVoid_VoidP_VoidP(processor_created)
# Acc and gyro signals
acc = libmetawear.mbl_mw_acc_get_acceleration_data_signal(self.device.board)
gyro = libmetawear.mbl_mw_gyro_bmi160_get_rotation_data_signal(self.device.board)
# Fuse the signals together
signals = (c_void_p * 1)()
signals[0] = gyro
libmetawear.mbl_mw_dataprocessor_fuser_create(acc, signals, 1, None, fn_wrapper)
e.wait()
# Subscribe to it
libmetawear.mbl_mw_datasignal_subscribe(self.processor, None, self.callback)
# Start the gyro and acc
libmetawear.mbl_mw_gyro_bmi160_enable_rotation_sampling(self.device.board)
libmetawear.mbl_mw_acc_enable_acceleration_sampling(self.device.board)
libmetawear.mbl_mw_gyro_bmi160_start(self.device.board)
libmetawear.mbl_mw_acc_start(self.device.board)
let acc = MetaWear.mbl_mw_acc_get_acceleration_data_signal(device.board)
let gyro = MetaWear.mbl_mw_gyro_bmi160_get_rotation_data_signal(device.board)
let fuser = await new Promise((resolve, reject) => {
console.log("creating fuser")
MetaWear.mbl_mw_dataprocessor_fuser_create(acc, new MetaWear.ArrayDataSignalP([gyro]), 1, ref.NULL, MetaWear.FnVoid_VoidP_DataProcessorP.toPointer((ctx, pointer) => {
if (!pointer) {
reject("Failed to create fuser")
} else {
resolve(pointer);
}
}))
console.log("fuser created?")
})
MetaWear.mbl_mw_datasignal_subscribe(fuser, ref.NULL, MetaWear.FnVoid_VoidP_DataP.toPointer((ctx, pointer) => {
var data = pointer.deref();
var values = data.parseValue({'nElem': 2});
let acc = values[0]
let gyro = values[1]
console.log(`acc: (${acc.x},${acc.y},${acc.z}), gyro; (${gyro.x},${gyro.y},${gyro.z})`)
}))
MetaWear.mbl_mw_gyro_bmi160_enable_rotation_sampling(device.board)
MetaWear.mbl_mw_acc_enable_acceleration_sampling(device.board)
MetaWear.mbl_mw_gyro_bmi160_start(device.board)
MetaWear.mbl_mw_acc_start(device.board)
// Use the helper functiosn created for you
fuserCreate()
1.17. Dongles¶
If you buy a Bluetooth dongle or you get one from our store, go ahead and plug it in.
It will either get recognized by your system immediately or it won’t. A few ways to check:
lsusb
is a utility for displaying information about USB buses in the system and the devices connected to them. Run the command:
>>> lsusb
This should at least show that you have the Dongle plugged in.
Now you need to know if it is recognized by your OS as a bluetooth controller.
You can do this using hcitool
, which is used to configure Bluetooth connections and send special commands to Bluetooth devices. You can also use hciconfig
which is used to configure Bluetooth devices:
>>> sudo hcitool dev
or
>>> sudo hciconfig
If you installed bluetooth utils earlier, you can also use bluetoothctl
. Bluetoothctl is the main command for configuring Bluetooth devices on Linux:
>>> bluetoothctl
list
You can look into each tool and how to use them with a little Googlefu. The important thing here is to make sure that your dongle is plugged in, working, and usable as a bluetooth controller.
If your bluetooth dongle is NOT recognized by your system; follow these steps:
Plug in the dongle.
Update the OS:
>>> sudo apt update >>> sudo apt upgrade
Reboot the machine:
>>> sudo reboot now
To specify a dongle when using the MetaWear APIs, you need to know the dongle’s MAC address.
In the screenshot above and example below, the dongle MAC address is B8:27:EB:F2:61:2E
. The MAC is found using the commands above. Something to note here is that the MAC address of the internal Bluetooth adapter of the RaspberryPi is 00:1A:7D:DA:71:13
.
Let’s go ahead and take a look at sending MetaWear API commands through a specific USB-BLE dongle and NOT through the internal adapater of the my PI.
// No example in this language
# The MAC of the dongle to be specified using hci_mac='' when creating the MetaWear object
device = MetaWear(sys.argv[1], hci_mac="B8:27:EB:F2:61:2E")
device.connect()
...
device.disconnect()
//Multiple Adapters
//hci0 is used by default to override set the NOBLE_HCI_DEVICE_ID environment variable to the interface number.
//Example, specify hci1:
//sudo NOBLE_HCI_DEVICE_ID=1 node <your file>.js
//Reporting all HCI events
//By default noble waits for both the advertisement data and scan response data for each Bluetooth address. If your device does not use scan response the following environment variable can be used to bypass it.
//sudo NOBLE_REPORT_ALL_HCI_EVENTS=1 node <your file>.js
var MetaWear = require('../index')//require('metawear');
// If you know the MAC address, you can uncomment this line
MetaWear.discoverByAddress('f9:b5:f9:81:3f:77', function(device) {
//MetaWear.discover(function (device) {
device.connectAndSetUp(function (error) {
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);
setTimeout(function () {
device.on('disconnect', function () {
process.exit(0);
});
MetaWear.mbl_mw_debug_reset(device.board);
}, 5000);
});
});
// No example in this language
1.18. Anonymous downloading¶
Anonymous data signals are a variant of the Logger type used to retrieve logged data from a board that was not programmed by the current host device. Use mbl_mw_metawearboard_create_anonymous_datasignals to sync the host device with the board’s current logger state. If the function fails, a null pointer will be returned and the uint32_t parameter instead corresponds to a status code from the SDK.
Because of the anonymous nature of the object, users will need to rely on an identifier string to determine what kind of data is being passed to each route. Generate the identifier string by calling mbl_mw_logger_generate_identifier for each MblMwDataLogger
type and match these values with mbl_mw_anonymous_datasignal_get_identifier.
Warning
Please note that not all loggers are supported.
#include "metawear/core/datasignal.h"
#include "metawear/core/logging.h"
#include "metawear/platform/memory.h"
#include "metawear/sensor/gyro_bmi160.h"
void identifier_demo(MblMwMetaWearBoard* board) {
auto gyro = mbl_mw_gyro_bmi160_get_rotation_data_signal(board);
auto gyro_y = mbl_mw_datasignal_get_component(gyro, MBL_MW_GYRO_ROTATION_Y_AXIS_INDEX);
mbl_mw_datasignal_log(gyro_y, nullptr, [](void* context, MblMwDataLogger* logger) -> void {
auto identifier = mbl_mw_logger_generate_identifier(logger);
cout << "gyro_y identifier = " << identifier << endl;
});
}
#include "metawear/core/anonymous_datasignal.h"
// Use mbl_mw_metawearboard_create_anonymous_datasignals to retrieve log data from
// another device
void anonymous_signal_demo(MblMwMetaWearboard* board) {
mbl_mw_metawearboard_create_anonymous_datasignals(board, nullptr, [](void* context, MblMwMetaWearBoard* board,
MblMwAnonymousDataSignal** anonymous_signals, uint32_t size) {
if (anonymous_signals == nullptr) {
cerr << "Failed to create anonymous signals, status = " << (int32_t) size << endl;
return;
}
for (uint32_t i = 0; i < size; i++) {
auto identifier = mbl_mw_anonymous_datasignal_get_identifier(anonymous_signals[i]);
// identifier earlier extracted from calling
// mbl_mw_logger_generate_identifier, use in if-else statements to identify
// which anonymous signal represents gyro y-axis data
if (!strcmp(identifier, "angular-velocity[1]")) {
mbl_mw_anonymous_datasignal_subscribe(anonymous_signals[i], context, [](void* context, const MblMwData* data) {
printf("gyro y-axis: %.3f", *((float*) data->value));
});
}
}
});
}
e = Event()
result = {}
# Callback function
def handler(ctx, board, signals, len):
result['length'] = len
result['signals'] = cast(signals, POINTER(c_void_p * len)) if signals is not None else None
e.set()
# Callback function pointer
handler_fn = FnVoid_VoidP_VoidP_VoidP_UInt(handler)
# DataHandler class
class DataHandler:
def __init__(self, signal):
self.identifier = libmetawear.mbl_mw_anonymous_datasignal_get_identifier(signal)
self.data_handler_fn = FnVoid_VoidP_DataP(lambda ctx, ptr: print({"identifier": self.identifier, "epoch": ptr.contents.epoch, "value": parse_value(ptr)}))
# Get anonymous signals
print("Creating anonymous signals")
libmetawear.mbl_mw_metawearboard_create_anonymous_datasignals(metawear.board, None, handler_fn)
e.wait()
# If anonymous signals fails
if (result['signals'] == None):
if (result['length'] != 0):
print("Error creating anonymous signals, status = " + str(result['length']))
else:
print("No active loggers detected")
# If anonymous signals succeeds
else:
e.clear()
# Stop logging
libmetawear.mbl_mw_logging_stop(metawear.board)
# Parse data results from anonymous signals
print(str(result['length']) + " active loggers discovered")
handlers = []
for x in range(0, result['length']):
wrapper = DataHandler(result['signals'].contents[x])
libmetawear.mbl_mw_anonymous_datasignal_subscribe(result['signals'].contents[x], None, wrapper.data_handler_fn)
handlers.append(wrapper)
# Callback - Progress update - helper function
def progress_update_handler(ctx, left, total):
if (left == 0):
e.set()
# Callback - Unknown entry - helper function
def unknown_entry_handler(ctx, id, epoch, data, length):
print("unknown entry = " + str(id))
print("Downloading log")
# Progress update Callback function pointer
progress_update_fn = FnVoid_VoidP_UInt_UInt(progress_update_handler)
# Unknown entry Callback function pointer
unknown_entry_fn = FnVoid_VoidP_UByte_Long_UByteP_UByte(unknown_entry_handler)
# LogDownloadHandler
download_handler= LogDownloadHandler(context = None, received_progress_update = progress_update_fn,
received_unknown_entry = unknown_entry_fn, received_unhandled_entry = cast(None, FnVoid_VoidP_DataP))
# Download
libmetawear.mbl_mw_logging_download(metawear.board, 10, byref(download_handler))
e.wait()
# Finish
print("Download completed")
e.clear()
metawear.on_disconnect = lambda status: e.set()
libmetawear.mbl_mw_debug_disconnect(metawear.board)
e.wait()
MetaWear.discover(function (device) {
console.log('connecting...' + device.address);
device.connectAndSetUp(function (error) {
console.log('connected!');
MetaWear.mbl_mw_metawearboard_create_anonymous_datasignals(device.board, ref.NULL,
MetaWear.FnVoid_VoidP_MetaWearBoardP_AnonymousDataSignalP_UInt.toPointer(function (context, board, anonymousSignals, size) {
if (!anonymousSignals) {
console.log('nothing being logged');
process.exit(1);
}
// Set the size on the array so we can index
anonymousSignals.length = size;
var i;
for (i = 0; i < size; i++) {
var identifier = MetaWear.mbl_mw_anonymous_datasignal_get_identifier(anonymousSignals[i]);
MetaWear.mbl_mw_anonymous_datasignal_subscribe(anonymousSignals[i], ref.NULL, MetaWear.FnVoid_VoidP_DataP.toPointer(function onSignal(context, dataPtr) {
var data = dataPtr.deref();
var pt = data.parseValue();
console.log(identifier + ':' + data.epoch + ' ' + JSON.stringify(pt));
}));
}
download(device, function () {
device.once('disconnect', function (reason) {
process.exit(0);
});
MetaWear.mbl_mw_macro_erase_all(device.board);
MetaWear.mbl_mw_debug_reset_after_gc(device.board);
MetaWear.mbl_mw_debug_disconnect(device.board);
});
}));
});
});
function download(device, callback) {
// 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);
if (entriesLeft === 0) {
callback(null);
}
});
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());
}
device.createAnonymousDatasignals().continueOnSuccessWith(device.apiAccessExecutor) { signals in
var states: [State] = []
signals.forEach {
let cString = mbl_mw_anonymous_datasignal_get_identifier($0)!
let identifier = String(cString: cString)
if let config = configs.first(where: { $0.isConfigForAnonymousEventName(identifier) }) {
let state = State(sensor: config, device: device, isStreaming: false)
states.append(state)
mbl_mw_anonymous_datasignal_subscribe($0, bridgeRetained(obj: state), state.handler)
}
}
return states
}
1.19. Macro¶
The on-board flash memory can also be used to store MetaWear commands instead of sensor data. Recorded commands can be executed any time after being programmed with the functions in macro.h header file.
To record commands:
- Call mbl_mw_macro_record to put the API in macro mode
- Use the MetaWear commands that you want programmed
- Exit macro mode with mbl_mw_macro_end_record
Macros can be set to run on boot by setting the exec_on_boot
parameter with a non-zero value.
Erasing macros is done with the mbl_mw_macro_erase_all method. The erase operation will not occur until you disconnect from the board.
#include "metawear/core/macro.h"
#include "metawear/peripheral/led.h"
void setup_macro(MblMwMetaWearBoard* board) {
static auto callback = [](void* context, MblMwMetaWearBoard* board, int32_t id) {
cout << "Macro ID = " << id << endl;
};
MblMwLedPattern pattern = { 16, 16, 0, 500, 0, 1000, 0, 5 };
mbl_mw_macro_record(board, 1);
mbl_mw_led_write_pattern(board, &pattern, MBL_MW_LED_COLOR_BLUE);
mbl_mw_led_play(board);
mbl_mw_macro_end_record(board, nullptr, callback);
}
...
mbl_mw_macro_erase_all(board)
e = Event()
# Start recording the macro
libmetawear.mbl_mw_macro_record(device.board, 1)
# Create an LED pattern
pattern= LedPattern(pulse_duration_ms=1000, high_time_ms=500, high_intensity=16, low_intensity=16, repeat_count=Const.LED_REPEAT_INDEFINITELY)
# Write the pattern
libmetawear.mbl_mw_led_write_pattern(device.board, byref(pattern), LedColor.BLUE)
# Play the pattern
libmetawear.mbl_mw_led_play(device.board)
# Stop recording the macro
create_voidp_int(lambda fn: libmetawear.mbl_mw_macro_end_record(device.board, None, fn), event = e)
console.log('Macro started');
MetaWear.mbl_mw_macro_record(device.board, 1)
console.log('LED');
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);
console.log('END');
var promise = new Promise((resolve, reject) => {
var macro = MetaWear.mbl_mw_macro_end_record(device.board, ref.NULL, MetaWear.FnVoid_VoidP_Int.toPointer(function onSignal(context, rec) {
console.log('Macro created');
console.log(rec);
resolve(rec);
}));
});
var rec = await promise;
// Record everything to macro
mbl_mw_macro_record(device.board, 1)
var redPattern = MblMwLedPattern(high_intensity: 10,
low_intensity: 0,
rise_time_ms: 100,
high_time_ms: 200,
fall_time_ms: 100,
pulse_duration_ms: 15000,
delay_time_ms: 2400,
repeat_count: 0xFF)
mbl_mw_led_write_pattern(device.board, &redPattern, MBL_MW_LED_COLOR_RED)
mbl_mw_led_play(device.board)
// Finish off this macro
return device.macroEndRecord()
1.20. Settings¶
The battery data provided by the Settings module reports both charge percetange and voltage, encapsulated by the MblMwBatteryState struct. Unlike the battery gatt servive, this battery data can be utilized with MetaWear features such as the logger.
#include "metawear/core/datasignal.h"
#include "metawear/core/settings.h"
#include "metawear/core/types.h"
void battery_stream(MblMwMetaWearBoard* board) {
auto battery_signal = mbl_mw_settings_get_battery_state_data_signal(board);
mbl_mw_datasignal_subscribe(battery_signal, nullptr, [](void* context, const MblMwData* data) -> void {
auto state = (MblMwBatteryState*) data->value;
printf("{voltage: %dmV, charge: %d}\n", state->voltage, state->charge);
});
mbl_mw_datasignal_read(battery_signal);
}
There are other settings available such as changing connection parameters, advertising parameters, and getting the power and charge status of the MetaSensor.
1.21. Peripherals¶
All boards come with general purpose I/O pins allowing users to attach their own sensors.
The I/O pins can be used as general analog or digital pins. Some of the I/Os also support I2C and SPI connectivity.
Let’s take a quick look at two examples. First, let’s read an analog input from one of the I/O pins.
Functions for communicating with the gpio pins are in the gpio.h header file.
Analog input data comes in 2 forms, an ADC value or a absolute reference value. These two modes are distinguished with the MblMwGpioAnalogReadMode enum.
To read analog data, call mbl_mw_datasignal_read with your analog input signal. ADC values are represented as an unsigned integer and are simply ratiometric values with no units. The absolute reference value is also represented as an unsigned integer but has units of milli volts.
#include "metawear/sensor/gpio.h"
void gpio_analog_stream(MblMwMetaWearBoard* board) {
auto adc_signal = mbl_mw_gpio_get_analog_input_data_signal(board, 0,
MBL_MW_GPIO_ANALOG_READ_MODE_ADC);
mbl_mw_datasignal_subscribe(adc_signal, nullptr, [](void* context, const MblMwData* data) {
// Cast value to uint32_t*
printf("gpio 0 adc= %d", *((uint32_t*) data->value));
});
mbl_mw_datasignal_read(adc_signal);
auto abs_ref_signal = mbl_mw_gpio_get_analog_input_data_signal(board, 1,
MBL_MW_GPIO_ANALOG_READ_MODE_ABS_REF);
mbl_mw_datasignal_subscribe(abs_ref_signal, nullptr, [](void* context, const MblMwData* data) {
// Cast value to uint32_t*
printf("gpio 1 voltage= %dmV", *((uint32_t*) data->value));
});
mbl_mw_datasignal_read(abs_ref_signal);
}
The next example is I2C.
The I2C module allows you to directly communicate with a sensor via the I2C bus. I2C functions are defined in the i2c.h header file.
I2C data signals are retrieved by calling mbl_mw_i2c_get_data_signal. You will need to pass two parameters:
- Length variable that sets how many bytes the signal is expected to receive
- An unique ID identifying the signal
If the id value has already been used, the length parameter will be ignored and the previously created signal will be returned.
#include "metawear/sensor/i2c.h"
// create and get a datasignal for 1 byte of i2c data
auto i2c_signal= mbl_mw_i2c_get_data_signal(board, 1, 0xa);
To read I2C data, use the mbl_mw_datasignal_read_with_parameters function with the parameters set by the MblMwI2cReadParameters struct.
#include "metawear/core/datasignal.h"
#include "metawear/sensor/i2c.h"
mbl_mw_datasignal_subscribe(i2c_signal, nullptr, [](void* context, const MblMwData* data) {
printf("WHO_AM_I= %x", ((uint8_t*) data->value)[0]);
});
MblMwI2cReadParameters read_params= {0x1c, 0xd};
mbl_mw_datasignal_read_with_parameters(i2c_signal, &read_params);
Writing data through the I2C bus is handled with the mbl_mw_i2c_write function.
#include "metawear/sensor/i2c.h"
void i2c_write(MblMwMetaWearBoard* board) {
// Write to the ctrl_meas register on the RPro/CPro barometer
uint8_t ctrl_meas= 0x37;
mbl_mw_i2c_write(board, 0x77, 0xf4, &ctrl_meas, 1);
}
1.22. Next Steps¶
To access the higher functionality of your MetaSensor, see the C++ docs: https://mbientlab.com/cppdocs/latest
Check out the examples folder in the repository of your chosen API language to learn more.