diff --git a/examples/ble/CMakeLists.txt b/examples/ble/CMakeLists.txt new file mode 100644 index 0000000000..037f9b2c8a --- /dev/null +++ b/examples/ble/CMakeLists.txt @@ -0,0 +1,33 @@ +# ############################################################################## +# apps/examples/ble/CMakeLists.txt +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_EXAMPLES_BLE) + nuttx_add_application( + NAME + ${CONFIG_EXAMPLES_BLE_PROGNAME} + PRIORITY + ${CONFIG_EXAMPLES_BLE_PRIORITY} + STACKSIZE + ${CONFIG_EXAMPLES_BLE_STACKSIZE} + MODULE + ${CONFIG_EXAMPLES_BLE} + SRCS + ble_main.c) +endif() diff --git a/examples/ble/Kconfig b/examples/ble/Kconfig new file mode 100644 index 0000000000..63618ea4c3 --- /dev/null +++ b/examples/ble/Kconfig @@ -0,0 +1,37 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config EXAMPLES_BLE + tristate "BLE sensor reading example" + default n + depends on WIRELESS_BLUETOOTH + ---help--- + Enable the BLE example + +if EXAMPLES_BLE + +config EXAMPLES_BLE_PROGNAME + string "Program name" + default "ble" + ---help--- + This is the name of the program that will be used when the NSH ELF + program is installed. + +config EXAMPLES_BLE_PRIORITY + int "BLE task priority" + default 100 + +config EXAMPLES_BLE_STACKSIZE + int "BLE stack size" + default DEFAULT_TASK_STACKSIZE + +config EXAMPLES_BLE_USE_BME680 + bool "Use BME680 sensor data" + default n + depends on SENSORS_BME680 + ---help--- + If enabled, use real BME680 sensor data. If disabled, use dummy data. + +endif diff --git a/examples/ble/Make.defs b/examples/ble/Make.defs new file mode 100644 index 0000000000..1795a888c5 --- /dev/null +++ b/examples/ble/Make.defs @@ -0,0 +1,23 @@ +############################################################################ +# apps/examples/ble/Make.defs +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifneq ($(CONFIG_EXAMPLES_BLE),) +CONFIGURED_APPS += $(APPDIR)/examples/ble +endif diff --git a/examples/ble/Makefile b/examples/ble/Makefile new file mode 100644 index 0000000000..b337670213 --- /dev/null +++ b/examples/ble/Makefile @@ -0,0 +1,39 @@ +############################################################################ +# apps/examples/ble/Makefile +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(APPDIR)/Make.defs + +# BLE Barometer sensor example built-in application info + +PROGNAME = $(CONFIG_EXAMPLES_BLE_PROGNAME) +PRIORITY = $(CONFIG_EXAMPLES_BLE_PRIORITY) +STACKSIZE = $(CONFIG_EXAMPLES_BLE_STACKSIZE) +MODULE = $(CONFIG_EXAMPLES_BLE) + +CSRCS = ble.c +MAINSRC = ble_main.c + +ifeq ($(CONFIG_EXAMPLES_BLE_USE_BME680),y) + CSRCS += sensors.c +else + CSRCS += dummy.c +endif + +include $(APPDIR)/Application.mk diff --git a/examples/ble/ble.c b/examples/ble/ble.c new file mode 100644 index 0000000000..f899a4a281 --- /dev/null +++ b/examples/ble/ble.c @@ -0,0 +1,198 @@ +#include +#include +#include +#include +#include "sensors.h" + +#define GASVC 0x0001 +#define NAME_CHR (GASVC + 0x0002) +#define NAME_DSC (GASVC + 0x0003) +#define APPEARANCE_CHR (GASVC + 0x0004) +#define APPEARANCE_DSC (GASVC + 0x0005) +#define SENSOR_SVC (APPEARANCE_DSC + 0x0001) +#define TEMP_CHR (SENSOR_SVC + 0x0001) +#define TEMP_DSC (SENSOR_SVC + 0x0002) +#define HUM_CHR (TEMP_DSC + 0x0001) +#define HUM_DSC (TEMP_DSC + 0x0002) +#define GAS_CHR (HUM_DSC + 0x0001) +#define GAS_DSC (HUM_DSC + 0x0002) +#define PRESS_CHR (GAS_DSC + 0x0001) +#define PRESS_DSC (GAS_DSC + 0x0002) + +// Bluetooth UUIDs +static struct bt_uuid_s g_gap_uuid = +{ + .type = BT_UUID_16, + .u.u16 = BT_UUID_GAP, +}; + +static struct bt_uuid_s g_device_name_uuid = +{ + .type = BT_UUID_16, + .u.u16 = BT_UUID_GAP_DEVICE_NAME, +}; + +static struct bt_uuid_s g_appearance_uuid = +{ + .type = BT_UUID_16, + .u.u16 = BT_UUID_GAP_APPEARANCE, +}; + +static struct bt_uuid_s g_sensor_uuid = { + .type = BT_UUID_128, + .u.u128 = { + 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, + 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0 + } +}; + +static struct bt_uuid_s g_temp_uuid = +{ + .type = BT_UUID_16, + .u.u16 = 0x2A6E, // Temperature UUID +}; + +static struct bt_uuid_s g_hum_uuid = +{ + .type = BT_UUID_16, + .u.u16 = 0x2A6F, // Humidity UUID +}; + +static struct bt_uuid_s g_press_uuid = +{ + .type = BT_UUID_16, + .u.u16 = 0x2A6D, // Pressure UUID +}; + +// GATT characteristics +static struct bt_gatt_chrc_s g_name_chrc = +{ + .properties = BT_GATT_CHRC_READ, + .value_handle = NAME_DSC, + .uuid = &g_device_name_uuid, +}; + +static struct bt_gatt_chrc_s g_appearance_chrc = +{ + .properties = BT_GATT_CHRC_READ, + .value_handle = APPEARANCE_DSC, + .uuid = &g_appearance_uuid, +}; + +static struct bt_gatt_chrc_s g_temp_chrc = +{ + .properties = BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, + .value_handle = TEMP_DSC, + .uuid = &g_temp_uuid, +}; + +static struct bt_gatt_chrc_s g_hum_chrc = +{ + .properties = BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, + .value_handle = HUM_DSC, + .uuid = &g_hum_uuid, +}; + +static struct bt_gatt_chrc_s g_press_chrc = +{ + .properties = BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, + .value_handle = PRESS_DSC, + .uuid = &g_press_uuid, +}; + +static int read_name(FAR struct bt_conn_s *conn, + FAR const struct bt_gatt_attr_s *attr, + FAR void *buf, uint8_t len, uint16_t offset) +{ + const char *name = CONFIG_DEVICE_NAME; + return bt_gatt_attr_read(conn, attr, buf, len, offset, name, strlen(name)); +} + +static int read_appearance(FAR struct bt_conn_s *conn, + FAR const struct bt_gatt_attr_s *attr, + FAR void *buf, uint8_t len, uint16_t offset) +{ + uint16_t appearance = 0; // Set appropriate appearance value + return bt_gatt_attr_read(conn, attr, buf, len, offset, &appearance, sizeof(appearance)); +} + +static int read_temperature(FAR struct bt_conn_s *conn, + FAR const struct bt_gatt_attr_s *attr, + FAR void *buf, uint8_t len, uint16_t offset) +{ + uint16_t temp = (uint16_t) (get_temperature() * 100); + return bt_gatt_attr_read(conn, attr, buf, len, offset, &temp, sizeof(uint16_t)); +} + +static int read_humidity(FAR struct bt_conn_s *conn, + FAR const struct bt_gatt_attr_s *attr, + FAR void *buf, uint8_t len, uint16_t offset) +{ + uint16_t hum = (uint16_t) (get_humidity() * 100); + return bt_gatt_attr_read(conn, attr, buf, len, offset, &hum, sizeof(uint16_t)); +} + +static int read_gas(FAR struct bt_conn_s *conn, + FAR const struct bt_gatt_attr_s *attr, + FAR void *buf, uint8_t len, uint16_t offset) +{ + uint16_t gas = (uint16_t) (get_gas_resistance() * 100); + return bt_gatt_attr_read(conn, attr, buf, len, offset, &gas, sizeof(uint16_t)); +} + +static int read_pressure(FAR struct bt_conn_s *conn, + FAR const struct bt_gatt_attr_s *attr, + FAR void *buf, uint8_t len, uint16_t offset) +{ + uint32_t press = (uint32_t) (get_pressure() * 1000); + return bt_gatt_attr_read(conn, attr, buf, len, offset, &press, sizeof(uint32_t)); +} + +// GATT attributes +static const struct bt_gatt_attr_s attrs[] = +{ + BT_GATT_PRIMARY_SERVICE(GASVC, &g_gap_uuid), + BT_GATT_CHARACTERISTIC(NAME_CHR, &g_name_chrc), + BT_GATT_DESCRIPTOR(NAME_DSC, &g_device_name_uuid, BT_GATT_PERM_READ, + read_name, NULL, NULL), + BT_GATT_CHARACTERISTIC(APPEARANCE_CHR, &g_appearance_chrc), + BT_GATT_DESCRIPTOR(APPEARANCE_DSC, &g_appearance_uuid, + BT_GATT_PERM_READ, read_appearance, NULL, NULL), + + BT_GATT_PRIMARY_SERVICE(SENSOR_SVC, &g_sensor_uuid), + BT_GATT_CHARACTERISTIC(TEMP_CHR, &g_temp_chrc), + BT_GATT_DESCRIPTOR(TEMP_DSC, &g_temp_uuid, BT_GATT_PERM_READ, read_temperature, NULL, NULL), + BT_GATT_CHARACTERISTIC(HUM_CHR, &g_hum_chrc), + BT_GATT_DESCRIPTOR(HUM_DSC, &g_hum_uuid, BT_GATT_PERM_READ, read_humidity, NULL, NULL), + BT_GATT_CHARACTERISTIC(PRESS_CHR, &g_press_chrc), + BT_GATT_DESCRIPTOR(PRESS_DSC, &g_press_uuid, BT_GATT_PERM_READ, read_pressure, NULL, NULL), +}; + +static void start_scanning(void) { + struct btreq_s btreq; + memset(&btreq, 0, sizeof(struct btreq_s)); + strlcpy(btreq.btr_name, "bnep0", 16); + btreq.btr_dupenable = false; + + int sockfd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP); + if (sockfd < 0) + { + fprintf(stderr, "ERROR: failed to create socket"); + return; + } + int ret = ioctl(sockfd, SIOCBTSCANSTART, + (unsigned long)((uintptr_t)&btreq)); + if (ret < 0) + { + fprintf(stderr, "ERROR: ioctl(SIOCBTSCANSTART) failed"); + } + close(sockfd); +} + +void setup_ble(void) { + // TODO: Scanning is enabled here in order to make advertising work. + // It has been noticed that advertising packets are not sent by default, + // unless scanning is enabled. + start_scanning(); + bt_gatt_register(attrs, nitems(attrs)); +} diff --git a/examples/ble/ble_main.c b/examples/ble/ble_main.c new file mode 100644 index 0000000000..f93af8e0ac --- /dev/null +++ b/examples/ble/ble_main.c @@ -0,0 +1,59 @@ +/**************************************************************************** + * apps/examples/ble/ble_main.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include "sensors.h" + +extern void setup_ble(void); + +#define STACKSIZE 2048 + +int main(int argc, FAR char *argv[]) { + int ret; + + init_sensors(); + setup_ble(); + + pthread_t thread; + pthread_attr_t attr; + struct sched_param param; + + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, STACKSIZE); + param.sched_priority = SCHED_PRIORITY_DEFAULT; + pthread_attr_setschedparam(&attr, ¶m); + + ret = pthread_create(&thread, &attr, monitor_sensors, NULL); + if (ret != 0) { + printf("Error: pthread_create failed: %d\n", ret); + return 1; + } + + pthread_join(thread, NULL); + + close_sensors_fd(); + + return 0; +} \ No newline at end of file diff --git a/examples/ble/dummy.c b/examples/ble/dummy.c new file mode 100644 index 0000000000..8ed5a2479a --- /dev/null +++ b/examples/ble/dummy.c @@ -0,0 +1,31 @@ +#include "sensors.h" + +float get_temperature(void) { + return 40; +} + +float get_pressure(void) { + return 1000; +} + +float get_gas_resistance(void) { + return 100; +} + +float get_humidity(void) { + return 30; +} + +void *monitor_sensors(void *arg) { + (void) arg; + while(1) (void) 0; + return NULL; +} + +int init_sensors(void) { + return 0; +} + +void close_sensors_fd(void) { + return; +} \ No newline at end of file diff --git a/examples/ble/sensors.c b/examples/ble/sensors.c new file mode 100644 index 0000000000..44b556b121 --- /dev/null +++ b/examples/ble/sensors.c @@ -0,0 +1,122 @@ +#include +#include + +#include "sensors.h" + +#define NB_LOWERHALFS 3 + +struct pollfd pfds[3]; +int baro_fd, hum_fd, gas_fd; + +struct sensor_baro baro_data; +struct sensor_humi humi_data; +struct sensor_gas gas_data; + +float get_temperature(void) { + return baro_data.temperature; +} + +float get_pressure(void) { + return baro_data.pressure; +} + +float get_gas_resistance(void) { + return gas_data.gas_resistance; +} + +float get_humidity(void) { + return humi_data.humidity; +} + +static void read_barometer(void) { + int ret = read(baro_fd, &baro_data, sizeof(struct sensor_baro)); + if (ret != sizeof(struct sensor_baro)) { + perror("Could not read temperature"); + } + return; +} + +static void read_humidity(void) { + int ret = read(hum_fd, &humi_data, sizeof(struct sensor_humi)); + if (ret != sizeof(struct sensor_humi)) { + perror("Could not read humidity"); + } + return; +} + +static void read_gas(void) { + int ret = read(gas_fd, &gas_data, sizeof(struct sensor_gas)); + if (ret != sizeof(struct sensor_gas)) { + perror("Could not read gas resistance"); + } +} + +void *monitor_sensors(void *arg) { + while (1) { + int ret = poll(pfds, NB_LOWERHALFS, -1); + if (ret < 0) + { + perror("Could not poll sensor."); + return NULL; + } + read_barometer(); + read_humidity(); + read_gas(); + + printf("Temperature [C] = %f\n" + "Pressure [hPa] = %f\n" + "Humidity [rH] = %f\n" + "Gas resistance [kOhm] = %f\n", + baro_data.temperature, baro_data.pressure, + humi_data.humidity, gas_data.gas_resistance); + + sleep(1); + } + return NULL; +} + +int init_sensors(void) { + baro_fd = open("/dev/uorb/sensor_baro0", O_RDONLY | O_NONBLOCK); + if (baro_fd < 0) { + printf("Failed to open barometer lowerhalf.\n"); + return -1; + } + + hum_fd = open("/dev/uorb/sensor_humi0", O_RDONLY | O_NONBLOCK); + if (hum_fd < 0) { + printf("Failed to open humidity sensor lowerhalf.\n"); + return -1; + } + + gas_fd = open("/dev/uorb/sensor_gas0", O_RDONLY | O_NONBLOCK); + if (gas_fd < 0) { + printf("Failed to open gas lowerhalf.\n"); + return -1; + } + + pfds[0].fd = baro_fd, pfds[1].fd = hum_fd, pfds[2].fd = gas_fd; + pfds[0].events = pfds[1].events = pfds[2].events = POLLIN; + + struct bme680_config_s config; + config.temp_os = BME680_OS_2X; + config.press_os = BME680_OS_16X; + config.filter_coef = BME680_FILTER_COEF3; + config.hum_os = BME680_OS_1X; + config.target_temp = 300; + config.amb_temp = 30; + config.heater_duration = 100; + config.nb_conv = 0; + + int ret = ioctl(baro_fd, SNIOC_CALIBRATE, &config); + if (ret < 0) { + perror("Failed to configure sensor"); + return -1; + } + return 0; +} + +void close_sensors_fd(void) { + close(baro_fd); + close(hum_fd); + close(gas_fd); +} \ No newline at end of file diff --git a/examples/ble/sensors.h b/examples/ble/sensors.h new file mode 100644 index 0000000000..1f1f81ba24 --- /dev/null +++ b/examples/ble/sensors.h @@ -0,0 +1,15 @@ +#include +#include +#include +#include +#include +#include + +float get_temperature(void); +float get_pressure(void); +float get_gas_resistance(void); +float get_humidity(void); + +void *monitor_sensors(void *arg); +int init_sensors(void); +void close_sensors_fd(void);