/* Device Registry - Implementação de diferentes tipos de dispositivos Zigbee */ #include "device_registry.h" #include "zcl/esp_zigbee_zcl_common.h" #include "esp_log.h" #include "esp_zb_switch.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #ifdef DEVICE_TYPE_RELAY #include "switch_driver.h" #endif #ifdef DEVICE_TYPE_TEMP_SENSOR #include "dht11_driver.h" #endif #if FEATURE_LED_INDICATOR #include "led_indicator.h" #endif static const char *TAG = "DEVICE_REGISTRY"; /* ========================= IMPLEMENTAÇÕES DE DISPOSITIVOS ========================= */ #ifdef DEVICE_TYPE_RELAY /* ===== RELÉ CONTROLÁVEL (ON/OFF LIGHT) ===== */ static esp_zb_cluster_list_t* relay_create_clusters(void) { esp_zb_cluster_list_t *cluster_list = esp_zb_zcl_cluster_list_create(); 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); esp_zb_cluster_list_add_on_off_cluster(cluster_list, device_create_onoff_cluster(), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); return cluster_list; } static esp_err_t relay_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message) { 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 relay_hardware_init(void) { ESP_LOGI(TAG, "Initializing relay hardware (GPIO %d)", GPIO_OUTPUT_RELAY); relay_driver_init(); } static void relay_update_state(void) { /* Relé é controlado por comandos Zigbee, não precisa polling */ } static const device_type_t relay_device = { .name = "Zigbee Relay", .device_id = ESP_ZB_HA_ON_OFF_OUTPUT_DEVICE_ID, .endpoint = HA_ONOFF_SWITCH_ENDPOINT, .create_clusters = relay_create_clusters, .attribute_handler = relay_attribute_handler, .hardware_init = relay_hardware_init, .update_state = relay_update_state, }; #endif /* DEVICE_TYPE_RELAY */ /* ========================= SENSOR DE TEMPERATURA ========================= */ #ifdef DEVICE_TYPE_TEMP_SENSOR static TaskHandle_t temp_sensor_task_handle = NULL; /* Task para ler sensor DHT11 periodicamente */ static void temp_sensor_task(void *pvParameters) { dht11_reading_t reading; TickType_t last_wake_time = xTaskGetTickCount(); while (1) { /* Aguarda intervalo configurado */ vTaskDelayUntil(&last_wake_time, pdMS_TO_TICKS(SENSOR_UPDATE_INTERVAL)); /* Lê temperatura e umidade do DHT11 */ esp_err_t ret = dht11_read(&reading); if (ret == ESP_OK && reading.valid) { /* Converte temperatura para formato Zigbee (0.01°C) */ int16_t temp_zigbee = (int16_t)(reading.temperature * 100); /* Converte umidade para formato Zigbee (0.01% RH) */ uint16_t humidity_zigbee = (uint16_t)(reading.humidity * 100); /* Atualiza atributos Zigbee */ esp_zb_lock_acquire(portMAX_DELAY); /* Atualiza temperatura */ 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 ); /* Atualiza umidade */ 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 - Temperature: %.1f°C (Zigbee: %d), Humidity: %.1f%% (Zigbee: %u)", reading.temperature, temp_zigbee, reading.humidity, humidity_zigbee); #if FEATURE_LED_INDICATOR /* Pisca LED para indicar leitura */ led_indicator_set_state(LED_CONNECTED); #endif } else { ESP_LOGW(TAG, "Failed to read DHT11 sensor: %s", esp_err_to_name(ret)); } } } static esp_zb_cluster_list_t* temp_sensor_create_clusters(void) { esp_zb_cluster_list_t *cluster_list = esp_zb_zcl_cluster_list_create(); 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); 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 temp_sensor_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message) { /* Sensor de temperatura não recebe comandos, apenas reporta */ return ESP_ERR_NOT_FOUND; } static void temp_sensor_hardware_init(void) { ESP_LOGI(TAG, "Initializing DHT11 temperature sensor on GPIO %d", GPIO_SENSOR_DHT11); /* Inicializa driver 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 periódica */ xTaskCreate( temp_sensor_task, "temp_sensor", 4096, NULL, 5, &temp_sensor_task_handle ); ESP_LOGI(TAG, "DHT11 sensor task started (update interval: %d ms)", SENSOR_UPDATE_INTERVAL); } static void temp_sensor_update_state(void) { /* A atualização é feita automaticamente pela task periódica */ } static const device_type_t temp_sensor_device = { .name = "Zigbee Temperature & Humidity Sensor DHT11", .device_id = ESP_ZB_HA_TEMPERATURE_SENSOR_DEVICE_ID, .endpoint = HA_ONOFF_SWITCH_ENDPOINT, .create_clusters = temp_sensor_create_clusters, .attribute_handler = temp_sensor_attribute_handler, .hardware_init = temp_sensor_hardware_init, .update_state = temp_sensor_update_state, }; #endif /* DEVICE_TYPE_TEMP_SENSOR */ /* ========================= SENSOR DE PORTA/JANELA ========================= */ #ifdef DEVICE_TYPE_DOOR_SENSOR static esp_zb_cluster_list_t* door_sensor_create_clusters(void) { esp_zb_cluster_list_t *cluster_list = esp_zb_zcl_cluster_list_create(); 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); esp_zb_cluster_list_add_ias_zone_cluster(cluster_list, device_create_ias_zone_cluster(), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); return cluster_list; } static esp_err_t door_sensor_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message) { /* Sensor IAS Zone não recebe comandos, apenas reporta */ return ESP_ERR_NOT_FOUND; } static void door_sensor_hardware_init(void) { ESP_LOGI(TAG, "Initializing door sensor (Reed Switch GPIO %d)", GPIO_SENSOR_REED); /* TODO: Configurar GPIO do reed switch com pull-up e interrupção */ } static void door_sensor_update_state(void) { /* TODO: Ler estado do reed switch e atualizar IAS Zone status */ } static const device_type_t door_sensor_device = { .name = "Zigbee Door/Window Sensor", .device_id = ESP_ZB_HA_IAS_ZONE_DEVICE_ID, .endpoint = HA_ONOFF_SWITCH_ENDPOINT, .create_clusters = door_sensor_create_clusters, .attribute_handler = door_sensor_attribute_handler, .hardware_init = door_sensor_hardware_init, .update_state = door_sensor_update_state, }; #endif /* DEVICE_TYPE_DOOR_SENSOR */ /* ========================= SENSOR DE MOVIMENTO ========================= */ #ifdef DEVICE_TYPE_MOTION_SENSOR static esp_zb_cluster_list_t* motion_sensor_create_clusters(void) { esp_zb_cluster_list_t *cluster_list = esp_zb_zcl_cluster_list_create(); 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); esp_zb_cluster_list_add_occupancy_sensing_cluster(cluster_list, device_create_occupancy_cluster(), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); return cluster_list; } static esp_err_t motion_sensor_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message) { /* Sensor de ocupação não recebe comandos, apenas reporta */ return ESP_ERR_NOT_FOUND; } static void motion_sensor_hardware_init(void) { ESP_LOGI(TAG, "Initializing motion sensor (PIR GPIO %d)", GPIO_SENSOR_PIR); /* TODO: Configurar GPIO do PIR com interrupção */ } static void motion_sensor_update_state(void) { /* TODO: Ler estado do PIR e atualizar occupancy */ } static const device_type_t motion_sensor_device = { .name = "Zigbee Motion Sensor", .device_id = ESP_ZB_HA_OCCUPANCY_SENSOR_DEVICE_ID, .endpoint = HA_ONOFF_SWITCH_ENDPOINT, .create_clusters = motion_sensor_create_clusters, .attribute_handler = motion_sensor_attribute_handler, .hardware_init = motion_sensor_hardware_init, .update_state = motion_sensor_update_state, }; #endif /* DEVICE_TYPE_MOTION_SENSOR */ /* ========================= SELETOR DE DISPOSITIVO ========================= */ const device_type_t* device_get_config(void) { #ifdef DEVICE_TYPE_RELAY return &relay_device; #elif defined(DEVICE_TYPE_TEMP_SENSOR) return &temp_sensor_device; #elif defined(DEVICE_TYPE_DOOR_SENSOR) return &door_sensor_device; #elif defined(DEVICE_TYPE_MOTION_SENSOR) return &motion_sensor_device; #else #error "Nenhum tipo de dispositivo definido!" return NULL; #endif } /* ========================= FUNÇÕES AUXILIARES PARA CLUSTERS ========================= */ esp_zb_attribute_list_t* device_create_basic_cluster(void) { esp_zb_basic_cluster_cfg_t basic_cfg = { .zcl_version = ESP_ZB_ZCL_BASIC_ZCL_VERSION_DEFAULT_VALUE, .power_source = ESP_ZB_ZCL_BASIC_POWER_SOURCE_DEFAULT_VALUE, }; return esp_zb_basic_cluster_create(&basic_cfg); } esp_zb_attribute_list_t* device_create_identify_cluster(void) { esp_zb_identify_cluster_cfg_t identify_cfg = { .identify_time = ESP_ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE, }; return esp_zb_identify_cluster_create(&identify_cfg); } esp_zb_attribute_list_t* device_create_onoff_cluster(void) { esp_zb_on_off_cluster_cfg_t on_off_cfg = { .on_off = ESP_ZB_ZCL_ON_OFF_ON_OFF_DEFAULT_VALUE, }; return esp_zb_on_off_cluster_create(&on_off_cfg); } esp_zb_attribute_list_t* device_create_temperature_cluster(void) { esp_zb_temperature_meas_cluster_cfg_t temp_cfg = { .measured_value = ESP_ZB_ZCL_TEMP_MEASUREMENT_MEASURED_VALUE_DEFAULT, .min_value = -5000, /* -50°C em unidades de 0.01°C */ .max_value = 12500, /* 125°C em unidades de 0.01°C */ }; return esp_zb_temperature_meas_cluster_create(&temp_cfg); } esp_zb_attribute_list_t* device_create_humidity_cluster(void) { esp_zb_humidity_meas_cluster_cfg_t humidity_cfg = { .measured_value = ESP_ZB_ZCL_REL_HUMIDITY_MEASUREMENT_MEASURED_VALUE_DEFAULT, .min_value = 0, /* 0% RH em unidades de 0.01% */ .max_value = 10000, /* 100% RH em unidades de 0.01% */ }; return esp_zb_humidity_meas_cluster_create(&humidity_cfg); } esp_zb_attribute_list_t* device_create_occupancy_cluster(void) { esp_zb_occupancy_sensing_cluster_cfg_t occupancy_cfg = { .occupancy = 0, .sensor_type = ESP_ZB_ZCL_OCCUPANCY_SENSING_OCCUPANCY_SENSOR_TYPE_PIR, }; return esp_zb_occupancy_sensing_cluster_create(&occupancy_cfg); } esp_zb_attribute_list_t* device_create_ias_zone_cluster(void) { esp_zb_ias_zone_cluster_cfg_t ias_zone_cfg = { .zone_state = ESP_ZB_ZCL_IAS_ZONE_ZONESTATE_NOT_ENROLLED, .zone_type = ESP_ZB_ZCL_IAS_ZONE_ZONETYPE_CONTACT_SWITCH, .zone_status = 0, }; return esp_zb_ias_zone_cluster_create(&ias_zone_cfg); }