feat: Adiciona suporte para dispositivo combo (relé + sensor de temperatura/umidade) e driver para sensor ultrassônico JSN-SR04T

This commit is contained in:
2026-01-17 00:34:24 +00:00
parent fcecd9d273
commit 5c063ff367
32 changed files with 467 additions and 9 deletions
Regular → Executable
View File
Regular → Executable
+15 -5
View File
@@ -10,9 +10,10 @@ extern "C" {
/* Descomente APENAS UMA das opções abaixo para definir o tipo de dispositivo */
// #define DEVICE_TYPE_RELAY /* Relé controlável (on/off light) */
#define DEVICE_TYPE_TEMP_SENSOR /* Sensor de temperatura */
// #define DEVICE_TYPE_DOOR_SENSOR /* Sensor de porta/janela */
// #define DEVICE_TYPE_MOTION_SENSOR /* Sensor de movimento */
// #define DEVICE_TYPE_TEMP_SENSOR /* Sensor de temperatura e umidade */
// #define DEVICE_TYPE_DOOR_SENSOR /* Sensor de porta/janela */
// #define DEVICE_TYPE_MOTION_SENSOR /* Sensor de movimento */
#define DEVICE_TYPE_COMBO_RELAY_SENSOR /* Relé + Sensor de temperatura/umidade */
/* ========================= CONFIGURAÇÃO DE HARDWARE ========================= */
@@ -41,6 +42,13 @@ extern "C" {
#define PIR_TIMEOUT_MS 60000 /* Timeout para resetar movimento detectado */
#endif
#ifdef DEVICE_TYPE_COMBO_RELAY_SENSOR
#define GPIO_OUTPUT_RELAY 4 /* Relé de saída */
#define GPIO_SENSOR_DHT11 5 /* GPIO para DHT11 (DATA pin) */
#define SENSOR_UPDATE_INTERVAL 10000 /* Intervalo de atualização em ms */
#define HAS_BUTTON_TOGGLE 1 /* Botão também faz toggle do relé */
#endif
/* ========================= FUNCIONALIDADES OPCIONAIS ========================= */
#define FEATURE_LED_INDICATOR 1 /* Habilita LED RGB indicador */
@@ -57,13 +65,15 @@ extern "C" {
/* Verifica se pelo menos um tipo de dispositivo foi selecionado */
#if !defined(DEVICE_TYPE_RELAY) && !defined(DEVICE_TYPE_TEMP_SENSOR) && \
!defined(DEVICE_TYPE_DOOR_SENSOR) && !defined(DEVICE_TYPE_MOTION_SENSOR)
!defined(DEVICE_TYPE_DOOR_SENSOR) && !defined(DEVICE_TYPE_MOTION_SENSOR) && \
!defined(DEVICE_TYPE_COMBO_RELAY_SENSOR)
#error "Nenhum tipo de dispositivo selecionado! Defina um DEVICE_TYPE_* em device_config.h"
#endif
/* Verifica se mais de um tipo foi selecionado */
#if (defined(DEVICE_TYPE_RELAY) + defined(DEVICE_TYPE_TEMP_SENSOR) + \
defined(DEVICE_TYPE_DOOR_SENSOR) + defined(DEVICE_TYPE_MOTION_SENSOR)) > 1
defined(DEVICE_TYPE_DOOR_SENSOR) + defined(DEVICE_TYPE_MOTION_SENSOR) + \
defined(DEVICE_TYPE_COMBO_RELAY_SENSOR)) > 1
#error "Múltiplos tipos de dispositivo selecionados! Defina apenas um DEVICE_TYPE_*"
#endif
Regular → Executable
+141
View File
@@ -15,6 +15,11 @@
#include "dht11_driver.h"
#endif
#ifdef DEVICE_TYPE_COMBO_RELAY_SENSOR
#include "switch_driver.h"
#include "dht11_driver.h"
#endif
#if FEATURE_LED_INDICATOR
#include "led_indicator.h"
#endif
@@ -289,6 +294,140 @@ static const device_type_t motion_sensor_device = {
};
#endif /* DEVICE_TYPE_MOTION_SENSOR */
/* ========================= DISPOSITIVO COMBO (RELÉ + SENSOR) ========================= */
#ifdef DEVICE_TYPE_COMBO_RELAY_SENSOR
static TaskHandle_t combo_sensor_task_handle = NULL;
/* Task para ler sensor DHT11 periodicamente */
static void combo_sensor_task(void *pvParameters)
{
dht11_reading_t reading;
TickType_t last_wake_time = xTaskGetTickCount();
while (1) {
vTaskDelayUntil(&last_wake_time, pdMS_TO_TICKS(SENSOR_UPDATE_INTERVAL));
esp_err_t ret = dht11_read(&reading);
if (ret == ESP_OK && reading.valid) {
int16_t temp_zigbee = (int16_t)(reading.temperature * 100);
uint16_t humidity_zigbee = (uint16_t)(reading.humidity * 100);
esp_zb_lock_acquire(portMAX_DELAY);
esp_zb_zcl_set_attribute_val(
HA_ONOFF_SWITCH_ENDPOINT,
ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT,
ESP_ZB_ZCL_CLUSTER_SERVER_ROLE,
ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID,
&temp_zigbee,
false
);
esp_zb_zcl_set_attribute_val(
HA_ONOFF_SWITCH_ENDPOINT,
ESP_ZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT,
ESP_ZB_ZCL_CLUSTER_SERVER_ROLE,
ESP_ZB_ZCL_ATTR_REL_HUMIDITY_MEASUREMENT_VALUE_ID,
&humidity_zigbee,
false
);
esp_zb_lock_release();
ESP_LOGI(TAG, "Sensor updated - Temp: %.1f°C (%d), Humidity: %.1f%% (%u)",
reading.temperature, temp_zigbee, reading.humidity, humidity_zigbee);
} else {
ESP_LOGW(TAG, "Failed to read DHT11: %s", esp_err_to_name(ret));
}
}
}
static esp_zb_cluster_list_t* combo_create_clusters(void)
{
esp_zb_cluster_list_t *cluster_list = esp_zb_zcl_cluster_list_create();
/* Clusters básicos */
esp_zb_cluster_list_add_basic_cluster(cluster_list, device_create_basic_cluster(), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_zb_cluster_list_add_identify_cluster(cluster_list, device_create_identify_cluster(), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
/* Cluster de controle do relé */
esp_zb_cluster_list_add_on_off_cluster(cluster_list, device_create_onoff_cluster(), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
/* Clusters de sensores */
esp_zb_cluster_list_add_temperature_meas_cluster(cluster_list, device_create_temperature_cluster(), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_zb_cluster_list_add_humidity_meas_cluster(cluster_list, device_create_humidity_cluster(), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
return cluster_list;
}
static esp_err_t combo_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message)
{
/* Handler de comandos on/off para o relé */
if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF &&
message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID) {
bool on_off = message->attribute.data.value ? *(bool *)message->attribute.data.value : 0;
ESP_LOGI(TAG, "Relay command received: %s", on_off ? "ON" : "OFF");
relay_set_state(on_off);
#if FEATURE_LED_INDICATOR
led_indicator_set_state(on_off ? LED_RELAY_ON : LED_RELAY_OFF);
#endif
return ESP_OK;
}
return ESP_ERR_NOT_FOUND;
}
static void combo_hardware_init(void)
{
ESP_LOGI(TAG, "Initializing combo device (Relay + DHT11 sensor)");
/* Inicializa relé */
relay_driver_init();
/* Inicializa DHT11 */
esp_err_t ret = dht11_init(GPIO_SENSOR_DHT11);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize DHT11: %s", esp_err_to_name(ret));
return;
}
/* Cria task para leitura do sensor */
xTaskCreate(
combo_sensor_task,
"combo_sensor",
4096,
NULL,
5,
&combo_sensor_task_handle
);
ESP_LOGI(TAG, "Combo device initialized - Relay: GPIO %d, DHT11: GPIO %d",
GPIO_OUTPUT_RELAY, GPIO_SENSOR_DHT11);
}
static void combo_update_state(void)
{
/* Atualização feita pela task periódica */
}
static const device_type_t combo_relay_sensor_device = {
.name = "Zigbee Relay + Temperature/Humidity Sensor",
.device_id = ESP_ZB_HA_ON_OFF_OUTPUT_DEVICE_ID,
.endpoint = HA_ONOFF_SWITCH_ENDPOINT,
.create_clusters = combo_create_clusters,
.attribute_handler = combo_attribute_handler,
.hardware_init = combo_hardware_init,
.update_state = combo_update_state,
};
#endif /* DEVICE_TYPE_COMBO_RELAY_SENSOR */
/* ========================= SELETOR DE DISPOSITIVO ========================= */
const device_type_t* device_get_config(void)
@@ -301,6 +440,8 @@ const device_type_t* device_get_config(void)
return &door_sensor_device;
#elif defined(DEVICE_TYPE_MOTION_SENSOR)
return &motion_sensor_device;
#elif defined(DEVICE_TYPE_COMBO_RELAY_SENSOR)
return &combo_relay_sensor_device;
#else
#error "Nenhum tipo de dispositivo definido!"
return NULL;
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
+241
View File
@@ -0,0 +1,241 @@
/* JSN-SR04T Ultrasonic Distance Sensor Driver Implementation */
#include "jsn_sr04_driver.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "rom/ets_sys.h"
static const char *TAG = "JSN_SR04";
/* Configuração do sensor */
static jsn_sr04_config_t sensor_config = {0};
static bool initialized = false;
/* Constantes físicas */
#define SPEED_OF_SOUND_CM_US 0.0343 /* Velocidade do som: 343 m/s = 0.0343 cm/µs */
#define TRIGGER_PULSE_US 10 /* Duração do pulso trigger em µs */
#define MIN_WAIT_BETWEEN_MS 60 /* Tempo mínimo entre leituras em ms */
/* Timeout padrão baseado na distância máxima */
#define DEFAULT_MAX_DISTANCE_CM 400
#define CALC_TIMEOUT_US(dist_cm) ((dist_cm * 2 * 100) / 343 + 1000)
static int64_t last_read_time = -MIN_WAIT_BETWEEN_MS * 1000;
/**
* @brief Aguarda mudança de nível no pino ECHO
*
* @param level Nível esperado (0 ou 1)
* @param timeout_us Timeout em microsegundos
* @return Tempo decorrido em µs, ou -1 se timeout
*/
static int64_t wait_for_echo_level(int level, uint32_t timeout_us)
{
int64_t start = esp_timer_get_time();
while (gpio_get_level(sensor_config.echo_gpio) != level) {
if ((esp_timer_get_time() - start) > timeout_us) {
return -1;
}
ets_delay_us(1);
}
return esp_timer_get_time() - start;
}
/**
* @brief Envia pulso de trigger
*/
static void send_trigger_pulse(void)
{
/* Garante que trigger está LOW */
gpio_set_level(sensor_config.trigger_gpio, 0);
ets_delay_us(2);
/* Pulso HIGH de 10µs */
gpio_set_level(sensor_config.trigger_gpio, 1);
ets_delay_us(TRIGGER_PULSE_US);
gpio_set_level(sensor_config.trigger_gpio, 0);
}
esp_err_t jsn_sr04_init(const jsn_sr04_config_t *config)
{
if (config == NULL) {
ESP_LOGE(TAG, "Config is NULL");
return ESP_ERR_INVALID_ARG;
}
if (initialized) {
ESP_LOGW(TAG, "Already initialized, reinitializing...");
jsn_sr04_deinit();
}
/* Salva configuração */
sensor_config = *config;
/* Define timeout padrão se não especificado */
if (sensor_config.max_distance_cm == 0) {
sensor_config.max_distance_cm = DEFAULT_MAX_DISTANCE_CM;
}
if (sensor_config.timeout_us == 0) {
sensor_config.timeout_us = CALC_TIMEOUT_US(sensor_config.max_distance_cm);
}
/* Configura GPIO TRIGGER como saída */
gpio_config_t trigger_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = (1ULL << sensor_config.trigger_gpio),
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_DISABLE,
};
esp_err_t ret = gpio_config(&trigger_conf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure TRIGGER GPIO %d", sensor_config.trigger_gpio);
return ret;
}
/* Configura GPIO ECHO como entrada */
gpio_config_t echo_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_INPUT,
.pin_bit_mask = (1ULL << sensor_config.echo_gpio),
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_DISABLE,
};
ret = gpio_config(&echo_conf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure ECHO GPIO %d", sensor_config.echo_gpio);
return ret;
}
/* Inicializa trigger em LOW */
gpio_set_level(sensor_config.trigger_gpio, 0);
initialized = true;
ESP_LOGI(TAG, "JSN-SR04T initialized - TRIGGER: GPIO%d, ECHO: GPIO%d, Max: %d cm",
sensor_config.trigger_gpio, sensor_config.echo_gpio,
sensor_config.max_distance_cm);
/* Aguarda sensor estabilizar */
vTaskDelay(pdMS_TO_TICKS(100));
return ESP_OK;
}
esp_err_t jsn_sr04_read(jsn_sr04_reading_t *reading)
{
if (!initialized) {
ESP_LOGE(TAG, "Sensor not initialized");
return ESP_ERR_INVALID_STATE;
}
if (reading == NULL) {
ESP_LOGE(TAG, "Reading pointer is NULL");
return ESP_ERR_INVALID_ARG;
}
/* Inicializa leitura como inválida */
reading->valid = false;
reading->distance_cm = 0.0f;
reading->echo_time_us = 0;
/* Verifica intervalo mínimo entre leituras */
int64_t current_time = esp_timer_get_time();
int64_t time_since_last = current_time - last_read_time;
if (time_since_last < (MIN_WAIT_BETWEEN_MS * 1000)) {
int64_t wait_us = (MIN_WAIT_BETWEEN_MS * 1000) - time_since_last;
ESP_LOGD(TAG, "Waiting %lld us before next reading", wait_us);
ets_delay_us((uint32_t)wait_us);
}
/* Desabilita interrupções para timing preciso */
portDISABLE_INTERRUPTS();
/* Envia pulso de trigger */
send_trigger_pulse();
/* Aguarda ECHO ir para HIGH (início do pulso) */
int64_t wait_high = wait_for_echo_level(1, sensor_config.timeout_us);
if (wait_high < 0) {
portENABLE_INTERRUPTS();
ESP_LOGW(TAG, "Timeout waiting for ECHO HIGH");
return ESP_ERR_TIMEOUT;
}
/* Mede duração do pulso HIGH */
int64_t start_time = esp_timer_get_time();
int64_t pulse_duration = wait_for_echo_level(0, sensor_config.timeout_us);
portENABLE_INTERRUPTS();
if (pulse_duration < 0) {
ESP_LOGW(TAG, "Timeout waiting for ECHO LOW");
return ESP_ERR_TIMEOUT;
}
/* Atualiza timestamp da última leitura */
last_read_time = esp_timer_get_time();
/* Calcula distância
* Fórmula: distância = (tempo_echo * velocidade_som) / 2
* Dividido por 2 porque o som vai e volta
*/
reading->echo_time_us = (uint32_t)pulse_duration;
reading->distance_cm = (pulse_duration * SPEED_OF_SOUND_CM_US) / 2.0f;
/* Valida leitura */
if (reading->distance_cm < 2.0f || reading->distance_cm > sensor_config.max_distance_cm) {
ESP_LOGD(TAG, "Distance out of range: %.2f cm", reading->distance_cm);
reading->valid = false;
return ESP_ERR_INVALID_RESPONSE;
}
reading->valid = true;
ESP_LOGI(TAG, "Distance: %.2f cm (echo: %u µs)",
reading->distance_cm, reading->echo_time_us);
return ESP_OK;
}
esp_err_t jsn_sr04_get_distance(float *distance_cm)
{
if (distance_cm == NULL) {
return ESP_ERR_INVALID_ARG;
}
jsn_sr04_reading_t reading;
esp_err_t ret = jsn_sr04_read(&reading);
if (ret == ESP_OK && reading.valid) {
*distance_cm = reading.distance_cm;
return ESP_OK;
}
return ret;
}
esp_err_t jsn_sr04_deinit(void)
{
if (!initialized) {
return ESP_OK;
}
/* Reseta GPIOs */
gpio_reset_pin(sensor_config.trigger_gpio);
gpio_reset_pin(sensor_config.echo_gpio);
initialized = false;
ESP_LOGI(TAG, "JSN-SR04T deinitialized");
return ESP_OK;
}
+66
View File
@@ -0,0 +1,66 @@
/* JSN-SR04T Ultrasonic Distance Sensor Driver */
#pragma once
#include "esp_err.h"
#include "driver/gpio.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Configuração do sensor JSN-SR04T
*/
typedef struct {
gpio_num_t trigger_gpio; /* GPIO para pino TRIGGER */
gpio_num_t echo_gpio; /* GPIO para pino ECHO */
uint32_t max_distance_cm; /* Distância máxima em cm (padrão: 400) */
uint32_t timeout_us; /* Timeout em microsegundos */
} jsn_sr04_config_t;
/**
* @brief Leitura do sensor
*/
typedef struct {
float distance_cm; /* Distância medida em centímetros */
uint32_t echo_time_us; /* Tempo do pulso echo em microsegundos */
bool valid; /* Indica se a leitura é válida */
} jsn_sr04_reading_t;
/**
* @brief Inicializa o sensor JSN-SR04T
*
* @param config Configuração do sensor
* @return ESP_OK em caso de sucesso
*/
esp_err_t jsn_sr04_init(const jsn_sr04_config_t *config);
/**
* @brief Lê a distância do sensor
*
* @param reading Ponteiro para estrutura que receberá os dados
* @return ESP_OK em caso de sucesso, ESP_ERR_TIMEOUT se não houver resposta
*
* @note Aguarda pelo menos 60ms entre leituras para melhor precisão
*/
esp_err_t jsn_sr04_read(jsn_sr04_reading_t *reading);
/**
* @brief Obtém apenas a distância em cm
*
* @param distance_cm Ponteiro para receber distância
* @return ESP_OK em caso de sucesso
*/
esp_err_t jsn_sr04_get_distance(float *distance_cm);
/**
* @brief Desabilita o sensor e libera recursos
*
* @return ESP_OK em caso de sucesso
*/
esp_err_t jsn_sr04_deinit(void);
#ifdef __cplusplus
}
#endif
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
+4 -4
View File
@@ -209,7 +209,7 @@ bool switch_driver_init(switch_func_pair_t *button_func_pair, uint8_t button_num
void relay_driver_init(void)
{
#ifdef DEVICE_TYPE_RELAY
#if defined(DEVICE_TYPE_RELAY) || defined(DEVICE_TYPE_COMBO_RELAY_SENSOR)
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
@@ -227,7 +227,7 @@ void relay_driver_init(void)
void relay_set_state(bool state)
{
#ifdef DEVICE_TYPE_RELAY
#if defined(DEVICE_TYPE_RELAY) || defined(DEVICE_TYPE_COMBO_RELAY_SENSOR)
relay_state = state;
gpio_set_level(GPIO_OUTPUT_RELAY, state ? 1 : 0);
@@ -240,14 +240,14 @@ void relay_set_state(bool state)
void relay_toggle(void)
{
#ifdef DEVICE_TYPE_RELAY
#if defined(DEVICE_TYPE_RELAY) || defined(DEVICE_TYPE_COMBO_RELAY_SENSOR)
relay_set_state(!relay_state);
#endif
}
bool relay_get_state(void)
{
#ifdef DEVICE_TYPE_RELAY
#if defined(DEVICE_TYPE_RELAY) || defined(DEVICE_TYPE_COMBO_RELAY_SENSOR)
return relay_state;
#else
return false;
Regular → Executable
View File