Python APIs

MetaSensors are supported on Linux. Developers can create their own Apps using Python3 on Linux.

For this tutorial, most images will reflect Ubuntu Desktop on a VM. We will also run a few commands and scripts on Raspbian OS (on a RaspberryPi).

You should be comfortable with your choice of Linux distro.


Some installation steps may change or look slightly different depending on your Linux distro of choice.

Source Code

You can find the Python code on Github here:

Python Tutorials

You can find tutorials to use the Python libs here:

C++ API Documentation

The C++ API calls are documented here:


Ubuntu is popular and highly recommended as the Linux distro of choice. Simply follow the Ubuntu install steps on the Ubuntu website :


The great thing about Ubuntu is that it comes with many packages installed. Please note that if you choose to install a bare-bones Linux distro, you may need to install many utilities, libraries and packages needed to support MetaWear development.

If you are going to use Raspbian OS, we recommend the “Raspbian Buster with desktop” (or similar), the lite and the max versions have either too much or too little pre-installed. Just follow the steps here.

For any other OS, simply follow the guide on their installation site.

OS Updates

If Ubuntu or your OS asks for an update, be kind and update! Run:

>>> sudo apt-get update

To upgrade (with caution) run: – this usually takes a while, DO NOT STOP IT.

>>> sudo apt upgrade


Make sure that the Bluetooth hardware (adapter) is recognized by your OS by using the following command:

>>> hcitool dev

If your distro does not come with Bluetooth drivers installed, install the required packages with their dependencies:

>>> sudo apt install bluetooth pi-bluetooth bluez blueman bluez-utils

Check the bluetooth status:

>>> systemctl status bluetooth

Connect your Bluetooth device and restart the Bluetooth services:

>>> sudo /etc/init.d/bluetooth restart


>>> sudo systemctl start bluetooth

Verify your Bluetooth device is detected along with the appropriate modules:

>>> lsusb
    Device 005: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)

Finally review the output of hcitool dev:

>>> hcitool dev
        hci0    00:11:95:00:1A:CF

You can also load the Bluetooth command-line tool, you need to enter the following command on your Raspberry Pi.

>>> bluetoothctl

Now that we are in the Bluetooth command-line tool, we need to go ahead and turn the agent on.

Switching the agent on will allow us to search for and pair with other Bluetooth devices. You can do this by using the command below.

>>> agent on

The next step is to tell the Bluetooth device on our Raspberry Pi to scan for other devices.

By scanning for devices, we can retrieve their MAC address and begin the process of pairing that device with the Raspberry Pi. To start the scan process, all you need to do is enter the following command.

>>> scan on

From this command, you should start seeing a result like what we have below.

>>> [bluetooth]# scan on
    Discovery started
        [CHG] Controller DC:A6:32:05:7F:06 Discovering: yes
        [NEW] Device 51:B8:16:6A:6F:C6 51-B8-16-6A-6F-C6
        [NEW] Device 40:23:43:3F:4E:58 BRAVIA 4K UR2

If you are having difficulties, please consult the community forum and manual for your OS of choice. The Ubuntu forum has already addressed many issues you may come upon.


Linux systems typically support USB to Bluetooth Dongle; they might even support multiple Dongles.

You can use these Dongles to get more sensors connected via the BLE link simultaneously.

First, plug in the USB to BLE Dongle.

Then, you can use lsusb -t to display how the USB devices and hubs are arranged and their allocated speeds.

>>> lsusb
    Bus 002 Device 005: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
>>> hciconfig
    hci0:   Type: USB
>>> hciconfig -a hci0
    Manufacturer: Accel Semiconductor Ltd. (74)

If it shows up as an hci device, great, you are done!

If not, there are a couple things to consider. First, you need to make sure it is actually a Linux supported dongle:

If your dongle is not on this list, you are out of luck and need to purchase a new one that is on the list.


