166 lines
7.0 KiB
C
166 lines
7.0 KiB
C
#include <stdio.h>
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "driver/gpio.h"
|
|
#include "esp_log.h"
|
|
#include "nvs_flash.h"
|
|
#include "esp_zigbee_core.h"
|
|
#include "ha/esp_zigbee_ha_standard.h"
|
|
#include "ha/esp_zigbee_ha_standard.h"
|
|
|
|
#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)
|
|
|
|
while(1) {
|
|
gpio_set_level(LED_PIN, led_is_on ? 1 : 0); //set whether the light is on or off
|
|
vTaskDelay(100 / portTICK_PERIOD_MS); // delay by 100 ms
|
|
}
|
|
}
|
|
|
|
static void zigbee_task(void *pvParameter) {
|
|
|
|
ESP_LOGI(TAG, "Starting Zigbee... "); // print log
|
|
|
|
esp_zb_cfg_t zb_nwk_cfg = { // default end device config
|
|
.esp_zb_role = ESP_ZB_DEVICE_TYPE_ED,
|
|
.install_code_policy = true,
|
|
.nwk_cfg.zed_cfg = {
|
|
.ed_timeout = ESP_ZB_ED_AGING_TIMEOUT_64MIN,
|
|
.keep_alive = 60000, //respond to coordinator every 60 seconds to let it know its alive.
|
|
}
|
|
};
|
|
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();
|
|
esp_zb_ep_list_t *ep_list = esp_zb_on_off_light_ep_create(1, &light_cfg); //TODO: endpoint_ID set to 1 for testing
|
|
|
|
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
|
|
|
|
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
|
|
|
|
// Print what happened to serial monitor
|
|
ESP_LOGI(TAG, "Home Assistant says: LED %s", led_is_on ? "ON" : "OFF");
|
|
}
|
|
}
|
|
|
|
return ESP_OK; // Return success status
|
|
}
|
|
|
|
// Call this if device won't join network - forces it back to discoverable state
|
|
void force_factory_reset(void)
|
|
{
|
|
ESP_LOGI(TAG, "Forcing factory reset - device will be discoverable again");
|
|
esp_zb_factory_reset(); // Erases all network info
|
|
esp_restart(); // Restart device
|
|
}
|
|
|
|
// 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
|
|
|
|
// 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 ===
|
|
case ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START:
|
|
ESP_LOGI(TAG, "Device started - looking for network to join");
|
|
// Start network steering = look for open Zigbee networks to join
|
|
esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING);
|
|
break;
|
|
|
|
// === NETWORK JOINING ATTEMPT RESULT ===
|
|
case ESP_ZB_BDB_SIGNAL_STEERING:
|
|
if (signal_struct->esp_err_status == ESP_OK) {
|
|
ESP_LOGI(TAG, "Successfully joined Zigbee network!");
|
|
} else {
|
|
ESP_LOGI(TAG, "Failed to join network, retrying in 5 seconds...");
|
|
esp_zb_scheduler_alarm((esp_zb_callback_t)commissioning_restart_cb, 0, 5000);
|
|
}
|
|
break;
|
|
|
|
// === ANY OTHER ZIGBEE EVENT ===
|
|
default:
|
|
ESP_LOGI(TAG, "Other Zigbee event: %d", sig_type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void app_main(void)
|
|
{
|
|
// Print startup banner to serial monitor
|
|
ESP_LOGI(TAG, "=== Simple Light controller for Zigbee & HA ===");
|
|
|
|
// NVS = Non-Volatile Storage
|
|
// Saves Zigbee network info so device remembers which network it joined
|
|
ESP_ERROR_CHECK(nvs_flash_init()); // ESP_ERROR_CHECK = crash if this fails
|
|
|
|
// Use default platform configuration (simpler approach)
|
|
ESP_ERROR_CHECK(esp_zb_platform_config(NULL)); // NULL = use defaults
|
|
|
|
// xTaskCreate = create a new task (mini-program that runs independently)
|
|
xTaskCreate(led_task, // Function to run (defined above)
|
|
"LED_Task", // Name for debugging (shows in task monitor)
|
|
2048, // Stack size in bytes (how much memory task can use)
|
|
NULL, // Parameters to pass to task (we don't need any)
|
|
1, // Priority 1-25 (1=lowest, 25=highest priority)
|
|
NULL); // Task handle (we don't need to control task later)
|
|
|
|
xTaskCreate(zigbee_task, // Function to run (defined above)
|
|
"Zigbee_Task", // Name for debugging
|
|
4096, // Larger stack - Zigbee needs more memory
|
|
NULL, // No parameters
|
|
5, // Higher priority - network communication is important
|
|
NULL); // No handle needed
|
|
|
|
ESP_LOGI(TAG, "Both tasks started - LED and Zigbee running independently!");
|
|
} |