Files
esp32_Simple_Light/main/simple_zb_light.c
2025-08-14 07:00:18 -04:00

175 lines
7.9 KiB
C

#include <stdio.h>
#include "freertos/FreeRTOS.h" // FreeRTOS kernel for task management
#include "freertos/task.h" // FreeRTOS task handling functions
#include "driver/gpio.h" // GPIO driver for controlling pins like LEDs
#include "esp_log.h" // ESP logging library for debug/info/error messages
#include "nvs_flash.h" // Non-volatile storage for persistent data
#include "simple_zb_light.h" // Custom header with Zigbee configs
#define LED_PIN GPIO_NUM_2
static const char *TAG = "SIMPLE_LIGHT_CONTROLLER";
static bool led_is_on = false;
static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message);
// Helper function for scheduler callback
static void commissioning_restart_cb(uint8_t param) {
esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING);
}
static void led_task(void *pvParameters) {
gpio_reset_pin(LED_PIN); // Reset pin to default state (clean slate)
gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT); // Set pin as output (can send voltage)
// Track previous state to avoid redundant GPIO writes
bool prev_state = !led_is_on;
while (1) {
// Update GPIO only on state change for efficiency
if (led_is_on != prev_state) {
gpio_set_level(LED_PIN, led_is_on ? 1 : 0); //set whether the light is on or off
// Log state changes for debugging
ESP_LOGI(TAG, "LED state updated to %s", led_is_on ? "ON" : "OFF");
prev_state = led_is_on;
}
// Increased delay to 500ms for lower CPU usage
vTaskDelay(500 / portTICK_PERIOD_MS); // delay by 500 ms
}
}
static void zigbee_task(void *pvParameter) {
ESP_LOGI(TAG, "Starting Zigbee... "); // print log
// Simplified config using ESP_ZB_ZED_CONFIG() for standard end device settings
esp_zb_cfg_t zb_nwk_cfg = ESP_ZB_ZED_CONFIG();
esp_zb_init(&zb_nwk_cfg); // Initialize Zigbee stack with config
// Create a light device with default settings
esp_zb_on_off_light_cfg_t light_cfg = ESP_ZB_DEFAULT_ON_OFF_LIGHT_CONFIG();
// Use HA_ESP_LIGHT_ENDPOINT (10) for standard Home Automation compatibility
esp_zb_ep_list_t *ep_list = esp_zb_on_off_light_ep_create(1, &light_cfg);
esp_zb_device_register(ep_list); // Tell Zigbee stack about our device
esp_zb_core_action_handler_register(zb_action_handler); // Set function to handle commands
// Set channel mask to scan all Zigbee channels (11-26)
esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK);
ESP_ERROR_CHECK(esp_zb_start(false)); // Start Zigbee (false = don't erase network info)
esp_zb_stack_main_loop(); // Run Zigbee forever (never returns)
}
// ========== HANDLE COMMANDS FROM HOME ASSISTANT ==========
// This function is called when Home Assistant sends commands to our device
static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message)
{
// callback_id = what type of action/command this is
// message = pointer to the actual command data
// Check if this is a "set attribute value" command (like turn on/off)
if (callback_id == ESP_ZB_CORE_SET_ATTR_VALUE_CB_ID) {
// Cast the generic message pointer to the specific message type we expect
const esp_zb_zcl_set_attr_value_message_t *msg =
(esp_zb_zcl_set_attr_value_message_t *)message;
// Check if this command is for the on/off cluster AND the on/off attribute
if (msg->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF && // Is this an on/off command?
msg->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID) { // Is this the on/off attribute?
// Extract the boolean value from the command
led_is_on = *(bool *)msg->attribute.data.value; // true = on, false = off
// Log the command received
ESP_LOGI(TAG, "Zigbee command received: LED %s", led_is_on ? "ON" : "OFF");
}
}
return ESP_OK; // Return success status
}
// This function is called when Zigbee network events happen (joining, errors, etc.)
void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct)
{
// Extract the signal type from the structure
uint32_t *p_sg_p = signal_struct->p_app_signal; // Pointer to signal data
esp_zb_app_signal_type_t sig_type = *p_sg_p; // Dereference to get signal type
// Extract error status for better logging
esp_err_t err_status = signal_struct->esp_err_status;
// Handle different types of network events
switch (sig_type) {
// === ZIGBEE STACK INITIALIZED ===
case ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP:
ESP_LOGI(TAG, "Zigbee stack initialized");
// Start the commissioning process (joining a network)
// BDB = Base Device Behavior (standard way devices join networks)
esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_INITIALIZATION);
break;
// === DEVICE STARTED FOR FIRST TIME ===
// Handle both first start and reboot consistently
case ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START:
case ESP_ZB_BDB_SIGNAL_DEVICE_REBOOT:
if (err_status == ESP_OK) {
// Log factory reset status
ESP_LOGI(TAG, "Device started in %s factory-reset mode", esp_zb_bdb_is_factory_new() ? "" : "non");
if (esp_zb_bdb_is_factory_new()) {
ESP_LOGI(TAG, "Start network steering");
esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING);
} else {
ESP_LOGI(TAG, "Device rebooted");
}
} else {
// Log initialization errors
ESP_LOGW(TAG, "Failed to initialize Zigbee stack (status: %s)", esp_err_to_name(err_status));
}
break;
// === NETWORK JOINING ATTEMPT RESULT ===
case ESP_ZB_BDB_SIGNAL_STEERING:
if (err_status == ESP_OK) {
// Log detailed network info on successful join
esp_zb_ieee_addr_t extended_pan_id;
esp_zb_get_extended_pan_id(extended_pan_id);
ESP_LOGI(TAG, "Joined network successfully (Extended PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x, PAN ID: 0x%04hx, Channel:%d)",
extended_pan_id[7], extended_pan_id[6], extended_pan_id[5], extended_pan_id[4],
extended_pan_id[3], extended_pan_id[2], extended_pan_id[1], extended_pan_id[0],
esp_zb_get_pan_id(), esp_zb_get_current_channel());
} else {
// Reduced retry delay to 1s for faster recovery
ESP_LOGW(TAG, "Failed to join network (status: %s), retrying in 1s...", esp_err_to_name(err_status));
esp_zb_scheduler_alarm((esp_zb_callback_t)commissioning_restart_cb, 0, 1000);
}
break;
// === ANY OTHER ZIGBEE EVENT ===
default:
// Log other events with error status
ESP_LOGI(TAG, "Other Zigbee event: %d (status: %s)", sig_type, esp_err_to_name(err_status));
break;
}
}
void app_main(void) {
ESP_LOGI(TAG, "=== Simple Light Controller for Zigbee & Zigbee2MQTT ===");
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);
// Initialize Zigbee platform configuration
esp_zb_platform_config_t config = {
.radio_config = ESP_ZB_DEFAULT_RADIO_CONFIG(), // Use macro from simple_zb_light.h
.host_config = ESP_ZB_DEFAULT_HOST_CONFIG(), // Use macro from simple_zb_light.h
};
ESP_ERROR_CHECK(esp_zb_platform_config(&config));
xTaskCreate(led_task, "LED_Task", 2048, NULL, 1, NULL);
xTaskCreate(zigbee_task, "Zigbee_Task", 4096, NULL, 5, NULL);
ESP_LOGI(TAG, "Both tasks started - LED and Zigbee running independently!");
}