If you have two of the same dongles, sometimes the second dongle may not be recognized by the system.

Once you have determined that your BLE Dongle is Linux supported, run the following commands with the Dongles plugged-in:

>>> sudo apt-get update
>>> sudo apt-get upgrade
>>> sudo reboot now

Virtual Machines (VMs)

If you are running in a virtual environment, you will need to take extra care to make sure the Bluetooth drivers and adapters are working in the VM before you do anything else.

For example, VirtualBox guest OS doesn’t recognize the Bluetooth adapter by default. There are many great tutorials to get this working like this one here.


Steps you can take:

  1. Disable Bluetooth Adapter on Host OS

  2. Launch Guest OS

  3. Enable Bluetooth Adapter on Host OS

  4. Enable Bluetooth Adapter in the usb device list (Guest OS): Devices->usb->check my device

You may also need to install the VirtualBox extension Pack or similar support software, see more information here.

MbientLab is not responsible for VM support. Please consult the forum for the appropriate VM software you are using if you run into issues.


You also need to make sure you have internet access. The quickest way to test this is to use ping in the terminal:

>>> ping

Also check with:

>>> ip address show
>>> ip link show

If you are having trouble, you can Google “How to connect to the internet on Ubuntu” or similar. There are many options to connect to the internet including wireless and LAN; we quickly highlight them below.

You can troubleshoot through the Ubuntu Network manager. If your local network connection isn’t working, ensure the Enable Networking and Enable Wi-Fi options are selected here in the menu:


Connect to a Wireless Network

If you have a wireless-enabled computer running the Ubuntu operating system, you can connect to a nearby wireless network to get to the internet. To do this:

  1. Open the System Menu on the right side of the top bar.

  2. Click on Wi-Fi Not Connected to expand the menu.

  3. Click on Select Network.

  4. Look through the names of the nearby networks. Select the one you want. If you don’t see the name of the network you want, click More to see additional networks. If you still don’t see the network you want, it may be hidden or you may be out of range.

  5. Enter the password for the network and click Connect.


Configure to a Wired Network

To set up most wired network connections, all you need to do is plug in a network cable. The wired network icon (settings) is displayed on the top bar with three dots while the connection is being established. The dots disappear when you are connected.

If this does not happen, you should first of all make sure that your network cable is plugged in. One end of the cable should be plugged into the rectangular Ethernet (network) port on your computer, and the other end should be plugged into a switch, router, network wall socket or similar (depending on the network setup you have). Sometimes, a light beside the Ethernet port will indicate that it is plugged in and active.

If you are still not connected, your network may not support automatic setup (DHCP). In this case you will have to configure it manually. Please consult the appropriate documentation.


Install Python3

You probably already have Python3 and pip3 installed.

To see which version of Python 3 you have installed, open a command prompt and run:

>>> python3 --version

If you are using Ubuntu 16.10 or newer, then you can easily install Python 3.6 with the following commands:

>>> sudo apt-get update
>>> sudo apt-get install python3.6

If you’re using another version of Ubuntu (e.g. the latest LTS release), we recommend using the deadsnakes PPA to install Python 3.6:

>>> sudo apt-get install software-properties-common
>>> sudo add-apt-repository ppa:deadsnakes/ppa
>>> sudo apt-get update
>>> sudo apt-get install python3.6

If you are using other Linux distribution, chances are you already have Python 3 pre-installed as well. If not, use your distribution’s package manager. For example on Fedora, you would use dnf:

>>> sudo dnf install python3

Note that if the version of the python3 package is not recent enough for you, there may be ways of installing more recent versions as well, depending on you distribution. For example installing the python36 package on Fedora 25 to get Python 3.6. If you are a Fedora user, you might want to read about multiple Python versions available in Fedora.


Do a final check:

>>> which python3

Working with Python3

At this point, you may have system Python 2.7 available as well.

>>> python

This will launch the Python 2 interpreter.

