diff --git a/usermods/BH1750_v2/usermod_bh1750.h b/usermods/BH1750_v2/usermod_bh1750.h index b65332bc..8e52612c 100644 --- a/usermods/BH1750_v2/usermod_bh1750.h +++ b/usermods/BH1750_v2/usermod_bh1750.h @@ -8,7 +8,6 @@ #pragma once #include "wled.h" -#include #include // the max frequency to check photoresistor, 10 seconds @@ -56,15 +55,6 @@ private: static const char _offset[]; static const char _HomeAssistantDiscovery[]; - // set the default pins based on the architecture, these get overridden by Usermod menu settings - #ifdef ARDUINO_ARCH_ESP32 // ESP32 boards - #define HW_PIN_SCL 22 - #define HW_PIN_SDA 21 - #else // ESP8266 boards - #define HW_PIN_SCL 5 - #define HW_PIN_SDA 4 - #endif - int8_t ioPin[2] = {HW_PIN_SCL, HW_PIN_SDA}; // I2C pins: SCL, SDA...defaults to Arch hardware pins but overridden at setup() bool initDone = false; bool sensorFound = false; @@ -123,14 +113,8 @@ private: public: void setup() { - bool HW_Pins_Used = (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA); // note whether architecture-based hardware SCL/SDA pins used - PinOwner po = PinOwner::UM_BH1750; // defaults to being pinowner for SCL/SDA pins - PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; // allocate pins - if (HW_Pins_Used) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins - if (!pinManager.allocateMultiplePins(pins, 2, po)) return; - - Wire.begin(ioPin[1], ioPin[0]); - + PinManagerPinType pins[2] = { { i2c_sda, true }, { i2c_scl, true } }; // allocate pins + if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) return; sensorFound = lightMeter.begin(); initDone = true; } @@ -216,9 +200,6 @@ public: top[FPSTR(_minReadInterval)] = minReadingInterval; top[FPSTR(_HomeAssistantDiscovery)] = HomeAssistantDiscovery; top[FPSTR(_offset)] = offset; - JsonArray io_pin = top.createNestedArray(F("pin")); - for (byte i=0; i<2; i++) io_pin.add(ioPin[i]); - top[F("help4Pins")] = F("SCL,SDA"); // help for Settings page DEBUG_PRINTLN(F("BH1750 config saved.")); } @@ -226,8 +207,6 @@ public: // called before setup() to populate properties from values stored in cfg.json bool readFromConfig(JsonObject &root) { - int8_t newPin[2]; for (byte i=0; i<2; i++) newPin[i] = ioPin[i]; // prepare to note changed pins - // we look for JSON object. JsonObject top = root[FPSTR(_name)]; if (top.isNull()) @@ -244,27 +223,12 @@ public: configComplete &= getJsonValue(top[FPSTR(_minReadInterval)], minReadingInterval, 500); //ms configComplete &= getJsonValue(top[FPSTR(_HomeAssistantDiscovery)], HomeAssistantDiscovery, false); configComplete &= getJsonValue(top[FPSTR(_offset)], offset, 1); - for (byte i=0; i<2; i++) configComplete &= getJsonValue(top[F("pin")][i], newPin[i], ioPin[i]); DEBUG_PRINT(FPSTR(_name)); if (!initDone) { - // first run: reading from cfg.json - for (byte i=0; i<2; i++) ioPin[i] = newPin[i]; DEBUG_PRINTLN(F(" config loaded.")); } else { DEBUG_PRINTLN(F(" config (re)loaded.")); - // changing parameters from settings page - bool pinsChanged = false; - for (byte i=0; i<2; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; } // check if any pins changed - if (pinsChanged) { //if pins changed, deallocate old pins and allocate new ones - PinOwner po = PinOwner::UM_BH1750; - if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins - pinManager.deallocateMultiplePins((const uint8_t *)ioPin, 2, po); // deallocate pins - for (byte i=0; i<2; i++) ioPin[i] = newPin[i]; - setup(); - } - // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[F("pin")].isNull(); } return configComplete; diff --git a/usermods/BME280_v2/usermod_bme280.h b/usermods/BME280_v2/usermod_bme280.h index e643e62b..f4fcb5f0 100644 --- a/usermods/BME280_v2/usermod_bme280.h +++ b/usermods/BME280_v2/usermod_bme280.h @@ -9,7 +9,6 @@ #include "wled.h" #include -#include #include // BME280 sensor #include // BME280 extended measurements @@ -34,7 +33,6 @@ private: #ifdef ESP8266 //uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8 #endif - int8_t ioPin[2] = {i2c_scl, i2c_sda}; // I2C pins: SCL, SDA...defaults to Arch hardware pins but overridden at setup() bool initDone = false; // BME280 sensor settings @@ -186,14 +184,9 @@ private: public: void setup() { - bool HW_Pins_Used = (ioPin[0]==i2c_scl && ioPin[1]==i2c_sda); // note whether architecture-based hardware SCL/SDA pins used - PinOwner po = PinOwner::UM_BME280; // defaults to being pinowner for SCL/SDA pins - PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; // allocate pins - if (HW_Pins_Used) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins - if (!pinManager.allocateMultiplePins(pins, 2, po)) { sensorType=0; return; } + PinManagerPinType pins[2] = { { i2c_sda, true }, { i2c_scl, true } }; // allocate pins + if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { sensorType=0; return; } - Wire.begin(ioPin[1], ioPin[0]); - if (!bme.begin()) { sensorType = 0; @@ -415,9 +408,6 @@ public: top[F("PublishAlways")] = PublishAlways; top[F("UseCelsius")] = UseCelsius; top[F("HomeAssistantDiscovery")] = HomeAssistantDiscovery; - JsonArray io_pin = top.createNestedArray(F("pin")); - for (byte i=0; i<2; i++) io_pin.add(ioPin[i]); - top[F("help4Pins")] = F("SCL,SDA"); // help for Settings page DEBUG_PRINTLN(F("BME280 config saved.")); } @@ -427,8 +417,6 @@ public: // default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor // setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed) - int8_t newPin[2]; for (byte i=0; i<2; i++) newPin[i] = ioPin[i]; // prepare to note changed pins - JsonObject top = root[FPSTR(_name)]; if (top.isNull()) { DEBUG_PRINT(F(_name)); @@ -447,27 +435,14 @@ public: configComplete &= getJsonValue(top[F("PublishAlways")], PublishAlways, false); configComplete &= getJsonValue(top[F("UseCelsius")], UseCelsius, true); configComplete &= getJsonValue(top[F("HomeAssistantDiscovery")], HomeAssistantDiscovery, false); - for (byte i=0; i<2; i++) configComplete &= getJsonValue(top[F("pin")][i], newPin[i], ioPin[i]); DEBUG_PRINT(FPSTR(_name)); if (!initDone) { // first run: reading from cfg.json - for (byte i=0; i<2; i++) ioPin[i] = newPin[i]; DEBUG_PRINTLN(F(" config loaded.")); } else { DEBUG_PRINTLN(F(" config (re)loaded.")); // changing parameters from settings page - bool pinsChanged = false; - for (byte i=0; i<2; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; } // check if any pins changed - if (pinsChanged) { //if pins changed, deallocate old pins and allocate new ones - PinOwner po = PinOwner::UM_BME280; - if (ioPin[0]==i2c_scl && ioPin[1]==i2c_sda) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins - pinManager.deallocateMultiplePins((const uint8_t *)ioPin, 2, po); // deallocate pins - for (byte i=0; i<2; i++) ioPin[i] = newPin[i]; - setup(); - } - // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[F("pin")].isNull(); } return configComplete; diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h index 7903fc9e..79983dc4 100644 --- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -20,7 +20,7 @@ * This usermod handles PIR sensor states. * The strip will be switched on and the off timer will be resetted when the sensor goes HIGH. * When the sensor state goes LOW, the off timer is started and when it expires, the strip is switched off. - * + * Maintained by: @blazoncek * * Usermods allow you to add own functionality to WLED more easily * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality @@ -38,21 +38,21 @@ public: ~PIRsensorSwitch() {} //Enable/Disable the PIR sensor - void EnablePIRsensor(bool en) { enabled = en; } + inline void EnablePIRsensor(bool en) { enabled = en; } // Get PIR sensor enabled/disabled state - bool PIRsensorEnabled() { return enabled; } + inline bool PIRsensorEnabled() { return enabled; } private: byte prevPreset = 0; byte prevPlaylist = 0; - uint32_t offTimerStart = 0; // off timer start time + volatile unsigned long offTimerStart = 0; // off timer start time + volatile bool PIRtriggered = false; // did PIR trigger? byte NotifyUpdateMode = CALL_MODE_NO_NOTIFY; // notification mode for stateUpdated(): CALL_MODE_NO_NOTIFY or CALL_MODE_DIRECT_CHANGE byte sensorPinState = LOW; // current PIR sensor pin state bool initDone = false; // status of initialization - bool PIRtriggered = false; unsigned long lastLoop = 0; // configurable parameters @@ -66,6 +66,7 @@ private: // flag to enable triggering only if WLED is initially off (LEDs are not on, preventing running effect being overwritten by PIR) bool m_offOnly = false; bool m_offMode = offMode; + bool m_override = false; // Home Assistant bool HomeAssistantDiscovery = false; // is HA discovery turned on @@ -81,173 +82,33 @@ private: static const char _offOnly[]; static const char _haDiscovery[]; static const char _notify[]; + static const char _override[]; /** * check if it is daytime * if sunrise/sunset is not defined (no NTP or lat/lon) default to nighttime */ - bool isDayTime() { - updateLocalTime(); - uint8_t hr = hour(localTime); - uint8_t mi = minute(localTime); - - if (sunrise && sunset) { - if (hour(sunrise)
hr) { - return true; - } else { - if (hour(sunrise)==hr && minute(sunrise)mi) { - return true; - } - } - } - return false; - } + static bool isDayTime(); /** * switch strip on/off */ - void switchStrip(bool switchOn) - { - if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return; //if lights on and off only, do nothing - if (PIRtriggered && switchOn) return; //if already on and triggered before, do nothing - PIRtriggered = switchOn; - DEBUG_PRINT(F("PIR: strip=")); DEBUG_PRINTLN(switchOn?"on":"off"); - if (switchOn) { - if (m_onPreset) { - if (currentPlaylist>0 && !offMode) { - prevPlaylist = currentPlaylist; - unloadPlaylist(); - } else if (currentPreset>0 && !offMode) { - prevPreset = currentPreset; - } else { - saveTemporaryPreset(); - prevPlaylist = 0; - prevPreset = 255; - } - applyPreset(m_onPreset, NotifyUpdateMode); - return; - } - // preset not assigned - if (bri == 0) { - bri = briLast; - stateUpdated(NotifyUpdateMode); - } - } else { - if (m_offPreset) { - applyPreset(m_offPreset, NotifyUpdateMode); - return; - } else if (prevPlaylist) { - if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPlaylist, NotifyUpdateMode); - prevPlaylist = 0; - return; - } else if (prevPreset) { - if (prevPreset<255) { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPreset, NotifyUpdateMode); } - else { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyTemporaryPreset(); } - prevPreset = 0; - return; - } - // preset not assigned - if (bri != 0) { - briLast = bri; - bri = 0; - stateUpdated(NotifyUpdateMode); - } - } - } - - void publishMqtt(const char* state) - { - #ifndef WLED_DISABLE_MQTT - //Check if MQTT Connected, otherwise it will crash the 8266 - if (WLED_MQTT_CONNECTED) { - char subuf[64]; - strcpy(subuf, mqttDeviceTopic); - strcat_P(subuf, PSTR("/motion")); - mqtt->publish(subuf, 0, false, state); - } - #endif - } + void switchStrip(bool switchOn); + void publishMqtt(const char* state); // Create an MQTT Binary Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop. - void publishHomeAssistantAutodiscovery() - { - #ifndef WLED_DISABLE_MQTT - if (WLED_MQTT_CONNECTED) { - StaticJsonDocument<600> doc; - char uid[24], json_str[1024], buf[128]; - - sprintf_P(buf, PSTR("%s Motion"), serverDescription); //max length: 33 + 7 = 40 - doc[F("name")] = buf; - sprintf_P(buf, PSTR("%s/motion"), mqttDeviceTopic); //max length: 33 + 7 = 40 - doc[F("stat_t")] = buf; - doc[F("pl_on")] = "on"; - doc[F("pl_off")] = "off"; - sprintf_P(uid, PSTR("%s_motion"), escapedMac.c_str()); - doc[F("uniq_id")] = uid; - doc[F("dev_cla")] = F("motion"); - doc[F("exp_aft")] = 1800; - - JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device - device[F("name")] = serverDescription; - device[F("ids")] = String(F("wled-sensor-")) + mqttClientID; - device[F("mf")] = "WLED"; - device[F("mdl")] = F("FOSS"); - device[F("sw")] = versionString; - - sprintf_P(buf, PSTR("homeassistant/binary_sensor/%s/config"), uid); - DEBUG_PRINTLN(buf); - size_t payload_size = serializeJson(doc, json_str); - DEBUG_PRINTLN(json_str); - - mqtt->publish(buf, 0, true, json_str, payload_size); // do we really need to retain? - } - #endif - } + void publishHomeAssistantAutodiscovery(); /** * Read and update PIR sensor state. * Initilize/reset switch off timer */ - bool updatePIRsensorState() - { - bool pinState = digitalRead(PIRsensorPin); - if (pinState != sensorPinState) { - sensorPinState = pinState; // change previous state - - if (sensorPinState == HIGH) { - offTimerStart = 0; - if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true); - else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); - publishMqtt("on"); - } else { - // start switch off timer - offTimerStart = millis(); - if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); - } - return true; - } - return false; - } + bool updatePIRsensorState(); /** * switch off the strip if the delay has elapsed */ - bool handleOffTimer() - { - if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay) { - offTimerStart = 0; - if (enabled == true) { - if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()) || PIRtriggered)) switchStrip(false); - else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); - publishMqtt("off"); - } - return true; - } - return false; - } + bool handleOffTimer(); public: //Functions called by WLED @@ -256,186 +117,57 @@ public: * 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) { - // pin retrieved from cfg.json (readFromConfig()) prior to running setup() - if (PIRsensorPin >= 0 && pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) { - // PIR Sensor mode INPUT_PULLUP - pinMode(PIRsensorPin, INPUT_PULLUP); - sensorPinState = digitalRead(PIRsensorPin); - } else { - if (PIRsensorPin >= 0) { - DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed.")); - } - PIRsensorPin = -1; // allocation failed - enabled = false; - } - } - initDone = true; - } + void setup(); /** * connected() is called every time the WiFi is (re)connected * Use it to initialize network interfaces */ - void connected() - { - } + //void connected(); /** * onMqttConnect() is called when MQTT connection is established */ - void onMqttConnect(bool sessionPresent) { - if (HomeAssistantDiscovery) { - publishHomeAssistantAutodiscovery(); - } - } + void onMqttConnect(bool sessionPresent); /** * loop() is called continuously. Here you can check for events, read sensors, etc. */ - void loop() - { - // only check sensors 4x/s - if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return; - lastLoop = millis(); - - if (!updatePIRsensorState()) { - handleOffTimer(); - } - } + void loop(); /** * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. * * Add PIR sensor state and switch off timer duration to jsoninfo */ - void addToJsonInfo(JsonObject &root) - { - JsonObject user = root["u"]; - if (user.isNull()) user = root.createNestedObject("u"); - - JsonArray infoArr = user.createNestedArray(FPSTR(_name)); - - String uiDomString; - if (enabled) { - if (offTimerStart > 0) - { - uiDomString = ""; - unsigned int offSeconds = (m_switchOffDelay - (millis() - offTimerStart)) / 1000; - if (offSeconds >= 3600) - { - uiDomString += (offSeconds / 3600); - uiDomString += F("h "); - offSeconds %= 3600; - } - if (offSeconds >= 60) - { - uiDomString += (offSeconds / 60); - offSeconds %= 60; - } - else if (uiDomString.length() > 0) - { - uiDomString += 0; - } - if (uiDomString.length() > 0) - { - uiDomString += F("min "); - } - uiDomString += (offSeconds); - infoArr.add(uiDomString + F("s")); - } else { - infoArr.add(sensorPinState ? F("sensor on") : F("inactive")); - } - } else { - infoArr.add(F("disabled")); - } - - uiDomString = F(" "); - infoArr.add(uiDomString); - - JsonObject sensor = root[F("sensor")]; - if (sensor.isNull()) sensor = root.createNestedObject(F("sensor")); - sensor[F("motion")] = sensorPinState || offTimerStart>0 ? true : false; - } + void addToJsonInfo(JsonObject &root); /** * onStateChanged() is used to detect WLED state change */ - void onStateChange(uint8_t mode) { - if (!initDone) return; - DEBUG_PRINT(F("PIR: offTimerStart=")); DEBUG_PRINTLN(offTimerStart); - if (PIRtriggered && offTimerStart) { - // checking PIRtriggered and offTimerStart will prevent cancellation upon On trigger - DEBUG_PRINTLN(F("PIR: Canceled.")); - offTimerStart = 0; - PIRtriggered = false; - } - } + void onStateChange(uint8_t mode); /** * 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); /** * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - - void readFromJsonState(JsonObject &root) - { - if (!initDone) return; // prevent crash on boot applyPreset() - JsonObject usermod = root[FPSTR(_name)]; - if (!usermod.isNull()) { - if (usermod[FPSTR(_enabled)].is()) { - enabled = usermod[FPSTR(_enabled)].as(); - } - } - } - + void readFromJsonState(JsonObject &root); /** * provide the changeable values */ - void addToConfig(JsonObject &root) - { - JsonObject top = root.createNestedObject(FPSTR(_name)); - top[FPSTR(_enabled)] = enabled; - top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000; - top["pin"] = PIRsensorPin; - top[FPSTR(_onPreset)] = m_onPreset; - top[FPSTR(_offPreset)] = m_offPreset; - top[FPSTR(_nightTime)] = m_nightTimeOnly; - top[FPSTR(_mqttOnly)] = m_mqttOnly; - top[FPSTR(_offOnly)] = m_offOnly; - top[FPSTR(_haDiscovery)] = HomeAssistantDiscovery; - top[FPSTR(_notify)] = (NotifyUpdateMode != CALL_MODE_NO_NOTIFY); - DEBUG_PRINTLN(F("PIR config saved.")); - } + void addToConfig(JsonObject &root); - void appendConfigData() - { - oappend(SET_F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('PIRsensorSwitch:notifications',1,'Periodic WS updates');")); // 0 is field type, 1 is actual field - } + /** + * provide UI information and allow extending UI options + */ + void appendConfigData(); /** * restore the changeable values @@ -443,72 +175,13 @@ public: * * The function should return true if configuration was successfully loaded or false if there was no configuration. */ - bool readFromConfig(JsonObject &root) - { - bool oldEnabled = enabled; - int8_t oldPin = PIRsensorPin; - - DEBUG_PRINT(FPSTR(_name)); - JsonObject top = root[FPSTR(_name)]; - if (top.isNull()) { - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); - return false; - } - - PIRsensorPin = top["pin"] | PIRsensorPin; - - enabled = top[FPSTR(_enabled)] | enabled; - - m_switchOffDelay = (top[FPSTR(_switchOffDelay)] | m_switchOffDelay/1000) * 1000; - - m_onPreset = top[FPSTR(_onPreset)] | m_onPreset; - m_onPreset = max(0,min(250,(int)m_onPreset)); - m_offPreset = top[FPSTR(_offPreset)] | m_offPreset; - m_offPreset = max(0,min(250,(int)m_offPreset)); - - m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly; - m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly; - m_offOnly = top[FPSTR(_offOnly)] | m_offOnly; - HomeAssistantDiscovery = top[FPSTR(_haDiscovery)] | HomeAssistantDiscovery; - - NotifyUpdateMode = top[FPSTR(_notify)] ? CALL_MODE_DIRECT_CHANGE : CALL_MODE_NO_NOTIFY; - - if (!initDone) { - // reading config prior to setup() - DEBUG_PRINTLN(F(" config loaded.")); - } else { - if (oldPin != PIRsensorPin || oldEnabled != enabled) { - // check if pin is OK - if (oldPin != PIRsensorPin && oldPin >= 0) { - // if we are changing pin in settings page - // deallocate old pin - pinManager.deallocatePin(oldPin, PinOwner::UM_PIR); - if (pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) { - pinMode(PIRsensorPin, INPUT_PULLUP); - } else { - // allocation failed - PIRsensorPin = -1; - enabled = false; - } - } - if (enabled) { - sensorPinState = digitalRead(PIRsensorPin); - } - } - DEBUG_PRINTLN(F(" config (re)loaded.")); - } - // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[FPSTR(_haDiscovery)].isNull(); - } + bool readFromConfig(JsonObject &root); /** * 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_PIRSWITCH; - } + uint16_t getId() { return USERMOD_ID_PIRSWITCH; } }; // strings to reduce flash memory usage (used more than twice) @@ -522,3 +195,360 @@ const char PIRsensorSwitch::_mqttOnly[] PROGMEM = "mqtt-only"; const char PIRsensorSwitch::_offOnly[] PROGMEM = "off-only"; const char PIRsensorSwitch::_haDiscovery[] PROGMEM = "HA-discovery"; const char PIRsensorSwitch::_notify[] PROGMEM = "notifications"; +const char PIRsensorSwitch::_override[] PROGMEM = "override"; + +bool PIRsensorSwitch::isDayTime() { + updateLocalTime(); + uint8_t hr = hour(localTime); + uint8_t mi = minute(localTime); + + if (sunrise && sunset) { + if (hour(sunrise)
hr) { + return true; + } else { + if (hour(sunrise)==hr && minute(sunrise)mi) { + return true; + } + } + } + return false; +} + +void PIRsensorSwitch::switchStrip(bool switchOn) +{ + if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return; //if lights on and off only, do nothing + if (PIRtriggered && switchOn) return; //if already on and triggered before, do nothing + PIRtriggered = switchOn; + DEBUG_PRINT(F("PIR: strip=")); DEBUG_PRINTLN(switchOn?"on":"off"); + if (switchOn) { + if (m_onPreset) { + if (currentPlaylist>0 && !offMode) { + prevPlaylist = currentPlaylist; + unloadPlaylist(); + } else if (currentPreset>0 && !offMode) { + prevPreset = currentPreset; + } else { + saveTemporaryPreset(); + prevPlaylist = 0; + prevPreset = 255; + } + applyPreset(m_onPreset, NotifyUpdateMode); + return; + } + // preset not assigned + if (bri == 0) { + bri = briLast; + stateUpdated(NotifyUpdateMode); + } + } else { + if (m_offPreset) { + applyPreset(m_offPreset, NotifyUpdateMode); + return; + } else if (prevPlaylist) { + if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPlaylist, NotifyUpdateMode); + prevPlaylist = 0; + return; + } else if (prevPreset) { + if (prevPreset<255) { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPreset, NotifyUpdateMode); } + else { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyTemporaryPreset(); } + prevPreset = 0; + return; + } + // preset not assigned + if (bri != 0) { + briLast = bri; + bri = 0; + stateUpdated(NotifyUpdateMode); + } + } +} + +void PIRsensorSwitch::publishMqtt(const char* state) +{ +#ifndef WLED_DISABLE_MQTT + //Check if MQTT Connected, otherwise it will crash the 8266 + if (WLED_MQTT_CONNECTED) { + char subuf[64]; + strcpy(subuf, mqttDeviceTopic); + strcat_P(subuf, PSTR("/motion")); + mqtt->publish(subuf, 0, false, state); + } +#endif +} + +void PIRsensorSwitch::publishHomeAssistantAutodiscovery() +{ +#ifndef WLED_DISABLE_MQTT + if (WLED_MQTT_CONNECTED) { + StaticJsonDocument<600> doc; + char uid[24], json_str[1024], buf[128]; + + sprintf_P(buf, PSTR("%s Motion"), serverDescription); //max length: 33 + 7 = 40 + doc[F("name")] = buf; + sprintf_P(buf, PSTR("%s/motion"), mqttDeviceTopic); //max length: 33 + 7 = 40 + doc[F("stat_t")] = buf; + doc[F("pl_on")] = "on"; + doc[F("pl_off")] = "off"; + sprintf_P(uid, PSTR("%s_motion"), escapedMac.c_str()); + doc[F("uniq_id")] = uid; + doc[F("dev_cla")] = F("motion"); + doc[F("exp_aft")] = 1800; + + JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device + device[F("name")] = serverDescription; + device[F("ids")] = String(F("wled-sensor-")) + mqttClientID; + device[F("mf")] = "WLED"; + device[F("mdl")] = F("FOSS"); + device[F("sw")] = versionString; + + sprintf_P(buf, PSTR("homeassistant/binary_sensor/%s/config"), uid); + DEBUG_PRINTLN(buf); + size_t payload_size = serializeJson(doc, json_str); + DEBUG_PRINTLN(json_str); + + mqtt->publish(buf, 0, true, json_str, payload_size); // do we really need to retain? + } +#endif +} + +bool PIRsensorSwitch::updatePIRsensorState() +{ + bool pinState = digitalRead(PIRsensorPin); + if (pinState != sensorPinState) { + sensorPinState = pinState; // change previous state + + if (sensorPinState == HIGH) { + offTimerStart = 0; + if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true); + else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); + publishMqtt("on"); + } else { + // start switch off timer + offTimerStart = millis(); + if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); + } + return true; + } + return false; +} + +bool PIRsensorSwitch::handleOffTimer() +{ + if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay) { + offTimerStart = 0; + if (enabled == true) { + if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()) || PIRtriggered)) switchStrip(false); + else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); + publishMqtt("off"); + } + return true; + } + return false; +} + +//Functions called by WLED + +void PIRsensorSwitch::setup() +{ + if (enabled) { + // pin retrieved from cfg.json (readFromConfig()) prior to running setup() + if (PIRsensorPin >= 0 && pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) { + // PIR Sensor mode INPUT_PULLUP + pinMode(PIRsensorPin, INPUT_PULLUP); + sensorPinState = digitalRead(PIRsensorPin); + } else { + if (PIRsensorPin >= 0) { + DEBUG_PRINTLN(F("PIRSensorSwitch pin allocation failed.")); + } + PIRsensorPin = -1; // allocation failed + enabled = false; + } + } + initDone = true; +} + +void PIRsensorSwitch::onMqttConnect(bool sessionPresent) +{ + if (HomeAssistantDiscovery) { + publishHomeAssistantAutodiscovery(); + } +} + +void PIRsensorSwitch::loop() +{ + // only check sensors 4x/s + if (!enabled || millis() - lastLoop < 250 || strip.isUpdating()) return; + lastLoop = millis(); + + if (!updatePIRsensorState()) { + handleOffTimer(); + } +} + +void PIRsensorSwitch::addToJsonInfo(JsonObject &root) +{ + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray infoArr = user.createNestedArray(FPSTR(_name)); + + String uiDomString; + if (enabled) { + if (offTimerStart > 0) + { + uiDomString = ""; + unsigned int offSeconds = (m_switchOffDelay - (millis() - offTimerStart)) / 1000; + if (offSeconds >= 3600) + { + uiDomString += (offSeconds / 3600); + uiDomString += F("h "); + offSeconds %= 3600; + } + if (offSeconds >= 60) + { + uiDomString += (offSeconds / 60); + offSeconds %= 60; + } + else if (uiDomString.length() > 0) + { + uiDomString += 0; + } + if (uiDomString.length() > 0) + { + uiDomString += F("min "); + } + uiDomString += (offSeconds); + infoArr.add(uiDomString + F("s")); + } else { + infoArr.add(sensorPinState ? F("sensor on") : F("inactive")); + } + } else { + infoArr.add(F("disabled")); + } + + uiDomString = F(" "); + infoArr.add(uiDomString); + + JsonObject sensor = root[F("sensor")]; + if (sensor.isNull()) sensor = root.createNestedObject(F("sensor")); + sensor[F("motion")] = sensorPinState || offTimerStart>0 ? true : false; +} + +void PIRsensorSwitch::onStateChange(uint8_t mode) { + if (!initDone) return; + DEBUG_PRINT(F("PIR: offTimerStart=")); DEBUG_PRINTLN(offTimerStart); + if (m_override && PIRtriggered && offTimerStart) { // debounce + // checking PIRtriggered and offTimerStart will prevent cancellation upon On trigger + DEBUG_PRINTLN(F("PIR: Canceled.")); + offTimerStart = 0; + PIRtriggered = false; + } +} + +void PIRsensorSwitch::readFromJsonState(JsonObject &root) +{ + if (!initDone) return; // prevent crash on boot applyPreset() + JsonObject usermod = root[FPSTR(_name)]; + if (!usermod.isNull()) { + if (usermod[FPSTR(_enabled)].is()) { + enabled = usermod[FPSTR(_enabled)].as(); + } + } +} + +void PIRsensorSwitch::addToConfig(JsonObject &root) +{ + JsonObject top = root.createNestedObject(FPSTR(_name)); + top[FPSTR(_enabled)] = enabled; + top[FPSTR(_switchOffDelay)] = m_switchOffDelay / 1000; + top["pin"] = PIRsensorPin; + top[FPSTR(_onPreset)] = m_onPreset; + top[FPSTR(_offPreset)] = m_offPreset; + top[FPSTR(_nightTime)] = m_nightTimeOnly; + top[FPSTR(_mqttOnly)] = m_mqttOnly; + top[FPSTR(_offOnly)] = m_offOnly; + top[FPSTR(_override)] = m_override; + top[FPSTR(_haDiscovery)] = HomeAssistantDiscovery; + top[FPSTR(_notify)] = (NotifyUpdateMode != CALL_MODE_NO_NOTIFY); + DEBUG_PRINTLN(F("PIR config saved.")); +} + +void PIRsensorSwitch::appendConfigData() +{ + oappend(SET_F("addInfo('PIRsensorSwitch:HA-discovery',1,'HA=Home Assistant');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('PIRsensorSwitch:notifications',1,'Periodic WS updates');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('PIRsensorSwitch:override',1,'Cancel timer on change');")); // 0 is field type, 1 is actual field +} + +bool PIRsensorSwitch::readFromConfig(JsonObject &root) +{ + bool oldEnabled = enabled; + int8_t oldPin = PIRsensorPin; + + DEBUG_PRINT(FPSTR(_name)); + JsonObject top = root[FPSTR(_name)]; + if (top.isNull()) { + DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + return false; + } + + PIRsensorPin = top["pin"] | PIRsensorPin; + + enabled = top[FPSTR(_enabled)] | enabled; + + m_switchOffDelay = (top[FPSTR(_switchOffDelay)] | m_switchOffDelay/1000) * 1000; + + m_onPreset = top[FPSTR(_onPreset)] | m_onPreset; + m_onPreset = max(0,min(250,(int)m_onPreset)); + m_offPreset = top[FPSTR(_offPreset)] | m_offPreset; + m_offPreset = max(0,min(250,(int)m_offPreset)); + + m_nightTimeOnly = top[FPSTR(_nightTime)] | m_nightTimeOnly; + m_mqttOnly = top[FPSTR(_mqttOnly)] | m_mqttOnly; + m_offOnly = top[FPSTR(_offOnly)] | m_offOnly; + m_override = top[FPSTR(_override)] | m_override; + HomeAssistantDiscovery = top[FPSTR(_haDiscovery)] | HomeAssistantDiscovery; + + NotifyUpdateMode = top[FPSTR(_notify)] ? CALL_MODE_DIRECT_CHANGE : CALL_MODE_NO_NOTIFY; + + if (!initDone) { + // reading config prior to setup() + DEBUG_PRINTLN(F(" config loaded.")); + } else { + if (oldPin != PIRsensorPin || oldEnabled != enabled) { + // check if pin is OK + if (oldPin != PIRsensorPin && oldPin >= 0) { + // if we are changing pin in settings page + // deallocate old pin + pinManager.deallocatePin(oldPin, PinOwner::UM_PIR); + if (pinManager.allocatePin(PIRsensorPin, false, PinOwner::UM_PIR)) { + pinMode(PIRsensorPin, INPUT_PULLUP); + } else { + // allocation failed + PIRsensorPin = -1; + enabled = false; + } + } + if (enabled) { + sensorPinState = digitalRead(PIRsensorPin); + } + } + DEBUG_PRINTLN(F(" config (re)loaded.")); + } + // use "return !top["newestParameter"].isNull();" when updating Usermod with new features + return !top[FPSTR(_override)].isNull(); +} diff --git a/usermods/ST7789_display/ST7789_display.h b/usermods/ST7789_display/ST7789_display.h index 93700c91..144cccbf 100644 --- a/usermods/ST7789_display/ST7789_display.h +++ b/usermods/ST7789_display/ST7789_display.h @@ -17,12 +17,6 @@ #ifndef TFT_HEIGHT #error Please define TFT_HEIGHT #endif - #ifndef TFT_MOSI - #error Please define TFT_MOSI - #endif - #ifndef TFT_SCLK - #error Please define TFT_SCLK - #endif #ifndef TFT_DC #error Please define TFT_DC #endif @@ -140,8 +134,14 @@ class St7789DisplayUsermod : public Usermod { */ void setup() { - PinManagerPinType pins[] = { { TFT_MOSI, true }, { TFT_MISO, false}, { TFT_SCLK, true }, { TFT_CS, true}, { TFT_DC, true}, { TFT_RST, true }, { TFT_BL, true } }; - if (!pinManager.allocateMultiplePins(pins, 7, PinOwner::UM_FourLineDisplay)) { enabled = false; return; } + PinManagerPinType spiPins[] = { { spi_mosi, true }, { spi_miso, false}, { spi_sclk, true } }; + if (!pinManager.allocateMultiplePins(spiPins, 3, PinOwner::HW_SPI)) { enabled = false; return; } + PinManagerPinType displayPins[] = { { TFT_CS, true}, { TFT_DC, true}, { TFT_RST, true }, { TFT_BL, true } }; + if (!pinManager.allocateMultiplePins(displayPins, sizeof(displayPins)/sizeof(PinManagerPinType), PinOwner::UM_FourLineDisplay)) { + pinManager.deallocateMultiplePins(spiPins, 3, PinOwner::HW_SPI); + enabled = false; + return; + } tft.init(); tft.setRotation(0); //Rotation here is set up for the text to be readable with the port on the left. Use 1 to flip. @@ -365,9 +365,6 @@ class St7789DisplayUsermod : public Usermod { { JsonObject top = root.createNestedObject("ST7789"); JsonArray pins = top.createNestedArray("pin"); - pins.add(TFT_MOSI); - pins.add(TFT_MISO); - pins.add(TFT_SCLK); pins.add(TFT_CS); pins.add(TFT_DC); pins.add(TFT_RST); @@ -376,6 +373,13 @@ class St7789DisplayUsermod : public Usermod { } + void appendConfigData() { + oappend(SET_F("addInfo('ST7789:pin[]',0,'','SPI CS');")); + oappend(SET_F("addInfo('ST7789:pin[]',1,'','SPI DC');")); + oappend(SET_F("addInfo('ST7789:pin[]',2,'','SPI RST');")); + oappend(SET_F("addInfo('ST7789:pin[]',2,'','SPI BL');")); + } + /* * readFromConfig() can be used to read back the custom settings you added with addToConfig(). * This is called by WLED when settings are loaded (currently this only happens once immediately after boot) diff --git a/usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h b/usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h index 4a42a7d5..bdf78484 100644 --- a/usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h +++ b/usermods/Si7021_MQTT_HA/usermod_si7021_mqtt_ha.h @@ -13,14 +13,6 @@ Adafruit_Si7021 si7021; -#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards -uint8_t SCL_PIN = 22; -uint8_t SDA_PIN = 21; -#else //ESP8266 boards -uint8_t SCL_PIN = 5; -uint8_t SDA_PIN = 4; -#endif - class Si7021_MQTT_HA : public Usermod { private: @@ -184,7 +176,6 @@ class Si7021_MQTT_HA : public Usermod { if (enabled) { Serial.println("Si7021_MQTT_HA: Starting!"); - Wire.begin(SDA_PIN, SCL_PIN); Serial.println("Si7021_MQTT_HA: Initializing sensors.. "); _initializeSensor(); } diff --git a/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h b/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h index 39c2c3ef..80e73b53 100644 --- a/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h +++ b/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h @@ -52,7 +52,6 @@ class UsermodVL53L0XGestures : public Usermod { void setup() { PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } }; if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; } - Wire.begin(); sensor.setTimeout(150); if (!sensor.init()) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 09cc931c..837668ee 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -569,16 +569,6 @@ class AudioReactive : public Usermod { #else int8_t i2sckPin = I2S_CKPIN; #endif - #ifndef ES7243_SDAPIN - int8_t sdaPin = -1; - #else - int8_t sdaPin = ES7243_SDAPIN; - #endif - #ifndef ES7243_SCLPIN - int8_t sclPin = -1; - #else - int8_t sclPin = ES7243_SCLPIN; - #endif #ifndef MCLK_PIN int8_t mclkPin = I2S_PIN_NO_CHANGE; /* ESP32: only -1, 0, 1, 3 allowed*/ #else @@ -1136,7 +1126,7 @@ class AudioReactive : public Usermod { DEBUGSR_PRINTLN(F("AR: ES7243 Microphone (right channel only).")); audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE); delay(100); - if (audioSource) audioSource->initialize(sdaPin, sclPin, i2swsPin, i2ssdPin, i2sckPin, mclkPin); + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); break; case 3: DEBUGSR_PRINT(F("AR: SPH0645 Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); @@ -1657,8 +1647,6 @@ class AudioReactive : public Usermod { pinArray.add(i2swsPin); pinArray.add(i2sckPin); pinArray.add(mclkPin); - pinArray.add(sdaPin); - pinArray.add(sclPin); JsonObject cfg = top.createNestedObject("config"); cfg[F("squelch")] = soundSquelch; @@ -1719,8 +1707,6 @@ class AudioReactive : public Usermod { configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][1], i2swsPin); configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][2], i2sckPin); configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][3], mclkPin); - configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][4], sdaPin); - configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][5], sclPin); configComplete &= getJsonValue(top["config"][F("squelch")], soundSquelch); configComplete &= getJsonValue(top["config"][F("gain")], sampleGain); @@ -1784,8 +1770,6 @@ class AudioReactive : public Usermod { #else oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'master clock','I2S MCLK');")); #endif - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',4,'','I2C SDA');")); - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',5,'','I2C SCL');")); } diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index 16bbbb65..4aa05716 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -1,6 +1,5 @@ #pragma once -#include #include "wled.h" #include #include @@ -383,21 +382,12 @@ class I2SSource : public AudioSource { */ class ES7243 : public I2SSource { private: - // I2C initialization functions for ES7243 - void _es7243I2cBegin() { - bool i2c_initialized = Wire.begin(pin_ES7243_SDA, pin_ES7243_SCL, 100000U); - if (i2c_initialized == false) { - DEBUGSR_PRINTLN(F("AR: ES7243 failed to initialize I2C bus driver.")); - } - } void _es7243I2cWrite(uint8_t reg, uint8_t val) { -#ifndef ES7243_ADDR - Wire.beginTransmission(0x13); - #define ES7243_ADDR 0x13 // default address -#else + #ifndef ES7243_ADDR + #define ES7243_ADDR 0x13 // default address + #endif Wire.beginTransmission(ES7243_ADDR); -#endif Wire.write((uint8_t)reg); Wire.write((uint8_t)val); uint8_t i2cErr = Wire.endTransmission(); // i2cErr == 0 means OK @@ -407,7 +397,6 @@ class ES7243 : public I2SSource { } void _es7243InitAdc() { - _es7243I2cBegin(); _es7243I2cWrite(0x00, 0x01); _es7243I2cWrite(0x06, 0x00); _es7243I2cWrite(0x05, 0x1B); @@ -422,44 +411,20 @@ public: _config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT; }; - void initialize(int8_t sdaPin, int8_t sclPin, int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { - // check that pins are valid - if ((sdaPin < 0) || (sclPin < 0)) { - DEBUGSR_PRINTF("\nAR: invalid ES7243 I2C pins: SDA=%d, SCL=%d\n", sdaPin, sclPin); - return; - } - + void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { if ((i2sckPin < 0) || (mclkPin < 0)) { DEBUGSR_PRINTF("\nAR: invalid I2S pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin); return; } - // Reserve SDA and SCL pins of the I2C interface - PinManagerPinType es7243Pins[2] = { { sdaPin, true }, { sclPin, true } }; - if (!pinManager.allocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C)) { - pinManager.deallocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C); - DEBUGSR_PRINTF("\nAR: Failed to allocate ES7243 I2C pins: SDA=%d, SCL=%d\n", sdaPin, sclPin); - return; - } - - pin_ES7243_SDA = sdaPin; - pin_ES7243_SCL = sclPin; - // First route mclk, then configure ADC over I2C, then configure I2S _es7243InitAdc(); I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); } void deinitialize() { - // Release SDA and SCL pins of the I2C interface - PinManagerPinType es7243Pins[2] = { { pin_ES7243_SDA, true }, { pin_ES7243_SCL, true } }; - pinManager.deallocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C); I2SSource::deinitialize(); } - - private: - int8_t pin_ES7243_SDA; - int8_t pin_ES7243_SCL; }; diff --git a/usermods/mpu6050_imu/usermod_mpu6050_imu.h b/usermods/mpu6050_imu/usermod_mpu6050_imu.h index b4dc01a4..8a6c3dc2 100644 --- a/usermods/mpu6050_imu/usermod_mpu6050_imu.h +++ b/usermods/mpu6050_imu/usermod_mpu6050_imu.h @@ -87,10 +87,8 @@ class MPU6050Driver : public Usermod { void setup() { PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } }; if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; } - // join I2C bus (I2Cdev library doesn't do this automatically) #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE - Wire.begin(); - Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties + Wire.setClock(400000U); // 400kHz I2C clock. Comment this line if having compilation difficulties #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE Fastwire::setup(400, true); #endif diff --git a/usermods/multi_relay/usermod_multi_relay.h b/usermods/multi_relay/usermod_multi_relay.h index 7e91e28a..07ffb2bd 100644 --- a/usermods/multi_relay/usermod_multi_relay.h +++ b/usermods/multi_relay/usermod_multi_relay.h @@ -20,6 +20,14 @@ #define ON true #define OFF false +#ifndef USERMOD_USE_PCF8574 + #undef USE_PCF8574 + #define USE_PCF8574 false +#else + #undef USE_PCF8574 + #define USE_PCF8574 true +#endif + #ifndef PCF8574_ADDRESS #define PCF8574_ADDRESS 0x20 // some may start at 0x38 #endif @@ -50,23 +58,17 @@ class MultiRelay : public Usermod { private: // array of relays - Relay _relay[MULTI_RELAY_MAX_RELAYS]; + Relay _relay[MULTI_RELAY_MAX_RELAYS]; - // switch timer start time - uint32_t _switchTimerStart = 0; - // old brightness - bool _oldMode; - - // usermod enabled - bool enabled = false; // needs to be configured (no default config) - // status of initialisation - bool initDone = false; - bool usePcf8574 = false; - uint8_t addrPcf8574 = PCF8574_ADDRESS; - bool HAautodiscovery = false; - - uint16_t periodicBroadcastSec = 60; - unsigned long lastBroadcast = 0; + uint32_t _switchTimerStart; // switch timer start time + bool _oldMode; // old brightness + bool enabled; // usermod enabled + bool initDone; // status of initialisation + bool usePcf8574; + uint8_t addrPcf8574; + bool HAautodiscovery; + uint16_t periodicBroadcastSec; + unsigned long lastBroadcast; // strings to reduce flash memory usage (used more than twice) static const char _name[]; @@ -103,7 +105,7 @@ class MultiRelay : public Usermod { /** * desctructor */ - ~MultiRelay() {} + //~MultiRelay() {} /** * Enable/Disable the usermod @@ -331,7 +333,16 @@ byte MultiRelay::IOexpanderRead(int address) { // public methods -MultiRelay::MultiRelay() { +MultiRelay::MultiRelay() + : _switchTimerStart(0) + , enabled(false) + , initDone(false) + , usePcf8574(USE_PCF8574) + , addrPcf8574(PCF8574_ADDRESS) + , HAautodiscovery(false) + , periodicBroadcastSec(60) + , lastBroadcast(0) +{ const int8_t defPins[] = {MULTI_RELAY_PINS}; for (size_t i=0; i -#include #include #include #include @@ -16,14 +15,6 @@ Adafruit_BMP280 bmp; Adafruit_Si7021 si7021; Adafruit_CCS811 ccs811; -#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards -uint8_t SCL_PIN = 22; -uint8_t SDA_PIN = 21; -#else //ESP8266 boards -uint8_t SCL_PIN = 5; -uint8_t SDA_PIN = 4; -#endif - class UserMod_SensorsToMQTT : public Usermod { private: @@ -231,7 +222,6 @@ public: void setup() { Serial.println("Starting!"); - Wire.begin(SDA_PIN, SCL_PIN); Serial.println("Initializing sensors.. "); _initialize(); } diff --git a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h index ab244ef2..a4e6291b 100644 --- a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h +++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h @@ -12,9 +12,8 @@ // for WLED. // // Dependencies -// * This usermod REQURES the ModeSortUsermod // * This Usermod works best, by far, when coupled -// with RotaryEncoderUIUsermod. +// with RotaryEncoderUI ALT Usermod. // // Make sure to enable NTP and set your time zone in WLED Config | Time. // @@ -23,22 +22,14 @@ // REQUIREMENT: * U8g2 (the version already in platformio.ini is fine) // REQUIREMENT: * Wire // +// If display does not work or looks corrupted check the +// constructor reference: +// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp +// or check the gallery: +// https://github.com/olikraus/u8g2/wiki/gallery -//The SCL and SDA pins are defined here. -#ifndef FLD_PIN_SCL - #define FLD_PIN_SCL i2c_scl -#endif -#ifndef FLD_PIN_SDA - #define FLD_PIN_SDA i2c_sda -#endif -#ifndef FLD_PIN_CLOCKSPI - #define FLD_PIN_CLOCKSPI spi_sclk -#endif - #ifndef FLD_PIN_DATASPI - #define FLD_PIN_DATASPI spi_mosi -#endif #ifndef FLD_PIN_CS - #define FLD_PIN_CS spi_cs + #define FLD_PIN_CS 15 #endif #ifdef ARDUINO_ARCH_ESP32 @@ -112,10 +103,10 @@ class FourLineDisplayUsermod : public Usermod { U8X8 *u8x8 = nullptr; // pointer to U8X8 display object #ifndef FLD_SPI_DEFAULT - int8_t ioPin[5] = {FLD_PIN_SCL, FLD_PIN_SDA, -1, -1, -1}; // I2C pins: SCL, SDA + int8_t ioPin[3] = {-1, -1, -1}; // I2C pins: SCL, SDA uint32_t ioFrequency = 400000; // in Hz (minimum is 100000, baseline is 400000 and maximum should be 3400000) #else - int8_t ioPin[5] = {FLD_PIN_CLOCKSPI, FLD_PIN_DATASPI, FLD_PIN_CS, FLD_PIN_DC, FLD_PIN_RESET}; // SPI pins: CLK, MOSI, CS, DC, RST + int8_t ioPin[3] = {FLD_PIN_CS, FLD_PIN_DC, FLD_PIN_RESET}; // custom SPI pins: CS, DC, RST uint32_t ioFrequency = 1000000; // in Hz (minimum is 500kHz, baseline is 1MHz and maximum should be 20MHz) #endif @@ -178,313 +169,50 @@ class FourLineDisplayUsermod : public Usermod { // https://github.com/olikraus/u8g2/wiki/gallery // some displays need this to properly apply contrast - void setVcomh(bool highContrast) { - u8x8_t *u8x8_struct = u8x8->getU8x8(); - u8x8_cad_StartTransfer(u8x8_struct); - u8x8_cad_SendCmd(u8x8_struct, 0x0db); //address of value - u8x8_cad_SendArg(u8x8_struct, highContrast ? 0x000 : 0x040); //value 0 for fix, reboot resets default back to 64 - u8x8_cad_EndTransfer(u8x8_struct); - } + void setVcomh(bool highContrast); + void startDisplay(); /** * Wrappers for screen drawing */ - void setFlipMode(uint8_t mode) { - if (type == NONE || !enabled) return; - u8x8->setFlipMode(mode); - } - void setContrast(uint8_t contrast) { - if (type == NONE || !enabled) return; - u8x8->setContrast(contrast); - } - void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) { - if (type == NONE || !enabled) return; - drawing = true; - u8x8->setFont(u8x8_font_chroma48medium8_r); - if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string); - else u8x8->drawString(col, row, string); - drawing = false; - } - void draw2x2String(uint8_t col, uint8_t row, const char *string) { - if (type == NONE || !enabled) return; - drawing = true; - u8x8->setFont(u8x8_font_chroma48medium8_r); - u8x8->draw2x2String(col, row, string); - drawing = false; - } - void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false) { - if (type == NONE || !enabled) return; - drawing = true; - u8x8->setFont(font); - if (!ignoreLH && lineHeight==2) u8x8->draw1x2Glyph(col, row, glyph); - else u8x8->drawGlyph(col, row, glyph); - drawing = false; - } - void draw2x2Glyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font) { - if (type == NONE || !enabled) return; - drawing = true; - u8x8->setFont(font); - u8x8->draw2x2Glyph(col, row, glyph); - drawing = false; - } - uint8_t getCols() { - if (type==NONE || !enabled) return 0; - return u8x8->getCols(); - } - void clear() { - if (type == NONE || !enabled) return; - drawing = true; - u8x8->clear(); - drawing = false; - } - void setPowerSave(uint8_t save) { - if (type == NONE || !enabled) return; - u8x8->setPowerSave(save); - } - - void center(String &line, uint8_t width) { - int len = line.length(); - if (len0; i--) line = ' ' + line; - for (byte i=line.length(); i 11) { AmPmHour -= 12; isitAM = false; } - if (AmPmHour == 0) { AmPmHour = 12; } - } - if (knownHour != hourCurrent) { - // only update date when hour changes - sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(month(localTime)), day(localTime)); - draw2x2String(2, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays, draw month and day - } - sprintf_P(lineBuffer,PSTR("%2d:%02d"), (useAMPM ? AmPmHour : hourCurrent), minuteCurrent); - draw2x2String(2, lineHeight*2, lineBuffer); //draw hour, min. blink ":" depending on odd/even seconds - if (useAMPM) drawString(12, lineHeight*2, (isitAM ? "AM" : "PM"), true); //draw am/pm if using 12 time - - drawStatusIcons(); //icons power, wifi, timer, etc - - knownMinute = minuteCurrent; - knownHour = hourCurrent; - } - if (showSeconds && secondCurrent != lastSecond) { - lastSecond = secondCurrent; - draw2x2String(6, lineHeight*2, secondCurrent%2 ? " " : ":"); - sprintf_P(lineBuffer, PSTR("%02d"), secondCurrent); - drawString(12, lineHeight*2+1, lineBuffer, true); // even with double sized rows print seconds in 1 line - } - } + void showTime(); /** * Enable sleep (turn the display off) or clock mode. */ - void sleepOrClock(bool enabled) { - if (enabled) { - displayTurnedOff = true; - if (clockMode && ntpEnabled) { - knownMinute = knownHour = 99; - showTime(); - } else - setPowerSave(1); - } else { - displayTurnedOff = false; - setPowerSave(0); - } - } + void sleepOrClock(bool enabled); public: // gets called once at boot. Do all initialization that doesn't depend on // network here - void setup() { - if (type == NONE || !enabled) return; - - bool isHW, isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64); - PinOwner po = PinOwner::UM_FourLineDisplay; - if (isSPI) { - uint8_t hw_sclk = spi_sclk<0 ? HW_PIN_CLOCKSPI : spi_sclk; - uint8_t hw_mosi = spi_mosi<0 ? HW_PIN_DATASPI : spi_mosi; - if (ioPin[0] < 0 || ioPin[1] < 0) { - ioPin[0] = hw_sclk; - ioPin[1] = hw_mosi; - } - isHW = (ioPin[0]==hw_sclk && ioPin[1]==hw_mosi); - PinManagerPinType cspins[3] = { { ioPin[2], true }, { ioPin[3], true }, { ioPin[4], true } }; - if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { type=NONE; return; } - if (isHW) po = PinOwner::HW_SPI; // allow multiple allocations of HW I2C bus pins - PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; - if (!pinManager.allocateMultiplePins(pins, 2, po)) { - pinManager.deallocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay); - type = NONE; - return; - } - } else { - uint8_t hw_scl = i2c_scl<0 ? HW_PIN_SCL : i2c_scl; - uint8_t hw_sda = i2c_sda<0 ? HW_PIN_SDA : i2c_sda; - if (ioPin[0] < 0 || ioPin[1] < 0) { - ioPin[0] = hw_scl; - ioPin[1] = hw_sda; - } - isHW = (ioPin[0]==hw_scl && ioPin[1]==hw_sda); - if (isHW) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins - PinManagerPinType pins[2] = { {ioPin[0], true }, { ioPin[1], true } }; - if (!pinManager.allocateMultiplePins(pins, 2, po)) { type=NONE; return; } - } - - DEBUG_PRINTLN(F("Allocating display.")); -/* -// At some point it may be good to not new/delete U8X8 object but use this instead -// (does not currently work) -//------------------------------------------------------------------------------- - switch (type) { - case SSD1306: - u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1306_128x32_univision, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino); - break; - case SH1106: - u8x8_Setup(u8x8.getU8x8(), u8x8_d_sh1106_128x64_winstar, u8x8_cad_ssd13xx_i2c, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino); - break; - case SSD1306_64: - u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1306_128x64_noname, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino); - break; - case SSD1305: - u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1305_128x32_adafruit, u8x8_cad_ssd13xx_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino); - break; - case SSD1305_64: - u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1305_128x64_adafruit, u8x8_cad_ssd13xx_i2c, u8x8_byte_arduino_sw_i2c, u8x8_gpio_and_delay_arduino); - break; - case SSD1306_SPI: - u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1306_128x32_univision, u8x8_cad_001, u8x8_byte_arduino_4wire_sw_spi, u8x8_gpio_and_delay_arduino); - break; - case SSD1306_SPI64: - u8x8_Setup(u8x8.getU8x8(), u8x8_d_ssd1306_128x64_noname, u8x8_cad_001, u8x8_byte_arduino_4wire_sw_spi, u8x8_gpio_and_delay_arduino); - break; - default: - type = NONE; - return; - } - if (isSPI) { - if (!isHW) u8x8_SetPin_4Wire_SW_SPI(u8x8.getU8x8(), ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]); - else u8x8_SetPin_4Wire_HW_SPI(u8x8.getU8x8(), ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset - } else { - if (!isHW) u8x8_SetPin_SW_I2C(u8x8.getU8x8(), ioPin[0], ioPin[1], U8X8_PIN_NONE); // SCL, SDA, reset - else u8x8_SetPin_HW_I2C(u8x8.getU8x8(), U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA - } -*/ - switch (type) { - case SSD1306: - if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset - else u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA - break; - case SH1106: - if (!isHW) u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset - else u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA - break; - case SSD1306_64: - if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset - else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA - break; - case SSD1305: - if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset - else u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA - break; - case SSD1305_64: - if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset - else u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA - break; - case SSD1306_SPI: - // u8x8 uses global SPI variable that is attached to VSPI bus on ESP32 - if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]); - else u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset - break; - case SSD1306_SPI64: - // u8x8 uses global SPI variable that is attached to VSPI bus on ESP32 - if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]); - else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset - break; - default: - u8x8 = nullptr; - } - - if (nullptr == u8x8) { - DEBUG_PRINTLN(F("Display init failed.")); - pinManager.deallocateMultiplePins((const uint8_t*)ioPin, isSPI ? 5 : 2, po); - type = NONE; - return; - } - - lineHeight = u8x8->getRows() > 4 ? 2 : 1; - DEBUG_PRINTLN(F("Starting display.")); - u8x8->setBusClock(ioFrequency); // can be used for SPI too - u8x8->begin(); - setFlipMode(flip); - setVcomh(contrastFix); - setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255 - setPowerSave(0); - //drawString(0, 0, "Loading..."); - overlayLogo(3500); - onUpdateBegin(false); // create Display task - initDone = true; - } + void setup(); // gets called every time WiFi is (re-)connected. Initialize own network // interfaces here - void connected() { - knownSsid = WiFi.SSID(); //apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() : - knownIp = Network.localIP(); //apActive ? IPAddress(4, 3, 2, 1) : Network.localIP(); - networkOverlay(PSTR("NETWORK INFO"),7000); - } + void connected(); /** * Da loop. */ - void loop() { - #ifndef ARDUINO_ARCH_ESP32 - if (!enabled || strip.isUpdating()) return; - unsigned long now = millis(); - if (now < nextUpdate) return; - nextUpdate = now + ((displayTurnedOff && clockMode && showSeconds) ? 1000 : refreshRate); - redraw(false); - #endif - } + void loop(); //function to update lastredraw void updateRedrawTime() { @@ -495,244 +223,26 @@ class FourLineDisplayUsermod : public Usermod { * Redraw the screen (but only if things have changed * or if forceRedraw). */ - void redraw(bool forceRedraw) { - bool needRedraw = false; - unsigned long now = millis(); - - if (type == NONE || !enabled) return; - if (overlayUntil > 0) { - if (now >= overlayUntil) { - // Time to display the overlay has elapsed. - overlayUntil = 0; - forceRedraw = true; - } else { - // We are still displaying the overlay - // Don't redraw. - return; - } - } + void redraw(bool forceRedraw); - while (drawing && millis()-now < 25) delay(1); // wait if someone else is drawing - if (drawing || lockRedraw) return; - - if (apActive && WLED_WIFI_CONFIGURED && now<15000) { - knownSsid = apSSID; - networkOverlay(PSTR("NETWORK INFO"),30000); - return; - } - - // Check if values which are shown on display changed from the last time. - if (forceRedraw) { - needRedraw = true; - clear(); - } else if ((bri == 0 && powerON) || (bri > 0 && !powerON)) { //trigger power icon - powerON = !powerON; - drawStatusIcons(); - return; - } else if (knownnightlight != nightlightActive) { //trigger moon icon - knownnightlight = nightlightActive; - drawStatusIcons(); - if (knownnightlight) { - String timer = PSTR("Timer On"); - center(timer,LINE_BUFFER_SIZE-1); - overlay(timer.c_str(), 2500, 6); - } - return; - } else if (wificonnected != interfacesInited) { //trigger wifi icon - wificonnected = interfacesInited; - drawStatusIcons(); - return; - } else if (knownMode != effectCurrent || knownPalette != effectPalette) { - if (displayTurnedOff) needRedraw = true; - else { - if (knownPalette != effectPalette) { showCurrentEffectOrPalette(effectPalette, JSON_palette_names, 2); knownPalette = effectPalette; } - if (knownMode != effectCurrent) { showCurrentEffectOrPalette(effectCurrent, JSON_mode_names, 3); knownMode = effectCurrent; } - lastRedraw = now; - return; - } - } else if (knownBrightness != bri) { - if (displayTurnedOff && nightlightActive) { knownBrightness = bri; } - else if (!displayTurnedOff) { updateBrightness(); lastRedraw = now; return; } - } else if (knownEffectSpeed != effectSpeed) { - if (displayTurnedOff) needRedraw = true; - else { updateSpeed(); lastRedraw = now; return; } - } else if (knownEffectIntensity != effectIntensity) { - if (displayTurnedOff) needRedraw = true; - else { updateIntensity(); lastRedraw = now; return; } - } - - if (!needRedraw) { - // Nothing to change. - // Turn off display after 1 minutes with no change. - if (sleepMode && !displayTurnedOff && (millis() - lastRedraw > screenTimeout)) { - // We will still check if there is a change in redraw() - // and turn it back on if it changed. - clear(); - sleepOrClock(true); - } else if (displayTurnedOff && ntpEnabled) { - showTime(); - } - return; - } - - lastRedraw = now; - - // Turn the display back on - wakeDisplay(); - - // Update last known values. - knownBrightness = bri; - knownMode = effectCurrent; - knownPalette = effectPalette; - knownEffectSpeed = effectSpeed; - knownEffectIntensity = effectIntensity; - knownnightlight = nightlightActive; - wificonnected = interfacesInited; - - // Do the actual drawing - // First row: Icons - draw2x2GlyphIcons(); - drawArrow(); - drawStatusIcons(); - - // Second row - updateBrightness(); - updateSpeed(); - updateIntensity(); - - // Third row - showCurrentEffectOrPalette(knownPalette, JSON_palette_names, 2); //Palette info - - // Fourth row - showCurrentEffectOrPalette(knownMode, JSON_mode_names, 3); //Effect Mode info - } - - void updateBrightness() { - knownBrightness = bri; - if (overlayUntil == 0) { - lockRedraw = true; - brightness100 = ((uint16_t)bri*100)/255; - char lineBuffer[4]; - sprintf_P(lineBuffer, PSTR("%-3d"), brightness100); - drawString(1, lineHeight, lineBuffer); - lockRedraw = false; - } - } - - void updateSpeed() { - knownEffectSpeed = effectSpeed; - if (overlayUntil == 0) { - lockRedraw = true; - fxspeed100 = ((uint16_t)effectSpeed*100)/255; - char lineBuffer[4]; - sprintf_P(lineBuffer, PSTR("%-3d"), fxspeed100); - drawString(5, lineHeight, lineBuffer); - lockRedraw = false; - } - } - - void updateIntensity() { - knownEffectIntensity = effectIntensity; - if (overlayUntil == 0) { - lockRedraw = true; - fxintensity100 = ((uint16_t)effectIntensity*100)/255; - char lineBuffer[4]; - sprintf_P(lineBuffer, PSTR("%-3d"), fxintensity100); - drawString(9, lineHeight, lineBuffer); - lockRedraw = false; - } - } - - void drawStatusIcons() { - uint8_t col = 15; - uint8_t row = 0; - lockRedraw = true; - drawGlyph(col, row, (wificonnected ? 20 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // wifi icon - if (lineHeight==2) { col--; } else { row++; } - drawGlyph(col, row, (bri > 0 ? 9 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // power icon - if (lineHeight==2) { col--; } else { col = row = 0; } - drawGlyph(col, row, (nightlightActive ? 6 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // moon icon for nighlight mode - lockRedraw = false; - } + void updateBrightness(); + void updateSpeed(); + void updateIntensity(); + void drawStatusIcons(); /** * marks the position of the arrow showing * the current setting being changed * pass line and colum info */ - void setMarkLine(byte newMarkLineNum, byte newMarkColNum) { - markLineNum = newMarkLineNum; - markColNum = newMarkColNum; - } + void setMarkLine(byte newMarkLineNum, byte newMarkColNum); //Draw the arrow for the current setting beiong changed - void drawArrow() { - lockRedraw = true; - if (markColNum != 255 && markLineNum !=255) drawGlyph(markColNum, markLineNum*lineHeight, 21, u8x8_4LineDisplay_WLED_icons_1x1); - lockRedraw = false; - } + void drawArrow(); //Display the current effect or palette (desiredEntry) // on the appropriate line (row). - void showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row) { - char lineBuffer[MAX_JSON_CHARS]; - if (overlayUntil == 0) { - lockRedraw = true; - // Find the mode name in JSON - uint8_t printedChars = extractModeName(inputEffPal, qstring, lineBuffer, MAX_JSON_CHARS-1); - if (lineBuffer[0]=='*' && lineBuffer[1]==' ') { - // remove "* " from dynamic palettes - for (byte i=2; i<=printedChars; i++) lineBuffer[i-2] = lineBuffer[i]; //include '\0' - printedChars -= 2; - } else if ((lineBuffer[0]==' ' && lineBuffer[1]>127)) { - // remove note symbol from effect names - for (byte i=5; i<=printedChars; i++) lineBuffer[i-5] = lineBuffer[i]; //include '\0' - printedChars -= 5; - } - if (lineHeight == 2) { // use this code for 8 line display - char smallBuffer1[MAX_MODE_LINE_SPACE]; - char smallBuffer2[MAX_MODE_LINE_SPACE]; - uint8_t smallChars1 = 0; - uint8_t smallChars2 = 0; - if (printedChars < MAX_MODE_LINE_SPACE) { // use big font if the text fits - while (printedChars < (MAX_MODE_LINE_SPACE-1)) lineBuffer[printedChars++]=' '; - lineBuffer[printedChars] = 0; - drawString(1, row*lineHeight, lineBuffer); - } else { // for long names divide the text into 2 lines and print them small - bool spaceHit = false; - for (uint8_t i = 0; i < printedChars; i++) { - switch (lineBuffer[i]) { - case ' ': - if (i > 4 && !spaceHit) { - spaceHit = true; - break; - } - if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i]; - else smallBuffer1[smallChars1++] = lineBuffer[i]; - break; - default: - if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i]; - else smallBuffer1[smallChars1++] = lineBuffer[i]; - break; - } - } - while (smallChars1 < (MAX_MODE_LINE_SPACE-1)) smallBuffer1[smallChars1++]=' '; - smallBuffer1[smallChars1] = 0; - drawString(1, row*lineHeight, smallBuffer1, true); - while (smallChars2 < (MAX_MODE_LINE_SPACE-1)) smallBuffer2[smallChars2++]=' '; - smallBuffer2[smallChars2] = 0; - drawString(1, row*lineHeight+1, smallBuffer2, true); - } - } else { // use this code for 4 ling displays - char smallBuffer3[MAX_MODE_LINE_SPACE+1]; // uses 1x1 icon for mode/palette - uint8_t smallChars3 = 0; - for (uint8_t i = 0; i < MAX_MODE_LINE_SPACE; i++) smallBuffer3[smallChars3++] = (i >= printedChars) ? ' ' : lineBuffer[i]; - smallBuffer3[smallChars3] = 0; - drawString(1, row*lineHeight, smallBuffer3, true); - } - lockRedraw = false; - } - } + void showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row); /** * If there screen is off or in clock is displayed, @@ -740,330 +250,59 @@ class FourLineDisplayUsermod : public Usermod { * the first input from the rotary encoder but * to wake up the screen. */ - bool wakeDisplay() { - if (type == NONE || !enabled) return false; - if (displayTurnedOff) { - unsigned long now = millis(); - while (drawing && millis()-now < 250) delay(1); // wait if someone else is drawing - if (drawing) return false; - lockRedraw = true; - clear(); - // Turn the display back on - sleepOrClock(false); - lockRedraw = false; - return true; - } - return false; - } + bool wakeDisplay(); /** * Allows you to show one line and a glyph as overlay for a period of time. * Clears the screen and prints. * Used in Rotary Encoder usermod. */ - void overlay(const char* line1, long showHowLong, byte glyphType) { - unsigned long now = millis(); - while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing - if (drawing) return; - lockRedraw = true; - // Turn the display back on - if (!wakeDisplay()) clear(); - // Print the overlay - if (glyphType>0 && glyphType<255) { - if (lineHeight == 2) drawGlyph(5, 0, glyphType, u8x8_4LineDisplay_WLED_icons_6x6, true); // use 3x3 font with draw2x2Glyph() if flash runs short and comment out 6x6 font - else drawGlyph(6, 0, glyphType, u8x8_4LineDisplay_WLED_icons_3x3, true); - } - if (line1) { - String buf = line1; - center(buf, getCols()); - drawString(0, (glyphType<255?3:0)*lineHeight, buf.c_str()); - } - overlayUntil = millis() + showHowLong; - lockRedraw = false; - } + void overlay(const char* line1, long showHowLong, byte glyphType); /** * Allows you to show Akemi WLED logo overlay for a period of time. * Clears the screen and prints. */ - void overlayLogo(long showHowLong) { - unsigned long now = millis(); - while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing - if (drawing) return; - lockRedraw = true; - // Turn the display back on - if (!wakeDisplay()) clear(); - // Print the overlay - if (lineHeight == 2) { - //add a bit of randomness - switch (millis()%3) { - case 0: - //WLED - draw2x2Glyph( 0, 2, 1, u8x8_wled_logo_2x2); - draw2x2Glyph( 4, 2, 2, u8x8_wled_logo_2x2); - draw2x2Glyph( 8, 2, 3, u8x8_wled_logo_2x2); - draw2x2Glyph(12, 2, 4, u8x8_wled_logo_2x2); - break; - case 1: - //WLED Akemi - drawGlyph( 2, 2, 1, u8x8_wled_logo_akemi_4x4, true); - drawGlyph( 6, 2, 2, u8x8_wled_logo_akemi_4x4, true); - drawGlyph(10, 2, 3, u8x8_wled_logo_akemi_4x4, true); - break; - case 2: - //Akemi - //draw2x2Glyph( 5, 0, 12, u8x8_4LineDisplay_WLED_icons_3x3); // use this if flash runs short and comment out 6x6 font - drawGlyph( 5, 0, 12, u8x8_4LineDisplay_WLED_icons_6x6, true); - drawString(6, 6, "WLED"); - break; - } - } else { - switch (millis()%3) { - case 0: - //WLED - draw2x2Glyph( 0, 0, 1, u8x8_wled_logo_2x2); - draw2x2Glyph( 4, 0, 2, u8x8_wled_logo_2x2); - draw2x2Glyph( 8, 0, 3, u8x8_wled_logo_2x2); - draw2x2Glyph(12, 0, 4, u8x8_wled_logo_2x2); - break; - case 1: - //WLED Akemi - drawGlyph( 2, 0, 1, u8x8_wled_logo_akemi_4x4); - drawGlyph( 6, 0, 2, u8x8_wled_logo_akemi_4x4); - drawGlyph(10, 0, 3, u8x8_wled_logo_akemi_4x4); - break; - case 2: - //Akemi - //drawGlyph( 6, 0, 12, u8x8_4LineDisplay_WLED_icons_4x4); // a bit nicer, but uses extra 1.5k flash - draw2x2Glyph( 6, 0, 12, u8x8_4LineDisplay_WLED_icons_2x2); - break; - } - } - overlayUntil = millis() + showHowLong; - lockRedraw = false; - } + void overlayLogo(long showHowLong); /** * Allows you to show two lines as overlay for a period of time. * Clears the screen and prints. * Used in Auto Save usermod */ - void overlay(const char* line1, const char* line2, long showHowLong) { - unsigned long now = millis(); - while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing - if (drawing) return; - lockRedraw = true; - // Turn the display back on - if (!wakeDisplay()) clear(); - // Print the overlay - if (line1) { - String buf = line1; - center(buf, getCols()); - drawString(0, 1*lineHeight, buf.c_str()); - } - if (line2) { - String buf = line2; - center(buf, getCols()); - drawString(0, 2*lineHeight, buf.c_str()); - } - overlayUntil = millis() + showHowLong; - lockRedraw = false; - } - - void networkOverlay(const char* line1, long showHowLong) { - unsigned long now = millis(); - while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing - if (drawing) return; - lockRedraw = true; - - String line; - // Turn the display back on - if (!wakeDisplay()) clear(); - // Print the overlay - if (line1) { - line = line1; - center(line, getCols()); - drawString(0, 0, line.c_str()); - } - // Second row with Wifi name - line = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0); - if (line.length() < getCols()) center(line, getCols()); - drawString(0, lineHeight, line.c_str()); - // Print `~` char to indicate that SSID is longer, than our display - if (knownSsid.length() > getCols()) { - drawString(getCols() - 1, 0, "~"); - } - // Third row with IP and Password in AP Mode - line = knownIp.toString(); - center(line, getCols()); - drawString(0, lineHeight*2, line.c_str()); - line = ""; - if (apActive) { - line = apPass; - } else if (strcmp(serverDescription, "WLED") != 0) { - line = serverDescription; - } - center(line, getCols()); - drawString(0, lineHeight*3, line.c_str()); - overlayUntil = millis() + showHowLong; - lockRedraw = false; - } + void overlay(const char* line1, const char* line2, long showHowLong); + void networkOverlay(const char* line1, long showHowLong); /** * 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 (!enabled - || b // butto 0 only - || buttonType[b] == BTN_TYPE_SWITCH - || 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 handleButton(uint8_t b); - unsigned long now = millis(); - static bool buttonPressedBefore = false; - static bool buttonLongPressed = false; - static unsigned long buttonPressedTime = 0; - static unsigned long buttonWaitTime = 0; - bool handled = true; - - //momentary button logic - if (isButtonPressed(b)) { //pressed - - if (!buttonPressedBefore) buttonPressedTime = now; - buttonPressedBefore = true; - - if (now - buttonPressedTime > 600) { //long press - buttonLongPressed = true; - //TODO: handleButton() handles button 0 without preset in a different way for double click - //so we need to override with same behaviour - longPressAction(0); - //handled = false; - } - - } else if (!isButtonPressed(b) && buttonPressedBefore) { //released - - long dur = now - buttonPressedTime; - if (dur < 50) { - buttonPressedBefore = false; - return true; - } //too short "press", debounce - - bool doublePress = buttonWaitTime; //did we have short press before? - buttonWaitTime = 0; - - if (!buttonLongPressed) { //short press - // if this is second release within 350ms it is a double press (buttonWaitTime!=0) - //TODO: handleButton() handles button 0 without preset in a different way for double click - if (doublePress) { - networkOverlay(PSTR("NETWORK INFO"),7000); - handled = true; - } else { - buttonWaitTime = now; - } - } - buttonPressedBefore = false; - buttonLongPressed = false; - } - // if 350ms elapsed since last press/release it is a short press - if (buttonWaitTime && now - buttonWaitTime > 350 && !buttonPressedBefore) { - buttonWaitTime = 0; - //TODO: handleButton() handles button 0 without preset in a different way for double click - //so we need to override with same behaviour - shortPressAction(0); - //handled = false; - } - return handled; - } - - #if CONFIG_FREERTOS_UNICORE - #define ARDUINO_RUNNING_CORE 0 - #else - #define ARDUINO_RUNNING_CORE 1 - #endif - void onUpdateBegin(bool init) { - #ifdef ARDUINO_ARCH_ESP32 - if (init && Display_Task) { - vTaskSuspend(Display_Task); // update is about to begin, disable task to prevent crash - } else { - // update has failed or create task requested - if (Display_Task) - vTaskResume(Display_Task); - else - xTaskCreatePinnedToCore( - [](void * par) { // Function to implement the task - // see https://www.freertos.org/vtaskdelayuntil.html - const TickType_t xFrequency = REFRESH_RATE_MS * portTICK_PERIOD_MS / 2; - TickType_t xLastWakeTime = xTaskGetTickCount(); - for(;;) { - delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy. - // taskYIELD(), yield(), vTaskDelay() and esp_task_wdt_feed() didn't seem to work. - vTaskDelayUntil(&xLastWakeTime, xFrequency); // release CPU, by doing nothing for REFRESH_RATE_MS millis - FourLineDisplayUsermod::getInstance()->redraw(false); - } - }, - "4LD", // Name of the task - 3072, // Stack size in words - NULL, // Task input parameter - 1, // Priority of the task (not idle) - &Display_Task, // Task handle - ARDUINO_RUNNING_CORE - ); - } - #endif - } + void onUpdateBegin(bool init); /* * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. * Below it is shown how this could be used for e.g. a light sensor */ - //void addToJsonInfo(JsonObject& root) { - //JsonObject user = root["u"]; - //if (user.isNull()) user = root.createNestedObject("u"); - //JsonArray data = user.createNestedArray(F("4LineDisplay")); - //data.add(F("Loaded.")); - //} + //void addToJsonInfo(JsonObject& root); /* * 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); /* * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - //void readFromJsonState(JsonObject& root) { - // if (!initDone) return; // prevent crash on boot applyPreset() - //} + //void readFromJsonState(JsonObject& root); - void appendConfigData() { - oappend(SET_F("dd=addDropdown('4LineDisplay','type');")); - oappend(SET_F("addOption(dd,'None',0);")); - oappend(SET_F("addOption(dd,'SSD1306',1);")); - oappend(SET_F("addOption(dd,'SH1106',2);")); - oappend(SET_F("addOption(dd,'SSD1306 128x64',3);")); - oappend(SET_F("addOption(dd,'SSD1305',4);")); - oappend(SET_F("addOption(dd,'SSD1305 128x64',5);")); - oappend(SET_F("addOption(dd,'SSD1306 SPI',6);")); - oappend(SET_F("addOption(dd,'SSD1306 SPI 128x64',7);")); - oappend(SET_F("addInfo('4LineDisplay:pin[]',0,'-1 use global','I2C/SPI CLK');")); - oappend(SET_F("addInfo('4LineDisplay:pin[]',1,'-1 use global','I2C/SPI DTA');")); - oappend(SET_F("addInfo('4LineDisplay:pin[]',2,'','SPI CS');")); - oappend(SET_F("addInfo('4LineDisplay:pin[]',3,'','SPI DC');")); - oappend(SET_F("addInfo('4LineDisplay:pin[]',4,'','SPI RST');")); - } + void appendConfigData(); /* * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. @@ -1079,40 +318,7 @@ class FourLineDisplayUsermod : public Usermod { * * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! */ - void addToConfig(JsonObject& root) { - // determine if we are using global HW pins (data & clock) - int8_t hw_dta, hw_clk; - if ((type == SSD1306_SPI || type == SSD1306_SPI64)) { - hw_clk = spi_sclk<0 ? HW_PIN_CLOCKSPI : spi_sclk; - hw_dta = spi_mosi<0 ? HW_PIN_DATASPI : spi_mosi; - } else { - hw_clk = i2c_scl<0 ? HW_PIN_SCL : i2c_scl; - hw_dta = i2c_sda<0 ? HW_PIN_SDA : i2c_sda; - } - - JsonObject top = root.createNestedObject(FPSTR(_name)); - top[FPSTR(_enabled)] = enabled; - - JsonArray io_pin = top.createNestedArray("pin"); - for (int i=0; i<5; i++) { - if (i==0 && ioPin[i]==hw_clk) io_pin.add(-1); // do not store global HW pin - else if (i==1 && ioPin[i]==hw_dta) io_pin.add(-1); // do not store global HW pin - else io_pin.add(ioPin[i]); - } - top["type"] = type; - top[FPSTR(_flip)] = (bool) flip; - top[FPSTR(_contrast)] = contrast; - top[FPSTR(_contrastFix)] = (bool) contrastFix; - #ifndef ARDUINO_ARCH_ESP32 - top[FPSTR(_refreshRate)] = refreshRate; - #endif - top[FPSTR(_screenTimeOut)] = screenTimeout/1000; - top[FPSTR(_sleepMode)] = (bool) sleepMode; - top[FPSTR(_clockMode)] = (bool) clockMode; - top[FPSTR(_showSeconds)] = (bool) showSeconds; - top[FPSTR(_busClkFrequency)] = ioFrequency/1000; - DEBUG_PRINTLN(F("4 Line Display config saved.")); - } + void addToConfig(JsonObject& root); /* * readFromConfig() can be used to read back the custom settings you added with addToConfig(). @@ -1122,80 +328,7 @@ class FourLineDisplayUsermod : public Usermod { * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) */ - bool readFromConfig(JsonObject& root) { - bool needsRedraw = false; - DisplayType newType = type; - int8_t oldPin[5]; for (byte i=0; i<5; i++) oldPin[i] = ioPin[i]; - - JsonObject top = root[FPSTR(_name)]; - if (top.isNull()) { - DEBUG_PRINT(FPSTR(_name)); - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); - return false; - } - - enabled = top[FPSTR(_enabled)] | enabled; - newType = top["type"] | newType; - for (byte i=0; i<5; i++) ioPin[i] = top["pin"][i] | ioPin[i]; - flip = top[FPSTR(_flip)] | flip; - contrast = top[FPSTR(_contrast)] | contrast; - #ifndef ARDUINO_ARCH_ESP32 - refreshRate = top[FPSTR(_refreshRate)] | refreshRate; - refreshRate = min(5000, max(250, (int)refreshRate)); - #endif - screenTimeout = (top[FPSTR(_screenTimeOut)] | screenTimeout/1000) * 1000; - sleepMode = top[FPSTR(_sleepMode)] | sleepMode; - clockMode = top[FPSTR(_clockMode)] | clockMode; - showSeconds = top[FPSTR(_showSeconds)] | showSeconds; - contrastFix = top[FPSTR(_contrastFix)] | contrastFix; - if (newType == SSD1306_SPI || newType == SSD1306_SPI64) - ioFrequency = min(20000, max(500, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency - else - ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency - - DEBUG_PRINT(FPSTR(_name)); - if (!initDone) { - // first run: reading from cfg.json - type = newType; - DEBUG_PRINTLN(F(" config loaded.")); - } else { - DEBUG_PRINTLN(F(" config (re)loaded.")); - // changing parameters from settings page - bool pinsChanged = false; - for (byte i=0; i<5; i++) if (ioPin[i] != oldPin[i]) { pinsChanged = true; break; } - if (pinsChanged || type!=newType) { - if (type != NONE) delete u8x8; - PinOwner po = PinOwner::UM_FourLineDisplay; - bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64); - if (isSPI) { - pinManager.deallocateMultiplePins((const uint8_t *)(&oldPin[2]), 3, po); - uint8_t hw_sclk = spi_sclk<0 ? HW_PIN_CLOCKSPI : spi_sclk; - uint8_t hw_mosi = spi_mosi<0 ? HW_PIN_DATASPI : spi_mosi; - bool isHW = (oldPin[0]==hw_sclk && oldPin[1]==hw_mosi); - if (isHW) po = PinOwner::HW_SPI; - } else { - uint8_t hw_scl = i2c_scl<0 ? HW_PIN_SCL : i2c_scl; - uint8_t hw_sda = i2c_sda<0 ? HW_PIN_SDA : i2c_sda; - bool isHW = (oldPin[0]==hw_scl && oldPin[1]==hw_sda); - if (isHW) po = PinOwner::HW_I2C; - } - pinManager.deallocateMultiplePins((const uint8_t *)oldPin, 2, po); - type = newType; - setup(); - needsRedraw |= true; - } else { - u8x8->setBusClock(ioFrequency); // can be used for SPI too - setVcomh(contrastFix); - setContrast(contrast); - setFlipMode(flip); - } - knownHour = 99; - if (needsRedraw && !wakeDisplay()) redraw(true); - else overlayLogo(3500); - } - // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[FPSTR(_contrastFix)].isNull(); - } + bool readFromConfig(JsonObject& root); /* * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). @@ -1220,3 +353,989 @@ const char FourLineDisplayUsermod::_busClkFrequency[] PROGMEM = "i2c-freq-kHz"; const char FourLineDisplayUsermod::_contrastFix[] PROGMEM = "contrastFix"; FourLineDisplayUsermod *FourLineDisplayUsermod::instance = nullptr; + +// some displays need this to properly apply contrast +void FourLineDisplayUsermod::setVcomh(bool highContrast) { + u8x8_t *u8x8_struct = u8x8->getU8x8(); + u8x8_cad_StartTransfer(u8x8_struct); + u8x8_cad_SendCmd(u8x8_struct, 0x0db); //address of value + u8x8_cad_SendArg(u8x8_struct, highContrast ? 0x000 : 0x040); //value 0 for fix, reboot resets default back to 64 + u8x8_cad_EndTransfer(u8x8_struct); +} + +void FourLineDisplayUsermod::startDisplay() { + lineHeight = u8x8->getRows() > 4 ? 2 : 1; + DEBUG_PRINTLN(F("Starting display.")); + u8x8->setBusClock(ioFrequency); // can be used for SPI too + u8x8->begin(); + setFlipMode(flip); + setVcomh(contrastFix); + setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255 + setPowerSave(0); + //drawString(0, 0, "Loading..."); + overlayLogo(3500); +} + +/** + * Wrappers for screen drawing + */ +void FourLineDisplayUsermod::setFlipMode(uint8_t mode) { + if (type == NONE || !enabled) return; + u8x8->setFlipMode(mode); +} +void FourLineDisplayUsermod::setContrast(uint8_t contrast) { + if (type == NONE || !enabled) return; + u8x8->setContrast(contrast); +} +void FourLineDisplayUsermod::drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH) { + if (type == NONE || !enabled) return; + drawing = true; + u8x8->setFont(u8x8_font_chroma48medium8_r); + if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string); + else u8x8->drawString(col, row, string); + drawing = false; +} +void FourLineDisplayUsermod::draw2x2String(uint8_t col, uint8_t row, const char *string) { + if (type == NONE || !enabled) return; + drawing = true; + u8x8->setFont(u8x8_font_chroma48medium8_r); + u8x8->draw2x2String(col, row, string); + drawing = false; +} +void FourLineDisplayUsermod::drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH) { + if (type == NONE || !enabled) return; + drawing = true; + u8x8->setFont(font); + if (!ignoreLH && lineHeight==2) u8x8->draw1x2Glyph(col, row, glyph); + else u8x8->drawGlyph(col, row, glyph); + drawing = false; +} +void FourLineDisplayUsermod::draw2x2Glyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font) { + if (type == NONE || !enabled) return; + drawing = true; + u8x8->setFont(font); + u8x8->draw2x2Glyph(col, row, glyph); + drawing = false; +} +uint8_t FourLineDisplayUsermod::getCols() { + if (type==NONE || !enabled) return 0; + return u8x8->getCols(); +} +void FourLineDisplayUsermod::clear() { + if (type == NONE || !enabled) return; + drawing = true; + u8x8->clear(); + drawing = false; +} +void FourLineDisplayUsermod::setPowerSave(uint8_t save) { + if (type == NONE || !enabled) return; + u8x8->setPowerSave(save); +} + +void FourLineDisplayUsermod::center(String &line, uint8_t width) { + int len = line.length(); + if (len0; i--) line = ' ' + line; + for (byte i=line.length(); i 11) { AmPmHour -= 12; isitAM = false; } + if (AmPmHour == 0) { AmPmHour = 12; } + } + if (knownHour != hourCurrent) { + // only update date when hour changes + sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(month(localTime)), day(localTime)); + draw2x2String(2, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays, draw month and day + } + sprintf_P(lineBuffer,PSTR("%2d:%02d"), (useAMPM ? AmPmHour : hourCurrent), minuteCurrent); + draw2x2String(2, lineHeight*2, lineBuffer); //draw hour, min. blink ":" depending on odd/even seconds + if (useAMPM) drawString(12, lineHeight*2, (isitAM ? "AM" : "PM"), true); //draw am/pm if using 12 time + + drawStatusIcons(); //icons power, wifi, timer, etc + + knownMinute = minuteCurrent; + knownHour = hourCurrent; + } + if (showSeconds && secondCurrent != lastSecond) { + lastSecond = secondCurrent; + draw2x2String(6, lineHeight*2, secondCurrent%2 ? " " : ":"); + sprintf_P(lineBuffer, PSTR("%02d"), secondCurrent); + drawString(12, lineHeight*2+1, lineBuffer, true); // even with double sized rows print seconds in 1 line + } +} + +/** + * Enable sleep (turn the display off) or clock mode. + */ +void FourLineDisplayUsermod::sleepOrClock(bool enabled) { + if (enabled) { + displayTurnedOff = true; + if (clockMode && ntpEnabled) { + knownMinute = knownHour = 99; + showTime(); + } else + setPowerSave(1); + } else { + displayTurnedOff = false; + setPowerSave(0); + } +} + +// gets called once at boot. Do all initialization that doesn't depend on +// network here +void FourLineDisplayUsermod::setup() { + if (type == NONE || !enabled) return; + + bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64); + + // check if pins are -1 and disable usermod as PinManager::allocateMultiplePins() will accept -1 as a valid pin + if (isSPI) { + PinManagerPinType cspins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } }; + if (ioPin[0]==-1 || ioPin[1]==-1 || ioPin[1]==-1) { type=NONE; return; } + if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { type=NONE; return; } + PinManagerPinType pins[2] = { { spi_sclk, true }, { spi_mosi, true } }; + if (spi_sclk==-1 || spi_mosi==-1 || !pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_SPI)) { + pinManager.deallocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay); + type = NONE; + return; + } + } else { + PinManagerPinType pins[2] = { {i2c_scl, true }, { i2c_sda, true } }; + if (i2c_scl==-1 || i2c_sda==-1 || !pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { type=NONE; return; } + } + + DEBUG_PRINTLN(F("Allocating display.")); + switch (type) { + // U8X8 uses Wire (or Wire1 with 2ND constructor) and will use existing Wire properties (calls Wire.begin() though) + case SSD1306: u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(); break; + case SH1106: u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(); break; + case SSD1306_64: u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(); break; + case SSD1305: u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(); break; + case SSD1305_64: u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(); break; + // U8X8 uses global SPI variable that is attached to VSPI bus on ESP32 + case SSD1306_SPI: u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_HW_SPI(ioPin[0], ioPin[1], ioPin[2]); break; // Pins are cs, dc, reset + case SSD1306_SPI64: u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[0], ioPin[1], ioPin[2]); break; // Pins are cs, dc, reset + // catchall + default: u8x8 = (U8X8 *) new U8X8_NULL(); break; + } + + if (nullptr == u8x8) { + DEBUG_PRINTLN(F("Display init failed.")); + if (isSPI) { + int8_t pins[] = {spi_sclk, spi_mosi}; + pinManager.deallocateMultiplePins((const uint8_t*)pins, 2, PinOwner::HW_SPI); + pinManager.deallocateMultiplePins((const uint8_t*)ioPin, 3, PinOwner::UM_FourLineDisplay); + } else { + int8_t pins[] = {i2c_scl, i2c_sda}; + pinManager.deallocateMultiplePins((const uint8_t*)pins, 2, PinOwner::HW_I2C); + } + type = NONE; + return; + } + + startDisplay(); + onUpdateBegin(false); // create Display task + initDone = true; +} + +// gets called every time WiFi is (re-)connected. Initialize own network +// interfaces here +void FourLineDisplayUsermod::connected() { + knownSsid = WiFi.SSID(); //apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() : + knownIp = Network.localIP(); //apActive ? IPAddress(4, 3, 2, 1) : Network.localIP(); + networkOverlay(PSTR("NETWORK INFO"),7000); +} + +/** + * Da loop. + */ +void FourLineDisplayUsermod::loop() { +#ifndef ARDUINO_ARCH_ESP32 + if (!enabled || strip.isUpdating()) return; + unsigned long now = millis(); + if (now < nextUpdate) return; + nextUpdate = now + ((displayTurnedOff && clockMode && showSeconds) ? 1000 : refreshRate); + redraw(false); +#endif +} + +/** + * Redraw the screen (but only if things have changed + * or if forceRedraw). + */ +void FourLineDisplayUsermod::redraw(bool forceRedraw) { + bool needRedraw = false; + unsigned long now = millis(); + + if (type == NONE || !enabled) return; + if (overlayUntil > 0) { + if (now >= overlayUntil) { + // Time to display the overlay has elapsed. + overlayUntil = 0; + forceRedraw = true; + } else { + // We are still displaying the overlay + // Don't redraw. + return; + } + } + + while (drawing && millis()-now < 25) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return; + + if (apActive && WLED_WIFI_CONFIGURED && now<15000) { + knownSsid = apSSID; + networkOverlay(PSTR("NETWORK INFO"),30000); + return; + } + + // Check if values which are shown on display changed from the last time. + if (forceRedraw) { + needRedraw = true; + clear(); + } else if ((bri == 0 && powerON) || (bri > 0 && !powerON)) { //trigger power icon + powerON = !powerON; + drawStatusIcons(); + return; + } else if (knownnightlight != nightlightActive) { //trigger moon icon + knownnightlight = nightlightActive; + drawStatusIcons(); + if (knownnightlight) { + String timer = PSTR("Timer On"); + center(timer,LINE_BUFFER_SIZE-1); + overlay(timer.c_str(), 2500, 6); + } + return; + } else if (wificonnected != interfacesInited) { //trigger wifi icon + wificonnected = interfacesInited; + drawStatusIcons(); + return; + } else if (knownMode != effectCurrent || knownPalette != effectPalette) { + if (displayTurnedOff) needRedraw = true; + else { + if (knownPalette != effectPalette) { showCurrentEffectOrPalette(effectPalette, JSON_palette_names, 2); knownPalette = effectPalette; } + if (knownMode != effectCurrent) { showCurrentEffectOrPalette(effectCurrent, JSON_mode_names, 3); knownMode = effectCurrent; } + lastRedraw = now; + return; + } + } else if (knownBrightness != bri) { + if (displayTurnedOff && nightlightActive) { knownBrightness = bri; } + else if (!displayTurnedOff) { updateBrightness(); lastRedraw = now; return; } + } else if (knownEffectSpeed != effectSpeed) { + if (displayTurnedOff) needRedraw = true; + else { updateSpeed(); lastRedraw = now; return; } + } else if (knownEffectIntensity != effectIntensity) { + if (displayTurnedOff) needRedraw = true; + else { updateIntensity(); lastRedraw = now; return; } + } + + if (!needRedraw) { + // Nothing to change. + // Turn off display after 1 minutes with no change. + if (sleepMode && !displayTurnedOff && (millis() - lastRedraw > screenTimeout)) { + // We will still check if there is a change in redraw() + // and turn it back on if it changed. + clear(); + sleepOrClock(true); + } else if (displayTurnedOff && ntpEnabled) { + showTime(); + } + return; + } + + lastRedraw = now; + + // Turn the display back on + wakeDisplay(); + + // Update last known values. + knownBrightness = bri; + knownMode = effectCurrent; + knownPalette = effectPalette; + knownEffectSpeed = effectSpeed; + knownEffectIntensity = effectIntensity; + knownnightlight = nightlightActive; + wificonnected = interfacesInited; + + // Do the actual drawing + // First row: Icons + draw2x2GlyphIcons(); + drawArrow(); + drawStatusIcons(); + + // Second row + updateBrightness(); + updateSpeed(); + updateIntensity(); + + // Third row + showCurrentEffectOrPalette(knownPalette, JSON_palette_names, 2); //Palette info + + // Fourth row + showCurrentEffectOrPalette(knownMode, JSON_mode_names, 3); //Effect Mode info +} + +void FourLineDisplayUsermod::updateBrightness() { + knownBrightness = bri; + if (overlayUntil == 0) { + lockRedraw = true; + brightness100 = ((uint16_t)bri*100)/255; + char lineBuffer[4]; + sprintf_P(lineBuffer, PSTR("%-3d"), brightness100); + drawString(1, lineHeight, lineBuffer); + lockRedraw = false; + } +} + +void FourLineDisplayUsermod::updateSpeed() { + knownEffectSpeed = effectSpeed; + if (overlayUntil == 0) { + lockRedraw = true; + fxspeed100 = ((uint16_t)effectSpeed*100)/255; + char lineBuffer[4]; + sprintf_P(lineBuffer, PSTR("%-3d"), fxspeed100); + drawString(5, lineHeight, lineBuffer); + lockRedraw = false; + } +} + +void FourLineDisplayUsermod::updateIntensity() { + knownEffectIntensity = effectIntensity; + if (overlayUntil == 0) { + lockRedraw = true; + fxintensity100 = ((uint16_t)effectIntensity*100)/255; + char lineBuffer[4]; + sprintf_P(lineBuffer, PSTR("%-3d"), fxintensity100); + drawString(9, lineHeight, lineBuffer); + lockRedraw = false; + } +} + +void FourLineDisplayUsermod::drawStatusIcons() { + uint8_t col = 15; + uint8_t row = 0; + lockRedraw = true; + drawGlyph(col, row, (wificonnected ? 20 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // wifi icon + if (lineHeight==2) { col--; } else { row++; } + drawGlyph(col, row, (bri > 0 ? 9 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // power icon + if (lineHeight==2) { col--; } else { col = row = 0; } + drawGlyph(col, row, (nightlightActive ? 6 : 0), u8x8_4LineDisplay_WLED_icons_1x1, true); // moon icon for nighlight mode + lockRedraw = false; +} + +/** + * marks the position of the arrow showing + * the current setting being changed + * pass line and colum info + */ +void FourLineDisplayUsermod::setMarkLine(byte newMarkLineNum, byte newMarkColNum) { + markLineNum = newMarkLineNum; + markColNum = newMarkColNum; +} + +//Draw the arrow for the current setting beiong changed +void FourLineDisplayUsermod::drawArrow() { + lockRedraw = true; + if (markColNum != 255 && markLineNum !=255) drawGlyph(markColNum, markLineNum*lineHeight, 21, u8x8_4LineDisplay_WLED_icons_1x1); + lockRedraw = false; +} + +//Display the current effect or palette (desiredEntry) +// on the appropriate line (row). +void FourLineDisplayUsermod::showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row) { + char lineBuffer[MAX_JSON_CHARS]; + if (overlayUntil == 0) { + lockRedraw = true; + // Find the mode name in JSON + uint8_t printedChars = extractModeName(inputEffPal, qstring, lineBuffer, MAX_JSON_CHARS-1); + if (lineBuffer[0]=='*' && lineBuffer[1]==' ') { + // remove "* " from dynamic palettes + for (byte i=2; i<=printedChars; i++) lineBuffer[i-2] = lineBuffer[i]; //include '\0' + printedChars -= 2; + } else if ((lineBuffer[0]==' ' && lineBuffer[1]>127)) { + // remove note symbol from effect names + for (byte i=5; i<=printedChars; i++) lineBuffer[i-5] = lineBuffer[i]; //include '\0' + printedChars -= 5; + } + if (lineHeight == 2) { // use this code for 8 line display + char smallBuffer1[MAX_MODE_LINE_SPACE]; + char smallBuffer2[MAX_MODE_LINE_SPACE]; + uint8_t smallChars1 = 0; + uint8_t smallChars2 = 0; + if (printedChars < MAX_MODE_LINE_SPACE) { // use big font if the text fits + while (printedChars < (MAX_MODE_LINE_SPACE-1)) lineBuffer[printedChars++]=' '; + lineBuffer[printedChars] = 0; + drawString(1, row*lineHeight, lineBuffer); + } else { // for long names divide the text into 2 lines and print them small + bool spaceHit = false; + for (uint8_t i = 0; i < printedChars; i++) { + switch (lineBuffer[i]) { + case ' ': + if (i > 4 && !spaceHit) { + spaceHit = true; + break; + } + if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i]; + else smallBuffer1[smallChars1++] = lineBuffer[i]; + break; + default: + if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i]; + else smallBuffer1[smallChars1++] = lineBuffer[i]; + break; + } + } + while (smallChars1 < (MAX_MODE_LINE_SPACE-1)) smallBuffer1[smallChars1++]=' '; + smallBuffer1[smallChars1] = 0; + drawString(1, row*lineHeight, smallBuffer1, true); + while (smallChars2 < (MAX_MODE_LINE_SPACE-1)) smallBuffer2[smallChars2++]=' '; + smallBuffer2[smallChars2] = 0; + drawString(1, row*lineHeight+1, smallBuffer2, true); + } + } else { // use this code for 4 ling displays + char smallBuffer3[MAX_MODE_LINE_SPACE+1]; // uses 1x1 icon for mode/palette + uint8_t smallChars3 = 0; + for (uint8_t i = 0; i < MAX_MODE_LINE_SPACE; i++) smallBuffer3[smallChars3++] = (i >= printedChars) ? ' ' : lineBuffer[i]; + smallBuffer3[smallChars3] = 0; + drawString(1, row*lineHeight, smallBuffer3, true); + } + lockRedraw = false; + } +} + +/** + * If there screen is off or in clock is displayed, + * this will return true. This allows us to throw away + * the first input from the rotary encoder but + * to wake up the screen. + */ +bool FourLineDisplayUsermod::wakeDisplay() { + if (type == NONE || !enabled) return false; + if (displayTurnedOff) { + unsigned long now = millis(); + while (drawing && millis()-now < 250) delay(1); // wait if someone else is drawing + if (drawing) return false; + lockRedraw = true; + clear(); + // Turn the display back on + sleepOrClock(false); + lockRedraw = false; + return true; + } + return false; +} + +/** + * Allows you to show one line and a glyph as overlay for a period of time. + * Clears the screen and prints. + * Used in Rotary Encoder usermod. + */ +void FourLineDisplayUsermod::overlay(const char* line1, long showHowLong, byte glyphType) { + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing) return; + lockRedraw = true; + // Turn the display back on + if (!wakeDisplay()) clear(); + // Print the overlay + if (glyphType>0 && glyphType<255) { + if (lineHeight == 2) drawGlyph(5, 0, glyphType, u8x8_4LineDisplay_WLED_icons_6x6, true); // use 3x3 font with draw2x2Glyph() if flash runs short and comment out 6x6 font + else drawGlyph(6, 0, glyphType, u8x8_4LineDisplay_WLED_icons_3x3, true); + } + if (line1) { + String buf = line1; + center(buf, getCols()); + drawString(0, (glyphType<255?3:0)*lineHeight, buf.c_str()); + } + overlayUntil = millis() + showHowLong; + lockRedraw = false; +} + +/** + * Allows you to show Akemi WLED logo overlay for a period of time. + * Clears the screen and prints. + */ +void FourLineDisplayUsermod::overlayLogo(long showHowLong) { + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing) return; + lockRedraw = true; + // Turn the display back on + if (!wakeDisplay()) clear(); + // Print the overlay + if (lineHeight == 2) { + //add a bit of randomness + switch (millis()%3) { + case 0: + //WLED + draw2x2Glyph( 0, 2, 1, u8x8_wled_logo_2x2); + draw2x2Glyph( 4, 2, 2, u8x8_wled_logo_2x2); + draw2x2Glyph( 8, 2, 3, u8x8_wled_logo_2x2); + draw2x2Glyph(12, 2, 4, u8x8_wled_logo_2x2); + break; + case 1: + //WLED Akemi + drawGlyph( 2, 2, 1, u8x8_wled_logo_akemi_4x4, true); + drawGlyph( 6, 2, 2, u8x8_wled_logo_akemi_4x4, true); + drawGlyph(10, 2, 3, u8x8_wled_logo_akemi_4x4, true); + break; + case 2: + //Akemi + //draw2x2Glyph( 5, 0, 12, u8x8_4LineDisplay_WLED_icons_3x3); // use this if flash runs short and comment out 6x6 font + drawGlyph( 5, 0, 12, u8x8_4LineDisplay_WLED_icons_6x6, true); + drawString(6, 6, "WLED"); + break; + } + } else { + switch (millis()%3) { + case 0: + //WLED + draw2x2Glyph( 0, 0, 1, u8x8_wled_logo_2x2); + draw2x2Glyph( 4, 0, 2, u8x8_wled_logo_2x2); + draw2x2Glyph( 8, 0, 3, u8x8_wled_logo_2x2); + draw2x2Glyph(12, 0, 4, u8x8_wled_logo_2x2); + break; + case 1: + //WLED Akemi + drawGlyph( 2, 0, 1, u8x8_wled_logo_akemi_4x4); + drawGlyph( 6, 0, 2, u8x8_wled_logo_akemi_4x4); + drawGlyph(10, 0, 3, u8x8_wled_logo_akemi_4x4); + break; + case 2: + //Akemi + //drawGlyph( 6, 0, 12, u8x8_4LineDisplay_WLED_icons_4x4); // a bit nicer, but uses extra 1.5k flash + draw2x2Glyph( 6, 0, 12, u8x8_4LineDisplay_WLED_icons_2x2); + break; + } + } + overlayUntil = millis() + showHowLong; + lockRedraw = false; +} + +/** + * Allows you to show two lines as overlay for a period of time. + * Clears the screen and prints. + * Used in Auto Save usermod + */ +void FourLineDisplayUsermod::overlay(const char* line1, const char* line2, long showHowLong) { + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing) return; + lockRedraw = true; + // Turn the display back on + if (!wakeDisplay()) clear(); + // Print the overlay + if (line1) { + String buf = line1; + center(buf, getCols()); + drawString(0, 1*lineHeight, buf.c_str()); + } + if (line2) { + String buf = line2; + center(buf, getCols()); + drawString(0, 2*lineHeight, buf.c_str()); + } + overlayUntil = millis() + showHowLong; + lockRedraw = false; +} + +void FourLineDisplayUsermod::networkOverlay(const char* line1, long showHowLong) { + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing) return; + lockRedraw = true; + + String line; + // Turn the display back on + if (!wakeDisplay()) clear(); + // Print the overlay + if (line1) { + line = line1; + center(line, getCols()); + drawString(0, 0, line.c_str()); + } + // Second row with Wifi name + line = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0); + if (line.length() < getCols()) center(line, getCols()); + drawString(0, lineHeight, line.c_str()); + // Print `~` char to indicate that SSID is longer, than our display + if (knownSsid.length() > getCols()) { + drawString(getCols() - 1, 0, "~"); + } + // Third row with IP and Password in AP Mode + line = knownIp.toString(); + center(line, getCols()); + drawString(0, lineHeight*2, line.c_str()); + line = ""; + if (apActive) { + line = apPass; + } else if (strcmp(serverDescription, "WLED") != 0) { + line = serverDescription; + } + center(line, getCols()); + drawString(0, lineHeight*3, line.c_str()); + overlayUntil = millis() + showHowLong; + lockRedraw = false; +} + + +/** + * handleButton() can be used to override default button behaviour. Returning true + * will prevent button working in a default way. + * Replicating button.cpp + */ +bool FourLineDisplayUsermod::handleButton(uint8_t b) { + yield(); + if (!enabled + || b // butto 0 only + || buttonType[b] == BTN_TYPE_SWITCH + || 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; + } + + unsigned long now = millis(); + static bool buttonPressedBefore = false; + static bool buttonLongPressed = false; + static unsigned long buttonPressedTime = 0; + static unsigned long buttonWaitTime = 0; + bool handled = true; + + //momentary button logic + if (isButtonPressed(b)) { //pressed + + if (!buttonPressedBefore) buttonPressedTime = now; + buttonPressedBefore = true; + + if (now - buttonPressedTime > 600) { //long press + buttonLongPressed = true; + //TODO: handleButton() handles button 0 without preset in a different way for double click + //so we need to override with same behaviour + longPressAction(0); + //handled = false; + } + + } else if (!isButtonPressed(b) && buttonPressedBefore) { //released + + long dur = now - buttonPressedTime; + if (dur < 50) { + buttonPressedBefore = false; + return true; + } //too short "press", debounce + + bool doublePress = buttonWaitTime; //did we have short press before? + buttonWaitTime = 0; + + if (!buttonLongPressed) { //short press + // if this is second release within 350ms it is a double press (buttonWaitTime!=0) + //TODO: handleButton() handles button 0 without preset in a different way for double click + if (doublePress) { + networkOverlay(PSTR("NETWORK INFO"),7000); + handled = true; + } else { + buttonWaitTime = now; + } + } + buttonPressedBefore = false; + buttonLongPressed = false; + } + // if 350ms elapsed since last press/release it is a short press + if (buttonWaitTime && now - buttonWaitTime > 350 && !buttonPressedBefore) { + buttonWaitTime = 0; + //TODO: handleButton() handles button 0 without preset in a different way for double click + //so we need to override with same behaviour + shortPressAction(0); + //handled = false; + } + return handled; +} + +#if CONFIG_FREERTOS_UNICORE +#define ARDUINO_RUNNING_CORE 0 +#else +#define ARDUINO_RUNNING_CORE 1 +#endif +void FourLineDisplayUsermod::onUpdateBegin(bool init) { +#ifdef ARDUINO_ARCH_ESP32 + if (init && Display_Task) { + vTaskSuspend(Display_Task); // update is about to begin, disable task to prevent crash + } else { + // update has failed or create task requested + if (Display_Task) + vTaskResume(Display_Task); + else + xTaskCreatePinnedToCore( + [](void * par) { // Function to implement the task + // see https://www.freertos.org/vtaskdelayuntil.html + const TickType_t xFrequency = REFRESH_RATE_MS * portTICK_PERIOD_MS / 2; + TickType_t xLastWakeTime = xTaskGetTickCount(); + for(;;) { + delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy. + // taskYIELD(), yield(), vTaskDelay() and esp_task_wdt_feed() didn't seem to work. + vTaskDelayUntil(&xLastWakeTime, xFrequency); // release CPU, by doing nothing for REFRESH_RATE_MS millis + FourLineDisplayUsermod::getInstance()->redraw(false); + } + }, + "4LD", // Name of the task + 3072, // Stack size in words + NULL, // Task input parameter + 1, // Priority of the task (not idle) + &Display_Task, // Task handle + ARDUINO_RUNNING_CORE + ); + } +#endif +} + +/* + * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. + * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. + * Below it is shown how this could be used for e.g. a light sensor + */ +//void FourLineDisplayUsermod::addToJsonInfo(JsonObject& root) { + //JsonObject user = root["u"]; + //if (user.isNull()) user = root.createNestedObject("u"); + //JsonArray data = user.createNestedArray(F("4LineDisplay")); + //data.add(F("Loaded.")); +//} + +/* + * 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 FourLineDisplayUsermod::addToJsonState(JsonObject& root) { +//} + +/* + * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ +//void FourLineDisplayUsermod::readFromJsonState(JsonObject& root) { +// if (!initDone) return; // prevent crash on boot applyPreset() +//} + +void FourLineDisplayUsermod::appendConfigData() { + oappend(SET_F("dd=addDropdown('4LineDisplay','type');")); + oappend(SET_F("addOption(dd,'None',0);")); + oappend(SET_F("addOption(dd,'SSD1306',1);")); + oappend(SET_F("addOption(dd,'SH1106',2);")); + oappend(SET_F("addOption(dd,'SSD1306 128x64',3);")); + oappend(SET_F("addOption(dd,'SSD1305',4);")); + oappend(SET_F("addOption(dd,'SSD1305 128x64',5);")); + oappend(SET_F("addOption(dd,'SSD1306 SPI',6);")); + oappend(SET_F("addOption(dd,'SSD1306 SPI 128x64',7);")); + oappend(SET_F("addInfo('4LineDisplay:type',1,'
Change may require reboot','');")); + oappend(SET_F("addInfo('4LineDisplay:pin[]',0,'','SPI CS');")); + oappend(SET_F("addInfo('4LineDisplay:pin[]',1,'','SPI DC');")); + oappend(SET_F("addInfo('4LineDisplay:pin[]',2,'','SPI RST');")); +} + +/* + * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. + * It will be called by WLED when settings are actually saved (for example, LED settings are saved) + * If you want to force saving the current state, use serializeConfig() in your loop(). + * + * CAUTION: serializeConfig() will initiate a filesystem write operation. + * It might cause the LEDs to stutter and will cause flash wear if called too often. + * Use it sparingly and always in the loop, never in network callbacks! + * + * addToConfig() will also not yet add your setting to one of the settings pages automatically. + * To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually. + * + * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! + */ +void FourLineDisplayUsermod::addToConfig(JsonObject& root) { + JsonObject top = root.createNestedObject(FPSTR(_name)); + top[FPSTR(_enabled)] = enabled; + + top["type"] = type; + JsonArray io_pin = top.createNestedArray("pin"); + for (int i=0; i<3; i++) io_pin.add(ioPin[i]); + top[FPSTR(_flip)] = (bool) flip; + top[FPSTR(_contrast)] = contrast; + top[FPSTR(_contrastFix)] = (bool) contrastFix; + #ifndef ARDUINO_ARCH_ESP32 + top[FPSTR(_refreshRate)] = refreshRate; + #endif + top[FPSTR(_screenTimeOut)] = screenTimeout/1000; + top[FPSTR(_sleepMode)] = (bool) sleepMode; + top[FPSTR(_clockMode)] = (bool) clockMode; + top[FPSTR(_showSeconds)] = (bool) showSeconds; + top[FPSTR(_busClkFrequency)] = ioFrequency/1000; + DEBUG_PRINTLN(F("4 Line Display config saved.")); +} + +/* + * readFromConfig() can be used to read back the custom settings you added with addToConfig(). + * This is called by WLED when settings are loaded (currently this only happens once immediately after boot) + * + * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), + * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. + * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) + */ +bool FourLineDisplayUsermod::readFromConfig(JsonObject& root) { + bool needsRedraw = false; + DisplayType newType = type; + int8_t oldPin[3]; for (byte i=0; i<3; i++) oldPin[i] = ioPin[i]; + + JsonObject top = root[FPSTR(_name)]; + if (top.isNull()) { + DEBUG_PRINT(FPSTR(_name)); + DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + return false; + } + + enabled = top[FPSTR(_enabled)] | enabled; + newType = top["type"] | newType; + for (byte i=0; i<3; i++) ioPin[i] = top["pin"][i] | ioPin[i]; + flip = top[FPSTR(_flip)] | flip; + contrast = top[FPSTR(_contrast)] | contrast; + #ifndef ARDUINO_ARCH_ESP32 + refreshRate = top[FPSTR(_refreshRate)] | refreshRate; + refreshRate = min(5000, max(250, (int)refreshRate)); + #endif + screenTimeout = (top[FPSTR(_screenTimeOut)] | screenTimeout/1000) * 1000; + sleepMode = top[FPSTR(_sleepMode)] | sleepMode; + clockMode = top[FPSTR(_clockMode)] | clockMode; + showSeconds = top[FPSTR(_showSeconds)] | showSeconds; + contrastFix = top[FPSTR(_contrastFix)] | contrastFix; + if (newType == SSD1306_SPI || newType == SSD1306_SPI64) + ioFrequency = min(20000, max(500, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency + else + ioFrequency = min(3400, max(100, (int)(top[FPSTR(_busClkFrequency)] | ioFrequency/1000))) * 1000; // limit frequency + + DEBUG_PRINT(FPSTR(_name)); + if (!initDone) { + // first run: reading from cfg.json + type = newType; + DEBUG_PRINTLN(F(" config loaded.")); + } else { + DEBUG_PRINTLN(F(" config (re)loaded.")); + // changing parameters from settings page + bool pinsChanged = false; + for (byte i=0; i<3; i++) if (ioPin[i] != oldPin[i]) { pinsChanged = true; break; } + if (pinsChanged || type!=newType) { + bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64); + bool newSPI = (newType == SSD1306_SPI || newType == SSD1306_SPI64); + if (isSPI) { + if (pinsChanged || !newSPI) pinManager.deallocateMultiplePins((const uint8_t*)oldPin, 3, PinOwner::UM_FourLineDisplay); + if (!newSPI) { + // was SPI but is no longer SPI + int8_t oldPins[] = {spi_sclk, spi_mosi}; + pinManager.deallocateMultiplePins((const uint8_t*)oldPins, 2, PinOwner::HW_SPI); + PinManagerPinType pins[2] = { {i2c_scl, true }, { i2c_sda, true } }; + if (i2c_scl==-1 || i2c_sda==-1 || !pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { newType=NONE; } + } else { + // still SPI but pins changed + PinManagerPinType cspins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } }; + if (ioPin[0]==-1 || ioPin[1]==-1 || ioPin[1]==-1) { newType=NONE; } + else if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; } + } + } else if (newSPI) { + // was I2C but is now SPI + int8_t oldPins[] = {i2c_scl, i2c_sda}; + pinManager.deallocateMultiplePins((const uint8_t*)oldPins, 2, PinOwner::HW_I2C); + PinManagerPinType pins[3] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true } }; + if (ioPin[0]==-1 || ioPin[1]==-1 || ioPin[1]==-1) { newType=NONE; } + else if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_FourLineDisplay)) { newType=NONE; } + else { + PinManagerPinType pins[2] = { { spi_sclk, true }, { spi_mosi, true } }; + if (spi_sclk==-1 || spi_mosi==-1 || !pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_SPI)) { + pinManager.deallocateMultiplePins(pins, 3, PinOwner::UM_FourLineDisplay); + newType = NONE; + } + } + } else { + // just I2C tye changed + } + type = newType; + switch (type) { + case SSD1306: + u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1306_128x32_univision, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino); + u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE); + break; + case SH1106: + u8x8_Setup(u8x8->getU8x8(), u8x8_d_sh1106_128x64_winstar, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino); + u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE); + break; + case SSD1306_64: + u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1306_128x64_noname, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino); + u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE); + break; + case SSD1305: + u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1305_128x32_adafruit, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino); + u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE); + break; + case SSD1305_64: + u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1305_128x64_adafruit, u8x8_cad_ssd13xx_fast_i2c, u8x8_byte_arduino_hw_i2c, u8x8_gpio_and_delay_arduino); + u8x8_SetPin_HW_I2C(u8x8->getU8x8(), U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE); + break; + case SSD1306_SPI: + u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1306_128x32_univision, u8x8_cad_001, u8x8_byte_arduino_hw_spi, u8x8_gpio_and_delay_arduino); + u8x8_SetPin_4Wire_HW_SPI(u8x8->getU8x8(), ioPin[0], ioPin[1], ioPin[2]); // Pins are cs, dc, reset + break; + case SSD1306_SPI64: + u8x8_Setup(u8x8->getU8x8(), u8x8_d_ssd1306_128x64_noname, u8x8_cad_001, u8x8_byte_arduino_hw_spi, u8x8_gpio_and_delay_arduino); + u8x8_SetPin_4Wire_HW_SPI(u8x8->getU8x8(), ioPin[0], ioPin[1], ioPin[2]); // Pins are cs, dc, reset + break; + default: + u8x8_Setup(u8x8->getU8x8(), u8x8_d_null_cb, u8x8_cad_empty, u8x8_byte_empty, u8x8_dummy_cb); + break; + } + startDisplay(); + needsRedraw |= true; + } else { + u8x8->setBusClock(ioFrequency); // can be used for SPI too + setVcomh(contrastFix); + setContrast(contrast); + setFlipMode(flip); + } + knownHour = 99; + if (needsRedraw && !wakeDisplay()) redraw(true); + else overlayLogo(3500); + } + // use "return !top["newestParameter"].isNull();" when updating Usermod with new features + return !top[FPSTR(_contrastFix)].isNull(); +} diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h index 35d3e6f3..27d58a75 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h @@ -45,7 +45,19 @@ #define ENCODER_SW_PIN 19 #endif -// The last UI state, remove color and saturation option if diplay not active(too many options) +#ifndef USERMOD_USE_PCF8574 + #undef USE_PCF8574 + #define USE_PCF8574 false +#else + #undef USE_PCF8574 + #define USE_PCF8574 true +#endif + +#ifndef PCF8574_ADDRESS + #define PCF8574_ADDRESS 0x20 // some may start at 0x38 +#endif + +// The last UI state, remove color and saturation option if display not active (too many options) #ifdef USERMOD_FOUR_LINE_DISPLAY #define LAST_UI_STATE 11 #else @@ -114,158 +126,340 @@ static int re_qstringCmp(const void *ap, const void *bp) { class RotaryEncoderUIUsermod : public Usermod { -private: - int8_t fadeAmount = 5; // Amount to change every step (brightness) - unsigned long loopTime; - unsigned long buttonPressedTime = 0; - unsigned long buttonWaitTime = 0; - bool buttonPressedBefore = false; - bool buttonLongPressed = false; + private: - int8_t pinA = ENCODER_DT_PIN; // DT from encoder - int8_t pinB = ENCODER_CLK_PIN; // CLK from encoder - int8_t pinC = ENCODER_SW_PIN; // SW from encoder + const int8_t fadeAmount; // Amount to change every step (brightness) + unsigned long loopTime; - unsigned char select_state = 0; // 0: brightness, 1: effect, 2: effect speed, ... + unsigned long buttonPressedTime; + unsigned long buttonWaitTime; + bool buttonPressedBefore; + bool buttonLongPressed; - uint16_t currentHue1 = 16; // default boot color - byte currentSat1 = 255; - -#ifdef USERMOD_FOUR_LINE_DISPLAY - FourLineDisplayUsermod *display; -#else - void* display = nullptr; + int8_t pinA; // DT from encoder + int8_t pinB; // CLK from encoder + int8_t pinC; // SW from encoder + + unsigned char select_state; // 0: brightness, 1: effect, 2: effect speed, ... + + uint16_t currentHue1; // default boot color + byte currentSat1; + uint8_t currentCCT; + + #ifdef USERMOD_FOUR_LINE_DISPLAY + FourLineDisplayUsermod *display; + #else + void* display; + #endif + + // Pointers the start of the mode names within JSON_mode_names + const char **modes_qstrings; + + // Array of mode indexes in alphabetical order. + byte *modes_alpha_indexes; + + // Pointers the start of the palette names within JSON_palette_names + const char **palettes_qstrings; + + // Array of palette indexes in alphabetical order. + byte *palettes_alpha_indexes; + + struct { // reduce memory footprint + bool Enc_A : 1; + bool Enc_B : 1; + bool Enc_A_prev : 1; + }; + + bool currentEffectAndPaletteInitialized; + uint8_t effectCurrentIndex; + uint8_t effectPaletteIndex; + uint8_t knownMode; + uint8_t knownPalette; + + byte presetHigh; + byte presetLow; + + bool applyToAll; + + bool initDone; + bool enabled; + + bool usePcf8574; + uint8_t addrPcf8574; + + // strings to reduce flash memory usage (used more than twice) + static const char _name[]; + static const char _enabled[]; + static const char _DT_pin[]; + static const char _CLK_pin[]; + static const char _SW_pin[]; + static const char _presetHigh[]; + static const char _presetLow[]; + static const char _applyToAll[]; + static const char _pcf8574[]; + static const char _pcfAddress[]; + + /** + * readPin() - read rotary encoder pin value + */ + byte readPin(uint8_t pin); + + /** + * Sort the modes and palettes to the index arrays + * modes_alpha_indexes and palettes_alpha_indexes. + */ + void sortModesAndPalettes(); + byte *re_initIndexArray(int numModes); + + /** + * Return an array of mode or palette names from the JSON string. + * They don't end in '\0', they end in '"'. + */ + const char **re_findModeStrings(const char json[], int numModes); + + /** + * Sort either the modes or the palettes using quicksort. + */ + void re_sortModes(const char **modeNames, byte *indexes, int count, int numSkip); + + public: + + RotaryEncoderUIUsermod() + : fadeAmount(5) + , buttonPressedTime(0) + , buttonWaitTime(0) + , buttonPressedBefore(false) + , buttonLongPressed(false) + , pinA(ENCODER_DT_PIN) + , pinB(ENCODER_CLK_PIN) + , pinC(ENCODER_SW_PIN) + , select_state(0) + , currentHue1(16) + , currentSat1(255) + , currentCCT(128) + , display(nullptr) + , modes_qstrings(nullptr) + , modes_alpha_indexes(nullptr) + , palettes_qstrings(nullptr) + , palettes_alpha_indexes(nullptr) + , currentEffectAndPaletteInitialized(false) + , effectCurrentIndex(0) + , effectPaletteIndex(0) + , knownMode(0) + , knownPalette(0) + , presetHigh(0) + , presetLow(0) + , applyToAll(true) + , initDone(false) + , enabled(true) + , usePcf8574(USE_PCF8574) + , addrPcf8574(PCF8574_ADDRESS) + {} + + /* + * 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_ROTARY_ENC_UI; } + /** + * Enable/Disable the usermod + */ + inline void enable(bool enable) { enabled = enable; } + + /** + * Get usermod enabled/disabled state + */ + inline bool isEnabled() { return enabled; } + + /** + * 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(); + + /** + * connected() is called every time the WiFi is (re)connected + * Use it to initialize network interfaces + */ + //void connected(); + + /** + * loop() is called continuously. Here you can check for events, read sensors, etc. + */ + void loop(); + +#ifndef WLED_DISABLE_MQTT + //bool onMqttMessage(char* topic, char* payload); + //void onMqttConnect(bool sessionPresent); #endif - // Pointers the start of the mode names within JSON_mode_names - const char **modes_qstrings = nullptr; + /** + * 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); - // Array of mode indexes in alphabetical order. - byte *modes_alpha_indexes = nullptr; + /** + * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. + */ + //void addToJsonInfo(JsonObject &root); - // Pointers the start of the palette names within JSON_palette_names - const char **palettes_qstrings = nullptr; + /** + * 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); - // Array of palette indexes in alphabetical order. - byte *palettes_alpha_indexes = nullptr; + /** + * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ + //void readFromJsonState(JsonObject &root); - unsigned char Enc_A; - unsigned char Enc_B; - unsigned char Enc_A_prev = 0; + /** + * provide the changeable values + */ + void addToConfig(JsonObject &root); - bool currentEffectAndPaletteInitialized = false; - uint8_t effectCurrentIndex = 0; - uint8_t effectPaletteIndex = 0; - uint8_t knownMode = 0; - uint8_t knownPalette = 0; + //void appendConfigData(); - uint8_t currentCCT = 128; + /** + * restore the changeable values + * 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); - byte presetHigh = 0; - byte presetLow = 0; + // custom methods + void displayNetworkInfo(); + void findCurrentEffectAndPalette(); + bool changeState(const char *stateName, byte markedLine, byte markedCol, byte glyph); + void lampUdated(); + void changeBrightness(bool increase); + void changeEffect(bool increase); + void changeEffectSpeed(bool increase); + void changeEffectIntensity(bool increase); + void changeCustom(uint8_t par, bool increase); + void changePalette(bool increase); + void changeHue(bool increase); + void changeSat(bool increase); + void changePreset(bool increase); + void changeCCT(bool increase); +}; - bool applyToAll = true; - bool initDone = false; - bool enabled = true; - - // strings to reduce flash memory usage (used more than twice) - static const char _name[]; - static const char _enabled[]; - static const char _DT_pin[]; - static const char _CLK_pin[]; - static const char _SW_pin[]; - static const char _presetHigh[]; - static const char _presetLow[]; - static const char _applyToAll[]; - - /** - * Sort the modes and palettes to the index arrays - * modes_alpha_indexes and palettes_alpha_indexes. - */ - void sortModesAndPalettes() { - //modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount()); - modes_qstrings = strip.getModeDataSrc(); - modes_alpha_indexes = re_initIndexArray(strip.getModeCount()); - re_sortModes(modes_qstrings, modes_alpha_indexes, strip.getModeCount(), MODE_SORT_SKIP_COUNT); - - palettes_qstrings = re_findModeStrings(JSON_palette_names, strip.getPaletteCount()); - palettes_alpha_indexes = re_initIndexArray(strip.getPaletteCount()); // only use internal palettes - - // How many palette names start with '*' and should not be sorted? - // (Also skipping the first one, 'Default'). - int skipPaletteCount = 1; - while (pgm_read_byte_near(palettes_qstrings[skipPaletteCount++]) == '*') ; - re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount(), skipPaletteCount); - } - - byte *re_initIndexArray(int numModes) { - byte *indexes = (byte *)malloc(sizeof(byte) * numModes); - for (byte i = 0; i < numModes; i++) { - indexes[i] = i; - } - return indexes; - } - - /** - * Return an array of mode or palette names from the JSON string. - * They don't end in '\0', they end in '"'. - */ - const char **re_findModeStrings(const char json[], int numModes) { - const char **modeStrings = (const char **)malloc(sizeof(const char *) * numModes); - uint8_t modeIndex = 0; - bool insideQuotes = false; - // advance past the mark for markLineNum that may exist. - char singleJsonSymbol; - - // Find the mode name in JSON - bool complete = false; - for (size_t i = 0; i < strlen_P(json); i++) { - singleJsonSymbol = pgm_read_byte_near(json + i); - if (singleJsonSymbol == '\0') break; - switch (singleJsonSymbol) { - case '"': - insideQuotes = !insideQuotes; - if (insideQuotes) { - // We have a new mode or palette - modeStrings[modeIndex] = (char *)(json + i + 1); - } - break; - case '[': - break; - case ']': - if (!insideQuotes) complete = true; - break; - case ',': - if (!insideQuotes) modeIndex++; - default: - if (!insideQuotes) break; +/** + * readPin() - read rotary encoder pin value + */ +byte RotaryEncoderUIUsermod::readPin(uint8_t pin) { + if (usePcf8574) { + byte _data = 0; + if (pin < 8) { + Wire.requestFrom(addrPcf8574, 1U); + if (Wire.available()) { + _data = Wire.read(); + _data = (_data>>pin) & 1; } - if (complete) break; } - return modeStrings; + return _data; + } else { + return digitalRead(pin); } +} - /** - * Sort either the modes or the palettes using quicksort. - */ - void re_sortModes(const char **modeNames, byte *indexes, int count, int numSkip) { - if (!modeNames) return; - listBeingSorted = modeNames; - qsort(indexes + numSkip, count - numSkip, sizeof(byte), re_qstringCmp); - listBeingSorted = nullptr; +/** + * Sort the modes and palettes to the index arrays + * modes_alpha_indexes and palettes_alpha_indexes. + */ +void RotaryEncoderUIUsermod::sortModesAndPalettes() { + //modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount()); + modes_qstrings = strip.getModeDataSrc(); + modes_alpha_indexes = re_initIndexArray(strip.getModeCount()); + re_sortModes(modes_qstrings, modes_alpha_indexes, strip.getModeCount(), MODE_SORT_SKIP_COUNT); + + palettes_qstrings = re_findModeStrings(JSON_palette_names, strip.getPaletteCount()); + palettes_alpha_indexes = re_initIndexArray(strip.getPaletteCount()); // only use internal palettes + + // How many palette names start with '*' and should not be sorted? + // (Also skipping the first one, 'Default'). + int skipPaletteCount = 1; + while (pgm_read_byte_near(palettes_qstrings[skipPaletteCount++]) == '*') ; + re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount(), skipPaletteCount); +} + +byte *RotaryEncoderUIUsermod::re_initIndexArray(int numModes) { + byte *indexes = (byte *)malloc(sizeof(byte) * numModes); + for (byte i = 0; i < numModes; i++) { + indexes[i] = i; } + return indexes; +} + +/** + * Return an array of mode or palette names from the JSON string. + * They don't end in '\0', they end in '"'. + */ +const char **RotaryEncoderUIUsermod::re_findModeStrings(const char json[], int numModes) { + const char **modeStrings = (const char **)malloc(sizeof(const char *) * numModes); + uint8_t modeIndex = 0; + bool insideQuotes = false; + // advance past the mark for markLineNum that may exist. + char singleJsonSymbol; + + // Find the mode name in JSON + bool complete = false; + for (size_t i = 0; i < strlen_P(json); i++) { + singleJsonSymbol = pgm_read_byte_near(json + i); + if (singleJsonSymbol == '\0') break; + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + if (insideQuotes) { + // We have a new mode or palette + modeStrings[modeIndex] = (char *)(json + i + 1); + } + break; + case '[': + break; + case ']': + if (!insideQuotes) complete = true; + break; + case ',': + if (!insideQuotes) modeIndex++; + default: + if (!insideQuotes) break; + } + if (complete) break; + } + return modeStrings; +} + +/** + * Sort either the modes or the palettes using quicksort. + */ +void RotaryEncoderUIUsermod::re_sortModes(const char **modeNames, byte *indexes, int count, int numSkip) { + if (!modeNames) return; + listBeingSorted = modeNames; + qsort(indexes + numSkip, count - numSkip, sizeof(byte), re_qstringCmp); + listBeingSorted = nullptr; +} -public: - /* - * 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() - { - DEBUG_PRINTLN(F("Usermod Rotary Encoder init.")); +// public methods + + +/* + * 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 RotaryEncoderUIUsermod::setup() +{ + DEBUG_PRINTLN(F("Usermod Rotary Encoder init.")); + + if (!usePcf8574) { PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } }; if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) { // BUG: configuring this usermod with conflicting pins @@ -284,638 +478,637 @@ public: pinMode(pinA, USERMOD_ROTARY_ENCODER_GPIO); pinMode(pinB, USERMOD_ROTARY_ENCODER_GPIO); pinMode(pinC, USERMOD_ROTARY_ENCODER_GPIO); + } else { + if ((i2c_sda == i2c_scl && i2c_sda == -1) || pinA<0 || pinB<0 || pinC<0) { + enabled = false; + return; + } + } - loopTime = millis(); + loopTime = millis(); - currentCCT = (approximateKelvinFromRGB(RGBW32(col[0], col[1], col[2], col[3])) - 1900) >> 5; + currentCCT = (approximateKelvinFromRGB(RGBW32(col[0], col[1], col[2], col[3])) - 1900) >> 5; - if (!initDone) sortModesAndPalettes(); + if (!initDone) sortModesAndPalettes(); #ifdef USERMOD_FOUR_LINE_DISPLAY - // This Usermod uses FourLineDisplayUsermod for the best experience. - // But it's optional. But you want it. - display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); - if (display != nullptr) { - display->setMarkLine(1, 0); - } + // This Usermod uses FourLineDisplayUsermod for the best experience. + // But it's optional. But you want it. + display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); + if (display != nullptr) { + display->setMarkLine(1, 0); + } #endif - initDone = true; - Enc_A = digitalRead(pinA); // Read encoder pins - Enc_B = digitalRead(pinB); - Enc_A_prev = Enc_A; + initDone = true; + Enc_A = readPin(pinA); // Read encoder pins + Enc_B = readPin(pinB); + Enc_A_prev = Enc_A; +} + +/* + * 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 RotaryEncoderUIUsermod::loop() +{ + if (!enabled || strip.isUpdating()) return; + unsigned long currentTime = millis(); // get the current elapsed time + + // Initialize effectCurrentIndex and effectPaletteIndex to + // current state. We do it here as (at least) effectCurrent + // is not yet initialized when setup is called. + + if (!currentEffectAndPaletteInitialized) { + findCurrentEffectAndPalette(); } - /* - * connected() is called every time the WiFi is (re)connected - * Use it to initialize network interfaces - */ - void connected() + if (modes_alpha_indexes[effectCurrentIndex] != effectCurrent || palettes_alpha_indexes[effectPaletteIndex] != effectPalette) { + currentEffectAndPaletteInitialized = false; + } + + if (currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz { - //Serial.println("Connected to WiFi!"); + loopTime = currentTime; // Updates loopTime + + bool buttonPressed = !readPin(pinC); //0=pressed, 1=released + if (buttonPressed) { + if (!buttonPressedBefore) buttonPressedTime = currentTime; + buttonPressedBefore = true; + if (currentTime-buttonPressedTime > 3000) { + if (!buttonLongPressed) displayNetworkInfo(); //long press for network info + buttonLongPressed = true; + } + } else if (!buttonPressed && buttonPressedBefore) { + bool doublePress = buttonWaitTime; + buttonWaitTime = 0; + if (!buttonLongPressed) { + if (doublePress) { + toggleOnOff(); + lampUdated(); + } else { + buttonWaitTime = currentTime; + } + } + buttonLongPressed = false; + buttonPressedBefore = false; + } + if (buttonWaitTime && currentTime-buttonWaitTime>350 && !buttonPressedBefore) { //same speed as in button.cpp + buttonWaitTime = 0; + char newState = select_state + 1; + bool changedState = false; + char lineBuffer[64]; + do { + // finde new state + switch (newState) { + case 0: strcpy_P(lineBuffer, PSTR("Brightness")); changedState = true; break; + case 1: if (!extractModeSlider(effectCurrent, 0, lineBuffer, 63)) newState++; else changedState = true; break; // speed + case 2: if (!extractModeSlider(effectCurrent, 1, lineBuffer, 63)) newState++; else changedState = true; break; // intensity + case 3: strcpy_P(lineBuffer, PSTR("Color Palette")); changedState = true; break; + case 4: strcpy_P(lineBuffer, PSTR("Effect")); changedState = true; break; + case 5: strcpy_P(lineBuffer, PSTR("Main Color")); changedState = true; break; + case 6: strcpy_P(lineBuffer, PSTR("Saturation")); changedState = true; break; + case 7: + if (!(strip.getSegment(applyToAll ? strip.getFirstSelectedSegId() : strip.getMainSegmentId()).getLightCapabilities() & 0x04)) newState++; + else { strcpy_P(lineBuffer, PSTR("CCT")); changedState = true; } + break; + case 8: if (presetHigh==0 || presetLow == 0) newState++; else { strcpy_P(lineBuffer, PSTR("Preset")); changedState = true; } break; + case 9: + case 10: + case 11: if (!extractModeSlider(effectCurrent, newState-7, lineBuffer, 63)) newState++; else changedState = true; break; // custom + } + if (newState > LAST_UI_STATE) newState = 0; + } while (!changedState); + if (display != nullptr) { + switch (newState) { + case 0: changedState = changeState(lineBuffer, 1, 0, 1); break; //1 = sun + case 1: changedState = changeState(lineBuffer, 1, 4, 2); break; //2 = skip forward + case 2: changedState = changeState(lineBuffer, 1, 8, 3); break; //3 = fire + case 3: changedState = changeState(lineBuffer, 2, 0, 4); break; //4 = custom palette + case 4: changedState = changeState(lineBuffer, 3, 0, 5); break; //5 = puzzle piece + case 5: changedState = changeState(lineBuffer, 255, 255, 7); break; //7 = brush + case 6: changedState = changeState(lineBuffer, 255, 255, 8); break; //8 = contrast + case 7: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star + case 8: changedState = changeState(lineBuffer, 255, 255, 11); break; //11 = heart + case 9: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star + case 10: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star + case 11: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star + } + } + if (changedState) select_state = newState; + } + + Enc_A = readPin(pinA); // Read encoder pins + Enc_B = readPin(pinB); + if ((Enc_A) && (!Enc_A_prev)) + { // A has gone from high to low + if (Enc_B == LOW) //changes to LOW so that then encoder registers a change at the very end of a pulse + { // B is high so clockwise + switch(select_state) { + case 0: changeBrightness(true); break; + case 1: changeEffectSpeed(true); break; + case 2: changeEffectIntensity(true); break; + case 3: changePalette(true); break; + case 4: changeEffect(true); break; + case 5: changeHue(true); break; + case 6: changeSat(true); break; + case 7: changeCCT(true); break; + case 8: changePreset(true); break; + case 9: changeCustom(1,true); break; + case 10: changeCustom(2,true); break; + case 11: changeCustom(3,true); break; + } + } + else if (Enc_B == HIGH) + { // B is low so counter-clockwise + switch(select_state) { + case 0: changeBrightness(false); break; + case 1: changeEffectSpeed(false); break; + case 2: changeEffectIntensity(false); break; + case 3: changePalette(false); break; + case 4: changeEffect(false); break; + case 5: changeHue(false); break; + case 6: changeSat(false); break; + case 7: changeCCT(false); break; + case 8: changePreset(false); break; + case 9: changeCustom(1,false); break; + case 10: changeCustom(2,false); break; + case 11: changeCustom(3,false); break; + } + } + } + Enc_A_prev = Enc_A; // Store value of A for next time } +} - /* - * 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 || strip.isUpdating()) return; - unsigned long currentTime = millis(); // get the current elapsed time - - // Initialize effectCurrentIndex and effectPaletteIndex to - // current state. We do it here as (at least) effectCurrent - // is not yet initialized when setup is called. - - if (!currentEffectAndPaletteInitialized) { - findCurrentEffectAndPalette(); - } - - if (modes_alpha_indexes[effectCurrentIndex] != effectCurrent || palettes_alpha_indexes[effectPaletteIndex] != effectPalette) { - currentEffectAndPaletteInitialized = false; - } - - if (currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz - { - loopTime = currentTime; // Updates loopTime - - bool buttonPressed = !digitalRead(pinC); //0=pressed, 1=released - if (buttonPressed) { - if (!buttonPressedBefore) buttonPressedTime = currentTime; - buttonPressedBefore = true; - if (currentTime-buttonPressedTime > 3000) { - if (!buttonLongPressed) displayNetworkInfo(); //long press for network info - buttonLongPressed = true; - } - } else if (!buttonPressed && buttonPressedBefore) { - bool doublePress = buttonWaitTime; - buttonWaitTime = 0; - if (!buttonLongPressed) { - if (doublePress) { - toggleOnOff(); - lampUdated(); - } else { - buttonWaitTime = currentTime; - } - } - buttonLongPressed = false; - buttonPressedBefore = false; - } - if (buttonWaitTime && currentTime-buttonWaitTime>350 && !buttonPressedBefore) { //same speed as in button.cpp - buttonWaitTime = 0; - char newState = select_state + 1; - bool changedState = false; - char lineBuffer[64]; - do { - // finde new state - switch (newState) { - case 0: strcpy_P(lineBuffer, PSTR("Brightness")); changedState = true; break; - case 1: if (!extractModeSlider(effectCurrent, 0, lineBuffer, 63)) newState++; else changedState = true; break; // speed - case 2: if (!extractModeSlider(effectCurrent, 1, lineBuffer, 63)) newState++; else changedState = true; break; // intensity - case 3: strcpy_P(lineBuffer, PSTR("Color Palette")); changedState = true; break; - case 4: strcpy_P(lineBuffer, PSTR("Effect")); changedState = true; break; - case 5: strcpy_P(lineBuffer, PSTR("Main Color")); changedState = true; break; - case 6: strcpy_P(lineBuffer, PSTR("Saturation")); changedState = true; break; - case 7: - if (!(strip.getSegment(applyToAll ? strip.getFirstSelectedSegId() : strip.getMainSegmentId()).getLightCapabilities() & 0x04)) newState++; - else { strcpy_P(lineBuffer, PSTR("CCT")); changedState = true; } - break; - case 8: if (presetHigh==0 || presetLow == 0) newState++; else { strcpy_P(lineBuffer, PSTR("Preset")); changedState = true; } break; - case 9: - case 10: - case 11: if (!extractModeSlider(effectCurrent, newState-7, lineBuffer, 63)) newState++; else changedState = true; break; // custom - } - if (newState > LAST_UI_STATE) newState = 0; - } while (!changedState); - if (display != nullptr) { - switch (newState) { - case 0: changedState = changeState(lineBuffer, 1, 0, 1); break; //1 = sun - case 1: changedState = changeState(lineBuffer, 1, 4, 2); break; //2 = skip forward - case 2: changedState = changeState(lineBuffer, 1, 8, 3); break; //3 = fire - case 3: changedState = changeState(lineBuffer, 2, 0, 4); break; //4 = custom palette - case 4: changedState = changeState(lineBuffer, 3, 0, 5); break; //5 = puzzle piece - case 5: changedState = changeState(lineBuffer, 255, 255, 7); break; //7 = brush - case 6: changedState = changeState(lineBuffer, 255, 255, 8); break; //8 = contrast - case 7: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star - case 8: changedState = changeState(lineBuffer, 255, 255, 11); break; //11 = heart - case 9: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star - case 10: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star - case 11: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star - } - } - if (changedState) select_state = newState; - } - - Enc_A = digitalRead(pinA); // Read encoder pins - Enc_B = digitalRead(pinB); - if ((Enc_A) && (!Enc_A_prev)) - { // A has gone from high to low - if (Enc_B == LOW) //changes to LOW so that then encoder registers a change at the very end of a pulse - { // B is high so clockwise - switch(select_state) { - case 0: changeBrightness(true); break; - case 1: changeEffectSpeed(true); break; - case 2: changeEffectIntensity(true); break; - case 3: changePalette(true); break; - case 4: changeEffect(true); break; - case 5: changeHue(true); break; - case 6: changeSat(true); break; - case 7: changeCCT(true); break; - case 8: changePreset(true); break; - case 9: changeCustom(1,true); break; - case 10: changeCustom(2,true); break; - case 11: changeCustom(3,true); break; - } - } - else if (Enc_B == HIGH) - { // B is low so counter-clockwise - switch(select_state) { - case 0: changeBrightness(false); break; - case 1: changeEffectSpeed(false); break; - case 2: changeEffectIntensity(false); break; - case 3: changePalette(false); break; - case 4: changeEffect(false); break; - case 5: changeHue(false); break; - case 6: changeSat(false); break; - case 7: changeCCT(false); break; - case 8: changePreset(false); break; - case 9: changeCustom(1,false); break; - case 10: changeCustom(2,false); break; - case 11: changeCustom(3,false); break; - } - } - } - Enc_A_prev = Enc_A; // Store value of A for next time - } - } - - void displayNetworkInfo() { - #ifdef USERMOD_FOUR_LINE_DISPLAY - display->networkOverlay(PSTR("NETWORK INFO"), 10000); - #endif - } - - void findCurrentEffectAndPalette() { - currentEffectAndPaletteInitialized = true; - for (uint8_t i = 0; i < strip.getModeCount(); i++) { - if (modes_alpha_indexes[i] == effectCurrent) { - effectCurrentIndex = i; - break; - } - } - - for (uint8_t i = 0; i < strip.getPaletteCount(); i++) { - if (palettes_alpha_indexes[i] == effectPalette) { - effectPaletteIndex = i; - break; - } - } - } - - boolean changeState(const char *stateName, byte markedLine, byte markedCol, byte glyph) { +void RotaryEncoderUIUsermod::displayNetworkInfo() { #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display != nullptr) { - if (display->wakeDisplay()) { - // Throw away wake up input - display->redraw(true); - return false; - } - display->overlay(stateName, 750, glyph); - display->setMarkLine(markedLine, markedCol); - } + display->networkOverlay(PSTR("NETWORK INFO"), 10000); #endif - return true; +} + +void RotaryEncoderUIUsermod::findCurrentEffectAndPalette() { + currentEffectAndPaletteInitialized = true; + for (uint8_t i = 0; i < strip.getModeCount(); i++) { + if (modes_alpha_indexes[i] == effectCurrent) { + effectCurrentIndex = i; + break; + } } - void lampUdated() { - //call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification) - // 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa - //setValuesFromFirstSelectedSeg(); //to make transition work on main segment (should no longer be required) - stateUpdated(CALL_MODE_BUTTON); - updateInterfaces(CALL_MODE_BUTTON); + for (uint8_t i = 0; i < strip.getPaletteCount(); i++) { + if (palettes_alpha_indexes[i] == effectPalette) { + effectPaletteIndex = i; + break; + } } +} - void changeBrightness(bool increase) { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - display->redraw(true); +bool RotaryEncoderUIUsermod::changeState(const char *stateName, byte markedLine, byte markedCol, byte glyph) { +#ifdef USERMOD_FOUR_LINE_DISPLAY + if (display != nullptr) { + if (display->wakeDisplay()) { // Throw away wake up input - return; - } - display->updateRedrawTime(); - #endif - bri = max(min((increase ? bri+fadeAmount : bri-fadeAmount), 255), 0); - lampUdated(); - #ifdef USERMOD_FOUR_LINE_DISPLAY - display->updateBrightness(); - #endif - } - - - void changeEffect(bool increase) { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { display->redraw(true); - // Throw away wake up input - return; + return false; } - display->updateRedrawTime(); - #endif - effectCurrentIndex = max(min((increase ? effectCurrentIndex+1 : effectCurrentIndex-1), strip.getModeCount()-1), 0); - effectCurrent = modes_alpha_indexes[effectCurrentIndex]; - stateChanged = true; - if (applyToAll) { - for (byte i=0; ioverlay(stateName, 750, glyph); + display->setMarkLine(markedLine, markedCol); + } +#endif + return true; +} + +void RotaryEncoderUIUsermod::lampUdated() { + //call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification) + // 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa + //setValuesFromFirstSelectedSeg(); //to make transition work on main segment (should no longer be required) + stateUpdated(CALL_MODE_BUTTON); + updateInterfaces(CALL_MODE_BUTTON); +} + +void RotaryEncoderUIUsermod::changeBrightness(bool increase) { +#ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; + } + display->updateRedrawTime(); +#endif + bri = max(min((increase ? bri+fadeAmount : bri-fadeAmount), 255), 0); + lampUdated(); +#ifdef USERMOD_FOUR_LINE_DISPLAY + display->updateBrightness(); +#endif +} + + +void RotaryEncoderUIUsermod::changeEffect(bool increase) { +#ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; + } + display->updateRedrawTime(); +#endif + effectCurrentIndex = max(min((increase ? effectCurrentIndex+1 : effectCurrentIndex-1), strip.getModeCount()-1), 0); + effectCurrent = modes_alpha_indexes[effectCurrentIndex]; + stateChanged = true; + if (applyToAll) { + for (byte i=0; ishowCurrentEffectOrPalette(effectCurrent, JSON_mode_names, 3); - #endif + } else { + Segment& seg = strip.getSegment(strip.getMainSegmentId()); + seg.setMode(effectCurrent); } + lampUdated(); +#ifdef USERMOD_FOUR_LINE_DISPLAY + display->showCurrentEffectOrPalette(effectCurrent, JSON_mode_names, 3); +#endif +} - void changeEffectSpeed(bool increase) { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - display->redraw(true); - // Throw away wake up input - return; - } - display->updateRedrawTime(); - #endif - effectSpeed = max(min((increase ? effectSpeed+fadeAmount : effectSpeed-fadeAmount), 255), 0); - stateChanged = true; - if (applyToAll) { - for (byte i=0; iwakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; + } + display->updateRedrawTime(); +#endif + effectSpeed = max(min((increase ? effectSpeed+fadeAmount : effectSpeed-fadeAmount), 255), 0); + stateChanged = true; + if (applyToAll) { + for (byte i=0; iupdateSpeed(); - #endif + } else { + Segment& seg = strip.getSegment(strip.getMainSegmentId()); + seg.speed = effectSpeed; } + lampUdated(); +#ifdef USERMOD_FOUR_LINE_DISPLAY + display->updateSpeed(); +#endif +} - void changeEffectIntensity(bool increase) { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - display->redraw(true); - // Throw away wake up input - return; - } - display->updateRedrawTime(); - #endif - effectIntensity = max(min((increase ? effectIntensity+fadeAmount : effectIntensity-fadeAmount), 255), 0); - stateChanged = true; - if (applyToAll) { - for (byte i=0; iwakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; + } + display->updateRedrawTime(); +#endif + effectIntensity = max(min((increase ? effectIntensity+fadeAmount : effectIntensity-fadeAmount), 255), 0); + stateChanged = true; + if (applyToAll) { + for (byte i=0; iupdateIntensity(); - #endif + } else { + Segment& seg = strip.getSegment(strip.getMainSegmentId()); + seg.intensity = effectIntensity; } + lampUdated(); +#ifdef USERMOD_FOUR_LINE_DISPLAY + display->updateIntensity(); +#endif +} - void changeCustom(uint8_t par, bool increase) { - uint8_t val = 0; - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - display->redraw(true); - // Throw away wake up input - return; - } - display->updateRedrawTime(); - #endif - stateChanged = true; - if (applyToAll) { - uint8_t id = strip.getFirstSelectedSegId(); - Segment& sid = strip.getSegment(id); - switch (par) { - case 3: val = sid.custom3 = max(min((increase ? sid.custom3+fadeAmount : sid.custom3-fadeAmount), 255), 0); break; - case 2: val = sid.custom2 = max(min((increase ? sid.custom2+fadeAmount : sid.custom2-fadeAmount), 255), 0); break; - default: val = sid.custom1 = max(min((increase ? sid.custom1+fadeAmount : sid.custom1-fadeAmount), 255), 0); break; - } - for (byte i=0; ioverlay(lineBuffer, 500, 10); // use star - #endif +void RotaryEncoderUIUsermod::changeCustom(uint8_t par, bool increase) { + uint8_t val = 0; +#ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; } - - - void changePalette(bool increase) { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - display->redraw(true); - // Throw away wake up input - return; + display->updateRedrawTime(); +#endif + stateChanged = true; + if (applyToAll) { + uint8_t id = strip.getFirstSelectedSegId(); + Segment& sid = strip.getSegment(id); + switch (par) { + case 3: val = sid.custom3 = max(min((increase ? sid.custom3+fadeAmount : sid.custom3-fadeAmount), 255), 0); break; + case 2: val = sid.custom2 = max(min((increase ? sid.custom2+fadeAmount : sid.custom2-fadeAmount), 255), 0); break; + default: val = sid.custom1 = max(min((increase ? sid.custom1+fadeAmount : sid.custom1-fadeAmount), 255), 0); break; } - display->updateRedrawTime(); - #endif - effectPaletteIndex = max(min((increase ? effectPaletteIndex+1 : effectPaletteIndex-1), strip.getPaletteCount()-1), 0); - effectPalette = palettes_alpha_indexes[effectPaletteIndex]; - stateChanged = true; - if (applyToAll) { - for (byte i=0; ioverlay(lineBuffer, 500, 10); // use star +#endif +} + + +void RotaryEncoderUIUsermod::changePalette(bool increase) { +#ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; + } + display->updateRedrawTime(); +#endif + effectPaletteIndex = max(min((increase ? effectPaletteIndex+1 : effectPaletteIndex-1), strip.getPaletteCount()-1), 0); + effectPalette = palettes_alpha_indexes[effectPaletteIndex]; + stateChanged = true; + if (applyToAll) { + for (byte i=0; ishowCurrentEffectOrPalette(effectPalette, JSON_palette_names, 2); - #endif + } else { + Segment& seg = strip.getSegment(strip.getMainSegmentId()); + seg.setPalette(effectPalette); } + lampUdated(); +#ifdef USERMOD_FOUR_LINE_DISPLAY + display->showCurrentEffectOrPalette(effectPalette, JSON_palette_names, 2); +#endif +} - void changeHue(bool increase){ - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - display->redraw(true); - // Throw away wake up input - return; - } - display->updateRedrawTime(); - #endif - currentHue1 = max(min((increase ? currentHue1+fadeAmount : currentHue1-fadeAmount), 255), 0); - colorHStoRGB(currentHue1*256, currentSat1, col); - stateChanged = true; - if (applyToAll) { - for (byte i=0; iwakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; + } + display->updateRedrawTime(); +#endif + currentHue1 = max(min((increase ? currentHue1+fadeAmount : currentHue1-fadeAmount), 255), 0); + colorHStoRGB(currentHue1*256, currentSat1, col); + stateChanged = true; + if (applyToAll) { + for (byte i=0; ioverlay(lineBuffer, 500, 7); // use brush - #endif + } else { + Segment& seg = strip.getSegment(strip.getMainSegmentId()); + seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]); } + lampUdated(); +#ifdef USERMOD_FOUR_LINE_DISPLAY + char lineBuffer[64]; + sprintf(lineBuffer, "%d", currentHue1); + display->overlay(lineBuffer, 500, 7); // use brush +#endif +} - void changeSat(bool increase){ - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - display->redraw(true); - // Throw away wake up input - return; - } - display->updateRedrawTime(); - #endif - currentSat1 = max(min((increase ? currentSat1+fadeAmount : currentSat1-fadeAmount), 255), 0); - colorHStoRGB(currentHue1*256, currentSat1, col); - if (applyToAll) { - for (byte i=0; iwakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; + } + display->updateRedrawTime(); +#endif + currentSat1 = max(min((increase ? currentSat1+fadeAmount : currentSat1-fadeAmount), 255), 0); + colorHStoRGB(currentHue1*256, currentSat1, col); + if (applyToAll) { + for (byte i=0; ioverlay(lineBuffer, 500, 8); // use contrast - #endif + } else { + Segment& seg = strip.getSegment(strip.getMainSegmentId()); + seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]); } + lampUdated(); +#ifdef USERMOD_FOUR_LINE_DISPLAY + char lineBuffer[64]; + sprintf(lineBuffer, "%d", currentSat1); + display->overlay(lineBuffer, 500, 8); // use contrast +#endif +} - void changePreset(bool increase) { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - display->redraw(true); - // Throw away wake up input - return; - } - display->updateRedrawTime(); - #endif - if (presetHigh && presetLow && presetHigh > presetLow) { - StaticJsonDocument<64> root; - char str[64]; - sprintf_P(str, PSTR("%d~%d~%s"), presetLow, presetHigh, increase?"":"-"); - root["ps"] = str; - deserializeState(root.as(), CALL_MODE_BUTTON_PRESET); +void RotaryEncoderUIUsermod::changePreset(bool increase) { +#ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; + } + display->updateRedrawTime(); +#endif + if (presetHigh && presetLow && presetHigh > presetLow) { + StaticJsonDocument<64> root; + char str[64]; + sprintf_P(str, PSTR("%d~%d~%s"), presetLow, presetHigh, increase?"":"-"); + root["ps"] = str; + deserializeState(root.as(), CALL_MODE_BUTTON_PRESET); /* - String apireq = F("win&PL=~"); - if (!increase) apireq += '-'; - apireq += F("&P1="); - apireq += presetLow; - apireq += F("&P2="); - apireq += presetHigh; - handleSet(nullptr, apireq, false); + String apireq = F("win&PL=~"); + if (!increase) apireq += '-'; + apireq += F("&P1="); + apireq += presetLow; + apireq += F("&P2="); + apireq += presetHigh; + handleSet(nullptr, apireq, false); */ - lampUdated(); - #ifdef USERMOD_FOUR_LINE_DISPLAY - sprintf(str, "%d", currentPreset); - display->overlay(str, 500, 11); // use heart - #endif - } - } - - void changeCCT(bool increase){ + lampUdated(); #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display && display->wakeDisplay()) { - display->redraw(true); - // Throw away wake up input - return; - } - display->updateRedrawTime(); + sprintf(str, "%d", currentPreset); + display->overlay(str, 500, 11); // use heart #endif - currentCCT = max(min((increase ? currentCCT+fadeAmount : currentCCT-fadeAmount), 255), 0); + } +} + +void RotaryEncoderUIUsermod::changeCCT(bool increase){ +#ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; + } + display->updateRedrawTime(); +#endif + currentCCT = max(min((increase ? currentCCT+fadeAmount : currentCCT-fadeAmount), 255), 0); // if (applyToAll) { - for (byte i=0; ioverlay(lineBuffer, 500, 10); // use star - #endif - } + lampUdated(); +#ifdef USERMOD_FOUR_LINE_DISPLAY + char lineBuffer[64]; + sprintf(lineBuffer, "%d", currentCCT); + display->overlay(lineBuffer, 500, 10); // use star +#endif +} - /* - * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. - * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. - * Below it is shown how this could be used for e.g. a light sensor - */ - /* - void addToJsonInfo(JsonObject& root) - { - int reading = 20; - //this code adds "u":{"Light":[20," lux"]} to the info object - JsonObject user = root["u"]; - if (user.isNull()) user = root.createNestedObject("u"); - JsonArray lightArr = user.createNestedArray("Light"); //name - lightArr.add(reading); //value - lightArr.add(" lux"); //unit - } +/* + * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. + * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. + * Below it is shown how this could be used for e.g. a light sensor */ +/* +void RotaryEncoderUIUsermod::addToJsonInfo(JsonObject& root) +{ + int reading = 20; + //this code adds "u":{"Light":[20," lux"]} to the info object + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + JsonArray lightArr = user.createNestedArray("Light"); //name + lightArr.add(reading); //value + lightArr.add(" lux"); //unit +} +*/ - /* - * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ - /* - void addToJsonState(JsonObject &root) - { - //root["user0"] = userVar0; - } +/* + * 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 RotaryEncoderUIUsermod::addToJsonState(JsonObject &root) +{ + //root["user0"] = userVar0; +} +*/ - /* - * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). - * Values in the state object may be modified by connected clients - */ - /* - void readFromJsonState(JsonObject &root) - { - //userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value - //if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!")); - } +/* + * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients */ +/* +void RotaryEncoderUIUsermod::readFromJsonState(JsonObject &root) +{ + //userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value + //if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!")); +} +*/ - /** - * addToConfig() (called from set.cpp) stores persistent properties to cfg.json - */ - void addToConfig(JsonObject &root) { - // we add JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}} - JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname - top[FPSTR(_enabled)] = enabled; - top[FPSTR(_DT_pin)] = pinA; - top[FPSTR(_CLK_pin)] = pinB; - top[FPSTR(_SW_pin)] = pinC; - top[FPSTR(_presetLow)] = presetLow; - top[FPSTR(_presetHigh)] = presetHigh; - top[FPSTR(_applyToAll)] = applyToAll; - DEBUG_PRINTLN(F("Rotary Encoder config saved.")); - } +/** + * addToConfig() (called from set.cpp) stores persistent properties to cfg.json + */ +void RotaryEncoderUIUsermod::addToConfig(JsonObject &root) { + // we add JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}} + JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname + top[FPSTR(_enabled)] = enabled; + top[FPSTR(_DT_pin)] = pinA; + top[FPSTR(_CLK_pin)] = pinB; + top[FPSTR(_SW_pin)] = pinC; + top[FPSTR(_presetLow)] = presetLow; + top[FPSTR(_presetHigh)] = presetHigh; + top[FPSTR(_applyToAll)] = applyToAll; + top[FPSTR(_pcf8574)] = usePcf8574; + top[FPSTR(_pcfAddress)] = addrPcf8574; + DEBUG_PRINTLN(F("Rotary Encoder config saved.")); +} - /** - * 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) { - // we look for JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}} - JsonObject top = root[FPSTR(_name)]; - if (top.isNull()) { - DEBUG_PRINT(FPSTR(_name)); - DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); - return false; - } - int8_t newDTpin = top[FPSTR(_DT_pin)] | pinA; - int8_t newCLKpin = top[FPSTR(_CLK_pin)] | pinB; - int8_t newSWpin = top[FPSTR(_SW_pin)] | pinC; - - presetHigh = top[FPSTR(_presetHigh)] | presetHigh; - presetLow = top[FPSTR(_presetLow)] | presetLow; - presetHigh = MIN(250,MAX(0,presetHigh)); - presetLow = MIN(250,MAX(0,presetLow)); - - enabled = top[FPSTR(_enabled)] | enabled; - applyToAll = top[FPSTR(_applyToAll)] | applyToAll; +//void RotaryEncoderUIUsermod::appendConfigData() { +// oappend(SET_F("addInfo('RotaryEncoderUIUsermod:PCF8574-address',1,'(not hex!)');")); +//} +/** + * 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 RotaryEncoderUIUsermod::readFromConfig(JsonObject &root) { + // we look for JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}} + JsonObject top = root[FPSTR(_name)]; + if (top.isNull()) { DEBUG_PRINT(FPSTR(_name)); - if (!initDone) { - // first run: reading from cfg.json - pinA = newDTpin; - pinB = newCLKpin; - pinC = newSWpin; - DEBUG_PRINTLN(F(" config loaded.")); - } else { - DEBUG_PRINTLN(F(" config (re)loaded.")); - // changing parameters from settings page - if (pinA!=newDTpin || pinB!=newCLKpin || pinC!=newSWpin) { + DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + return false; + } + int8_t newDTpin = top[FPSTR(_DT_pin)] | pinA; + int8_t newCLKpin = top[FPSTR(_CLK_pin)] | pinB; + int8_t newSWpin = top[FPSTR(_SW_pin)] | pinC; + + presetHigh = top[FPSTR(_presetHigh)] | presetHigh; + presetLow = top[FPSTR(_presetLow)] | presetLow; + presetHigh = MIN(250,MAX(0,presetHigh)); + presetLow = MIN(250,MAX(0,presetLow)); + + enabled = top[FPSTR(_enabled)] | enabled; + applyToAll = top[FPSTR(_applyToAll)] | applyToAll; + + usePcf8574 = top[FPSTR(_pcf8574)] | usePcf8574; + addrPcf8574 = top[FPSTR(_pcfAddress)] | addrPcf8574; + + DEBUG_PRINT(FPSTR(_name)); + if (!initDone) { + // first run: reading from cfg.json + pinA = newDTpin; + pinB = newCLKpin; + pinC = newSWpin; + DEBUG_PRINTLN(F(" config loaded.")); + } else { + DEBUG_PRINTLN(F(" config (re)loaded.")); + // changing parameters from settings page + if (pinA!=newDTpin || pinB!=newCLKpin || pinC!=newSWpin) { + if (!usePcf8574) { pinManager.deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI); pinManager.deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI); pinManager.deallocatePin(pinC, PinOwner::UM_RotaryEncoderUI); pinA = newDTpin; pinB = newCLKpin; pinC = newSWpin; - if (pinA<0 || pinB<0 || pinC<0) { - enabled = false; - return true; - } - setup(); } + if (pinA<0 || pinB<0 || pinC<0) { + enabled = false; + return true; + } + setup(); } - // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[FPSTR(_applyToAll)].isNull(); } + // use "return !top["newestParameter"].isNull();" when updating Usermod with new features + return !top[FPSTR(_pcf8574)].isNull(); +} - /* - * 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_ROTARY_ENC_UI; - } -}; // strings to reduce flash memory usage (used more than twice) const char RotaryEncoderUIUsermod::_name[] PROGMEM = "Rotary-Encoder"; @@ -926,3 +1119,5 @@ const char RotaryEncoderUIUsermod::_SW_pin[] PROGMEM = "SW-pin"; const char RotaryEncoderUIUsermod::_presetHigh[] PROGMEM = "preset-high"; const char RotaryEncoderUIUsermod::_presetLow[] PROGMEM = "preset-low"; const char RotaryEncoderUIUsermod::_applyToAll[] PROGMEM = "apply-2-all-seg"; +const char RotaryEncoderUIUsermod::_pcf8574[] PROGMEM = "use-PCF8574"; +const char RotaryEncoderUIUsermod::_pcfAddress[] PROGMEM = "PCF8574-address"; diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 16daac68..d65fce9d 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -297,9 +297,11 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { PinManagerPinType i2c[2] = { { i2c_sda, true }, { i2c_scl, true } }; if (i2c_scl >= 0 && i2c_sda >= 0 && pinManager.allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) { #ifdef ESP32 - Wire.setPins(i2c_sda, i2c_scl); // this will fail if Wire is initilised (Wire.begin() called prior) + if (!Wire.setPins(i2c_sda, i2c_scl)) { i2c_scl = i2c_sda = -1; } // this will fail if Wire is initilised (Wire.begin() called prior) + else Wire.begin(); + #else + Wire.begin(i2c_sda, i2c_scl); #endif - Wire.begin(); } else { i2c_sda = -1; i2c_scl = -1; diff --git a/wled00/data/index.css b/wled00/data/index.css index 640839d5..5be49819 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -410,6 +410,7 @@ button { position: -webkit-sticky; position: sticky; bottom: 0; + max-width: 300px; } #sliders .labels { @@ -418,7 +419,7 @@ button { } .slider { - max-width: 300px; + /*max-width: 300px;*/ /* margin: 5px auto; add 5px; if you want some vertical space but looks ugly */ border-radius: 24px; position: relative; @@ -434,12 +435,16 @@ button { z-index: 0; } +#sliders .slider { + padding-right: 64px; /* offset for bubble */ +} + #sliders .slider, #info .slider { background-color: var(--c-2); } #sliders .sliderwrap, .sbs .sliderwrap { - left: 16px; /* offset for icon */ + left: 32px; /* offset for icon */ } .filter, .option { @@ -685,18 +690,19 @@ img { .sliderbubble { width: 24px; - position: relative; + position: absolute; display: inline-block; - border-radius: 10px; + border-radius: 16px; background: var(--c-3); color: var(--c-f); - padding: 2px 4px; + padding: 4px; font-size: 14px; - right: 3px; - transition: visibility 0.25s ease, opacity 0.25s ease; + right: 6px; + transition: visibility .25s ease,opacity .25s ease; opacity: 0; visibility: hidden; - left: 8px; + /* left: 8px; */ + top: 4px; } output.sliderbubbleshow { @@ -1022,8 +1028,9 @@ textarea { /* segment power wrapper */ .sbs { - padding: 1px 0 1px 20px; + /*padding: 1px 0 1px 20px;*/ display: var(--sgp); + width: 100%; } .pname { @@ -1538,8 +1545,11 @@ TD .checkmark, TD .radiomark { #sliders .sliderbubble { display: none; } - .sliderwrap { - width: calc(100% - 28px); + #sliders .sliderwrap, .sbs .sliderwrap { + width: calc(100% - 42px); + } + #sliders .slider { + padding-right: 0; } #sliders .sliderwrap { left: 12px; diff --git a/wled00/data/index.js b/wled00/data/index.js index ccdbab96..6674ba06 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -497,6 +497,7 @@ function loadFX(callback = null) populateEffects(); }) .catch((e)=>{ + //setTimeout(loadFX, 250); // retry showToast(e, true); }) .finally(()=>{ @@ -524,6 +525,7 @@ function loadFXData(callback = null) }) .catch((e)=>{ fxdata = []; + //setTimeout(loadFXData, 250); // retry showToast(e, true); }) .finally(()=>{ @@ -1253,6 +1255,7 @@ function updateSelectedFx() // hide non-0D effects if segment only has 1 pixel (0D) var fxs = parent.querySelectorAll('.lstI'); for (const fx of fxs) { + if (!fx.dataset.opt) continue; let opts = fx.dataset.opt.split(";"); if (fx.dataset.id>0) { if (segLmax==0) fx.classList.add('hide'); // none of the segments selected (hide all effects) diff --git a/wled00/data/settings_2D.htm b/wled00/data/settings_2D.htm index 5258439b..d29f2826 100644 --- a/wled00/data/settings_2D.htm +++ b/wled00/data/settings_2D.htm @@ -322,7 +322,7 @@ Y:
Serpentine:
- Pressing Populate will create LED panel layout with pre-arranged matrix.
Values above will not affect final layout.
+ Pressing Populate will create LED panel layout with pre-arranged matrix.
Values above will not affect final layout.
WARNING: You may need to update each panel parameters after they are generated.

diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index c28efd53..7972755d 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -576,7 +576,7 @@ Length:
Maximum Current: mA
-