This shows you the differences between two versions of the page.
| — |
iothings:laboratoare:2025_code:lab5_3 [2025/10/27 21:09] (current) dan.tudose created |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | <code C main.cpp> | ||
| + | // Sparrow Zigbee nodes: switch (end device) and light (router) | ||
| + | |||
| + | #include <Arduino.h> | ||
| + | #include <esp_sleep.h> | ||
| + | #include "driver/gpio.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" | ||
| + | #include "zcl/esp_zigbee_zcl_command.h" | ||
| + | #include "zcl/esp_zigbee_zcl_on_off.h" | ||
| + | |||
| + | constexpr uint8_t SWITCH_ENDPOINT = 1; | ||
| + | constexpr uint32_t COMMISSIONING_AWAKE_MS = 15000; | ||
| + | constexpr uint32_t POST_COMMAND_HOLD_MS = 250; | ||
| + | constexpr uint32_t BUTTON_RELEASE_TIMEOUT_MS = 5000; | ||
| + | |||
| + | ZigbeeSwitch switchEp(SWITCH_ENDPOINT); | ||
| + | |||
| + | RTC_DATA_ATTR bool switchIsOn = false; | ||
| + | |||
| + | static void waitForButtonRelease() { | ||
| + | uint32_t start = millis(); | ||
| + | while (digitalRead(SWITCH_PIN) == LOW) { | ||
| + | uint32_t heldMs = millis() - start; | ||
| + | if (heldMs >= FACTORY_RESET_HOLD_MS) { | ||
| + | Serial.println("Factory reset Zigbee (switch)..."); | ||
| + | Zigbee.factoryReset(true); // does not return | ||
| + | } | ||
| + | if (heldMs >= BUTTON_RELEASE_TIMEOUT_MS) { | ||
| + | break; | ||
| + | } | ||
| + | delay(10); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | static void enterDeepSleep(uint32_t graceMs = 0) { | ||
| + | if (graceMs > 0) { | ||
| + | delay(graceMs); | ||
| + | } | ||
| + | Serial.println("Switch entering deep sleep…"); | ||
| + | Serial.flush(); | ||
| + | esp_deep_sleep_start(); | ||
| + | } | ||
| + | |||
| + | static bool sendOnOffCommand(bool turnOn) { | ||
| + | esp_zb_zcl_on_off_cmd_t cmd_req = {}; | ||
| + | cmd_req.address_mode = ESP_ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT; | ||
| + | cmd_req.zcl_basic_cmd.src_endpoint = SWITCH_ENDPOINT; | ||
| + | cmd_req.on_off_cmd_id = turnOn ? ESP_ZB_ZCL_CMD_ON_OFF_ON_ID : ESP_ZB_ZCL_CMD_ON_OFF_OFF_ID; | ||
| + | |||
| + | esp_zb_lock_acquire(portMAX_DELAY); | ||
| + | esp_err_t err = esp_zb_zcl_on_off_cmd_req(&cmd_req); | ||
| + | esp_zb_lock_release(); | ||
| + | if (err != ESP_OK) { | ||
| + | Serial.printf("Failed to send on/off command: 0x%x\n", err); | ||
| + | return false; | ||
| + | } | ||
| + | return true; | ||
| + | } | ||
| + | |||
| + | void setup() { | ||
| + | Serial.begin(115200); | ||
| + | delay(20); | ||
| + | pinMode(SWITCH_PIN, INPUT_PULLUP); | ||
| + | |||
| + | esp_err_t pdCfg = esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); | ||
| + | if (pdCfg != ESP_OK) { | ||
| + | Serial.printf("RTC peripheral power config failed: 0x%x\n", pdCfg); | ||
| + | } | ||
| + | esp_err_t wakeCfg = esp_deep_sleep_enable_gpio_wakeup(1ULL << SWITCH_PIN, ESP_GPIO_WAKEUP_GPIO_LOW); | ||
| + | if (wakeCfg != ESP_OK) { | ||
| + | Serial.printf("Failed to enable EXT0 wakeup: 0x%x\n", wakeCfg); | ||
| + | } | ||
| + | |||
| + | 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"); | ||
| + | uint32_t joinStart = millis(); | ||
| + | while (!Zigbee.connected()) { | ||
| + | Serial.print("."); | ||
| + | delay(200); | ||
| + | if (millis() - joinStart > 20000) { | ||
| + | Serial.println("\nJoin timeout, restarting…"); | ||
| + | ESP.restart(); | ||
| + | } | ||
| + | } | ||
| + | Serial.println("\nJoined!"); | ||
| + | |||
| + | esp_sleep_wakeup_cause_t wakeCause = esp_sleep_get_wakeup_cause(); | ||
| + | bool wokeFromButton = (wakeCause == ESP_SLEEP_WAKEUP_EXT0); | ||
| + | |||
| + | if (wokeFromButton) { | ||
| + | switchIsOn = !switchIsOn; | ||
| + | Serial.printf("Switch state → %s\n", switchIsOn ? "ON" : "OFF"); | ||
| + | if (sendOnOffCommand(switchIsOn)) { | ||
| + | Serial.println("On/Off command sent."); | ||
| + | } | ||
| + | waitForButtonRelease(); | ||
| + | enterDeepSleep(POST_COMMAND_HOLD_MS); | ||
| + | } else { | ||
| + | Serial.println("Cold boot: staying awake for commissioning window."); | ||
| + | delay(COMMISSIONING_AWAKE_MS); | ||
| + | waitForButtonRelease(); | ||
| + | enterDeepSleep(); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | void loop() { | ||
| + | enterDeepSleep(); | ||
| + | } | ||
| + | |||
| + | #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 | ||
| + | |||
| + | </code> | ||