>>> python3

This will launch the Python 3 interpreter.

Setuptools & Pip3

The two most crucial third-party Python packages are setuptools and pip.

Once installed, you can download, install and uninstall any compliant Python software product with a single command. It also enables you to add this network installation capability to your own Python software with very little work.

Python 3.4 and later include pip by default.

To see if pip is installed, open a command prompt and run

>>> command -v pip (or pip3)

To install pip, follow the official pip installation guide. It will look something like this:

>>> sudo apt install python3-pip

You can also use your distribution’s package manager to install these.

Note that on some Linux distributions including Ubuntu and Fedora the pip command is meant for Python 2, while the pip3 command is meant for Python 3.

>>> command -v pip3

Upgrade Pip3

It is essential that your pip be up to date. For good measure, run the commands below:

>>>  pip3 install pip --upgrade
>>>  pip3 install --upgrade pip setuptools

About Pip and Python

When a package is installed globally, it’s made available to all users that log into the system. Typically, that means Python and all packages will get installed to a directory under /usr/local/bin/ for a Unix-based system.

Conversely, when a package is installed locally, it’s only made available to the user that installed it. Locally installed Python and all packages will be installed under a directory similar to ~/.local/bin/ for a Unix-based system.

When I use pip to install python libraries, where in the folder hierarchy do they go?

For a single user (i.e. not using sudo), usually in:

>>> ~/.local/lib/python3

For system-wide (i.e. using sudo pip) then usually in:

>>> /usr/local/lib/python3

And for Python packages installed by apt (apt-get) then they usually go in:

>>> /usr/lib/python3

Normally Python then uses the sub-directory site-packages but Debian based installs (of which Raspbian is) use the sub-directory dist-packages. So if you do

>>> sudo pip3 install mylib

then mylib should end up in:

>>> /usr/local/lib/python3/dist-packages

So keep in mind whether or not you will want to execute your python scripts with sudo or not:

>>> python3
>>> sudo python3

Both work but from different directories and you need to install packages your script will use accordingly.

You need to install the metawear pip3 package accordingly:

>>> python3 -m pip install metawear
>>> sudo python3 -m pip install metawear

You can install both if you want.

Just remember, they end up in different directories. Otherwise you will get an error: ModuleNotFoundError: No module named mbientlab.

>>> python3 -m pip install metawear <-- installed in /home/pi/.local/lib
>>> sudo python3 -m pip install metawear <-- installed in /usr/local/lib

Here’s a quick example showing the different folders I have my python packages installed in:


Please keep in mind ALL of our example scripts work with and without sudo EXCEPT for the example.

How to list Python packages that are globally installed:

>>> pip list
>>> pip freeze

How to List Python Packages that are Locally Installed:

>>> pip list --user
>>> pip freeze --user


The metawear libraries depend on warble, bluez, and cpp compilers, let’s install those first (some may already be installed and that’s ok):

Install Dev tools

These are nice to have tools as well as the latest bluetooth packages:

>>> sudo apt-get install build-essential
>>> sudo apt-get install bluez
>>> sudo apt-get install libboost-all-dev
>>> sudo apt-get install libbluetooth-dev

Install Compilers


If you don’t need to re-compile our C++ library, please skip this section.

Make sure that you have a C++ compiler if you intend to make modifications to our C++ libraries.

Compilers that support C++14:

>>> which g++
>>> which gcc
>>> which make

Otherwise run something like sudo apt-get install -y gcc-6 g++-6 clang-3.8. You may need to Googlefu this.


Install Git

Git is a distributed version-control system for tracking changes in source code during software development. It is designed for coordinating work among programmers, but it can be used to track changes in any set of files.

Git is nice to have if you are going to fork our repository or create your own. You can learn more about it with some Googlefu.

>>> sudo apt-get install git-core

Check that everything works:

>>> git --version


Finally, let’s use pip to install the metawear python package.


