diff --git a/examples/.build-rules.yml b/examples/.build-rules.yml index 3d9256a68..c9935aa0e 100644 --- a/examples/.build-rules.yml +++ b/examples/.build-rules.yml @@ -128,3 +128,9 @@ examples/bridge_apps/bridge_cli: - if: IDF_TARGET in ["esp32", "esp32c3"] temporary: true reason: the other targets are not tested yet + +examples/sensors: + enable: + - if: IDF_TARGET in ["esp32c3"] + temporary: true + reason: the other targets are not tested yet diff --git a/examples/sensors/CMakeLists.txt b/examples/sensors/CMakeLists.txt new file mode 100644 index 000000000..db8fe6476 --- /dev/null +++ b/examples/sensors/CMakeLists.txt @@ -0,0 +1,30 @@ +# 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) + +if(NOT DEFINED ENV{ESP_MATTER_PATH}) + message(FATAL_ERROR "Please set ESP_MATTER_PATH to the path of esp-matter repo") +endif(NOT DEFINED ENV{ESP_MATTER_PATH}) + +set(PROJECT_VER "1.0") +set(PROJECT_VER_NUMBER 1) + +set(ESP_MATTER_PATH $ENV{ESP_MATTER_PATH}) +set(MATTER_SDK_PATH ${ESP_MATTER_PATH}/connectedhomeip/connectedhomeip) + +# This should be done before using the IDF_TARGET variable. +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +set(EXTRA_COMPONENT_DIRS + "${ESP_MATTER_PATH}/examples/common" + "${MATTER_SDK_PATH}/config/esp32/components" + "${ESP_MATTER_PATH}/components" + ${extra_components_dirs_append}) + +project(sensors) + +idf_build_set_property(CXX_COMPILE_OPTIONS "-std=gnu++17;-Os;-DCHIP_HAVE_CONFIG_H;-Wno-overloaded-virtual" APPEND) +idf_build_set_property(C_COMPILE_OPTIONS "-Os" APPEND) +# For RISCV chips, project_include.cmake sets -Wno-format, but does not clear various +# flags that depend on -Wformat +idf_build_set_property(COMPILE_OPTIONS "-Wno-format-nonliteral;-Wno-format-security" APPEND) diff --git a/examples/sensors/README.md b/examples/sensors/README.md new file mode 100644 index 000000000..a489f53d7 --- /dev/null +++ b/examples/sensors/README.md @@ -0,0 +1,53 @@ +# Matter Sensors + +This example demonstrates the integration of temperature and humidity sensors (SHTC3) +and an occupancy sensor (PIR). + +This application creates the temperature sensor, humidity sensor, and occupancy sensor +on endpoint 1, 2, and 3 respectively. + +See the [docs](https://docs.espressif.com/projects/esp-matter/en/latest/esp32/developing.html) +for more information about building and flashing the firmware. + +## Connecting the sensors + +- Connecting the SHTC3, temperature and humidity sensor + +| ESP32-C3 Pin | SHTC3 Pin | +|--------------|-----------| +| GND | GND | +| 3V3 | VCC | +| GPIO 4 | SDA | +| GPIO 5 | SCL | + +- Connecting the PIR sensor + +| ESP32-C3 Pin | PIR Pin | +|--------------|---------| +| GND | GND | +| 3V3 | VCC | +| GPIO 7 | Output | + +**_NOTE:_**: +- Above mentioned wiring connection is configured by default in the example. +- Ensure that the GPIO pins used for the sensors are correctly configured through menuconfig. +- Modify the configuration parameters as needed for your specific hardware setup. + +## Usage + +- Commission the app using Matter controller and read the attributes. + +Below, we are using chip-tool to commission and subscribe the sensor attributes. +- +``` +# Commission +chip-tool pairing ble-wifi 1 (SSID) (PASSPHRASE) 20202021 3840 + +# Start chip-tool in interactive mode +chip-tool interactive start + +# Subscribe to attributes +> temperaturemeasurement subscribe measured-value 3 10 1 1 +> relativehumiditymeasurement subscribe measured-value 3 10 1 2 +> occupancysensing subscribe occupancy 3 10 1 3 +``` diff --git a/examples/sensors/main/CMakeLists.txt b/examples/sensors/main/CMakeLists.txt new file mode 100644 index 000000000..6d7421c78 --- /dev/null +++ b/examples/sensors/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register(SRC_DIRS "." "drivers" + PRIV_INCLUDE_DIRS "." "drivers" "${ESP_MATTER_PATH}/examples/common/utils") + +set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17) +target_compile_options(${COMPONENT_LIB} PRIVATE "-DCHIP_HAVE_CONFIG_H") diff --git a/examples/sensors/main/Kconfig.projbuild b/examples/sensors/main/Kconfig.projbuild new file mode 100644 index 000000000..433caec1b --- /dev/null +++ b/examples/sensors/main/Kconfig.projbuild @@ -0,0 +1,21 @@ +menu "Example Configuration" + + config SHTC3_I2C_SDA_PIN + int "I2C SDA Pin" + default 4 + help + GPIO number for I2C master data + + config SHTC3_I2C_SCL_PIN + int "I2C SCL Pin" + default 5 + help + GPIO number for I2C master clock + + config PIR_DATA_PIN + int "PIR Data Pin" + default 7 + help + Default PIR Data Pin + +endmenu diff --git a/examples/sensors/main/app_main.cpp b/examples/sensors/main/app_main.cpp new file mode 100644 index 000000000..e82f78a61 --- /dev/null +++ b/examples/sensors/main/app_main.cpp @@ -0,0 +1,226 @@ +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// drivers implemented by this example +#include +#include + +static const char *TAG = "app_main"; + +using namespace esp_matter; +using namespace esp_matter::attribute; +using namespace esp_matter::endpoint; +using namespace chip::app::Clusters; + +// Application cluster specification, 7.18.2.11. Temperature +// represents a temperature on the Celsius scale with a resolution of 0.01°C. +// temp = (temperature in °C) x 100 +static void temp_sensor_notification(uint16_t endpoint_id, float temp, void *user_data) +{ + // schedule the attribute update so that we can report it from matter thread + chip::DeviceLayer::SystemLayer().ScheduleLambda([endpoint_id, temp]() { + attribute_t * attribute = attribute::get(endpoint_id, + TemperatureMeasurement::Id, + TemperatureMeasurement::Attributes::MeasuredValue::Id); + + esp_matter_attr_val_t val = esp_matter_invalid(NULL); + attribute::get_val(attribute, &val); + val.val.i16 = static_cast(temp * 100); + + attribute::update(endpoint_id, TemperatureMeasurement::Id, TemperatureMeasurement::Attributes::MeasuredValue::Id, &val); + }); +} + +// Application cluster specification, 2.6.4.1. MeasuredValue Attribute +// represents the humidity in percent. +// humidity = (humidity in %) x 100 +static void humidity_sensor_notification(uint16_t endpoint_id, float humidity, void *user_data) +{ + // schedule the attribute update so that we can report it from matter thread + chip::DeviceLayer::SystemLayer().ScheduleLambda([endpoint_id, humidity]() { + attribute_t * attribute = attribute::get(endpoint_id, + RelativeHumidityMeasurement::Id, + RelativeHumidityMeasurement::Attributes::MeasuredValue::Id); + + esp_matter_attr_val_t val = esp_matter_invalid(NULL); + attribute::get_val(attribute, &val); + val.val.u16 = static_cast(humidity * 100); + + attribute::update(endpoint_id, RelativeHumidityMeasurement::Id, RelativeHumidityMeasurement::Attributes::MeasuredValue::Id, &val); + }); +} + +static void occupancy_sensor_notification(uint16_t endpoint_id, bool occupancy, void *user_data) +{ + // schedule the attribute update so that we can report it from matter thread + chip::DeviceLayer::SystemLayer().ScheduleLambda([endpoint_id, occupancy]() { + attribute_t * attribute = attribute::get(endpoint_id, + OccupancySensing::Id, + OccupancySensing::Attributes::Occupancy::Id); + + esp_matter_attr_val_t val = esp_matter_invalid(NULL); + attribute::get_val(attribute, &val); + val.val.b = occupancy; + + attribute::update(endpoint_id, OccupancySensing::Id, OccupancySensing::Attributes::Occupancy::Id, &val); + }); +} + +static esp_err_t factory_reset_button_register() +{ + button_handle_t push_button; + esp_err_t err = bsp_iot_button_create(&push_button, NULL, BSP_BUTTON_NUM); + VerifyOrReturnError(err == ESP_OK, err); + return app_reset_button_register(push_button); +} + +static void open_commissioning_window_if_necessary() +{ + VerifyOrReturn(chip::Server::GetInstance().GetFabricTable().FabricCount() == 0); + + chip::CommissioningWindowManager & commissionMgr = chip::Server::GetInstance().GetCommissioningWindowManager(); + VerifyOrReturn(commissionMgr.IsCommissioningWindowOpen() == false); + + // After removing last fabric, this example does not remove the Wi-Fi credentials + // and still has IP connectivity so, only advertising on DNS-SD. + CHIP_ERROR err = commissionMgr.OpenBasicCommissioningWindow(chip::System::Clock::Seconds16(300), + chip::CommissioningWindowAdvertisement::kDnssdOnly); + if (err != CHIP_NO_ERROR) + { + ESP_LOGE(TAG, "Failed to open commissioning window, err:%" CHIP_ERROR_FORMAT, err.Format()); + } +} + +static void app_event_cb(const ChipDeviceEvent *event, intptr_t arg) +{ + switch (event->Type) { + case chip::DeviceLayer::DeviceEventType::kCommissioningComplete: + ESP_LOGI(TAG, "Commissioning complete"); + break; + + case chip::DeviceLayer::DeviceEventType::kFailSafeTimerExpired: + ESP_LOGI(TAG, "Commissioning failed, fail safe timer expired"); + break; + + case chip::DeviceLayer::DeviceEventType::kFabricRemoved: + ESP_LOGI(TAG, "Fabric removed successfully"); + open_commissioning_window_if_necessary(); + break; + + case chip::DeviceLayer::DeviceEventType::kBLEDeinitialized: + ESP_LOGI(TAG, "BLE deinitialized and memory reclaimed"); + break; + + default: + break; + } +} + +// This callback is invoked when clients interact with the Identify Cluster. +// In the callback implementation, an endpoint can identify itself. (e.g., by flashing an LED or light). +static esp_err_t app_identification_cb(identification::callback_type_t type, uint16_t endpoint_id, uint8_t effect_id, + uint8_t effect_variant, void *priv_data) +{ + ESP_LOGI(TAG, "Identification callback: type: %u, effect: %u, variant: %u", type, effect_id, effect_variant); + return ESP_OK; +} + +// This callback is called for every attribute update. The callback implementation shall +// handle the desired attributes and return an appropriate error code. If the attribute +// is not of your interest, please do not return an error code and strictly return ESP_OK. +static esp_err_t app_attribute_update_cb(attribute::callback_type_t type, uint16_t endpoint_id, uint32_t cluster_id, + uint32_t attribute_id, esp_matter_attr_val_t *val, void *priv_data) +{ + // Since this is just a sensor and we don't expect any writes on our temperature sensor, + // so, return success. + return ESP_OK; +} + +extern "C" void app_main() +{ + /* Initialize the ESP NVS layer */ + nvs_flash_init(); + + /* Initialize push button on the dev-kit to reset the device */ + esp_err_t err = factory_reset_button_register(); + ABORT_APP_ON_FAILURE(ESP_OK == err, ESP_LOGE(TAG, "Failed to initialize reset button, err:%d", err)); + + /* Create a Matter node and add the mandatory Root Node device type on endpoint 0 */ + node::config_t node_config; + node_t *node = node::create(&node_config, app_attribute_update_cb, app_identification_cb); + ABORT_APP_ON_FAILURE(node != nullptr, ESP_LOGE(TAG, "Failed to create Matter node")); + + // add temperature sensor device + temperature_sensor::config_t temp_sensor_config; + endpoint_t * temp_sensor_ep = temperature_sensor::create(node, &temp_sensor_config, ENDPOINT_FLAG_NONE, NULL); + ABORT_APP_ON_FAILURE(temp_sensor_ep != nullptr, ESP_LOGE(TAG, "Failed to create temperature_sensor endpoint")); + + // add the humidity sensor device + humidity_sensor::config_t humidity_sensor_config; + endpoint_t * humidity_sensor_ep = humidity_sensor::create(node, &humidity_sensor_config, ENDPOINT_FLAG_NONE, NULL); + ABORT_APP_ON_FAILURE(humidity_sensor_ep != nullptr, ESP_LOGE(TAG, "Failed to create humidity_sensor endpoint")); + + // initialize temperature and humidity sensor driver (shtc3) + static shtc3_sensor_config_t shtc3_config = { + .temperature = { + .cb = temp_sensor_notification, + .endpoint_id = endpoint::get_id(temp_sensor_ep), + }, + .humidity = { + .cb = humidity_sensor_notification, + .endpoint_id = endpoint::get_id(humidity_sensor_ep), + }, + }; + err = shtc3_sensor_init(&shtc3_config); + ABORT_APP_ON_FAILURE(err == ESP_OK, ESP_LOGE(TAG, "Failed to initialize temperature sensor driver")); + + // add the occupancy sensor + occupancy_sensor::config_t occupancy_sensor_config; + occupancy_sensor_config.occupancy_sensing.occupancy_sensor_type = + chip::to_underlying(OccupancySensing::OccupancySensorTypeEnum::kPir); + occupancy_sensor_config.occupancy_sensing.occupancy_sensor_type_bitmap = + chip::to_underlying(OccupancySensing::OccupancySensorTypeBitmap::kPir); + + endpoint_t * occupancy_sensor_ep = occupancy_sensor::create(node, &occupancy_sensor_config, ENDPOINT_FLAG_NONE, NULL); + ABORT_APP_ON_FAILURE(occupancy_sensor_ep != nullptr, ESP_LOGE(TAG, "Failed to create occupancy_sensor endpoint")); + + // initialize occupancy sensor driver (pir) + static pir_sensor_config_t pir_config = { + .cb = occupancy_sensor_notification, + .endpoint_id = endpoint::get_id(occupancy_sensor_ep), + }; + err = pir_sensor_init(&pir_config); + ABORT_APP_ON_FAILURE(err == ESP_OK, ESP_LOGE(TAG, "Failed to initialize occupancy sensor driver")); + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + /* Set OpenThread platform config */ + esp_openthread_platform_config_t config = { + .radio_config = ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG(), + .host_config = ESP_OPENTHREAD_DEFAULT_HOST_CONFIG(), + .port_config = ESP_OPENTHREAD_DEFAULT_PORT_CONFIG(), + }; + set_openthread_platform_config(&config); +#endif + + /* Matter start */ + err = esp_matter::start(app_event_cb); + ABORT_APP_ON_FAILURE(err == ESP_OK, ESP_LOGE(TAG, "Failed to start Matter, err:%d", err)); +} diff --git a/examples/sensors/main/app_openthread_config.h b/examples/sensors/main/app_openthread_config.h new file mode 100644 index 000000000..66cc667e9 --- /dev/null +++ b/examples/sensors/main/app_openthread_config.h @@ -0,0 +1,31 @@ +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#pragma once + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD +#include +#include "esp_openthread_types.h" + +#define ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG() \ + { \ + .radio_mode = RADIO_MODE_NATIVE, \ + } + +#define ESP_OPENTHREAD_DEFAULT_HOST_CONFIG() \ + { \ + .host_connection_mode = HOST_CONNECTION_MODE_NONE, \ + } + +#define ESP_OPENTHREAD_DEFAULT_PORT_CONFIG() \ + { \ + .storage_partition_name = "nvs", \ + .netif_queue_size = 10, \ + .task_queue_size = 10, \ + } +#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD diff --git a/examples/sensors/main/drivers/pir.cpp b/examples/sensors/main/drivers/pir.cpp new file mode 100644 index 000000000..173770adb --- /dev/null +++ b/examples/sensors/main/drivers/pir.cpp @@ -0,0 +1,64 @@ +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include + +#include + +#define PIR_SENSOR_PIN (static_cast(CONFIG_PIR_DATA_PIN)) + +typedef struct { + pir_sensor_config_t *config; + bool is_initialized; +} pir_sensor_ctx_t; + +static pir_sensor_ctx_t s_ctx; + +static void IRAM_ATTR pir_gpio_handler(void *arg) +{ + static bool occupancy = false; + bool new_occupancy = gpio_get_level(PIR_SENSOR_PIN); + + // we only need to notify application layer if occupancy changed + if (occupancy != new_occupancy) { + occupancy = new_occupancy; + if (s_ctx.config->cb) { + s_ctx.config->cb(s_ctx.config->endpoint_id, new_occupancy, s_ctx.config->user_data); + } + } +} + +static void pir_gpio_init(gpio_num_t pin) +{ + gpio_reset_pin(pin); + gpio_set_intr_type(pin, GPIO_INTR_ANYEDGE); + gpio_set_direction(pin, GPIO_MODE_INPUT); + + gpio_set_pull_mode(pin, GPIO_PULLDOWN_ONLY); + + gpio_install_isr_service(0); + gpio_isr_handler_add(pin, pir_gpio_handler, NULL); +} + +esp_err_t pir_sensor_init(pir_sensor_config_t *config) +{ + if (config == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (s_ctx.is_initialized) { + return ESP_ERR_INVALID_STATE; + } + + pir_gpio_init(PIR_SENSOR_PIN); + + s_ctx.config = config; + return ESP_OK; +} diff --git a/examples/sensors/main/drivers/pir.h b/examples/sensors/main/drivers/pir.h new file mode 100644 index 000000000..efc2e6e3a --- /dev/null +++ b/examples/sensors/main/drivers/pir.h @@ -0,0 +1,37 @@ +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +// This file implements the PIR sensor driver. +// This is implemented keeping the Matter requirements in mind. +#pragma once + +#include + +using pir_sensor_cb_t = void (*)(uint16_t endpoint_id, bool occupied, void *user_data); + +typedef struct { + // This callback functon will be called periodically to report the temperature. + pir_sensor_cb_t cb = NULL; + // endpoint_id associated with temperature sensor + uint16_t endpoint_id; + // user data + void *user_data = NULL; +} pir_sensor_config_t; + +/** + * @brief Initialize sensor driver. This function should be called only once + * + * @param config sensor configurations. This should last for the lifetime of the driver + * as driver layer do not make a copy of this object. + * + * @return esp_err_t - ESP_OK on success, + * ESP_ERR_INVALID_ARG if config is NULL + * ESP_ERR_INVALID_STATE if driver is already initialized + * appropriate error code otherwise + */ +esp_err_t pir_sensor_init(pir_sensor_config_t *config); diff --git a/examples/sensors/main/drivers/shtc3.cpp b/examples/sensors/main/drivers/shtc3.cpp new file mode 100644 index 000000000..e30b765fe --- /dev/null +++ b/examples/sensors/main/drivers/shtc3.cpp @@ -0,0 +1,179 @@ +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include +#include + +#include + +#include + +static const char * TAG = "shtc3"; + +#define I2C_MASTER_SCL_IO CONFIG_SHTC3_I2C_SCL_PIN +#define I2C_MASTER_SDA_IO CONFIG_SHTC3_I2C_SDA_PIN +#define I2C_MASTER_NUM I2C_NUM_0 /*!< I2C port number for master dev */ +#define I2C_MASTER_FREQ_HZ 100000 /*!< I2C master clock frequency */ + +#define SHTC3_SENSOR_ADDR 0x70 /*!< I2C address of SHTC3 sensor */ + +typedef struct { + shtc3_sensor_config_t *config; + esp_timer_handle_t timer; + bool is_initialized = false; +} shtc3_sensor_ctx_t; + +static shtc3_sensor_ctx_t s_ctx; + +static esp_err_t shtc3_init_i2c() +{ + i2c_config_t i2c_conf = { + .mode = I2C_MODE_MASTER, + .sda_io_num = I2C_MASTER_SDA_IO, + .scl_io_num = I2C_MASTER_SCL_IO, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master = { + .clk_speed = I2C_MASTER_FREQ_HZ, + }, + }; + + esp_err_t err = i2c_param_config(I2C_MASTER_NUM, &i2c_conf); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to configure I2C driver, err:%d", err); + return err; + } + + return i2c_driver_install(I2C_MASTER_NUM, I2C_MODE_MASTER, 0, 0, 0); +} + +static esp_err_t shtc3_read(uint8_t *data, size_t size) +{ + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (SHTC3_SENSOR_ADDR << 1) | I2C_MASTER_WRITE, true /* enable_ack */); + // Read temperature first then humidity, with clock stretching enabled + i2c_master_write_byte(cmd, 0x7C, true /* enable_ack */); + i2c_master_write_byte(cmd, 0xA2, true /* enable_ack */); + i2c_master_stop(cmd); + i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, pdMS_TO_TICKS(1000)); + i2c_cmd_link_delete(cmd); + cmd = NULL; + + // Wait for measurement to complete + vTaskDelay(pdMS_TO_TICKS(15)); + + cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (SHTC3_SENSOR_ADDR << 1) | I2C_MASTER_READ, true /* enable_ack */); + i2c_master_read(cmd, data, size, I2C_MASTER_LAST_NACK); + i2c_master_stop(cmd); + i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, pdMS_TO_TICKS(1000)); + i2c_cmd_link_delete(cmd); + cmd = NULL; + + return ESP_OK; +} + +// Temperature in degree Celsius +static float shtc3_get_temp(uint16_t raw_temp) +{ + return 175.0f * (static_cast(raw_temp) / 65535.0f) - 45.0f; +} + +// Humidity in percentage +static float shtc3_get_humidity(uint16_t raw_humidity) +{ + return 100.0f * (static_cast(raw_humidity) / 65535.0f); +} + +static esp_err_t shtc3_get_read_temp_and_humidity(float & temp, float & humidity) +{ + // foreach temperature and humidity: two bytes data, one byte for checksum + uint8_t data[6] = {0}; + + esp_err_t err = shtc3_read(data, sizeof(data)); + if (err != ESP_OK) { + return err; + } + + uint16_t raw_temp = (data[0] << 8) | data[1]; + uint16_t raw_humidity = (data[3] << 8) | data[4]; + + temp = shtc3_get_temp(raw_temp); + humidity = shtc3_get_humidity(raw_humidity); + + return ESP_OK; +} + +static void timer_cb_internal(void *arg) +{ + auto *ctx = (shtc3_sensor_ctx_t *) arg; + if (!(ctx && ctx->config)) { + return; + } + + float temp, humidity; + esp_err_t err = shtc3_get_read_temp_and_humidity(temp, humidity); + if (err != ESP_OK) { + return; + } + if (ctx->config->temperature.cb) { + ctx->config->temperature.cb(ctx->config->temperature.endpoint_id, temp, ctx->config->user_data); + } + if (ctx->config->humidity.cb) { + ctx->config->humidity.cb(ctx->config->humidity.endpoint_id, humidity, ctx->config->user_data); + } +} + +esp_err_t shtc3_sensor_init(shtc3_sensor_config_t *config) +{ + if (config == NULL) { + return ESP_ERR_INVALID_ARG; + } + // we need at least one callback so that we can start notifying application layer + if (config->temperature.cb == NULL || config->humidity.cb == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (s_ctx.is_initialized) { + return ESP_ERR_INVALID_STATE; + } + + esp_err_t err = shtc3_init_i2c(); + if (err != ESP_OK) { + return err; + } + + // keep the pointer to config + s_ctx.config = config; + + esp_timer_create_args_t args = { + .callback = timer_cb_internal, + .arg = &s_ctx, + }; + + err = esp_timer_create(&args, &s_ctx.timer); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_timer_create failed, err:%d", err); + return err; + } + + err = esp_timer_start_periodic(s_ctx.timer, config->interval_ms * 1000); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_timer_start_periodic failed: %d", err); + return err; + } + + s_ctx.is_initialized = true; + ESP_LOGI(TAG, "shtc3 initialized successfully"); + + return ESP_OK; +} diff --git a/examples/sensors/main/drivers/shtc3.h b/examples/sensors/main/drivers/shtc3.h new file mode 100644 index 000000000..eeef00844 --- /dev/null +++ b/examples/sensors/main/drivers/shtc3.h @@ -0,0 +1,55 @@ +/* + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +// This file implements the SHTC3 temperature and humidity sensor driver. +// This is implemented keeping the Matter requirements in mind. +// +// Datasheet: https://sensirion.com/media/documents/643F9C8E/63A5A436/Datasheet_SHTC3.pdf + +#pragma once + +#include + +using shtc3_sensor_cb_t = void (*)(uint16_t endpoint_id, float value, void *user_data); + +typedef struct { + struct { + // This callback functon will be called periodically to report the temperature. + shtc3_sensor_cb_t cb = NULL; + // endpoint_id associated with temperature sensor + uint16_t endpoint_id; + } temperature; + + struct { + // This callback functon will be called periodically to report the humidity. + shtc3_sensor_cb_t cb = NULL; + // endpoint_id associated with humidity sensor + uint16_t endpoint_id; + } humidity; + + // user data + void *user_data = NULL; + + // polling interval in milliseconds, defaults to 5000 ms + uint32_t interval_ms = 5000; +} shtc3_sensor_config_t; + +/** + * @brief Initialize sensor driver. This function should be called only once + * When initializing, at least one callback should be provided, else it + * returns ESP_ERR_INVALID_ARG. + * + * @param config sensor configurations. This should last for the lifetime of the driver + * as driver layer do not make a copy of this object. + * + * @return esp_err_t - ESP_OK on success, + * ESP_ERR_INVALID_ARG if config is NULL + * ESP_ERR_INVALID_STATE if driver is already initialized + * appropriate error code otherwise + */ +esp_err_t shtc3_sensor_init(shtc3_sensor_config_t *config); diff --git a/examples/sensors/main/idf_component.yml b/examples/sensors/main/idf_component.yml new file mode 100644 index 000000000..547b39ec2 --- /dev/null +++ b/examples/sensors/main/idf_component.yml @@ -0,0 +1,8 @@ +dependencies: + espressif/cmake_utilities: + version: 0.* + rules: # will add "optional_component" only when all if clauses are True + - if: "idf_version >=5.0" + - if: "target in [esp32c2]" + esp_bsp_generic: + version: "^1.1.0" diff --git a/examples/sensors/partitions.csv b/examples/sensors/partitions.csv new file mode 100644 index 000000000..ffe5f242e --- /dev/null +++ b/examples/sensors/partitions.csv @@ -0,0 +1,10 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: Firmware partition offset needs to be 64K aligned, initial 36K (9 sectors) are reserved for bootloader and partition table +esp_secure_cert, 0x3F, ,0xd000, 0x2000, encrypted +nvs, data, nvs, 0x10000, 0xC000, +nvs_keys, data, nvs_keys,, 0x1000, encrypted +otadata, data, ota, , 0x2000 +phy_init, data, phy, , 0x1000, +ota_0, app, ota_0, 0x20000, 0x1E0000, +ota_1, app, ota_1, 0x200000, 0x1E0000, +fctry, data, nvs, 0x3E0000, 0x6000 diff --git a/examples/sensors/sdkconfig.defaults b/examples/sensors/sdkconfig.defaults new file mode 100644 index 000000000..55053b358 --- /dev/null +++ b/examples/sensors/sdkconfig.defaults @@ -0,0 +1,53 @@ +# Default to 921600 baud when flashing and monitoring device +CONFIG_ESPTOOLPY_BAUD_921600B=y +CONFIG_ESPTOOLPY_BAUD=921600 +CONFIG_ESPTOOLPY_COMPRESSED=y +CONFIG_ESPTOOLPY_MONITOR_BAUD_115200B=y +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y + +#enable BT +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y + +#disable BT connection reattempt +CONFIG_BT_NIMBLE_ENABLE_CONN_REATTEMPT=n + +#enable lwip ipv6 autoconfig +CONFIG_LWIP_IPV6_AUTOCONFIG=y + +# Use a custom partition table +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_OFFSET=0xC000 + +#enable lwIP route hooks +CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT=y +CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT=y + +# Button +CONFIG_BUTTON_PERIOD_TIME_MS=20 +CONFIG_BUTTON_LONG_PRESS_TIME_MS=5000 + +# disable softap by default +CONFIG_ESP_WIFI_SOFTAP_SUPPORT=n + +# Disable DS Peripheral +CONFIG_ESP_SECURE_CERT_DS_PERIPHERAL=n + +# Enable HKDF in mbedtls +CONFIG_MBEDTLS_HKDF_C=y + +# Increase LwIP IPv6 address number to 6 (MAX_FABRIC + 1) +# unique local addresses for fabrics(MAX_FABRIC), a link local address(1) +CONFIG_LWIP_IPV6_NUM_ADDRESSES=6 + +# ESP32-DevKitC Settings +# Buttons +CONFIG_BSP_BUTTONS_NUM=1 +CONFIG_BSP_BUTTON_1_TYPE_GPIO=y +CONFIG_BSP_BUTTON_1_GPIO=0 +CONFIG_BSP_BUTTON_1_LEVEL=0 + +# Enable OTA Requestor +CONFIG_ENABLE_OTA_REQUESTOR=y diff --git a/examples/sensors/sdkconfig.defaults.esp32c3 b/examples/sensors/sdkconfig.defaults.esp32c3 new file mode 100644 index 000000000..7e8be3dfe --- /dev/null +++ b/examples/sensors/sdkconfig.defaults.esp32c3 @@ -0,0 +1,8 @@ +CONFIG_IDF_TARGET="esp32c3" + +# ESP32-C3-DevKitC-02 Settings +# Buttons +CONFIG_BSP_BUTTONS_NUM=1 +CONFIG_BSP_BUTTON_1_TYPE_GPIO=y +CONFIG_BSP_BUTTON_1_GPIO=9 +CONFIG_BSP_BUTTON_1_LEVEL=0