From 1a96ce7fd3a6f9c1b4616b95359f04754ba849f5 Mon Sep 17 00:00:00 2001 From: sam rolfe Date: Fri, 10 Oct 2025 12:39:01 +1100 Subject: [PATCH] Initial project for reptile feeder Open multiple boxes to release crickets using 16 channel controller from ESP --- .gitignore | 20 ++++++ platformio.ini | 11 +++ src/main.cpp | 180 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 .gitignore create mode 100644 platformio.ini create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f84cf6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# PlatformIO +.pio +.vscode + +# Build artifacts +*.o +*.a +*.so +*.elf +*.bin +*.hex +*.map +*.d + +# Other +*.pyc +*.pyo +__pycache__/ +.DS_Store +Thumbs.db diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..cc4d564 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,11 @@ +[env:esp32-c6-devkitc-1] +platform = espressif32 +board = esp32-c6-devkitc-1 +framework = arduino, espidf +monitor_speed = 115200 +lib_deps = + adafruit/Adafruit PWM Servo Driver Library@2.4.1 + +build_flags = + -DARDUINO_USB_MODE=1 + -DARDUINO_USB_CDC_ON_BOOT=1 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..cb01480 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,180 @@ +#include +#include +#include +#include "esp_zigbee_core.h" + +// --- Configuration --- +#define CHANNEL_COUNT 16 // 16 servos +#define ZIGBEE_ENDPOINT_START 1 + +// PCA9685 Settings +Adafruit_PWMServoDriver pca9685 = Adafruit_PWMServoDriver(); +#define SERVO_FREQ 50 // Analog servos run at ~50 Hz +#define SERVO_MIN_PULSE 150 // Corresponds to 0 degrees +#define SERVO_MAX_PULSE 600 // Corresponds to 180 degrees +#define SERVO_OPEN_ANGLE 90 // Angle in degrees for "open" state +#define SERVO_CLOSED_ANGLE 0 // Angle in degrees for "closed" state + +// --- Zigbee Device Setup --- +// Basic Zigbee device configuration +#define ESP_ZB_PRIMARY_CHANNEL_MASK (1l << 15) // Use Zigbee channel 15 +#define ESP_ZB_MANUFACTURER_NAME "T3-Labs" +#define ESP_ZB_MODEL_NAME "ReptileFeeder.16x" + +// --- Function Declarations --- +void move_servo(uint8_t servo_num, uint8_t angle); +static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask); +static esp_err_t zb_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message); +static esp_err_t zb_action_handler(esp_zb_core_action_signal_t *signal_struct); +void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct); + +// --- Main Application --- + +void setup() +{ + Serial.begin(115200); + Wire.begin(); // Default I2C pins for ESP32-C6 are typically GPIO8/GPIO9 + + // Initialize PCA9685 + pca9685.begin(); + pca9685.setPWMFreq(SERVO_FREQ); + Serial.println("PCA9685 Initialized. Setting all servos to closed position."); + for (int i = 0; i < CHANNEL_COUNT; i++) { + move_servo(i, SERVO_CLOSED_ANGLE); + delay(50); + } + Serial.println("Servos initialized."); + + // Initialize Zigbee stack + esp_zb_cfg_t zb_cfg = ESP_ZB_DEFAULT_CFG(); + zb_cfg.esp_zb_role = ESP_ZB_DEVICE_TYPE_ROUTER; + zb_cfg.install_code_policy = false; + zb_cfg.nwk_cfg.max_children = 0; // Router doesn't need to manage children + esp_zb_init(&zb_cfg); + + // Create On/Off clusters for each servo + for (int i = 0; i < CHANNEL_COUNT; ++i) { + esp_zb_cluster_list_t *cluster_list = esp_zb_zcl_cluster_list_create(); + + // On/Off cluster + esp_zb_on_off_cluster_cfg_t on_off_cfg; + on_off_cfg.on_off = false; // Default to off + esp_zb_attribute_list_t *on_off_cluster = esp_zb_on_off_cluster_create(&on_off_cfg); + esp_zb_cluster_list_add_cluster(cluster_list, on_off_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + + // Basic cluster + esp_zb_basic_cluster_cfg_t basic_cfg; + basic_cfg.zcl_version = ESP_ZB_ZCL_VERSION_DEFAULT; + basic_cfg.power_source = ESP_ZB_ZCL_BASIC_POWER_SOURCE_UNKNOWN; + esp_zb_attribute_list_t *basic_cluster = esp_zb_basic_cluster_create(&basic_cfg); + esp_zb_cluster_list_add_cluster(cluster_list, basic_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE); + + // Create an endpoint for the servo + esp_zb_endpoint_config_t endpoint_cfg = { + .endpoint = (uint8_t)(ZIGBEE_ENDPOINT_START + i), + .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, + .app_device_id = ESP_ZB_HA_ON_OFF_LIGHT_DEVICE_ID, + .app_device_version = 0 + }; + esp_zb_ep_list_add_ep(esp_zb_get_ep_list(), cluster_list, endpoint_cfg); + } + + // Set Zigbee stack signal handler + esp_zb_set_primary_network_channel_set(ESP_ZB_PRIMARY_CHANNEL_MASK); + ESP_ERROR_CHECK(esp_zb_start(false)); + esp_zb_set_app_signal_handler(esp_zb_app_signal_handler); + + Serial.println("Zigbee Initialized and Started."); +} + +void loop() +{ + // The Zigbee stack runs in its own task. + // The loop can be used for other non-blocking tasks if needed. + delay(1000); +} + +// --- Function Definitions --- + +/** + * @brief Moves a servo to a specific angle. + * @param servo_num The servo channel (0-15). + * @param angle The target angle (0-180). + */ +void move_servo(uint8_t servo_num, uint8_t angle) +{ + if (servo_num >= CHANNEL_COUNT || angle > 180) { + return; // Invalid input + } + uint16_t pulselen = map(angle, 0, 180, SERVO_MIN_PULSE, SERVO_MAX_PULSE); + pca9685.setPWM(servo_num, 0, pulselen); +} + +/** + * @brief Zigbee application signal handler. + */ +void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct) +{ + uint32_t *p_sg_p = signal_struct->p_app_signal; + esp_err_t err_status = signal_struct->esp_err_status; + + switch (signal_struct->signal_type) { + case ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP: + Serial.println("Zigbee stack initialized"); + esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_INITIALIZATION); + break; + case ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START: + case ESP_ZB_BDB_SIGNAL_DEVICE_REBOOT: + if (err_status == ESP_OK) { + Serial.println("Device started, commissioning."); + esp_zb_bdb_start_top_level_commissioning(ESP_ZB_BDB_MODE_NETWORK_STEERING); + } else { + Serial.println("Failed to initialize Zigbee stack"); + } + break; + case ESP_ZB_BDB_SIGNAL_STEERING_COMPLETE: + if (err_status == ESP_OK) { + esp_zb_ieee_addr_t extended_pan_id; + esp_zb_get_extended_pan_id(extended_pan_id); + Serial.printf("Successfully joined network. PAN ID: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + 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]); + } else { + Serial.printf("Network steering failed, status: %d\n", err_status); + } + break; + case ESP_ZB_ZCL_SIGNAL_SET_ATTR_VALUE: + zb_attribute_handler((esp_zb_zcl_set_attr_value_message_t *)p_sg_p); + break; + default: + // Serial.printf("Received Zigbee signal: %d\n", signal_struct->signal_type); + break; + } +} + +/** + * @brief Handles incoming Zigbee attribute changes (e.g., On/Off commands). + */ +static esp_err_t zb_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message) +{ + if (message->info.status != ESP_ZB_ZCL_STATUS_SUCCESS) { + return ESP_OK; + } + + uint8_t endpoint = message->info.dst_endpoint; + uint16_t cluster_id = message->info.cluster; + uint16_t attr_id = message->attribute.id; + bool is_on = false; + + if (cluster_id == ESP_ZB_ZCL_CLUSTER_ID_ON_OFF && attr_id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID) { + is_on = *(bool *)message->attribute.data.value; + uint8_t servo_num = endpoint - ZIGBEE_ENDPOINT_START; + + Serial.printf("Endpoint %d (Servo %d) command: %s\n", endpoint, servo_num, is_on ? "ON" : "OFF"); + + if (servo_num < CHANNEL_COUNT) { + move_servo(servo_num, is_on ? SERVO_OPEN_ANGLE : SERVO_CLOSED_ANGLE); + } + } + return ESP_OK; +}