Please keep in mind you may want to install the MetaWear package with or without sudo as mentioned earlier

>>> pip3 install metawear
>>> sudo pip3 install metawear

You can also run the specific command:

>>> python3 -m pip install metawear
>>> sudo python3 -m pip install metawear

Metawear has dependencies including pygattlib and PyWarble. We will check some below to make sure everything installed correctly.

Upgrade MetaWear

You should always upgrade the metawear package. Run:

>>> pip3 install metawear --upgrade
>>> sudo pip3 install metawear --upgrade

Check and Install Warble

MetaWear should install Warble automatically. Check to see if it installed correctly:

>>> pip3 list --user warble   (local)
>>> pip3 list warble          (global / sudo)


If you have warble installed, skip the rest of this section.

PyWarble provides Python classes that wrap around the exported functions of the Warble C library.

Warble wraps around the libblepp library, which is included as a submodule of Warble.

If warble failed to install; install it with the following command:

>>> pip3 install warble
>>> sudo pip3 install warble

If the pip install fails, you can download the Warble repo and build it from source (expert developers only). You will need to have BlueZ, Boost headers, and GNU Make installed along with a C++ compiler that support C++14 to run these commands:

>>> git clone
>>> git submodule update --init
>>> make

API Code Repository

Head over to our Python Github page:


You can clone the repository or simply download as a ZIP file:

>>>  git clone

Make sure the MetaWear-SDK-Cpp submodule is also downloaded if you intend to modify the C++ libraries (expert developers only). If it is not present, use the command git clone --recurse-submodules

>>>  git clone --recurse-submodules


Import the MetaWear class and libmetawear variable from the metawear module and everything from the cbindings module.

>>> from mbientlab.metawear import MetaWear, libmetawear
>>> from mbientlab.metawear.cbindings import *

If you do not know the MAC address of your device, use PyWarble to scan for nearby devices.

from mbientlab.warble import *
from mbientlab.metawear import *
from threading import Event

e = Event()
address = None
def device_discover_task(result):
    global address
    if (result.has_service_uuid(MetaWear.GATT_SERVICE)):
        # grab the first discovered metawear device
        address = result.mac



Once you have the device’s MAC address, create a MetaWear object with the MAC address and connect to the device.

>>> device = MetaWear(address)
>>> device.connect()

Upon a successful connection, you can begin calling any of the functions from the C++ SDK, for example, blinking the LED green.

pattern= LedPattern(repeat_count= Const.LED_REPEAT_INDEFINITELY)
libmetawear.mbl_mw_led_load_preset_pattern(byref(pattern), LedPreset.BLINK)
libmetawear.mbl_mw_led_write_pattern(device.board, byref(pattern), LedColor.GREEN)

First Script

Let’s go ahead and run our first script.

Head over to the examples folder and take a look at the example.


Make sure you have a MetaWear device with you, fully-charged, and that you have that device’s MAC address handy.


This python code will scan for nearby Bluetooth devices and print out a list of available devices. You can then choose a device you want to connect to.

Run the code:

>>>  # usage: sudo python
>>>  sudo python3


Don’t forget you have to use sudo to run this one example because it uses the bluetooth OS level tools which require root permissions.


If you didn’t install the metawear package with sudo, this may not work. That’s ok, you can still run the other scripts.

Select the device you want to connect to. The MAC address for my favorite MetaSensor is D8:B5:11:21:96:06.

>>>  Select your device (-1 to rescan): 1

The code will then connect to this device for 5 seconds, retrieve device information, and disconnect.


As you can see, your Windows machine connected to the MetaSensor using Bluetooth and retrieved some information about the devices such as the firmware version and the manufacturer name.

Congratulations, you ran your first Python script!

Example Scripts

The example makes the LED on your MetaSensor blink a green color.


For this example you can refer to the API documentation for LEDs here and the API references for the LED here.

