Using the indoor bike feature with fitness machine service

I want to read and write data such as incline and resistance from the Zwift app using my nrf52dk device. Based on the information I found online, it seems I need to communicate with the fitness machine service. Since there is no ready example in the Zephyr library, I am struggling. There is a CSC example in Zephyr, and I used it to send speed and cadence values to the Zwift app. However, I still don't understand how to receive data from the Zwift app.

  • Hi

    Please check out the Bluetooth Low Energy fundamentals course in our DevAcademy to get a feel for how to develop BLE applications in the nRF Connect SDK. We also have a lesson on data exchange between devices and setting up services and characteristics in lesson 4: https://academy.nordicsemi.com/courses/bluetooth-low-energy-fundamentals/lessons/lesson-4-bluetooth-le-data-exchange/topic/services-and-characteristics/ 

    Can you share some details on what you're having trouble with on the Zwift app. My guess is that it's only specifically set up to print data from the fitness machine service

    Best regards,

    Simon

  • Today I did some research. I found a code that uses the treadmill data feature of the fitness machine service. This was a good development for me. However, I don't know how and what kind of data will come when I generally use the indoor bike feature. I am not sure what variable the incoming data will be associated with, or when writing data, which data type and which data I should assign to which variable.

    /* main.c - Fitness Machine Service app main entry point */
    
    /*
     * Copyright (c) 2023 Shanghai Panchip Microelectronics Co.,Ltd.
     *
     * SPDX-License-Identifier: Apache-2.0
     */
    
    #include <stdbool.h>
    #include <zephyr/types.h>
    #include <stddef.h>
    #include <string.h>
    #include <errno.h>
    #include <random/rand32.h>
    #include <sys/printk.h>
    #include <sys/byteorder.h>
    #include <zephyr.h>
    
    #include <bluetooth/bluetooth.h>
    #include <bluetooth/hci.h>
    #include <bluetooth/conn.h>
    #include <bluetooth/uuid.h>
    #include <bluetooth/gatt.h>
    
    #include "ftm.h"
    
    static bool treadmil_data_configured;
    static bool ctrl_point_configured;
    static bool training_status_configured;
    static bool ftm_simulate;
    treadmill_Data_t g_treadmillData;
    training_status g_trainingStatus = idle;
    
    /* ------------------------ FTM function implementation start ------------------------ */
    void HeartRateRangeRead(uint8_t minHeartRate, uint8_t maxHeartRate, uint8_t minIncrement, void *output)
    {
    	uint8_t heartRateRange[3] = {};
    
    	heartRateRange[0] = minHeartRate;
    	heartRateRange[1] = maxHeartRate;
    	heartRateRange[2] = minIncrement;
    
    	memcpy(output, heartRateRange, sizeof(heartRateRange));
    }
    
    static void treadmil_data_ccc_cfg_changed(const struct bt_gatt_attr *attr,
    					  uint16_t value)
    {
    	printk("treadmil_data notify changed:%d\n", value);
    	treadmil_data_configured = value == BT_GATT_CCC_NOTIFY;
    
    	ftm_simulate = true;
    }
    
    void FitnessMachineFeatureAccessRead(uint32_t featureField, uint32_t targetSettingFeatures, void *output)
    {
    	uint8_t feature[8] = { 0x00 };
    
    	feature[0] = 0x01;
    
    	for (int i = 0; i < 4; i++) {
    		feature[i] = (featureField >> (i * 8)) & 0xFF;
    		feature[i + 4] = (targetSettingFeatures >> (i * 8)) & 0xFF;
    	}
    
    	memcpy(output, feature, sizeof(feature));
    }
    
    void TrainingStatusAccessRead(void *output)
    {
    	uint8_t status[2] = {};
    
    	status[0] = 0x00;               /* training status string disabled*/
    	status[1] = g_trainingStatus;
    
    	memcpy(output, status, sizeof(status));
    }
    
    
    void SpeedRangeRead(uint16_t minSpeed, uint16_t maxSpeed, uint16_t minIncrement, void *output)
    {
    	uint8_t speedRange[6] = {};
    
    	for (int i = 0; i < 2; i++) {
    		speedRange[i] = (minSpeed >> (i * 8)) & 0xFF;
    		speedRange[i + 2] = (maxSpeed >> (i * 8)) & 0xFF;
    		speedRange[i + 4] = (minIncrement >> (i * 8)) & 0xFF;
    	}
    
    	memcpy(output, speedRange, sizeof(speedRange));
    }
    
    void InclinationRangeRead(int16_t minInclination, int16_t maxInclination, uint16_t minIncrement, void *output)
    {
    	uint8_t inclinationRange[6] = {};
    
    	for (int i = 0; i < 2; i++) {
    		inclinationRange[i] = (minInclination >> (i * 8)) & 0xFF;
    		inclinationRange[i + 2] = (maxInclination >> (i * 8)) & 0xFF;
    		inclinationRange[i + 4] = (minIncrement >> (i * 8)) & 0xFF;
    	}
    	memcpy(output, inclinationRange, sizeof(inclinationRange));
    }
    
    void addTwoOctData(uint8_t *treadmillData, uint16_t data, int dataIndex)
    {
    	treadmillData[dataIndex] = data & 0xFF;
    	treadmillData[dataIndex + 1] = (data >> 8) & 0xFF;
    }
    /* ------------------------ FTM function implementation end ------------------------ */
    
    /* ------------------------ FTM GATT characteristic or descriptor callback start------------------------ */
    static void training_status_ccc_cfg_changed(const struct bt_gatt_attr *attr,
    					    uint16_t value)
    {
    	training_status_configured = value == BT_GATT_CCC_NOTIFY;
    	printk("training_status notify changed:%d\n", value);
    }
    
    static void ctrl_point_ccc_cfg_changed(const struct bt_gatt_attr *attr,
    				       uint16_t value)
    {
    	printk("ctrl_point notify changed:%d\n", value);
    	ctrl_point_configured = value == BT_GATT_CCC_INDICATE;
    }
    
    static void ftm_status_ccc_cfg_changed(const struct bt_gatt_attr *attr,
    				       uint16_t value)
    {
    	printk("ftm_status notify changed:%d\n", value);
    }
    
    static ssize_t read_ftm_feature(struct bt_conn *conn,
    				const struct bt_gatt_attr *attr, void *buf,
    				uint16_t len, uint16_t offset)
    {
    	printk("%s\n", __func__);
    
    	uint8_t ftm_feature[8];
    
    	FitnessMachineFeatureAccessRead(average_speed | total_distance, speed_target_setting, ftm_feature);
    
    	return bt_gatt_attr_read(conn, attr, buf, len, offset,
    				 &ftm_feature[0], sizeof(ftm_feature));
    }
    
    static ssize_t read_training_status(struct bt_conn *conn,
    				    const struct bt_gatt_attr *attr, void *buf,
    				    uint16_t len, uint16_t offset)
    {
    	uint8_t status[2] = {};
    
    	printk("%s\n", __func__);
    
    	TrainingStatusAccessRead(status);
    
    	return bt_gatt_attr_read(conn, attr, buf, len, offset,
    				 &status[0], sizeof(status));
    }
    
    static ssize_t read_speed_range(struct bt_conn *conn,
    				const struct bt_gatt_attr *attr, void *buf,
    				uint16_t len, uint16_t offset)
    {
    	uint8_t speedRange[6];
    
    	printk("%s\n", __func__);
    
    	SpeedRangeRead(0x0064, 0x80FF, 0x0064, speedRange);
    
    	return bt_gatt_attr_read(conn, attr, buf, len, offset,
    				 &speedRange[0], sizeof(speedRange));
    }
    
    static ssize_t read_inclination_range(struct bt_conn *conn,
    				      const struct bt_gatt_attr *attr, void *buf,
    				      uint16_t len, uint16_t offset)
    {
    	uint8_t inclinationRange[6];
    
    	printk("%s\n", __func__);
    
    	InclinationRangeRead(0x0065, 0xFFFF, 0x0064, inclinationRange);
    
    	return bt_gatt_attr_read(conn, attr, buf, len, offset,
    				 &inclinationRange[0], sizeof(inclinationRange));
    }
    
    static ssize_t read_resistance_level_range(struct bt_conn *conn,
    					   const struct bt_gatt_attr *attr, void *buf,
    					   uint16_t len, uint16_t offset)
    {
    	printk("%s\n", __func__);
    
    	return 0;
    }
    
    static ssize_t read_heart_rate_range(struct bt_conn *conn,
    				     const struct bt_gatt_attr *attr, void *buf,
    				     uint16_t len, uint16_t offset)
    {
    	uint8_t heartRateRange[3];
    
    	printk("%s\n", __func__);
    
    	HeartRateRangeRead(0x40, 0x80, 0x01, heartRateRange);
    
    	return bt_gatt_attr_read(conn, attr, buf, len, offset,
    				 &heartRateRange[0], sizeof(heartRateRange));
    }
    
    static ssize_t write_ctrl_point(struct bt_conn *conn,
    				const struct bt_gatt_attr *attr,
    				const void *buf, uint16_t len, uint16_t offset,
    				uint8_t flags)
    {
    	const struct write_ctrl_point_req *req = buf;
    	uint8_t status;
    
    	printk("%s\n", __func__);
    
    	if (!ctrl_point_configured) {
    		return BT_GATT_ERR(BT_ATT_ERR_CCC_IMPROPER_CONF);
    	}
    
    	switch (req->op) {
    	case cp_request_control:
    		if (len != sizeof(req->op)) {
    			status = cp_invalid_parameter;
    			break;
    		}
    		printk("ctrl_point op: request_control\n");
    		status = cp_success;
    		break;
    	case cp_reset:
    		if (len != sizeof(req->op)) {
    			status = cp_invalid_parameter;
    			break;
    		}
    		printk("ctrl_point op: reset\n");
    		status = cp_success;
    		break;
    	case cp_start:
    		if (len != sizeof(req->op)) {
    			status = cp_invalid_parameter;
    			break;
    		}
    		printk("ctrl_point op: start\n");
    		status = cp_success;
    		break;
    	case cp_stop:
    		if (len != sizeof(req->op) + sizeof(req->stop_param)) {
    			status = cp_invalid_parameter;
    			break;
    		}
    
    		if (req->stop_param == 0x01) {          /* means stop*/
    			/* do something for stop*/
    			printk("ctrl_point op: stop\n");
    		} else if (req->stop_param == 0x02) {   /* means pause*/
    			/* do something for pause*/
    			printk("ctrl_point op: pause\n");
    		}
    		status = cp_success;
    		break;
    	case cp_set_target_speed:
    		if (len != sizeof(req->op) + sizeof(req->target_speed)) {
    			status = cp_invalid_parameter;
    			break;
    		}
    
    		printk("ctrl_point op: set_target_speed: %d\n", req->target_speed);
    
    		status = cp_success;
    		break;
    	case cp_set_target_inclination:
    		if (len != sizeof(req->op) + sizeof(req->target_inclination)) {
    			status = cp_invalid_parameter;
    			break;
    		}
    
    		printk("ctrl_point op: set_target_inclination: %d\n", req->target_inclination);
    
    		status = cp_success;
    		break;
    	default:
    		printk("ctrl_point not supported op: 0x%02x\n", req->op);
    		status = cp_op_code_not_supported;
    	}
    
    	ctrl_point_ind(conn, req->op, status, NULL, 0);
    
    	return len;
    }
    
    /* ------------------------ FTM GATT characteristic or descriptor callback end------------------------ */
    
    BT_GATT_SERVICE_DEFINE(ftm_svc,
    		       BT_GATT_PRIMARY_SERVICE(BT_UUID_FTM),                            /*index:0*/
    
    		       BT_GATT_CHARACTERISTIC(BT_UUID_FTM_FEATURE, BT_GATT_CHRC_READ,   /*index:1-2*/
    					      BT_GATT_PERM_READ, read_ftm_feature, NULL, NULL),
    
    		       BT_GATT_CHARACTERISTIC(BT_UUID_FTM_TREADMIL_DATA, BT_GATT_CHRC_NOTIFY,   /*index:3-4*/
    					      0x00, NULL, NULL, NULL),
    		       BT_GATT_CCC(treadmil_data_ccc_cfg_changed,                               /*index:5*/
    				   BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
    
    		       BT_GATT_CHARACTERISTIC(BT_UUID_FTM_TRAINING_STATUS, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
    					      BT_GATT_PERM_READ, read_training_status, NULL, NULL),     /*index:6-7*/
    		       BT_GATT_CCC(training_status_ccc_cfg_changed,                                     /*index:8*/
    				   BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
    
    		       BT_GATT_CHARACTERISTIC(BT_UUID_FTM_SUPPORT_SPEED_RANGE, BT_GATT_CHRC_READ, /*index:9-10*/
    					      BT_GATT_PERM_READ, read_speed_range, NULL, NULL),
    
    		       BT_GATT_CHARACTERISTIC(BT_UUID_FTM_SUPPORT_INCLINATION_RANGE, BT_GATT_CHRC_READ, /*index:11-12*/
    					      BT_GATT_PERM_READ, read_inclination_range, NULL, NULL),
    
    		       BT_GATT_CHARACTERISTIC(BT_UUID_FTM_SUPPORT_RESISTANCE_LEVEL_RANGE, BT_GATT_CHRC_READ, /*index:13-14*/
    					      BT_GATT_PERM_READ, read_resistance_level_range, NULL, NULL),
    
    		       BT_GATT_CHARACTERISTIC(BT_UUID_FTM_SUPPORT_HEART_RATE_RANGE, BT_GATT_CHRC_READ, /*index:15-16*/
    					      BT_GATT_PERM_READ, read_heart_rate_range, NULL, NULL),
    
    		       BT_GATT_CHARACTERISTIC(BT_UUID_FTM_CONTROL_POINT, /*index:17-18*/
    					      BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE,
    					      BT_GATT_PERM_WRITE, NULL, write_ctrl_point,
    					      NULL),
    		       BT_GATT_CCC(ctrl_point_ccc_cfg_changed, /*index:19*/
    				   BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
    
    		       BT_GATT_CHARACTERISTIC(BT_UUID_FTM_STATUS, BT_GATT_CHRC_NOTIFY,  /*index:20-21*/
    					      0x00, NULL, NULL, NULL),
    		       BT_GATT_CCC(ftm_status_ccc_cfg_changed,                          /*index:22*/
    				   BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
    		       );
    
    /* ------------------------ FTM GATT notification function start------------------------ */
    
    static void ctrl_point_ind(struct bt_conn *conn, uint8_t req_op, uint8_t status,
    			   const void *data, uint16_t data_len)
    {
    	struct ctrl_point_ind_t *ind;
    	uint8_t buf[sizeof(*ind) + data_len];
    
    	ind = (void *) buf;
    	ind->op = cp_response_code;
    	ind->req_op = req_op;
    	ind->status = status;
    
    	/* Send data if present */
    	if (data && data_len) {
    		memcpy(ind->data, data, data_len);
    	}
    
    	bt_gatt_notify(conn, &ftm_svc.attrs[17], buf, sizeof(buf));
    }
    
    static void fitness_machine_status_notify(struct bt_conn *conn, uint8_t op, const void *data, uint16_t data_len)
    {
    	struct fitness_machine_status_t *ftm_status;
    	uint8_t buf[sizeof(*ftm_status) + data_len];
    
    	ftm_status = (void *) buf;
    	ftm_status->op = op;
    
    	/* Send data if present */
    	if (data && data_len) {
    		memcpy(ftm_status->data, data, data_len);
    	}
    
    	bt_gatt_notify(conn, &ftm_svc.attrs[20], buf, sizeof(buf));
    }
    
    void TreadmillDataAccessNotify(treadmill_Data_t data)
    {
    	uint8_t treadmillData[16] = { 0x00 };
    	uint16_t flag = data.flags;
    	uint16_t flagBit = 0x0001;
    	int dataIndex = 2;
    
    	treadmillData[0] = flag & 0xFF;
    	treadmillData[1] = (flag >> 8) & 0xFF;
    	if ((flag & 0x0001) == 0) {
    		uint16_t speedData = (data.instantaneousSpeed); /* Instantaneous Speed */
    
    		addTwoOctData(treadmillData, speedData, dataIndex);
    		dataIndex += 2;
    	}
    
    	for (int i = 0; i < 11; i++) {
    		flagBit = 0x0001 << i;
    		if ((flag & flagBit) == flagBit) {      /* iterate flag bits*/
    			switch (i) {
    			case 1:                         /* Average Speed */
    			{
    				uint16_t averageData = data.averageSpeed;
    
    				addTwoOctData(treadmillData, averageData, dataIndex);
    				dataIndex += 2;
    				break;
    			}
    			case 2: /* Total Distance */
    			{
    				uint32_t distanceData = data.totalDistance;
    
    				treadmillData[dataIndex] = distanceData & 0xFF;
    				treadmillData[dataIndex + 1] = (distanceData >> 8) & 0xFF;
    				treadmillData[dataIndex + 2] = (distanceData >> 16) & 0xFF;
    				dataIndex += 3;
    				break;
    			}
    			case 3: /* Inclination */
    			{
    				int16_t inclination = data.inclination;
    
    				addTwoOctData(treadmillData, inclination, dataIndex);
    				dataIndex += 2;
    				break;
    			}
    			case 4: /* Elevation Gain */
    			{
    				uint16_t positiveGain = data.positiveElevationGain;
    				uint16_t negativeGain = data.negativeElevationGain;
    
    				addTwoOctData(treadmillData, positiveGain, dataIndex);
    				dataIndex += 2;
    				addTwoOctData(treadmillData, negativeGain, dataIndex);
    				dataIndex += 2;
    				break;
    			}
    			case 7: /* Expended Energy */
    			{
    
    				uint16_t energy = data.totalEnergy;
    				uint16_t energyPerHour = data.energyPerHour;
    				uint8_t energyPerMinute = data.energyPerMinute;
    
    				addTwoOctData(treadmillData, energy, dataIndex);
    				dataIndex += 2;
    				addTwoOctData(treadmillData, energyPerHour, dataIndex);
    				dataIndex += 2;
    				treadmillData[dataIndex] = energyPerMinute;
    				dataIndex += 1;
    				break;
    			}
    			case 8: /* Heart Rate */
    			{
    				uint8_t heartRate = data.heartRate;
    
    				treadmillData[dataIndex] = heartRate;
    				dataIndex += 1;
    				break;
    			}
    			case 10: /* Elapsed Time */
    			{
    				uint16_t time = data.elapsedTime;
    
    				addTwoOctData(treadmillData, time, dataIndex);
    				dataIndex += 2;
    				break;
    			}
    			default:
    				break;
    			}
    		}
    	}
    
    	bt_gatt_notify(NULL, &ftm_svc.attrs[3], &treadmillData[0], dataIndex);
    }
    /* ------------------------ FTM GATT notification function end------------------------ */
    
    static void ftm_simulation(void)
    {
    	if (treadmil_data_configured) {
    		g_treadmillData.flags = instantaneous_speed | treadmill_average_speed | treadmill_total_distance |
    								treadmill_elapsed_time | treadmill_expended_energy;
    		g_treadmillData.instantaneousSpeed = 720;
    		g_treadmillData.averageSpeed = 720;
    		g_treadmillData.totalDistance += 2;
    		g_treadmillData.totalEnergy = (g_treadmillData.totalDistance / 15);
    		g_treadmillData.energyPerHour = 500;
    		g_treadmillData.energyPerMinute = 500 / 60;
    		g_treadmillData.elapsedTime += 1;
    
    		TreadmillDataAccessNotify(g_treadmillData);
    	}
    }
    
    static void connected(struct bt_conn *conn, uint8_t err)
    {
    	if (err) {
    		printk("Connection failed (err 0x%02x)\n", err);
    	} else {
    		printk("Connected\n");
    	}
    }
    
    static void disconnected(struct bt_conn *conn, uint8_t reason)
    {
    	printk("Disconnected (reason 0x%02x)\n", reason);
    }
    
    BT_CONN_CB_DEFINE(conn_callbacks) = {
    	.connected = connected,
    	.disconnected = disconnected,
    };
    
    #define DEVICE_NAME CONFIG_BT_DEVICE_NAME
    #define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
    
    static uint8_t ftm_svc_data[5] = {
    	BT_UUID_16_ENCODE(BT_UUID_FTM_VAL),
    	0x01,           /*Fitness Machine Available:0: False 1: True*/
    	0x01, 0x00      /*Treadmill Supported:Bit0*/
    };
    
    static const struct bt_data ad[] = {
    	BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    	BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
    	BT_DATA_BYTES(BT_DATA_UUID16_ALL,
    		      BT_UUID_16_ENCODE(BT_UUID_FTM_VAL)),
    	BT_DATA(BT_DATA_SVC_DATA16, ftm_svc_data, 5),
    };
    
    static void bt_ready(void)
    {
    	int err;
    
    	printk("Bluetooth initialized\n");
    
    	err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad), NULL, 0);
    	if (err) {
    		printk("Advertising failed to start (err %d)\n", err);
    		return;
    	}
    
    	printk("Advertising successfully started\n");
    }
    
    void main(void)
    {
    	int err;
    
    	err = bt_enable(NULL);
    	if (err) {
    		printk("Bluetooth init failed (err %d)\n", err);
    		return;
    	}
    
    	bt_ready();
    
    	while (1) {
    		k_sleep(K_SECONDS(1));
    
    		/* FTM simulation */
    		if (ftm_simulate) {
    			ftm_simulation();
    
    			if (g_treadmillData.elapsedTime == 10) {
    				fitness_machine_status_notify(NULL, start_by_user, NULL, 0);
    			}
    		}
    	}
    }
    
    ftm.h

    And yes, I need to read the fitness machine service file. Thank you.

  • "

    void TreadmillDataAccessNotify(treadmill_Data_t data)
    {
    uint8_t treadmillData[16] = { 0x00 };
    uint16_t flag = data.flags;
    uint16_t flagBit = 0x0001;
    int dataIndex = 2;

    treadmillData[0] = flag & 0xFF;
    treadmillData[1] = (flag >> 8) & 0xFF;
    if ((flag & 0x0001) == 0) {
    uint16_t speedData = (data.instantaneousSpeed); /* Instantaneous Speed */

    addTwoOctData(treadmillData, speedData, dataIndex);
    dataIndex += 2;
    }

    for (int i = 0; i < 11; i++) {
    flagBit = 0x0001 << i;
    if ((flag & flagBit) == flagBit) { /* iterate flag bits*/
    switch (i) {
    case 1: /* Average Speed */
    {
    uint16_t averageData = data.averageSpeed;

    addTwoOctData(treadmillData, averageData, dataIndex);
    dataIndex += 2;
    break;
    }
    case 2: /* Total Distance */
    {
    uint32_t distanceData = data.totalDistance;

    treadmillData[dataIndex] = distanceData & 0xFF;
    treadmillData[dataIndex + 1] = (distanceData >> 8) & 0xFF;
    treadmillData[dataIndex + 2] = (distanceData >> 16) & 0xFF;
    dataIndex += 3;
    break;
    }
    case 3: /* Inclination */
    {
    int16_t inclination = data.inclination;

    addTwoOctData(treadmillData, inclination, dataIndex);
    dataIndex += 2;
    break;
    }
    case 4: /* Elevation Gain */
    {
    uint16_t positiveGain = data.positiveElevationGain;
    uint16_t negativeGain = data.negativeElevationGain;

    addTwoOctData(treadmillData, positiveGain, dataIndex);
    dataIndex += 2;
    addTwoOctData(treadmillData, negativeGain, dataIndex);
    dataIndex += 2;
    break;
    }
    case 7: /* Expended Energy */
    {

    uint16_t energy = data.totalEnergy;
    uint16_t energyPerHour = data.energyPerHour;
    uint8_t energyPerMinute = data.energyPerMinute;

    addTwoOctData(treadmillData, energy, dataIndex);
    dataIndex += 2;
    addTwoOctData(treadmillData, energyPerHour, dataIndex);
    dataIndex += 2;
    treadmillData[dataIndex] = energyPerMinute;
    dataIndex += 1;
    break;
    }
    case 8: /* Heart Rate */
    {
    uint8_t heartRate = data.heartRate;

    treadmillData[dataIndex] = heartRate;
    dataIndex += 1;
    break;
    }
    case 10: /* Elapsed Time */
    {
    uint16_t time = data.elapsedTime;

    addTwoOctData(treadmillData, time, dataIndex);
    dataIndex += 2;
    break;
    }
    default:
    break;
    }
    }
    }

    bt_gatt_notify(NULL, &ftm_svc.attrs[3], &treadmillData[0], dataIndex);
    }"
    This piece of code seems to be processing data from the treadmill. So, how can I process indoor bike data?

Related