diff --git a/src/LEDs.cpp b/src/LEDs.cpp index 1b7dec5..133a4c0 100644 --- a/src/LEDs.cpp +++ b/src/LEDs.cpp @@ -21,13 +21,19 @@ int led_1_cnt = DEFAULT_LED1_CNT, led_2_cnt, led_3_cnt; ControlType led_1_cntrl = DEFAULT_LED1_CNTRL, led_2_cntrl, led_3_cntrl; std::vector leds, statusLeds, countLeds, motionLeds; bool online; +unsigned long lastSave = 0; -LED* newLed(uint8_t index, ControlType cntrl, int type, int pin, int cnt) { - if (pin == -1) return new LED(index, Control_Type_None); - if (type >= 2) - return new Addressable(index, cntrl, type - 2, pin, cnt); - else - return new SinglePWM(index, cntrl, type == 1, pin); +LED* newLed(uint8_t index, ControlType cntrl, int type, int pin, int cnt, String stateStr) { + LED* led; + if (pin == -1) { + led = new LED(index, Control_Type_None); + } else if (type >= 2) { + led = new Addressable(index, cntrl, type - 2, pin, cnt); + } else { + led = new SinglePWM(index, cntrl, type == 1, pin); + } + led->setStateString(stateStr); + return led; } void ConnectToWifi() { @@ -38,20 +44,23 @@ void ConnectToWifi() { led_1_pin = HeadlessWiFiSettings.integer("led_1_pin", -1, 39, DEFAULT_LED1_PIN, "Pin (-1 to disable)"); led_1_cnt = HeadlessWiFiSettings.integer("led_1_cnt", -1, 39, DEFAULT_LED1_CNT, "Count (only applies to Addressable LEDs)"); led_1_cntrl = (ControlType)HeadlessWiFiSettings.dropdown("led_1_cntrl", ledControlTypes, DEFAULT_LED1_CNTRL, "LED Control"); + String led_1_state = HeadlessWiFiSettings.string("led_1_state", true, "LED State"); led_2_type = HeadlessWiFiSettings.dropdown("led_2_type", ledTypes, 0, "LED Type"); led_2_pin = HeadlessWiFiSettings.integer("led_2_pin", -1, 39, -1, "Pin (-1 to disable)"); led_2_cnt = HeadlessWiFiSettings.integer("led_2_cnt", -1, 39, 1, "Count (only applies to Addressable LEDs)"); led_2_cntrl = (ControlType)HeadlessWiFiSettings.dropdown("led_2_cntrl", ledControlTypes, 0, "LED Control"); + String led_2_state = HeadlessWiFiSettings.string("led_2_state", true, "LED State"); led_3_type = HeadlessWiFiSettings.dropdown("led_3_type", ledTypes, 0, "LED Type"); led_3_pin = HeadlessWiFiSettings.integer("led_3_pin", -1, 39, -1, "Pin (-1 to disable)"); led_3_cnt = HeadlessWiFiSettings.integer("led_3_cnt", -1, 39, 1, "Count (only applies to Addressable LEDs)"); led_3_cntrl = (ControlType)HeadlessWiFiSettings.dropdown("led_3_cntrl", ledControlTypes, 0, "LED Control"); + String led_3_state = HeadlessWiFiSettings.string("led_3_state", true, "LED State"); - leds.push_back(newLed(1, led_1_cntrl, led_1_type, led_1_pin, led_1_cnt)); - leds.push_back(newLed(2, led_2_cntrl, led_2_type, led_2_pin, led_2_cnt)); - leds.push_back(newLed(3, led_3_cntrl, led_3_type, led_3_pin, led_3_cnt)); + leds.push_back(newLed(1, led_1_cntrl, led_1_type, led_1_pin, led_1_cnt, led_1_state)); + leds.push_back(newLed(2, led_2_cntrl, led_2_type, led_2_pin, led_2_cnt, led_2_state)); + leds.push_back(newLed(3, led_3_cntrl, led_3_type, led_3_pin, led_3_cnt, led_3_state)); std::copy_if(leds.begin(), leds.end(), std::back_inserter(statusLeds), [](LED* a) { return a->getControlType() == Control_Type_Status; }); std::copy_if(leds.begin(), leds.end(), std::back_inserter(countLeds), [](LED* a) { return a->getControlType() == Control_Type_Count; }); std::copy_if(leds.begin(), leds.end(), std::back_inserter(motionLeds), [](LED* a) { return a->getControlType() == Control_Type_Motion; }); @@ -66,15 +75,21 @@ bool sendState(LED* bulb) { auto slug = slugify(bulb->getName()); auto state = bulb->getState(); doc["state"] = state ? MQTT_STATE_ON_PAYLOAD : MQTT_STATE_OFF_PAYLOAD; - if (state) { - doc["brightness"] = bulb->getBrightness(); + doc["color_mode"] = bulb->hasRgbw() ? "rgbw" : bulb->hasRgb() ? "rgb": "brightness"; + doc["brightness"] = bulb->getBrightness(); + if (bulb->hasRgbw()) { + auto color = doc.createNestedObject("color"); + auto c = bulb->getColor(); + color["r"] = c.red; + color["g"] = c.green; + color["b"] = c.blue; + color["w"] = c.white; + } else if (bulb->hasRgb()) { auto color = doc.createNestedObject("color"); auto c = bulb->getColor(); color["r"] = c.red; color["g"] = c.green; color["b"] = c.blue; - // doc["white_value"] = bulb->getColor().white; - // doc["color_temp"] = bulb->getColorTemperature(); } serializeJson(doc, buffer); String setTopic = Sprintf("%s/%s", roomsTopic.c_str(), slug.c_str()); @@ -83,17 +98,30 @@ bool sendState(LED* bulb) { void Setup() { for (auto& led : leds) - led->begin(); + led->update(); +} + +void Save() { + for (auto& led : leds) + if (led->getDirty()) { + led->setDirty(false); + Serial.printf("Saving %s: %s\r\n", led->getStateFilename().c_str(), led->getStateString().c_str()); + spurt(led->getStateFilename(), led->getStateString()); + } } void Loop() { for (auto& led : leds) led->service(); + if (millis() - lastSave > 60000) { + lastSave = millis(); + Save(); + } } bool SendDiscovery() { for (auto& led : leds) - if (led->getControlType() == Control_Type_MQTT && !sendLightDiscovery(led->getName(), EC_NONE, led->hasRgb())) + if (led->getControlType() == Control_Type_MQTT && !sendLightDiscovery(led->getName(), EC_NONE, led->hasRgb(), led->hasRgbw())) return false; return true; } @@ -155,12 +183,12 @@ LED* findBulb(String& command) { return led; } } - return NULL; + return nullptr; } bool Command(String& command, String& pay) { auto bulb = findBulb(command); - if (bulb == NULL) return false; + if (bulb == nullptr) return false; DynamicJsonDocument root(pay.length() + 100); auto err = deserializeJson(root, pay); if (err) { @@ -198,31 +226,28 @@ bool Command(String& command, String& pay) { return true; } - int count = 0, lastCount = 0; - void Counting(bool added) - { - if (added) { - count++; - } else { - count--; - } - if (count != lastCount) { - lastCount = count; - for (auto& led : countLeds) - led->setState(count > 0); - } +int count = 0, lastCount = 0; +void Counting(bool added) { + if (added) { + count++; + } else { + count--; } - - void Count(unsigned int countVal) - { - count = countVal; - for (auto& led: countLeds) + if (count != lastCount) { + lastCount = count; + for (auto& led : countLeds) led->setState(count > 0); } +} - void Motion(bool pir, bool radar) - { - for (auto& led: motionLeds) - led->setState(pir || radar); - } +void Count(unsigned int countVal) { + count = countVal; + for (auto& led : countLeds) + led->setState(count > 0); +} + +void Motion(bool pir, bool radar) { + for (auto& led : motionLeds) + led->setState(pir || radar); +} } // namespace LEDs diff --git a/src/led/Addressable.cpp b/src/led/Addressable.cpp index 5d70187..1e7cfaf 100644 --- a/src/led/Addressable.cpp +++ b/src/led/Addressable.cpp @@ -22,59 +22,39 @@ neoPixelType getNeoPixelType(int type) { return NEO_GRB + NEO_KHZ800; } -void Addressable::begin() { - if (ws2812fx == NULL) { +void Addressable::update() { + if (ws2812fx == nullptr) { ws2812fx = new WS2812FX(cnt, pin, getNeoPixelType(type), 1, 1); ws2812fx->init(); - ws2812fx->setColor(255, 255, 128); - ws2812fx->setBrightness(64); ws2812fx->setMode(FX_MODE_STATIC); - ws2812fx->start(); } + + Color color = LED::getColor(); + ws2812fx->setColor(color.red, color.green, color.blue); + ws2812fx->setBrightness(mapBrightness(LED::getBrightness())); + if (LED::getState()) + ws2812fx->start(); + else + ws2812fx->stop(); } void Addressable::service() { - if (ws2812fx == NULL) begin(); - ws2812fx->service(); + if (ws2812fx != nullptr) + ws2812fx->service(); } -bool Addressable::setColor(uint8_t p_red, uint8_t p_green, uint8_t p_blue) { - if (!LED::setColor(p_red, p_green, p_blue)) return false; - if (ws2812fx == NULL) begin(); - ws2812fx->setColor(p_red, p_green, p_blue); - ws2812fx->setBrightness(LED::getBrightness()); - LED::setState(true); - return true; +uint8_t Addressable::mapBrightness(uint8_t brightness) { + // Special case for zero brightness + if (brightness == 0) return 0; + + // For non-zero values, ensure we have at least brightness level 1 + // and map the rest of the range proportionally + long const result = 1 + ((long)(brightness - 1) * (MAX_BRIGHTNESS - 1)) / 254; + + // Ensure we stay within byte range + return (uint8_t)min(result, (long)MAX_BRIGHTNESS); } -bool Addressable::setBrightness(uint8_t p_brightness) { - if (!LED::setBrightness(p_brightness)) return false; - if (ws2812fx == NULL) begin(); - ws2812fx->setBrightness(map(p_brightness, 0, 255, 0, MAX_BRIGHTNESS)); - LED::setState(p_brightness > 0); - return true; -} - -bool Addressable::setState(bool p_state) { - if (!LED::setState(p_state)) return false; - if (ws2812fx == NULL) begin(); - ws2812fx->setBrightness(map(p_state ? LED::getBrightness() : 0, 0, 255, 0, MAX_BRIGHTNESS)); - return true; -} - -bool Addressable::setWhite(uint8_t p_white) { - Serial.printf("Addressable::setWhite: p_white=%d\r\n", p_white); - if (ws2812fx == NULL) begin(); - ws2812fx->setColor(p_white, p_white, p_white); - return true; -} - -bool Addressable::setEffect(const char* p_effect) { - // ws2812fx->setMode(p_effect); - return true; -} - -bool Addressable::hasRgbw() -{ - return this->type==1 || this->type==3; +bool Addressable::hasRgbw() { + return this->type == 1 || this->type == 3; } diff --git a/src/led/Addressable.h b/src/led/Addressable.h index da1702e..01196b9 100644 --- a/src/led/Addressable.h +++ b/src/led/Addressable.h @@ -6,14 +6,9 @@ class Addressable : public LED { public: Addressable(uint8_t index, ControlType controlType, int type, int pin, int cnt); - void begin() override; + void update() override; void service() override; - bool setColor(uint8_t p_red, uint8_t p_green, uint8_t p_blue) override; - bool setWhite(uint8_t p_white) override; - bool setBrightness(uint8_t brightness) override; - bool setState(bool p_state) override; - bool setEffect(const char* p_effect) override; bool hasRgb() override { return true; } bool hasRgbw() override; @@ -23,4 +18,5 @@ class Addressable : public LED { int pin; int cnt; int cntrl; + uint8_t mapBrightness(uint8_t brightness); }; diff --git a/src/led/LED.cpp b/src/led/LED.cpp index 9e85357..b226352 100644 --- a/src/led/LED.cpp +++ b/src/led/LED.cpp @@ -2,10 +2,10 @@ #include "string_utils.h" -void LED::begin() {} +void LED::update() {} void LED::service() {} -uint8_t LED::getBrightness(void) { +uint8_t LED::getBrightness() { return brightness; } @@ -14,12 +14,12 @@ bool LED::setBrightness(uint8_t p_brightness) { if (p_brightness == brightness) return false; if (p_brightness > 0) brightness = p_brightness; - else - LED::setState(false); + dirty = true; + update(); return true; } -const Color LED::getColor(void) { +const Color LED::getColor() { return color; } @@ -27,20 +27,24 @@ bool LED::setColor(uint32_t color) { return LED::setColor((color & 0xFF0000) >> 16, (color & 0x00FF00) >> 8, (color & 0x0000FF)); } -bool LED::setColor(uint8_t p_red, uint8_t p_green, uint8_t p_blue) { - if (p_red == color.red && p_green == color.green && p_blue == color.blue) { +bool LED::setColor(uint8_t p_red, uint8_t p_green, uint8_t p_blue, uint8_t p_white) { + if (p_red == color.red && p_green == color.green && p_blue == color.blue && p_white == color.white) { return false; } // Serial.printf("LED::setColor(%d, %d, %d)\r\n", p_red, p_green, p_blue); color.red = p_red; color.green = p_green; color.blue = p_blue; + color.white = p_white; + dirty = true; + update(); return true; } bool LED::setWhite(uint8_t p_white) { - Serial.printf("LED::setWhite(%d)\r\n", p_white); - return false; + //Serial.printf("LED::setWhite(%d)\r\n", p_white); + if (!LED::setColor(255, 255, 255) && !LED::setBrightness(p_white)) return false; + return true; } uint16_t LED::getColorTemperature(void) { @@ -48,16 +52,18 @@ uint16_t LED::getColorTemperature(void) { } bool LED::setColorTemperature(uint16_t p_colorTemperature) { - Serial.printf("LED::setColorTemperature(%d)\r\n", p_colorTemperature); + //Serial.printf("LED::setColorTemperature(%d)\r\n", p_colorTemperature); + dirty = true; return false; } bool LED::setEffect(const char *p_effect) { - Serial.printf("LED::setEffect(%s)\r\n", p_effect); + //Serial.printf("LED::setEffect(%s)\r\n", p_effect); + dirty = true; return false; } -bool LED::getState(void) { +bool LED::getState() { return state; } @@ -65,6 +71,8 @@ bool LED::setState(bool p_state) { if (state == p_state) return false; // Serial.printf("LED::setState(%s)\r\n", p_state ? "true" : "false"); state = p_state; + dirty = true; + update(); return true; } @@ -80,3 +88,38 @@ LED::LED(uint8_t index, ControlType controlType) { const String LED::getId() { return Sprintf("led_%d", index); } + +const String LED::getStateFilename() { + return Sprintf("/led_%d_state", index); +} + +const String LED::getStateString() { + // Format: BBRGGBBWW (B=brightness, R=red, G=green, B=blue, W=white) + char stateStr[11]; + sprintf(stateStr, "%02X%02X%02X%02X%02X", + brightness, + color.red, + color.green, + color.blue, + color.white); + return String(stateStr); +} + +void LED::setStateString(String stateStr) { + if (stateStr.length() == 10) { + // Parse hex values - each value is 2 hex digits + uint8_t brightness = strtol(stateStr.substring(0, 2).c_str(), NULL, 16); + uint8_t r = strtol(stateStr.substring(2, 4).c_str(), NULL, 16); + uint8_t g = strtol(stateStr.substring(4, 6).c_str(), NULL, 16); + uint8_t b = strtol(stateStr.substring(6, 8).c_str(), NULL, 16); + uint8_t w = strtol(stateStr.substring(8, 10).c_str(), NULL, 16); + + if (hasRgbw()) { + setColor(r, g, b, w); + } else if (hasRgb()) { + setColor(r, g, b); + } + + setBrightness(brightness); + } +} diff --git a/src/led/LED.h b/src/led/LED.h index e994bcc..645d121 100644 --- a/src/led/LED.h +++ b/src/led/LED.h @@ -20,7 +20,7 @@ struct Color { class LED { public: LED(uint8_t index, ControlType controlType); - virtual void begin(); + virtual void update(); virtual void service(); virtual uint8_t getBrightness(void); @@ -28,7 +28,7 @@ class LED { const virtual Color getColor(void); virtual bool setColor(uint32_t color); - virtual bool setColor(uint8_t red, uint8_t green, uint8_t blue); + virtual bool setColor(uint8_t red, uint8_t green, uint8_t blue, uint8_t white = 0); virtual bool setWhite(uint8_t white); @@ -45,6 +45,13 @@ class LED { const String getId(); const String getName(); + bool getDirty() { return this->dirty; } + void setDirty(bool dirty) { this->dirty = dirty; } + + const String getStateFilename(); + const String getStateString(); + void setStateString(String encoded); + virtual bool hasRgb() { return false; } virtual bool hasRgbw() { return false; } @@ -53,5 +60,6 @@ class LED { uint8_t index; Color color = {255, 255, 128, 128}; bool state = true; - uint8_t brightness = 64; + uint8_t brightness = 128; + bool dirty = false; }; diff --git a/src/led/SinglePWM.cpp b/src/led/SinglePWM.cpp index 2ceef52..160df75 100644 --- a/src/led/SinglePWM.cpp +++ b/src/led/SinglePWM.cpp @@ -12,8 +12,8 @@ void SinglePWM::init() { ledcAttachPin(pin, getIndex()); } -void SinglePWM::begin() { - setDuty(LED::getBrightness()); +void SinglePWM::update() { + setDuty(LED::getState() ? LED::getBrightness() : 0); } void SinglePWM::setDuty(uint32_t x) { @@ -25,15 +25,3 @@ void SinglePWM::setDuty(uint32_t x) { void SinglePWM::service() { } - -bool SinglePWM::setState(bool state) { - if (!LED::setState(state)) return false; - setDuty(state ? LED::getBrightness() : 0); - return true; -} - -bool SinglePWM::setBrightness(uint8_t brightness) { - if (!LED::setBrightness(brightness)) return false; - setDuty(brightness); - return true; -} diff --git a/src/led/SinglePWM.h b/src/led/SinglePWM.h index 7aa5d75..4506c0c 100644 --- a/src/led/SinglePWM.h +++ b/src/led/SinglePWM.h @@ -6,12 +6,9 @@ class SinglePWM : public LED { public: SinglePWM(uint8_t index, ControlType controlType, bool inverted, int pin); - void begin() override; + void update() override; void service() override; - bool setState(bool state) override; - bool setBrightness(uint8_t brightness) override; - private: void init(); void setDuty(uint32_t value); diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 5392acc..cf08f77 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -192,7 +192,7 @@ bool sendNumberDiscovery(const String &name, const String &entityCategory) return pub(discoveryTopic.c_str(), 0, true, buffer.c_str()); } -bool sendLightDiscovery(const String &name, const String &entityCategory, bool rgb) +bool sendLightDiscovery(const String &name, const String &entityCategory, bool rgb, bool rgbw) { auto slug = slugify(name); @@ -204,7 +204,15 @@ bool sendLightDiscovery(const String &name, const String &entityCategory, bool r doc["stat_t"] = "~/" + slug; doc["cmd_t"] = "~/" + slug + "/set"; doc["brightness"] = true; - doc["rgb"] = rgb; + + if (rgbw) { + doc["supported_color_modes"][0] = "rgbw"; + } else if (rgb) { + doc["supported_color_modes"][0] = "rgb"; + } else { + doc["supported_color_modes"][0] = "brightness"; + } + if (!entityCategory.isEmpty()) doc["entity_category"] = entityCategory; String buffer = String(); diff --git a/src/mqtt.h b/src/mqtt.h index e93c579..fdf25a0 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -21,7 +21,7 @@ bool sendSensorDiscovery(const String &name, const String &entityCategory, const bool sendButtonDiscovery(const String &name, const String &entityCategory); bool sendSwitchDiscovery(const String &name, const String &entityCategory); bool sendNumberDiscovery(const String &name, const String &entityCategory); -bool sendLightDiscovery(const String &name, const String &entityCategory, bool rgb); +bool sendLightDiscovery(const String &name, const String &entityCategory, bool rgb, bool rgbw); bool sendDeleteDiscovery(const String &domain, const String &name);