Try it out by running:

>>>  python3 D8:B5:11:21:96:06

This script connects to a specific MetaSensor by specifying the MAC address on the cmd line. Blinks the LEDs with a preset pattern and then disconnects:


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


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.

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:

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')


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.

In Python, we use PyWarble 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.

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!"


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
# Clear the logger of saved entries
# Remove all macros on the flash memory
# Restarts the board after performing garbage collection
print("Erase logger, state, and macros")

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


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:

# 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


# Disable the gyroscope

# Unsubscribe to it

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.


The binding files that bridge the C++ to the Python APIs are particularly important.

They describe all the different data types, sensor classes, settings and more:


Bindings File


The bindings are very useful and you should have them handy in the language of your choice.


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:

# 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)

# 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


# Disable the accelerometer

# Unsubscribe to it
signal = libmetawear.mbl_mw_acc_get_acceleration_data_signal(device.board)


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.


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.


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:

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

print("Logging data for 5s")

# Turn off the accelerometer
# Stop logging

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):

# 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))


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.


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:

# 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_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

See the Timer section below to understand how we can do continuous reads of some of the analog sensors and peripherals.


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.

# 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
# Stop and clear the pattern from memory


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.

# Callback function
def commandsRecorded(self, context, event, status):
    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:
# 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
# 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["event"].wait()


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.

# 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:
# The first command is to read the signal - temp data - based on the timer - every 1s
# Stop recording commands
create_voidp_int(lambda fn: libmetawear.mbl_mw_event_end_record(timer, None, fn), event = e)

# Start the timer


# Delete the timer

# Unsubscribe to the temp signal

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.

# 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


# Stop the accelerometer

# Unsubscribe
signal = libmetawear.mbl_mw_acc_get_acceleration_data_signal(device.board)

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:




Calculates absolute orientation from accelerometer, gyro, and magnetometer


Calculates relative orientation in space from accelerometer and gyro data


Determines geographic direction from th Earth’s magnetic field


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.

# 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);

# 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);


# Stop sensor fusion

# Unsubscribe
signal = libmetawear.mbl_mw_sensor_fusion_get_data_signal(s.device.board, SensorFusionData.QUATERNION);

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:

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:

# 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)

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.

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.

There are many more processors which are detailed here.

Multiple sensors

# 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

# Start the gyroscope

# Stop everything after 10 seconds



acc = libmetawear.mbl_mw_acc_get_acceleration_data_signal(s.device.board)

gyro = libmetawear.mbl_mw_gyro_bmi160_get_rotation_data_signal(s.device.board)

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.

e = Event()

# Callback function
def processor_created(context, pointer):
    self.processor = pointer

# 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)

# Subscribe to it
libmetawear.mbl_mw_datasignal_subscribe(self.processor, None, self.callback)

# Start the gyro and acc


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


>>> 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

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:

  1. Plug in the dongle.

  2. Update the OS:

    >>> sudo apt update
    >>> sudo apt upgrade
  3. 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.

# 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")

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.


Please note that not all loggers are supported.

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
# 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)

# If anonymous signals fails
if (result['signals'] == None):
    if (result['length'] != 0):
        print("Error creating anonymous signals, status = " + str(result['length']))
        print("No active loggers detected")
# If anonymous signals succeeds
    # Stop logging

    # 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)

    # Callback - Progress update - helper function
    def progress_update_handler(ctx, left, total):
        if (left == 0):

    # 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))

    # Finish
    print("Download completed")
    metawear.on_disconnect = lambda status: e.set()


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:

  1. Call mbl_mw_macro_record to put the API in macro mode

  2. Use the MetaWear commands that you want programmed

  3. 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.

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

# Stop recording the macro
create_voidp_int(lambda fn: libmetawear.mbl_mw_macro_end_record(device.board, None, fn), event = e)

Next steps

Advanced developers building more complex apps can refer to the Python Documentation.