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.

Parents
  • 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.

Reply
  • 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.

Children
No Data
Related