diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md deleted file mode 100644 index c3536b2c..00000000 --- a/.github/ISSUE_TEMPLATE/bug.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: Bug -about: Noticed an issue with your lights? -title: '' -labels: bug -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. Please quickly search existing issues first! - -**To Reproduce** -Steps to reproduce the behavior, if consistently possible - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**WLED version** - - Board: [e.g. Wemos D1, ESP32 dev] - - Version [e.g. 0.10.0, dev200603] - - Format [e.g. Binary, self-compiled] - -**Additional context** -Anything else you'd like to say about the problem? - -Thank you for your help! diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 00000000..30e5f3bc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,83 @@ +name: Bug Report +description: File a bug report +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Please quickly search existing issues first before submitting a bug. + - type: textarea + id: what-happened + attributes: + label: What happened? + description: A clear and concise description of what the bug is. + placeholder: Tell us what the problem is. + validations: + required: true + - type: textarea + id: how-to-reproduce + attributes: + label: To Reproduce Bug + description: Steps to reproduce the behavior, if consistently possible. + placeholder: Tell us how to make the bug appear. + validations: + required: true + - type: textarea + id: expected-behavior + attributes: + label: Expected Behavior + description: A clear and concise description of what you expected to happen. + placeholder: Tell us what you expected to happen. + validations: + required: true + - type: dropdown + id: install_format + attributes: + label: Install Method + description: How did you install WLED? + options: + - Binary from WLED.me + - Self-Compiled + validations: + required: true + - type: input + id: version + attributes: + label: What version of WLED? + description: You can find this in by going to Config -> Security & Updates -> Scroll to Bottom. Copy and paste the entire line after "Server message" + placeholder: "e.g. WLED 0.13.0-b4 (build 2110110)" + validations: + required: true + - type: dropdown + id: Board + attributes: + label: Which microcontroller/board are you seeing the problem on? + multiple: true + options: + - ESP8266 + - ESP32 + - Other + validations: + required: true + - type: textarea + id: logs + attributes: + label: Relevant log/trace output + description: Please copy and paste any relevant log output if you have it. This will be automatically formatted into code, so no need for backticks. + render: shell + - type: textarea + attributes: + label: Anything else? + description: | + Links? References? Anything that will give us more context about the issue you are encountering! + Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. + validations: + required: false + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/Aircoookie/WLED/blob/master/CODE_OF_CONDUCT.md) + options: + - label: I agree to follow this project's Code of Conduct + required: true \ No newline at end of file diff --git a/usermods/Artemis_reciever/usermod.cpp b/usermods/Artemis_reciever/usermod.cpp index 22736852..5f230dfd 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 9496dc02..a3507770 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/multi_relay/readme.md b/usermods/multi_relay/readme.md index a5f47bf5..ebf2056a 100644 --- a/usermods/multi_relay/readme.md +++ b/usermods/multi_relay/readme.md @@ -16,6 +16,12 @@ Examples 1. 4 relays at all, relay 2 will be toggled: `http://[device-ip]/relays?toggle=0,1,0,0` 2. 3 relays at all, relay 1&3 will be switched on: `http://[device-ip]/relays?switch=1,0,1` +## JSON API +You can switch relay state using the following JSON object transmitted to: `http://[device-ip]/json` + +Switch relay 0 on: `{"MultiRelay":{"relay":0,"on":true}}` +Switch relay4 3 & 4 off: `{"MultiRelay":[{"relay":2,"on":false},{"relay":3,"on":false}]}` + ## MQTT API wled/deviceMAC/relay/0/command on|off|toggle diff --git a/usermods/multi_relay/usermod_multi_relay.h b/usermods/multi_relay/usermod_multi_relay.h index c3d55d66..9a92d4e2 100644 --- a/usermods/multi_relay/usermod_multi_relay.h +++ b/usermods/multi_relay/usermod_multi_relay.h @@ -6,6 +6,8 @@ #define MULTI_RELAY_MAX_RELAYS 4 #endif +#define WLED_DEBOUNCE_THRESHOLD 50 //only consider button input of at least 50ms as valid (debouncing) + #define ON true #define OFF false @@ -23,6 +25,7 @@ typedef struct relay_t { bool state; bool external; uint16_t delay; + int8_t button; } Relay; @@ -35,7 +38,7 @@ class MultiRelay : public Usermod { // switch timer start time uint32_t _switchTimerStart = 0; // old brightness - bool _oldBrightness = 0; + bool _oldMode; // usermod enabled bool enabled = false; // needs to be configured (no default config) @@ -49,6 +52,7 @@ class MultiRelay : public Usermod { static const char _delay_str[]; static const char _activeHigh[]; static const char _external[]; + static const char _button[]; void publishMqtt(const char* state, int relay) { @@ -170,6 +174,7 @@ class MultiRelay : public Usermod { _relay[i].active = false; _relay[i].state = false; _relay[i].external = false; + _relay[i].button = -1; } } /** @@ -261,11 +266,12 @@ class MultiRelay : public Usermod { if (!pinManager.allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) { _relay[i].pin = -1; // allocation failed } else { - switchRelay(i, _relay[i].state = (bool)bri); + if (!_relay[i].external) _relay[i].state = offMode; + switchRelay(i, _relay[i].state); _relay[i].active = false; } } - _oldBrightness = (bool)bri; + _oldMode = offMode; initDone = true; } @@ -281,24 +287,110 @@ class MultiRelay : public Usermod { * loop() is called continuously. Here you can check for events, read sensors, etc. */ void loop() { + yield(); if (!enabled || strip.isUpdating()) return; static unsigned long lastUpdate = 0; - if (millis() - lastUpdate < 200) return; // update only 5 times/s + if (millis() - lastUpdate < 100) return; // update only 10 times/s lastUpdate = millis(); //set relay when LEDs turn on - if (_oldBrightness != (bool)bri) { - _oldBrightness = (bool)bri; + if (_oldMode != offMode) { + _oldMode = offMode; _switchTimerStart = millis(); for (uint8_t i=0; i=0) _relay[i].active = true; + if (_relay[i].pin>=0 && !_relay[i].external) _relay[i].active = true; } } handleOffTimer(); } + /** + * handleButton() can be used to override default button behaviour. Returning true + * will prevent button working in a default way. + * Replicating button.cpp + */ + bool handleButton(uint8_t b) { + yield(); + if (buttonType[b] == BTN_TYPE_NONE || buttonType[b] == BTN_TYPE_RESERVED || buttonType[b] == BTN_TYPE_PIR_SENSOR || buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) { + return false; + } + + bool handled = false; + for (uint8_t i=0; i WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce) + for (uint8_t i=0; i=0 && _relay[i].button == b) { + switchRelay(i, buttonPressedBefore[b]); + buttonLongPressed[b] = buttonPressedBefore[b]; //save the last "long term" switch state + } + } + } + return handled; + } + + //momentary button logic + if (isButtonPressed(b)) { //pressed + + if (!buttonPressedBefore[b]) buttonPressedTime[b] = now; + buttonPressedBefore[b] = true; + + if (now - buttonPressedTime[b] > 600) { //long press + buttonLongPressed[b] = true; + } + + } else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released + + long dur = now - buttonPressedTime[b]; + if (dur < WLED_DEBOUNCE_THRESHOLD) { + buttonPressedBefore[b] = false; + return handled; + } //too short "press", debounce + bool doublePress = buttonWaitTime[b]; //did we have short press before? + buttonWaitTime[b] = 0; + + 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] = now; + } + } + buttonPressedBefore[b] = false; + buttonLongPressed[b] = false; + } + // if 450ms elapsed since last press/release it is a short press + if (buttonWaitTime[b] && now - buttonWaitTime[b] > 350 && !buttonPressedBefore[b]) { + buttonWaitTime[b] = 0; + for (uint8_t i=0; i=0 && _relay[i].button == b) { + toggleRelay(i); + } + } + } + return handled; + } + /** * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. */ @@ -310,6 +402,26 @@ class MultiRelay : public Usermod { JsonArray infoArr = user.createNestedArray(F("Number of relays")); //name infoArr.add(String(getActiveRelayCount())); + + String uiDomString; + for (uint8_t i=0; i"); + uiDomString += F("Relay "); + uiDomString += i; + uiDomString += F(" "); + JsonArray infoArr = user.createNestedArray(uiDomString); // timer value + + infoArr.add(_relay[i].state ? "on" : "off"); + } } } @@ -317,15 +429,46 @@ class MultiRelay : public Usermod { * 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) { - //} + void addToJsonState(JsonObject &root) { + if (!initDone || !enabled) return; // prevent crash on boot applyPreset() + JsonObject multiRelay = root[FPSTR(_name)]; + if (multiRelay.isNull()) { + multiRelay = root.createNestedObject(FPSTR(_name)); + } + #if MULTI_RELAY_MAX_RELAYS > 1 + JsonArray rel_arr = multiRelay.createNestedArray(F("relays")); + for (uint8_t i=0; i() && usermod[FPSTR(_relay_str)].is() && usermod[FPSTR(_relay_str)].as()>=0) { + switchRelay(usermod[FPSTR(_relay_str)].as(), usermod["on"].as()); + } + } else if (root[FPSTR(_name)].is()) { + JsonArray relays = root[FPSTR(_name)].as(); + for (JsonVariant r : relays) { + if (r["on"].is() && r[FPSTR(_relay_str)].is() && r[FPSTR(_relay_str)].as()>=0) { + switchRelay(r[FPSTR(_relay_str)].as(), r["on"].as()); + } + } + } + } /** * provide the changeable values @@ -341,6 +484,7 @@ class MultiRelay : public Usermod { relay[FPSTR(_activeHigh)] = _relay[i].mode; relay[FPSTR(_delay_str)] = _relay[i].delay; relay[FPSTR(_external)] = _relay[i].external; + relay[FPSTR(_button)] = _relay[i].button; } DEBUG_PRINTLN(F("MultiRelay config saved.")); } @@ -370,6 +514,7 @@ class MultiRelay : public Usermod { _relay[i].mode = top[parName][FPSTR(_activeHigh)] | _relay[i].mode; _relay[i].external = top[parName][FPSTR(_external)] | _relay[i].external; _relay[i].delay = top[parName][FPSTR(_delay_str)] | _relay[i].delay; + _relay[i].button = top[parName][FPSTR(_button)] | _relay[i].button; // begin backwards compatibility (beta) remove when 0.13 is released parName += '-'; _relay[i].pin = top[parName+"pin"] | _relay[i].pin; @@ -394,7 +539,7 @@ class MultiRelay : public Usermod { for (uint8_t i=0; i=0 && pinManager.allocatePin(_relay[i].pin, true, PinOwner::UM_MultiRelay)) { if (!_relay[i].external) { - switchRelay(i, _relay[i].state = (bool)bri); + switchRelay(i, offMode); } } else { _relay[i].pin = -1; @@ -404,7 +549,7 @@ class MultiRelay : public Usermod { DEBUG_PRINTLN(F(" config (re)loaded.")); } // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[F("relay-0")]["pin"].isNull(); + return !top[F("relay-0")][FPSTR(_button)].isNull(); } /** @@ -424,3 +569,4 @@ const char MultiRelay::_relay_str[] PROGMEM = "relay"; const char MultiRelay::_delay_str[] PROGMEM = "delay-s"; const char MultiRelay::_activeHigh[] PROGMEM = "active-high"; const char MultiRelay::_external[] PROGMEM = "external"; +const char MultiRelay::_button[] PROGMEM = "button"; diff --git a/usermods/project_cars_shiftlight/wled06_usermod.ino b/usermods/project_cars_shiftlight/wled06_usermod.ino index 0edad7dd..9d3f1d44 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 00000000..10777e36 --- /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 00000000..c292736c --- /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/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 3440305f..d5163915 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(); @@ -636,10 +635,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 593a971f..4c9a2ae3 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 ba6a0c97..39c1e4f7 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -5,15 +5,20 @@ */ #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 void shortPressAction(uint8_t b) { - if (!macroButton[b]) - { - toggleOnOff(); - colorUpdated(CALL_MODE_BUTTON); + if (!macroButton[b]) { + switch (b) { + case 0: toggleOnOff(); colorUpdated(CALL_MODE_BUTTON); break; + default: ++effectCurrent %= strip.getModeCount(); colorUpdated(CALL_MODE_BUTTON); break; + } } else { applyPreset(macroButton[b], CALL_MODE_BUTTON); } @@ -26,6 +31,44 @@ void shortPressAction(uint8_t b) } } +void longPressAction(uint8_t b) +{ + if (!macroLongPress[b]) { + switch (b) { + case 0: _setRandomColor(false,true); break; + default: bri += 8; colorUpdated(CALL_MODE_BUTTON); buttonPressedTime[b] = millis(); break; // repeatable action + } + } else { + applyPreset(macroLongPress[b], CALL_MODE_BUTTON); + } + + // publish MQTT message + if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { + char subuf[64]; + sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); + mqtt->publish(subuf, 0, false, "long"); + } +} + +void doublePressAction(uint8_t b) +{ + if (!macroDoublePress[b]) { + switch (b) { + //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 { + applyPreset(macroDoublePress[b], CALL_MODE_BUTTON); + } + + // publish MQTT message + if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { + char subuf[64]; + sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); + mqtt->publish(subuf, 0, false, "double"); + } +} + bool isButtonPressed(uint8_t i) { if (btnPin[i]<0) return false; @@ -175,6 +218,8 @@ void handleButton() if (btnPin[b]<0 || buttonType[b] == BTN_TYPE_NONE) continue; #endif + if (usermods.handleButton(b)) continue; // did usermod handle buttons + if ((buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) && millis() - lastRead > 250) { // button is not a button but a potentiometer if (b+1 == WLED_MAX_BUTTONS) lastRead = millis(); handleAnalog(b); continue; @@ -186,61 +231,46 @@ void handleButton() } //momentary button logic - if (isButtonPressed(b)) //pressed - { + if (isButtonPressed(b)) { //pressed + if (!buttonPressedBefore[b]) buttonPressedTime[b] = millis(); buttonPressedBefore[b] = true; - if (millis() - buttonPressedTime[b] > 600) //long press - { - if (!buttonLongPressed[b]) - { - if (macroLongPress[b]) {applyPreset(macroLongPress[b], CALL_MODE_BUTTON);} - else _setRandomColor(false,true); - - // publish MQTT message - if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { - char subuf[64]; - sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); - mqtt->publish(subuf, 0, false, "long"); - } - - buttonLongPressed[b] = true; + 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 + longPressAction(b); + buttonPressedTime[b] = millis() - WLED_LONG_REPEATED_ACTION; //300ms } + buttonLongPressed[b] = true; } - } - else if (!isButtonPressed(b) && buttonPressedBefore[b]) //released - { + + } else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released + long dur = millis() - buttonPressedTime[b]; if (dur < WLED_DEBOUNCE_THRESHOLD) {buttonPressedBefore[b] = false; continue;} //too short "press", debounce - bool doublePress = buttonWaitTime[b]; + bool doublePress = buttonWaitTime[b]; //did we have a short press before? buttonWaitTime[b] = 0; - if (dur > 6000 && b==0) //long press on button 0 - { + 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 (macroDoublePress[b]) - { + } else if (!buttonLongPressed[b]) { //short press + 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) { - applyPreset(macroDoublePress[b], CALL_MODE_BUTTON); - - // publish MQTT message - if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { - char subuf[64]; - sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); - mqtt->publish(subuf, 0, false, "double"); - } - } else buttonWaitTime[b] = millis(); - } else shortPressAction(b); + doublePressAction(b); + } else { + buttonWaitTime[b] = millis(); + } + } } buttonPressedBefore[b] = false; buttonLongPressed[b] = false; } - if (buttonWaitTime[b] && millis() - buttonWaitTime[b] > 450 && !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 4aaa6ecb..8e47e236 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -77,18 +77,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(); @@ -116,15 +111,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 @@ -527,7 +519,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 b03595a5..51205d29 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -63,6 +63,7 @@ #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 diff --git a/wled00/data/settings_time.htm b/wled00/data/settings_time.htm index f8efd130..eb785b92 100644 --- a/wled00/data/settings_time.htm +++ b/wled00/data/settings_time.htm @@ -130,6 +130,7 @@ +
UTC offset: seconds (max. 18 hours)
Current local time is unknown.
diff --git a/wled00/dmx.cpp b/wled00/dmx.cpp index 64e02ec1..28421954 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 d556e676..ba9e939e 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 22c4dcf2..ebc83ec0 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -193,6 +193,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 @@ -209,6 +210,7 @@ class Usermod { public: virtual void loop() {} virtual void handleOverlayDraw() {} + virtual bool handleButton(uint8_t b) { return false; } virtual void setup() {} virtual void connected() {} virtual void addToJsonState(JsonObject& obj) {} @@ -229,7 +231,7 @@ class UsermodManager { public: void loop(); void handleOverlayDraw(); - + bool handleButton(uint8_t b); void setup(); void connected(); void addToJsonState(JsonObject& obj); diff --git a/wled00/html_settings.h b/wled00/html_settings.h index 47eea772..61e19054 100644 --- a/wled00/html_settings.h +++ b/wled00/html_settings.h @@ -353,6 +353,7 @@ North Korea +
UTC offset: seconds (max. 18 hours)
Current local time is unknown.
Latitude (N): ()) { + *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); @@ -212,7 +224,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(); @@ -321,18 +333,18 @@ 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) { savePreset(ps, true, nullptr, root); } else { - ps = root[F("pdel")] | -1; //deletion + ps = root[F("pdel")]; //deletion if (ps > 0) { 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!) if (!presetId) unloadPlaylist(); //stop playlist if preset changed manually - applyPreset(ps, callMode); + applyPreset(presetCycCurr, callMode); return stateResponse; } @@ -486,7 +498,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; @@ -866,7 +878,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 045da956..eb8a5200 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 cb1da562..63f6d95e 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -52,7 +52,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_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 80b1a09a..3a1d6062 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -519,21 +519,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; @@ -543,8 +543,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 f04809d0..877e79bb 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/um_manager.cpp b/wled00/um_manager.cpp index a9aac784..caaf100d 100644 --- a/wled00/um_manager.cpp +++ b/wled00/um_manager.cpp @@ -6,6 +6,13 @@ //Usermod Manager internals 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(); } +bool UsermodManager::handleButton(uint8_t b) { + bool overrideIO = false; + for (byte i = 0; i < numMods; i++) { + if (ums[i]->handleButton(b)) overrideIO = true; + } + return overrideIO; +} 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 8045a4df..431ac965 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -100,6 +100,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() { /* @@ -187,4 +191,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 3b1ee384..f59a5b8f 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -212,13 +212,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 b6ae7274..c44dc951 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -262,7 +262,6 @@ WLED_GLOBAL bool noWifiSleep _INIT(false); #endif // LED CONFIG -WLED_GLOBAL uint16_t ledCount _INIT(DEFAULT_LED_COUNT); // overcurrent prevented by ABL 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 diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp index 38369a91..55a79520 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 f402de11..908c3a4e 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -387,7 +387,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]) + ",";