diff --git a/usermods/seven_segment_display_reloaded/readme.md b/usermods/seven_segment_display_reloaded/readme.md deleted file mode 100644 index 94788df7e4..0000000000 --- a/usermods/seven_segment_display_reloaded/readme.md +++ /dev/null @@ -1,132 +0,0 @@ -# Seven Segment Display Reloaded - -Uses the overlay feature to create a configurable seven segment display. -Optimized for maximum configurability and use with seven segment clocks by parallyze (https://www.instructables.com/member/parallyze/instructables/) -Very loosely based on the existing usermod "seven segment display". - - -## Installation - -Add the compile-time option `-D USERMOD_SSDR` to your `platformio.ini` (or `platformio_override.ini`) or use `#define USERMOD_SSDR` in `my_config.h`. - -For the auto brightness option, the usermod SN_Photoresistor or BH1750_V2 has to be installed as well. See SN_Photoresistor/readme.md or BH1750_V2/readme.md for instructions. - -## Settings -All settings can be controlled via the usermod settings page. -Part of the settings can be controlled through MQTT with a raw payload or through a json request to /json/state. - -### enabled -Enables/disables this usermod - -### inverted -Enables the inverted mode in which the background should be enabled and the digits should be black (LEDs off) - -### Colon-blinking -Enables the blinking colon(s) if they are defined - -### Leading-Zero -Shows the leading zero of the hour if it exists (i.e. shows `07` instead of `7`) - -### enable-auto-brightness -Enables the auto brightness feature. Can be used only when the usermods SN_Photoresistor or BH1750_V2 are installed. - -### auto-brightness-min / auto-brightness-max -The lux value calculated from usermod SN_Photoresistor or BH1750_V2 will be mapped to the values defined here. -The mapping, 0 - 1000 lux, will be mapped to auto-brightness-min and auto-brightness-max - -WLED current protection will override the calculated value if it is too high. - -### Display-Mask -Defines the type of the time/date display. -For example "H:m" (default) -- H - 00-23 hours -- h - 01-12 hours -- k - 01-24 hours -- m - 00-59 minutes -- s - 00-59 seconds -- d - 01-31 day of month -- M - 01-12 month -- y - 21 last two positions of year -- Y - 2021 year -- : for a colon - -### LED-Numbers -- LED-Numbers-Hours -- LED-Numbers-Minutes -- LED-Numbers-Seconds -- LED-Numbers-Colons -- LED-Numbers-Day -- LED-Numbers-Month -- LED-Numbers-Year - -See following example for usage. - - -## Example - -Example of an LED definition: -``` - < A > -/\ /\ -F B -\/ \/ - < G > -/\ /\ -E C -\/ \/ - < D > -``` - -LEDs or Range of LEDs are separated by a comma "," - -Segments are separated by a semicolon ";" and are read as A;B;C;D;E;F;G - -Digits are separated by colon ":" -> A;B;C;D;E;F;G:A;B;C;D;E;F;G - -Ranges are defined as lower to higher (lower first) - -For example, a clock definition for the following clock (https://www.instructables.com/Lazy-7-Quick-Build-Edition/) is - -- hour "59,46;47-48;50-51;52-53;54-55;57-58;49,56:0,13;1-2;4-5;6-7;8-9;11-12;3,10" - -- minute "37-38;39-40;42-43;44,31;32-33;35-36;34,41:21-22;23-24;26-27;28,15;16-17;19-20;18,25" - -or - -- hour "6,7;8,9;11,12;13,0;1,2;4,5;3,10:52,53;54,55;57,58;59,46;47,48;50,51;49,56" - -- minute "15,28;16,17;19,20;21,22;23,24;26,27;18,25:31,44;32,33;35,36;37,38;39,40;42,43;34,41" - -depending on the orientation. - -# Example details: -hour "59,46;47-48;50-51;52-53;54-55;57-58;49,56:0,13;1-2;4-5;6-7;8-9;11-12;3,10" - -there are two digits separated by ":" - -- 59,46;47-48;50-51;52-53;54-55;57-58;49,56 -- 0,13;1-2;4-5;6-7;8-9;11-12;3,10 - -In the first digit, -the **segment A** consists of the LEDs number **59 and 46**., **segment B** consists of the LEDs number **47, 48** and so on - -The second digit starts again with **segment A** and LEDs **0 and 13**, **segment B** consists of the LEDs number **1 and 2** and so on - -### first digit of the hour -- Segment A: 59, 46 -- Segment B: 47, 48 -- Segment C: 50, 51 -- Segment D: 52, 53 -- Segment E: 54, 55 -- Segment F: 57, 58 -- Segment G: 49, 56 - -### second digit of the hour - -- Segment A: 0, 13 -- Segment B: 1, 2 -- Segment C: 4, 5 -- Segment D: 6, 7 -- Segment E: 8, 9 -- Segment F: 11, 12 -- Segment G: 3, 10 diff --git a/usermods/seven_segment_display_reloaded/seven_segment_display_reloaded.cpp b/usermods/seven_segment_display_reloaded/seven_segment_display_reloaded.cpp deleted file mode 100644 index 893e061bc8..0000000000 --- a/usermods/seven_segment_display_reloaded/seven_segment_display_reloaded.cpp +++ /dev/null @@ -1,603 +0,0 @@ -#include "wled.h" -#ifdef USERMOD_SN_PHOTORESISTOR - #include "SN_Photoresistor.h" -#endif -#ifdef USERMOD_BH1750 - #include "BH1750_v2.h" -#endif - -#ifdef WLED_DISABLE_MQTT -#error "This user mod requires MQTT to be enabled." -#endif - -class UsermodSSDR : public Usermod { - -//#define REFRESHTIME 497 - -private: - //Runtime variables. - unsigned long umSSDRLastRefresh = 0; - unsigned long umSSDRResfreshTime = 3000; - bool umSSDRDisplayTime = false; - bool umSSDRInverted = false; - bool umSSDRColonblink = true; - bool umSSDRLeadingZero = false; - bool umSSDREnableLDR = false; - String umSSDRHours = ""; - String umSSDRMinutes = ""; - String umSSDRSeconds = ""; - String umSSDRColons = ""; - String umSSDRDays = ""; - String umSSDRMonths = ""; - String umSSDRYears = ""; - uint16_t umSSDRLength = 0; - uint16_t umSSDRBrightnessMin = 0; - uint16_t umSSDRBrightnessMax = 255; - - bool* umSSDRMask = 0; - - /*// H - 00-23 hours - // h - 01-12 hours - // k - 01-24 hours - // m - 00-59 minutes - // s - 00-59 seconds - // d - 01-31 day of month - // M - 01-12 month - // y - 21 last two positions of year - // Y - 2021 year - // : for a colon - */ - String umSSDRDisplayMask = "H:m"; //This should reflect physical equipment. - - /* Segment order, seen from the front: - - < A > - /\ /\ - F B - \/ \/ - < G > - /\ /\ - E C - \/ \/ - < D > - - */ - - uint8_t umSSDRNumbers[11][7] = { - // A B C D E F G - { 1, 1, 1, 1, 1, 1, 0 }, // 0 - { 0, 1, 1, 0, 0, 0, 0 }, // 1 - { 1, 1, 0, 1, 1, 0, 1 }, // 2 - { 1, 1, 1, 1, 0, 0, 1 }, // 3 - { 0, 1, 1, 0, 0, 1, 1 }, // 4 - { 1, 0, 1, 1, 0, 1, 1 }, // 5 - { 1, 0, 1, 1, 1, 1, 1 }, // 6 - { 1, 1, 1, 0, 0, 0, 0 }, // 7 - { 1, 1, 1, 1, 1, 1, 1 }, // 8 - { 1, 1, 1, 1, 0, 1, 1 }, // 9 - { 0, 0, 0, 0, 0, 0, 0 } // blank - }; - - //String to reduce flash memory usage - static const char _str_name[]; - static const char _str_ldrEnabled[]; - static const char _str_timeEnabled[]; - static const char _str_inverted[]; - static const char _str_colonblink[]; - static const char _str_leadingZero[]; - static const char _str_displayMask[]; - static const char _str_hours[]; - static const char _str_minutes[]; - static const char _str_seconds[]; - static const char _str_colons[]; - static const char _str_days[]; - static const char _str_months[]; - static const char _str_years[]; - static const char _str_minBrightness[]; - static const char _str_maxBrightness[]; - -#ifdef USERMOD_SN_PHOTORESISTOR - Usermod_SN_Photoresistor *ptr; -#else - void* ptr = nullptr; -#endif -#ifdef USERMOD_BH1750 - Usermod_BH1750* bh1750 = nullptr; -#else - void* bh1750 = nullptr; -#endif - - void _overlaySevenSegmentDraw() { - int displayMaskLen = static_cast(umSSDRDisplayMask.length()); - bool colonsDone = false; - _setAllFalse(); - for (int index = 0; index < displayMaskLen; index++) { - int timeVar = 0; - switch (umSSDRDisplayMask[index]) { - case 'h': - timeVar = hourFormat12(localTime); - _showElements(&umSSDRHours, timeVar, 0, !umSSDRLeadingZero); - break; - case 'H': - timeVar = hour(localTime); - _showElements(&umSSDRHours, timeVar, 0, !umSSDRLeadingZero); - break; - case 'k': - timeVar = hour(localTime) + 1; - _showElements(&umSSDRHours, timeVar, 0, !umSSDRLeadingZero); - break; - case 'm': - timeVar = minute(localTime); - _showElements(&umSSDRMinutes, timeVar, 0, 0); - break; - case 's': - timeVar = second(localTime); - _showElements(&umSSDRSeconds, timeVar, 0, 0); - break; - case 'd': - timeVar = day(localTime); - _showElements(&umSSDRDays, timeVar, 0, 0); - break; - case 'M': - timeVar = month(localTime); - _showElements(&umSSDRMonths, timeVar, 0, 0); - break; - case 'y': - timeVar = second(localTime); - _showElements(&umSSDRYears, timeVar, 0, 0); - break; - case 'Y': - timeVar = year(localTime); - _showElements(&umSSDRYears, timeVar, 0, 0); - break; - case ':': - if (!colonsDone) { // only call _setColons once as all colons are printed when the first colon is found - _setColons(); - colonsDone = true; - } - break; - } - } - _setMaskToLeds(); - } - - void _setColons() { - if ( umSSDRColonblink ) { - if ( second(localTime) % 2 == 0 ) { - _showElements(&umSSDRColons, 0, 1, 0); - } - } else { - _showElements(&umSSDRColons, 0, 1, 0); - } - } - - void _showElements(String *map, int timevar, bool isColon, bool removeZero - -) { - if ((map != nullptr) && (*map != nullptr) && !(*map).equals("")) { - int length = String(timevar).length(); - bool addZero = false; - if (length == 1) { - length = 2; - addZero = true; - } - int timeArr[length]; - if(addZero) { - if(removeZero) - { - timeArr[1] = 10; - timeArr[0] = timevar; - } - else - { - timeArr[1] = 0; - timeArr[0] = timevar; - } - } else { - int count = 0; - while (timevar) { - timeArr[count] = timevar%10; - timevar /= 10; - count++; - }; - } - - - int colonsLen = static_cast((*map).length()); - int count = 0; - int countSegments = 0; - int countDigit = 0; - bool range = false; - int lastSeenLedNr = 0; - - for (int index = 0; index < colonsLen; index++) { - switch ((*map)[index]) { - case '-': - lastSeenLedNr = _checkForNumber(count, index, map); - count = 0; - range = true; - break; - case ':': - _setLeds(_checkForNumber(count, index, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon); - count = 0; - range = false; - countDigit++; - countSegments = 0; - break; - case ';': - _setLeds(_checkForNumber(count, index, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon); - count = 0; - range = false; - countSegments++; - break; - case ',': - _setLeds(_checkForNumber(count, index, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon); - count = 0; - range = false; - break; - default: - count++; - break; - } - } - _setLeds(_checkForNumber(count, colonsLen, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon); - } - } - - void _setLeds(int lednr, int lastSeenLedNr, bool range, int countSegments, int number, bool colon) { - if ((lednr < 0) || (lednr >= umSSDRLength)) return; // prevent array bounds violation - - if (!(colon && umSSDRColonblink) && ((number < 0) || (countSegments < 0))) return; - if ((colon && umSSDRColonblink) || umSSDRNumbers[number][countSegments]) { - - if (range) { - for(int i = max(0, lastSeenLedNr); i <= lednr; i++) { - umSSDRMask[i] = true; - } - } else { - umSSDRMask[lednr] = true; - } - } - } - - void _setMaskToLeds() { - for(int i = 0; i <= umSSDRLength; i++) { - if ((!umSSDRInverted && !umSSDRMask[i]) || (umSSDRInverted && umSSDRMask[i])) { - strip.setPixelColor(i, 0x000000); - } - } - } - - void _setAllFalse() { - for(int i = 0; i <= umSSDRLength; i++) { - umSSDRMask[i] = false; - } - } - - int _checkForNumber(int count, int index, String *map) { - String number = (*map).substring(index - count, index); - return number.toInt(); - } - - void _publishMQTTint_P(const char *subTopic, int value) - { - if(mqtt == NULL) return; - - char buffer[64]; - char valBuffer[12]; - sprintf_P(buffer, PSTR("%s/%S/%S"), mqttDeviceTopic, _str_name, subTopic); - sprintf_P(valBuffer, PSTR("%d"), value); - mqtt->publish(buffer, 2, true, valBuffer); - } - - void _publishMQTTstr_P(const char *subTopic, String Value) - { - if(mqtt == NULL) return; - char buffer[64]; - sprintf_P(buffer, PSTR("%s/%S/%S"), mqttDeviceTopic, _str_name, subTopic); - mqtt->publish(buffer, 2, true, Value.c_str(), Value.length()); - } - - bool _cmpIntSetting_P(char *topic, char *payload, const char *setting, void *value) - { - if (strcmp_P(topic, setting) == 0) - { - *((int *)value) = strtol(payload, NULL, 10); - _publishMQTTint_P(setting, *((int *)value)); - return true; - } - return false; - } - - bool _handleSetting(char *topic, char *payload) { - if (_cmpIntSetting_P(topic, payload, _str_timeEnabled, &umSSDRDisplayTime)) { - return true; - } - if (_cmpIntSetting_P(topic, payload, _str_ldrEnabled, &umSSDREnableLDR)) { - return true; - } - if (_cmpIntSetting_P(topic, payload, _str_inverted, &umSSDRInverted)) { - return true; - } - if (_cmpIntSetting_P(topic, payload, _str_colonblink, &umSSDRColonblink)) { - return true; - } - if (_cmpIntSetting_P(topic, payload, _str_leadingZero, &umSSDRLeadingZero)) { - return true; - } - if (strcmp_P(topic, _str_displayMask) == 0) { - umSSDRDisplayMask = String(payload); - _publishMQTTstr_P(_str_displayMask, umSSDRDisplayMask); - return true; - } - return false; - } - - void _updateMQTT() - { - _publishMQTTint_P(_str_timeEnabled, umSSDRDisplayTime); - _publishMQTTint_P(_str_ldrEnabled, umSSDREnableLDR); - _publishMQTTint_P(_str_inverted, umSSDRInverted); - _publishMQTTint_P(_str_colonblink, umSSDRColonblink); - _publishMQTTint_P(_str_leadingZero, umSSDRLeadingZero); - - _publishMQTTstr_P(_str_hours, umSSDRHours); - _publishMQTTstr_P(_str_minutes, umSSDRMinutes); - _publishMQTTstr_P(_str_seconds, umSSDRSeconds); - _publishMQTTstr_P(_str_colons, umSSDRColons); - _publishMQTTstr_P(_str_days, umSSDRDays); - _publishMQTTstr_P(_str_months, umSSDRMonths); - _publishMQTTstr_P(_str_years, umSSDRYears); - _publishMQTTstr_P(_str_displayMask, umSSDRDisplayMask); - - _publishMQTTint_P(_str_minBrightness, umSSDRBrightnessMin); - _publishMQTTint_P(_str_maxBrightness, umSSDRBrightnessMax); - } - - void _addJSONObject(JsonObject& root) { - JsonObject ssdrObj = root[FPSTR(_str_name)]; - if (ssdrObj.isNull()) { - ssdrObj = root.createNestedObject(FPSTR(_str_name)); - } - - ssdrObj[FPSTR(_str_timeEnabled)] = umSSDRDisplayTime; - ssdrObj[FPSTR(_str_ldrEnabled)] = umSSDREnableLDR; - ssdrObj[FPSTR(_str_inverted)] = umSSDRInverted; - ssdrObj[FPSTR(_str_colonblink)] = umSSDRColonblink; - ssdrObj[FPSTR(_str_leadingZero)] = umSSDRLeadingZero; - ssdrObj[FPSTR(_str_displayMask)] = umSSDRDisplayMask; - ssdrObj[FPSTR(_str_hours)] = umSSDRHours; - ssdrObj[FPSTR(_str_minutes)] = umSSDRMinutes; - ssdrObj[FPSTR(_str_seconds)] = umSSDRSeconds; - ssdrObj[FPSTR(_str_colons)] = umSSDRColons; - ssdrObj[FPSTR(_str_days)] = umSSDRDays; - ssdrObj[FPSTR(_str_months)] = umSSDRMonths; - ssdrObj[FPSTR(_str_years)] = umSSDRYears; - ssdrObj[FPSTR(_str_minBrightness)] = umSSDRBrightnessMin; - ssdrObj[FPSTR(_str_maxBrightness)] = umSSDRBrightnessMax; - } - -public: - //Functions called by WLED - - /* - * setup() is called once at boot. WiFi is not yet connected at this point. - * You can use it to initialize variables, sensors or similar. - */ - void setup() { - umSSDRLength = strip.getLengthTotal(); - if (umSSDRMask != 0) { - umSSDRMask = (bool*) realloc(umSSDRMask, umSSDRLength * sizeof(bool)); - } else { - umSSDRMask = (bool*) malloc(umSSDRLength * sizeof(bool)); - } - _setAllFalse(); - - #ifdef USERMOD_SN_PHOTORESISTOR - ptr = (Usermod_SN_Photoresistor*) UsermodManager::lookup(USERMOD_ID_SN_PHOTORESISTOR); - #endif - #ifdef USERMOD_BH1750 - bh1750 = (Usermod_BH1750*) UsermodManager::lookup(USERMOD_ID_BH1750); - #endif - DEBUG_PRINTLN(F("Setup done")); - } - - /* - * loop() is called continuously. Here you can check for events, read sensors, etc. - */ - void loop() { - if (!umSSDRDisplayTime || strip.isUpdating()) { - return; - } - #ifdef USERMOD_SN_PHOTORESISTOR - if(bri != 0 && umSSDREnableLDR && (millis() - umSSDRLastRefresh > umSSDRResfreshTime)) { - if (ptr != nullptr) { - uint16_t lux = ptr->getLastLDRValue(); - uint16_t brightness = map(lux, 0, 1000, umSSDRBrightnessMin, umSSDRBrightnessMax); - if (bri != brightness) { - bri = brightness; - stateUpdated(1); - } - } - umSSDRLastRefresh = millis(); - } - #endif - #ifdef USERMOD_BH1750 - if(bri != 0 && umSSDREnableLDR && (millis() - umSSDRLastRefresh > umSSDRResfreshTime)) { - if (bh1750 != nullptr) { - float lux = bh1750->getIlluminance(); - uint16_t brightness = map(lux, 0, 1000, umSSDRBrightnessMin, umSSDRBrightnessMax); - if (bri != brightness) { - DEBUG_PRINTF("Adjusting brightness based on lux value: %.2f lx, new brightness: %d\n", lux, brightness); - bri = brightness; - stateUpdated(1); - } - } - umSSDRLastRefresh = millis(); - } - #endif - } - - void handleOverlayDraw() { - if (umSSDRDisplayTime) { - _overlaySevenSegmentDraw(); - } - } - -/* - * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. - * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. - * Below it is shown how this could be used for e.g. a light sensor - */ - void addToJsonInfo(JsonObject& root) { - JsonObject user = root[F("u")]; - if (user.isNull()) { - user = root.createNestedObject(F("u")); - } - JsonArray enabled = user.createNestedArray("Time enabled"); - enabled.add(umSSDRDisplayTime); - JsonArray invert = user.createNestedArray("Time inverted"); - invert.add(umSSDRInverted); - JsonArray blink = user.createNestedArray("Blinking colon"); - blink.add(umSSDRColonblink); - JsonArray zero = user.createNestedArray("Show the hour leading zero"); - zero.add(umSSDRLeadingZero); - JsonArray ldrEnable = user.createNestedArray("Auto Brightness enabled"); - ldrEnable.add(umSSDREnableLDR); - - } - - /* - * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ - void addToJsonState(JsonObject& root) { - JsonObject user = root[F("u")]; - if (user.isNull()) { - user = root.createNestedObject(F("u")); - } - _addJSONObject(user); - } - - /* - * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ - void readFromJsonState(JsonObject& root) { - JsonObject user = root[F("u")]; - if (!user.isNull()) { - JsonObject ssdrObj = user[FPSTR(_str_name)]; - umSSDRDisplayTime = ssdrObj[FPSTR(_str_timeEnabled)] | umSSDRDisplayTime; - umSSDREnableLDR = ssdrObj[FPSTR(_str_ldrEnabled)] | umSSDREnableLDR; - umSSDRInverted = ssdrObj[FPSTR(_str_inverted)] | umSSDRInverted; - umSSDRColonblink = ssdrObj[FPSTR(_str_colonblink)] | umSSDRColonblink; - umSSDRLeadingZero = ssdrObj[FPSTR(_str_leadingZero)] | umSSDRLeadingZero; - umSSDRDisplayMask = ssdrObj[FPSTR(_str_displayMask)] | umSSDRDisplayMask; - } - } - - void onMqttConnect(bool sessionPresent) { - char subBuffer[48]; - if (mqttDeviceTopic[0] != 0) - { - _updateMQTT(); - //subscribe for sevenseg messages on the device topic - sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttDeviceTopic, _str_name); - mqtt->subscribe(subBuffer, 2); - } - - if (mqttGroupTopic[0] != 0) - { - //subscribe for sevenseg messages on the group topic - sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttGroupTopic, _str_name); - mqtt->subscribe(subBuffer, 2); - } - } - - bool onMqttMessage(char *topic, char *payload) { - //If topic begins with sevenSeg cut it off, otherwise not our message. - size_t topicPrefixLen = strlen_P(PSTR("/wledSS/")); - if (strncmp_P(topic, PSTR("/wledSS/"), topicPrefixLen) == 0) { - topic += topicPrefixLen; - } else { - return false; - } - //We only care if the topic ends with /set - size_t topicLen = strlen(topic); - if (topicLen > 4 && - topic[topicLen - 4] == '/' && - topic[topicLen - 3] == 's' && - topic[topicLen - 2] == 'e' && - topic[topicLen - 1] == 't') - { - //Trim /set and handle it - topic[topicLen - 4] = '\0'; - _handleSetting(topic, payload); - } - return true; - } - - void addToConfig(JsonObject &root) { - _addJSONObject(root); - } - - bool readFromConfig(JsonObject &root) { - JsonObject top = root[FPSTR(_str_name)]; - - if (top.isNull()) { - DEBUG_PRINT(FPSTR(_str_name)); - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); - return false; - } - - umSSDRDisplayTime = (top[FPSTR(_str_timeEnabled)] | umSSDRDisplayTime); - umSSDREnableLDR = (top[FPSTR(_str_ldrEnabled)] | umSSDREnableLDR); - umSSDRInverted = (top[FPSTR(_str_inverted)] | umSSDRInverted); - umSSDRColonblink = (top[FPSTR(_str_colonblink)] | umSSDRColonblink); - umSSDRLeadingZero = (top[FPSTR(_str_leadingZero)] | umSSDRLeadingZero); - - umSSDRDisplayMask = top[FPSTR(_str_displayMask)] | umSSDRDisplayMask; - umSSDRHours = top[FPSTR(_str_hours)] | umSSDRHours; - umSSDRMinutes = top[FPSTR(_str_minutes)] | umSSDRMinutes; - umSSDRSeconds = top[FPSTR(_str_seconds)] | umSSDRSeconds; - umSSDRColons = top[FPSTR(_str_colons)] | umSSDRColons; - umSSDRDays = top[FPSTR(_str_days)] | umSSDRDays; - umSSDRMonths = top[FPSTR(_str_months)] | umSSDRMonths; - umSSDRYears = top[FPSTR(_str_years)] | umSSDRYears; - umSSDRBrightnessMin = top[FPSTR(_str_minBrightness)] | umSSDRBrightnessMin; - umSSDRBrightnessMax = top[FPSTR(_str_maxBrightness)] | umSSDRBrightnessMax; - - DEBUG_PRINT(FPSTR(_str_name)); - DEBUG_PRINTLN(F(" config (re)loaded.")); - - return true; - } - /* - * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). - * This could be used in the future for the system to determine whether your usermod is installed. - */ - uint16_t getId() { - return USERMOD_ID_SSDR; - } -}; - -const char UsermodSSDR::_str_name[] PROGMEM = "UsermodSSDR"; -const char UsermodSSDR::_str_timeEnabled[] PROGMEM = "enabled"; -const char UsermodSSDR::_str_inverted[] PROGMEM = "inverted"; -const char UsermodSSDR::_str_colonblink[] PROGMEM = "Colon-blinking"; -const char UsermodSSDR::_str_leadingZero[] PROGMEM = "Leading-Zero"; -const char UsermodSSDR::_str_displayMask[] PROGMEM = "Display-Mask"; -const char UsermodSSDR::_str_hours[] PROGMEM = "LED-Numbers-Hours"; -const char UsermodSSDR::_str_minutes[] PROGMEM = "LED-Numbers-Minutes"; -const char UsermodSSDR::_str_seconds[] PROGMEM = "LED-Numbers-Seconds"; -const char UsermodSSDR::_str_colons[] PROGMEM = "LED-Numbers-Colons"; -const char UsermodSSDR::_str_days[] PROGMEM = "LED-Numbers-Day"; -const char UsermodSSDR::_str_months[] PROGMEM = "LED-Numbers-Month"; -const char UsermodSSDR::_str_years[] PROGMEM = "LED-Numbers-Year"; -const char UsermodSSDR::_str_ldrEnabled[] PROGMEM = "enable-auto-brightness"; -const char UsermodSSDR::_str_minBrightness[] PROGMEM = "auto-brightness-min"; -const char UsermodSSDR::_str_maxBrightness[] PROGMEM = "auto-brightness-max"; - - -static UsermodSSDR seven_segment_display_reloaded; -REGISTER_USERMOD(seven_segment_display_reloaded); \ No newline at end of file diff --git a/usermods/seven_segment_display_reloaded/library.json b/usermods/seven_segment_display_reloaded_v2/library.json similarity index 62% rename from usermods/seven_segment_display_reloaded/library.json rename to usermods/seven_segment_display_reloaded_v2/library.json index 1b7d0687f2..21aaca134b 100644 --- a/usermods/seven_segment_display_reloaded/library.json +++ b/usermods/seven_segment_display_reloaded_v2/library.json @@ -1,5 +1,5 @@ { - "name": "seven_segment_display_reloaded", + "name": "seven_segment_display_reloaded_v2", "build": { "libArchive": false, "extraScript": "setup_deps.py" diff --git a/usermods/seven_segment_display_reloaded_v2/readme.md b/usermods/seven_segment_display_reloaded_v2/readme.md new file mode 100644 index 0000000000..c36965c2cf --- /dev/null +++ b/usermods/seven_segment_display_reloaded_v2/readme.md @@ -0,0 +1,281 @@ +# Seven Segment Display Reloaded + +Uses the overlay feature to create a configurable seven segment display. +Optimized for maximum configurability and use with seven segment clocks by [parallyze](https://www.instructables.com/member/parallyze/instructables/). +Very loosely based on the existing usermod "seven segment display". + +## Installation + +Add the compile-time option `-D USERMOD_SSDR` to your `platformio.ini` (or `platformio_override.ini`) or use `#define USERMOD_SSDR` in `my_config.h`. + +# Compiling + +To enable, add `seven_segment_display_reloaded_v2` to your `custom_usermods` (e.g. in `platformio_override.ini`) +```ini +[env:usermod_ssdr_d1_mini] +extends = env:d1_mini +custom_usermods = ${env:d1_mini.custom_usermods} seven_segment_display_reloaded_v2 +``` + +For the auto brightness option, the usermod **SN_Photoresistor** or **BH1750_V2** has to be installed as well. See [SN_Photoresistor/readme.md](SN_Photoresistor/readme.md) or [BH1750_V2/readme.md](BH1750_V2/readme.md) for instructions. + +## Available Compile-Time Parameters + +These parameters can be configured at compile time using `#define` statements in `my_config.h`. The following table summarizes the available options: + +| Parameter | Default Value | Description | +|-----------------------------------|---------------|-------------| +| SSDR_ENABLED | true | Enable SSDR usermod | +| SSDR_ENABLE_AUTO_BRIGHTNESS | false | Enable auto brightness (requires USERMOD_SN_PHOTORESISTOR) | +| SSDR_BRIGHTNESS_MIN | 0 | Minimum brightness value for auto brightness mapping | +| SSDR_BRIGHTNESS_MAX | 128 | Maximum brightness value for auto brightness mapping | +| SSDR_INVERTED | false | Inverted display (background on, digits off) | +| SSDR_COLONBLINK | true | Enable blinking colon(s) | +| SSDR_LEADING_ZERO | false | Show leading zero for hours (e.g., "07" instead of "7") | +| SSDR_DISPLAY_MASK | "H:m" | Display mask for time format (see below) | +| SSDR_HOURS | (see example) | LED definition for hours digits | +| SSDR_MINUTES | (see example) | LED definition for minutes digits | +| SSDR_SECONDS | "" | Reserved for seconds if needed | +| SSDR_COLONS | "266-275" | Segment range for colon separators | +| SSDR_LIGHT | "252-265" | Segment range for light indicator | +| SSDR_DAYS | "" | Reserved for day display if needed | +| SSDR_MONTHS | "" | Reserved for month display if needed | +| SSDR_YEARS | "" | Reserved for year display if needed | +| umSSDR_INVERT_AUTO_BRIGHTNESS | false | Invert brightness mapping (maps lux min to brightness max) | +| umSSDR_LUX_MIN | 0 | Minimum lux level for brightness mapping | +| umSSDR_LUX_MAX | 1000 | Maximum lux level for brightness mapping | + +Additionally, the usermod allows overriding the internal LED segment number mapping with the optional macro: +- umSSDR_NUMBERS + +## Settings + +All settings can be controlled via the usermod settings page. +Some settings can also be controlled through MQTT with a raw payload or via a JSON request to `/json/state`. + +### Parameters Controlled in the Settings Page + +- **enabled** + Enables/disables this usermod. + +- **inverted** + Enables the inverted mode in which the background is lit and the digits are off (black). + +- **Colon-blinking** + Enables the blinking colon(s) if they are defined. + +- **Leading-Zero** + Shows a leading zero for hours when applicable (e.g., "07" instead of "7"). + +- **enable-auto-brightness** + Enables the auto brightness feature. This works only when the usermod **SN_Photoresistor** or **BH1750_V2** is installed. + +- **auto-brightness-min / auto-brightness-max** + Maps the lux value from the SN_Photoresistor or BH1750_V2 to brightness values. + The mapping (default: 0–1000 lux) is mapped to the defined auto brightness limits. Note that WLED current protection might override the calculated value if it is too high. + +- **lux-min** + Defines the minimum lux level for brightness mapping. When the lux value is at or below this level, the display brightness is set to the minimum brightness. Default is `0` lux. + +- **lux-max** + Defines the maximum lux level for brightness mapping. When the lux value is at or above this level, the display brightness is set to the maximum brightness. Default is `1000` lux. + +- **invert-auto-brightness** + Inverts the mapping logic for brightness. When enabled (`true`), `lux-min` maps to the maximum brightness and `lux-max` maps to the minimum brightness. When disabled (`false`), `lux-min` maps to the minimum brightness and `lux-max` to the maximum brightness. + +- **Display-Mask** + Defines the layout for the time/date display. For example, `"H:m"` is the default. The mask characters include: + - **H** - 00–23 hours + - **h** - 01–12 hours + - **k** - 01–24 hours + - **m** - 00–59 minutes + - **s** - 00–59 seconds + - **d** - 01–31 day of month + - **M** - 01–12 month + - **y** - Last two digits of year + - **Y** - Full year (e.g., 2021) + - **L** - Light LED indicator + - **:** - Colon separator + +- **LED-Numbers** + LED segment definitions for various parts of the display: + - LED-Numbers-Hours + - LED-Numbers-Minutes + - LED-Numbers-Seconds + - LED-Numbers-Colons + - LED-Numbers-Light + - LED-Numbers-Day + - LED-Numbers-Month + - LED-Numbers-Year + +## Example LED Definitions + +The following is an example of an LED layout for a seven segment display. The diagram below shows the segment positions: + +``` + < A > +/\ /\ +F B +\/ \/ + < G > +/\ /\ +E C +\/ \/ + < D > +``` + +A digit segment can consist of single LED numbers and LED ranges, separated by commas (,) +An example would be 1,3,6-8,23,30-32. In this example the LEDs with the numbers 1,3,6,7,8,23,30,31 and 32 would make up a Segment. +Segments for each digit are separated by semicolons (;) and digits are separated by a colon (:). + +### Example for a Clock Display + +- **Hour Definition Example:** + + 59,46;47-48;50-51;52-53;54-55;57-58;49,56:0,13;1-2;4-5;6-7;8-9;11-12;3,10 + + The definition above represents two digits (separated by ":"): + + **First digit (of the hour):** + - Segment A: 59, 46 + - Segment B: 47, 48 + - Segment C: 50, 51 + - Segment D: 52, 53 + - Segment E: 54, 55 + - Segment F: 57, 58 + - Segment G: 49, 56 + + **Second digit (of the hour):** + - Segment A: 0, 13 + - Segment B: 1, 2 + - Segment C: 4, 5 + - Segment D: 6, 7 + - Segment E: 8, 9 + - Segment F: 11, 12 + - Segment G: 3, 10 + +- **Minute Definition Example:** + + 37-38;39-40;42-43;44,31;32-33;35-36;34,41:21-22;23-24;26-27;28,15;16-17;19-20;18,25 + + (Definitions can be adjusted according to the physical orientation of your LEDs.) + +## Additional Notes + +- **Dynamic Brightness Control:** +Auto brightness is computed using sensor readings from either the SN_Photoresistor or BH1750. The value is mapped between the defined brightness and lux limits, with an option to invert the mapping. + +- **Disabling LED Output:** +A public function `disableOutputFunction(bool state)` is provided to externally disable or enable the LED output. + +## Additional Projects + +### 1. Giant Hidden Shelf Edge Clock + +This project, available on [Thingiverse](https://www.thingiverse.com/thing:4207524), uses a large hidden shelf edge as the display for a clock. If you build the modified clock that also shows 24 hours, use the following settings in your configuration: + +my_config.h Settings: +-------------------------------- + +``` +#define USERMOD_SSDR + +#define umSSDR_ENABLED true // Enable SSDR usermod +#define umSSDR_ENABLE_AUTO_BRIGHTNESS false // Enable auto brightness (requires USERMOD_SN_PHOTORESISTOR) +#define umSSDR_INVERTED false // Inverted display +#define umSSDR_COLONBLINK true // Colon blink enabled +#define umSSDR_LEADING_ZERO false // Leading zero disabled +#define umSSDR_DISPLAY_MASK "H:mL" // Display mask for time format + +// Segment definitions for hours, minutes, seconds, colons, light, days, months, and years +#define umSSDR_HOURS "135-143;126-134;162-170;171-179;180-188;144-152;153-161:198-206;189-197;225-233;234-242;243-251;207-215;216-224" +#define umSSDR_MINUTES "9-17;0-8;36-44;45-53;54-62;18-26;27-35:72-80;63-71;99-107;108-116;117-125;81-89;90-98" +#define umSSDR_SECONDS "" +#define umSSDR_COLONS "266-275" // Segment range for colons +#define umSSDR_LIGHT "252-265" // Segment range for light indicator (added for this project) +#define umSSDR_DAYS "" // Reserved for days if needed +#define umSSDR_MONTHS "" // Reserved for months if needed +#define umSSDR_YEARS "" // Reserved for years if needed + +#define umSSDR_INVERT_AUTO_BRIGHTNESS true +#define umSSDR_LUX_MIN 50 +#define umSSDR_LUX_MAX 1000 + +// Brightness limits +#define umSSDR_BRIGHTNESS_MIN 0 // Minimum brightness +#define umSSDR_BRIGHTNESS_MAX 128 // Maximum brightness +``` + +-------------------------------- + +*Note:* For this project, the `umSSDR_LIGHT` parameter was added to provide a dedicated segment for a light indicator. + +--- + +### 2. EleksTube Retro Glows Analog Nixie Tube Clock (Non-IPS Version) + +The EleksTube project, available at [EleksTube Retro Glows Analog Nixie Tube Clock](https://elekstube.com/products/elekstube-r2-6-bit-electronic-led-luminous-retro-glows-analog-nixie-tube-clock). With the following settings, the SSDR usermod becomes more versatile and can be used with this clock as well: + +my_config.h Settings: +-------------------------------- + +``` +#define umSSDR_ENABLED true // Enable SSDR usermod +#define umSSDR_ENABLE_AUTO_BRIGHTNESS false // Enable auto brightness (requires USERMOD_SN_PHOTORESISTOR) +#define umSSDR_INVERTED false // Inverted display +#define umSSDR_COLONBLINK false // Colon blink disabled +#define umSSDR_LEADING_ZERO true // Leading zero enabled +#define umSSDR_DISPLAY_MASK "H:m:s" // Display mask for time format + +// Segment definitions for hours, minutes, seconds, colons, light, days, months, and years +#define umSSDR_HOURS "20,30;21,31;22,32;23,33;24,34;25,35;26,36;27,37;28,38;29,39:0,10;1,11;2,12;3,13;4,14;5,15;6,16;7,17;8,18;9,19" +#define umSSDR_MINUTES "60,70;61,71;62,72;63,73;64,74;65,75;66,76;67,77;68,78;69,79:40,50;41,51;42,52;43,53;44,54;45,55;46,56;47,57;48,58;49,59" +#define umSSDR_SECONDS "100,110;101,111;102,112;103,113;104,114;105,115;106,116;107,117;108,118;109,119:80,90;81,91;82,92;83,93;84,94;85,95;86,96;87,97;88,98;89,99" +#define umSSDR_COLONS "" // No colon segment mapping needed +#define umSSDR_LIGHT "" // No light indicator defined +#define umSSDR_DAYS "" // Reserved for days if needed +#define umSSDR_MONTHS "" // Reserved for months if needed +#define umSSDR_YEARS "" // Reserved for years if needed + +#define umSSDR_INVERT_AUTO_BRIGHTNESS true +#define umSSDR_LUX_MIN 50 +#define umSSDR_LUX_MAX 1000 + +// Brightness limits +#define umSSDR_BRIGHTNESS_MIN 0 // Minimum brightness +#define umSSDR_BRIGHTNESS_MAX 128 // Maximum brightness + +#define umSSDR_NUMBERS { \ + { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 }, /* 0 */ \ + { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* 1 */ \ + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 }, /* 2 */ \ + { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, /* 3 */ \ + { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 }, /* 4 */ \ + { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 }, /* 5 */ \ + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 }, /* 6 */ \ + { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, /* 7 */ \ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, /* 8 */ \ + { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }, /* 9 */ \ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } /* Blank */ \ +} +``` + +### 3. Lazy Clock(s) by paralyze +[Lazy 7 Quick build edition](https://www.instructables.com/Lazy-7-Quick-Build-Edition/). The SSDR usermod can be used to drive this and other clock designs by [paralyze](https://www.instructables.com/member/parallyze/) +For example the Lazy 7 Quick build edition has the following settings - depending on the orientation. + +- hour "59,46;47-48;50-51;52-53;54-55;57-58;49,56:0,13;1-2;4-5;6-7;8-9;11-12;3,10" + +- minute "37-38;39-40;42-43;44,31;32-33;35-36;34,41:21-22;23-24;26-27;28,15;16-17;19-20;18,25" + +or + +- hour "6,7;8,9;11,12;13,0;1,2;4,5;3,10:52,53;54,55;57,58;59,46;47,48;50,51;49,56" + +- minute "15,28;16,17;19,20;21,22;23,24;26,27;18,25:31,44;32,33;35,36;37,38;39,40;42,43;34,41" + +-------------------------------- + +With these modifications, the SSDR usermod becomes even more versatile, allowing it to be used on a wide variety of segment clocks and projects. + +*Note:* The ability to override the LED segment mapping via `umSSDR_NUMBERS` provides additional flexibility for adapting to different physical displays. diff --git a/usermods/seven_segment_display_reloaded/setup_deps.py b/usermods/seven_segment_display_reloaded_v2/setup_deps.py similarity index 86% rename from usermods/seven_segment_display_reloaded/setup_deps.py rename to usermods/seven_segment_display_reloaded_v2/setup_deps.py index 1c51acccec..4b05e852dd 100644 --- a/usermods/seven_segment_display_reloaded/setup_deps.py +++ b/usermods/seven_segment_display_reloaded_v2/setup_deps.py @@ -7,4 +7,4 @@ if "SN_Photoresistor" in libs: env.Append(CPPDEFINES=[("USERMOD_SN_PHOTORESISTOR")]) if any(mod in ("BH1750_v2", "BH1750") for mod in libs): - env.Append(CPPDEFINES=[("USERMOD_BH1750")]) + env.Append(CPPDEFINES=[("USERMOD_BH1750")]) \ No newline at end of file diff --git a/usermods/seven_segment_display_reloaded_v2/seven_segment_display_reloaded_v2.cpp b/usermods/seven_segment_display_reloaded_v2/seven_segment_display_reloaded_v2.cpp new file mode 100644 index 0000000000..6e3a439412 --- /dev/null +++ b/usermods/seven_segment_display_reloaded_v2/seven_segment_display_reloaded_v2.cpp @@ -0,0 +1,662 @@ +#include "seven_segment_display_reloaded_v2.h" + +#ifdef WLED_DISABLE_MQTT +#error "This user mod requires MQTT to be enabled." +#endif + + void UsermodSSDR::_overlaySevenSegmentDraw() { + int displayMaskLen = static_cast(umSSDRDisplayMask.length()); + bool colonsDone = false; + bool lightDone = false; + _setAllFalse(); + + for (int index = 0; index < displayMaskLen; index++) { + int timeVar = 0; + char c = umSSDRDisplayMask[index]; + switch (c) { + case 'h': + timeVar = hourFormat12(localTime); + _showElements(&umSSDRHours, timeVar, 0, !umSSDRLeadingZero); + break; + case 'H': + timeVar = hour(localTime); + _showElements(&umSSDRHours, timeVar, 0, !umSSDRLeadingZero); + break; + case 'k': + timeVar = hour(localTime) + 1; + _showElements(&umSSDRHours, timeVar, 0, !umSSDRLeadingZero); + break; + case 'm': + timeVar = minute(localTime); + _showElements(&umSSDRMinutes, timeVar, 0, 0); + break; + case 's': + timeVar = second(localTime); + _showElements(&umSSDRSeconds, timeVar, 0, 0); + break; + case 'd': + timeVar = day(localTime); + _showElements(&umSSDRDays, timeVar, 0, 0); + break; + case 'M': + timeVar = month(localTime); + _showElements(&umSSDRMonths, timeVar, 0, 0); + break; + case 'y': + timeVar = year(localTime) % 100; // Fix: Get last two digits of year + _showElements(&umSSDRYears, timeVar, 0, 0); + break; + case 'Y': + timeVar = year(localTime); + _showElements(&umSSDRYears, timeVar, 0, 0); + break; + case ':': + if (!colonsDone) { // only call _setColons once as all colons are printed when the first colon is found + _setColons(); + colonsDone = true; + } + break; + case 'L': + if (!lightDone) { // only call _showElements once + _showElements(&umSSDRLight, 0, 1, 0); + lightDone = true; + } + break; + default: + _logUsermodSSDR("Unknown mask char '%c'", c); + break; + } + } + _setMaskToLeds(); + } + + void UsermodSSDR::_setColons() { + if ( umSSDRColonblink ) { + if ( second(localTime) % 2 == 0 ) { + _showElements(&umSSDRColons, 0, 1, 0); + } + } else { + _showElements(&umSSDRColons, 0, 1, 0); + } + } + + void UsermodSSDR::_showElements(String *map, int timevar, bool isColon, bool removeZero) { + if (map && map->length() > 0) { + uint8_t length = 1; + for (int tmp = timevar; tmp >= 10; tmp /= 10) ++length; + + bool addZero = false; + if (length == 1) { + length = 2; + addZero = true; + } + uint8_t timeArr[4] = {10,10,10,10}; // supports up to 4 digits (0000-9999) + if (length > 4) length = 4; // safety clamp + + if (addZero) { + if (removeZero) { + timeArr[0] = timevar; + timeArr[1] = 10; // blank digit + } else { + timeArr[0] = timevar; + timeArr[1] = 0; + } + } else { + int count = 0; + while (timevar) { + timeArr[count] = timevar % 10; + timevar /= 10; + count++; + } + } + + int colonsLen = static_cast((*map).length()); + int count = 0; + int countSegments = 0; + int countDigit = 0; + bool range = false; + int lastSeenLedNr = 0; + + for (int index = 0; index < colonsLen; index++) { + switch ((*map)[index]) { + case '-': + lastSeenLedNr = _checkForNumber(count, index, map); + count = 0; + range = true; + break; + case ':': + if (countDigit < length) { // Prevent array out of bounds + _setLeds(_checkForNumber(count, index, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon); + } + count = 0; + range = false; + countDigit++; + countSegments = 0; + break; + case ';': + if (countDigit < length) { // Prevent array out of bounds + _setLeds(_checkForNumber(count, index, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon); + } + count = 0; + range = false; + countSegments++; + break; + case ',': + if (countDigit < length) { // Prevent array out of bounds + _setLeds(_checkForNumber(count, index, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon); + } + count = 0; + range = false; + break; + default: + count++; + break; + } + } + + // Process final segment if any + if (count > 0 && countDigit < length) { + _setLeds(_checkForNumber(count, colonsLen, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon); + } + } + } + + void UsermodSSDR::_setLeds(int lednr, int lastSeenLedNr, bool range, int countSegments, int number, bool colon) { + if (!umSSDRMask || (lednr < 0) || (lednr >= umSSDRLength)) return; // prevent array bounds violation + + if (!(colon && umSSDRColonblink) && ((number < 0) || (countSegments < 0))) return; + + if ((colon && umSSDRColonblink) || (number < 10 && umSSDRNumbers[number][countSegments])) { + if (range) { + for(int i = max(0, lastSeenLedNr); i <= lednr; i++) { + umSSDRMask[i] = true; + } + } else { + umSSDRMask[lednr] = true; + } + } + } + + void UsermodSSDR::_setMaskToLeds() { + if (!umSSDRMask) return; // Safety check + + for(int i = 0; i < umSSDRLength; i++) { // Changed <= to < to prevent buffer overflow + if ((!umSSDRInverted && !umSSDRMask[i]) || (umSSDRInverted && umSSDRMask[i])) { + strip.setPixelColor(i, 0x000000); + } + } + } + + void UsermodSSDR::_setAllFalse() { + if (!umSSDRMask) return; // Safety check + + for(int i = 0; i < umSSDRLength; i++) { // Changed <= to < to prevent buffer overflow + umSSDRMask[i] = false; + } + } + + int UsermodSSDR::_checkForNumber(int count, int index, String *map) { + if (count <= 0 || map == nullptr || index < count) return 0; // Added safety checks + + String number = (*map).substring(index - count, index); + return number.toInt(); + } + + void UsermodSSDR::_publishMQTTint_P(const char *subTopic, int value) + { + #ifndef WLED_DISABLE_MQTT + _logUsermodSSDR("Entering _publishMQTTint_P topic='%s' value=%d", subTopic, value); + if (WLED_MQTT_CONNECTED) { + if(mqtt == NULL){ + _logUsermodSSDR("MQTT pointer NULL"); + return; + } + + char buffer[128], nameBuffer[30], topicBuffer[30], valBuffer[12]; + + // Copy PROGMEM strings to local buffers + strlcpy(nameBuffer, reinterpret_cast(_str_name), sizeof(nameBuffer)); + strlcpy(topicBuffer, reinterpret_cast(subTopic), sizeof(topicBuffer)); + + int result = snprintf(buffer, sizeof(buffer), "%s/%s/%s", mqttDeviceTopic, nameBuffer, topicBuffer); + if (result < 0 || result >= sizeof(buffer)) { + _logUsermodSSDR("Buffer overflow in _publishMQTTint_P"); + return; // Buffer overflow check + } + snprintf_P(valBuffer, sizeof(valBuffer), PSTR("%d"), value); + + _logUsermodSSDR("Publishing INT to: '%s', value: '%s'", buffer, valBuffer); + mqtt->publish(buffer, 2, true, valBuffer); + } + #endif + } + + void UsermodSSDR::_publishMQTTstr_P(const char *subTopic, String Value) + { + #ifndef WLED_DISABLE_MQTT + _logUsermodSSDR("Entering _publishMQTTstr_P topic='%s' value='%s'", subTopic, Value.c_str()); + if (WLED_MQTT_CONNECTED) { + if(mqtt == NULL){ + _logUsermodSSDR("MQTT pointer NULL"); + return; + } + + char buffer[128], nameBuffer[30], topicBuffer[30]; + + strlcpy(nameBuffer, reinterpret_cast(_str_name), sizeof(nameBuffer)); + strlcpy(topicBuffer, reinterpret_cast(subTopic), sizeof(topicBuffer)); + + int result = snprintf(buffer, sizeof(buffer), "%s/%s/%s", mqttDeviceTopic, nameBuffer, topicBuffer); + if (result < 0 || result >= sizeof(buffer)) { + _logUsermodSSDR("Buffer overflow in _publishMQTTstr_P"); + return; // Buffer overflow check + } + + _logUsermodSSDR("Publishing STR to: '%s', value: '%s'", buffer, Value.c_str()); + mqtt->publish(buffer, 2, true, Value.c_str(), Value.length()); + } + #endif + } + + bool UsermodSSDR::_handleSetting(char *topic, char *payload) { + _logUsermodSSDR("Handling setting. Topic='%s', Payload='%s'", topic, payload); + + if (_cmpIntSetting_P(topic, payload, _str_timeEnabled, &umSSDRDisplayTime)) { + _logUsermodSSDR("Updated timeEnabled"); + return true; + } + if (_cmpIntSetting_P(topic, payload, _str_ldrEnabled, &umSSDREnableLDR)) { + _logUsermodSSDR("Updated ldrEnabled"); + return true; + } + if (_cmpIntSetting_P(topic, payload, _str_inverted, &umSSDRInverted)) { + _logUsermodSSDR("Updated inverted"); + return true; + } + if (_cmpIntSetting_P(topic, payload, _str_colonblink, &umSSDRColonblink)) { + _logUsermodSSDR("Updated colonblink"); + return true; + } + if (_cmpIntSetting_P(topic, payload, _str_leadingZero, &umSSDRLeadingZero)) { + _logUsermodSSDR("Updated leadingZero"); + return true; + } + + char displayMaskBuffer[30]; + strlcpy(displayMaskBuffer, reinterpret_cast(_str_displayMask), sizeof(displayMaskBuffer)); + + _logUsermodSSDR("Comparing '%s' with '%s'", topic, displayMaskBuffer); + + if (strcmp(topic, displayMaskBuffer) == 0) { + umSSDRDisplayMask = String(payload); + + _logUsermodSSDR("Updated displayMask to '%s'", umSSDRDisplayMask.c_str()); + _publishMQTTstr_P(_str_displayMask, umSSDRDisplayMask); + return true; + } + _logUsermodSSDR("No matching setting found"); + return false; + } + + void UsermodSSDR::_updateMQTT() + { + _publishMQTTint_P(_str_timeEnabled, umSSDRDisplayTime); + _publishMQTTint_P(_str_ldrEnabled, umSSDREnableLDR); + _publishMQTTint_P(_str_minBrightness, umSSDRBrightnessMin); + _publishMQTTint_P(_str_maxBrightness, umSSDRBrightnessMax); + _publishMQTTint_P(_str_luxMin, umSSDRLuxMin); + _publishMQTTint_P(_str_luxMax, umSSDRLuxMax); + _publishMQTTint_P(_str_invertAutoBrightness, umSSDRInvertAutoBrightness); + _publishMQTTint_P(_str_inverted, umSSDRInverted); + _publishMQTTint_P(_str_colonblink, umSSDRColonblink); + _publishMQTTint_P(_str_leadingZero, umSSDRLeadingZero); + + _publishMQTTstr_P(_str_hours, umSSDRHours); + _publishMQTTstr_P(_str_minutes, umSSDRMinutes); + _publishMQTTstr_P(_str_seconds, umSSDRSeconds); + _publishMQTTstr_P(_str_colons, umSSDRColons); + _publishMQTTstr_P(_str_light, umSSDRLight); + _publishMQTTstr_P(_str_days, umSSDRDays); + _publishMQTTstr_P(_str_months, umSSDRMonths); + _publishMQTTstr_P(_str_years, umSSDRYears); + _publishMQTTstr_P(_str_displayMask, umSSDRDisplayMask); + } + + void UsermodSSDR::_addJSONObject(JsonObject& root) { + JsonObject ssdrObj = root[FPSTR(_str_name)]; + if (ssdrObj.isNull()) { + ssdrObj = root.createNestedObject(FPSTR(_str_name)); + } + + ssdrObj[FPSTR(_str_timeEnabled)] = umSSDRDisplayTime; + ssdrObj[FPSTR(_str_ldrEnabled)] = umSSDREnableLDR; + ssdrObj[FPSTR(_str_minBrightness)] = umSSDRBrightnessMin; + ssdrObj[FPSTR(_str_maxBrightness)] = umSSDRBrightnessMax; + ssdrObj[FPSTR(_str_luxMin)] = umSSDRLuxMin; + ssdrObj[FPSTR(_str_luxMax)] = umSSDRLuxMax; + ssdrObj[FPSTR(_str_invertAutoBrightness)] = umSSDRInvertAutoBrightness; + ssdrObj[FPSTR(_str_inverted)] = umSSDRInverted; + ssdrObj[FPSTR(_str_colonblink)] = umSSDRColonblink; + ssdrObj[FPSTR(_str_leadingZero)] = umSSDRLeadingZero; + ssdrObj[FPSTR(_str_displayMask)] = umSSDRDisplayMask; + ssdrObj[FPSTR(_str_hours)] = umSSDRHours; + ssdrObj[FPSTR(_str_minutes)] = umSSDRMinutes; + ssdrObj[FPSTR(_str_seconds)] = umSSDRSeconds; + ssdrObj[FPSTR(_str_colons)] = umSSDRColons; + ssdrObj[FPSTR(_str_light)] = umSSDRLight; + ssdrObj[FPSTR(_str_days)] = umSSDRDays; + ssdrObj[FPSTR(_str_months)] = umSSDRMonths; + ssdrObj[FPSTR(_str_years)] = umSSDRYears; + } + + void UsermodSSDR::setup() { + umSSDRLength = strip.getLengthTotal(); + + // Fixed memory allocation to prevent leaks + if (umSSDRMask != nullptr) { + free(umSSDRMask); // Free previous allocation if exists + } + umSSDRMask = (bool*) calloc(umSSDRLength, sizeof(bool)); // Use calloc to initialize to 0 + + if (umSSDRMask == nullptr) { + _logUsermodSSDR("Failed to allocate memory for SSDR mask"); + return; // Early return on memory allocation failure + } + _setAllFalse(); + + #ifdef USERMOD_SN_PHOTORESISTOR + photoResistor = (Usermod_SN_Photoresistor*) UsermodManager::lookup(USERMOD_ID_SN_PHOTORESISTOR); + #endif + #ifdef USERMOD_BH1750 + bh1750 = (Usermod_BH1750*) UsermodManager::lookup(USERMOD_ID_BH1750); + #endif + _logUsermodSSDR("Setup done"); + } + + // Clean up any allocated memory in the destructor + UsermodSSDR::~UsermodSSDR() { + if (umSSDRMask != nullptr) { + free(umSSDRMask); + umSSDRMask = nullptr; + } + } + + /* + * loop() is called continuously. Here you can check for events, read sensors, etc. + */ + void UsermodSSDR::loop() { + if (!umSSDRDisplayTime || strip.isUpdating() || umSSDRMask == nullptr) { + return; + } + + #if defined(USERMOD_SN_PHOTORESISTOR) || defined(USERMOD_BH1750) + if(bri != 0 && umSSDREnableLDR && (millis() - umSSDRLastRefresh > umSSDRRefreshTime) && !externalLedOutputDisabled) { + float lux = -1; // Initialize lux with an invalid value + + #ifdef USERMOD_SN_PHOTORESISTOR + if (photoResistor != nullptr) { + lux = photoResistor->getLastLDRValue(); + } + #endif + + #ifdef USERMOD_BH1750 + if (bh1750 != nullptr) { + lux = bh1750->getIlluminance(); + } + #endif + + if (lux >= 0) { // Ensure we got a valid lux value + uint16_t brightness; + + // Constrain lux values within defined range + float constrainedLux = constrain(lux, umSSDRLuxMin, umSSDRLuxMax); + + // float linear interpolation to preserve full 0–255 resolution + float spanLux = umSSDRLuxMax - umSSDRLuxMin; + float spanBright = umSSDRBrightnessMax - umSSDRBrightnessMin; + float ratio = (constrainedLux - umSSDRLuxMin) / spanLux; + + if (!umSSDRInvertAutoBrightness) { + brightness = static_cast(ratio * spanBright + umSSDRBrightnessMin); + } else { + brightness = static_cast((1.0f - ratio) * spanBright + umSSDRBrightnessMin); + } + _logUsermodSSDR("Lux=%.2f brightness=%d", lux, brightness); + + if (bri != brightness) { + _logUsermodSSDR("Adjusting brightness based on lux value: %.2f lx, new brightness: %d", lux, brightness); + bri = brightness; + stateUpdated(1); + } + } + umSSDRLastRefresh = millis(); + } + #endif + } + + void UsermodSSDR::handleOverlayDraw() { + if (umSSDRDisplayTime && !externalLedOutputDisabled && umSSDRMask != nullptr) { + _overlaySevenSegmentDraw(); + } + } + + /* + * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. + * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. + * Below it is shown how this could be used for e.g. a light sensor + */ + void UsermodSSDR::addToJsonInfo(JsonObject& root) { + JsonObject user = root[F("u")]; + if (user.isNull()) { + user = root.createNestedObject(F("u")); + } + + // Create a nested array for energy data + JsonArray ssr_json = user.createNestedArray(F("Seven segment reloaded:")); + + if (!umSSDRDisplayTime) { + ssr_json.add(F("disabled")); // Indicate that the module is disabled + } else { + // File needs to be UTF-8 to show an arrow that points down and right instead of an question mark + JsonArray invert = user.createNestedArray("⤷ Time inverted"); + invert.add(umSSDRInverted); + + JsonArray blink = user.createNestedArray("⤷ Blinking colon"); + blink.add(umSSDRColonblink); + + JsonArray zero = user.createNestedArray("⤷ Show hour leading zero"); + zero.add(umSSDRLeadingZero); + + JsonArray ldrEnable = user.createNestedArray(F("⤷ Auto Brightness enabled")); + ldrEnable.add(umSSDREnableLDR); + + JsonArray ldrInvert = user.createNestedArray(F("⤷ Auto Brightness inverted")); + ldrInvert.add(umSSDRInvertAutoBrightness); + } + } + + /* + * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ + void UsermodSSDR::addToJsonState(JsonObject& root) { + JsonObject user = root[F("u")]; + if (user.isNull()) { + user = root.createNestedObject(F("u")); + } + _addJSONObject(user); + } + + /* + * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ + void UsermodSSDR::readFromJsonState(JsonObject& root) { + JsonObject user = root[F("u")]; + if (!user.isNull()) { + JsonObject ssdrObj = user[FPSTR(_str_name)]; + umSSDRDisplayTime = ssdrObj[FPSTR(_str_timeEnabled)] | umSSDRDisplayTime; + umSSDREnableLDR = ssdrObj[FPSTR(_str_ldrEnabled)] | umSSDREnableLDR; + umSSDRBrightnessMin = ssdrObj[FPSTR(_str_minBrightness)] | umSSDRBrightnessMin; + umSSDRBrightnessMax = ssdrObj[FPSTR(_str_maxBrightness)] | umSSDRBrightnessMax; + umSSDRLuxMin = ssdrObj[FPSTR(_str_luxMin)] | umSSDRLuxMin; + umSSDRLuxMax = ssdrObj[FPSTR(_str_luxMax)] | umSSDRLuxMax; + umSSDRInvertAutoBrightness = ssdrObj[FPSTR(_str_invertAutoBrightness)] | umSSDRInvertAutoBrightness; + umSSDRInverted = ssdrObj[FPSTR(_str_inverted)] | umSSDRInverted; + umSSDRColonblink = ssdrObj[FPSTR(_str_colonblink)] | umSSDRColonblink; + umSSDRLeadingZero = ssdrObj[FPSTR(_str_leadingZero)] | umSSDRLeadingZero; + umSSDRDisplayMask = ssdrObj[FPSTR(_str_displayMask)] | umSSDRDisplayMask; + umSSDRHours = ssdrObj[FPSTR(_str_hours)] | umSSDRHours; + umSSDRMinutes = ssdrObj[FPSTR(_str_minutes)] | umSSDRMinutes; + umSSDRSeconds = ssdrObj[FPSTR(_str_seconds)] | umSSDRSeconds; + umSSDRColons = ssdrObj[FPSTR(_str_colons)] | umSSDRColons; + umSSDRLight = ssdrObj[FPSTR(_str_light)] | umSSDRLight; + umSSDRDays = ssdrObj[FPSTR(_str_days)] | umSSDRDays; + umSSDRMonths = ssdrObj[FPSTR(_str_months)] | umSSDRMonths; + umSSDRYears = ssdrObj[FPSTR(_str_years)] | umSSDRYears; + } + } + + void UsermodSSDR::onMqttConnect(bool sessionPresent) { + if (!umSSDRDisplayTime) { + _logUsermodSSDR("DisplayTime disabled, skipping subscribe"); + return; + } + #ifndef WLED_DISABLE_MQTT + if (WLED_MQTT_CONNECTED) { + int written; + char subBuffer[48], nameBuffer[30]; + + // Copy PROGMEM string to local buffer + strlcpy(nameBuffer, reinterpret_cast(_str_name), sizeof(nameBuffer)); + + _logUsermodSSDR("Connected. DeviceTopic='%s', GroupTopic='%s'", mqttDeviceTopic, mqttGroupTopic); + + if (mqttDeviceTopic[0] != 0) + { + _updateMQTT(); + //subscribe for sevenseg messages on the device topic + written = snprintf(subBuffer, sizeof(subBuffer), "%s/%s/+/set", mqttDeviceTopic, nameBuffer); + if (written < 0 || written >= int(sizeof(subBuffer))) { + _logUsermodSSDR("subBuffer overflow – device topic too long"); + return; + } + _logUsermodSSDR("Subscribing to device topic: '%s'", subBuffer); + mqtt->subscribe(subBuffer, 2); + } + + if (mqttGroupTopic[0] != 0) + { + //subscribe for sevenseg messages on the group topic + written = snprintf(subBuffer, sizeof(subBuffer), "%s/%s/+/set", mqttGroupTopic, nameBuffer); + if (written < 0 || written >= int(sizeof(subBuffer))) { + _logUsermodSSDR("subBuffer overflow – group topic too long"); + return; + } + _logUsermodSSDR("Subscribing to group topic: '%s'", subBuffer); + mqtt->subscribe(subBuffer, 2); + } + } + #endif + } + + bool UsermodSSDR::onMqttMessage(char *topic, char *payload) { + #ifndef WLED_DISABLE_MQTT + if (umSSDRDisplayTime) { + //If topic begins with sevenSeg cut it off, otherwise not our message. + size_t topicPrefixLen = strlen_P(PSTR("/wledSS/")); + if (strncmp_P(topic, PSTR("/wledSS/"), topicPrefixLen) == 0) { + _logUsermodSSDR("onMqttMessage topic='%s' payload='%s'", topic, payload); + topic += topicPrefixLen; + _logUsermodSSDR("Topic starts with /wledSS/, modified topic: '%s'", topic); + } else { + return false; + } + //We only care if the topic ends with /set + size_t topicLen = strlen(topic); + if (topicLen > 4 && + topic[topicLen - 4] == '/' && + topic[topicLen - 3] == 's' && + topic[topicLen - 2] == 'e' && + topic[topicLen - 1] == 't') + { + //Trim /set and handle it + topic[topicLen - 4] = '\0'; + _logUsermodSSDR("Processing setting. Setting='%s', Value='%s'", topic, payload); + bool result = _handleSetting(topic, payload); + _logUsermodSSDR("Handling result: %s", result ? "success" : "failure"); + } + } + return true; + #endif + } + + void UsermodSSDR::addToConfig(JsonObject &root) { + _addJSONObject(root); + + if (!umSSDRDisplayTime) return; + _updateMQTT(); + } + + bool UsermodSSDR::readFromConfig(JsonObject &root) { + JsonObject top = root[FPSTR(_str_name)]; + + if (top.isNull()) { + _logUsermodSSDR("%s: No config found. (Using defaults.)", reinterpret_cast(_str_name)); + return false; + } + umSSDRDisplayTime = (top[FPSTR(_str_timeEnabled)] | umSSDRDisplayTime); + umSSDREnableLDR = (top[FPSTR(_str_ldrEnabled)] | umSSDREnableLDR); + umSSDRInverted = (top[FPSTR(_str_inverted)] | umSSDRInverted); + umSSDRColonblink = (top[FPSTR(_str_colonblink)] | umSSDRColonblink); + umSSDRLeadingZero = (top[FPSTR(_str_leadingZero)] | umSSDRLeadingZero); + umSSDRDisplayMask = top[FPSTR(_str_displayMask)] | umSSDRDisplayMask; + umSSDRHours = top[FPSTR(_str_hours)] | umSSDRHours; + umSSDRMinutes = top[FPSTR(_str_minutes)] | umSSDRMinutes; + umSSDRSeconds = top[FPSTR(_str_seconds)] | umSSDRSeconds; + umSSDRColons = top[FPSTR(_str_colons)] | umSSDRColons; + umSSDRLight = top[FPSTR(_str_light)] | umSSDRLight; + umSSDRDays = top[FPSTR(_str_days)] | umSSDRDays; + umSSDRMonths = top[FPSTR(_str_months)] | umSSDRMonths; + umSSDRYears = top[FPSTR(_str_years)] | umSSDRYears; + umSSDRBrightnessMin = top[FPSTR(_str_minBrightness)] | umSSDRBrightnessMin; + umSSDRBrightnessMax = top[FPSTR(_str_maxBrightness)] | umSSDRBrightnessMax; + umSSDRLuxMin = top[FPSTR(_str_luxMin)] | umSSDRLuxMin; + umSSDRLuxMax = top[FPSTR(_str_luxMax)] | umSSDRLuxMax; + umSSDRInvertAutoBrightness = top[FPSTR(_str_invertAutoBrightness)] | umSSDRInvertAutoBrightness; + + _logUsermodSSDR("%s: config (re)loaded.)", reinterpret_cast(_str_name)); + + return true; + } + /* + * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). + * This could be used in the future for the system to determine whether your usermod is installed. + */ + uint16_t UsermodSSDR::getId() { + return USERMOD_ID_SSDR; + } + +const char UsermodSSDR::_str_name[] PROGMEM = "UsermodSSDR"; +const char UsermodSSDR::_str_timeEnabled[] PROGMEM = "enabled"; +const char UsermodSSDR::_str_inverted[] PROGMEM = "inverted"; +const char UsermodSSDR::_str_colonblink[] PROGMEM = "Colon-blinking"; +const char UsermodSSDR::_str_leadingZero[] PROGMEM = "Leading-Zero"; +const char UsermodSSDR::_str_displayMask[] PROGMEM = "Display-Mask"; +const char UsermodSSDR::_str_hours[] PROGMEM = "LED-Numbers-Hours"; +const char UsermodSSDR::_str_minutes[] PROGMEM = "LED-Numbers-Minutes"; +const char UsermodSSDR::_str_seconds[] PROGMEM = "LED-Numbers-Seconds"; +const char UsermodSSDR::_str_colons[] PROGMEM = "LED-Numbers-Colons"; +const char UsermodSSDR::_str_light[] PROGMEM = "LED-Numbers-Light"; +const char UsermodSSDR::_str_days[] PROGMEM = "LED-Numbers-Day"; +const char UsermodSSDR::_str_months[] PROGMEM = "LED-Numbers-Month"; +const char UsermodSSDR::_str_years[] PROGMEM = "LED-Numbers-Year"; +const char UsermodSSDR::_str_ldrEnabled[] PROGMEM = "enable-auto-brightness"; +const char UsermodSSDR::_str_minBrightness[] PROGMEM = "auto-brightness-min"; +const char UsermodSSDR::_str_maxBrightness[] PROGMEM = "auto-brightness-max"; +const char UsermodSSDR::_str_luxMin[] PROGMEM = "lux-min"; +const char UsermodSSDR::_str_luxMax[] PROGMEM = "lux-max"; +const char UsermodSSDR::_str_invertAutoBrightness[] PROGMEM = "invert-auto-brightness"; + +static UsermodSSDR seven_segment_display_reloaded; +REGISTER_USERMOD(seven_segment_display_reloaded); \ No newline at end of file diff --git a/usermods/seven_segment_display_reloaded_v2/seven_segment_display_reloaded_v2.h b/usermods/seven_segment_display_reloaded_v2/seven_segment_display_reloaded_v2.h new file mode 100644 index 0000000000..755ee15a0c --- /dev/null +++ b/usermods/seven_segment_display_reloaded_v2/seven_segment_display_reloaded_v2.h @@ -0,0 +1,268 @@ +#pragma once +#include "wled.h" + +// logging macro: +#define _logUsermodSSDR(fmt, ...) \ + DEBUG_PRINTF("[SSDR] " fmt "\n", ##__VA_ARGS__) + +#ifdef USERMOD_SN_PHOTORESISTOR + #include "SN_Photoresistor.h" +#endif +#ifdef USERMOD_BH1750 + #include "BH1750_v2.h" +#endif + +//#define REFRESHTIME 497 + +class UsermodSSDR : public Usermod { + private: + //Runtime variables. + unsigned long umSSDRLastRefresh = 0; + unsigned long umSSDRRefreshTime = 3000; + uint16_t umSSDRLength = 0; + + // Configurable settings for the SSDR Usermod + // Enabled usermod + #ifndef umSSDR_ENABLED + #define umSSDR_ENABLED false + #endif + + #ifndef umSSDR_ENABLE_AUTO_BRIGHTNESS + #define umSSDR_ENABLE_AUTO_BRIGHTNESS false + #endif + + #ifndef umSSDR_BRIGHTNESS_MIN + #define umSSDR_BRIGHTNESS_MIN 0 + #endif + + #ifndef umSSDR_BRIGHTNESS_MAX + #define umSSDR_BRIGHTNESS_MAX 255 + #endif + + #ifndef umSSDR_INVERT_AUTO_BRIGHTNESS + #define umSSDR_INVERT_AUTO_BRIGHTNESS false + #endif + + #ifndef umSSDR_LUX_MIN + #define umSSDR_LUX_MIN 0 + #endif + + #ifndef umSSDR_LUX_MAX + #define umSSDR_LUX_MAX 1000 + #endif + + #ifndef umSSDR_INVERTED + #define umSSDR_INVERTED false + #endif + + #ifndef umSSDR_COLONBLINK + #define umSSDR_COLONBLINK true + #endif + + #ifndef umSSDR_LEADING_ZERO + #define umSSDR_LEADING_ZERO false + #endif + + // Display mask for physical layout + /*// H - 00-23 hours + // h - 01-12 hours + // k - 01-24 hours + // m - 00-59 minutes + // s - 00-59 seconds + // d - 01-31 day of month + // M - 01-12 month + // y - 21 last two positions of year + // Y - 2021 year + // L - Light LED + // This option defines a separate LED (or set of LEDs) that can be controlled independently + // from the other clock display LEDs. It can be switched on or off to provide additional + // lighting for the display or serve as an ambient light source, without affecting the + // clock's visual display of time and date. + // : for a colon + */ + + #ifndef umSSDR_DISPLAY_MASK + #define umSSDR_DISPLAY_MASK "H:m" + #endif + + #ifndef umSSDR_HOURS + #define umSSDR_HOURS "" + #endif + + #ifndef umSSDR_MINUTES + #define umSSDR_MINUTES "" + #endif + + #ifndef umSSDR_SECONDS + #define umSSDR_SECONDS "" + #endif + + #ifndef umSSDR_COLONS + #define umSSDR_COLONS "" + #endif + + #ifndef umSSDR_LIGHT + #define umSSDR_LIGHT "" + #endif + + #ifndef umSSDR_DAYS + #define umSSDR_DAYS "" + #endif + + #ifndef umSSDR_MONTHS + #define umSSDR_MONTHS "" + #endif + + #ifndef umSSDR_YEARS + #define umSSDR_YEARS "" + #endif + + #ifndef umSSDR_NUMBERS + /* Segment order, seen from the front: + < A > + /\ /\ + F B + \/ \/ + < G > + /\ /\ + E C + \/ \/ + < D > + */ + uint8_t umSSDRNumbers[11][7] = { + // A B C D E F G + { 1, 1, 1, 1, 1, 1, 0 }, // 0 + { 0, 1, 1, 0, 0, 0, 0 }, // 1 + { 1, 1, 0, 1, 1, 0, 1 }, // 2 + { 1, 1, 1, 1, 0, 0, 1 }, // 3 + { 0, 1, 1, 0, 0, 1, 1 }, // 4 + { 1, 0, 1, 1, 0, 1, 1 }, // 5 + { 1, 0, 1, 1, 1, 1, 1 }, // 6 + { 1, 1, 1, 0, 0, 0, 0 }, // 7 + { 1, 1, 1, 1, 1, 1, 1 }, // 8 + { 1, 1, 1, 1, 0, 1, 1 }, // 9 + { 0, 0, 0, 0, 0, 0, 0 } // blank + }; + #else + uint8_t umSSDRNumbers[11][10] = umSSDR_NUMBERS; + #endif + + bool umSSDRDisplayTime = umSSDR_ENABLED; + bool umSSDREnableLDR = umSSDR_ENABLE_AUTO_BRIGHTNESS; + uint16_t umSSDRBrightnessMin = umSSDR_BRIGHTNESS_MIN; + uint16_t umSSDRBrightnessMax = umSSDR_BRIGHTNESS_MAX; + bool umSSDRInvertAutoBrightness = umSSDR_INVERT_AUTO_BRIGHTNESS; + uint16_t umSSDRLuxMin = umSSDR_LUX_MIN; + uint16_t umSSDRLuxMax = umSSDR_LUX_MAX; + bool umSSDRInverted = umSSDR_INVERTED; + bool umSSDRColonblink = umSSDR_COLONBLINK; + bool umSSDRLeadingZero = umSSDR_LEADING_ZERO; + String umSSDRDisplayMask = umSSDR_DISPLAY_MASK; + String umSSDRHours = umSSDR_HOURS; + String umSSDRMinutes = umSSDR_MINUTES; + String umSSDRSeconds = umSSDR_SECONDS; + String umSSDRColons = umSSDR_COLONS; + String umSSDRLight = umSSDR_LIGHT; + String umSSDRDays = umSSDR_DAYS; + String umSSDRMonths = umSSDR_MONTHS; + String umSSDRYears = umSSDR_YEARS; + + bool* umSSDRMask = nullptr; + bool externalLedOutputDisabled = false; + + // Forward declarations of private methods + void _overlaySevenSegmentDraw(); + void _setColons(); + void _showElements(String *map, int timevar, bool isColon, bool removeZero); + void _setLeds(int lednr, int lastSeenLedNr, bool range, int countSegments, int number, bool colon); + void _setMaskToLeds(); + void _setAllFalse(); + int _checkForNumber(int count, int index, String *map); + void _publishMQTTint_P(const char *subTopic, int value); + void _publishMQTTstr_P(const char *subTopic, String Value); + + template + bool _cmpIntSetting_P(char *topic, char *payload, const char *setting, T *value) { + char settingBuffer[30]; + strlcpy(settingBuffer, setting, sizeof(settingBuffer)); + + _logUsermodSSDR("Checking topic='%s' payload='%s' against setting='%s'", topic, payload, settingBuffer); + + if (strcmp(topic, settingBuffer) != 0) return false; + + T oldValue = *value; + *value = static_cast(strtol(payload, nullptr, 10)); + _publishMQTTint_P(setting, static_cast(*value)); + _logUsermodSSDR("Setting '%s' updated from %d to %d", setting, static_cast(oldValue), static_cast(*value)); + return true; + } + + bool _handleSetting(char *topic, char *payload); + void _updateMQTT(); + void _addJSONObject(JsonObject& root); + + // String constants + static const char _str_name[]; + static const char _str_timeEnabled[]; + static const char _str_ldrEnabled[]; + static const char _str_minBrightness[]; + static const char _str_maxBrightness[]; + static const char _str_luxMin[]; + static const char _str_luxMax[]; + static const char _str_invertAutoBrightness[]; + static const char _str_inverted[]; + static const char _str_colonblink[]; + static const char _str_leadingZero[]; + static const char _str_displayMask[]; + static const char _str_hours[]; + static const char _str_minutes[]; + static const char _str_seconds[]; + static const char _str_colons[]; + static const char _str_light[]; + static const char _str_days[]; + static const char _str_months[]; + static const char _str_years[]; + + // Optional usermod dependencies + #ifdef USERMOD_SN_PHOTORESISTOR + Usermod_SN_Photoresistor *photoResistor = nullptr; + #else + #define photoResistor nullptr + #endif + + #ifdef USERMOD_BH1750 + Usermod_BH1750* bh1750 = nullptr; + #else + #define bh1750 nullptr + #endif + + public: + // Core usermod functions + void setup() override; + void loop() override; + void handleOverlayDraw(); + void addToJsonInfo(JsonObject& root) override; + void addToJsonState(JsonObject& root) override; + void readFromJsonState(JsonObject& root) override; + void onMqttConnect(bool sessionPresent) override; + bool onMqttMessage(char *topic, char *payload) override; + void addToConfig(JsonObject &root) override; + bool readFromConfig(JsonObject &root) override; + uint16_t getId() override; + + // Public function that can be called from other usermods + /** + * Allows external usermods to temporarily disable the LED output of this usermod. + * This is useful when multiple usermods might be trying to control the same LEDs. + * @param state true to disable LED output, false to enable + */ + void disableOutputFunction(bool state) { + externalLedOutputDisabled = state; + #ifdef DEBUG_PRINTF + _logUsermodSSDR("disableOutputFunction was triggered by an external Usermod: %s", externalLedOutputDisabled ? "true" : "false"); + #endif + } + + // Destructor to clean up memory + ~UsermodSSDR(); +}; \ No newline at end of file diff --git a/wled00/const.h b/wled00/const.h index 8891dfcaee..38895e5f67 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -172,7 +172,7 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); #define USERMOD_ID_SEVEN_SEGMENT_DISPLAY 21 //Usermod "usermod_v2_seven_segment_display.h" #define USERMOD_RGB_ROTARY_ENCODER 22 //Usermod "rgb-rotary-encoder.h" #define USERMOD_ID_QUINLED_AN_PENTA 23 //Usermod "quinled-an-penta.h" -#define USERMOD_ID_SSDR 24 //Usermod "usermod_v2_seven_segment_display_reloaded.h" +#define USERMOD_ID_SSDR 24 //Usermod "seven_segment_display_reloaded_v2.cpp" #define USERMOD_ID_CRONIXIE 25 //Usermod "usermod_cronixie.h" #define USERMOD_ID_WIZLIGHTS 26 //Usermod "wizlights.h" #define USERMOD_ID_WORDCLOCK 27 //Usermod "usermod_v2_word_clock.h"