// Sparrow Zigbee nodes: switch (end device) and light (router) #include <Arduino.h> #include "Zigbee.h" constexpr uint8_t SWITCH_PIN = 9; constexpr uint32_t FACTORY_RESET_HOLD_MS = 3000; constexpr uint32_t BUTTON_DEBOUNCE_MS = 50; #if defined(SPARROW_SWITCH) #include "ep/ZigbeeSwitch.h" constexpr uint8_t SWITCH_ENDPOINT = 1; ZigbeeSwitch switchEp(SWITCH_ENDPOINT); static bool lastButtonLevel = HIGH; static uint32_t lastButtonChangeMs = 0; static uint32_t pressStartMs = 0; static bool longPressHandled = false; void setup() { Serial.begin(115200); pinMode(SWITCH_PIN, INPUT_PULLUP); switchEp.setManufacturerAndModel("Sparrow", "Switch"); switchEp.setPowerSource(ZB_POWER_SOURCE_BATTERY, 100); Zigbee.addEndpoint(&switchEp); Serial.println("Starting Zigbee (switch)..."); if (!Zigbee.begin()) { Serial.println("Zigbee failed to start, restarting…"); delay(1000); ESP.restart(); } Serial.print("Joining network"); while (!Zigbee.connected()) { Serial.print("."); delay(200); } Serial.println("\nJoined!"); } void loop() { uint32_t now = millis(); bool reading = digitalRead(SWITCH_PIN); if (reading != lastButtonLevel && (now - lastButtonChangeMs) >= BUTTON_DEBOUNCE_MS) { lastButtonChangeMs = now; lastButtonLevel = reading; if (reading == LOW) { pressStartMs = now; longPressHandled = false; } else { if (pressStartMs != 0 && !longPressHandled) { Serial.println("Switch pressed → toggling bound light"); switchEp.lightToggle(); } pressStartMs = 0; } } if (lastButtonLevel == LOW && pressStartMs != 0 && !longPressHandled && (now - pressStartMs) >= FACTORY_RESET_HOLD_MS) { longPressHandled = true; Serial.println("Factory reset Zigbee (switch)..."); Zigbee.factoryReset(true); } delay(10); } #elif defined(SPARROW_LIGHT) #include <Adafruit_NeoPixel.h> #include "ep/ZigbeeLight.h" constexpr uint8_t LIGHT_ENDPOINT = 1; constexpr uint8_t NEOPIXEL_PIN = 3; constexpr uint8_t NEOPIXEL_COUNT = 1; constexpr uint8_t NEOPIXEL_BRIGHTNESS = 48; constexpr uint32_t IDENTIFY_BLINK_INTERVAL_MS = 250; constexpr uint32_t IDENTIFY_DEFAULT_DURATION_MS = 5000; ZigbeeLight lightEp(LIGHT_ENDPOINT); Adafruit_NeoPixel statusPixel(NEOPIXEL_COUNT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800); static bool lightState = false; static bool lastButtonLevel = HIGH; static uint32_t lastButtonChangeMs = 0; static uint32_t pressStartMs = 0; static bool longPressHandled = false; static bool identifyActive = false; static bool identifyBlinkOn = false; static bool identifyReset = false; static uint32_t identifyDeadlineMs = 0; static uint32_t identifyLastToggleMs = 0; static void refreshNeoPixel() { if (identifyActive) { if (identifyBlinkOn) { statusPixel.setPixelColor(0, statusPixel.Color(0, 0, 255)); // blue during identify } else { statusPixel.clear(); } } else { if (lightState) { statusPixel.setPixelColor(0, statusPixel.Color(255, 200, 80)); // warm white } else { statusPixel.clear(); } } statusPixel.show(); } static void handleLightChange(bool on) { lightState = on; if (!identifyActive) { refreshNeoPixel(); } } static void handleIdentify(uint16_t seconds) { if (seconds == 0) { identifyActive = false; identifyReset = true; return; } uint32_t durationMs = seconds > 0 ? static_cast<uint32_t>(seconds) * 1000UL : IDENTIFY_DEFAULT_DURATION_MS; if (durationMs == 0) { durationMs = IDENTIFY_DEFAULT_DURATION_MS; } identifyDeadlineMs = millis() + durationMs; identifyLastToggleMs = 0; identifyBlinkOn = false; identifyActive = true; identifyReset = true; } void setup() { Serial.begin(115200); pinMode(SWITCH_PIN, INPUT_PULLUP); statusPixel.begin(); statusPixel.setBrightness(NEOPIXEL_BRIGHTNESS); statusPixel.clear(); statusPixel.show(); lightEp.setManufacturerAndModel("Sparrow", "Light"); lightEp.onLightChange(handleLightChange); lightEp.onIdentify(handleIdentify); Zigbee.addEndpoint(&lightEp); Serial.println("Starting Zigbee (light)..."); if (!Zigbee.begin()) { Serial.println("Zigbee failed to start, restarting…"); delay(1000); ESP.restart(); } Serial.print("Joining network"); while (!Zigbee.connected()) { Serial.print("."); delay(200); } Serial.println("\nJoined!"); lightEp.setLight(false); } void loop() { uint32_t now = millis(); if (identifyReset) { identifyReset = false; refreshNeoPixel(); } if (identifyActive) { if ((int32_t)(identifyDeadlineMs - now) <= 0) { identifyActive = false; refreshNeoPixel(); } else if (identifyLastToggleMs == 0 || (now - identifyLastToggleMs) >= IDENTIFY_BLINK_INTERVAL_MS) { identifyLastToggleMs = now; identifyBlinkOn = !identifyBlinkOn; refreshNeoPixel(); } } bool reading = digitalRead(SWITCH_PIN); if (reading != lastButtonLevel && (now - lastButtonChangeMs) >= BUTTON_DEBOUNCE_MS) { lastButtonChangeMs = now; lastButtonLevel = reading; if (reading == LOW) { pressStartMs = now; longPressHandled = false; } else { if (pressStartMs != 0 && !longPressHandled) { bool newState = !lightState; Serial.printf("Local light toggle → %s\n", newState ? "ON" : "OFF"); lightEp.setLight(newState); } pressStartMs = 0; } } if (lastButtonLevel == LOW && pressStartMs != 0 && !longPressHandled && (now - pressStartMs) >= FACTORY_RESET_HOLD_MS) { longPressHandled = true; identifyActive = false; identifyReset = true; Serial.println("Factory reset Zigbee (light)..."); Zigbee.factoryReset(true); } delay(10); } #else #error "Define SPARROW_SWITCH or SPARROW_LIGHT for this firmware." #endif