| /* main.c - Application main entry point */ |
| |
| /* |
| * Copyright (c) 2016 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <stdbool.h> |
| #include <zephyr/types.h> |
| #include <stddef.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <zephyr/sys/printk.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/kernel.h> |
| |
| #include <zephyr/bluetooth/bluetooth.h> |
| #include <zephyr/bluetooth/hci.h> |
| #include <zephyr/bluetooth/conn.h> |
| #include <zephyr/bluetooth/uuid.h> |
| #include <zephyr/bluetooth/gatt.h> |
| #include <zephyr/bluetooth/services/bas.h> |
| |
| #define SENSOR_1_NAME "Temperature Sensor 1" |
| #define SENSOR_2_NAME "Temperature Sensor 2" |
| #define SENSOR_3_NAME "Humidity Sensor" |
| |
| /* Sensor Internal Update Interval [seconds] */ |
| #define SENSOR_1_UPDATE_IVAL 5 |
| #define SENSOR_2_UPDATE_IVAL 12 |
| #define SENSOR_3_UPDATE_IVAL 60 |
| |
| /* ESS error definitions */ |
| #define ESS_ERR_WRITE_REJECT 0x80 |
| #define ESS_ERR_COND_NOT_SUPP 0x81 |
| |
| /* ESS Trigger Setting conditions */ |
| #define ESS_TRIGGER_INACTIVE 0x00 |
| #define ESS_TRIGGER_FIXED_TIME_INTERVAL 0x01 |
| #define ESS_TRIGGER_NO_LESS_THAN_SPECIFIED_TIME 0x02 |
| #define ESS_TRIGGER_VALUE_CHANGED 0x03 |
| #define ESS_TRIGGER_LESS_THAN_REF_VALUE 0x04 |
| #define ESS_TRIGGER_LESS_OR_EQUAL_TO_REF_VALUE 0x05 |
| #define ESS_TRIGGER_GREATER_THAN_REF_VALUE 0x06 |
| #define ESS_TRIGGER_GREATER_OR_EQUAL_TO_REF_VALUE 0x07 |
| #define ESS_TRIGGER_EQUAL_TO_REF_VALUE 0x08 |
| #define ESS_TRIGGER_NOT_EQUAL_TO_REF_VALUE 0x09 |
| |
| /* ESS Measurement Descriptor – Sampling Functions */ |
| #define ESS_DESC_SAMPLING_UNSPECIFIED 0x00 |
| #define ESS_DESC_SAMPLING_INSTANTANEOUS 0x01 |
| #define ESS_DESC_SAMPLING_ARITHMETIC_MEAN 0x02 |
| #define ESS_DESC_SAMPLING_RMS 0x03 |
| #define ESS_DESC_SAMPLING_MAXIMUM 0x04 |
| #define ESS_DESC_SAMPLING_MINIMUM 0x05 |
| #define ESS_DESC_SAMPLING_ACCUMULATED 0x06 |
| #define ESS_DESC_SAMPLING_COUNT 0x07 |
| |
| /* ES Measurement Descriptor - Applications */ |
| #define ESS_DESC_APP_UNSPECIFIED 0x00 |
| #define ESS_DESC_APP_AIR 0x01 |
| #define ESS_DESC_APP_WATER 0x02 |
| #define ESS_DESC_APP_BAROMETRIC 0x03 |
| #define ESS_DESC_APP_SOIL 0x04 |
| #define ESS_DESC_APP_INFRARED 0x05 |
| #define ESS_DESC_APP_MAP_DATABASE 0x06 |
| #define ESS_DESC_APP_BAROMETRIC_ELEVATION_SOURCE 0x07 |
| #define ESS_DESC_APP_GPS_ONLY_ELEVATION_SOURCE 0x08 |
| #define ESS_DESC_APP_GPS_AND_MAP_DATABASE_ELEVATION_SOURCE 0x09 |
| #define ESS_DESC_APP_VERTICAL_DATUM_ELEVATION_SOURCE 0x0A |
| #define ESS_DESC_APP_ONSHORE 0x0B |
| #define ESS_DESC_APP_ONBOARD_VESSEL_OR_VEHICLE 0x0C |
| #define ESS_DESC_APP_FRONT 0x0D |
| #define ESS_DESC_APP_BACK_REAR 0x0E |
| #define ESS_DESC_APP_UPPER 0x0F |
| #define ESS_DESC_APP_LOWER 0x10 |
| #define ESS_DESC_APP_PRIMARY 0x11 |
| #define ESS_DESC_APP_SECONDARY 0x12 |
| #define ESS_DESC_APP_OUTDOOR 0x13 |
| #define ESS_DESC_APP_INDOOR 0x14 |
| #define ESS_DESC_APP_TOP 0x15 |
| #define ESS_DESC_APP_BOTTOM 0x16 |
| #define ESS_DESC_APP_MAIN 0x17 |
| #define ESS_DESC_APP_BACKUP 0x18 |
| #define ESS_DESC_APP_AUXILIARY 0x19 |
| #define ESS_DESC_APP_SUPPLEMENTARY 0x1A |
| #define ESS_DESC_APP_INSIDE 0x1B |
| #define ESS_DESC_APP_OUTSIDE 0x1C |
| #define ESS_DESC_APP_LEFT 0x1D |
| #define ESS_DESC_APP_RIGHT 0x1E |
| #define ESS_DESC_APP_INTERNAL 0x1F |
| #define ESS_DESC_APP_EXTERNAL 0x20 |
| #define ESS_DESC_APP_SOLAR 0x21 |
| |
| static ssize_t read_u16(struct bt_conn *conn, const struct bt_gatt_attr *attr, |
| void *buf, uint16_t len, uint16_t offset) |
| { |
| const uint16_t *u16 = attr->user_data; |
| uint16_t value = sys_cpu_to_le16(*u16); |
| |
| return bt_gatt_attr_read(conn, attr, buf, len, offset, &value, |
| sizeof(value)); |
| } |
| |
| /* Environmental Sensing Service Declaration */ |
| |
| struct es_measurement { |
| uint16_t flags; /* Reserved for Future Use */ |
| uint8_t sampling_func; |
| uint32_t meas_period; |
| uint32_t update_interval; |
| uint8_t application; |
| uint8_t meas_uncertainty; |
| }; |
| |
| struct temperature_sensor { |
| int16_t temp_value; |
| |
| /* Valid Range */ |
| int16_t lower_limit; |
| int16_t upper_limit; |
| |
| /* ES trigger setting - Value Notification condition */ |
| uint8_t condition; |
| union { |
| uint32_t seconds; |
| int16_t ref_val; /* Reference temperature */ |
| }; |
| |
| struct es_measurement meas; |
| }; |
| |
| struct humidity_sensor { |
| int16_t humid_value; |
| |
| struct es_measurement meas; |
| }; |
| |
| static bool simulate_temp; |
| static struct temperature_sensor sensor_1 = { |
| .temp_value = 1200, |
| .lower_limit = -10000, |
| .upper_limit = 10000, |
| .condition = ESS_TRIGGER_VALUE_CHANGED, |
| .meas.sampling_func = ESS_DESC_SAMPLING_UNSPECIFIED, |
| .meas.meas_period = 0x01, |
| .meas.update_interval = SENSOR_1_UPDATE_IVAL, |
| .meas.application = ESS_DESC_APP_OUTSIDE, |
| .meas.meas_uncertainty = 0x04, |
| }; |
| |
| static struct temperature_sensor sensor_2 = { |
| .temp_value = 1800, |
| .lower_limit = -1000, |
| .upper_limit = 5000, |
| .condition = ESS_TRIGGER_VALUE_CHANGED, |
| .meas.sampling_func = ESS_DESC_SAMPLING_UNSPECIFIED, |
| .meas.meas_period = 0x01, |
| .meas.update_interval = SENSOR_2_UPDATE_IVAL, |
| .meas.application = ESS_DESC_APP_INSIDE, |
| .meas.meas_uncertainty = 0x04, |
| }; |
| |
| static struct humidity_sensor sensor_3 = { |
| .humid_value = 6233, |
| .meas.sampling_func = ESS_DESC_SAMPLING_ARITHMETIC_MEAN, |
| .meas.meas_period = 0x0e10, |
| .meas.update_interval = SENSOR_3_UPDATE_IVAL, |
| .meas.application = ESS_DESC_APP_OUTSIDE, |
| .meas.meas_uncertainty = 0x01, |
| }; |
| |
| static void temp_ccc_cfg_changed(const struct bt_gatt_attr *attr, |
| uint16_t value) |
| { |
| simulate_temp = value == BT_GATT_CCC_NOTIFY; |
| } |
| |
| struct read_es_measurement_rp { |
| uint16_t flags; /* Reserved for Future Use */ |
| uint8_t sampling_function; |
| uint8_t measurement_period[3]; |
| uint8_t update_interval[3]; |
| uint8_t application; |
| uint8_t measurement_uncertainty; |
| } __packed; |
| |
| static ssize_t read_es_measurement(struct bt_conn *conn, |
| const struct bt_gatt_attr *attr, void *buf, |
| uint16_t len, uint16_t offset) |
| { |
| const struct es_measurement *value = attr->user_data; |
| struct read_es_measurement_rp rsp; |
| |
| rsp.flags = sys_cpu_to_le16(value->flags); |
| rsp.sampling_function = value->sampling_func; |
| sys_put_le24(value->meas_period, rsp.measurement_period); |
| sys_put_le24(value->update_interval, rsp.update_interval); |
| rsp.application = value->application; |
| rsp.measurement_uncertainty = value->meas_uncertainty; |
| |
| return bt_gatt_attr_read(conn, attr, buf, len, offset, &rsp, |
| sizeof(rsp)); |
| } |
| |
| static ssize_t read_temp_valid_range(struct bt_conn *conn, |
| const struct bt_gatt_attr *attr, void *buf, |
| uint16_t len, uint16_t offset) |
| { |
| const struct temperature_sensor *sensor = attr->user_data; |
| uint16_t tmp[] = {sys_cpu_to_le16(sensor->lower_limit), |
| sys_cpu_to_le16(sensor->upper_limit)}; |
| |
| return bt_gatt_attr_read(conn, attr, buf, len, offset, tmp, |
| sizeof(tmp)); |
| } |
| |
| struct es_trigger_setting_seconds { |
| uint8_t condition; |
| uint8_t sec[3]; |
| } __packed; |
| |
| struct es_trigger_setting_reference { |
| uint8_t condition; |
| int16_t ref_val; |
| } __packed; |
| |
| static ssize_t read_temp_trigger_setting(struct bt_conn *conn, |
| const struct bt_gatt_attr *attr, |
| void *buf, uint16_t len, |
| uint16_t offset) |
| { |
| const struct temperature_sensor *sensor = attr->user_data; |
| |
| switch (sensor->condition) { |
| /* Operand N/A */ |
| case ESS_TRIGGER_INACTIVE: |
| __fallthrough; |
| case ESS_TRIGGER_VALUE_CHANGED: |
| return bt_gatt_attr_read(conn, attr, buf, len, offset, |
| &sensor->condition, |
| sizeof(sensor->condition)); |
| /* Seconds */ |
| case ESS_TRIGGER_FIXED_TIME_INTERVAL: |
| __fallthrough; |
| case ESS_TRIGGER_NO_LESS_THAN_SPECIFIED_TIME: { |
| struct es_trigger_setting_seconds rp; |
| |
| rp.condition = sensor->condition; |
| sys_put_le24(sensor->seconds, rp.sec); |
| |
| return bt_gatt_attr_read(conn, attr, buf, len, offset, |
| &rp, sizeof(rp)); |
| } |
| /* Reference temperature */ |
| default: { |
| struct es_trigger_setting_reference rp; |
| |
| rp.condition = sensor->condition; |
| rp.ref_val = sys_cpu_to_le16(sensor->ref_val); |
| |
| return bt_gatt_attr_read(conn, attr, buf, len, offset, |
| &rp, sizeof(rp)); |
| } |
| } |
| } |
| |
| static bool check_condition(uint8_t condition, int16_t old_val, int16_t new_val, |
| int16_t ref_val) |
| { |
| switch (condition) { |
| case ESS_TRIGGER_INACTIVE: |
| return false; |
| case ESS_TRIGGER_FIXED_TIME_INTERVAL: |
| case ESS_TRIGGER_NO_LESS_THAN_SPECIFIED_TIME: |
| /* TODO: Check time requirements */ |
| return false; |
| case ESS_TRIGGER_VALUE_CHANGED: |
| return new_val != old_val; |
| case ESS_TRIGGER_LESS_THAN_REF_VALUE: |
| return new_val < ref_val; |
| case ESS_TRIGGER_LESS_OR_EQUAL_TO_REF_VALUE: |
| return new_val <= ref_val; |
| case ESS_TRIGGER_GREATER_THAN_REF_VALUE: |
| return new_val > ref_val; |
| case ESS_TRIGGER_GREATER_OR_EQUAL_TO_REF_VALUE: |
| return new_val >= ref_val; |
| case ESS_TRIGGER_EQUAL_TO_REF_VALUE: |
| return new_val == ref_val; |
| case ESS_TRIGGER_NOT_EQUAL_TO_REF_VALUE: |
| return new_val != ref_val; |
| default: |
| return false; |
| } |
| } |
| |
| static void update_temperature(struct bt_conn *conn, |
| const struct bt_gatt_attr *chrc, int16_t value, |
| struct temperature_sensor *sensor) |
| { |
| bool notify = check_condition(sensor->condition, |
| sensor->temp_value, value, |
| sensor->ref_val); |
| |
| /* Update temperature value */ |
| sensor->temp_value = value; |
| |
| /* Trigger notification if conditions are met */ |
| if (notify) { |
| value = sys_cpu_to_le16(sensor->temp_value); |
| |
| bt_gatt_notify(conn, chrc, &value, sizeof(value)); |
| } |
| } |
| |
| BT_GATT_SERVICE_DEFINE(ess_svc, |
| BT_GATT_PRIMARY_SERVICE(BT_UUID_ESS), |
| |
| /* Temperature Sensor 1 */ |
| BT_GATT_CHARACTERISTIC(BT_UUID_TEMPERATURE, |
| BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, |
| BT_GATT_PERM_READ, |
| read_u16, NULL, &sensor_1.temp_value), |
| BT_GATT_DESCRIPTOR(BT_UUID_ES_MEASUREMENT, BT_GATT_PERM_READ, |
| read_es_measurement, NULL, &sensor_1.meas), |
| BT_GATT_CUD(SENSOR_1_NAME, BT_GATT_PERM_READ), |
| BT_GATT_DESCRIPTOR(BT_UUID_VALID_RANGE, BT_GATT_PERM_READ, |
| read_temp_valid_range, NULL, &sensor_1), |
| BT_GATT_DESCRIPTOR(BT_UUID_ES_TRIGGER_SETTING, |
| BT_GATT_PERM_READ, read_temp_trigger_setting, |
| NULL, &sensor_1), |
| BT_GATT_CCC(temp_ccc_cfg_changed, |
| BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), |
| |
| /* Temperature Sensor 2 */ |
| BT_GATT_CHARACTERISTIC(BT_UUID_TEMPERATURE, |
| BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, |
| BT_GATT_PERM_READ, |
| read_u16, NULL, &sensor_2.temp_value), |
| BT_GATT_DESCRIPTOR(BT_UUID_ES_MEASUREMENT, BT_GATT_PERM_READ, |
| read_es_measurement, NULL, &sensor_2.meas), |
| BT_GATT_CUD(SENSOR_2_NAME, BT_GATT_PERM_READ), |
| BT_GATT_DESCRIPTOR(BT_UUID_VALID_RANGE, BT_GATT_PERM_READ, |
| read_temp_valid_range, NULL, &sensor_2), |
| BT_GATT_DESCRIPTOR(BT_UUID_ES_TRIGGER_SETTING, |
| BT_GATT_PERM_READ, read_temp_trigger_setting, |
| NULL, &sensor_2), |
| BT_GATT_CCC(temp_ccc_cfg_changed, |
| BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), |
| |
| /* Humidity Sensor */ |
| BT_GATT_CHARACTERISTIC(BT_UUID_HUMIDITY, BT_GATT_CHRC_READ, |
| BT_GATT_PERM_READ, |
| read_u16, NULL, &sensor_3.humid_value), |
| BT_GATT_CUD(SENSOR_3_NAME, BT_GATT_PERM_READ), |
| BT_GATT_DESCRIPTOR(BT_UUID_ES_MEASUREMENT, BT_GATT_PERM_READ, |
| read_es_measurement, NULL, &sensor_3.meas), |
| ); |
| |
| static void ess_simulate(void) |
| { |
| static uint8_t i; |
| uint16_t val; |
| |
| if (!(i % SENSOR_1_UPDATE_IVAL)) { |
| val = 1200 + i; |
| update_temperature(NULL, &ess_svc.attrs[2], val, &sensor_1); |
| } |
| |
| if (!(i % SENSOR_2_UPDATE_IVAL)) { |
| val = 1800 + i; |
| update_temperature(NULL, &ess_svc.attrs[9], val, &sensor_2); |
| } |
| |
| if (!(i % SENSOR_3_UPDATE_IVAL)) { |
| sensor_3.humid_value = 6233 + (i % 13); |
| } |
| |
| if (!(i % INT8_MAX)) { |
| i = 0U; |
| } |
| |
| i++; |
| } |
| |
| static const struct bt_data ad[] = { |
| BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), |
| BT_DATA_BYTES(BT_DATA_GAP_APPEARANCE, 0x00, 0x03), |
| BT_DATA_BYTES(BT_DATA_UUID16_ALL, |
| BT_UUID_16_ENCODE(BT_UUID_ESS_VAL), |
| BT_UUID_16_ENCODE(BT_UUID_BAS_VAL)), |
| }; |
| |
| static const struct bt_data sd[] = { |
| BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1), |
| }; |
| |
| static void connected(struct bt_conn *conn, uint8_t err) |
| { |
| if (err) { |
| printk("Connection failed, err 0x%02x %s\n", err, bt_hci_err_to_str(err)); |
| } else { |
| printk("Connected\n"); |
| } |
| } |
| |
| static void disconnected(struct bt_conn *conn, uint8_t reason) |
| { |
| printk("Disconnected, reason 0x%02x %s\n", reason, bt_hci_err_to_str(reason)); |
| } |
| |
| BT_CONN_CB_DEFINE(conn_callbacks) = { |
| .connected = connected, |
| .disconnected = disconnected, |
| }; |
| |
| static void bt_ready(void) |
| { |
| int err; |
| |
| printk("Bluetooth initialized\n"); |
| |
| err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd)); |
| if (err) { |
| printk("Advertising failed to start (err %d)\n", err); |
| return; |
| } |
| |
| printk("Advertising successfully started\n"); |
| } |
| |
| static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey) |
| { |
| char addr[BT_ADDR_LE_STR_LEN]; |
| |
| bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); |
| |
| printk("Passkey for %s: %06u\n", addr, passkey); |
| } |
| |
| static void auth_cancel(struct bt_conn *conn) |
| { |
| char addr[BT_ADDR_LE_STR_LEN]; |
| |
| bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); |
| |
| printk("Pairing cancelled: %s\n", addr); |
| } |
| |
| static struct bt_conn_auth_cb auth_cb_display = { |
| .passkey_display = auth_passkey_display, |
| .passkey_entry = NULL, |
| .cancel = auth_cancel, |
| }; |
| |
| static void bas_notify(void) |
| { |
| uint8_t battery_level = bt_bas_get_battery_level(); |
| |
| battery_level--; |
| |
| if (!battery_level) { |
| battery_level = 100U; |
| } |
| |
| bt_bas_set_battery_level(battery_level); |
| } |
| |
| int main(void) |
| { |
| int err; |
| |
| err = bt_enable(NULL); |
| if (err) { |
| printk("Bluetooth init failed (err %d)\n", err); |
| return 0; |
| } |
| |
| bt_ready(); |
| |
| bt_conn_auth_cb_register(&auth_cb_display); |
| |
| while (1) { |
| k_sleep(K_SECONDS(1)); |
| |
| /* Temperature simulation */ |
| if (simulate_temp) { |
| ess_simulate(); |
| } |
| |
| /* Battery level simulation */ |
| bas_notify(); |
| } |
| return 0; |
| } |