Last active
December 28, 2025 18:21
-
-
Save HarvsG/5471bc770d84a2214ff2c0f0a473816f to your computer and use it in GitHub Desktop.
A ESPHome configuration for a deluminator as used by dumbledoor. It should turn out the nearest zigbee light! https://discord.com/channels/429907082951524364/1454852824650354729
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include "esp_zigbee_core.h" | |
| #include "esp_log.h" | |
| #include <string> | |
| #include <atomic> | |
| static const char *TAG = "DELUMINATOR_ZIGBEE"; | |
| static const uint8_t COORDINATOR_IEEE[8] = {0xad, 0x4a, 0x07, 0xff, 0xff, 0x2e, 0x21, 0x00}; | |
| static volatile uint8_t g_best_ieee[8] = {0}; | |
| static volatile int16_t g_max_lqi = -1; | |
| static std::atomic<bool> g_found{false}; | |
| class ZigbeeScanner { | |
| public: | |
| static void lqi_cb(const esp_zb_zdo_mgmt_lqi_rsp_t *rsp, void *user_ctx) { | |
| if (!rsp || rsp->status != 0) return; | |
| ESP_LOGI(TAG, "Table Page: %d/%d", rsp->start_index, rsp->neighbor_table_entries); | |
| esp_zb_zdo_neighbor_table_list_record_t *record = rsp->neighbor_table_list; | |
| for (int i = 0; i < rsp->neighbor_table_list_count; i++) { | |
| if (record->network_addr == 0x0000 || memcmp(record->extended_addr, COORDINATOR_IEEE, 8) == 0) { | |
| record++; continue; | |
| } | |
| char ieee_str[24]; | |
| snprintf(ieee_str, sizeof(ieee_str), "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", | |
| record->extended_addr[7], record->extended_addr[6], record->extended_addr[5], | |
| record->extended_addr[4], record->extended_addr[3], record->extended_addr[2], | |
| record->extended_addr[1], record->extended_addr[0]); | |
| ESP_LOGI(TAG, " -> Bulb: %s | LQI: %d", ieee_str, record->lqi); | |
| // We only care about the highest LQI seen across ALL pages | |
| if (record->lqi > g_max_lqi) { | |
| g_max_lqi = record->lqi; | |
| memcpy((void*)g_best_ieee, record->extended_addr, 8); | |
| g_found = true; | |
| ESP_LOGW(TAG, " *** CURRENT CLOSEST: %s (LQI: %d) ***", ieee_str, record->lqi); | |
| } | |
| record++; | |
| } | |
| uint8_t processed = rsp->start_index + rsp->neighbor_table_list_count; | |
| if (processed < rsp->neighbor_table_entries) { | |
| request_page(processed); | |
| } | |
| } | |
| static void request_page(uint8_t index) { | |
| esp_zb_zdo_mgmt_lqi_req_param_t req; | |
| req.start_index = index; | |
| req.dst_addr = esp_zb_get_short_address(); | |
| if (esp_zb_lock_acquire(pdMS_TO_TICKS(100))) { | |
| esp_zb_zdo_mgmt_lqi_req(&req, lqi_cb, NULL); | |
| esp_zb_lock_release(); | |
| } | |
| } | |
| static void trigger_fresh_scan() { | |
| ESP_LOGI(TAG, "--- REFRESHING MESH (Active Proximity Mode) ---"); | |
| g_max_lqi = -1; | |
| g_found = false; | |
| // "Stir the Mesh": Send a broadcast to an address that doesn't exist. | |
| // This forces Routers (bulbs) to check the packet, updating the LQI. | |
| esp_zb_zdo_ieee_addr_req_param_t ping; | |
| ping.addr_of_interest = 0xFFFE; // Nobody has this short address | |
| ping.request_type = 0x00; | |
| ping.start_index = 0; | |
| if (esp_zb_lock_acquire(pdMS_TO_TICKS(500))) { | |
| // Using ieee_addr_req as a broadcast ping | |
| esp_zb_zdo_ieee_addr_req(&ping, nullptr, nullptr); | |
| esp_zb_lock_release(); | |
| } | |
| // Give the radio 200ms to receive ACKs and update the Neighbor Table | |
| vTaskDelay(pdMS_TO_TICKS(200)); | |
| request_page(0); | |
| } | |
| static std::string get_strongest_ieee() { | |
| if (!g_found.load()) return ""; | |
| char buf[24]; | |
| snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", | |
| g_best_ieee[7], g_best_ieee[6], g_best_ieee[5], g_best_ieee[4], | |
| g_best_ieee[3], g_best_ieee[2], g_best_ieee[1], g_best_ieee[0]); | |
| return std::string(buf); | |
| } | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| esphome: | |
| name: "deluminator" | |
| friendly_name: "Deluminator" | |
| includes: | |
| - deluminate.h | |
| on_boot: | |
| priority: -100 # Run very late to ensure radio is stable | |
| then: | |
| - logger.log: "✨ Deluminator is stable and ready!" | |
| esp32: | |
| board: esp32-c6-devkitc-1 | |
| framework: | |
| type: esp-idf | |
| # Forces ESPHome to use our custom memory layout | |
| partitions: partitions_zigbee.csv | |
| # Helps resolve the 0x10000 overlap by explicitly | |
| # defining the variant behavior for C6 | |
| variant: esp32c6 | |
| logger: | |
| level: DEBUG | |
| hardware_uart: USB_SERIAL_JTAG | |
| external_components: | |
| - source: github://luar123/zigbee_esphome | |
| components: [zigbee] | |
| api: | |
| reboot_timeout: 1min # Increased from 0s to prevent aggressive rebooting | |
| encryption: | |
| key: "<YOUR KEY>" | |
| ota: | |
| - platform: esphome | |
| password: "<YOUR KEY>" | |
| wifi: | |
| ssid: !secret wifi_ssid | |
| password: !secret wifi_password | |
| # Waveshare C6-Zero Onboard NeoPixel (WS2812) | |
| light: | |
| - platform: esp32_rmt_led_strip | |
| id: wand_led | |
| name: "Wand RGB" | |
| pin: GPIO8 | |
| num_leds: 1 | |
| rgb_order: RGB # on some devices this is GRB | |
| chipset: WS2812 | |
| zigbee: | |
| id: zb_hub | |
| router: true # if we use end devices then it is lazy about updating signal strength | |
| script: | |
| - id: deluminate_sequence | |
| mode: restart | |
| then: | |
| - light.turn_on: | |
| id: wand_led | |
| transition_length: 0s | |
| brightness: 100% | |
| red: 0% | |
| green: 10% | |
| blue: 100% | |
| - lambda: |- | |
| ESP_LOGI("main", "Starting Deluminator scan..."); | |
| ZigbeeScanner::trigger_fresh_scan(); | |
| - delay: 1500ms # there may be room to optimise this delay for snappier performance | |
| - lambda: |- | |
| std::string target = ZigbeeScanner::get_strongest_ieee(); | |
| auto call = id(wand_led).turn_on(); | |
| call.set_transition_length(200); // Fixed: uses integer ms | |
| if (!target.empty()) { | |
| ESP_LOGI("main", "Target found! Sending IEEE %s to Home Assistant", target.c_str()); | |
| id(send_toggle_to_ha).execute(target); | |
| // Turn LED Green to indicate success | |
| call.set_rgb(0, 1, 0); | |
| } else { | |
| ESP_LOGW("main", "No Zigbee targets found in range."); | |
| // Turn LED Red to indicate failure | |
| call.set_rgb(1, 0, 0); | |
| } | |
| call.perform(); | |
| - delay: 2s | |
| - light.turn_off: wand_led | |
| - id: send_toggle_to_ha | |
| parameters: | |
| target_ieee: string | |
| then: | |
| - homeassistant.service: | |
| service: zha.issue_zigbee_cluster_command | |
| data: | |
| ieee: !lambda "return target_ieee;" | |
| endpoint_id: "1" | |
| cluster_id: "6" | |
| command: "2" | |
| command_type: "server" | |
| data_template: | |
| params: "{}" # otherwise an error is returned by ZHA | |
| button: # Button exposed to HA for easier testing | |
| - platform: template | |
| name: "Remote Delumination" | |
| icon: "mdi:auto-fix" | |
| on_press: | |
| - script.execute: deluminate_sequence | |
| binary_sensor: | |
| - platform: gpio | |
| pin: | |
| number: GPIO9 # Onboard BOOT button for Waveshare Zero | |
| inverted: true | |
| mode: INPUT_PULLUP | |
| name: "Wand Button" | |
| on_press: | |
| - script.execute: deluminate_sequence |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Name | Type | SubType | Offset | Size | Flags | |
|---|---|---|---|---|---|---|
| nvs | data | nvs | 0x9000 | 0x4000 | ||
| otadata | data | ota | 0xd000 | 0x2000 | ||
| phy_init | data | phy | 0xf000 | 0x1000 | ||
| factory | app | factory | 0x10000 | 0x250000 | ||
| zb_storage | data | fat | 16K | |||
| zb_fct | data | fat | 1K |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment