From ccd3152b24442dcf5aafcc6efaa92b10e9d3021d Mon Sep 17 00:00:00 2001 From: Gregory Schmidt Date: Wed, 29 Sep 2021 19:23:32 -0800 Subject: [PATCH 01/11] Add Basic Overlay support to Usermods. --- usermods/seven_segment_display/readme.md | 10 + .../usermod_v2_seven_segment_display.h | 247 ++++++++++++++++++ wled00/const.h | 1 + wled00/fcn_declare.h | 2 + wled00/overlay.cpp | 10 +- wled00/um_manager.cpp | 3 +- wled00/usermods_list.cpp | 8 + 7 files changed, 277 insertions(+), 4 deletions(-) create mode 100644 usermods/seven_segment_display/readme.md create mode 100644 usermods/seven_segment_display/usermod_v2_seven_segment_display.h diff --git a/usermods/seven_segment_display/readme.md b/usermods/seven_segment_display/readme.md new file mode 100644 index 00000000..9d485f5f --- /dev/null +++ b/usermods/seven_segment_display/readme.md @@ -0,0 +1,10 @@ +# Seven Segment Display + +In this usermod file you can find the documentation on how to take advantage of the new version 2 usermods! + +## Installation + +Copy `usermod_v2_example.h` to the wled00 directory. +Uncomment the corresponding lines in `usermods_list.cpp` and compile! +_(You shouldn't need to actually install this, it does nothing useful)_ + diff --git a/usermods/seven_segment_display/usermod_v2_seven_segment_display.h b/usermods/seven_segment_display/usermod_v2_seven_segment_display.h new file mode 100644 index 00000000..85582c86 --- /dev/null +++ b/usermods/seven_segment_display/usermod_v2_seven_segment_display.h @@ -0,0 +1,247 @@ +#pragma once + +#include "wled.h" + + + +class SevenSegmentDisplay : public Usermod { + private: + //Private class members. You can declare variables and functions only accessible to your usermod here + unsigned long lastTime = 0; + + // set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer) + bool testBool = false; + unsigned long testULong = 42424242; + float testFloat = 42.42; + String testString = "Forty-Two"; + + // These config variables have defaults set inside readFromConfig() + int testInt; + long testLong; + int8_t testPins[2]; + + 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() { + //Serial.println("Hello from my usermod!"); + } + + /* + * loop() is called continuously. Here you can check for events, read sensors, etc. + * + * Tips: + * 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection. + * Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker. + * + * 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds. + * Instead, use a timer check as shown here. + */ + void loop() { + if (millis() - lastTime > 1000) { + //Serial.println("I'm alive!"); + lastTime = millis(); + } + } + +void onMqttConnect(bool sessionPresent) +{ + if (mqttDeviceTopic[0] == 0) + return; + + for (int pinNr = 0; pinNr < NUM_SWITCH_PINS; pinNr++) { + char buf[128]; + StaticJsonDocument<1024> json; + sprintf(buf, "%s Switch %d", serverDescription, pinNr + 1); + json[F("name")] = buf; + + sprintf(buf, "%s/switch/%d", mqttDeviceTopic, pinNr); + json["~"] = buf; + strcat(buf, "/set"); + mqtt->subscribe(buf, 0); + + json[F("stat_t")] = "~/state"; + json[F("cmd_t")] = "~/set"; + json[F("pl_off")] = F("OFF"); + json[F("pl_on")] = F("ON"); + + char uid[16]; + sprintf(uid, "%s_sw%d", escapedMac.c_str(), pinNr); + json[F("unique_id")] = uid; + + strcpy(buf, mqttDeviceTopic); + strcat(buf, "/status"); + json[F("avty_t")] = buf; + json[F("pl_avail")] = F("online"); + json[F("pl_not_avail")] = F("offline"); + //TODO: dev + sprintf(buf, "homeassistant/switch/%s/config", uid); + char json_str[1024]; + size_t payload_size = serializeJson(json, json_str); + mqtt->publish(buf, 0, true, json_str, payload_size); + updateState(pinNr); + } +} + +bool onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) +{ + //Note: Payload is not necessarily null terminated. Check "len" instead. + for (int pinNr = 0; pinNr < NUM_SWITCH_PINS; pinNr++) { + char buf[64]; + sprintf(buf, "%s/switch/%d/set", mqttDeviceTopic, pinNr); + if (strcmp(topic, buf) == 0) { + //Any string starting with "ON" is interpreted as ON, everything else as OFF + setState(pinNr, len >= 2 && payload[0] == 'O' && payload[1] == 'N'); + return true; + } + } +} + + /* + * 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) + { + int reading = 20; + //this code adds "u":{"Light":[20," lux"]} to the info object + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray lightArr = user.createNestedArray("Light"); //name + lightArr.add(reading); //value + lightArr.add(" lux"); //unit + } + */ + + + /* + * 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) + { + //root["user0"] = userVar0; + } + + + /* + * 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) + { + userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value + //if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!")); + } + + + /* + * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. + * It will be called by WLED when settings are actually saved (for example, LED settings are saved) + * If you want to force saving the current state, use serializeConfig() in your loop(). + * + * CAUTION: serializeConfig() will initiate a filesystem write operation. + * It might cause the LEDs to stutter and will cause flash wear if called too often. + * Use it sparingly and always in the loop, never in network callbacks! + * + * addToConfig() will make your settings editable through the Usermod Settings page automatically. + * + * Usermod Settings Overview: + * - Numeric values are treated as floats in the browser. + * - If the numeric value entered into the browser contains a decimal point, it will be parsed as a C float + * before being returned to the Usermod. The float data type has only 6-7 decimal digits of precision, and + * doubles are not supported, numbers will be rounded to the nearest float value when being parsed. + * The range accepted by the input field is +/- 1.175494351e-38 to +/- 3.402823466e+38. + * - If the numeric value entered into the browser doesn't contain a decimal point, it will be parsed as a + * C int32_t (range: -2147483648 to 2147483647) before being returned to the usermod. + * Overflows or underflows are truncated to the max/min value for an int32_t, and again truncated to the type + * used in the Usermod when reading the value from ArduinoJson. + * - Pin values can be treated differently from an integer value by using the key name "pin" + * - "pin" can contain a single or array of integer values + * - On the Usermod Settings page there is simple checking for pin conflicts and warnings for special pins + * - Red color indicates a conflict. Yellow color indicates a pin with a warning (e.g. an input-only pin) + * - Tip: use int8_t to store the pin value in the Usermod, so a -1 value (pin not set) can be used + * + * See usermod_v2_auto_save.h for an example that saves Flash space by reusing ArduinoJson key name strings + * + * If you need a dedicated settings page with custom layout for your Usermod, that takes a lot more work. + * You will have to add the setting to the HTML, xml.cpp and set.cpp manually. + * See the WLED Soundreactive fork (code and wiki) for reference. https://github.com/atuline/WLED + * + * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! + */ + void addToConfig(JsonObject& root) + { + JsonObject top = root.createNestedObject("exampleUsermod"); + top["great"] = userVar0; //save these vars persistently whenever settings are saved + top["testBool"] = testBool; + top["testInt"] = testInt; + top["testLong"] = testLong; + top["testULong"] = testULong; + top["testFloat"] = testFloat; + top["testString"] = testString; + JsonArray pinArray = top.createNestedArray("pin"); + pinArray.add(testPins[0]); + pinArray.add(testPins[1]); + } + + + /* + * readFromConfig() can be used to read back the custom settings you added with addToConfig(). + * This is called by WLED when settings are loaded (currently this only happens immediately after boot, or after saving on the Usermod Settings page) + * + * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), + * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. + * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) + * + * Return true in case the config values returned from Usermod Settings were complete, or false if you'd like WLED to save your defaults to disk (so any missing values are editable in Usermod Settings) + * + * getJsonValue() returns false if the value is missing, or copies the value into the variable provided and returns true if the value is present + * The configComplete variable is true only if the "exampleUsermod" object and all values are present. If any values are missing, WLED will know to call addToConfig() to save them + * + * This function is guaranteed to be called on boot, but could also be called every time settings are updated + */ + bool readFromConfig(JsonObject& root) + { + // default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor + // setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed) + + JsonObject top = root["exampleUsermod"]; + + bool configComplete = !top.isNull(); + + configComplete &= getJsonValue(top["great"], userVar0); + configComplete &= getJsonValue(top["testBool"], testBool); + configComplete &= getJsonValue(top["testULong"], testULong); + configComplete &= getJsonValue(top["testFloat"], testFloat); + configComplete &= getJsonValue(top["testString"], testString); + + // A 3-argument getJsonValue() assigns the 3rd argument as a default value if the Json value is missing + configComplete &= getJsonValue(top["testInt"], testInt, 42); + configComplete &= getJsonValue(top["testLong"], testLong, -42424242); + configComplete &= getJsonValue(top["pin"][0], testPins[0], -1); + configComplete &= getJsonValue(top["pin"][1], testPins[1], -1); + + return configComplete; + } + + + /* + * 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_SEVEN_SEGMENT_DISPLAY; + } + + //More methods can be added in the future, this example will then be extended. + //Your usermod will remain compatible as it does not need to implement all methods from the Usermod base class! +}; \ No newline at end of file diff --git a/wled00/const.h b/wled00/const.h index e5061627..f7983e64 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -59,6 +59,7 @@ #define USERMOD_ID_ELEKSTUBE_IPS 16 //Usermod "usermod_elekstube_ips.h" #define USERMOD_ID_SN_PHOTORESISTOR 17 //Usermod "usermod_sn_photoresistor.h" #define USERMOD_ID_BATTERY_STATUS_BASIC 18 //Usermod "usermod_v2_battery_status_basic.h" +#define USERMOD_ID_SEVEN_SEGMENT_DISPLAY 19 //Usermod "usermod_v2_seven_segment_display.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 3c458a15..bcf89a2d 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -205,6 +205,7 @@ void sendSysInfoUDP(); class Usermod { public: virtual void loop() {} + virtual void handleOverlayDraw() {} virtual void setup() {} virtual void connected() {} virtual void addToJsonState(JsonObject& obj) {} @@ -224,6 +225,7 @@ class UsermodManager { public: void loop(); + void handleOverlayDraw(); void setup(); void connected(); diff --git a/wled00/overlay.cpp b/wled00/overlay.cpp index 98339686..ef0e2b69 100644 --- a/wled00/overlay.cpp +++ b/wled00/overlay.cpp @@ -17,7 +17,9 @@ void initCronixie() } } - +/* + * handleOverlays is essentially the equivalent of usermods.loop + */ void handleOverlays() { initCronixie(); @@ -111,8 +113,11 @@ void _overlayAnalogCountdown() } } - +/* + * All overlays should be moved to usermods + */ void handleOverlayDraw() { + usermods.handleOverlayDraw(); if (!overlayCurrent) return; switch (overlayCurrent) { @@ -121,7 +126,6 @@ void handleOverlayDraw() { } } - /* * Support for the Cronixie clock */ diff --git a/wled00/um_manager.cpp b/wled00/um_manager.cpp index 040a8450..a9aac784 100644 --- a/wled00/um_manager.cpp +++ b/wled00/um_manager.cpp @@ -4,7 +4,8 @@ */ //Usermod Manager internals -void UsermodManager::loop() { for (byte i = 0; i < numMods; i++) ums[i]->loop(); } +void UsermodManager::loop() { for (byte i = 0; i < numMods; i++) ums[i]->loop(); } +void UsermodManager::handleOverlayDraw() { for (byte i = 0; i < numMods; i++) ums[i]->handleOverlayDraw(); } void UsermodManager::setup() { for (byte i = 0; i < numMods; i++) ums[i]->setup(); } void UsermodManager::connected() { for (byte i = 0; i < numMods; i++) ums[i]->connected(); } diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index fd5ffffd..6b8f930e 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -86,6 +86,10 @@ #include "../usermods/rgb-rotary-encoder/rgb-rotary-encoder.h" #endif +#ifdef USERMOD_SEVEN_SEGMENT +#include "../usermods/seven_segment_display/usermod_v2_seven_segment_display.h" +#endif + void registerUsermods() { /* @@ -167,4 +171,8 @@ void registerUsermods() #ifdef RGB_ROTARY_ENCODER usermods.add(new RgbRotaryEncoderUsermod()); #endif + + #ifdef USERMOD_SEVEN_SEGMENT + usermods.add(new SevenSegmentDisplay()); + #endif } From 22fc58d93b98b9946f9bf63ea2591d2c3f149344 Mon Sep 17 00:00:00 2001 From: Gregory Schmidt Date: Wed, 29 Sep 2021 20:01:26 -0800 Subject: [PATCH 02/11] Add seven segment overlay usermod --- .../usermod_v2_seven_segment_display.h | 235 ++++++++++++++++-- 1 file changed, 221 insertions(+), 14 deletions(-) diff --git a/usermods/seven_segment_display/usermod_v2_seven_segment_display.h b/usermods/seven_segment_display/usermod_v2_seven_segment_display.h index 85582c86..7b2ba702 100644 --- a/usermods/seven_segment_display/usermod_v2_seven_segment_display.h +++ b/usermods/seven_segment_display/usermod_v2_seven_segment_display.h @@ -5,20 +5,224 @@ class SevenSegmentDisplay : public Usermod { + + #define WLED_SS_BUFFLEN 6 private: //Private class members. You can declare variables and functions only accessible to your usermod here - unsigned long lastTime = 0; + unsigned long lastRefresh = 0; + char ssDisplayBuffer[WLED_SS_BUFFLEN+1]; //Runtime buffer of what should be displayed. + char ssCharacterMask[36] = {0x77,0x11,0x6B,0x3B,0x1D,0x3E,0x7E,0x13,0x7F,0x1F,0x5F,0x7B,0x66,0x79,0x6E,0x4E,0x76,0x5D,0x44,0x71,0x5E,0x64,0x27,0x58,0x77,0x4F,0x1F,0x48,0x3E,0x6C,0x75,0x25,0x7D,0x2A,0x3D,0x6B}; + int ssDisplayMessageIdx = 0; //Position of the start of the message to be physically displayed. + // set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer) - bool testBool = false; - unsigned long testULong = 42424242; - float testFloat = 42.42; - String testString = "Forty-Two"; + unsigned long resfreshTime = 497; + byte ssLEDPerSegment = 1; //The number of LEDs in each segment of the 7 seg (total per digit is 7 * ssLedPerSegment) + byte ssLEDPerPeriod = 1; //A Period will have 1x and a Colon will have 2x + int ssStartLED = 0; //The pixel that the display starts at. + // HH - 0-23. hh - 1-12, kk - 1-24 hours + // MM or mm - 0-59 minutes + // SS or ss = 0-59 seconds + // : for a colon + // All others for alpha numeric, (will be blank when displaying time) + char ssDisplayMask[WLED_SS_BUFFLEN+1] = "HHMMSS"; //Physical Display Mask, this should reflect physical equipment. + // ssDisplayConfig + // ------- + // / A / 0 - EDCGFAB + // / F / B 1 - EDCBAFG + // / / 2 - GCDEFAB + // ------- 3 - GBAFEDC + // / G / 4 - FABGEDC + // / E / C 5 - FABCDEG + // / / + // ------- + // D + byte ssDisplayConfig = 5; //Physical configuration of the Seven segment display + char ssDisplayMessage[50] = "ABCDEF";//Message that can scroll across the display + bool ssDoDisplayMessage = 1; //If not, display time. + int ssDisplayMessageTime = 10; //Length of time to display message before returning to time, in seconds. <0 would be indefinite. High Select of ssDisplayMessageTime and time to finish current scroll + int ssScrollSpeed = 500; //Time between advancement of extended message scrolling, in milliseconds. - // These config variables have defaults set inside readFromConfig() - int testInt; - long testLong; - int8_t testPins[2]; + + + void _overlaySevenSegmentProcess() + { + //Do time for now. + if(!ssDoDisplayMessage) + { + //Format the ssDisplayBuffer based on ssDisplayMask + for(int index = 0; index < WLED_SS_BUFFLEN; index++) + { + //Only look for time formatting if there are at least 2 characters left in the buffer. + if((index < WLED_SS_BUFFLEN - 1) && (ssDisplayMask[index] == ssDisplayMask[index + 1])) + { + int timeVar = 0; + switch(ssDisplayMask[index]) + { + case 'h': + timeVar = hourFormat12(localTime); + break; + case 'H': + timeVar = hour(localTime); + break; + case 'k': + timeVar = hour(localTime) + 1; + break; + case 'M': + case 'm': + timeVar = minute(localTime); + break; + case 'S': + case 's': + timeVar = second(localTime); + break; + + } + + //Only want to leave a blank in the hour formatting. + if((ssDisplayMask[index] == 'h' || ssDisplayMask[index] == 'H' || ssDisplayMask[index] == 'k') && timeVar < 10) + ssDisplayBuffer[index] = ' '; + else + ssDisplayBuffer[index] = 0x30 + (timeVar / 10); + ssDisplayBuffer[index + 1] = 0x30 + (timeVar % 10); + + //Need to increment the index because of the second digit. + index++; + } + else + { + ssDisplayBuffer[index] = (ssDisplayMask[index] == ':' ? ':' : ' '); + } + } + } + else + { + /* This will handle displaying a message and the scrolling of the message if its longer than the buffer length */ + //TODO: Progress message starting point depending on display length, message length, display time, etc... + + //Display message + for(int index = 0; index < WLED_SS_BUFFLEN; index++){ + ssDisplayBuffer[index] = ssDisplayMessage[ssDisplayMessageIdx+index]; + } + } + } + + void _overlaySevenSegmentDraw() + { + + //Start pixels at ssStartLED, Use ssLEDPerSegment, ssLEDPerPeriod, ssDisplayBuffer + int indexLED = 0; + for(int indexBuffer = 0; indexBuffer < WLED_SS_BUFFLEN; indexBuffer++) + { + if(ssDisplayBuffer[indexBuffer] == 0) break; + else if(ssDisplayBuffer[indexBuffer] == '.') + { + //Won't ever turn off LED lights for a period. (or will we?) + indexLED += ssLEDPerPeriod; + continue; + } + else if(ssDisplayBuffer[indexBuffer] == ':') + { + //Turn off colon if odd second? + indexLED += ssLEDPerPeriod * 2; + } + else if(ssDisplayBuffer[indexBuffer] == ' ') + { + //Turn off all 7 segments. + _overlaySevenSegmentLEDOutput(0, indexLED); + indexLED += ssLEDPerSegment * 7; + } + else + { + //Turn off correct segments. + _overlaySevenSegmentLEDOutput(_overlaySevenSegmentGetCharMask(ssDisplayBuffer[indexBuffer]), indexLED); + indexLED += ssLEDPerSegment * 7; + } + + } + + } + + void _overlaySevenSegmentLEDOutput(char mask, int indexLED) + { + for(char index = 0; index < 7; index++) + { + if((mask & (0x40 >> index)) != (0x40 >> index)) + { + for(int numPerSeg = 0; numPerSeg < ssLEDPerSegment; numPerSeg++) + { + strip.setPixelColor(indexLED, 0x000000); + } + } + indexLED += ssLEDPerSegment; + } + } + + char _overlaySevenSegmentGetCharMask(char var) + { + //ssCharacterMask + if(var > 0x60) //Essentially a "toLower" call. + var -= 0x20; + if(var > 0x9) //Meaning it is a non-numeric + var -= 0x07; + var -= 0x30; //Shift ascii down to start numeric 0 at index 0. + + char mask = ssCharacterMask[var]; + /* + 0 - EDCGFAB + 1 - EDCBAFG + 2 - GCDEFAB + 3 - GBAFEDC + 4 - FABGEDC + 5 - FABCDEG + */ + switch(ssDisplayConfig) + { + case 1: + mask = _overlaySevenSegmentSwapBits(mask, 0, 3, 1); + mask = _overlaySevenSegmentSwapBits(mask, 1, 2, 1); + break; + case 2: + mask = _overlaySevenSegmentSwapBits(mask, 3, 6, 1); + mask = _overlaySevenSegmentSwapBits(mask, 4, 5, 1); + break; + case 3: + mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3); + mask = _overlaySevenSegmentSwapBits(mask, 3, 6, 1); + mask = _overlaySevenSegmentSwapBits(mask, 4, 5, 1); + break; + case 4: + mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3); + break; + case 5: + mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3); + mask = _overlaySevenSegmentSwapBits(mask, 0, 3, 1); + mask = _overlaySevenSegmentSwapBits(mask, 1, 2, 1); + break; + } + return mask; + } + + char _overlaySevenSegmentSwapBits(char x, char p1, char p2, char n) + { + /* Move all bits of first set to rightmost side */ + char set1 = (x >> p1) & ((1U << n) - 1); + + /* Move all bits of second set to rightmost side */ + char set2 = (x >> p2) & ((1U << n) - 1); + + /* Xor the two sets */ + char Xor = (set1 ^ set2); + + /* Put the Xor bits back to their original positions */ + Xor = (Xor << p1) | (Xor << p2); + + /* Xor the 'Xor' with the original number so that the + two sets are swapped */ + char result = x ^ Xor; + + return result; + } public: //Functions called by WLED @@ -42,12 +246,16 @@ class SevenSegmentDisplay : public Usermod { * Instead, use a timer check as shown here. */ void loop() { - if (millis() - lastTime > 1000) { - //Serial.println("I'm alive!"); - lastTime = millis(); + if (millis() - lastRefresh > resfreshTime) { + _overlaySevenSegmentProcess(); + lastRefresh = millis(); } } + void handleOverlayDraw(){ + _overlaySevenSegmentDraw(); + } + void onMqttConnect(bool sessionPresent) { if (mqttDeviceTopic[0] == 0) @@ -242,6 +450,5 @@ bool onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties return USERMOD_ID_SEVEN_SEGMENT_DISPLAY; } - //More methods can be added in the future, this example will then be extended. - //Your usermod will remain compatible as it does not need to implement all methods from the Usermod base class! + }; \ No newline at end of file From 3ac772badc5ca1d84307a43a99561c428c81f882 Mon Sep 17 00:00:00 2001 From: Gregory Schmidt Date: Wed, 29 Sep 2021 20:59:02 -0800 Subject: [PATCH 03/11] Add seven_seg debug build --- platformio.ini | 16 +- .../usermod_v2_seven_segment_display.h | 340 +++++++++--------- 2 files changed, 185 insertions(+), 171 deletions(-) diff --git a/platformio.ini b/platformio.ini index 0a64a94c..09830217 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,7 +12,7 @@ ; default_envs = travis_esp8266, travis_esp32 # Release binaries -default_envs = nodemcuv2, esp01_1m_full, esp32dev, esp32_eth +;default_envs = nodemcuv2, esp01_1m_full, esp32dev, esp32_eth # Build everything ; default_envs = esp32dev, esp8285_4CH_MagicHome, esp8285_4CH_H801, codm-controller-0.6-rev2, codm-controller-0.6, esp32s2_saola, d1_mini_5CH_Shojo_PCB, d1_mini, sp501e, travis_esp8266, travis_esp32, nodemcuv2, esp32_eth, anavi_miracle_controller, esp07, esp01_1m_full, m5atom, h803wf, d1_mini_ota, heltec_wifi_kit_8, esp8285_5CH_H801, d1_mini_debug, wemos_shield_esp32, elekstube_ips @@ -23,6 +23,7 @@ default_envs = nodemcuv2, esp01_1m_full, esp32dev, esp32_eth ; default_envs = esp01_1m_full ; default_envs = esp07 ; default_envs = d1_mini + default_envs = d1_mini_seven_seg ; default_envs = heltec_wifi_kit_8 ; default_envs = h803wf ; default_envs = d1_mini_debug @@ -498,3 +499,16 @@ monitor_filters = esp32_exception_decoder lib_deps = ${esp32.lib_deps} TFT_eSPI @ ^2.3.70 + +# ------------------------------------------------------------------------------ +# User Mod SevenSegment +# ------------------------------------------------------------------------------ +[env:d1_mini_seven_seg] +board = d1_mini +build_type = debug +platform = ${common.platform_wled_default} +platform_packages = ${common.platform_packages} +board_build.ldscript = ${common.ldscript_4m1m} +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags_esp8266} ${common.debug_flags} -D USERMOD_SEVEN_SEGMENT +lib_deps = ${esp8266.lib_deps} diff --git a/usermods/seven_segment_display/usermod_v2_seven_segment_display.h b/usermods/seven_segment_display/usermod_v2_seven_segment_display.h index 7b2ba702..7a889403 100644 --- a/usermods/seven_segment_display/usermod_v2_seven_segment_display.h +++ b/usermods/seven_segment_display/usermod_v2_seven_segment_display.h @@ -256,199 +256,199 @@ class SevenSegmentDisplay : public Usermod { _overlaySevenSegmentDraw(); } -void onMqttConnect(bool sessionPresent) -{ - if (mqttDeviceTopic[0] == 0) - return; +// void onMqttConnect(bool sessionPresent) +// { +// if (mqttDeviceTopic[0] == 0) +// return; - for (int pinNr = 0; pinNr < NUM_SWITCH_PINS; pinNr++) { - char buf[128]; - StaticJsonDocument<1024> json; - sprintf(buf, "%s Switch %d", serverDescription, pinNr + 1); - json[F("name")] = buf; +// for (int pinNr = 0; pinNr < NUM_SWITCH_PINS; pinNr++) { +// char buf[128]; +// StaticJsonDocument<1024> json; +// sprintf(buf, "%s Switch %d", serverDescription, pinNr + 1); +// json[F("name")] = buf; - sprintf(buf, "%s/switch/%d", mqttDeviceTopic, pinNr); - json["~"] = buf; - strcat(buf, "/set"); - mqtt->subscribe(buf, 0); +// sprintf(buf, "%s/switch/%d", mqttDeviceTopic, pinNr); +// json["~"] = buf; +// strcat(buf, "/set"); +// mqtt->subscribe(buf, 0); - json[F("stat_t")] = "~/state"; - json[F("cmd_t")] = "~/set"; - json[F("pl_off")] = F("OFF"); - json[F("pl_on")] = F("ON"); +// json[F("stat_t")] = "~/state"; +// json[F("cmd_t")] = "~/set"; +// json[F("pl_off")] = F("OFF"); +// json[F("pl_on")] = F("ON"); - char uid[16]; - sprintf(uid, "%s_sw%d", escapedMac.c_str(), pinNr); - json[F("unique_id")] = uid; +// char uid[16]; +// sprintf(uid, "%s_sw%d", escapedMac.c_str(), pinNr); +// json[F("unique_id")] = uid; - strcpy(buf, mqttDeviceTopic); - strcat(buf, "/status"); - json[F("avty_t")] = buf; - json[F("pl_avail")] = F("online"); - json[F("pl_not_avail")] = F("offline"); - //TODO: dev - sprintf(buf, "homeassistant/switch/%s/config", uid); - char json_str[1024]; - size_t payload_size = serializeJson(json, json_str); - mqtt->publish(buf, 0, true, json_str, payload_size); - updateState(pinNr); - } -} +// strcpy(buf, mqttDeviceTopic); +// strcat(buf, "/status"); +// json[F("avty_t")] = buf; +// json[F("pl_avail")] = F("online"); +// json[F("pl_not_avail")] = F("offline"); +// //TODO: dev +// sprintf(buf, "homeassistant/switch/%s/config", uid); +// char json_str[1024]; +// size_t payload_size = serializeJson(json, json_str); +// mqtt->publish(buf, 0, true, json_str, payload_size); +// updateState(pinNr); +// } +// } -bool onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) -{ - //Note: Payload is not necessarily null terminated. Check "len" instead. - for (int pinNr = 0; pinNr < NUM_SWITCH_PINS; pinNr++) { - char buf[64]; - sprintf(buf, "%s/switch/%d/set", mqttDeviceTopic, pinNr); - if (strcmp(topic, buf) == 0) { - //Any string starting with "ON" is interpreted as ON, everything else as OFF - setState(pinNr, len >= 2 && payload[0] == 'O' && payload[1] == 'N'); - return true; - } - } -} +// bool onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) +// { +// //Note: Payload is not necessarily null terminated. Check "len" instead. +// for (int pinNr = 0; pinNr < NUM_SWITCH_PINS; pinNr++) { +// char buf[64]; +// sprintf(buf, "%s/switch/%d/set", mqttDeviceTopic, pinNr); +// if (strcmp(topic, buf) == 0) { +// //Any string starting with "ON" is interpreted as ON, everything else as OFF +// setState(pinNr, len >= 2 && payload[0] == 'O' && payload[1] == 'N'); +// return true; +// } +// } +// } - /* - * 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) - { - int reading = 20; - //this code adds "u":{"Light":[20," lux"]} to the info object - JsonObject user = root["u"]; - if (user.isNull()) user = root.createNestedObject("u"); +// /* +// * 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) +// { +// int reading = 20; +// //this code adds "u":{"Light":[20," lux"]} to the info object +// JsonObject user = root["u"]; +// if (user.isNull()) user = root.createNestedObject("u"); - JsonArray lightArr = user.createNestedArray("Light"); //name - lightArr.add(reading); //value - lightArr.add(" lux"); //unit - } - */ +// JsonArray lightArr = user.createNestedArray("Light"); //name +// lightArr.add(reading); //value +// lightArr.add(" lux"); //unit +// } +// */ - /* - * 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) - { - //root["user0"] = userVar0; - } +// /* +// * 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) +// { +// //root["user0"] = userVar0; +// } - /* - * 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) - { - userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value - //if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!")); - } +// /* +// * 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) +// { +// userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value +// //if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!")); +// } - /* - * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. - * It will be called by WLED when settings are actually saved (for example, LED settings are saved) - * If you want to force saving the current state, use serializeConfig() in your loop(). - * - * CAUTION: serializeConfig() will initiate a filesystem write operation. - * It might cause the LEDs to stutter and will cause flash wear if called too often. - * Use it sparingly and always in the loop, never in network callbacks! - * - * addToConfig() will make your settings editable through the Usermod Settings page automatically. - * - * Usermod Settings Overview: - * - Numeric values are treated as floats in the browser. - * - If the numeric value entered into the browser contains a decimal point, it will be parsed as a C float - * before being returned to the Usermod. The float data type has only 6-7 decimal digits of precision, and - * doubles are not supported, numbers will be rounded to the nearest float value when being parsed. - * The range accepted by the input field is +/- 1.175494351e-38 to +/- 3.402823466e+38. - * - If the numeric value entered into the browser doesn't contain a decimal point, it will be parsed as a - * C int32_t (range: -2147483648 to 2147483647) before being returned to the usermod. - * Overflows or underflows are truncated to the max/min value for an int32_t, and again truncated to the type - * used in the Usermod when reading the value from ArduinoJson. - * - Pin values can be treated differently from an integer value by using the key name "pin" - * - "pin" can contain a single or array of integer values - * - On the Usermod Settings page there is simple checking for pin conflicts and warnings for special pins - * - Red color indicates a conflict. Yellow color indicates a pin with a warning (e.g. an input-only pin) - * - Tip: use int8_t to store the pin value in the Usermod, so a -1 value (pin not set) can be used - * - * See usermod_v2_auto_save.h for an example that saves Flash space by reusing ArduinoJson key name strings - * - * If you need a dedicated settings page with custom layout for your Usermod, that takes a lot more work. - * You will have to add the setting to the HTML, xml.cpp and set.cpp manually. - * See the WLED Soundreactive fork (code and wiki) for reference. https://github.com/atuline/WLED - * - * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! - */ - void addToConfig(JsonObject& root) - { - JsonObject top = root.createNestedObject("exampleUsermod"); - top["great"] = userVar0; //save these vars persistently whenever settings are saved - top["testBool"] = testBool; - top["testInt"] = testInt; - top["testLong"] = testLong; - top["testULong"] = testULong; - top["testFloat"] = testFloat; - top["testString"] = testString; - JsonArray pinArray = top.createNestedArray("pin"); - pinArray.add(testPins[0]); - pinArray.add(testPins[1]); - } +// /* +// * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. +// * It will be called by WLED when settings are actually saved (for example, LED settings are saved) +// * If you want to force saving the current state, use serializeConfig() in your loop(). +// * +// * CAUTION: serializeConfig() will initiate a filesystem write operation. +// * It might cause the LEDs to stutter and will cause flash wear if called too often. +// * Use it sparingly and always in the loop, never in network callbacks! +// * +// * addToConfig() will make your settings editable through the Usermod Settings page automatically. +// * +// * Usermod Settings Overview: +// * - Numeric values are treated as floats in the browser. +// * - If the numeric value entered into the browser contains a decimal point, it will be parsed as a C float +// * before being returned to the Usermod. The float data type has only 6-7 decimal digits of precision, and +// * doubles are not supported, numbers will be rounded to the nearest float value when being parsed. +// * The range accepted by the input field is +/- 1.175494351e-38 to +/- 3.402823466e+38. +// * - If the numeric value entered into the browser doesn't contain a decimal point, it will be parsed as a +// * C int32_t (range: -2147483648 to 2147483647) before being returned to the usermod. +// * Overflows or underflows are truncated to the max/min value for an int32_t, and again truncated to the type +// * used in the Usermod when reading the value from ArduinoJson. +// * - Pin values can be treated differently from an integer value by using the key name "pin" +// * - "pin" can contain a single or array of integer values +// * - On the Usermod Settings page there is simple checking for pin conflicts and warnings for special pins +// * - Red color indicates a conflict. Yellow color indicates a pin with a warning (e.g. an input-only pin) +// * - Tip: use int8_t to store the pin value in the Usermod, so a -1 value (pin not set) can be used +// * +// * See usermod_v2_auto_save.h for an example that saves Flash space by reusing ArduinoJson key name strings +// * +// * If you need a dedicated settings page with custom layout for your Usermod, that takes a lot more work. +// * You will have to add the setting to the HTML, xml.cpp and set.cpp manually. +// * See the WLED Soundreactive fork (code and wiki) for reference. https://github.com/atuline/WLED +// * +// * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! +// */ +// void addToConfig(JsonObject& root) +// { +// JsonObject top = root.createNestedObject("exampleUsermod"); +// top["great"] = userVar0; //save these vars persistently whenever settings are saved +// top["testBool"] = testBool; +// top["testInt"] = testInt; +// top["testLong"] = testLong; +// top["testULong"] = testULong; +// top["testFloat"] = testFloat; +// top["testString"] = testString; +// JsonArray pinArray = top.createNestedArray("pin"); +// pinArray.add(testPins[0]); +// pinArray.add(testPins[1]); +// } - /* - * readFromConfig() can be used to read back the custom settings you added with addToConfig(). - * This is called by WLED when settings are loaded (currently this only happens immediately after boot, or after saving on the Usermod Settings page) - * - * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), - * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. - * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) - * - * Return true in case the config values returned from Usermod Settings were complete, or false if you'd like WLED to save your defaults to disk (so any missing values are editable in Usermod Settings) - * - * getJsonValue() returns false if the value is missing, or copies the value into the variable provided and returns true if the value is present - * The configComplete variable is true only if the "exampleUsermod" object and all values are present. If any values are missing, WLED will know to call addToConfig() to save them - * - * This function is guaranteed to be called on boot, but could also be called every time settings are updated - */ - bool readFromConfig(JsonObject& root) - { - // default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor - // setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed) +// /* +// * readFromConfig() can be used to read back the custom settings you added with addToConfig(). +// * This is called by WLED when settings are loaded (currently this only happens immediately after boot, or after saving on the Usermod Settings page) +// * +// * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), +// * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. +// * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) +// * +// * Return true in case the config values returned from Usermod Settings were complete, or false if you'd like WLED to save your defaults to disk (so any missing values are editable in Usermod Settings) +// * +// * getJsonValue() returns false if the value is missing, or copies the value into the variable provided and returns true if the value is present +// * The configComplete variable is true only if the "exampleUsermod" object and all values are present. If any values are missing, WLED will know to call addToConfig() to save them +// * +// * This function is guaranteed to be called on boot, but could also be called every time settings are updated +// */ +// bool readFromConfig(JsonObject& root) +// { +// // default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor +// // setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed) - JsonObject top = root["exampleUsermod"]; +// JsonObject top = root["exampleUsermod"]; - bool configComplete = !top.isNull(); +// bool configComplete = !top.isNull(); - configComplete &= getJsonValue(top["great"], userVar0); - configComplete &= getJsonValue(top["testBool"], testBool); - configComplete &= getJsonValue(top["testULong"], testULong); - configComplete &= getJsonValue(top["testFloat"], testFloat); - configComplete &= getJsonValue(top["testString"], testString); +// configComplete &= getJsonValue(top["great"], userVar0); +// configComplete &= getJsonValue(top["testBool"], testBool); +// configComplete &= getJsonValue(top["testULong"], testULong); +// configComplete &= getJsonValue(top["testFloat"], testFloat); +// configComplete &= getJsonValue(top["testString"], testString); - // A 3-argument getJsonValue() assigns the 3rd argument as a default value if the Json value is missing - configComplete &= getJsonValue(top["testInt"], testInt, 42); - configComplete &= getJsonValue(top["testLong"], testLong, -42424242); - configComplete &= getJsonValue(top["pin"][0], testPins[0], -1); - configComplete &= getJsonValue(top["pin"][1], testPins[1], -1); +// // A 3-argument getJsonValue() assigns the 3rd argument as a default value if the Json value is missing +// configComplete &= getJsonValue(top["testInt"], testInt, 42); +// configComplete &= getJsonValue(top["testLong"], testLong, -42424242); +// configComplete &= getJsonValue(top["pin"][0], testPins[0], -1); +// configComplete &= getJsonValue(top["pin"][1], testPins[1], -1); - return configComplete; - } +// return configComplete; +// } - /* - * 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_SEVEN_SEGMENT_DISPLAY; - } +// /* +// * 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_SEVEN_SEGMENT_DISPLAY; +// } }; \ No newline at end of file From d00b4335b59cc1de79a1cf31e991e69d46781953 Mon Sep 17 00:00:00 2001 From: Gregory Schmidt Date: Fri, 1 Oct 2021 21:34:20 -0800 Subject: [PATCH 04/11] Add scrolling message to seven seg um --- .../usermod_v2_seven_segment_display.h | 49 ++++++++++++------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/usermods/seven_segment_display/usermod_v2_seven_segment_display.h b/usermods/seven_segment_display/usermod_v2_seven_segment_display.h index 7a889403..68fabb1e 100644 --- a/usermods/seven_segment_display/usermod_v2_seven_segment_display.h +++ b/usermods/seven_segment_display/usermod_v2_seven_segment_display.h @@ -7,11 +7,13 @@ class SevenSegmentDisplay : public Usermod { #define WLED_SS_BUFFLEN 6 + #define REFRESHTIME 497 private: //Private class members. You can declare variables and functions only accessible to your usermod here unsigned long lastRefresh = 0; + unsigned long lastCharacterStep = 0; char ssDisplayBuffer[WLED_SS_BUFFLEN+1]; //Runtime buffer of what should be displayed. - char ssCharacterMask[36] = {0x77,0x11,0x6B,0x3B,0x1D,0x3E,0x7E,0x13,0x7F,0x1F,0x5F,0x7B,0x66,0x79,0x6E,0x4E,0x76,0x5D,0x44,0x71,0x5E,0x64,0x27,0x58,0x77,0x4F,0x1F,0x48,0x3E,0x6C,0x75,0x25,0x7D,0x2A,0x3D,0x6B}; + char ssCharacterMask[36] = {0x77,0x11,0x6B,0x3B,0x1D,0x3E,0x7E,0x13,0x7F,0x1F,0x5F,0x7C,0x66,0x79,0x6E,0x4E,0x76,0x5D,0x44,0x71,0x5E,0x64,0x27,0x58,0x77,0x4F,0x1F,0x48,0x3E,0x6C,0x75,0x25,0x7D,0x2A,0x3D,0x6B}; int ssDisplayMessageIdx = 0; //Position of the start of the message to be physically displayed. @@ -20,13 +22,14 @@ class SevenSegmentDisplay : public Usermod { byte ssLEDPerSegment = 1; //The number of LEDs in each segment of the 7 seg (total per digit is 7 * ssLedPerSegment) byte ssLEDPerPeriod = 1; //A Period will have 1x and a Colon will have 2x int ssStartLED = 0; //The pixel that the display starts at. - // HH - 0-23. hh - 1-12, kk - 1-24 hours + /* HH - 0-23. hh - 1-12, kk - 1-24 hours // MM or mm - 0-59 minutes // SS or ss = 0-59 seconds // : for a colon // All others for alpha numeric, (will be blank when displaying time) + */ char ssDisplayMask[WLED_SS_BUFFLEN+1] = "HHMMSS"; //Physical Display Mask, this should reflect physical equipment. - // ssDisplayConfig + /* ssDisplayConfig // ------- // / A / 0 - EDCGFAB // / F / B 1 - EDCBAFG @@ -37,15 +40,15 @@ class SevenSegmentDisplay : public Usermod { // / / // ------- // D + */ byte ssDisplayConfig = 5; //Physical configuration of the Seven segment display - char ssDisplayMessage[50] = "ABCDEF";//Message that can scroll across the display - bool ssDoDisplayMessage = 1; //If not, display time. - int ssDisplayMessageTime = 10; //Length of time to display message before returning to time, in seconds. <0 would be indefinite. High Select of ssDisplayMessageTime and time to finish current scroll - int ssScrollSpeed = 500; //Time between advancement of extended message scrolling, in milliseconds. + char ssDisplayMessage[50] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";//Message that can scroll across the display + bool ssDoDisplayMessage = false; //If not, display time. + unsigned long ssScrollSpeed = 1000; //Time between advancement of extended message scrolling, in milliseconds. - void _overlaySevenSegmentProcess() + unsigned long _overlaySevenSegmentProcess() { //Do time for now. if(!ssDoDisplayMessage) @@ -94,16 +97,33 @@ class SevenSegmentDisplay : public Usermod { ssDisplayBuffer[index] = (ssDisplayMask[index] == ':' ? ':' : ' '); } } + return REFRESHTIME; } else { /* This will handle displaying a message and the scrolling of the message if its longer than the buffer length */ //TODO: Progress message starting point depending on display length, message length, display time, etc... + //Increase the displayed message index to progress it one character. + ssDisplayMessageIdx++; + + //Check to see if the message has scrolled completely + size_t len = strlen(ssDisplayMessage); //really should grab this when the message is set. TODO + if(ssDisplayMessageIdx > len) + { + //If it has displayed the whole message and the display time has exceeded, go back to clock. + ssDisplayMessageIdx = 0; + ssDoDisplayMessage = false; + return REFRESHTIME; + } //Display message for(int index = 0; index < WLED_SS_BUFFLEN; index++){ - ssDisplayBuffer[index] = ssDisplayMessage[ssDisplayMessageIdx+index]; + if(ssDisplayMessageIdx + index < len) + ssDisplayBuffer[index] = ssDisplayMessage[ssDisplayMessageIdx+index]; + else + ssDisplayBuffer[index] = ' '; } + return ssScrollSpeed; } } @@ -163,7 +183,7 @@ class SevenSegmentDisplay : public Usermod { //ssCharacterMask if(var > 0x60) //Essentially a "toLower" call. var -= 0x20; - if(var > 0x9) //Meaning it is a non-numeric + if(var > 0x39) //Meaning it is a non-numeric var -= 0x07; var -= 0x30; //Shift ascii down to start numeric 0 at index 0. @@ -237,17 +257,10 @@ class SevenSegmentDisplay : public Usermod { /* * loop() is called continuously. Here you can check for events, read sensors, etc. - * - * Tips: - * 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection. - * Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker. - * - * 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds. - * Instead, use a timer check as shown here. */ void loop() { if (millis() - lastRefresh > resfreshTime) { - _overlaySevenSegmentProcess(); + resfreshTime = _overlaySevenSegmentProcess(); lastRefresh = millis(); } } From 5dac6690d7070d5c52010ec45b7e06a62c97af3c Mon Sep 17 00:00:00 2001 From: Gregory Schmidt Date: Thu, 7 Oct 2021 23:56:57 -0800 Subject: [PATCH 05/11] Add msg scroll. Add MQTT and Config support --- .../usermod_v2_seven_segment_display.h | 801 +++++++++--------- 1 file changed, 404 insertions(+), 397 deletions(-) diff --git a/usermods/seven_segment_display/usermod_v2_seven_segment_display.h b/usermods/seven_segment_display/usermod_v2_seven_segment_display.h index 68fabb1e..e2ea7937 100644 --- a/usermods/seven_segment_display/usermod_v2_seven_segment_display.h +++ b/usermods/seven_segment_display/usermod_v2_seven_segment_display.h @@ -2,34 +2,37 @@ #include "wled.h" +class SevenSegmentDisplay : public Usermod +{ +#define WLED_SS_BUFFLEN 6 +#define REFRESHTIME 497 +private: + //Runtime variables. + unsigned long lastRefresh = 0; + unsigned long lastCharacterStep = 0; + //char ssDisplayBuffer[WLED_SS_BUFFLEN+1]; //Runtime buffer of what should be displayed. + String ssDisplayBuffer = ""; + char ssCharacterMask[36] = {0x77, 0x11, 0x6B, 0x3B, 0x1D, 0x3E, 0x7E, 0x13, 0x7F, 0x1F, 0x5F, 0x7C, 0x66, 0x79, 0x6E, 0x4E, 0x76, 0x5D, 0x44, 0x71, 0x5E, 0x64, 0x27, 0x58, 0x77, 0x4F, 0x1F, 0x48, 0x3E, 0x6C, 0x75, 0x25, 0x7D, 0x2A, 0x3D, 0x6B}; + int ssDisplayMessageIdx = 0; //Position of the start of the message to be physically displayed. + bool ssDoDisplayTime = true; + int ssVirtualDisplayMessageIdxStart = 0; + int ssVirtualDisplayMessageIdxEnd = 0; + unsigned long resfreshTime = 497; -class SevenSegmentDisplay : public Usermod { - - #define WLED_SS_BUFFLEN 6 - #define REFRESHTIME 497 - private: - //Private class members. You can declare variables and functions only accessible to your usermod here - unsigned long lastRefresh = 0; - unsigned long lastCharacterStep = 0; - char ssDisplayBuffer[WLED_SS_BUFFLEN+1]; //Runtime buffer of what should be displayed. - char ssCharacterMask[36] = {0x77,0x11,0x6B,0x3B,0x1D,0x3E,0x7E,0x13,0x7F,0x1F,0x5F,0x7C,0x66,0x79,0x6E,0x4E,0x76,0x5D,0x44,0x71,0x5E,0x64,0x27,0x58,0x77,0x4F,0x1F,0x48,0x3E,0x6C,0x75,0x25,0x7D,0x2A,0x3D,0x6B}; - int ssDisplayMessageIdx = 0; //Position of the start of the message to be physically displayed. - - - // set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer) - unsigned long resfreshTime = 497; - byte ssLEDPerSegment = 1; //The number of LEDs in each segment of the 7 seg (total per digit is 7 * ssLedPerSegment) - byte ssLEDPerPeriod = 1; //A Period will have 1x and a Colon will have 2x - int ssStartLED = 0; //The pixel that the display starts at. - /* HH - 0-23. hh - 1-12, kk - 1-24 hours + // set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer) + int ssLEDPerSegment = 1; //The number of LEDs in each segment of the 7 seg (total per digit is 7 * ssLedPerSegment) + int ssLEDPerPeriod = 1; //A Period will have 1x and a Colon will have 2x + int ssStartLED = 0; //The pixel that the display starts at. + /* HH - 0-23. hh - 1-12, kk - 1-24 hours // MM or mm - 0-59 minutes // SS or ss = 0-59 seconds // : for a colon // All others for alpha numeric, (will be blank when displaying time) */ - char ssDisplayMask[WLED_SS_BUFFLEN+1] = "HHMMSS"; //Physical Display Mask, this should reflect physical equipment. - /* ssDisplayConfig + //char ssDisplayMask[WLED_SS_BUFFLEN+1] = "HHMMSS"; //Physical Display Mask, this should reflect physical equipment. + String ssDisplayMask = "HHMMSS"; + /* ssDisplayConfig // ------- // / A / 0 - EDCGFAB // / F / B 1 - EDCBAFG @@ -41,153 +44,153 @@ class SevenSegmentDisplay : public Usermod { // ------- // D */ - byte ssDisplayConfig = 5; //Physical configuration of the Seven segment display - char ssDisplayMessage[50] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";//Message that can scroll across the display - bool ssDoDisplayMessage = false; //If not, display time. - unsigned long ssScrollSpeed = 1000; //Time between advancement of extended message scrolling, in milliseconds. + int ssDisplayConfig = 5; //Physical configuration of the Seven segment display + String ssDisplayMessage = "testing123"; + bool ssTimeEnabled = true; //If not, display message. + unsigned long ssScrollSpeed = 1000; //Time between advancement of extended message scrolling, in milliseconds. - - - unsigned long _overlaySevenSegmentProcess() + unsigned long _overlaySevenSegmentProcess() + { + //Do time for now. + if (ssDoDisplayTime) { - //Do time for now. - if(!ssDoDisplayMessage) + //Format the ssDisplayBuffer based on ssDisplayMask + int displayMaskLen = static_cast(ssDisplayMask.length()); + for (int index = 0; index < displayMaskLen; index++) { - //Format the ssDisplayBuffer based on ssDisplayMask - for(int index = 0; index < WLED_SS_BUFFLEN; index++) + //Only look for time formatting if there are at least 2 characters left in the buffer. + if ((index < displayMaskLen - 1) && (ssDisplayMask[index] == ssDisplayMask[index + 1])) { - //Only look for time formatting if there are at least 2 characters left in the buffer. - if((index < WLED_SS_BUFFLEN - 1) && (ssDisplayMask[index] == ssDisplayMask[index + 1])) + int timeVar = 0; + switch (ssDisplayMask[index]) { - int timeVar = 0; - switch(ssDisplayMask[index]) - { - case 'h': - timeVar = hourFormat12(localTime); - break; - case 'H': - timeVar = hour(localTime); - break; - case 'k': - timeVar = hour(localTime) + 1; - break; - case 'M': - case 'm': - timeVar = minute(localTime); - break; - case 'S': - case 's': - timeVar = second(localTime); - break; - - } - - //Only want to leave a blank in the hour formatting. - if((ssDisplayMask[index] == 'h' || ssDisplayMask[index] == 'H' || ssDisplayMask[index] == 'k') && timeVar < 10) - ssDisplayBuffer[index] = ' '; - else - ssDisplayBuffer[index] = 0x30 + (timeVar / 10); - ssDisplayBuffer[index + 1] = 0x30 + (timeVar % 10); - - //Need to increment the index because of the second digit. - index++; + case 'h': + timeVar = hourFormat12(localTime); + break; + case 'H': + timeVar = hour(localTime); + break; + case 'k': + timeVar = hour(localTime) + 1; + break; + case 'M': + case 'm': + timeVar = minute(localTime); + break; + case 'S': + case 's': + timeVar = second(localTime); + break; } - else - { - ssDisplayBuffer[index] = (ssDisplayMask[index] == ':' ? ':' : ' '); - } - } - return REFRESHTIME; - } - else - { - /* This will handle displaying a message and the scrolling of the message if its longer than the buffer length */ - //TODO: Progress message starting point depending on display length, message length, display time, etc... - //Increase the displayed message index to progress it one character. - ssDisplayMessageIdx++; - - //Check to see if the message has scrolled completely - size_t len = strlen(ssDisplayMessage); //really should grab this when the message is set. TODO - if(ssDisplayMessageIdx > len) - { - //If it has displayed the whole message and the display time has exceeded, go back to clock. - ssDisplayMessageIdx = 0; - ssDoDisplayMessage = false; - return REFRESHTIME; - } - //Display message - for(int index = 0; index < WLED_SS_BUFFLEN; index++){ - if(ssDisplayMessageIdx + index < len) - ssDisplayBuffer[index] = ssDisplayMessage[ssDisplayMessageIdx+index]; - else + //Only want to leave a blank in the hour formatting. + if ((ssDisplayMask[index] == 'h' || ssDisplayMask[index] == 'H' || ssDisplayMask[index] == 'k') && timeVar < 10) ssDisplayBuffer[index] = ' '; - } - return ssScrollSpeed; - } - } - - void _overlaySevenSegmentDraw() - { - - //Start pixels at ssStartLED, Use ssLEDPerSegment, ssLEDPerPeriod, ssDisplayBuffer - int indexLED = 0; - for(int indexBuffer = 0; indexBuffer < WLED_SS_BUFFLEN; indexBuffer++) - { - if(ssDisplayBuffer[indexBuffer] == 0) break; - else if(ssDisplayBuffer[indexBuffer] == '.') - { - //Won't ever turn off LED lights for a period. (or will we?) - indexLED += ssLEDPerPeriod; - continue; - } - else if(ssDisplayBuffer[indexBuffer] == ':') - { - //Turn off colon if odd second? - indexLED += ssLEDPerPeriod * 2; - } - else if(ssDisplayBuffer[indexBuffer] == ' ') - { - //Turn off all 7 segments. - _overlaySevenSegmentLEDOutput(0, indexLED); - indexLED += ssLEDPerSegment * 7; + else + ssDisplayBuffer[index] = 0x30 + (timeVar / 10); + ssDisplayBuffer[index + 1] = 0x30 + (timeVar % 10); + + //Need to increment the index because of the second digit. + index++; } else { - //Turn off correct segments. - _overlaySevenSegmentLEDOutput(_overlaySevenSegmentGetCharMask(ssDisplayBuffer[indexBuffer]), indexLED); - indexLED += ssLEDPerSegment * 7; + ssDisplayBuffer[index] = (ssDisplayMask[index] == ':' ? ':' : ' '); } - } - + return REFRESHTIME; } - - void _overlaySevenSegmentLEDOutput(char mask, int indexLED) + else { - for(char index = 0; index < 7; index++) + /* This will handle displaying a message and the scrolling of the message if its longer than the buffer length */ + + //Check to see if the message has scrolled completely + int len = static_cast(ssDisplayMessage.length()); + if (ssDisplayMessageIdx > len) { - if((mask & (0x40 >> index)) != (0x40 >> index)) - { - for(int numPerSeg = 0; numPerSeg < ssLEDPerSegment; numPerSeg++) - { - strip.setPixelColor(indexLED, 0x000000); - } - } - indexLED += ssLEDPerSegment; + //If it has scrolled the whole message, reset it. + setSevenSegmentMessage(ssDisplayMessage); + return REFRESHTIME; + } + //Display message + int displayMaskLen = static_cast(ssDisplayMask.length()); + for (int index = 0; index < displayMaskLen; index++) + { + if (ssDisplayMessageIdx + index < len && ssDisplayMessageIdx + index >= 0) + ssDisplayBuffer[index] = ssDisplayMessage[ssDisplayMessageIdx + index]; + else + ssDisplayBuffer[index] = ' '; + } + + //Increase the displayed message index to progress it one character if the length exceeds the display length. + if (len > displayMaskLen) + ssDisplayMessageIdx++; + + return ssScrollSpeed; + } + } + + void _overlaySevenSegmentDraw() + { + + //Start pixels at ssStartLED, Use ssLEDPerSegment, ssLEDPerPeriod, ssDisplayBuffer + int indexLED = 0; + int displayMaskLen = static_cast(ssDisplayMask.length()); + for (int indexBuffer = 0; indexBuffer < displayMaskLen; indexBuffer++) + { + if (ssDisplayBuffer[indexBuffer] == 0) + break; + else if (ssDisplayBuffer[indexBuffer] == '.') + { + //Won't ever turn off LED lights for a period. (or will we?) + indexLED += ssLEDPerPeriod; + continue; + } + else if (ssDisplayBuffer[indexBuffer] == ':') + { + //Turn off colon if odd second? + indexLED += ssLEDPerPeriod * 2; + } + else if (ssDisplayBuffer[indexBuffer] == ' ') + { + //Turn off all 7 segments. + _overlaySevenSegmentLEDOutput(0, indexLED); + indexLED += ssLEDPerSegment * 7; + } + else + { + //Turn off correct segments. + _overlaySevenSegmentLEDOutput(_overlaySevenSegmentGetCharMask(ssDisplayBuffer[indexBuffer]), indexLED); + indexLED += ssLEDPerSegment * 7; } } + } - char _overlaySevenSegmentGetCharMask(char var) + void _overlaySevenSegmentLEDOutput(char mask, int indexLED) + { + for (char index = 0; index < 7; index++) { - //ssCharacterMask - if(var > 0x60) //Essentially a "toLower" call. - var -= 0x20; - if(var > 0x39) //Meaning it is a non-numeric - var -= 0x07; - var -= 0x30; //Shift ascii down to start numeric 0 at index 0. - - char mask = ssCharacterMask[var]; + if ((mask & (0x40 >> index)) != (0x40 >> index)) + { + for (int numPerSeg = 0; numPerSeg < ssLEDPerSegment; numPerSeg++) + { + strip.setPixelColor(indexLED, 0x000000); + } + } + indexLED += ssLEDPerSegment; + } + } + + char _overlaySevenSegmentGetCharMask(char var) + { + //ssCharacterMask + if (var > 0x60) //Essentially a "toLower" call. + var -= 0x20; + if (var > 0x39) //Meaning it is a non-numeric + var -= 0x07; + var -= 0x30; //Shift ascii down to start numeric 0 at index 0. + + char mask = ssCharacterMask[static_cast(var)]; /* 0 - EDCGFAB 1 - EDCBAFG @@ -196,272 +199,276 @@ class SevenSegmentDisplay : public Usermod { 4 - FABGEDC 5 - FABCDEG */ - switch(ssDisplayConfig) - { - case 1: - mask = _overlaySevenSegmentSwapBits(mask, 0, 3, 1); - mask = _overlaySevenSegmentSwapBits(mask, 1, 2, 1); - break; - case 2: - mask = _overlaySevenSegmentSwapBits(mask, 3, 6, 1); - mask = _overlaySevenSegmentSwapBits(mask, 4, 5, 1); - break; - case 3: - mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3); - mask = _overlaySevenSegmentSwapBits(mask, 3, 6, 1); - mask = _overlaySevenSegmentSwapBits(mask, 4, 5, 1); - break; - case 4: - mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3); - break; - case 5: - mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3); - mask = _overlaySevenSegmentSwapBits(mask, 0, 3, 1); - mask = _overlaySevenSegmentSwapBits(mask, 1, 2, 1); - break; - } - return mask; - } - - char _overlaySevenSegmentSwapBits(char x, char p1, char p2, char n) + switch (ssDisplayConfig) { - /* Move all bits of first set to rightmost side */ - char set1 = (x >> p1) & ((1U << n) - 1); - - /* Move all bits of second set to rightmost side */ - char set2 = (x >> p2) & ((1U << n) - 1); - - /* Xor the two sets */ - char Xor = (set1 ^ set2); - - /* Put the Xor bits back to their original positions */ - Xor = (Xor << p1) | (Xor << p2); - - /* Xor the 'Xor' with the original number so that the + case 1: + mask = _overlaySevenSegmentSwapBits(mask, 0, 3, 1); + mask = _overlaySevenSegmentSwapBits(mask, 1, 2, 1); + break; + case 2: + mask = _overlaySevenSegmentSwapBits(mask, 3, 6, 1); + mask = _overlaySevenSegmentSwapBits(mask, 4, 5, 1); + break; + case 3: + mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3); + mask = _overlaySevenSegmentSwapBits(mask, 3, 6, 1); + mask = _overlaySevenSegmentSwapBits(mask, 4, 5, 1); + break; + case 4: + mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3); + break; + case 5: + mask = _overlaySevenSegmentSwapBits(mask, 0, 4, 3); + mask = _overlaySevenSegmentSwapBits(mask, 0, 3, 1); + mask = _overlaySevenSegmentSwapBits(mask, 1, 2, 1); + break; + } + return mask; + } + + char _overlaySevenSegmentSwapBits(char x, char p1, char p2, char n) + { + /* Move all bits of first set to rightmost side */ + char set1 = (x >> p1) & ((1U << n) - 1); + + /* Move all bits of second set to rightmost side */ + char set2 = (x >> p2) & ((1U << n) - 1); + + /* Xor the two sets */ + char Xor = (set1 ^ set2); + + /* Put the Xor bits back to their original positions */ + Xor = (Xor << p1) | (Xor << p2); + + /* Xor the 'Xor' with the original number so that the two sets are swapped */ - char result = x ^ Xor; + char result = x ^ Xor; + + return result; + } + void _publishMQTTint(const char* subTopic, int value) + { + char buffer[64]; + char valBuffer[12]; + sprintf_P(buffer, PSTR("%s/sevenSeg/%s"), mqttDeviceTopic, subTopic); + sprintf_P(valBuffer, PSTR("%d"), value); + mqtt->publish(buffer, 2, true, valBuffer); + } + void _publishMQTTstr(const char* subTopic, String Value) + { + char buffer[64]; + sprintf_P(buffer, PSTR("%s/sevenSeg/%s"), mqttDeviceTopic, subTopic); + mqtt->publish(buffer, 2, true, Value.c_str(), Value.length()); + } + void _updateMQTT() + { + _publishMQTTint(PSTR("perSegment"), ssLEDPerSegment); + _publishMQTTint(PSTR("perPeriod"), ssLEDPerPeriod); + _publishMQTTint(PSTR("startIdx"), ssStartLED); + _publishMQTTint(PSTR("displayCfg"), ssDisplayConfig); + _publishMQTTint(PSTR("timeEnable"), ssTimeEnabled); + _publishMQTTint(PSTR("scrollSpd"), ssScrollSpeed); + + _publishMQTTstr(PSTR("displayMask"), ssDisplayMask); + _publishMQTTstr(PSTR("displayMsg"), ssDisplayMessage); + } + + void _handleMQTT(char *topic, char *payload) + { + if(strcmp_P(topic, PSTR("perSegment"))==0) + { + ssLEDPerSegment = strtol(payload, NULL, 10); + _publishMQTTint(topic, ssLEDPerSegment); + return; + } + if(strcmp_P(topic, PSTR("perPeriod"))==0) + { + ssLEDPerPeriod = strtol(payload, NULL, 10); + _publishMQTTint(topic, ssLEDPerPeriod); + return; + } + if(strcmp_P(topic, PSTR("startIdx"))==0) + { + ssStartLED = strtol(payload, NULL, 10); + _publishMQTTint(topic, ssStartLED); + return; + } + if(strcmp_P(topic, PSTR("displayCfg"))==0) + { + ssDisplayConfig = strtol(payload, NULL, 10); + _publishMQTTint(topic, ssDisplayConfig); + return; + } + if(strcmp_P(topic, PSTR("timeEnable"))==0) + { + ssTimeEnabled = strtol(payload, NULL, 10); + ssDoDisplayTime = ssTimeEnabled; + _publishMQTTint(topic, ssTimeEnabled); + return; + } + if(strcmp_P(topic, PSTR("scrollSpd"))==0) + { + ssScrollSpeed = strtol(payload, NULL, 10); + _publishMQTTint(topic, ssScrollSpeed); + return; + } + if(strcmp_P(topic, PSTR("displayMask"))==0) + { + ssDisplayMask = String(payload); + ssDisplayBuffer = ssDisplayMask; + _publishMQTTstr(topic, ssDisplayMask); + return; + } + if(strcmp_P(topic, PSTR("displayMsg"))==0) + { + setSevenSegmentMessage(String(payload)); + return; + } + } + +public: + void setSevenSegmentMessage(String message) + { + //If the message isn't blank display it otherwise show time, if enabled. + if (message.length() < 1 || message == "~") + ssDoDisplayTime = ssTimeEnabled; + else + ssDoDisplayTime = false; + + //Determine is the message is longer than the display, if it is configure it to scroll the message. + if (message.length() > ssDisplayMask.length()) + ssDisplayMessageIdx = -ssDisplayMask.length(); + else + ssDisplayMessageIdx = 0; - return result; - } + //If the message isn't the same, update runtime/mqtt (most calls will be resetting message scroll) + if(!ssDisplayMessage.equals(message)) + { + _publishMQTTstr(PSTR("displayMsg"), message); + ssDisplayMessage = message; + } + } + //Functions called by WLED - 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() { - //Serial.println("Hello from my usermod!"); - } + void setup() + { + ssDisplayBuffer = ssDisplayMask; + } - /* + /* * loop() is called continuously. Here you can check for events, read sensors, etc. */ - void loop() { - if (millis() - lastRefresh > resfreshTime) { - resfreshTime = _overlaySevenSegmentProcess(); - lastRefresh = millis(); + void loop() + { + if (millis() - lastRefresh > resfreshTime) + { + //In theory overlaySevenSegmentProcess should return the amount of time until it changes next. + //So we should be okay to trigger the stripi on every process loop. + resfreshTime = _overlaySevenSegmentProcess(); + lastRefresh = millis(); + strip.trigger(); + } + } + + void handleOverlayDraw() + { + _overlaySevenSegmentDraw(); + } + + 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/sevenSeg/+/set"), mqttDeviceTopic); + mqtt->subscribe(subBuffer, 2); + } + + if (mqttGroupTopic[0] != 0) + { + //subcribe for sevenseg messages on the group topic + sprintf_P(subBuffer, PSTR("%s/sevenSeg/+/set"), mqttGroupTopic); + 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("/sevenSeg/")); + if (strncmp_P(topic, PSTR("/sevenSeg/"), 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'; + _handleMQTT(topic, payload); + + } + return true; + } + + void addToConfig(JsonObject& root) + { + JsonObject top = root[FPSTR("sevenseg")]; + if (top.isNull()) { + top = root.createNestedObject(FPSTR("sevenseg")); } - } + top[FPSTR("perSegment")] = ssLEDPerSegment; + top[FPSTR("perPeriod")] = ssLEDPerPeriod; + top[FPSTR("startIdx")] = ssStartLED; + top[FPSTR("displayMask")] = ssDisplayMask; + top[FPSTR("displayCfg")] = ssDisplayConfig; + top[FPSTR("displayMsg")] = ssDisplayMessage; + top[FPSTR("timeEnable")] = ssTimeEnabled; + top[FPSTR("scrollSpd")] = ssScrollSpeed; + } - void handleOverlayDraw(){ - _overlaySevenSegmentDraw(); - } + bool readFromConfig(JsonObject& root) + { + JsonObject top = root[FPSTR("sevenseg")]; -// void onMqttConnect(bool sessionPresent) -// { -// if (mqttDeviceTopic[0] == 0) -// return; + bool configComplete = !top.isNull(); -// for (int pinNr = 0; pinNr < NUM_SWITCH_PINS; pinNr++) { -// char buf[128]; -// StaticJsonDocument<1024> json; -// sprintf(buf, "%s Switch %d", serverDescription, pinNr + 1); -// json[F("name")] = buf; + //if sevenseg section doesn't exist return + if(!configComplete) return configComplete; -// sprintf(buf, "%s/switch/%d", mqttDeviceTopic, pinNr); -// json["~"] = buf; -// strcat(buf, "/set"); -// mqtt->subscribe(buf, 0); + configComplete &= getJsonValue(top[FPSTR("perSegment")], ssLEDPerSegment); + configComplete &= getJsonValue(top[FPSTR("perPeriod")], ssLEDPerPeriod); + configComplete &= getJsonValue(top[FPSTR("startIdx")], ssStartLED); + configComplete &= getJsonValue(top[FPSTR("displayMask")], ssDisplayMask); + configComplete &= getJsonValue(top[FPSTR("displayCfg")], ssDisplayConfig); -// json[F("stat_t")] = "~/state"; -// json[F("cmd_t")] = "~/set"; -// json[F("pl_off")] = F("OFF"); -// json[F("pl_on")] = F("ON"); + String newDisplayMessage; + configComplete &= getJsonValue(top[FPSTR("displayMsg")], newDisplayMessage); + setSevenSegmentMessage(newDisplayMessage); -// char uid[16]; -// sprintf(uid, "%s_sw%d", escapedMac.c_str(), pinNr); -// json[F("unique_id")] = uid; + configComplete &= getJsonValue(top[FPSTR("timeEnable")], ssTimeEnabled); + configComplete &= getJsonValue(top[FPSTR("scrollSpd")], ssScrollSpeed); -// strcpy(buf, mqttDeviceTopic); -// strcat(buf, "/status"); -// json[F("avty_t")] = buf; -// json[F("pl_avail")] = F("online"); -// json[F("pl_not_avail")] = F("offline"); -// //TODO: dev -// sprintf(buf, "homeassistant/switch/%s/config", uid); -// char json_str[1024]; -// size_t payload_size = serializeJson(json, json_str); -// mqtt->publish(buf, 0, true, json_str, payload_size); -// updateState(pinNr); -// } -// } + return configComplete; + } -// bool onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) -// { -// //Note: Payload is not necessarily null terminated. Check "len" instead. -// for (int pinNr = 0; pinNr < NUM_SWITCH_PINS; pinNr++) { -// char buf[64]; -// sprintf(buf, "%s/switch/%d/set", mqttDeviceTopic, pinNr); -// if (strcmp(topic, buf) == 0) { -// //Any string starting with "ON" is interpreted as ON, everything else as OFF -// setState(pinNr, len >= 2 && payload[0] == 'O' && payload[1] == 'N'); -// return true; -// } -// } -// } - -// /* -// * 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) -// { -// int reading = 20; -// //this code adds "u":{"Light":[20," lux"]} to the info object -// JsonObject user = root["u"]; -// if (user.isNull()) user = root.createNestedObject("u"); - -// JsonArray lightArr = user.createNestedArray("Light"); //name -// lightArr.add(reading); //value -// lightArr.add(" lux"); //unit -// } -// */ - - -// /* -// * 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) -// { -// //root["user0"] = userVar0; -// } - - -// /* -// * 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) -// { -// userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value -// //if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!")); -// } - - -// /* -// * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. -// * It will be called by WLED when settings are actually saved (for example, LED settings are saved) -// * If you want to force saving the current state, use serializeConfig() in your loop(). -// * -// * CAUTION: serializeConfig() will initiate a filesystem write operation. -// * It might cause the LEDs to stutter and will cause flash wear if called too often. -// * Use it sparingly and always in the loop, never in network callbacks! -// * -// * addToConfig() will make your settings editable through the Usermod Settings page automatically. -// * -// * Usermod Settings Overview: -// * - Numeric values are treated as floats in the browser. -// * - If the numeric value entered into the browser contains a decimal point, it will be parsed as a C float -// * before being returned to the Usermod. The float data type has only 6-7 decimal digits of precision, and -// * doubles are not supported, numbers will be rounded to the nearest float value when being parsed. -// * The range accepted by the input field is +/- 1.175494351e-38 to +/- 3.402823466e+38. -// * - If the numeric value entered into the browser doesn't contain a decimal point, it will be parsed as a -// * C int32_t (range: -2147483648 to 2147483647) before being returned to the usermod. -// * Overflows or underflows are truncated to the max/min value for an int32_t, and again truncated to the type -// * used in the Usermod when reading the value from ArduinoJson. -// * - Pin values can be treated differently from an integer value by using the key name "pin" -// * - "pin" can contain a single or array of integer values -// * - On the Usermod Settings page there is simple checking for pin conflicts and warnings for special pins -// * - Red color indicates a conflict. Yellow color indicates a pin with a warning (e.g. an input-only pin) -// * - Tip: use int8_t to store the pin value in the Usermod, so a -1 value (pin not set) can be used -// * -// * See usermod_v2_auto_save.h for an example that saves Flash space by reusing ArduinoJson key name strings -// * -// * If you need a dedicated settings page with custom layout for your Usermod, that takes a lot more work. -// * You will have to add the setting to the HTML, xml.cpp and set.cpp manually. -// * See the WLED Soundreactive fork (code and wiki) for reference. https://github.com/atuline/WLED -// * -// * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! -// */ -// void addToConfig(JsonObject& root) -// { -// JsonObject top = root.createNestedObject("exampleUsermod"); -// top["great"] = userVar0; //save these vars persistently whenever settings are saved -// top["testBool"] = testBool; -// top["testInt"] = testInt; -// top["testLong"] = testLong; -// top["testULong"] = testULong; -// top["testFloat"] = testFloat; -// top["testString"] = testString; -// JsonArray pinArray = top.createNestedArray("pin"); -// pinArray.add(testPins[0]); -// pinArray.add(testPins[1]); -// } - - -// /* -// * readFromConfig() can be used to read back the custom settings you added with addToConfig(). -// * This is called by WLED when settings are loaded (currently this only happens immediately after boot, or after saving on the Usermod Settings page) -// * -// * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), -// * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. -// * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) -// * -// * Return true in case the config values returned from Usermod Settings were complete, or false if you'd like WLED to save your defaults to disk (so any missing values are editable in Usermod Settings) -// * -// * getJsonValue() returns false if the value is missing, or copies the value into the variable provided and returns true if the value is present -// * The configComplete variable is true only if the "exampleUsermod" object and all values are present. If any values are missing, WLED will know to call addToConfig() to save them -// * -// * This function is guaranteed to be called on boot, but could also be called every time settings are updated -// */ -// bool readFromConfig(JsonObject& root) -// { -// // default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor -// // setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed) - -// JsonObject top = root["exampleUsermod"]; - -// bool configComplete = !top.isNull(); - -// configComplete &= getJsonValue(top["great"], userVar0); -// configComplete &= getJsonValue(top["testBool"], testBool); -// configComplete &= getJsonValue(top["testULong"], testULong); -// configComplete &= getJsonValue(top["testFloat"], testFloat); -// configComplete &= getJsonValue(top["testString"], testString); - -// // A 3-argument getJsonValue() assigns the 3rd argument as a default value if the Json value is missing -// configComplete &= getJsonValue(top["testInt"], testInt, 42); -// configComplete &= getJsonValue(top["testLong"], testLong, -42424242); -// configComplete &= getJsonValue(top["pin"][0], testPins[0], -1); -// configComplete &= getJsonValue(top["pin"][1], testPins[1], -1); - -// return configComplete; -// } - - -// /* -// * 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_SEVEN_SEGMENT_DISPLAY; -// } - - + /* + * 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_SEVEN_SEGMENT_DISPLAY; + } }; \ No newline at end of file From 355525c248ce50f53d5da8d11097ff09b7647b97 Mon Sep 17 00:00:00 2001 From: Gregory Schmidt Date: Sat, 9 Oct 2021 11:14:52 -0800 Subject: [PATCH 06/11] Add readme --- usermods/seven_segment_display/readme.md | 55 +++++++++++++++++++++--- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/usermods/seven_segment_display/readme.md b/usermods/seven_segment_display/readme.md index 9d485f5f..ca52383d 100644 --- a/usermods/seven_segment_display/readme.md +++ b/usermods/seven_segment_display/readme.md @@ -1,10 +1,55 @@ # Seven Segment Display -In this usermod file you can find the documentation on how to take advantage of the new version 2 usermods! +Usermod that uses the overlay feature to create a configurable seven segment display. +This has only been tested on a single configuration. Colon support is entirely untested. -## Installation +## Installation -Copy `usermod_v2_example.h` to the wled00 directory. -Uncomment the corresponding lines in `usermods_list.cpp` and compile! -_(You shouldn't need to actually install this, it does nothing useful)_ +Add the compile-time option `-D USERMOD_SEVEN_SEGMENT` to your `platformio.ini` (or `platformio_override.ini`) or use `#define USERMOD_SEVEN_SEGMENT` in `my_config.h`. +## Settings +Settings can be controlled through both the usermod setting page and through MQTT with a raw payload. +##### Example + Topic ```/sevenSeg/perSegment/set``` + Payload ```3``` +#### perSegment -- ssLEDPerSegment +The number of individual LEDs per segment. There are 7 segments per digit. +#### perPeriod -- ssLEDPerPeriod +The number of individual LEDs per period. A ':' has 2x periods. +#### startIdx -- ssStartLED +Index of the LED that the display starts at. Allows a seven segment display to be in the middle of a string. +#### timeEnable -- ssTimeEnabled +When true, when displayMask is configured for a time output and no message is set the time will be displayed. +#### scrollSpd -- ssScrollSpeed +Time, in milliseconds, between message shifts when the length of displayMsg exceeds the length of the displayMask. +#### displayMask -- ssDisplayMask +This should represent the configuration of the physical display. +
+HH - 0-23. hh - 1-12, kk - 1-24 hours  
+MM or mm - 0-59 minutes  
+SS or ss = 0-59 seconds  
+: for a colon  
+All others for alpha numeric, (will be blank when displaying time)
+
+##### Example +```HHMMSS ``` +```hh:MM:SS ``` +#### displayMsg -- ssDisplayMessage +Message to be displayed across the display. If the length exceeds the length of the displayMask the message will scroll at scrollSpd. To 'remove' a message or revert back to time, if timeEnabled is true, set the message to '~'. +#### displayCfg -- ssDisplayConfig +The order that your LEDs are configured. All seven segments in the display need to be wired the same way. +
+           -------
+         /   A   /          0 - EDCGFAB
+        / F     / B         1 - EDCBAFG
+       /       /            2 - GCDEFAB
+       -------              3 - GBAFEDC
+     /   G   /              4 - FABGEDC
+    / E     / C             5 - FABCDEG
+   /       /
+   -------
+      D
+
+ +## Version +20211009 - Initial release From 6cd770b4c71f38ec8a22d409c855ec23f166cace Mon Sep 17 00:00:00 2001 From: Gregory Schmidt Date: Sat, 9 Oct 2021 11:29:41 -0800 Subject: [PATCH 07/11] Restore platformio.inii --- platformio.ini | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/platformio.ini b/platformio.ini index 09830217..0a64a94c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,7 +12,7 @@ ; default_envs = travis_esp8266, travis_esp32 # Release binaries -;default_envs = nodemcuv2, esp01_1m_full, esp32dev, esp32_eth +default_envs = nodemcuv2, esp01_1m_full, esp32dev, esp32_eth # Build everything ; default_envs = esp32dev, esp8285_4CH_MagicHome, esp8285_4CH_H801, codm-controller-0.6-rev2, codm-controller-0.6, esp32s2_saola, d1_mini_5CH_Shojo_PCB, d1_mini, sp501e, travis_esp8266, travis_esp32, nodemcuv2, esp32_eth, anavi_miracle_controller, esp07, esp01_1m_full, m5atom, h803wf, d1_mini_ota, heltec_wifi_kit_8, esp8285_5CH_H801, d1_mini_debug, wemos_shield_esp32, elekstube_ips @@ -23,7 +23,6 @@ ; default_envs = esp01_1m_full ; default_envs = esp07 ; default_envs = d1_mini - default_envs = d1_mini_seven_seg ; default_envs = heltec_wifi_kit_8 ; default_envs = h803wf ; default_envs = d1_mini_debug @@ -499,16 +498,3 @@ monitor_filters = esp32_exception_decoder lib_deps = ${esp32.lib_deps} TFT_eSPI @ ^2.3.70 - -# ------------------------------------------------------------------------------ -# User Mod SevenSegment -# ------------------------------------------------------------------------------ -[env:d1_mini_seven_seg] -board = d1_mini -build_type = debug -platform = ${common.platform_wled_default} -platform_packages = ${common.platform_packages} -board_build.ldscript = ${common.ldscript_4m1m} -build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags_esp8266} ${common.debug_flags} -D USERMOD_SEVEN_SEGMENT -lib_deps = ${esp8266.lib_deps} From de454e8b78bdc70681feed3322de50eb2a3986a9 Mon Sep 17 00:00:00 2001 From: Christian Schwinne Date: Mon, 11 Oct 2021 01:29:13 +0200 Subject: [PATCH 08/11] Edit comments --- wled00/overlay.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/wled00/overlay.cpp b/wled00/overlay.cpp index ef0e2b69..2c631495 100644 --- a/wled00/overlay.cpp +++ b/wled00/overlay.cpp @@ -17,9 +17,8 @@ void initCronixie() } } -/* - * handleOverlays is essentially the equivalent of usermods.loop - */ + +//handleOverlays is essentially the equivalent of usermods.loop void handleOverlays() { initCronixie(); @@ -113,9 +112,6 @@ void _overlayAnalogCountdown() } } -/* - * All overlays should be moved to usermods - */ void handleOverlayDraw() { usermods.handleOverlayDraw(); if (!overlayCurrent) return; From d47157eec3884e1fe9b0ceb8c9d711e70e50ec7f Mon Sep 17 00:00:00 2001 From: Gregory Schmidt Date: Sun, 10 Oct 2021 16:26:14 -0800 Subject: [PATCH 09/11] Refactor string usage --- .../usermod_v2_seven_segment_display.h | 199 ++++++++++-------- 1 file changed, 108 insertions(+), 91 deletions(-) diff --git a/usermods/seven_segment_display/usermod_v2_seven_segment_display.h b/usermods/seven_segment_display/usermod_v2_seven_segment_display.h index e2ea7937..3f79bc17 100644 --- a/usermods/seven_segment_display/usermod_v2_seven_segment_display.h +++ b/usermods/seven_segment_display/usermod_v2_seven_segment_display.h @@ -11,7 +11,6 @@ private: //Runtime variables. unsigned long lastRefresh = 0; unsigned long lastCharacterStep = 0; - //char ssDisplayBuffer[WLED_SS_BUFFLEN+1]; //Runtime buffer of what should be displayed. String ssDisplayBuffer = ""; char ssCharacterMask[36] = {0x77, 0x11, 0x6B, 0x3B, 0x1D, 0x3E, 0x7E, 0x13, 0x7F, 0x1F, 0x5F, 0x7C, 0x66, 0x79, 0x6E, 0x4E, 0x76, 0x5D, 0x44, 0x71, 0x5E, 0x64, 0x27, 0x58, 0x77, 0x4F, 0x1F, 0x48, 0x3E, 0x6C, 0x75, 0x25, 0x7D, 0x2A, 0x3D, 0x6B}; int ssDisplayMessageIdx = 0; //Position of the start of the message to be physically displayed. @@ -30,8 +29,7 @@ private: // : for a colon // All others for alpha numeric, (will be blank when displaying time) */ - //char ssDisplayMask[WLED_SS_BUFFLEN+1] = "HHMMSS"; //Physical Display Mask, this should reflect physical equipment. - String ssDisplayMask = "HHMMSS"; + String ssDisplayMask = "HHMMSS"; //Physical Display Mask, this should reflect physical equipment. /* ssDisplayConfig // ------- // / A / 0 - EDCGFAB @@ -47,7 +45,21 @@ private: int ssDisplayConfig = 5; //Physical configuration of the Seven segment display String ssDisplayMessage = "testing123"; bool ssTimeEnabled = true; //If not, display message. - unsigned long ssScrollSpeed = 1000; //Time between advancement of extended message scrolling, in milliseconds. + unsigned int ssScrollSpeed = 1000; //Time between advancement of extended message scrolling, in milliseconds. + + //String to reduce flash memory usage + static const char _str_perSegment[]; + static const char _str_perPeriod[]; + static const char _str_startIdx[]; + static const char _str_displayCfg[]; + static const char _str_timeEnabled[]; + static const char _str_scrollSpd[]; + static const char _str_displayMask[]; + static const char _str_displayMsg[]; + static const char _str_sevenSeg[]; + static const char _str_subFormat[]; + static const char _str_topicFormat[]; + unsigned long _overlaySevenSegmentProcess() { @@ -134,7 +146,7 @@ private: { //Start pixels at ssStartLED, Use ssLEDPerSegment, ssLEDPerPeriod, ssDisplayBuffer - int indexLED = 0; + int indexLED = ssStartLED; int displayMaskLen = static_cast(ssDisplayMask.length()); for (int indexBuffer = 0; indexBuffer < displayMaskLen; indexBuffer++) { @@ -174,7 +186,7 @@ private: { for (int numPerSeg = 0; numPerSeg < ssLEDPerSegment; numPerSeg++) { - strip.setPixelColor(indexLED, 0x000000); + strip.setPixelColor(indexLED+numPerSeg, 0x000000); } } indexLED += ssLEDPerSegment; @@ -183,13 +195,22 @@ private: char _overlaySevenSegmentGetCharMask(char var) { - //ssCharacterMask - if (var > 0x60) //Essentially a "toLower" call. - var -= 0x20; - if (var > 0x39) //Meaning it is a non-numeric - var -= 0x07; - var -= 0x30; //Shift ascii down to start numeric 0 at index 0. - + if (var >= 0x30 && var <= 0x39) + { /*If its a number, shift to index 0.*/ + var -= 0x30; + } + else if (var >= 0x41 && var <= 0x5a) + { /*If its an Upper case, shift to index 0xA.*/ + var -= 0x37; + } + else if (var >= 0x61 && var <= 0x7A) + { /*If its a lower case, shift to index 0xA.*/ + var -= 0x57; + } + else + { /* Else unsupported, return 0; */ + return 0; + } char mask = ssCharacterMask[static_cast(var)]; /* 0 - EDCGFAB @@ -246,84 +267,70 @@ private: return result; } - void _publishMQTTint(const char* subTopic, int value) + + void _publishMQTTint_P(const char* subTopic, int value) { char buffer[64]; char valBuffer[12]; - sprintf_P(buffer, PSTR("%s/sevenSeg/%s"), mqttDeviceTopic, subTopic); + sprintf_P(buffer, PSTR("%s/sevenSeg/%S"), mqttDeviceTopic, subTopic); + Serial.println(buffer); sprintf_P(valBuffer, PSTR("%d"), value); mqtt->publish(buffer, 2, true, valBuffer); } - void _publishMQTTstr(const char* subTopic, String Value) + void _publishMQTTstr_P(const char* subTopic, String Value) { char buffer[64]; - sprintf_P(buffer, PSTR("%s/sevenSeg/%s"), mqttDeviceTopic, subTopic); + sprintf_P(buffer, PSTR("%s/sevenSeg/%S"), mqttDeviceTopic, subTopic); mqtt->publish(buffer, 2, true, Value.c_str(), Value.length()); } void _updateMQTT() { - _publishMQTTint(PSTR("perSegment"), ssLEDPerSegment); - _publishMQTTint(PSTR("perPeriod"), ssLEDPerPeriod); - _publishMQTTint(PSTR("startIdx"), ssStartLED); - _publishMQTTint(PSTR("displayCfg"), ssDisplayConfig); - _publishMQTTint(PSTR("timeEnable"), ssTimeEnabled); - _publishMQTTint(PSTR("scrollSpd"), ssScrollSpeed); + _publishMQTTint_P(_str_perSegment, ssLEDPerSegment); + _publishMQTTint_P(_str_perPeriod, ssLEDPerPeriod); + _publishMQTTint_P(_str_startIdx, ssStartLED); + _publishMQTTint_P(_str_displayCfg, ssDisplayConfig); + _publishMQTTint_P(_str_timeEnabled, ssTimeEnabled); + _publishMQTTint_P(_str_scrollSpd, ssScrollSpeed); - _publishMQTTstr(PSTR("displayMask"), ssDisplayMask); - _publishMQTTstr(PSTR("displayMsg"), ssDisplayMessage); + _publishMQTTstr_P(_str_displayMask, ssDisplayMask); + _publishMQTTstr_P(_str_displayMsg, ssDisplayMessage); } - - void _handleMQTT(char *topic, char *payload) + 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(strcmp_P(topic, PSTR("perSegment"))==0) - { - ssLEDPerSegment = strtol(payload, NULL, 10); - _publishMQTTint(topic, ssLEDPerSegment); - return; - } - if(strcmp_P(topic, PSTR("perPeriod"))==0) - { - ssLEDPerPeriod = strtol(payload, NULL, 10); - _publishMQTTint(topic, ssLEDPerPeriod); - return; - } - if(strcmp_P(topic, PSTR("startIdx"))==0) - { - ssStartLED = strtol(payload, NULL, 10); - _publishMQTTint(topic, ssStartLED); - return; - } - if(strcmp_P(topic, PSTR("displayCfg"))==0) - { - ssDisplayConfig = strtol(payload, NULL, 10); - _publishMQTTint(topic, ssDisplayConfig); - return; - } - if(strcmp_P(topic, PSTR("timeEnable"))==0) - { - ssTimeEnabled = strtol(payload, NULL, 10); - ssDoDisplayTime = ssTimeEnabled; - _publishMQTTint(topic, ssTimeEnabled); - return; - } - if(strcmp_P(topic, PSTR("scrollSpd"))==0) - { - ssScrollSpeed = strtol(payload, NULL, 10); - _publishMQTTint(topic, ssScrollSpeed); - return; - } - if(strcmp_P(topic, PSTR("displayMask"))==0) + if (_cmpIntSetting_P(topic, payload, _str_perSegment, &ssLEDPerSegment)) + return true; + if (_cmpIntSetting_P(topic, payload, _str_perPeriod, &ssLEDPerPeriod)) + return true; + if (_cmpIntSetting_P(topic, payload, _str_startIdx, &ssStartLED)) + return true; + if (_cmpIntSetting_P(topic, payload, _str_displayCfg, &ssDisplayConfig)) + return true; + if (_cmpIntSetting_P(topic, payload, _str_timeEnabled, &ssTimeEnabled)) + return true; + if (_cmpIntSetting_P(topic, payload, _str_scrollSpd, &ssScrollSpeed)) + return true; + if(strcmp_P(topic, _str_displayMask)==0) { ssDisplayMask = String(payload); ssDisplayBuffer = ssDisplayMask; - _publishMQTTstr(topic, ssDisplayMask); - return; + _publishMQTTstr_P(_str_displayMask, ssDisplayMask); + return true; } - if(strcmp_P(topic, PSTR("displayMsg"))==0) + if(strcmp_P(topic, _str_displayMsg)==0) { setSevenSegmentMessage(String(payload)); - return; + return true; } + return false; } public: @@ -344,7 +351,7 @@ public: //If the message isn't the same, update runtime/mqtt (most calls will be resetting message scroll) if(!ssDisplayMessage.equals(message)) { - _publishMQTTstr(PSTR("displayMsg"), message); + _publishMQTTstr_P(_str_displayMsg, message); ssDisplayMessage = message; } } @@ -416,7 +423,7 @@ public: { //Trim /set and handle it topic[topicLen - 4] = '\0'; - _handleMQTT(topic, payload); + _handleSetting(topic, payload); } return true; @@ -424,41 +431,41 @@ public: void addToConfig(JsonObject& root) { - JsonObject top = root[FPSTR("sevenseg")]; + JsonObject top = root[FPSTR(_str_sevenSeg)]; if (top.isNull()) { - top = root.createNestedObject(FPSTR("sevenseg")); + top = root.createNestedObject(FPSTR(_str_sevenSeg)); } - top[FPSTR("perSegment")] = ssLEDPerSegment; - top[FPSTR("perPeriod")] = ssLEDPerPeriod; - top[FPSTR("startIdx")] = ssStartLED; - top[FPSTR("displayMask")] = ssDisplayMask; - top[FPSTR("displayCfg")] = ssDisplayConfig; - top[FPSTR("displayMsg")] = ssDisplayMessage; - top[FPSTR("timeEnable")] = ssTimeEnabled; - top[FPSTR("scrollSpd")] = ssScrollSpeed; + top[FPSTR(_str_perSegment)] = ssLEDPerSegment; + top[FPSTR(_str_perPeriod)] = ssLEDPerPeriod; + top[FPSTR(_str_startIdx)] = ssStartLED; + top[FPSTR(_str_displayMask)] = ssDisplayMask; + top[FPSTR(_str_displayCfg)] = ssDisplayConfig; + top[FPSTR(_str_displayMsg)] = ssDisplayMessage; + top[FPSTR(_str_timeEnabled)] = ssTimeEnabled; + top[FPSTR(_str_scrollSpd)] = ssScrollSpeed; } bool readFromConfig(JsonObject& root) { - JsonObject top = root[FPSTR("sevenseg")]; + JsonObject top = root[FPSTR(_str_sevenSeg)]; bool configComplete = !top.isNull(); //if sevenseg section doesn't exist return if(!configComplete) return configComplete; - configComplete &= getJsonValue(top[FPSTR("perSegment")], ssLEDPerSegment); - configComplete &= getJsonValue(top[FPSTR("perPeriod")], ssLEDPerPeriod); - configComplete &= getJsonValue(top[FPSTR("startIdx")], ssStartLED); - configComplete &= getJsonValue(top[FPSTR("displayMask")], ssDisplayMask); - configComplete &= getJsonValue(top[FPSTR("displayCfg")], ssDisplayConfig); + configComplete &= getJsonValue(top[FPSTR(_str_perSegment)], ssLEDPerSegment); + configComplete &= getJsonValue(top[FPSTR(_str_perPeriod)], ssLEDPerPeriod); + configComplete &= getJsonValue(top[FPSTR(_str_startIdx)], ssStartLED); + configComplete &= getJsonValue(top[FPSTR(_str_displayMask)], ssDisplayMask); + configComplete &= getJsonValue(top[FPSTR(_str_displayCfg)], ssDisplayConfig); String newDisplayMessage; - configComplete &= getJsonValue(top[FPSTR("displayMsg")], newDisplayMessage); + configComplete &= getJsonValue(top[FPSTR(_str_displayMsg)], newDisplayMessage); setSevenSegmentMessage(newDisplayMessage); - configComplete &= getJsonValue(top[FPSTR("timeEnable")], ssTimeEnabled); - configComplete &= getJsonValue(top[FPSTR("scrollSpd")], ssScrollSpeed); + configComplete &= getJsonValue(top[FPSTR(_str_timeEnabled)], ssTimeEnabled); + configComplete &= getJsonValue(top[FPSTR(_str_scrollSpd)], ssScrollSpeed); return configComplete; } @@ -471,4 +478,14 @@ public: { return USERMOD_ID_SEVEN_SEGMENT_DISPLAY; } -}; \ No newline at end of file +}; + + const char SevenSegmentDisplay::_str_perSegment[] PROGMEM = "perSegment"; + const char SevenSegmentDisplay::_str_perPeriod[] PROGMEM = "perPeriod"; + const char SevenSegmentDisplay::_str_startIdx[] PROGMEM = "startIdx"; + const char SevenSegmentDisplay::_str_displayCfg[] PROGMEM = "displayCfg"; + const char SevenSegmentDisplay::_str_timeEnabled[] PROGMEM = "timeEnabled"; + const char SevenSegmentDisplay::_str_scrollSpd[] PROGMEM = "scrollSpd"; + const char SevenSegmentDisplay::_str_displayMask[] PROGMEM = "displayMask"; + const char SevenSegmentDisplay::_str_displayMsg[] PROGMEM = "displayMsg"; + const char SevenSegmentDisplay::_str_sevenSeg[] PROGMEM = "sevenSeg"; \ No newline at end of file From 445b6ee13ffb3e0357dad6b5cf67c2757d3dd037 Mon Sep 17 00:00:00 2001 From: Gregory Schmidt Date: Sun, 10 Oct 2021 17:05:55 -0800 Subject: [PATCH 10/11] Fix MQTT Null publish --- .../usermod_v2_seven_segment_display.h | 76 ++++++++++--------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/usermods/seven_segment_display/usermod_v2_seven_segment_display.h b/usermods/seven_segment_display/usermod_v2_seven_segment_display.h index 3f79bc17..72e4fc92 100644 --- a/usermods/seven_segment_display/usermod_v2_seven_segment_display.h +++ b/usermods/seven_segment_display/usermod_v2_seven_segment_display.h @@ -22,14 +22,14 @@ private: // set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer) int ssLEDPerSegment = 1; //The number of LEDs in each segment of the 7 seg (total per digit is 7 * ssLedPerSegment) int ssLEDPerPeriod = 1; //A Period will have 1x and a Colon will have 2x - int ssStartLED = 0; //The pixel that the display starts at. + int ssStartLED = 0; //The pixel that the display starts at. /* HH - 0-23. hh - 1-12, kk - 1-24 hours // MM or mm - 0-59 minutes // SS or ss = 0-59 seconds // : for a colon // All others for alpha numeric, (will be blank when displaying time) */ - String ssDisplayMask = "HHMMSS"; //Physical Display Mask, this should reflect physical equipment. + String ssDisplayMask = "HHMMSS"; //Physical Display Mask, this should reflect physical equipment. /* ssDisplayConfig // ------- // / A / 0 - EDCGFAB @@ -44,7 +44,7 @@ private: */ int ssDisplayConfig = 5; //Physical configuration of the Seven segment display String ssDisplayMessage = "testing123"; - bool ssTimeEnabled = true; //If not, display message. + bool ssTimeEnabled = true; //If not, display message. unsigned int ssScrollSpeed = 1000; //Time between advancement of extended message scrolling, in milliseconds. //String to reduce flash memory usage @@ -60,7 +60,6 @@ private: static const char _str_subFormat[]; static const char _str_topicFormat[]; - unsigned long _overlaySevenSegmentProcess() { //Do time for now. @@ -120,7 +119,7 @@ private: int len = static_cast(ssDisplayMessage.length()); if (ssDisplayMessageIdx > len) { - //If it has scrolled the whole message, reset it. + //If it has scrolled the whole message, reset it. setSevenSegmentMessage(ssDisplayMessage); return REFRESHTIME; } @@ -186,7 +185,7 @@ private: { for (int numPerSeg = 0; numPerSeg < ssLEDPerSegment; numPerSeg++) { - strip.setPixelColor(indexLED+numPerSeg, 0x000000); + strip.setPixelColor(indexLED + numPerSeg, 0x000000); } } indexLED += ssLEDPerSegment; @@ -267,22 +266,26 @@ private: return result; } - - void _publishMQTTint_P(const char* subTopic, int value) + + void _publishMQTTint_P(const char *subTopic, int value) { + if(mqtt == NULL) return; + char buffer[64]; char valBuffer[12]; sprintf_P(buffer, PSTR("%s/sevenSeg/%S"), mqttDeviceTopic, subTopic); - Serial.println(buffer); sprintf_P(valBuffer, PSTR("%d"), value); mqtt->publish(buffer, 2, true, valBuffer); } - void _publishMQTTstr_P(const char* subTopic, String Value) + + void _publishMQTTstr_P(const char *subTopic, String Value) { + if(mqtt == NULL) return; char buffer[64]; sprintf_P(buffer, PSTR("%s/sevenSeg/%S"), mqttDeviceTopic, subTopic); mqtt->publish(buffer, 2, true, Value.c_str(), Value.length()); } + void _updateMQTT() { _publishMQTTint_P(_str_perSegment, ssLEDPerSegment); @@ -295,15 +298,18 @@ private: _publishMQTTstr_P(_str_displayMask, ssDisplayMask); _publishMQTTstr_P(_str_displayMsg, ssDisplayMessage); } - bool _cmpIntSetting_P(char* topic, char* payload, const char* setting, void* value){ - if(strcmp_P(topic, setting) == 0) + + 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)); + *((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_perSegment, &ssLEDPerSegment)) @@ -318,14 +324,14 @@ private: return true; if (_cmpIntSetting_P(topic, payload, _str_scrollSpd, &ssScrollSpeed)) return true; - if(strcmp_P(topic, _str_displayMask)==0) + if (strcmp_P(topic, _str_displayMask) == 0) { ssDisplayMask = String(payload); ssDisplayBuffer = ssDisplayMask; _publishMQTTstr_P(_str_displayMask, ssDisplayMask); return true; } - if(strcmp_P(topic, _str_displayMsg)==0) + if (strcmp_P(topic, _str_displayMsg) == 0) { setSevenSegmentMessage(String(payload)); return true; @@ -347,9 +353,9 @@ public: ssDisplayMessageIdx = -ssDisplayMask.length(); else ssDisplayMessageIdx = 0; - + //If the message isn't the same, update runtime/mqtt (most calls will be resetting message scroll) - if(!ssDisplayMessage.equals(message)) + if (!ssDisplayMessage.equals(message)) { _publishMQTTstr_P(_str_displayMsg, message); ssDisplayMessage = message; @@ -424,17 +430,17 @@ public: //Trim /set and handle it topic[topicLen - 4] = '\0'; _handleSetting(topic, payload); - } return true; } - void addToConfig(JsonObject& root) + void addToConfig(JsonObject &root) { JsonObject top = root[FPSTR(_str_sevenSeg)]; - if (top.isNull()) { - top = root.createNestedObject(FPSTR(_str_sevenSeg)); - } + if (top.isNull()) + { + top = root.createNestedObject(FPSTR(_str_sevenSeg)); + } top[FPSTR(_str_perSegment)] = ssLEDPerSegment; top[FPSTR(_str_perPeriod)] = ssLEDPerPeriod; top[FPSTR(_str_startIdx)] = ssStartLED; @@ -445,14 +451,15 @@ public: top[FPSTR(_str_scrollSpd)] = ssScrollSpeed; } - bool readFromConfig(JsonObject& root) + bool readFromConfig(JsonObject &root) { JsonObject top = root[FPSTR(_str_sevenSeg)]; bool configComplete = !top.isNull(); //if sevenseg section doesn't exist return - if(!configComplete) return configComplete; + if (!configComplete) + return configComplete; configComplete &= getJsonValue(top[FPSTR(_str_perSegment)], ssLEDPerSegment); configComplete &= getJsonValue(top[FPSTR(_str_perPeriod)], ssLEDPerPeriod); @@ -466,7 +473,6 @@ public: configComplete &= getJsonValue(top[FPSTR(_str_timeEnabled)], ssTimeEnabled); configComplete &= getJsonValue(top[FPSTR(_str_scrollSpd)], ssScrollSpeed); - return configComplete; } @@ -480,12 +486,12 @@ public: } }; - const char SevenSegmentDisplay::_str_perSegment[] PROGMEM = "perSegment"; - const char SevenSegmentDisplay::_str_perPeriod[] PROGMEM = "perPeriod"; - const char SevenSegmentDisplay::_str_startIdx[] PROGMEM = "startIdx"; - const char SevenSegmentDisplay::_str_displayCfg[] PROGMEM = "displayCfg"; - const char SevenSegmentDisplay::_str_timeEnabled[] PROGMEM = "timeEnabled"; - const char SevenSegmentDisplay::_str_scrollSpd[] PROGMEM = "scrollSpd"; - const char SevenSegmentDisplay::_str_displayMask[] PROGMEM = "displayMask"; - const char SevenSegmentDisplay::_str_displayMsg[] PROGMEM = "displayMsg"; - const char SevenSegmentDisplay::_str_sevenSeg[] PROGMEM = "sevenSeg"; \ No newline at end of file +const char SevenSegmentDisplay::_str_perSegment[] PROGMEM = "perSegment"; +const char SevenSegmentDisplay::_str_perPeriod[] PROGMEM = "perPeriod"; +const char SevenSegmentDisplay::_str_startIdx[] PROGMEM = "startIdx"; +const char SevenSegmentDisplay::_str_displayCfg[] PROGMEM = "displayCfg"; +const char SevenSegmentDisplay::_str_timeEnabled[] PROGMEM = "timeEnabled"; +const char SevenSegmentDisplay::_str_scrollSpd[] PROGMEM = "scrollSpd"; +const char SevenSegmentDisplay::_str_displayMask[] PROGMEM = "displayMask"; +const char SevenSegmentDisplay::_str_displayMsg[] PROGMEM = "displayMsg"; +const char SevenSegmentDisplay::_str_sevenSeg[] PROGMEM = "sevenSeg"; \ No newline at end of file From 1b50fbab22948f24036d55eb68eeba3cfce2f193 Mon Sep 17 00:00:00 2001 From: Gregory Schmidt Date: Sun, 10 Oct 2021 17:24:36 -0800 Subject: [PATCH 11/11] Additional Flash string concat --- .../usermod_v2_seven_segment_display.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/usermods/seven_segment_display/usermod_v2_seven_segment_display.h b/usermods/seven_segment_display/usermod_v2_seven_segment_display.h index 72e4fc92..5c0022e0 100644 --- a/usermods/seven_segment_display/usermod_v2_seven_segment_display.h +++ b/usermods/seven_segment_display/usermod_v2_seven_segment_display.h @@ -43,7 +43,7 @@ private: // D */ int ssDisplayConfig = 5; //Physical configuration of the Seven segment display - String ssDisplayMessage = "testing123"; + String ssDisplayMessage = "~"; bool ssTimeEnabled = true; //If not, display message. unsigned int ssScrollSpeed = 1000; //Time between advancement of extended message scrolling, in milliseconds. @@ -273,7 +273,7 @@ private: char buffer[64]; char valBuffer[12]; - sprintf_P(buffer, PSTR("%s/sevenSeg/%S"), mqttDeviceTopic, subTopic); + sprintf_P(buffer, PSTR("%s/%S/%S"), mqttDeviceTopic, _str_sevenSeg, subTopic); sprintf_P(valBuffer, PSTR("%d"), value); mqtt->publish(buffer, 2, true, valBuffer); } @@ -282,7 +282,7 @@ private: { if(mqtt == NULL) return; char buffer[64]; - sprintf_P(buffer, PSTR("%s/sevenSeg/%S"), mqttDeviceTopic, subTopic); + sprintf_P(buffer, PSTR("%s/%S/%S"), mqttDeviceTopic, _str_sevenSeg, subTopic); mqtt->publish(buffer, 2, true, Value.c_str(), Value.length()); } @@ -399,14 +399,14 @@ public: { _updateMQTT(); //subscribe for sevenseg messages on the device topic - sprintf_P(subBuffer, PSTR("%s/sevenSeg/+/set"), mqttDeviceTopic); + sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttDeviceTopic, _str_sevenSeg); mqtt->subscribe(subBuffer, 2); } if (mqttGroupTopic[0] != 0) { //subcribe for sevenseg messages on the group topic - sprintf_P(subBuffer, PSTR("%s/sevenSeg/+/set"), mqttGroupTopic); + sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttGroupTopic, _str_sevenSeg); mqtt->subscribe(subBuffer, 2); } }