diff --git a/usermods/Artemis_reciever/usermod.cpp b/usermods/Artemis_reciever/usermod.cpp index 227368525..5f230dfd8 100644 --- a/usermods/Artemis_reciever/usermod.cpp +++ b/usermods/Artemis_reciever/usermod.cpp @@ -24,7 +24,7 @@ void RGBNET_readValues() { int channel = UDP.read(); //channel data is not used we only supports one channel - int len = UDP.read(RGBNET_packet, ledCount*3); + int len = UDP.read(RGBNET_packet, strip.getLengthTotal()*3); if(len==0){ return; } @@ -50,7 +50,7 @@ void handleConfig(AsyncWebServerRequest *request) \"channels\": [\ {\ \"channel\": 1,\ - \"leds\": " + ledCount + "\ + \"leds\": " + strip.getLengthTotal() + "\ },\ {\ \"channel\": 2,\ diff --git a/usermods/BH1750_v2/readme.md b/usermods/BH1750_v2/readme.md index 9496dc026..a35077700 100644 --- a/usermods/BH1750_v2/readme.md +++ b/usermods/BH1750_v2/readme.md @@ -9,11 +9,11 @@ Copy the example `platformio_override.ini` to the root directory. This file sho ### Define Your Options -* `USERMOD_BH1750` - define this to have this user mod included wled00\usermods_list.cpp -* `USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL` - the max number of milliseconds between measurements, defaults to 10000ms -* `USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL` - the min number of milliseconds between measurements, defaults to 500ms -* `USERMOD_BH1750_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 10 seconds -* `USERMOD_BH1750_OFFSET_VALUE` - the offset value to report on, defaults to 1 +* `USERMOD_BH1750` - define this to have this user mod included wled00\usermods_list.cpp +* `USERMOD_BH1750_MAX_MEASUREMENT_INTERVAL` - the max number of milliseconds between measurements, defaults to 10000ms +* `USERMOD_BH1750_MIN_MEASUREMENT_INTERVAL` - the min number of milliseconds between measurements, defaults to 500ms +* `USERMOD_BH1750_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 10 seconds +* `USERMOD_BH1750_OFFSET_VALUE` - the offset value to report on, defaults to 1 All parameters can be configured at runtime using Usermods settings page. diff --git a/usermods/project_cars_shiftlight/wled06_usermod.ino b/usermods/project_cars_shiftlight/wled06_usermod.ino index 0edad7ddd..9d3f1d447 100644 --- a/usermods/project_cars_shiftlight/wled06_usermod.ino +++ b/usermods/project_cars_shiftlight/wled06_usermod.ino @@ -5,6 +5,8 @@ * I've had good results with settings around 5 (20 fps). * */ +#include "wled.h" + const uint8_t PCARS_dimcolor = 20; WiFiUDP UDP; const unsigned int PCARS_localUdpPort = 5606; // local port to listen on @@ -49,11 +51,12 @@ void PCARS_readValues() { void PCARS_buildcolorbars() { boolean activated = false; float ledratio = 0; + uint16_t totalLen = strip.getLengthTotal(); - for (uint16_t i = 0; i < ledCount; i++) { + for (uint16_t i = 0; i < totalLen; i++) { if (PCARS_rpmRatio < .95 || (millis() % 100 > 70 )) { - ledratio = (float)i / (float)ledCount; + ledratio = (float)i / (float)totalLen; if (ledratio < PCARS_rpmRatio) { activated = true; } else { diff --git a/usermods/quinled-an-penta/quinled-an-penta.h b/usermods/quinled-an-penta/quinled-an-penta.h new file mode 100644 index 000000000..10777e36c --- /dev/null +++ b/usermods/quinled-an-penta/quinled-an-penta.h @@ -0,0 +1,755 @@ +#pragma once + +#include "U8g2lib.h" +#include "SHT85.h" +#include "Wire.h" +#include "wled.h" + +class QuinLEDAnPentaUsermod : public Usermod +{ + private: + bool enabled = false; + bool firstRunDone = false; + bool initDone = false; + U8G2 *oledDisplay = nullptr; + SHT *sht30TempHumidSensor; + + // Network info vars + bool networkHasChanged = false; + bool lastKnownNetworkConnected; + IPAddress lastKnownIp; + bool lastKnownWiFiConnected; + String lastKnownSsid; + bool lastKnownApActive; + char *lastKnownApSsid; + char *lastKnownApPass; + byte lastKnownApChannel; + int lastKnownEthType; + bool lastKnownEthLinkUp; + + // Brightness / LEDC vars + byte lastKnownBri = 0; + int8_t currentBussesNumPins[5] = {0, 0, 0, 0, 0}; + int8_t currentLedPins[5] = {0, 0, 0, 0, 0}; + uint8_t currentLedcReads[5] = {0, 0, 0, 0, 0}; + uint8_t lastKnownLedcReads[5] = {0, 0, 0, 0, 0}; + + // OLED vars + bool oledEnabled = false; + bool oledInitDone = false; + bool oledUseProgressBars = false; + bool oledFlipScreen = false; + bool oledFixBuggedScreen = false; + byte oledMaxPage = 3; + byte oledCurrentPage = 3; // Start with the network page to help identifying the IP + byte oledSecondsPerPage = 10; + unsigned long oledLogoDrawn = 0; + unsigned long oledLastTimeUpdated = 0; + unsigned long oledLastTimePageChange = 0; + unsigned long oledLastTimeFixBuggedScreen = 0; + + // SHT30 vars + bool shtEnabled = false; + bool shtInitDone = false; + bool shtReadDataSuccess = false; + byte shtI2cAddress = 0x44; + unsigned long shtLastTimeUpdated = 0; + bool shtDataRequested = false; + float shtCurrentTemp = 0; + float shtLastKnownTemp = 0; + float shtCurrentHumidity = 0; + float shtLastKnownHumidity = 0; + + // Pin/IO vars + const int8_t anPentaPins[5] = {14, 13, 12, 4, 2}; + int8_t oledSpiClk = 15; + int8_t oledSpiData = 16; + int8_t oledSpiCs = 0; + int8_t oledSpiDc = 32; + int8_t oledSpiRst = 33; + int8_t shtSda = 1; + int8_t shtScl = 3; + + + bool isAnPentaLedPin(int8_t pin) + { + for(int8_t i = 0; i <= 4; i++) + { + if(anPentaPins[i] == pin) + return true; + } + return false; + } + + void getCurrentUsedLedPins() + { + for (int8_t lp = 0; lp <= 4; lp++) currentLedPins[lp] = 0; + byte numBusses = busses.getNumBusses(); + byte numUsedPins = 0; + + for (int8_t b = 0; b < numBusses; b++) { + Bus* curBus = busses.getBus(b); + if (curBus != nullptr) { + uint8_t pins[5] = {0, 0, 0, 0, 0}; + currentBussesNumPins[b] = curBus->getPins(pins); + for (int8_t p = 0; p < currentBussesNumPins[b]; p++) { + if (isAnPentaLedPin(pins[p])) { + currentLedPins[numUsedPins] = pins[p]; + numUsedPins++; + } + } + } + } + } + + void getCurrentLedcValues() + { + byte numBusses = busses.getNumBusses(); + byte numLedc = 0; + + for (int8_t b = 0; b < numBusses; b++) { + Bus* curBus = busses.getBus(b); + if (curBus != nullptr) { + uint32_t curPixColor = curBus->getPixelColor(0); + uint8_t _data[5] = {255, 255, 255, 255, 255}; + _data[3] = curPixColor >> 24; + _data[0] = curPixColor >> 16; + _data[1] = curPixColor >> 8; + _data[2] = curPixColor; + + for (uint8_t i = 0; i < currentBussesNumPins[b]; i++) { + currentLedcReads[numLedc] = (_data[i] * bri) / 255; + numLedc++; + } + } + } + } + + + void initOledDisplay() + { + PinManagerPinType pins[5] = { { oledSpiClk, true }, { oledSpiData, true }, { oledSpiCs, true }, { oledSpiDc, true }, { oledSpiRst, true } }; + if (!pinManager.allocateMultiplePins(pins, 5, PinOwner::UM_QuinLEDAnPenta)) { + DEBUG_PRINTF("[%s] OLED pin allocation failed!\n", _name); + oledEnabled = oledInitDone = false; + return; + } + + oledDisplay = (U8G2 *) new U8G2_SSD1306_128X64_NONAME_2_4W_SW_SPI(U8G2_R0, oledSpiClk, oledSpiData, oledSpiCs, oledSpiDc, oledSpiRst); + if (oledDisplay == nullptr) { + DEBUG_PRINTF("[%s] OLED init failed!\n", _name); + oledEnabled = oledInitDone = false; + return; + } + + oledDisplay->begin(); + oledDisplay->setBusClock(40 * 1000 * 1000); + oledDisplay->setContrast(10); + oledDisplay->setPowerSave(0); + oledDisplay->setFont(u8g2_font_6x10_tf); + oledDisplay->setFlipMode(oledFlipScreen); + + oledDisplay->firstPage(); + do { + oledDisplay->drawXBMP(0, 16, 128, 36, quinLedLogo); + } while (oledDisplay->nextPage()); + oledLogoDrawn = millis(); + + oledInitDone = true; + } + + void cleanupOledDisplay() + { + if (oledInitDone) { + oledDisplay->clear(); + } + + pinManager.deallocatePin(oledSpiClk, PinOwner::UM_QuinLEDAnPenta); + pinManager.deallocatePin(oledSpiData, PinOwner::UM_QuinLEDAnPenta); + pinManager.deallocatePin(oledSpiCs, PinOwner::UM_QuinLEDAnPenta); + pinManager.deallocatePin(oledSpiDc, PinOwner::UM_QuinLEDAnPenta); + pinManager.deallocatePin(oledSpiRst, PinOwner::UM_QuinLEDAnPenta); + + delete oledDisplay; + + oledEnabled = false; + oledInitDone = false; + } + + bool isOledReady() + { + return oledEnabled && oledInitDone; + } + + void initSht30TempHumiditySensor() + { + PinManagerPinType pins[2] = { { shtSda, true }, { shtScl, true } }; + if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_QuinLEDAnPenta)) { + DEBUG_PRINTF("[%s] SHT30 pin allocation failed!\n", _name); + shtEnabled = shtInitDone = false; + return; + } + + TwoWire *wire = new TwoWire(1); + wire->setClock(400000); + + sht30TempHumidSensor = (SHT *) new SHT30(); + sht30TempHumidSensor->begin(shtI2cAddress, wire); + // The SHT lib calls wire.begin() again without the SDA and SCL pins... So call it again here... + wire->begin(shtSda, shtScl); + if (sht30TempHumidSensor->readStatus() == 0xFFFF) { + DEBUG_PRINTF("[%s] SHT30 init failed!\n", _name); + shtEnabled = shtInitDone = false; + return; + } + + shtInitDone = true; + } + + void cleanupSht30TempHumiditySensor() + { + if (shtInitDone) { + sht30TempHumidSensor->reset(); + } + + pinManager.deallocatePin(shtSda, PinOwner::UM_QuinLEDAnPenta); + pinManager.deallocatePin(shtScl, PinOwner::UM_QuinLEDAnPenta); + + delete sht30TempHumidSensor; + + shtEnabled = false; + shtInitDone = false; + } + + void cleanup() + { + if (isOledReady()) { + cleanupOledDisplay(); + } + + if (isShtReady()) { + cleanupSht30TempHumiditySensor(); + } + + enabled = false; + } + + bool oledCheckForNetworkChanges() + { + if (lastKnownNetworkConnected != Network.isConnected() || lastKnownIp != Network.localIP() + || lastKnownWiFiConnected != WiFi.isConnected() || lastKnownSsid != WiFi.SSID() + || lastKnownApActive != apActive || lastKnownApSsid != apSSID || lastKnownApPass != apPass || lastKnownApChannel != apChannel) { + lastKnownNetworkConnected = Network.isConnected(); + lastKnownIp = Network.localIP(); + lastKnownWiFiConnected = WiFi.isConnected(); + lastKnownSsid = WiFi.SSID(); + lastKnownApActive = apActive; + lastKnownApSsid = apSSID; + lastKnownApPass = apPass; + lastKnownApChannel = apChannel; + + return networkHasChanged = true; + } + #ifdef WLED_USE_ETHERNET + if (lastKnownEthType != ethernetType || lastKnownEthLinkUp != ETH.linkUp()) { + lastKnownEthType = ethernetType; + lastKnownEthLinkUp = ETH.linkUp(); + + return networkHasChanged = true; + } + #endif + + return networkHasChanged = false; + } + + byte oledGetNextPage() + { + return oledCurrentPage + 1 <= oledMaxPage ? oledCurrentPage + 1 : 1; + } + + void oledShowPage(byte page, bool updateLastTimePageChange = false) + { + oledCurrentPage = page; + updateOledDisplay(); + oledLastTimeUpdated = millis(); + if (updateLastTimePageChange) oledLastTimePageChange = oledLastTimeUpdated; + } + + /* + * Page 1: Overall brightness and LED outputs + * Page 2: General info like temp, humidity and others + * Page 3: Network info + */ + void updateOledDisplay() + { + if (!isOledReady()) return; + + oledDisplay->firstPage(); + do { + oledDisplay->setFont(u8g2_font_chroma48medium8_8r); + oledDisplay->drawStr(0, 8, serverDescription); + oledDisplay->drawHLine(0, 13, 127); + oledDisplay->setFont(u8g2_font_6x10_tf); + + byte charPerRow = 21; + byte oledRow = 23; + switch (oledCurrentPage) { + // LED Outputs + case 1: + { + char charCurrentBrightness[charPerRow+1] = "Brightness:"; + if (oledUseProgressBars) { + oledDisplay->drawStr(0, oledRow, charCurrentBrightness); + // There is no method to draw a filled box with rounded corners. So draw the rounded frame first, then fill that frame accordingly to LED percentage + oledDisplay->drawRFrame(68, oledRow - 6, 60, 7, 2); + oledDisplay->drawBox(69, oledRow - 5, int(round(58*getPercentageForBrightness(bri)) / 100), 5); + } + else { + sprintf(charCurrentBrightness, "%s %d%%", charCurrentBrightness, getPercentageForBrightness(bri)); + oledDisplay->drawStr(0, oledRow, charCurrentBrightness); + } + oledRow += 8; + + byte drawnLines = 0; + for (int8_t app = 0; app <= 4; app++) { + for (int8_t clp = 0; clp <= 4; clp++) { + if (anPentaPins[app] == currentLedPins[clp]) { + char charCurrentLedcReads[17]; + sprintf(charCurrentLedcReads, "LED %d:", app+1); + if (oledUseProgressBars) { + oledDisplay->drawStr(0, oledRow+(drawnLines*8), charCurrentLedcReads); + oledDisplay->drawRFrame(38, oledRow - 6 + (drawnLines * 8), 90, 7, 2); + oledDisplay->drawBox(39, oledRow - 5 + (drawnLines * 8), int(round(88*getPercentageForBrightness(currentLedcReads[clp])) / 100), 5); + } + else { + sprintf(charCurrentLedcReads, "%s %d%%", charCurrentLedcReads, getPercentageForBrightness(currentLedcReads[clp])); + oledDisplay->drawStr(0, oledRow+(drawnLines*8), charCurrentLedcReads); + } + + drawnLines++; + } + } + } + break; + } + + // Various info + case 2: + { + if (isShtReady() && shtReadDataSuccess) { + char charShtCurrentTemp[charPerRow+4]; // Reserve 3 more bytes than usual as we gonna have one UTF8 char which can be up to 4 bytes. + sprintf(charShtCurrentTemp, "Temperature: %.02f°C", shtCurrentTemp); + char charShtCurrentHumidity[charPerRow+1]; + sprintf(charShtCurrentHumidity, "Humidity: %.02f RH", shtCurrentHumidity); + + oledDisplay->drawUTF8(0, oledRow, charShtCurrentTemp); + oledDisplay->drawStr(0, oledRow + 10, charShtCurrentHumidity); + oledRow += 20; + } + + if (mqttEnabled && mqttServer[0] != 0) { + char charMqttStatus[charPerRow+1]; + sprintf(charMqttStatus, "MQTT: %s", (WLED_MQTT_CONNECTED ? "Connected" : "Disconnected")); + oledDisplay->drawStr(0, oledRow, charMqttStatus); + oledRow += 10; + } + + // Always draw these two on the bottom + char charUptime[charPerRow+1]; + sprintf(charUptime, "Uptime: %ds", int(millis()/1000 + rolloverMillis*4294967)); // From json.cpp + oledDisplay->drawStr(0, 53, charUptime); + + char charWledVersion[charPerRow+1]; + sprintf(charWledVersion, "WLED v%s", versionString); + oledDisplay->drawStr(0, 63, charWledVersion); + break; + } + + // Network Info + case 3: + #ifdef WLED_USE_ETHERNET + if (lastKnownEthType == WLED_ETH_NONE) { + oledDisplay->drawStr(0, oledRow, "Ethernet: No board selected"); + oledRow += 10; + } + else if (!lastKnownEthLinkUp) { + oledDisplay->drawStr(0, oledRow, "Ethernet: Link Down"); + oledRow += 10; + } + #endif + + if (lastKnownNetworkConnected) { + #ifdef WLED_USE_ETHERNET + if (lastKnownEthLinkUp) { + oledDisplay->drawStr(0, oledRow, "Ethernet: Link Up"); + oledRow += 10; + } + else + #endif + // Wi-Fi can be active with ETH being connected, but we don't mind... + if (lastKnownWiFiConnected) { + #ifdef WLED_USE_ETHERNET + if (!lastKnownEthLinkUp) { + #endif + + oledDisplay->drawStr(0, oledRow, "Wi-Fi: Connected"); + char currentSsidChar[lastKnownSsid.length() + 1]; + lastKnownSsid.toCharArray(currentSsidChar, lastKnownSsid.length() + 1); + char charCurrentSsid[50]; + sprintf(charCurrentSsid, "SSID: %s", currentSsidChar); + oledDisplay->drawStr(0, oledRow + 10, charCurrentSsid); + oledRow += 20; + + #ifdef WLED_USE_ETHERNET + } + #endif + } + + String currentIpStr = lastKnownIp.toString(); + char currentIpChar[currentIpStr.length() + 1]; + currentIpStr.toCharArray(currentIpChar, currentIpStr.length() + 1); + char charCurrentIp[30]; + sprintf(charCurrentIp, "IP: %s", currentIpChar); + oledDisplay->drawStr(0, oledRow, charCurrentIp); + } + // If WLED AP is active. Theoretically, it can even be active with ETH being connected, but we don't mind... + else if (lastKnownApActive) { + char charCurrentApStatus[charPerRow+1]; + sprintf(charCurrentApStatus, "WLED AP: %s (Ch: %d)", (lastKnownApActive ? "On" : "Off"), lastKnownApChannel); + oledDisplay->drawStr(0, oledRow, charCurrentApStatus); + + char charCurrentApSsid[charPerRow+1]; + sprintf(charCurrentApSsid, "SSID: %s", lastKnownApSsid); + oledDisplay->drawStr(0, oledRow + 10, charCurrentApSsid); + + char charCurrentApPass[charPerRow+1]; + sprintf(charCurrentApPass, "PW: %s", lastKnownApPass); + oledDisplay->drawStr(0, oledRow + 20, charCurrentApPass); + + // IP is hardcoded / no var exists in WLED at the time this mod was coded, so also hardcode it here + oledDisplay->drawStr(0, oledRow + 30, "IP: 4.3.2.1"); + } + + break; + } + } while (oledDisplay->nextPage()); + } + + bool isShtReady() + { + return shtEnabled && shtInitDone; + } + + + public: + // strings to reduce flash memory usage (used more than twice) + static const char _name[]; + static const char _enabled[]; + static const char _oledEnabled[]; + static const char _oledUseProgressBars[]; + static const char _oledFlipScreen[]; + static const char _oledSecondsPerPage[]; + static const char _oledFixBuggedScreen[]; + static const char _shtEnabled[]; + static const unsigned char quinLedLogo[]; + + + static int8_t getPercentageForBrightness(byte brightness) + { + return int(((float)brightness / (float)255) * 100); + } + + + /* + * 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() + { + if (enabled) { + lastKnownBri = bri; + + if (oledEnabled) { + initOledDisplay(); + } + + if (shtEnabled) { + initSht30TempHumiditySensor(); + } + + getCurrentUsedLedPins(); + + initDone = true; + } + + firstRunDone = true; + } + + /* + * 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 (!enabled || !initDone || strip.isUpdating()) return; + + if (isShtReady()) { + if (millis() - shtLastTimeUpdated > 30000 && !shtDataRequested) { + sht30TempHumidSensor->requestData(); + shtDataRequested = true; + + shtLastTimeUpdated = millis(); + } + + if (shtDataRequested) { + if (sht30TempHumidSensor->dataReady()) { + if (sht30TempHumidSensor->readData()) { + shtCurrentTemp = sht30TempHumidSensor->getTemperature(); + shtCurrentHumidity = sht30TempHumidSensor->getHumidity(); + shtReadDataSuccess = true; + } + else { + shtReadDataSuccess = false; + } + + shtDataRequested = false; + } + } + } + + if (isOledReady() && millis() - oledLogoDrawn > 3000) { + // Check for changes on the current page and update the OLED if a change is detected + if (millis() - oledLastTimeUpdated > 150) { + // If there was a network change, force page 3 (network page) + if (oledCheckForNetworkChanges()) { + oledCurrentPage = 3; + } + // Only redraw a page if there was a change for that page + switch (oledCurrentPage) { + case 1: + lastKnownBri = bri; + // Probably causes lag to always do ledcRead(), so rather re-do the math, 'cause we can't easily get it... + getCurrentLedcValues(); + + if (bri != lastKnownBri || lastKnownLedcReads[0] != currentLedcReads[0] || lastKnownLedcReads[1] != currentLedcReads[1] || lastKnownLedcReads[2] != currentLedcReads[2] + || lastKnownLedcReads[3] != currentLedcReads[3] || lastKnownLedcReads[4] != currentLedcReads[4]) { + lastKnownLedcReads[0] = currentLedcReads[0]; lastKnownLedcReads[1] = currentLedcReads[1]; lastKnownLedcReads[2] = currentLedcReads[2]; lastKnownLedcReads[3] = currentLedcReads[3]; lastKnownLedcReads[4] = currentLedcReads[4]; + + oledShowPage(1); + } + break; + + case 2: + if (shtLastKnownTemp != shtCurrentTemp || shtLastKnownHumidity != shtCurrentHumidity) { + shtLastKnownTemp = shtCurrentTemp; + shtLastKnownHumidity = shtCurrentHumidity; + + oledShowPage(2); + } + break; + + case 3: + if (networkHasChanged) { + networkHasChanged = false; + + oledShowPage(3, true); + } + break; + } + } + // Cycle through OLED pages + if (millis() - oledLastTimePageChange > oledSecondsPerPage * 1000) { + // Periodically fixing a "bugged out" OLED. More details in the ReadMe + if (oledFixBuggedScreen && millis() - oledLastTimeFixBuggedScreen > 60000) { + oledDisplay->begin(); + oledLastTimeFixBuggedScreen = millis(); + } + oledShowPage(oledGetNextPage(), true); + } + } + } + + void addToConfig(JsonObject &root) + { + JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname + + top[FPSTR(_enabled)] = enabled; + top[FPSTR(_oledEnabled)] = oledEnabled; + top[FPSTR(_oledUseProgressBars)] = oledUseProgressBars; + top[FPSTR(_oledFlipScreen)] = oledFlipScreen; + top[FPSTR(_oledSecondsPerPage)] = oledSecondsPerPage; + top[FPSTR(_oledFixBuggedScreen)] = oledFixBuggedScreen; + top[FPSTR(_shtEnabled)] = shtEnabled; + + // Update LED pins on config save + getCurrentUsedLedPins(); + } + + /** + * readFromConfig() is called before setup() to populate properties from values stored in cfg.json + * + * The function should return true if configuration was successfully loaded or false if there was no configuration. + */ + bool readFromConfig(JsonObject &root) + { + JsonObject top = root[FPSTR(_name)]; + if (top.isNull()) { + DEBUG_PRINTF("[%s] No config found. (Using defaults.)\n", _name); + return false; + } + + bool oldEnabled = enabled; + bool oldOledEnabled = oledEnabled; + bool oldOledFlipScreen = oledFlipScreen; + bool oldShtEnabled = shtEnabled; + + getJsonValue(top[FPSTR(_enabled)], enabled); + getJsonValue(top[FPSTR(_oledEnabled)], oledEnabled); + getJsonValue(top[FPSTR(_oledUseProgressBars)], oledUseProgressBars); + getJsonValue(top[FPSTR(_oledFlipScreen)], oledFlipScreen); + getJsonValue(top[FPSTR(_oledSecondsPerPage)], oledSecondsPerPage); + getJsonValue(top[FPSTR(_oledFixBuggedScreen)], oledFixBuggedScreen); + getJsonValue(top[FPSTR(_shtEnabled)], shtEnabled); + + // First run: reading from cfg.json, nothing to do here, will be all done in setup() + if (!firstRunDone) { + DEBUG_PRINTF("[%s] First run, nothing to do\n", _name); + } + // Check if mod has been en-/disabled + else if (enabled != oldEnabled) { + enabled ? setup() : cleanup(); + DEBUG_PRINTF("[%s] Usermod has been en-/disabled\n", _name); + } + // Config has been changed, so adopt to changes + else if (enabled) { + if (oldOledEnabled != oledEnabled) { + oledEnabled ? initOledDisplay() : cleanupOledDisplay(); + } + else if (oledEnabled && oldOledFlipScreen != oledFlipScreen) { + oledDisplay->clear(); + oledDisplay->setFlipMode(oledFlipScreen); + oledShowPage(oledCurrentPage); + } + + if (oldShtEnabled != shtEnabled) { + shtEnabled ? initSht30TempHumiditySensor() : cleanupSht30TempHumiditySensor(); + } + + DEBUG_PRINTF("[%s] Config (re)loaded\n", _name); + } + + return true; + } + + void addToJsonInfo(JsonObject& root) + { + if (!enabled && !isShtReady()) { + return; + } + + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray jsonTemp = user.createNestedArray("Temperature"); + JsonArray jsonHumidity = user.createNestedArray("Humidity"); + + if (shtLastTimeUpdated == 0 || !shtReadDataSuccess) { + jsonTemp.add(0); + jsonHumidity.add(0); + if (shtLastTimeUpdated == 0) { + jsonTemp.add(" Not read yet"); + jsonHumidity.add(" Not read yet"); + } + else { + jsonTemp.add(" Error"); + jsonHumidity.add(" Error"); + } + + return; + } + + jsonHumidity.add(shtCurrentHumidity); + jsonHumidity.add(" RH"); + + jsonTemp.add(shtCurrentTemp); + jsonTemp.add(" °C"); + } + + /* + * 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_QUINLED_AN_PENTA; + } +}; + +// strings to reduce flash memory usage (used more than twice) +// Config settings +const char QuinLEDAnPentaUsermod::_name[] PROGMEM = "QuinLED-An-Penta"; +const char QuinLEDAnPentaUsermod::_enabled[] PROGMEM = "Enabled"; +const char QuinLEDAnPentaUsermod::_oledEnabled[] PROGMEM = "Enable-OLED"; +const char QuinLEDAnPentaUsermod::_oledUseProgressBars[] PROGMEM = "OLED-Use-Progress-Bars"; +const char QuinLEDAnPentaUsermod::_oledFlipScreen[] PROGMEM = "OLED-Flip-Screen-180"; +const char QuinLEDAnPentaUsermod::_oledSecondsPerPage[] PROGMEM = "OLED-Seconds-Per-Page"; +const char QuinLEDAnPentaUsermod::_oledFixBuggedScreen[] PROGMEM = "OLED-Fix-Bugged-Screen"; +const char QuinLEDAnPentaUsermod::_shtEnabled[] PROGMEM = "Enable-SHT30-Temp-Humidity-Sensor"; +// Other strings + +const unsigned char QuinLEDAnPentaUsermod::quinLedLogo[] PROGMEM = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, 0xFD, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x00, 0x80, 0xFF, + 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x3F, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x1F, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xF0, 0x07, 0xFE, 0xFF, 0xFF, 0x0F, 0xFC, + 0xFF, 0xFF, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xFC, 0x0F, 0xFE, + 0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0xE3, 0xFF, 0xA5, 0xFF, 0xFF, 0xFF, + 0x0F, 0xFC, 0x1F, 0xFE, 0xFF, 0xFF, 0x1F, 0xFC, 0xFF, 0xFF, 0xE1, 0xFF, + 0x00, 0xF0, 0xE3, 0xFF, 0x0F, 0xFE, 0x1F, 0xFE, 0xFF, 0xFF, 0x3F, 0xFF, + 0xFF, 0xFF, 0xE3, 0xFF, 0x00, 0xF0, 0x00, 0xFF, 0x07, 0xFE, 0x1F, 0xFC, + 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0xFF, 0x00, 0xF0, 0x00, 0xFE, + 0x07, 0xFF, 0x1F, 0xFC, 0xF0, 0xC7, 0x3F, 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, + 0xF1, 0xFF, 0x00, 0xFC, 0x07, 0xFF, 0x1F, 0xFE, 0xF0, 0xC3, 0x1F, 0xFE, + 0x00, 0xFF, 0xE1, 0xFF, 0xF1, 0xFF, 0x30, 0xF8, 0x07, 0xFF, 0x1F, 0xFE, + 0xF0, 0xC3, 0x1F, 0xFE, 0x00, 0xFC, 0xC3, 0xFF, 0xE1, 0xFF, 0xF0, 0xF0, + 0x03, 0xFF, 0x0F, 0x7E, 0xF0, 0xC3, 0x1F, 0x7E, 0x00, 0xF8, 0xE3, 0xFF, + 0xE1, 0xFF, 0xF1, 0xF1, 0x83, 0xFF, 0x0F, 0x7E, 0xF0, 0xC3, 0x1F, 0x7E, + 0x00, 0xF0, 0xC3, 0xFF, 0xE1, 0xFF, 0xF1, 0xE1, 0x83, 0xFF, 0x0F, 0xFE, + 0xF0, 0xC3, 0x1F, 0xFE, 0xF8, 0xF0, 0xC3, 0xFF, 0xA1, 0xFF, 0xF1, 0xE3, + 0x81, 0xFF, 0x0F, 0x7E, 0xF0, 0xC1, 0x1F, 0x7E, 0xF0, 0xF0, 0xC3, 0xFF, + 0x01, 0xF8, 0xE1, 0xC3, 0x83, 0xFF, 0x0F, 0x7F, 0xF8, 0xC3, 0x1F, 0x7E, + 0xF8, 0xF0, 0xC3, 0xFF, 0x03, 0xF8, 0xE1, 0xC7, 0x81, 0xE4, 0x0F, 0x7F, + 0xF0, 0xC3, 0x1F, 0xFE, 0xF8, 0xF0, 0xC3, 0xFF, 0x01, 0xF8, 0xE3, 0xC7, + 0x01, 0xC0, 0x07, 0x7F, 0xF8, 0xC1, 0x1F, 0x7E, 0xF0, 0xE1, 0xC3, 0xFF, + 0xC3, 0xFD, 0xE1, 0x87, 0x01, 0x00, 0x07, 0x7F, 0xF8, 0xC3, 0x1F, 0x7E, + 0xF8, 0xF0, 0xC3, 0xFF, 0xE3, 0xFF, 0xE3, 0x87, 0x01, 0x00, 0x82, 0x3F, + 0xF8, 0xE1, 0x1F, 0xFE, 0xF8, 0xE1, 0xC3, 0xFF, 0xC3, 0xFF, 0xC3, 0x87, + 0x01, 0x00, 0x80, 0x3F, 0xF8, 0xC1, 0x1F, 0x7E, 0xF0, 0xF1, 0xC3, 0xFF, + 0xC3, 0xFF, 0xC3, 0x87, 0x03, 0x0F, 0x80, 0x3F, 0xF8, 0xE1, 0x0F, 0x7E, + 0xF8, 0xE1, 0x87, 0xFF, 0xC3, 0xFF, 0xC7, 0x87, 0x03, 0x04, 0xC0, 0x7F, + 0xF0, 0xE1, 0x0F, 0xFF, 0xF8, 0xF1, 0x87, 0xFF, 0xC3, 0xFF, 0xC3, 0x87, + 0x07, 0x00, 0xE0, 0x7F, 0x00, 0xE0, 0x1F, 0x7E, 0xF0, 0xE0, 0xC3, 0xFF, + 0xC7, 0xFF, 0x87, 0x87, 0x0F, 0x00, 0xE0, 0x7F, 0x00, 0xE0, 0x0F, 0x7F, + 0xF8, 0xE1, 0x07, 0x80, 0x07, 0xEA, 0x87, 0xC1, 0x0F, 0x00, 0x80, 0xFF, + 0x00, 0xE0, 0x1F, 0x7E, 0xF0, 0xE1, 0x07, 0x00, 0x03, 0x80, 0x07, 0xC0, + 0x7F, 0x00, 0x00, 0xFF, 0x01, 0xE0, 0x1F, 0xFF, 0xF8, 0xE1, 0x07, 0x00, + 0x07, 0x00, 0x07, 0xE0, 0xFF, 0xF7, 0x01, 0xFF, 0x57, 0xF7, 0x9F, 0xFF, + 0xFC, 0xF1, 0x0F, 0x00, 0x07, 0x80, 0x0F, 0xE0, 0xFF, 0xFF, 0x03, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xBF, 0xFE, + 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +}; \ No newline at end of file diff --git a/usermods/quinled-an-penta/readme.md b/usermods/quinled-an-penta/readme.md new file mode 100644 index 000000000..c292736c8 --- /dev/null +++ b/usermods/quinled-an-penta/readme.md @@ -0,0 +1,69 @@ +# QuinLED-An-Penta +The (un)official usermod to get the best out of the QuinLED-An-Penta, like using the OLED and the SHT30 temperature/humidity sensor. + +## Requirements +* "u8gs" by olikraus, v2.28 or higher: https://github.com/olikraus/u8g2 +* "SHT85" by Rob Tillaart, v0.2 or higher: https://github.com/RobTillaart/SHT85 + +## Usermod installation +Simply copy the below block (build task) to your `platformio_override.ini` and compile WLED using this new build task. Or use an existing one and add the buildflag `-D QUINLED_AN_PENTA`. + +ESP32 (**without** ethernet): +``` +[env:custom_esp32dev_usermod_quinled_an_penta] +extends = env:esp32dev +build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32 -D QUINLED_AN_PENTA +lib_deps = ${esp32.lib_deps} + olikraus/U8g2@~2.28.8 + robtillaart/SHT85@~0.2.0 +``` + +ESP32 (**with** ethernet): +``` +[env:custom_esp32dev_usermod_quinled_an_penta] +extends = env:esp32dev +build_flags = ${common.build_flags_esp32} -D WLED_RELEASE_NAME=ESP32_Ethernet -D WLED_USE_ETHERNET -D QUINLED_AN_PENTA +lib_deps = ${esp32.lib_deps} + olikraus/U8g2@~2.28.8 + robtillaart/SHT85@~0.2.0 +``` + +## Some words about the (optional) OLED +This mod has been optimized for an SSD1306 driven 128x64 OLED. Using a smaller OLED or an OLED using a different driver will result in unexpected results. +I highly recommend using these "two color monochromatic OLEDs", which have the first 16 pixels in a different color than the other 48, e.g. a yellow/blue OLED. +Also note, you need to have an **SPI** driven OLED, **not i2c**! + +### My OLED flickers after some time, what should I do? +That's a tricky one: During development I saw that the OLED sometimes starts to "bug out" / flicker and won't work anymore. This seems to be caused by the high PWM interference the board produces. It seems to loose it's settings and then doesn't know how to draw anymore. Turns out the only way to fix this is to call the libraries `begin()` method again which will re-initialize the display. +If you're facing this issue, you can enable a setting I've added which will call the `begin()` roughly every 60 seconds between a page change. This will make the page change take ~500ms, but will fix the display. + +## Configuration +Navigate to the "Config" and then to the "Usermods" section. If you compiled WLED with `-D QUINLED_AN_PENTA`, you will see the config for it there: +* Enable-OLED: + * What it does: Enabled the optional SPI driven OLED that can be mounted to the 7-pin female header + * Possible values: Enabled/Disabled + * Default: Disabled +* OLED-Use-Progress-Bars: + * What it does: Toggle between showing percentage numbers or a progress-bar-like visualization for overall brightness and each LED channels brightness level + * Possible values: Enabled/Disabled + * Default: Disabled +* OLED-Flip-Screen-180: + * What it does: Flips the screen 180° / upside-down + * Possible values: Enabled/Disabled + * Default: Disabled +* OLED-Seconds-Per-Page: + * What it does: Defines how long the OLED should stay on one page in seconds before changing to the next + * Possible values: Enabled/Disabled + * Default: 10 +* OLED-Fix-Bugged-Screen: + * What it does: Enable this if your OLED flickers after some time. For more info read above under ["My OLED flickers after some time, what should I do?"](#My-OLED-flickers-after-some-time-what-should-I-do) + * Possible values: Enabled/Disabled + * Default: Disabled +* Enable-SHT30-Temp-Humidity-Sensor: + * What it does: Enabled the onboard SHT30 temperature and humidity sensor + * Possible values: Enabled/Disabled + * Default: Disabled + +## Change log +2021-10 +* First implementation. \ No newline at end of file diff --git a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h index cde220121..e57641bf9 100644 --- a/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h +++ b/usermods/rgb-rotary-encoder/rgb-rotary-encoder.h @@ -40,7 +40,7 @@ class RgbRotaryEncoderUsermod : public Usermod void initRotaryEncoder() { PinManagerPinType pins[2] = { { eaIo, false }, { ebIo, false } }; - if (!pinManager.allocateMultiplePins(pins, 2, UM_RGBRotaryEncoder)) { + if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_RGBRotaryEncoder)) { eaIo = -1; ebIo = -1; cleanup(); @@ -208,7 +208,7 @@ class RgbRotaryEncoderUsermod : public Usermod lastKnownBri = bri; updateLeds(); - colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE); + colorUpdated(CALL_MODE_DIRECT_CHANGE); } // If the brightness is changed not with the rotary, update the rotary @@ -323,7 +323,7 @@ class RgbRotaryEncoderUsermod : public Usermod */ uint16_t getId() { - return 0x4711; + return USERMOD_RGB_ROTARY_ENCODER; } //More methods can be added in the future, this example will then be extended. diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 345ba64d4..dd526732f 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -108,7 +108,6 @@ void WS2812FX::finalizeInit(void) if (pins[0] == 3) bd->reinit(); #endif } - ledCount = _length; //segments are created in makeAutoSegments(); @@ -637,10 +636,9 @@ void WS2812FX::resetSegments() { } void WS2812FX::makeAutoSegments() { - uint16_t segStarts[MAX_NUM_SEGMENTS] = {0}; - uint16_t segStops [MAX_NUM_SEGMENTS] = {0}; - if (autoSegments) { //make one segment per bus + uint16_t segStarts[MAX_NUM_SEGMENTS] = {0}; + uint16_t segStops [MAX_NUM_SEGMENTS] = {0}; uint8_t s = 0; for (uint8_t i = 0; i < busses.getNumBusses(); i++) { Bus* b = busses.getBus(i); diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 593a971f9..4c9a2ae31 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -601,9 +601,10 @@ class BusManager { return numBusses; } + //semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit()) uint16_t getTotalLength() { uint16_t len = 0; - for (uint8_t i=0; igetLength(); + for (uint8_t i=0; igetLength(); return len; } diff --git a/wled00/button.cpp b/wled00/button.cpp index d64ae0a4f..39c1e4f7f 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -5,6 +5,10 @@ */ #define WLED_DEBOUNCE_THRESHOLD 50 //only consider button input of at least 50ms as valid (debouncing) +#define WLED_LONG_PRESS 600 //long press if button is released after held for at least 600ms +#define WLED_DOUBLE_PRESS 350 //double press if another press within 350ms after a short press +#define WLED_LONG_REPEATED_ACTION 300 //how often a repeated action (e.g. dimming) is fired on long press on button IDs >0 +#define WLED_LONG_AP 6000 //how long the button needs to be held to activate WLED-AP static const char _mqtt_topic_button[] PROGMEM = "%s/button/%d"; // optimize flash usage @@ -50,7 +54,7 @@ void doublePressAction(uint8_t b) { if (!macroDoublePress[b]) { switch (b) { - case 0: toggleOnOff(); colorUpdated(CALL_MODE_BUTTON); break; + //case 0: toggleOnOff(); colorUpdated(CALL_MODE_BUTTON); break; //instant short press on button 0 if no macro set default: ++effectPalette %= strip.getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break; } } else { @@ -232,11 +236,11 @@ void handleButton() if (!buttonPressedBefore[b]) buttonPressedTime[b] = millis(); buttonPressedBefore[b] = true; - if (millis() - buttonPressedTime[b] > 600) { //long press + if (millis() - buttonPressedTime[b] > WLED_LONG_PRESS) { //long press if (!buttonLongPressed[b]) longPressAction(b); - else if (b) { // repeatable action (~3 times per s) on button > 0 + else if (b) { //repeatable action (~3 times per s) on button > 0 longPressAction(b); - buttonPressedTime[b] = millis() - 300; // 300ms + buttonPressedTime[b] = millis() - WLED_LONG_REPEATED_ACTION; //300ms } buttonLongPressed[b] = true; } @@ -245,25 +249,28 @@ void handleButton() long dur = millis() - buttonPressedTime[b]; if (dur < WLED_DEBOUNCE_THRESHOLD) {buttonPressedBefore[b] = false; continue;} //too short "press", debounce - bool doublePress = buttonWaitTime[b]; //did we have short press before? + bool doublePress = buttonWaitTime[b]; //did we have a short press before? buttonWaitTime[b] = 0; - if (b == 0 && dur > 6000) { //long press on button 0 (when released) + if (b == 0 && dur > WLED_LONG_AP) { //long press on button 0 (when released) WLED::instance().initAP(true); } else if (!buttonLongPressed[b]) { //short press - // if this is second release within 350ms it is a double press (buttonWaitTime!=0) - if (doublePress) { - doublePressAction(b); - } else { - buttonWaitTime[b] = millis(); + if (b == 0 && !macroDoublePress[b]) { //don't wait for double press on button 0 if no double press macro set + shortPressAction(b); + } else { //double press if less than 350 ms between current press and previous short press release (buttonWaitTime!=0) + if (doublePress) { + doublePressAction(b); + } else { + buttonWaitTime[b] = millis(); + } } } buttonPressedBefore[b] = false; buttonLongPressed[b] = false; } - // if 450ms elapsed since last press/release it is a short press - if (buttonWaitTime[b] && millis() - buttonWaitTime[b] > 350 && !buttonPressedBefore[b]) { + //if 350ms elapsed since last short press release it is a short press + if (buttonWaitTime[b] && millis() - buttonWaitTime[b] > WLED_DOUBLE_PRESS && !buttonPressedBefore[b]) { buttonWaitTime[b] = 0; shortPressAction(b); } diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index b78bfe189..f4e72ff01 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -80,18 +80,13 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { // initialize LED pins and lengths prior to other HW (except for ethernet) JsonObject hw_led = hw[F("led")]; - CJSON(ledCount, hw_led[F("total")]); - if (ledCount > MAX_LEDS) ledCount = MAX_LEDS; - CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]); CJSON(strip.milliampsPerLed, hw_led[F("ledma")]); uint8_t rgbwMode = hw_led[F("rgbwm")] | RGBW_MODE_DUAL; // use global setting (legacy) CJSON(allowCCT, hw_led["cct"]); JsonArray ins = hw_led["ins"]; - - uint16_t lC = 0; - + if (fromFS || !ins.isNull()) { uint8_t s = 0; // bus iterator busses.removeAll(); @@ -119,15 +114,12 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { bool refresh = elm["ref"] | false; ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh s++; - uint16_t busEnd = start + length; - if (busEnd > lC) lC = busEnd; BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, awMode); mem += BusManager::memUsage(bc); if (mem <= MAX_LED_MEMORY && busses.getNumBusses() <= WLED_MAX_BUSSES) busses.add(bc); // finalization will be done in WLED::beginStrip() } // finalization done in beginStrip() } - if (lC > ledCount) ledCount = lC; // fix incorrect total length (honour analog setup) if (hw_led["rev"]) busses.getBus(0)->reversed = true; //set 0.11 global reversed setting for first bus // read multiple button configuration @@ -531,7 +523,7 @@ void serializeConfig() { JsonObject hw = doc.createNestedObject("hw"); JsonObject hw_led = hw.createNestedObject("led"); - hw_led[F("total")] = ledCount; + hw_led[F("total")] = strip.getLengthTotal(); //no longer read, but provided for compatibility on downgrade hw_led[F("maxpwr")] = strip.ablMilliampsMax; hw_led[F("ledma")] = strip.milliampsPerLed; hw_led["cct"] = allowCCT; diff --git a/wled00/const.h b/wled00/const.h index 507800827..c2877f59e 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -62,6 +62,8 @@ #define USERMOD_ID_PWM_FAN 19 //Usermod "usermod_PWM_fan.h" #define USERMOD_ID_BH1750 20 //Usermod "usermod_bh1750.h" #define USERMOD_ID_SEVEN_SEGMENT_DISPLAY 21 //Usermod "usermod_v2_seven_segment_display.h" +#define USERMOD_RGB_ROTARY_ENCODER 22 //Usermod "rgb-rotary-encoder.h" +#define USERMOD_ID_QUINLED_AN_PENTA 23 //Usermod "quinled-an-penta.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot @@ -300,7 +302,7 @@ //this is merely a default now and can be changed at runtime #ifndef LEDPIN -#ifdef ESP8266 +#if defined(ESP8266) || (defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)) #define LEDPIN 2 // GPIO2 (D4) on Wemod D1 mini compatible boards #else #define LEDPIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards diff --git a/wled00/data/settings_time.htm b/wled00/data/settings_time.htm index 13e4d3b93..ca6923716 100644 --- a/wled00/data/settings_time.htm +++ b/wled00/data/settings_time.htm @@ -132,6 +132,7 @@ +
UTC offset: seconds (max. 18 hours)
Current local time is unknown.
diff --git a/wled00/dmx.cpp b/wled00/dmx.cpp index 64e02ec17..284219547 100644 --- a/wled00/dmx.cpp +++ b/wled00/dmx.cpp @@ -18,7 +18,8 @@ void handleDMX() uint8_t brightness = strip.getBrightness(); - for (int i = DMXStartLED; i < ledCount; i++) { // uses the amount of LEDs as fixture count + uint16_t len = strip.getLengthTotal(); + for (int i = DMXStartLED; i < len; i++) { // uses the amount of LEDs as fixture count uint32_t in = strip.getPixelColor(i); // get the colors for the individual fixtures as suggested by Aircoookie in issue #462 byte w = W(in); diff --git a/wled00/e131.cpp b/wled00/e131.cpp index d556e6768..ba9e939e2 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -34,9 +34,11 @@ void handleDDPPacket(e131_packet_t* p) { realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP); - for (uint16_t i = start; i < stop; i++) { - setRealtimePixel(i, data[c], data[c+1], data[c+2], 0); - c+=3; + if (!realtimeOverride) { + for (uint16_t i = start; i < stop; i++) { + setRealtimePixel(i, data[c], data[c+1], data[c+2], 0); + c+=3; + } } bool push = p->flags & DDP_PUSH_FLAG; @@ -102,6 +104,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ // update status info realtimeIP = clientIP; byte wChannel = 0; + uint16_t totalLen = strip.getLengthTotal(); switch (DMXMode) { case DMX_MODE_DISABLED: @@ -114,7 +117,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ realtimeLock(realtimeTimeoutMs, mde); if (realtimeOverride) return; wChannel = (dmxChannels-DMXAddress+1 > 3) ? e131_data[DMXAddress+3] : 0; - for (uint16_t i = 0; i < ledCount; i++) + for (uint16_t i = 0; i < totalLen; i++) setRealtimePixel(i, e131_data[DMXAddress+0], e131_data[DMXAddress+1], e131_data[DMXAddress+2], wChannel); break; @@ -129,7 +132,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ bri = e131_data[DMXAddress+0]; strip.setBrightness(bri); } - for (uint16_t i = 0; i < ledCount; i++) + for (uint16_t i = 0; i < totalLen; i++) setRealtimePixel(i, e131_data[DMXAddress+1], e131_data[DMXAddress+2], e131_data[DMXAddress+3], wChannel); break; diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 4d1007fc1..b995f31e4 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -199,6 +199,7 @@ bool isAsterisksOnly(const char* str, byte maxLen); void handleSettingsSet(AsyncWebServerRequest *request, byte subPage); bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply=true); int getNumVal(const String* req, uint16_t pos); +void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255); bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte maxv=255); //udp.cpp diff --git a/wled00/html_settings.h b/wled00/html_settings.h index 94213b50e..9d3da43d2 100644 --- a/wled00/html_settings.h +++ b/wled00/html_settings.h @@ -353,14 +353,15 @@ US-MST/MDT
UTC offset: seconds (max. 18 hours)
-Current local time is unknown.
Latitude (N): - - Longitude (E):

Clock

Clock Overlay: - +
UTC offset: seconds (max. 18 hours)
Current local time is +unknown.
Latitude (N): Longitude (E):

Clock

Clock Overlay:
First LED: Last LED: ()) { + *val = elem; + return true; + } else if (elem.is()) { + const char* str = elem; + size_t len = strnlen(str, 12); + if (len == 0 || len > 10) return false; + parseNumber(str, val, vmin, vmax); + return true; + } + return false; //key does not exist +} + void deserializeSegment(JsonObject elem, byte it, byte presetId) { byte id = elem["id"] | it; @@ -62,12 +76,10 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId) } if (stop > start && seg.offset > len -1) seg.offset = len -1; - int segbri = elem["bri"] | -1; - if (segbri == 0) { - seg.setOption(SEG_OPTION_ON, 0, id); - } else if (segbri > 0) { - seg.setOpacity(segbri, id); - seg.setOption(SEG_OPTION_ON, 1, id); + byte segbri = 0; + if (getVal(elem["bri"], &segbri)) { + if (segbri > 0) seg.setOpacity(segbri, id); + seg.setOption(SEG_OPTION_ON, segbri, id); } bool on = elem["on"] | seg.getOption(SEG_OPTION_ON); @@ -218,7 +230,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) strip.applyToAllSelected = false; bool stateResponse = root[F("v")] | false; - bri = root["bri"] | bri; + getVal(root["bri"], &bri); bool on = root["on"] | (bri > 0); if (!on != !bri) toggleOnOff(); @@ -327,21 +339,21 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) strip.deserializeMap(ledmap); } - int ps = root[F("psave")] | -1; + byte ps = root[F("psave")]; if (ps > 0) { DEBUG_PRINTLN(F("Saving preset")); savePreset(ps, true, nullptr, root); } else { - ps = root[F("pdel")] | -1; //deletion + ps = root[F("pdel")]; //deletion if (ps > 0) { DEBUG_PRINTLN(F("Deleting preset")); deletePreset(ps); } - ps = root["ps"] | -1; //load preset (clears state request!) - if (ps >= 0) { + + if (getVal(root["ps"], &presetCycCurr, 1, 5)) { //load preset (clears state request!) DEBUG_PRINTLN(F("Applying preset")); if (!presetId) unloadPlaylist(); //stop playlist if preset changed manually - applyPreset(ps, callMode); + applyPreset(presetCycCurr, callMode); return stateResponse; } @@ -478,7 +490,7 @@ void serializeInfo(JsonObject root) //root[F("cn")] = WLED_CODENAME; JsonObject leds = root.createNestedObject("leds"); - leds[F("count")] = ledCount; + leds[F("count")] = strip.getLengthTotal(); leds[F("rgbw")] = strip.isRgbw; leds[F("wv")] = false; leds["cct"] = allowCCT; @@ -945,7 +957,7 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient) } #endif - uint16_t used = ledCount; + uint16_t used = strip.getLengthTotal(); uint16_t n = (used -1) /MAX_LIVE_LEDS +1; //only serve every n'th LED if count over MAX_LIVE_LEDS char buffer[2000]; strcpy_P(buffer, PSTR("{\"leds\":[")); diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index 4513f9784..0f812b94c 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -31,6 +31,7 @@ Timezone* tz; #define TZ_HAWAII 18 #define TZ_NOVOSIBIRSK 19 #define TZ_ANCHORAGE 20 +#define TZ_MX_CENTRAL 21 #define TZ_INIT 255 byte tzCurrent = TZ_INIT; //uninitialized @@ -141,6 +142,11 @@ void updateTimezone() { tcrStandard = {First, Sun, Nov, 2, -540}; //AKST = UTC - 9 hours break; } + case TZ_MX_CENTRAL : { + tcrDaylight = {First, Sun, Apr, 2, -300}; //CDT = UTC - 5 hours + tcrStandard = {Last, Sun, Oct, 2, -360}; //CST = UTC - 6 hours + break; + } } tzCurrent = currentTimezone; diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index 43d19203c..37d08e6db 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -35,7 +35,6 @@ enum struct PinOwner : uint8_t { DMX = 0x8A, // 'DMX' == hard-coded to IO2 // Use UserMod IDs from const.h here UM_Unspecified = USERMOD_ID_UNSPECIFIED, // 0x01 - UM_RGBRotaryEncoder = USERMOD_ID_UNSPECIFIED, // 0x01 // No define in const.h for this user module -- consider adding? UM_Example = USERMOD_ID_EXAMPLE, // 0x02 // Usermod "usermod_v2_example.h" UM_Temperature = USERMOD_ID_TEMPERATURE, // 0x03 // Usermod "usermod_temperature.h" // #define USERMOD_ID_FIXNETSERVICES // 0x04 // Usermod "usermod_Fix_unreachable_netservices.h" -- Does not allocate pins @@ -52,6 +51,8 @@ enum struct PinOwner : uint8_t { // #define USERMOD_ID_RTC // 0x0F // Usermod "usermod_rtc.h" -- Uses "standard" I2C pins ... TODO -- enable shared I2C bus use // #define USERMOD_ID_ELEKSTUBE_IPS // 0x10 // Usermod "usermod_elekstube_ips.h" -- Uses quite a few pins ... see Hardware.h and User_Setup.h // #define USERMOD_ID_SN_PHOTORESISTOR // 0x11 // Usermod "usermod_sn_photoresistor.h" -- Uses hard-coded pin (PHOTORESISTOR_PIN == A0), but could be easily updated to use pinManager + UM_RGBRotaryEncoder = USERMOD_RGB_ROTARY_ENCODER, // 0x16 // Usermod "rgb-rotary-encoder.h" + UM_QuinLEDAnPenta = USERMOD_ID_QUINLED_AN_PENTA, // 0x17 // Usermod "quinled-an-penta.h" }; static_assert(0u == static_cast(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected"); diff --git a/wled00/set.cpp b/wled00/set.cpp index 7f30c4823..19dcfd4e8 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -503,21 +503,21 @@ int getNumVal(const String* req, uint16_t pos) } -//helper to get int value at a position in string -bool updateVal(const String* req, const char* key, byte* val, byte minv, byte maxv) +//helper to get int value with in/decrementing support via ~ syntax +void parseNumber(const char* str, byte* val, byte minv, byte maxv) { - int pos = req->indexOf(key); - if (pos < 1) return false; - - if (req->charAt(pos+3) == '~') { - int out = getNumVal(req, pos+1); + if (str == nullptr || str[0] == '\0') return; + if (str[0] == 'r') {*val = random8(minv,maxv); return;} + if (str[0] == '~') { + int out = atoi(str +1); if (out == 0) { - // we only have ~ (and perhaps -) - if (req->charAt(pos+4) == '-') { - *val = (int)(*val -1) < (int)minv ? maxv : min((int)maxv,(*val -1)); + if (str[1] == '0') return; + if (str[1] == '-') + { + *val = (int)(*val -1) < (int)minv ? maxv : min((int)maxv,(*val -1)); //-1, wrap around } else { - *val = (int)(*val +1) > (int)maxv ? minv : max((int)minv,(*val +1)); + *val = (int)(*val +1) > (int)maxv ? minv : max((int)minv,(*val +1)); //+1, wrap around } } else { out += *val; @@ -527,8 +527,25 @@ bool updateVal(const String* req, const char* key, byte* val, byte minv, byte ma } } else { - *val = getNumVal(req, pos); + byte p1 = atoi(str); + const char* str2 = strchr(str,'~'); //min/max range (for preset cycle, e.g. "1~5~") + if (str2) { + byte p2 = atoi(str2+1); + while (isdigit((str2+1)[0])) str2++; + parseNumber(str2+1, val, p1, p2); + } else { + *val = p1; + } } +} + + +bool updateVal(const String* req, const char* key, byte* val, byte minv, byte maxv) +{ + int pos = req->indexOf(key); + if (pos < 1) return false; + if (req->length() < (unsigned int)(pos + 4)) return false; + parseNumber(req->c_str() + pos +3, val, minv, maxv); return true; } diff --git a/wled00/udp.cpp b/wled00/udp.cpp index f04809d02..877e79bb7 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -92,7 +92,8 @@ void notify(byte callMode, bool followUp) void realtimeLock(uint32_t timeoutMs, byte md) { if (!realtimeMode && !realtimeOverride){ - for (uint16_t i = 0; i < ledCount; i++) + uint16_t totalLen = strip.getLengthTotal(); + for (uint16_t i = 0; i < totalLen; i++) { strip.setPixelColor(i,0,0,0,0); } @@ -168,10 +169,11 @@ void handleNotifications() realtimeLock(realtimeTimeoutMs, REALTIME_MODE_HYPERION); if (realtimeOverride) return; uint16_t id = 0; + uint16_t totalLen = strip.getLengthTotal(); for (uint16_t i = 0; i < packetSize -2; i += 3) { setRealtimePixel(id, lbuf[i], lbuf[i+1], lbuf[i+2], 0); - id++; if (id >= ledCount) break; + id++; if (id >= totalLen) break; } strip.show(); return; @@ -339,9 +341,10 @@ void handleNotifications() byte numPackets = udpIn[5]; uint16_t id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED + uint16_t totalLen = strip.getLengthTotal(); for (uint16_t i = 6; i < tpmPayloadFrameSize + 4; i += 3) { - if (id < ledCount) + if (id < totalLen) { setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); id++; @@ -372,6 +375,7 @@ void handleNotifications() } if (realtimeOverride) return; + uint16_t totalLen = strip.getLengthTotal(); if (udpIn[0] == 1) //warls { for (uint16_t i = 2; i < packetSize -3; i += 4) @@ -385,7 +389,7 @@ void handleNotifications() { setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); - id++; if (id >= ledCount) break; + id++; if (id >= totalLen) break; } } else if (udpIn[0] == 3) //drgbw { @@ -394,14 +398,14 @@ void handleNotifications() { setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]); - id++; if (id >= ledCount) break; + id++; if (id >= totalLen) break; } } else if (udpIn[0] == 4) //dnrgb { uint16_t id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00); for (uint16_t i = 4; i < packetSize -2; i += 3) { - if (id >= ledCount) break; + if (id >= totalLen) break; setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); id++; } @@ -410,7 +414,7 @@ void handleNotifications() uint16_t id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00); for (uint16_t i = 4; i < packetSize -2; i += 4) { - if (id >= ledCount) break; + if (id >= totalLen) break; setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]); id++; } @@ -438,7 +442,7 @@ void handleNotifications() void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w) { uint16_t pix = i + arlsOffset; - if (pix < ledCount) + if (pix < strip.getLengthTotal()) { if (!arlsDisableGammaCorrection && strip.gammaCorrectCol) { @@ -479,6 +483,7 @@ void sendSysInfoUDP() if (!udp2Connected) return; IPAddress ip = Network.localIP(); + if (!ip || ip == IPAddress(255,255,255,255)) ip = IPAddress(4,3,2,1); // TODO: make a nice struct of it and clean up // 0: 1 byte 'binary token 255' diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index 72807165a..0a16381f3 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -104,6 +104,10 @@ #include "../usermods/seven_segment_display/usermod_v2_seven_segment_display.h" #endif +#ifdef QUINLED_AN_PENTA +#include "../usermods/quinled-an-penta/quinled-an-penta.h" +#endif + void registerUsermods() { /* @@ -195,4 +199,8 @@ void registerUsermods() #ifdef USERMOD_SEVEN_SEGMENT usermods.add(new SevenSegmentDisplay()); #endif + + #ifdef QUINLED_AN_PENTA + usermods.add(new QuinLEDAnPentaUsermod()); + #endif } diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 2cb1d39f2..ce6924737 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -138,13 +138,10 @@ void WLED::loop() bool aligned = strip.checkSegmentAlignment(); //see if old segments match old bus(ses) busses.removeAll(); uint32_t mem = 0; - ledCount = 1; for (uint8_t i = 0; i < WLED_MAX_BUSSES; i++) { if (busConfigs[i] == nullptr) break; mem += BusManager::memUsage(*busConfigs[i]); if (mem <= MAX_LED_MEMORY) { - uint16_t totalNew = busConfigs[i]->start + busConfigs[i]->count; - if (totalNew > ledCount && totalNew <= MAX_LEDS) ledCount = totalNew; //total is end of last bus (where start + len is max.) busses.add(*busConfigs[i]); } delete busConfigs[i]; busConfigs[i] = nullptr; diff --git a/wled00/wled.h b/wled00/wled.h index 72c97674f..fd789f7fe 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -265,9 +265,8 @@ WLED_GLOBAL bool noWifiSleep _INIT(false); #endif // LED CONFIG -WLED_GLOBAL uint16_t ledCount _INIT(0); // overcurrent prevented by ABL (filled in cfg.cpp, set.cpp or FX_fcn.cpp) -WLED_GLOBAL bool turnOnAtBoot _INIT(true); // turn on LEDs at power-up -WLED_GLOBAL byte bootPreset _INIT(0); // save preset to load after power-up +WLED_GLOBAL bool turnOnAtBoot _INIT(true); // turn on LEDs at power-up +WLED_GLOBAL byte bootPreset _INIT(0); // save preset to load after power-up //if true, a segment per bus will be created on boot and LED settings save //if false, only one segment spanning the total LEDs is created, diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp index 38369a912..55a79520a 100644 --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -89,7 +89,16 @@ void loadSettingsFromEEPROM() if (apChannel > 13 || apChannel < 1) apChannel = 1; apHide = EEPROM.read(228); if (apHide > 1) apHide = 1; - ledCount = EEPROM.read(229) + ((EEPROM.read(398) << 8) & 0xFF00); if (ledCount > MAX_LEDS || ledCount == 0) ledCount = 30; + uint16_t length = EEPROM.read(229) + ((EEPROM.read(398) << 8) & 0xFF00); //was ledCount + if (length > MAX_LEDS || length == 0) length = 30; + uint8_t pins[5] = {2, 255, 255, 255, 255}; + uint8_t colorOrder = COL_ORDER_GRB; + if (lastEEPROMversion > 9) colorOrder = EEPROM.read(383); + if (colorOrder > COL_ORDER_GBR) colorOrder = COL_ORDER_GRB; + bool skipFirst = EEPROM.read(2204); + bool reversed = EEPROM.read(252); + BusConfig bc = BusConfig(EEPROM.read(372) ? TYPE_SK6812_RGBW : TYPE_WS2812_RGB, pins, 0, length, colorOrder, reversed, skipFirst); + busses.add(bc); notifyButton = EEPROM.read(230); notifyTwice = EEPROM.read(231); @@ -143,7 +152,7 @@ void loadSettingsFromEEPROM() arlsOffset = EEPROM.read(368); if (!EEPROM.read(367)) arlsOffset = -arlsOffset; turnOnAtBoot = EEPROM.read(369); - strip.isRgbw = EEPROM.read(372); + //strip.isRgbw = EEPROM.read(372); //374 - strip.paletteFade apBehavior = EEPROM.read(376); diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index e35e498db..708e8ee15 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -409,7 +409,7 @@ String dmxProcessor(const String& var) mapJS += "\nCN=" + String(DMXChannels) + ";\n"; mapJS += "CS=" + String(DMXStart) + ";\n"; mapJS += "CG=" + String(DMXGap) + ";\n"; - mapJS += "LC=" + String(ledCount) + ";\n"; + mapJS += "LC=" + String(strip.getLengthTotal()) + ";\n"; mapJS += "var CH=["; for (int i=0;i<15;i++) { mapJS += String(DMXFixtureMap[i]) + ",";