diff --git a/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_sensor_model_api.h b/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_sensor_model_api.h index 52bc082dd..b703fc500 100644 --- a/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_sensor_model_api.h +++ b/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_sensor_model_api.h @@ -345,6 +345,91 @@ esp_err_t esp_ble_mesh_sensor_client_set_state(esp_ble_mesh_client_common_param_ */ #define ESP_BLE_MESH_SENSOR_DATA_ZERO_LEN 0x7F +/** @def ESP_BLE_MESH_GET_SENSOR_DATA_FORMAT + * + * @brief Get format of the sensor data. + * + * @note Multiple sensor data may be concatenated. Make sure the _data pointer is + * updated before getting the format of the corresponding sensor data. + * + * @param _data Pointer to the start of the sensor data. + * + * @return Format of the sensor data. + */ +#define ESP_BLE_MESH_GET_SENSOR_DATA_FORMAT(_data) (((_data)[0]) & BIT_MASK(1)) + +/** @def ESP_BLE_MESH_GET_SENSOR_DATA_LENGTH + * + * @brief Get length of the sensor data. + * + * @note Multiple sensor data may be concatenated. Make sure the _data pointer is + * updated before getting the length of the corresponding sensor data. + * + * @param _data Pointer to the start of the sensor data. + * @param _fmt Format of the sensor data. + * + * @return Length (zero-based) of the sensor data. + */ +#define ESP_BLE_MESH_GET_SENSOR_DATA_LENGTH(_data, _fmt) \ + (((_fmt) == ESP_BLE_MESH_SENSOR_DATA_FORMAT_A) ? ((((_data)[0]) >> 1) & BIT_MASK(4)) : ((((_data)[0]) >> 1) & BIT_MASK(7))) + +/** @def ESP_BLE_MESH_GET_SENSOR_DATA_PROPERTY_ID + * + * @brief Get Sensor Property ID of the sensor data. + * + * @note Multiple sensor data may be concatenated. Make sure the _data pointer is + * updated before getting Sensor Property ID of the corresponding sensor data. + * + * @param _data Pointer to the start of the sensor data. + * @param _fmt Format of the sensor data. + * + * @return Sensor Property ID of the sensor data. + */ +#define ESP_BLE_MESH_GET_SENSOR_DATA_PROPERTY_ID(_data, _fmt) \ + (((_fmt) == ESP_BLE_MESH_SENSOR_DATA_FORMAT_A) ? ((((_data)[1]) << 3) | (((_data)[0]) >> 5)) : ((((_data)[2]) << 8) | ((_data)[1]))) + +/** @def ESP_BLE_MESH_SENSOR_DATA_FORMAT_A_MPID + * + * @brief Generate a MPID value for sensor data with Format A. + * + * @note 1. The Format field is 0b0 and indicates that Format A is used. + * 2. The Length field is a 1-based uint4 value (valid range 0x0–0xF, + * representing range of 1–16). + * 3. The Property ID is an 11-bit bit field representing 11 LSb of a Property ID. + * 4. This format may be used for Property Values that are not longer than 16 + * octets and for Property IDs less than 0x0800. + * + * @param _len Length of Sensor Raw value. + * @param _id Sensor Property ID. + * + * @return 2-octet MPID value for sensor data with Format A. + * + */ +#define ESP_BLE_MESH_SENSOR_DATA_FORMAT_A_MPID(_len, _id) \ + ((((_id) & BIT_MASK(11)) << 5) | (((_len) & BIT_MASK(4)) << 1) | ESP_BLE_MESH_SENSOR_DATA_FORMAT_A) + +/** @def ESP_BLE_MESH_SENSOR_DATA_FORMAT_B_MPID + * + * @brief Generate a MPID value for sensor data with Format B. + * + * @note 1. The Format field is 0b1 and indicates Format B is used. + * 2. The Length field is a 1-based uint7 value (valid range 0x0–0x7F, representing + * range of 1–127). The value 0x7F represents a length of zero. + * 3. The Property ID is a 16-bit bit field representing a Property ID. + * 4. This format may be used for Property Values not longer than 128 octets and for + * any Property IDs. Property values longer than 128 octets are not supported by + * the Sensor Status message. + * 5. Exclude the generated 1-octet value, the 2-octet Sensor Property ID + * + * @param _len Length of Sensor Raw value. + * @param _id Sensor Property ID. + * + * @return 3-octet MPID value for sensor data with Format B. + * + */ +#define ESP_BLE_MESH_SENSOR_DATA_FORMAT_B_MPID(_len, _id) \ + (((_id) << 8) | (((_len) & BIT_MASK(7)) << 1) | ESP_BLE_MESH_SENSOR_DATA_FORMAT_B) + /** This enum value is value of Sensor Sampling Function */ enum esp_ble_mesh_sensor_sample_func { ESP_BLE_MESH_SAMPLE_FUNC_UNSPECIFIED, diff --git a/components/bt/esp_ble_mesh/mesh_models/server/sensor_server.c b/components/bt/esp_ble_mesh/mesh_models/server/sensor_server.c index d4b0a432a..54aac2874 100644 --- a/components/bt/esp_ble_mesh/mesh_models/server/sensor_server.c +++ b/components/bt/esp_ble_mesh/mesh_models/server/sensor_server.c @@ -290,7 +290,8 @@ static void send_sensor_settings_status(struct bt_mesh_model *model, for (i = 0; i < srv->state_count; i++) { state = &srv->states[i]; if (state->sensor_property_id != INVALID_SENSOR_PROPERTY_ID && - state->sensor_property_id == prop_id) { + state->sensor_property_id == prop_id && + state->setting_count && state->settings) { for (j = 0; j < state->setting_count; j++) { item = &state->settings[j]; if (item->property_id != INVALID_SENSOR_SETTING_PROPERTY_ID) { @@ -325,7 +326,8 @@ static struct sensor_setting *find_sensor_setting(struct bt_mesh_model *model, for (i = 0; i < srv->state_count; i++) { state = &srv->states[i]; if (state->sensor_property_id != INVALID_SENSOR_PROPERTY_ID && - state->sensor_property_id == prop_id) { + state->sensor_property_id == prop_id && + state->setting_count && state->settings) { for (j = 0; j < state->setting_count; j++) { item = &state->settings[j]; if (item->property_id != INVALID_SENSOR_SETTING_PROPERTY_ID && @@ -422,14 +424,22 @@ static void send_sensor_column_status(struct bt_mesh_model *model, state = &srv->states[i]; if (state->sensor_property_id != INVALID_SENSOR_PROPERTY_ID && state->sensor_property_id == prop_id) { - length = SENSOR_PROPERTY_ID_LEN + state->series_column.raw_value_x->len; + length = SENSOR_PROPERTY_ID_LEN; + if (state->series_column.raw_value_x) { + length += state->series_column.raw_value_x->len; + } /** * TODO: column width & raw value y in Sensor Column Status are optional, * here we need to add some conditions to decide whether put these two * in the status message. */ if (optional) { - length += state->series_column.column_width->len + state->series_column.raw_value_y->len; + if (state->series_column.column_width) { + length += state->series_column.column_width->len; + } + if (state->series_column.raw_value_y) { + length += state->series_column.raw_value_y->len; + } } break; } @@ -453,13 +463,19 @@ static void send_sensor_column_status(struct bt_mesh_model *model, bt_mesh_model_msg_init(msg, BLE_MESH_MODEL_OP_SENSOR_COLUMN_STATUS); net_buf_simple_add_le16(msg, prop_id); if (i != srv->state_count) { - net_buf_simple_add_mem(msg, state->series_column.raw_value_x->data, - state->series_column.raw_value_x->len); + if (state->series_column.raw_value_x) { + net_buf_simple_add_mem(msg, state->series_column.raw_value_x->data, + state->series_column.raw_value_x->len); + } if (optional) { - net_buf_simple_add_mem(msg, state->series_column.column_width->data, - state->series_column.column_width->len); - net_buf_simple_add_mem(msg, state->series_column.raw_value_y->data, - state->series_column.raw_value_y->len); + if (state->series_column.column_width) { + net_buf_simple_add_mem(msg, state->series_column.column_width->data, + state->series_column.column_width->len); + } + if (state->series_column.raw_value_y) { + net_buf_simple_add_mem(msg, state->series_column.raw_value_y->data, + state->series_column.raw_value_y->len); + } } } @@ -489,9 +505,15 @@ static void send_sensor_series_status(struct bt_mesh_model *model, * decide whether put these three in the status message. */ if (optional) { - length += state->series_column.raw_value_x->len + - state->series_column.column_width->len + - state->series_column.raw_value_y->len; + if (state->series_column.raw_value_x) { + length += state->series_column.raw_value_x->len; + } + if (state->series_column.column_width) { + length += state->series_column.column_width->len; + } + if (state->series_column.raw_value_y) { + length += state->series_column.raw_value_y->len; + } } break; } @@ -517,12 +539,18 @@ static void send_sensor_series_status(struct bt_mesh_model *model, net_buf_simple_add_le16(msg, prop_id); if (i != srv->state_count) { if (optional) { - net_buf_simple_add_mem(msg, state->series_column.raw_value_x->data, - state->series_column.raw_value_x->len); - net_buf_simple_add_mem(msg, state->series_column.column_width->data, - state->series_column.column_width->len); - net_buf_simple_add_mem(msg, state->series_column.raw_value_y->data, - state->series_column.raw_value_y->len); + if (state->series_column.raw_value_x) { + net_buf_simple_add_mem(msg, state->series_column.raw_value_x->data, + state->series_column.raw_value_x->len); + } + if (state->series_column.column_width) { + net_buf_simple_add_mem(msg, state->series_column.column_width->data, + state->series_column.column_width->len); + } + if (state->series_column.raw_value_y) { + net_buf_simple_add_mem(msg, state->series_column.raw_value_y->data, + state->series_column.raw_value_y->len); + } } } @@ -972,17 +1000,29 @@ static int check_sensor_server_init(struct bt_mesh_sensor_state *state_start, BT_ERR("%s, Invalid Sensor Property ID 0x%04x", __func__, state->sensor_property_id); return -EINVAL; } - if (state->setting_count == 0U || state->settings == NULL) { - BT_ERR("%s, Invalid Sensor Setting state", __func__); - return -EINVAL; - } - for (j = 0; j < state->setting_count; j++) { - setting = &state->settings[j]; - if (setting->property_id == INVALID_SENSOR_SETTING_PROPERTY_ID || setting->raw == NULL) { - BT_ERR("%s, Invalid Sensor Setting state internal parameter", __func__); + /* Check if the same Sensor Property ID exists */ + for (int k = i + 1; k < state_count; k++) { + if (state->sensor_property_id == state_start[k].sensor_property_id) { + BT_ERR("%s, Same Sensor Property ID 0x%04x exists", __func__, state->sensor_property_id); return -EINVAL; } } + if (state->setting_count && state->settings) { + for (j = 0; j < state->setting_count; j++) { + setting = &state->settings[j]; + if (setting->property_id == INVALID_SENSOR_SETTING_PROPERTY_ID || setting->raw == NULL) { + BT_ERR("%s, Invalid Sensor Setting state", __func__); + return -EINVAL; + } + /* Check if the same Sensor Setting Property ID exists */ + for (int k = j + 1; k < state->setting_count; k++) { + if (setting->property_id == state->settings[k].property_id) { + BT_ERR("%s, Same Sensor Setting Property ID 0x%04x exists", __func__, setting->property_id); + return -EINVAL; + } + } + } + } if (state->cadence) { if (state->cadence->trigger_delta_down == NULL || state->cadence->trigger_delta_up == NULL || @@ -996,12 +1036,6 @@ static int check_sensor_server_init(struct bt_mesh_sensor_state *state_start, BT_ERR("%s, Invalid Sensor Data state", __func__); return -EINVAL; } - if (state->series_column.raw_value_x == NULL || - state->series_column.column_width == NULL || - state->series_column.raw_value_y == NULL) { - BT_ERR("%s, Invalid Sensor Series column state", __func__); - return -EINVAL; - } } return 0; @@ -1018,11 +1052,10 @@ static int sensor_server_init(struct bt_mesh_model *model) case BLE_MESH_MODEL_ID_SENSOR_SRV: { struct bt_mesh_sensor_srv *srv = model->user_data; if (srv->state_count == 0U || srv->states == NULL) { - BT_ERR("%s, Invalid Sensor state parameter, model_id 0x%04x", __func__, model->id); + BT_ERR("%s, Invalid Sensor state, model_id 0x%04x", __func__, model->id); return -EINVAL; } if (check_sensor_server_init(srv->states, srv->state_count)) { - BT_ERR("%s, Invalid Sensor Server init value", __func__); return -EINVAL; } srv->model = model; @@ -1031,11 +1064,10 @@ static int sensor_server_init(struct bt_mesh_model *model) case BLE_MESH_MODEL_ID_SENSOR_SETUP_SRV: { struct bt_mesh_sensor_setup_srv *srv = model->user_data; if (srv->state_count == 0U || srv->states == NULL) { - BT_ERR("%s, Invalid parameter, model_id 0x%04x", __func__, model->id); + BT_ERR("%s, Invalid Sensor state, model_id 0x%04x", __func__, model->id); return -EINVAL; } if (check_sensor_server_init(srv->states, srv->state_count)) { - BT_ERR("%s, Invalid Sensor Setup Server init value", __func__); return -EINVAL; } srv->model = model; diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/CMakeLists.txt b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/CMakeLists.txt new file mode 100644 index 000000000..e8789f74e --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(sensor_client) diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/Makefile b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/Makefile new file mode 100644 index 000000000..cb51153ee --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := sensor_client + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/README.md b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/README.md new file mode 100644 index 000000000..7e7a5b5cf --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/README.md @@ -0,0 +1,89 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +ESP BLE Mesh Sensor Client Example +================================== + +This example demonstrates how to create a sensor client model in Provisioner, and the [sensor server example](../sensor_server) demonstrates how to create a sensor server model and a sensor setup server model in an unprovisioned device. + +### 1. Introduction + +Sensor Server model is model which is used to expose a series of sensor states. When Sensor Server model is present on an element, the Sensor Setup Server model shall also be present as an extension of the Sensor Server model. +Sensor Client model is used to consume the state values exposed by the Sensor Server/Setup Server models via Sensor messages. + +Sensor state is a composite state which contains the following parts: +* Sensor Descriptor state +* Sensor Setting state +* Sensor Cadence state +* Sensor Data state +* Sensor Series Column state + +#### Sensor Descriptor state + +Sensor Descriptor state represents the attributes describing the sensor data. This state does not change throughout the lifetime of an element. + +#### Sensor Setting state + +Sensor Setting state controls parameters of a sensor. For example, an occupancy sensor may have a “sensitivity” setting that controls the sensitivity of the sensor. Sensitivity may be adjusted to prevent small animals from triggering the sensor. + +#### Sensor Cadence state + +Sensor Cadence state controls the cadence of sensor reports. It allows a sensor to be configured to send measured values using Sensor Status messages at a different cadence for a range of measured values. It also allows a sensor to be configured to send measured values when the value changes up or down by more than a configured delta value. + +#### Sensor Data state + +Sensor Data state is a sequence of one or more pairs of Sensor Property ID and Raw Value fields, with each Raw Value field size and representation defined by the characteristics referenced by the Sensor Property ID. + +#### Sensor Series Column state + +Values measured by sensors may be organized as arrays (and represented as series of columns, such as histograms). It contains Sensor Raw Value X, Sensor Column Width and Sensor Raw Value Y. + +> Note: For the supported sensor types, please refer to the [Mesh Device Properties Spec](https://www.bluetooth.com/specifications/mesh-specifications/mesh-properties/). + +### 2. Example Description + +#### Sensor Client example +In the [Sensor Client example](./), the mesh device is a Provisioner with a sensor client model. Once the device which runs the sensor server example is provisioned and configured successfully by the Provisioner, users can press the button to send sensor messages to get the corresponding state (i.e. sensor descriptor, sensor data, etc.) values. + +#### Sensor Server example + +In the [Sensor Server example](../sensor_server), the mesh device is an unprovisioned device with a sensor server model and a sensor setup server model. + +The sensor server model supports two instances of Sensor states, the first one (Sensor Property ID 0x0056) represents the "Present Indoor Ambient Temperature", and the other (Sensor Property ID 0x005B) represents the "Present Outdoor Ambient Temperature". The characteristic of the two properties is "Temperature 8", which is used to represent a measure of temperature with a unit of 0.5 degree Celsius. The minimum value of the characteristic is -64.0, and the maximum value is 63.5. + +Currently only the Sensor Descriptor state, Sensor Data state are supported by the Sensor states. The Sensor Property ID, Sensor Descriptor state, format and length of the corresponding Sensor Data state are all pre-initialized. + +### 3. Procedures +* Device A runs the sensor client example, and device B runs the sensor server example; +* Working as a Provisioner, once the unprovisioned device beacon from device B is received by device A, device A will provision and configure device B into a node; +* After the provisioning and configuration procedure is completed successfully, users can press the "Boot" button on device A to get the sensor state values of device B; +* Based on the press count, different sensor messages will be sent to device B. Sensor Descriptor Get will be sent when the button is pressed for the first time, at the second time Sensor Cadence Get will be sent, and the following messages are: Sensor Settings Get, Sensor Get and Sensor Series Get; +* If continuing to press the button, the same sensor messages with the above sequence will be sent to device B. + +### 4. Relationship + +This section is about the relationship between sensor models and messages, which gives a list of messages that can be sent or published by a specific sensor model. + +* Sensor Client model + * Sensor Descriptor Get + * Sensor Cadence Get + * Sensor Cadence Set + * Sensor Cadence Set Unacknowledged + * Sensor Settings Get + * Sensor Setting Get + * Sensor Setting Set + * Sensor Setting Set Unacknowledged + * Sensor Get + * Sensor Column Get + * Sensor Series Get + +* Sensor Server model + * Sensor Descriptor Status + * Sensor Status + * Sensor Column Status + * Sensor Series Status + +* Sensor Setup Server model + * Sensor Cadence Status + * Sensor Settings Status + * Sensor Setting Status diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/CMakeLists.txt b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/CMakeLists.txt new file mode 100644 index 000000000..6c385ee21 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/CMakeLists.txt @@ -0,0 +1,7 @@ + +set(COMPONENT_SRCS "button.c" + "button_obj.cpp") + +set(COMPONENT_ADD_INCLUDEDIRS ". include") + +register_component() diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/Kconfig b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/Kconfig new file mode 100644 index 000000000..308410a69 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/Kconfig @@ -0,0 +1,21 @@ +menu "Button" + + choice BUTTON_TIMER_IMPLEMENT + bool "Button Timer Mode" + default BUTTON_USE_ESP_TIMER + help + Choose a implementation of timer for button instance. + + config BUTTON_USE_RTOS_TIMER + bool "Use FreeRTOS Timer" + + config BUTTON_USE_ESP_TIMER + bool "Use ESP Timer" + endchoice + + config BUTTON_IO_GLITCH_FILTER_TIME_MS + int "IO glitch filter timer ms (10~100)" + range 10 100 + default 50 + +endmenu \ No newline at end of file diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/README.md b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/README.md new file mode 100644 index 000000000..0c55e2d29 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/README.md @@ -0,0 +1,46 @@ +# Component: Button + +* This component defines a button as a well encapsulated object. +* A button device is defined by: + * GPIO number on which the button is attached. + * Active level which decided by peripheral hardware. + * Trigger mode which decides whether to call serial trigger callback during pressing + * Serial threshold seconds which decides that serial trigger callback will be called after how many seconds' pressing +* A button device can provide: + * One push event callback + * One release event callback + * One short-time tap event callback + * One serial trigger event callback + * Several long-time press event callback + We can set different jitter filters for all the events. + Once any of the long press callback is triggered, the short tap event will not be triggered. + These components are based on GPIO provided by ESP-IDF and soft timer provided by FreeRTOS. + +* To use the button device, you need to: + * create a button object returned by iot_button_create(). + * Then hook different event callbacks to the button object. + * To free the object, you can call iot_button_delete to delete the button object and free the used memory. + +* Todo: Add hardware timer mode(because sometimes soft-timer callback function is limited) + +### NOTE: +> All the event callback function are implemented by FreeRTOS soft timer APIs, the callback must follow the rule: + + + +``` + Button callback functions execute in the context of the timer service task. + It is therefore essential that button callback functions never attempt to block. + For example, a button callback function must not call vTaskDelay(), vTaskDelayUntil(), or specify a non zero block time when accessing a queue or a semaphore. +``` + +> In addition: +> You can adjust the following macros within FreeRTOS to adjust the stack depth/queue length/task priority of the timer service. + + +``` +#define configUSE_TIMERS //enable soft-timer +#define configTIMER_TASK_PRIORITY // priority of the timers service task +#define configQueue_LENGTH // length of timer command queue +#define configTIMER_TASK_STACK_DEPTH // stack depth of the soft-timer +``` \ No newline at end of file diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/button.c b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/button.c new file mode 100644 index 000000000..ac391a5c3 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/button.c @@ -0,0 +1,434 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed 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 +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/timers.h" +#include "esp_log.h" +#include "driver/gpio.h" +#include "iot_button.h" +#include "esp_timer.h" + +#define USE_ESP_TIMER CONFIG_BUTTON_USE_ESP_TIMER +#if USE_ESP_TIMER +#define STOP_TIMER(tmr) esp_timer_stop(tmr) +#define DELETE_TIMER(tmr) esp_timer_delete(tmr) +#else +#define STOP_TIMER(tmr) xTimerStop(tmr, portMAX_DELAY) +#define DELETE_TIMER(tmr) xTimerDelete(tmr, portMAX_DELAY); +#endif + +#define IOT_CHECK(tag, a, ret) if(!(a)) { \ + ESP_LOGE(tag,"%s:%d (%s)", __FILE__, __LINE__, __FUNCTION__); \ + return (ret); \ + } +#define ERR_ASSERT(tag, param) IOT_CHECK(tag, (param) == ESP_OK, ESP_FAIL) +#define POINT_ASSERT(tag, param, ret) IOT_CHECK(tag, (param) != NULL, (ret)) + +typedef enum { + BUTTON_STATE_IDLE = 0, + BUTTON_STATE_PUSH, + BUTTON_STATE_PRESSED, +} button_status_t; + +typedef struct button_dev button_dev_t; +typedef struct btn_cb button_cb_t; + +struct btn_cb{ + TickType_t interval; + button_cb cb; + void* arg; + #if !USE_ESP_TIMER + TimerHandle_t tmr; + #else + esp_timer_handle_t tmr; + #endif + button_dev_t *pbtn; + button_cb_t *next_cb; +}; + +struct button_dev{ + uint8_t io_num; + uint8_t active_level; + uint32_t serial_thres_sec; + button_status_t state; + button_cb_t tap_short_cb; + button_cb_t tap_psh_cb; + button_cb_t tap_rls_cb; + button_cb_t press_serial_cb; + button_cb_t* cb_head; +}; + +#define BUTTON_GLITCH_FILTER_TIME_MS CONFIG_BUTTON_IO_GLITCH_FILTER_TIME_MS +static const char* TAG = "button"; + +// static void button_press_cb(xTimerHandle tmr) +static void button_press_cb(void* tmr) +{ + #if !USE_ESP_TIMER + button_cb_t* btn_cb = (button_cb_t*) pvTimerGetTimerID(tmr); + #else + button_cb_t* btn_cb = (button_cb_t*)(tmr); + #endif + + button_dev_t* btn = btn_cb->pbtn; + // low, then restart + if (btn->active_level == gpio_get_level(btn->io_num)) { + btn->state = BUTTON_STATE_PRESSED; + if (btn_cb->cb) { + btn_cb->cb(btn_cb->arg); + } + } +} + +// static void button_tap_psh_cb(xTimerHandle tmr) +static void button_tap_psh_cb(void* tmr) +{ + #if !USE_ESP_TIMER + button_cb_t* btn_cb = (button_cb_t*) pvTimerGetTimerID(tmr); + #else + button_cb_t* btn_cb = (button_cb_t*)(tmr); + #endif + + button_dev_t* btn = btn_cb->pbtn; + STOP_TIMER(btn->tap_rls_cb.tmr); + + int lv = gpio_get_level(btn->io_num); + + if (btn->active_level == lv) { + // high, then key is up + btn->state = BUTTON_STATE_PUSH; + if (btn->press_serial_cb.tmr) { + #if !USE_ESP_TIMER + xTimerChangePeriod(btn->press_serial_cb.tmr, btn->serial_thres_sec*1000 / portTICK_PERIOD_MS, portMAX_DELAY); + xTimerReset(btn->press_serial_cb.tmr, portMAX_DELAY); + #else + esp_timer_stop(btn->press_serial_cb.tmr); + esp_timer_start_once(btn->press_serial_cb.tmr, btn->serial_thres_sec * 1000 * 1000); + #endif + + } + if (btn->tap_psh_cb.cb) { + btn->tap_psh_cb.cb(btn->tap_psh_cb.arg); + } + } else { + // 50ms, check if this is a real key up + if (btn->tap_rls_cb.tmr) { + STOP_TIMER(btn->tap_rls_cb.tmr); + #if !USE_ESP_TIMER + xTimerReset(btn->tap_rls_cb.tmr, portMAX_DELAY); + #else + esp_timer_start_once(btn->tap_rls_cb.tmr, btn->tap_rls_cb.interval * portTICK_PERIOD_MS * 1000); + #endif + } + } +} + +static void button_tap_rls_cb(void* tmr) +{ + #if !USE_ESP_TIMER + button_cb_t* btn_cb = (button_cb_t*) pvTimerGetTimerID(tmr); + #else + button_cb_t* btn_cb = (button_cb_t*)(tmr); + #endif + button_dev_t* btn = btn_cb->pbtn; + STOP_TIMER(btn->tap_rls_cb.tmr); + + if (btn->active_level == gpio_get_level(btn->io_num)) { + + } else { + // high, then key is up + button_cb_t *pcb = btn->cb_head; + while (pcb != NULL) { + if (pcb->tmr != NULL) { + STOP_TIMER(pcb->tmr); + } + pcb = pcb->next_cb; + } + if (btn->press_serial_cb.tmr && btn->press_serial_cb.tmr != NULL) { + STOP_TIMER(btn->press_serial_cb.tmr); + } + if (btn->tap_short_cb.cb && btn->state == BUTTON_STATE_PUSH) { + btn->tap_short_cb.cb(btn->tap_short_cb.arg); + } + if(btn->tap_rls_cb.cb && btn->state != BUTTON_STATE_IDLE) { + btn->tap_rls_cb.cb(btn->tap_rls_cb.arg); + } + btn->state = BUTTON_STATE_IDLE; + } +} + +static void button_press_serial_cb(void* tmr) +{ + #if !USE_ESP_TIMER + button_dev_t* btn = (button_dev_t*) pvTimerGetTimerID(tmr); + #else + button_dev_t* btn = (button_dev_t*)(tmr); + #endif + + if (btn->press_serial_cb.cb) { + btn->press_serial_cb.cb(btn->press_serial_cb.arg); + } + #if !USE_ESP_TIMER + xTimerChangePeriod(btn->press_serial_cb.tmr, btn->press_serial_cb.interval, portMAX_DELAY); + xTimerReset(btn->press_serial_cb.tmr, portMAX_DELAY); + #else + esp_timer_stop(btn->press_serial_cb.tmr); + esp_timer_start_once(btn->press_serial_cb.tmr, btn->press_serial_cb.interval * portTICK_PERIOD_MS * 1000); + #endif +} + +static void button_gpio_isr_handler(void* arg) +{ + button_dev_t* btn = (button_dev_t*) arg; + portBASE_TYPE HPTaskAwoken = pdFALSE; + int level = gpio_get_level(btn->io_num); + if (level == btn->active_level) { + if (btn->tap_psh_cb.tmr) { + #if !USE_ESP_TIMER + xTimerStopFromISR(btn->tap_psh_cb.tmr, &HPTaskAwoken); + xTimerResetFromISR(btn->tap_psh_cb.tmr, &HPTaskAwoken); + #else + esp_timer_stop(btn->tap_psh_cb.tmr); + esp_timer_start_once(btn->tap_psh_cb.tmr, btn->tap_psh_cb.interval * portTICK_PERIOD_MS * 1000); + #endif + } + + button_cb_t *pcb = btn->cb_head; + while (pcb != NULL) { + if (pcb->tmr != NULL) { + #if !USE_ESP_TIMER + xTimerStopFromISR(pcb->tmr, &HPTaskAwoken); + xTimerResetFromISR(pcb->tmr, &HPTaskAwoken); + #else + esp_timer_stop(pcb->tmr); + esp_timer_start_once(pcb->tmr, pcb->interval * portTICK_PERIOD_MS * 1000); + #endif + } + pcb = pcb->next_cb; + } + } else { + // 50ms, check if this is a real key up + if (btn->tap_rls_cb.tmr) { + #if !USE_ESP_TIMER + xTimerStopFromISR(btn->tap_rls_cb.tmr, &HPTaskAwoken); + xTimerResetFromISR(btn->tap_rls_cb.tmr, &HPTaskAwoken); + #else + esp_timer_stop(btn->tap_rls_cb.tmr); + esp_timer_start_once(btn->tap_rls_cb.tmr, btn->tap_rls_cb.interval * portTICK_PERIOD_MS * 1000); + #endif + } + } + if(HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); + } +} + +#if !USE_ESP_TIMER +static void button_free_tmr(xTimerHandle* tmr) +#else +static void button_free_tmr(esp_timer_handle_t *tmr) +#endif +{ + if (tmr && *tmr) { + STOP_TIMER(*tmr); + DELETE_TIMER(*tmr); + *tmr = NULL; + } +} + +esp_err_t iot_button_delete(button_handle_t btn_handle) +{ + POINT_ASSERT(TAG, btn_handle, ESP_ERR_INVALID_ARG); + button_dev_t* btn = (button_dev_t*) btn_handle; + gpio_set_intr_type(btn->io_num, GPIO_INTR_DISABLE); + gpio_isr_handler_remove(btn->io_num); + + button_free_tmr(&btn->tap_rls_cb.tmr); + button_free_tmr(&btn->tap_psh_cb.tmr); + button_free_tmr(&btn->tap_short_cb.tmr); + button_free_tmr(&btn->press_serial_cb.tmr); + + button_cb_t *pcb = btn->cb_head; + while (pcb != NULL) { + button_cb_t *cb_next = pcb->next_cb; + button_free_tmr(&pcb->tmr); + free(pcb); + pcb = cb_next; + } + free(btn); + return ESP_OK; +} + +button_handle_t iot_button_create(gpio_num_t gpio_num, button_active_t active_level) +{ + #if USE_ESP_TIMER + ets_printf("use esp timer !!!\n"); + esp_timer_init(); + #endif + + IOT_CHECK(TAG, gpio_num < GPIO_NUM_MAX, NULL); + button_dev_t* btn = (button_dev_t*) calloc(1, sizeof(button_dev_t)); + POINT_ASSERT(TAG, btn, NULL); + btn->active_level = active_level; + btn->io_num = gpio_num; + btn->state = BUTTON_STATE_IDLE; + btn->tap_rls_cb.arg = NULL; + btn->tap_rls_cb.cb = NULL; + btn->tap_rls_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_PERIOD_MS; + btn->tap_rls_cb.pbtn = btn; + #if !USE_ESP_TIMER + btn->tap_rls_cb.tmr = xTimerCreate("btn_rls_tmr", btn->tap_rls_cb.interval, pdFALSE, + &btn->tap_rls_cb, button_tap_rls_cb); + #else + esp_timer_create_args_t tmr_param_rls; + tmr_param_rls.arg = &btn->tap_rls_cb; + tmr_param_rls.callback = button_tap_rls_cb; + tmr_param_rls.dispatch_method = ESP_TIMER_TASK; + tmr_param_rls.name = "btn_rls_tmr"; + esp_timer_create(&tmr_param_rls, &btn->tap_rls_cb.tmr); + #endif + + btn->tap_psh_cb.arg = NULL; + btn->tap_psh_cb.cb = NULL; + btn->tap_psh_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_PERIOD_MS; + btn->tap_psh_cb.pbtn = btn; + #if !USE_ESP_TIMER + btn->tap_psh_cb.tmr = xTimerCreate("btn_psh_tmr", btn->tap_psh_cb.interval, pdFALSE, + &btn->tap_psh_cb, button_tap_psh_cb); + #else + esp_timer_create_args_t tmr_param_psh; + tmr_param_psh.arg = &btn->tap_psh_cb; + tmr_param_psh.callback = button_tap_psh_cb; + tmr_param_psh.dispatch_method = ESP_TIMER_TASK; + tmr_param_psh.name = "btn_psh_tmr"; + esp_timer_create(&tmr_param_psh, &btn->tap_psh_cb.tmr); + #endif + gpio_install_isr_service(0); + gpio_config_t gpio_conf; + gpio_conf.intr_type = GPIO_INTR_ANYEDGE; + gpio_conf.mode = GPIO_MODE_INPUT; + gpio_conf.pin_bit_mask = (1ULL << gpio_num); + gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&gpio_conf); + gpio_isr_handler_add(gpio_num, button_gpio_isr_handler, btn); + return (button_handle_t) btn; +} + +esp_err_t iot_button_rm_cb(button_handle_t btn_handle, button_cb_type_t type) +{ + button_dev_t* btn = (button_dev_t*) btn_handle; + button_cb_t* btn_cb = NULL; + if (type == BUTTON_CB_PUSH) { + btn_cb = &btn->tap_psh_cb; + } else if (type == BUTTON_CB_RELEASE) { + btn_cb = &btn->tap_rls_cb; + } else if (type == BUTTON_CB_TAP) { + btn_cb = &btn->tap_short_cb; + } else if (type == BUTTON_CB_SERIAL) { + btn_cb = &btn->press_serial_cb; + } + btn_cb->cb = NULL; + btn_cb->arg = NULL; + btn_cb->pbtn = btn; + button_free_tmr(&btn_cb->tmr); + return ESP_OK; +} + +esp_err_t iot_button_set_serial_cb(button_handle_t btn_handle, uint32_t start_after_sec, TickType_t interval_tick, button_cb cb, void* arg) +{ + button_dev_t* btn = (button_dev_t*) btn_handle; + btn->serial_thres_sec = start_after_sec; + + if (btn->press_serial_cb.tmr == NULL) { + #if !USE_ESP_TIMER + btn->press_serial_cb.tmr = xTimerCreate("btn_serial_tmr", btn->serial_thres_sec*1000 / portTICK_PERIOD_MS, + pdFALSE, btn, button_press_serial_cb); + #else + esp_timer_create_args_t tmr_param_ser; + tmr_param_ser.arg = btn; + tmr_param_ser.callback = button_press_serial_cb; + tmr_param_ser.dispatch_method = ESP_TIMER_TASK; + tmr_param_ser.name = "btn_serial_tmr"; + esp_timer_create(&tmr_param_ser, &btn->press_serial_cb.tmr); + #endif + } + btn->press_serial_cb.arg = arg; + btn->press_serial_cb.cb = cb; + btn->press_serial_cb.interval = interval_tick; + btn->press_serial_cb.pbtn = btn; + #if !USE_ESP_TIMER + xTimerChangePeriod(btn->press_serial_cb.tmr, btn->serial_thres_sec*1000 / portTICK_PERIOD_MS, portMAX_DELAY); + #endif + return ESP_OK; +} + +esp_err_t iot_button_set_evt_cb(button_handle_t btn_handle, button_cb_type_t type, button_cb cb, void* arg) +{ + POINT_ASSERT(TAG, btn_handle, ESP_ERR_INVALID_ARG); + button_dev_t* btn = (button_dev_t*) btn_handle; + if (type == BUTTON_CB_PUSH) { + btn->tap_psh_cb.arg = arg; + btn->tap_psh_cb.cb = cb; + btn->tap_psh_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_RATE_MS; + btn->tap_psh_cb.pbtn = btn; + #if !USE_ESP_TIMER + xTimerChangePeriod(btn->tap_psh_cb.tmr, btn->tap_psh_cb.interval, portMAX_DELAY); + #endif + } else if (type == BUTTON_CB_RELEASE) { + btn->tap_rls_cb.arg = arg; + btn->tap_rls_cb.cb = cb; + btn->tap_rls_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_RATE_MS; + btn->tap_rls_cb.pbtn = btn; + #if !USE_ESP_TIMER + xTimerChangePeriod(btn->tap_rls_cb.tmr, btn->tap_psh_cb.interval, portMAX_DELAY); + #endif + } else if (type == BUTTON_CB_TAP) { + btn->tap_short_cb.arg = arg; + btn->tap_short_cb.cb = cb; + btn->tap_short_cb.interval = BUTTON_GLITCH_FILTER_TIME_MS / portTICK_RATE_MS; + btn->tap_short_cb.pbtn = btn; + } else if (type == BUTTON_CB_SERIAL) { + iot_button_set_serial_cb(btn_handle, 1, 1000 / portTICK_RATE_MS, cb, arg); + } + return ESP_OK; +} + +esp_err_t iot_button_add_custom_cb(button_handle_t btn_handle, uint32_t press_sec, button_cb cb, void* arg) +{ + POINT_ASSERT(TAG, btn_handle, ESP_ERR_INVALID_ARG); + IOT_CHECK(TAG, press_sec != 0, ESP_ERR_INVALID_ARG); + button_dev_t* btn = (button_dev_t*) btn_handle; + button_cb_t* cb_new = (button_cb_t*) calloc(1, sizeof(button_cb_t)); + POINT_ASSERT(TAG, cb_new, ESP_FAIL); + cb_new->arg = arg; + cb_new->cb = cb; + cb_new->interval = press_sec * 1000 / portTICK_PERIOD_MS; + cb_new->pbtn = btn; + #if !USE_ESP_TIMER + cb_new->tmr = xTimerCreate("btn_press_tmr", cb_new->interval, pdFALSE, cb_new, button_press_cb); + #else + esp_timer_create_args_t tmr_param_cus; + tmr_param_cus.arg = cb_new; + tmr_param_cus.callback = button_press_cb; + tmr_param_cus.dispatch_method = ESP_TIMER_TASK; + tmr_param_cus.name = "btn_press_custom_tmr"; + esp_timer_create(&tmr_param_cus, &cb_new->tmr); + #endif + cb_new->next_cb = btn->cb_head; + btn->cb_head = cb_new; + return ESP_OK; +} diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/button_obj.cpp b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/button_obj.cpp new file mode 100644 index 000000000..bf49238e5 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/button_obj.cpp @@ -0,0 +1,48 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed 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 "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "iot_button.h" + +CButton::CButton(gpio_num_t gpio_num, button_active_t active_level) +{ + m_btn_handle = iot_button_create(gpio_num, active_level); +} + +CButton::~CButton() +{ + iot_button_delete(m_btn_handle); + m_btn_handle = NULL; +} + +esp_err_t CButton::set_evt_cb(button_cb_type_t type, button_cb cb, void* arg) +{ + return iot_button_set_evt_cb(m_btn_handle, type, cb, arg); +} + +esp_err_t CButton::set_serial_cb(button_cb cb, void* arg, TickType_t interval_tick, uint32_t start_after_sec) +{ + return iot_button_set_serial_cb(m_btn_handle, start_after_sec, interval_tick, cb, arg); +} + +esp_err_t CButton::add_custom_cb(uint32_t press_sec, button_cb cb, void* arg) +{ + return iot_button_add_custom_cb(m_btn_handle, press_sec, cb, arg); +} + +esp_err_t CButton::rm_cb(button_cb_type_t type) +{ + return iot_button_rm_cb(m_btn_handle, type); +} diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/component.mk b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/component.mk new file mode 100644 index 000000000..a98f634ea --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/include/iot_button.h b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/include/iot_button.h new file mode 100644 index 000000000..0a8564d68 --- /dev/null +++ b/examples/bluetooth/esp_ble_mesh/ble_mesh_sensor_model/sensor_client/components/button/include/iot_button.h @@ -0,0 +1,231 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed 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. +#ifndef _IOT_BUTTON_H_ +#define _IOT_BUTTON_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "driver/gpio.h" +#include "freertos/portmacro.h" + +typedef void (* button_cb)(void*); +typedef void* button_handle_t; + +typedef enum { + BUTTON_ACTIVE_HIGH = 1, /*!