20 New Device Tutorial
vanous edited this page 3 weeks ago

Supporting a new device in Gadgetbridge

This is a step by step tutorial for supporting a new device in Gadgetbridge. There are some differences depending on the transport layer being used (Bluetooth Classic, Bluetooth LE, WiFi, ...), but most of it is independent of that. The tutorial only covers the basics to get you started quickly. For more details, have a look at the existing implementations for other devices. Try to do your implementation in a similar way (where it makes sense), so as to ease maintenance.

Before you start - some interesting articles about BLE device communication discovery: https://medium.com/@arunmag

Here you’ll find some information how to reverse engineer the BT protocol of your device: https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/BT-Protocol-Reverse-Engineering

See Appendix: Infrastructure for some general implementation topics and Developer-Documentation for an overview into Gadgetbridge’s inner workings and important classes.

Before going into the details, let’s have a short look at the process the user has to go through in order to connect a new device.

Start Device Discovery
  -> Select one of the displayed devices
    -> If necessary: pair/authenticate with the device
      -> If necessary: provide certain settings like nick name, age, height, ...
        -> Initialize the device
          -> Device can be used

For this tutorial we will add support for the exemplary SuperBand 4000 device, so many things are named after that.

Part 1: Discovery

The first part deals with discovering SuperBand 4000 devices and telling Gadgetbridge that/how they are supported.

  1. Add a new device type constant SUPERBAND_4000 to nodomain.freeyourgadget.gadgetbridge.model.DeviceType. Pick a new number as the key and leave 10 numbers “room” to the last number. These are reserved so that related devices can have nearby numbers. Also specify a default icon (coloured) and a disabled icon (greyscale). If you don’t have any yet, reuse some other icons.

    SUPERBAND_4000(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, "SuperBand 4000");
  2. Create a new package nodomain.freeyourgadget.gadgetbridge.devices.superband4000.

  3. In that package, create a class SuperBand4000DeviceCoordinator that extends nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator. This class tells Gadgetbridge whether a discovered device is supported and what features are supported (with the implementation in Gadgetbridge, not in general).

    public class SuperBand4000DeviceCoordinator extends AbstractDeviceCoordinator {
    1. Implement the method getDeviceType() and return the constant DeviceType.SUPERBAND_4000 that you previously added.
        public DeviceType getDeviceType() {
            return DeviceType.SUPERBAND_4000;
    1. Implement the method getManufacturer() and return the manufacturer name.
        public String getManufacturer() {
            return "SuperManufacturer";
    1. If your device is a Bluetooth LE device, override the method createBLEScanFilters() and return a list of ScanFilters for recognizing your device during discovery by a Bluetooth service UUID for example. You can skip this if you cannot or don’t know how to recognize your device that way yet.
        public Collection<? extends ScanFilter> createBLEScanFilters() {
    1. Implement the method DeviceType getSupportedType(GBDeviceCandidate candidate). This method will be called during discovery in order to check wether the discovered device is supported by Gadgetbridge. To decide this, you might check further services, the MAC address, or even the device name if you don’t know a better way, yet. If you’re determined to support the given device candidate, you return the recognized DeviceType. Typically this is the one that you return in getDeviceType(). If you do not recognize the given candidate, return DeviceType.UNKNOWN so that another DeviceCoordinator can be asked.
        public DeviceType getSupportedType(GBDeviceCandidate candidate) {
            if (candidate.supportsService(SUPERBAND_4000Constants.UUID_SERVICE_SUPERBAND_4000)) {
                return DeviceType.SUPERBAND_4000;
            return DeviceType.UNKNOWN;
    1. Implement getBondingStyle() depending on how your device needs to be paired on the Bluetooth level. Return BONDING_STYLE_BOND to perform BLE pairing, BONDING_STYLE_ASK to let the user decide that during discovery, or BONDING_STYLE_NONE to not perform BLE pairing at all.
        public int getBondingStyle(){
            return BONDING_STYLE_NONE;
    1. If your device needs some very special kind of pairing/bonding/authentication before it can be used, you may override the method getPairingActivity() in which you return an Activity class that will be shown for your device after the device discovery process. Otherwise, just return null.
    public Class<? extends Activity> getPairingActivity() {
        return null;
    1. There are several more methods that you need to implement, mostly supportsXXX() where XXX is a certain feature a device may support or not. As soon as you implement support for one such feature, make sure to announce it by returning true in the corresponding supportsXXX() method. Otherwise the feature will not be available in Gadgetbridge.
    public boolean supportsWeather() {
        return false;
    public boolean supportsFindDevice() {
        return false;
  4. Register your SuperBand4000DeviceCoordinator class with nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper#createCoordinators() so that it will be used.

        private List<DeviceCoordinator> createCoordinators() {
            List<DeviceCoordinator> result = new ArrayList<>();
            result.add(new SuperBand4000DeviceCoordinator());

So, that was the first part. With these changes, Gadgetbridge should be able to find and display your device in the Discovery view. If that is not the case, add breakpoints in your device coordinator’s getSupportedType() method to find out why. Also check the ScanFilters, if any. Only once your device is found and displayed during discovery, does it make sense to continue with the next steps.

Part 2: Communication

The next part deals with the communication between Gadgetbridge and your device. This part is very specific to the device and the communication protocol being used.

This code lives in a separate package because it is used by a contiuously running background Service. This service is the nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService.

  1. Create the package nodomain.freeyourgadget.gadgetbridge.service.devices.superband4000
  2. Create a SuperBand4000DeviceSupport class in that package. As the super class, choose one of the existing abstract classes like nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport or nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport, depending on the transport layer.
  3. Edit nodomain.freeyourgadget.gadgetbridge.service.DeviceSupportFactory#createBTDeviceSupport() and add the code to instantiate your support class for DeviceType.SUPERBAND_4000.

Bluetooth Classic

For Bluetooth Classic with AbstractSerialDeviceSupport as super class, you will usually create at least two other classes: a SuperBand4000IOThread and a SuperBand4000Protocol.

  • The *IOThread class manages the Bluetooth rfcomm socket in a non-blocking way for callers.
  • The *Protocol class understands and handles the incoming and outgoing messages.

Bluetooth LE

For Bluetooth LE, you will use the nodomain.freeyourgadget.gadgetbridge.service.btle.Transaction and nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue classes to communicate with the device via GATT events. Theses classes will take care of synchronizing everything.

See the subclasses of nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile for some ready-to-be-used implementations of standard BLE services/profiles.

Activity Databases

Most Fitness devices provide per-minute data which contain things like intensity of movement, activity type (sleep, deep sleep, awake, ...) and heartrate. We store this data in database tables.

Defining a Database

Gadgetbridge uses greenDAO. That means we do not use sql directly, but generated classes which are defined here:


You can see that each device defines it’s own table, some of them more then one. The reasons behind this are.

  • Different devices provide different data (some provide HR data, some not), others may have an environment light sensor.
  • Each device uses a different way of encoding the data
  • Some devices provide data though additional data streams (eg. sleep overlay data with from-to timestamps)
  • Most important: This allows us to store data as raw as possible and interpreting things on the fly. A good example are Huami devices which encode unknown flags which we store but do not know how to make sense of them, if we did we could properly interpret past data

Start by adding a new method (imagine the Super Band 4000 device again). The following would be appropriate for a device which has a step counter, HR, and activity types (sleep, deepsleep, awake,...)

Heart rate is normally in bpm, and steps are an absolute number, so these are pretty straight forward.

For activity types it is often more complicated because there are often custom or unknown values for different vendors. By keeping the raw value we prevent information loss: We can interpret the values on the fly and charts will be updated accordingly when the code that interprets the value will be updates.

Also the intensity has often different ranges, 0-255 or 0-15 for example. We want to keep them raw, they will be normalized by the Sample provider (see next section).

The method that defines the database table would look like:

private static Entity addSuperBand4000ActivitySample(Schema schema, Entity user, Entity device) {
        Entity activitySample = addEntity(schema, "SuperBand4000ActivitySample");
        addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
        return activitySample;

add that that in

public static void main()

Do not forget to bump the schema version, if it was at 24, bump it to 25

Schema schema = new Schema(24, MAIN_PACKAGE + ".entities");

Now when you build Gadgetbridge entity classes will be generated and the database you defined will be created

Creating a sample provider

For our band the sample provider would look like the following.

Pay special attention to normalizeType() and normalizeIntensity().

In our example the SuperBand4000 has different sleep types we do not handle yet, they will be mapped to someting close that we support. Still no data is lost.

In normalizeIntensity() you we assume the band stores intensity in a range from 0-255, we normalize that to 0.0f-1.0f so that the charts work properly.

package nodomain.freeyourgadget.gadgetbridge.devices.superband4000;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.SuperBand4000ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.SuperBand4000ActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;

public class SuperBand4000SampleProvider extends AbstractSampleProvider<SuperBand4000ActivitySample> {
    public SuperBand4000SampleProvider(GBDevice device, DaoSession session) {
        super(device, session);

    public AbstractDao<SuperBand4000ActivitySample, ?> getSampleDao() {
        return getSession().getSuperBand4000ActivitySampleDao();

    protected Property getRawKindSampleProperty() {
        return SuperBand4000ActivitySampleDao.Properties.RawKind;

    protected Property getTimestampSampleProperty() {
        return SuperBand4000ActivitySampleDao.Properties.Timestamp;

    protected Property getDeviceIdentifierSampleProperty() {
        return SuperBand4000ActivitySampleDao.Properties.DeviceId;

    public int normalizeType(int rawType) {
        switch (rawType) {
            case 1: //DEEP_NAP
            case 2: //DEEP_SLEEP
                return ActivityKind.TYPE_DEEP_SLEEP;
            case 3: //LIGHT_NAP
            case 4: //LIGHT_SLEEP
                return ActivityKind.TYPE_LIGHT_SLEEP;
            case 5: //ACTIVITY
            case 6: //WALK
            case 7: //RUN
                return ActivityKind.TYPE_ACTIVITY;
                return ActivityKind.TYPE_UNKNOWN;

    public int toRawActivityKind(int activityKind) {
        switch (activityKind) {
            case ActivityKind.TYPE_ACTIVITY:
                return 5; // ACTIVITY
            case ActivityKind.TYPE_DEEP_SLEEP:
                return 2; // DEEP_SLEEP
            case ActivityKind.TYPE_LIGHT_SLEEP:
                return 4; // LIGH_SLEEP
                return 5; //ACTIVITY

    public float normalizeIntensity(int rawIntensity) {
        return rawIntensity / 255.0f;

    public SuperBand4000ActivitySample createActivitySample() {
        return new SuperBand4000ActivitySample();

Now you have to register that sample provider in your SuperBand4000Coordinator

public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
    return new SuperBand4000SampleProvider(device, session);

Now the Gadgetbridge charts activity is able to access your data, let’s see how to get it into the database in the next section

Writing data to your database

Imaging your device sends you aggregated per-minute data in batches of 16 samples with a simple header containing the number of samples an an initial timestamp. The code would look something like that:

private void processSamples(byte[] data) {
    ByteBuffer buf = ByteBuffer.wrap(data);
    int samples = buf.get();
    int timestamp = buf.getInt();

    SuperBand4000Samples[] superBand4000Samples = new SuperBand4000Samples[samples];

    try (DBHandler db = GBApplication.acquireDB()) {
        SuperBand4000SampleProvider sampleProvider = new SuperBand4000Provider(device, db.getDaoSession());
        Long userId = DBHelper.getUser(db.getDaoSession()).getId();
        Long deviceId = DBHelper.getDevice(getDevice(), db.getDaoSession()).getId();
        for (int i = 0; i < samples; i++) {
            int rawIntensity = buf.get() & 0xff;
            int steps = buf.getShort();
            int rawType = buf.get() & 0xff;
            int hr = buf.get() & 0xff;
            superBand4000Samples[i] = new SuperBand4000Sample(timestamp + i * 60, deviceId, userId, rawIntensity, steps, rawType, hr);
    } catch (Exception e) {
        LOG.error("Error acquiring database", e);

Thats it!

If you have a device that does proper analysis for sleep on-device like the Pebble Time, which sends sleep data in a completely different format after a sleep session has ended for super, you will have to do some more work. TODO: explain how to to it.

Per-Device settings

Historcally Gadgetbridge had device type specific settings, which were submenus in the global Settings activity. While there are still some submenus like “Pebble” or “Mi Band/Amazfit” those are considered deprecated and new devices should implement settings by relying on the “per-device” settings “framework” which Gadgetbridge provides. Using the framework will make a “settings” icon visible inside the device card in the Gadgetbridge main screen.

There are multiple reasons why we are switching to per-device settings:

  1. A growing list of devices that are supported let the menu grow and it was already cluttered while most of the devices listed are completely uninteresting to users who only uses one single devie

  2. Per-device settings allows to configure devices of the same type in a different way, this is even necessary for paring keys that some newer devices require to be obtained before using Gadgetbridge.

  3. Amazfit/Mi Band devices all have a different feature set, and people will not understand why they are presented options that won’t work on their device.

Adding list of supported settings to the device coordinator class

Technically you provide a list of xml snippets that are combined to a settings activity during runtime.

You can find those that are already present here in


To specify which ones are supported for you device add a list in your SuperBand4000Coordinator

    public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
        return new int[]{

In this example, your super band supports switching the date format, allows you to receive a disconnect notification if the Bluetooth connection drops, and a pairing key can be specified to use with the device. Those three settings will be combined into a settings activity during runtime automatically!

If you need a setting that is not availible in any of the xml files, you have to create your own. But please keep in mind to use generic key names, do not prefix them with your device name or anything! Try to create xml files that can be reused for other devices. If you think you need settings that are unlikely being reused by other devices, create a devicesettings_superband4000.xml file where you collect those setting.

Accessing settings from you device’s support class

To access a SharedPreferences object which is specific to the device in use there is a helper function in GBApplicaction called getDeviceSpecificSharedPrefs().

SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());

No you can access the settings by the same key that was defined in the corresponding devicesettings_*xml file.

Reacting to changes on the fly

Most alredy defined preferences have a handler that will cause onSendConfiguration(String config) to be called in your Support class.

For the example of the date format it can look like this:

public void onSendConfiguration(String config) {
    TransactionBuilder builder;
    switch (config) {
        case DeviceSettingsPreferenceConst.PREF_DATEFORMAT:
            String dateFormat = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString("dateformat", "MM/dd/yyyy");
            // send something to the watch

If you created a new xml file with a new setting you can add a new change handler for the key you specified in the devicesetting_*xml file by adding one line to DeviceSpecificSettingsFragment.java


Final Words

We hope that this tutorial will help you get started with adding support for new devices in Gadgetbridge.

For questions and improvements, do not hesitate to contact us!

Happy hacking,
the Gadgetbridge Team