#pragma once #include "wled.h" 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 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_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_ID_SN_PHOTORESISTOR Usermod_SN_Photoresistor *ptr; #else void* ptr = 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, 1); break; case 'H': timeVar = hour(localTime); _showElements(&umSSDRHours, timeVar, 0, 1); break; case 'k': timeVar = hour(localTime) + 1; _showElements(&umSSDRHours, timeVar, 0, 0); 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).equals("") && !(*map) == NULL) { 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 ((colon && umSSDRColonblink) || umSSDRNumbers[number][countSegments]) { if (range) { for(int i = 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 (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); _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_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_ID_SN_PHOTORESISTOR ptr = (Usermod_SN_Photoresistor*) usermods.lookup(USERMOD_ID_SN_PHOTORESISTOR); #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_ID_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; colorUpdated(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 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; 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) { //subcribe 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 beings iwth 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); 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_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";