From 4c429d9ca54db9caf07bad5122fa48e5d08b2227 Mon Sep 17 00:00:00 2001
From: DTTerastar
Date: Wed, 5 Mar 2025 21:30:23 -0500
Subject: [PATCH] Save and restore LED state
---
src/LEDs.cpp | 105 +++++++++++++++++++++++++---------------
src/led/Addressable.cpp | 68 +++++++++-----------------
src/led/Addressable.h | 8 +--
src/led/LED.cpp | 67 ++++++++++++++++++++-----
src/led/LED.h | 14 ++++--
src/led/SinglePWM.cpp | 16 +-----
src/led/SinglePWM.h | 5 +-
src/mqtt.cpp | 12 ++++-
src/mqtt.h | 2 +-
9 files changed, 171 insertions(+), 126 deletions(-)
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);