From f368bbec3244674976dfef58fe435acae9a77e10 Mon Sep 17 00:00:00 2001 From: itCarl Date: Thu, 2 Sep 2021 15:41:19 +0200 Subject: [PATCH 01/22] added MQTT support, Battery voltage to Info, circuit diagram to readme, minor fixes --- .../battery_connection_schematic_01.png | Bin 0 -> 49110 bytes .../battery_connection_schematic_02.png | Bin 0 -> 46728 bytes .../assets/battery_info_screen.png | Bin 0 -> 69179 bytes usermods/battery_status_basic/readme.md | 16 +++- .../usermod_v2_battery_status_basic.h | 90 ++++++++++++++---- 5 files changed, 86 insertions(+), 20 deletions(-) create mode 100644 usermods/battery_status_basic/assets/battery_connection_schematic_01.png create mode 100644 usermods/battery_status_basic/assets/battery_connection_schematic_02.png create mode 100644 usermods/battery_status_basic/assets/battery_info_screen.png diff --git a/usermods/battery_status_basic/assets/battery_connection_schematic_01.png b/usermods/battery_status_basic/assets/battery_connection_schematic_01.png new file mode 100644 index 0000000000000000000000000000000000000000..5ce01de6865b24533789f7006ecf0140c2f1b5b2 GIT binary patch literal 49110 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 literal 0 HcmV?d00001 diff --git a/usermods/battery_status_basic/assets/battery_connection_schematic_02.png b/usermods/battery_status_basic/assets/battery_connection_schematic_02.png new file mode 100644 index 0000000000000000000000000000000000000000..03f41ca0d8546e5678fd0042ae36b38eb9eb2e1e GIT binary patch literal 46728 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 literal 0 HcmV?d00001 diff --git a/usermods/battery_status_basic/assets/battery_info_screen.png b/usermods/battery_status_basic/assets/battery_info_screen.png new file mode 100644 index 0000000000000000000000000000000000000000..50eb53465b325bbf97b614648cbbf4c64e43dfd5 GIT binary patch literal 69179 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 literal 0 HcmV?d00001 diff --git a/usermods/battery_status_basic/readme.md b/usermods/battery_status_basic/readme.md index 7bff98f4..276b23c1 100644 --- a/usermods/battery_status_basic/readme.md +++ b/usermods/battery_status_basic/readme.md @@ -2,16 +2,25 @@ This Usermod allows you to monitor the battery level of your battery powered project. -You can see the battery level in the `info modal` right under the `estimated current`. +You can see the battery level and voltage in the `info modal`. For this to work the positive side of the (18650) battery must be connected to pin `A0` of the d1mini/esp8266 with a 100k ohm resistor (see [Useful Links](#useful-links)). If you have a esp32 board it is best to connect the positive side of the battery to ADC1 (GPIO32 - GPIO39) +

+ +

+ ## Installation define `USERMOD_BATTERY_STATUS_BASIC` in `my_config.h` +### Basic wiring diagram +

+ +

+ ### Define Your Options * `USERMOD_BATTERY_STATUS_BASIC` - define this (in `my_config.h`) to have this user mod included wled00\usermods_list.cpp @@ -45,6 +54,11 @@ Specification from: [Molicel INR18650-M35A, 3500mAh 10A Lithium-ion battery, 3. * https://arduinodiy.wordpress.com/2016/12/25/monitoring-lipo-battery-voltage-with-wemos-d1-minibattery-shield-and-thingspeak/ ## Change Log +2021-09-02 +* added "Battery voltage" to info +* added circuit diagram to readme +* added MQTT support, sending battery voltage +* minor fixes 2021-08-15 * changed `USERMOD_BATTERY_MIN_VOLTAGE` to 2.6 volt as default for 18650 batteries diff --git a/usermods/battery_status_basic/usermod_v2_battery_status_basic.h b/usermods/battery_status_basic/usermod_v2_battery_status_basic.h index f6271c27..ab9cba3b 100644 --- a/usermods/battery_status_basic/usermod_v2_battery_status_basic.h +++ b/usermods/battery_status_basic/usermod_v2_battery_status_basic.h @@ -29,7 +29,7 @@ #endif -// the frequency to check the battery, 1 minute +// the frequency to check the battery, 30 sec #ifndef USERMOD_BATTERY_MEASUREMENT_INTERVAL #define USERMOD_BATTERY_MEASUREMENT_INTERVAL 30000 #endif @@ -53,7 +53,8 @@ class UsermodBatteryBasic : public Usermod int8_t batteryPin = USERMOD_BATTERY_MEASUREMENT_PIN; // how often to read the battery voltage unsigned long readingInterval = USERMOD_BATTERY_MEASUREMENT_INTERVAL; - unsigned long lastTime = 0; + unsigned long nextReadTime = 0; + unsigned long lastReadTime = 0; // battery min. voltage float minBatteryVoltage = USERMOD_BATTERY_MIN_VOLTAGE; // battery max. voltage @@ -68,6 +69,7 @@ class UsermodBatteryBasic : public Usermod // mapped battery level based on voltage long batteryLevel = 0; bool initDone = false; + bool initializing = true; // strings to reduce flash memory usage (used more than twice) @@ -82,6 +84,19 @@ class UsermodBatteryBasic : public Usermod return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } + float truncate(float val, byte dec) + { + float x = val * pow(10, dec); + float y = round(x); + float z = x - y; + if ((int)z == 5) + { + y++; + } + x = y / pow(10, dec); + return x; + } + public: @@ -107,6 +122,9 @@ class UsermodBatteryBasic : public Usermod pinMode(batteryPin, INPUT); #endif + nextReadTime = millis() + readingInterval; + lastReadTime = millis(); + initDone = true; } @@ -129,26 +147,38 @@ class UsermodBatteryBasic : public Usermod { if(strip.isUpdating()) return; - unsigned long now = millis(); - // check the battery level every USERMOD_BATTERY_MEASUREMENT_INTERVAL (ms) - if (now - lastTime >= readingInterval) { + if (millis() < nextReadTime) return; - // read battery raw input - rawValue = analogRead(batteryPin); - // calculate the voltage - voltage = (rawValue / adcPrecision) * maxBatteryVoltage ; + nextReadTime = millis() + readingInterval; + lastReadTime = millis(); + initializing = false; - // translate battery voltage into percentage - /* - the standard "map" function doesn't work - https://www.arduino.cc/reference/en/language/functions/math/map/ notes and warnings at the bottom - */ - batteryLevel = mapf(voltage, minBatteryVoltage, maxBatteryVoltage, 0, 100); + // read battery raw input + rawValue = analogRead(batteryPin); - lastTime = now; + // calculate the voltage + voltage = (rawValue / adcPrecision) * maxBatteryVoltage ; + // check if voltage is within specified voltage range + voltage = voltagemaxBatteryVoltage?-1.0f:voltage; + + // translate battery voltage into percentage + /* + the standard "map" function doesn't work + https://www.arduino.cc/reference/en/language/functions/math/map/ notes and warnings at the bottom + */ + batteryLevel = mapf(voltage, minBatteryVoltage, maxBatteryVoltage, 0, 100); + + + // SmartHome stuff + if (WLED_MQTT_CONNECTED) { + char subuf[64]; + strcpy(subuf, mqttDeviceTopic); + strcat_P(subuf, PSTR("/voltage")); + mqtt->publish(subuf, 0, false, String(voltage).c_str()); } + } @@ -163,9 +193,31 @@ class UsermodBatteryBasic : public Usermod JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); - JsonArray battery = user.createNestedArray("Battery level"); - battery.add(batteryLevel); - battery.add(F(" %")); + // info modal display names + JsonArray batteryPercentage = user.createNestedArray("Battery level"); + JsonArray batteryVoltage = user.createNestedArray("Battery voltage"); + + if (initializing) { + batteryPercentage.add((nextReadTime - millis()) / 1000); + batteryPercentage.add(" sec"); + batteryVoltage.add((nextReadTime - millis()) / 1000); + batteryVoltage.add(" sec"); + return; + } + + if(batteryLevel < 0) { + batteryPercentage.add(F("invalid")); + } else { + batteryPercentage.add(batteryLevel); + } + batteryPercentage.add(F(" %")); + + if(voltage < 0) { + batteryVoltage.add(F("invalid")); + } else { + batteryVoltage.add(truncate(voltage, 2)); + } + batteryVoltage.add(F(" V")); } From 7a129e6de1c517b88a17145e0931c240c63e4704 Mon Sep 17 00:00:00 2001 From: Proto-molecule Date: Fri, 1 Oct 2021 20:46:58 -0700 Subject: [PATCH 02/22] Add new Usermod --- .../readme.md | 45 + .../usermod_v2_four_line_display_ALT.h | 918 ++++++++++++++++++ .../readme.md | 45 + .../usermod_v2_rotary_encoder_ui_ALT.h | 569 +++++++++++ wled00/usermods_list.cpp | 12 +- 5 files changed, 1587 insertions(+), 2 deletions(-) create mode 100644 usermods/usermod_v2_four_line_display_ALT/readme.md create mode 100644 usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h create mode 100644 usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md create mode 100644 usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h diff --git a/usermods/usermod_v2_four_line_display_ALT/readme.md b/usermods/usermod_v2_four_line_display_ALT/readme.md new file mode 100644 index 00000000..67cde353 --- /dev/null +++ b/usermods/usermod_v2_four_line_display_ALT/readme.md @@ -0,0 +1,45 @@ +# I2C 4 Line Display Usermod ALT + +Thank you to the authors of the original version of these usermods. It would not have been possible without them! +"usermod_v2_four_line_display" +"usermod_v2_rotary_encoder_ui" + +The core of these usermods are a copy of the originals. The main changes are done to the FourLineDisplay usermod. +The display usermod UI has been completely changed. + + +The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod. +Without the display it functions identical to the original. +The original "usermod_v2_auto_save" will not work with the display just yet. + +Press the encoder to cycle through the options: + *Brightness + *Speed + *Intensity + *Palette + *Effect + *Main Color (only if display is used) + *Saturation (only if display is used) + +Press and hold the encoder to display Network Info + if AP is active then it will display AP ssid and Password + +Also shows if the timer is enabled + +[See the pair of usermods in action](https://www.youtube.com/watch?v=ulZnBt9z3TI) + +## Installation + +Please refer to the original `usermod_v2_rotary_encoder_ui` readme for the main instructions +Then to activate this alternative usermod add `#define USE_ALT_DISPlAY` to the `usermods_list.cpp` file, + or add `-D USE_ALT_DISPlAY` to the original `platformio_override.ini.sample` file + + +### PlatformIO requirements + +Note: the Four Line Display usermod requires the libraries `U8g2` and `Wire`. + +## Change Log + +2021-10 +* First public release \ No newline at end of file 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 new file mode 100644 index 00000000..b1e55202 --- /dev/null +++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h @@ -0,0 +1,918 @@ +#pragma once + +#include "wled.h" +#include // from https://github.com/olikraus/u8g2/ + +// +// Insired by the usermod_v2_four_line_display +// +// v2 usermod for using 128x32 or 128x64 i2c +// OLED displays to provide a four line display +// for WLED. +// +// Dependencies +// * This usermod REQURES the ModeSortUsermod +// * This Usermod works best, by far, when coupled +// with RotaryEncoderUIUsermod. +// +// Make sure to enable NTP and set your time zone in WLED Config | Time. +// +// REQUIREMENT: You must add the following requirements to +// REQUIREMENT: "lib_deps" within platformio.ini / platformio_override.ini +// REQUIREMENT: * U8g2 (the version already in platformio.ini is fine) +// REQUIREMENT: * Wire +// + +//The SCL and SDA pins are defined here. +#ifdef ARDUINO_ARCH_ESP32 + #ifndef FLD_PIN_SCL + #define FLD_PIN_SCL 22 + #endif + #ifndef FLD_PIN_SDA + #define FLD_PIN_SDA 21 + #endif + #ifndef FLD_PIN_CLOCKSPI + #define FLD_PIN_CLOCKSPI 18 + #endif + #ifndef FLD_PIN_DATASPI + #define FLD_PIN_DATASPI 23 + #endif + #ifndef FLD_PIN_DC + #define FLD_PIN_DC 19 + #endif + #ifndef FLD_PIN_CS + #define FLD_PIN_CS 5 + #endif + #ifndef FLD_PIN_RESET + #define FLD_PIN_RESET 26 + #endif +#else + #ifndef FLD_PIN_SCL + #define FLD_PIN_SCL 5 + #endif + #ifndef FLD_PIN_SDA + #define FLD_PIN_SDA 4 + #endif + #ifndef FLD_PIN_CLOCKSPI + #define FLD_PIN_CLOCKSPI 14 + #endif + #ifndef FLD_PIN_DATASPI + #define FLD_PIN_DATASPI 13 + #endif + #ifndef FLD_PIN_DC + #define FLD_PIN_DC 12 + #endif + #ifndef FLD_PIN_CS + #define FLD_PIN_CS 15 + #endif + #ifndef FLD_PIN_RESET + #define FLD_PIN_RESET 16 + #endif +#endif + +// When to time out to the clock or blank the screen +// if SLEEP_MODE_ENABLED. +#define SCREEN_TIMEOUT_MS 60*1000 // 1 min + +#define TIME_INDENT 0 +#define DATE_INDENT 2 + +// Minimum time between redrawing screen in ms +#define USER_LOOP_REFRESH_RATE_MS 100 + +// Extra char (+1) for null +#define LINE_BUFFER_SIZE 16+1 +#define MAX_JSON_CHARS 19+1 +#define MAX_MODE_LINE_SPACE 13+1 + +typedef enum { + NONE = 0, + SSD1306, // U8X8_SSD1306_128X32_UNIVISION_HW_I2C + SH1106, // U8X8_SH1106_128X64_WINSTAR_HW_I2C + SSD1306_64, // U8X8_SSD1306_128X64_NONAME_HW_I2C + SSD1305, // U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C + SSD1305_64, // U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C + SSD1306_SPI, // U8X8_SSD1306_128X32_NONAME_HW_SPI + SSD1306_SPI64 // U8X8_SSD1306_128X64_NONAME_HW_SPI +} DisplayType; + +/* + Fontname: benji_custom_icons_2x + Copyright: + Glyphs: 1/1 + BBX Build Mode: 3 + * 2x2 custom icons that are not available in the U8X8 library + * 64 = custom palette +*/ +const uint8_t u8x8_font_benji_custom_icons_2x2[37] U8X8_FONT_SECTION("u8x8_font_benji_custom_icons_2x2") = + "@@\2\2\360\370\234\236\376\363\363\377\377\363\363\376><\370\360\3\17\77yy\377\377\377\377\317\17\17" + "\17\17\7\3"; + +/* + Fontname: benji_custom_icons_6x + Copyright: + Glyphs: 8/8 + BBX Build Mode: 3 + * 6x6 icons take up a lot of memory, theres not enough momory for the required librries + * these are just the ruquired icons stripped for the U8x8 libraries in addition to a few new custom icons + * 1 = sun + * 2 = skip forward + * 3 = fire + * 4 = custom palette + * 5 = puzzle piece + * 6 = moon + * 7 = brush + * 8 = custom saturation +*/ +const uint8_t u8x8_font_benji_custom_icons_6x6[2308] U8X8_FONT_SECTION("u8x8_font_benji_custom_icons_6x6") = + "\1\10\6\6\0\0\0\0\0\0\200\300\300\300\300\200\0\0\0\0\0\0\0\0\0\36\77\77\77\77\36\0" + "\0\0\0\0\0\0\0\0\200\300\300\300\300\200\0\0\0\0\0\0\0\0\0\0\0\0\7\17\17\17\17\7" + "\0\0\0\0\200\300\340\340\340\360\360\360\360\360\360\340\340\340\300\200\0\0\0\0\7\17\17\17\17\7\0\0" + "\0\0\0\0\300\340\340\340\340\300\0\0\0\0\0\0\340\374\376\377\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\376\374\340\0\0\0\0\0\0\300\340\340\340\340\300\3\7\7\7\7\3\0\0\0\0\0\0" + "\7\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\7\0\0\0\0\0\0\3\7" + "\7\7\7\3\0\0\0\0\0\0\340\360\360\360\360\340\0\0\0\0\1\3\7\7\7\17\17\17\17\17\17\7" + "\7\7\3\1\0\0\0\0\340\360\360\360\360\340\0\0\0\0\0\0\0\0\0\0\0\0\1\3\3\3\3\1" + "\0\0\0\0\0\0\0\0\0x\374\374\374\374x\0\0\0\0\0\0\0\0\0\1\3\3\3\3\1\0\0" + "\0\0\0\0\300\200\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\300\200\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\376\376\374\370\360\360\340\300\200" + "\200\0\0\0\0\0\0\0\0\0\0\0\377\377\377\376\376\374\370\360\360\340\300\200\200\0\0\0\0\0\0\0" + "\0\0\0\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\376\374\374\370\360\340\340\300\200\0\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\376\374\374\370\360\340\340\300\200\0\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\177\77\77\37\17\7\7\3\1\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\77\37\17\7" + "\7\3\1\0\377\377\377\177\177\77\37\17\17\7\3\1\1\0\0\0\0\0\0\0\0\0\0\0\377\377\377\177" + "\177\77\37\17\17\7\3\1\1\0\0\0\0\0\0\0\0\0\0\0\3\1\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\3\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\376\374\374\370\360\340\300\200\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\200\340\360\374" + "\377\377\377\377\377\377\377\377\377\376\370\300\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\300\340\360\374\376\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\37\0\0\0\0" + "\0\0\4\370\360\360\340\300\200\0\0\0\0\0\0\0\0\0\0\0\370\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\177\77\37\7\3\0\0\0\0\0\200\300\360\374\377\377\377\377\377\377\377\376\370\340\0\0\0" + "\0\0\0\0\3\37\177\377\377\377\377\377\377\377\377\377\77\17\7\1\0\0\0\0\0\200\300\360\370\374\376\377" + "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0\0\1\3\7\17\37\77\77\177\200" + "\0\0\0\0\0\0\340\374\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\17\1\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\200\300\340\340\360\360\370|<>>>~\377\377\377\377\377\377\377\177" + "\77\36\36\36\36<|\370\370\360\360\340\340\200\0\0\0\0\0\0\0\0\300\360\374\376\377\377\377\377\377\377" + "\377\360\340\300\300\300\300\340\360\377\377\377\377\377\377\370\360\340\340\340\340\360\370\377\377\377\377\377\377\377\377\377" + "\374\360\340\200\360\377\377\377\377\377\207\3\1\1\1\1\3\207\377\377\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\207\3\1\1\1\1\3\207\377\377\377\377\377\17\377\377\377\377\377\377\377\376~>>" + "\77\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\376\376\376\376\377\377\377" + "\177\77\37\7\0\0\3\17\77\177\377\377\360\340\300\300\300\300\340\360\377\377\377\377\377\377\377\377\377\377\77\17" + "\17\7\7\7\7\7\7\7\7\7\3\3\3\3\1\0\0\0\0\0\0\0\0\0\0\0\0\1\3\7\17\37" + "\37\77\77\177\177\177\377\377\377\377\377\377\377\377\377~\30\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\370\374\376\377\377\377\377\377\377\376\374\360\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\360\360\360\360\360\360\360\360\360\360\360\360" + "\360\363\377\377\377\377\377\377\377\377\363\360\360\360\360\360\360\360\360\360\360\360\360\360\0\0\0\0\0\0\0\0" + "\0\0\0\0\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0\0\0\0\0\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\374\374\376\376\377\377\377\377" + "\377\376\374\360\377\377\377\377\377\377\377\377\377\377\377\377\177\77\37\17\17\17\17\17\17\37\77\177\377\377\377\377" + "\377\377\377\377\377\377\377\377\3\3\7\7\17\17\17\17\7\7\3\0\377\377\377\377\377\377\377\377\377\377\377\377" + "\360\300\0\0\0\0\0\0\0\0\300\360\377\377\377\377\377\377\377\377\377\377\377\377\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\200\300\340\360\360\370\374\374\376\376\7\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\300\360\374\376\377\377\377\377\377\377\377" + "\377\377\377\340\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\374\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\374\360\300\200\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\17\177\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\376\374\370\360\360\340\340\300\300\300\200\200\200\200\0\0\0\0\0\0\200\200" + "\200\200\0\0\0\0\1\7\37\77\177\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377" + "\377\377\377\377\377\377\377\377\377\377\377\377\377\177\77\37\7\1\0\0\0\0\0\0\0\0\0\0\1\3\3\7" + "\17\17\37\37\37\77\77\77\77\177\177\177\177\177\177\77\77\77\77\37\37\37\17\17\7\3\3\1\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\200\200\300\340\360\360\370\374\374\376\377~\34\10\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\200\300\300\340\360\360\370\374\376\376\377\377\377\377\377\377\177\77\17\7\3" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\4\6\17\17\37\77\177\377" + "\377\377\377\377\377\377\77\37\7\3\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\300\370\374\376" + "\376\377\377\377\377\377\377\376\376\374\370\340\0\0\0\0\3\17\7\3\1\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\200\360\377\377\377\377\377\377\377\377\377\377\377\377\377\377\177\17\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0`px\374\376\377\377\377\377\377\377" + "\177\177\177\77\77\37\17\7\3\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\200\300\300\200\0\0\0\0\0\0\0\0\0\14\36\77\77\36\14\0\0" + "\0\0\0\0\0\0\0\200\300\300\200\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\3\7\17\17\7\3" + "\0\200\300\340\360\360\370\370\370\374\374\374\374\370\370\370\360\360\340\300\200\0\3\7\17\17\7\3\0\0\0\0" + "\0\0\0\0\300\340\360\360\340\300\0\0\0\0\340\374\377\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177" + "\177\177\177\177\177\377\374\340\0\0\0\0\300\340\360\360\340\300\0\0\0\1\3\3\1\0\0\0\0\0\1\17" + "\77\177\370\340\300\200\200\0\0\0\0\0\0\0\0\200\200\300\340\370\177\77\17\1\0\0\0\0\0\1\3\3" + "\1\0\0\0\0\0\0\0\0\0\60x\374\374x\60\0\0\0\1\3\3\7\7\7\16\16\16\16\7\7\7" + "\3\3\1\0\0\0\60x\374\374x\60\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\14\36\77\77\36\14\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0"; + +class FourLineDisplayUsermod : public Usermod { + + private: + + bool initDone = false; + unsigned long lastTime = 0; + + // HW interface & configuration + 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 + uint32_t ioFrequency = 400000; // in Hz (minimum is 100000, baseline is 400000 and maximum should be 3400000) + DisplayType type = SSD1306_64; // display type + #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 + DisplayType type = SSD1306_SPI; // display type + #endif + bool flip = false; // flip display 180° + uint8_t contrast = 10; // screen contrast + uint8_t lineHeight = 1; // 1 row or 2 rows + uint32_t refreshRate = USER_LOOP_REFRESH_RATE_MS; // in ms + uint32_t screenTimeout = SCREEN_TIMEOUT_MS; // in ms + bool sleepMode = true; // allow screen sleep? + bool clockMode = false; // display clock + + // needRedraw marks if redraw is required to prevent often redrawing. + bool needRedraw = true; + + // Next variables hold the previous known values to determine if redraw is + // required. + String knownSsid = ""; + IPAddress knownIp; + uint8_t knownBrightness = 0; + uint8_t knownEffectSpeed = 0; + uint8_t knownEffectIntensity = 0; + uint8_t knownMode = 0; + uint8_t knownPalette = 0; + uint8_t knownMinute = 99; + byte brightness100; + byte fxspeed100; + byte fxintensity100; + bool knownnightlight = nightlightActive; + bool wificonnected = interfacesInited; + bool powerON = true; + + bool displayTurnedOff = false; + unsigned long lastUpdate = 0; + unsigned long lastRedraw = 0; + unsigned long overlayUntil = 0; + // Set to 2 or 3 to mark lines 2 or 3. Other values ignored. + byte markLineNum = 0; + byte markColNum = 0; + + // strings to reduce flash memory usage (used more than twice) + static const char _name[]; + static const char _contrast[]; + static const char _refreshRate[]; + static const char _screenTimeOut[]; + static const char _flip[]; + static const char _sleepMode[]; + static const char _clockMode[]; + static const char _busClkFrequency[]; + + // 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 + + public: + + // gets called once at boot. Do all initialization that doesn't depend on + // network here + void setup() { + if (type == NONE) return; + if (type == SSD1306_SPI || type == SSD1306_SPI64) { + PinManagerPinType pins[5] = { { ioPin[0], true }, { ioPin[1], true}, { ioPin[2], true }, { ioPin[3], true}, { ioPin[4], true }}; + if (!pinManager.allocateMultiplePins(pins, 5, PinOwner::UM_FourLineDisplay)) { type=NONE; return; } + } else { + PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true} }; + if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::UM_FourLineDisplay)) { type=NONE; return; } + } + DEBUG_PRINTLN(F("Allocating display.")); + switch (type) { + case SSD1306: + #ifdef ESP8266 + if (!(ioPin[0]==5 && ioPin[1]==4)) + u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset + else + #endif + u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA + lineHeight = 1; + break; + case SH1106: + #ifdef ESP8266 + if (!(ioPin[0]==5 && ioPin[1]==4)) + u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset + else + #endif + u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA + lineHeight = 2; + break; + case SSD1306_64: + #ifdef ESP8266 + if (!(ioPin[0]==5 && ioPin[1]==4)) + u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset + else + #endif + u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA + lineHeight = 2; + break; + case SSD1305: + #ifdef ESP8266 + if (!(ioPin[0]==5 && ioPin[1]==4)) + u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_NONAME_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset + else + #endif + u8x8 = (U8X8 *) new U8X8_SSD1305_128X32_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA + lineHeight = 1; + break; + case SSD1305_64: + #ifdef ESP8266 + if (!(ioPin[0]==5 && ioPin[1]==4)) + u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_SW_I2C(ioPin[0], ioPin[1]); // SCL, SDA, reset + else + #endif + u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA + lineHeight = 2; + break; + case SSD1306_SPI: + if (!(ioPin[0]==FLD_PIN_CLOCKSPI && ioPin[1]==FLD_PIN_DATASPI)) // if not overridden these sould be HW accellerated + 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 + lineHeight = 1; + break; + case SSD1306_SPI64: + if (!(ioPin[0]==FLD_PIN_CLOCKSPI && ioPin[1]==FLD_PIN_DATASPI)) // if not overridden these sould be HW accellerated + 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 + lineHeight = 2; + break; + default: + u8x8 = nullptr; + } + if (nullptr == u8x8) { + DEBUG_PRINTLN(F("Display init failed.")); + for (byte i=0; i<5 && ioPin[i]>=0; i++) pinManager.deallocatePin(ioPin[i], PinOwner::UM_FourLineDisplay); + type = NONE; + return; + } + + initDone = true; + DEBUG_PRINTLN(F("Starting display.")); + if (!(type == SSD1306_SPI || type == SSD1306_SPI64)) u8x8->setBusClock(ioFrequency); // can be used for SPI too + u8x8->begin(); + setFlipMode(flip); + 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..."); + } + + // gets called every time WiFi is (re-)connected. Initialize own network + // interfaces here + void connected() {} + + /** + * Da loop. + */ + void loop() { + if (displayTurnedOff && millis() - lastUpdate < 1000) { + return; + }else if (millis() - lastUpdate < refreshRate){ + return;} + redraw(false); + lastUpdate = millis(); + } + + /** + * Wrappers for screen drawing + */ + void setFlipMode(uint8_t mode) { + if (type==NONE) return; + u8x8->setFlipMode(mode); + } + void setContrast(uint8_t contrast) { + if (type==NONE) return; + u8x8->setContrast(contrast); + } + void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) { + if (type==NONE) return; + u8x8->setFont(u8x8_font_chroma48medium8_r); + if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string); + else u8x8->drawString(col, row, string); + } + void draw2x2String(uint8_t col, uint8_t row, const char *string) { + if (type==NONE) return; + u8x8->setFont(u8x8_font_chroma48medium8_r); + u8x8->draw2x2String(col, row, string); + } + void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false) { + if (type==NONE) return; + u8x8->setFont(font); + if (!ignoreLH && lineHeight==2) u8x8->draw1x2Glyph(col, row, glyph); + else u8x8->drawGlyph(col, row, glyph); + } + uint8_t getCols() { + if (type==NONE) return 0; + return u8x8->getCols(); + } + void clear() { + if (type==NONE) return; + u8x8->clear(); + } + void setPowerSave(uint8_t save) { + if (type==NONE) 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 0) { + if (millis() >= overlayUntil) { + // Time to display the overlay has elapsed. + overlayUntil = 0; + forceRedraw = true; + } else { + // We are still displaying the overlay + // Don't redraw. + return; + } + } + + + // Check if values which are shown on display changed from the last time. + if (forceRedraw) { + needRedraw = true; + } else if ((bri == 0 && powerON) || (bri > 0 && !powerON)) { //trigger power icon + powerON = !powerON; + drawStatusIcons(); + lastRedraw = millis(); + } else if (knownnightlight != nightlightActive) { //trigger moon icon + knownnightlight = nightlightActive; + drawStatusIcons(); + if (knownnightlight) overlay(" Timer On", 1000, 6); + lastRedraw = millis(); + }else if (wificonnected != interfacesInited){ //trigger wifi icon + wificonnected = interfacesInited; + drawStatusIcons(); + lastRedraw = millis(); + } else if (knownMode != effectCurrent) { + knownMode = effectCurrent; + if(displayTurnedOff)needRedraw = true; + else showCurrentEffectOrPalette(knownMode, JSON_mode_names, 3); + } else if (knownPalette != effectPalette) { + knownPalette = effectPalette; + if(displayTurnedOff)needRedraw = true; + else showCurrentEffectOrPalette(knownPalette, JSON_palette_names, 2); + } else if (knownBrightness != bri) { + if(displayTurnedOff && nightlightActive){needRedraw = false; knownBrightness = bri;} + else if(displayTurnedOff)needRedraw = true; + else updateBrightness(); + } else if (knownEffectSpeed != effectSpeed) { + if(displayTurnedOff)needRedraw = true; + else updateSpeed(); + } else if (knownEffectIntensity != effectIntensity) { + if(displayTurnedOff)needRedraw = true; + else updateIntensity(); + } + + + 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. + sleepOrClock(true); + } else if (displayTurnedOff && clockMode) { + showTime(); + } + return; + } else { + clear(); + } + + needRedraw = false; + lastRedraw = millis(); + + if (displayTurnedOff) { + // Turn the display back on + sleepOrClock(false); + } + + // Update last known values. + knownSsid = apActive ? apSSID : WiFi.SSID(); //apActive ? WiFi.softAPSSID() : + knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); + 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){ + brightness100 = (((float)(bri)/255)*100); + char lineBuffer[4]; + sprintf_P(lineBuffer, PSTR("%-3d"), brightness100); + drawString(1, lineHeight, lineBuffer); + lastRedraw = millis();} + } + + void updateSpeed(){ + knownEffectSpeed = effectSpeed; + if(overlayUntil == 0){ + fxspeed100 = (((float)(effectSpeed)/255)*100); + char lineBuffer[4]; + sprintf_P(lineBuffer, PSTR("%-3d"), fxspeed100); + drawString(5, lineHeight, lineBuffer); + lastRedraw = millis();} + } + + void updateIntensity(){ + knownEffectIntensity = effectIntensity; + if(overlayUntil == 0){ + fxintensity100 = (((float)(effectIntensity)/255)*100); + char lineBuffer[4]; + sprintf_P(lineBuffer, PSTR("%-3d"), fxintensity100); + drawString(9, lineHeight, lineBuffer); + lastRedraw = millis();} + } + + void draw2x2GlyphIcons(){ + drawGlyph(1, 0, 69, u8x8_font_open_iconic_weather_2x2, true);//brightness icon + drawGlyph(5, 0, 72, u8x8_font_open_iconic_play_2x2, true);//speed icon + drawGlyph(9, 0, 78, u8x8_font_open_iconic_thing_2x2, true);//intensity icon + drawGlyph(14, 2*lineHeight, 64, u8x8_font_benji_custom_icons_2x2,true);//palette icon + drawGlyph(14, 3*lineHeight, 70, u8x8_font_open_iconic_thing_2x2,true);//effect icon + } + + void drawStatusIcons(){ + drawGlyph(14, 0, 80 + (wificonnected?0:1), u8x8_font_open_iconic_embedded_1x1, true); // wifi icon + drawGlyph(15, 0, 78 + (bri > 0 ? 0 : 3), u8x8_font_open_iconic_embedded_1x1, true); // power icon + drawGlyph(13, 0, 66 + (nightlightActive?0:4), u8x8_font_open_iconic_weather_1x1, true); // moon icon for nighlight mode + } + + /** + * 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; + } + + //Draw the arrow for the current setting beiong changed + void drawArrow(){ + if(markColNum != 255 && markLineNum !=255)drawGlyph(markColNum, markLineNum*lineHeight, 69, u8x8_font_open_iconic_play_1x1); + } + + //Display the current effect or palette (desiredEntry) + // on the appropriate line (row). + void showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row) { + knownMode = effectCurrent; + knownPalette = effectPalette; + if(overlayUntil == 0){ + char lineBuffer[MAX_JSON_CHARS]; + char smallBuffer1[MAX_MODE_LINE_SPACE]; + char smallBuffer2[MAX_MODE_LINE_SPACE]; + uint8_t qComma = 0; + bool insideQuotes = false; + bool spaceHit = false; + uint8_t printedChars = 0; + uint8_t smallChars1 = 0; + uint8_t smallChars2 = 0; + uint8_t totalCount = 0; + char singleJsonSymbol; + + // Find the mode name in JSON + for (size_t i = 0; i < strlen_P(qstring); i++) { + singleJsonSymbol = pgm_read_byte_near(qstring + i); + if (singleJsonSymbol == '\0') break; + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + break; + case '[': + case ']': + break; + case ',': + qComma++; + default: + if (!insideQuotes || (qComma != inputEffPal)) break; + lineBuffer[printedChars++] = singleJsonSymbol; + totalCount++; + } + if ((qComma > inputEffPal)) break; + } + + if(printedChars < (MAX_MODE_LINE_SPACE)){ + for (;printedChars < (MAX_MODE_LINE_SPACE-1); printedChars++) {lineBuffer[printedChars]=' '; } + lineBuffer[printedChars] = 0; + drawString(1, row*lineHeight, lineBuffer); + lastRedraw = millis(); + }else{ + for (uint8_t i = 0; i < printedChars; i++){ + switch (lineBuffer[i]){ + case ' ': + if(i > 4 && !spaceHit) { + spaceHit = true; + break;} + if(!spaceHit) smallBuffer1[smallChars1++] = lineBuffer[i]; + if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i]; + break; + default: + if(!spaceHit) smallBuffer1[smallChars1++] = lineBuffer[i]; + if (spaceHit) smallBuffer2[smallChars2++] = lineBuffer[i]; + break; + } + } + for (; smallChars1 < (MAX_MODE_LINE_SPACE-1); smallChars1++) smallBuffer1[smallChars1]=' '; + smallBuffer1[smallChars1] = 0; + drawString(1, row*lineHeight, smallBuffer1, true); + for (; smallChars2 < (MAX_MODE_LINE_SPACE-1); smallChars2++) smallBuffer2[smallChars2]=' '; + smallBuffer2[smallChars2] = 0; + drawString(1, row*lineHeight+1, smallBuffer2, true); + lastRedraw = millis(); + } + } + } + + /** + * 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 wakeDisplay() { + //knownHour = 99; + if (displayTurnedOff) { + // Turn the display back on + sleepOrClock(false); + redraw(true); + return true; + } + return false; + } + + /** + * Allows you to show up to two lines as overlay for a + * period of time. + * Clears the screen and prints on the middle two lines. + */ + void overlay(const char* line1, long showHowLong, byte glyphType) { + if (displayTurnedOff) { + // Turn the display back on + sleepOrClock(false); + } + + // Print the overlay + clear(); + if (glyphType > 0)drawGlyph(5, 0, glyphType, u8x8_font_benji_custom_icons_6x6, true); + if (line1) drawString(0, 3*lineHeight, line1); + overlayUntil = millis() + showHowLong; + } + + void networkOverlay(const char* line1, long showHowLong) { + if (displayTurnedOff) { + // Turn the display back on + sleepOrClock(false); + } + // Print the overlay + clear(); + // First row string + if (line1) drawString(0, 0, line1); + // Second row with Wifi name + String ssidString = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0); // + drawString(0, lineHeight, ssidString.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 Psssword in AP Mode + drawString(0, lineHeight*2, (knownIp.toString()).c_str()); + if (apActive) { + String appassword = apPass; + drawString(0, lineHeight*3, appassword.c_str()); + } + overlayUntil = millis() + showHowLong; + } + + + /** + * Enable sleep (turn the display off) or clock mode. + */ + void sleepOrClock(bool enabled) { + if (enabled) { + if (clockMode) { + clear(); + knownMinute = 99; + showTime(); + }else setPowerSave(1); + displayTurnedOff = true; + } + else { + setPowerSave(0); + displayTurnedOff = false; + } + } + + /** + * Display the current date and time in large characters + * on the middle rows. Based 24 or 12 hour depending on + * the useAMPM configuration. + */ + void showTime() { + if(knownMinute != minute(localTime)){ //only redraw clock if it has changed + char lineBuffer[LINE_BUFFER_SIZE]; + + //updateLocalTime(); + byte AmPmHour = hour(localTime); + boolean isitAM = true; + if (useAMPM) { + if (AmPmHour > 11) AmPmHour -= 12; + if (AmPmHour == 0) AmPmHour = 12; + if (hour(localTime) > 11) isitAM = false; + } + clear(); + drawStatusIcons(); //icons power, wifi, timer, etc + + sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(month(localTime)), day(localTime)); + draw2x2String(DATE_INDENT, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays, draw month and day + + sprintf_P(lineBuffer,PSTR("%2d:%02d"), (useAMPM ? AmPmHour : hour(localTime)), minute(localTime)); + draw2x2String(TIME_INDENT+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 + knownMinute = minute(localTime); + } + } + + /* + * 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.")); + //} + + /* + * 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) { + //} + + /* + * 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() + //} + + /* + * 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 addToConfig(JsonObject& root) { + JsonObject top = root.createNestedObject(FPSTR(_name)); + JsonArray io_pin = top.createNestedArray("pin"); + for (byte i=0; i<5; i++) io_pin.add(ioPin[i]); + top["help4PinTypes"] = F("Clk,Data,CS,DC,RST"); // help for Settings page + top["type"] = type; + top[FPSTR(_flip)] = (bool) flip; + top[FPSTR(_contrast)] = contrast; + top[FPSTR(_refreshRate)] = refreshRate/10; + top[FPSTR(_screenTimeOut)] = screenTimeout/1000; + top[FPSTR(_sleepMode)] = (bool) sleepMode; + top[FPSTR(_clockMode)] = (bool) clockMode; + 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 readFromConfig(JsonObject& root) { + bool needsRedraw = false; + DisplayType newType = type; + int8_t newPin[5]; for (byte i=0; i<5; i++) newPin[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; + } + + newType = top["type"] | newType; + for (byte i=0; i<5; i++) newPin[i] = top["pin"][i] | ioPin[i]; + flip = top[FPSTR(_flip)] | flip; + contrast = top[FPSTR(_contrast)] | contrast; + refreshRate = (top[FPSTR(_refreshRate)] | refreshRate/10) * 10; + screenTimeout = (top[FPSTR(_screenTimeOut)] | screenTimeout/1000) * 1000; + sleepMode = top[FPSTR(_sleepMode)] | sleepMode; + clockMode = top[FPSTR(_clockMode)] | clockMode; + 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 + for (byte i=0; i<5; i++) ioPin[i] = newPin[i]; + 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] != newPin[i]) { pinsChanged = true; break; } + if (pinsChanged || type!=newType) { + if (type != NONE) delete u8x8; + for (byte i=0; i<5; i++) { + if (ioPin[i]>=0) pinManager.deallocatePin(ioPin[i], PinOwner::UM_FourLineDisplay); + ioPin[i] = newPin[i]; + } + if (ioPin[0]<0 || ioPin[1]<0) { // data & clock must be > -1 + type = NONE; + return true; + } else type = newType; + setup(); + needsRedraw |= true; + } + if (!(type == SSD1306_SPI || type == SSD1306_SPI64)) u8x8->setBusClock(ioFrequency); // can be used for SPI too + setContrast(contrast); + setFlipMode(flip); + if (needsRedraw && !wakeDisplay()) redraw(true); + } + // use "return !top["newestParameter"].isNull();" when updating Usermod with new features + return !(top[_busClkFrequency]).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_FOUR_LINE_DISP; + } +}; + +// strings to reduce flash memory usage (used more than twice) +const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay"; +const char FourLineDisplayUsermod::_contrast[] PROGMEM = "contrast"; +const char FourLineDisplayUsermod::_refreshRate[] PROGMEM = "refreshRate0.01Sec"; +const char FourLineDisplayUsermod::_screenTimeOut[] PROGMEM = "screenTimeOutSec"; +const char FourLineDisplayUsermod::_flip[] PROGMEM = "flip"; +const char FourLineDisplayUsermod::_sleepMode[] PROGMEM = "sleepMode"; +const char FourLineDisplayUsermod::_clockMode[] PROGMEM = "clockMode"; +const char FourLineDisplayUsermod::_busClkFrequency[] PROGMEM = "i2c-freq-kHz"; diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md b/usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md new file mode 100644 index 00000000..a140f25b --- /dev/null +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md @@ -0,0 +1,45 @@ +# Rotary Encoder UI Usermod ALT + +Thank you to the authors of the original version of these usermods. It would not have been possible without them! +"usermod_v2_four_line_display" +"usermod_v2_rotary_encoder_ui" + +The core of these usermods are a copy of the originals. The main changes are done to the FourLineDisplay usermod. +The display usermod UI has been completely changed. + + +The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod. +Without the display it functions identical to the original. +The original "usermod_v2_auto_save" will not work with the display just yet. + +Press the encoder to cycle through the options: + *Brightness + *Speed + *Intensity + *Palette + *Effect + *Main Color (only if display is used) + *Saturation (only if display is used) + +Press and hold the encoder to display Network Info + if AP is active then it will display AP ssid and Password + +Also shows if the timer is enabled + +[See the pair of usermods in action](https://www.youtube.com/watch?v=ulZnBt9z3TI) + +## Installation + +Please refer to the original `usermod_v2_rotary_encoder_ui` readme for the main instructions +Then to activate this alternative usermod add `#define USE_ALT_DISPlAY` to the `usermods_list.cpp` file, + or add `-D USE_ALT_DISPlAY` to the original `platformio_override.ini.sample` file + + +### PlatformIO requirements + +Note: the Four Line Display usermod requires the libraries `U8g2` and `Wire`. + +## Change Log + +2021-10 +* First public release \ No newline at end of file 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 new file mode 100644 index 00000000..625af0af --- /dev/null +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h @@ -0,0 +1,569 @@ +#pragma once + +#include "wled.h" + +// +// Inspired by the original v2 usermods +// * usermod_v2_rotaty_encoder_ui +// +// v2 usermod that provides a rotary encoder-based UI. +// +// This usermod allows you to control: +// +// * Brightness +// * Selected Effect +// * Effect Speed +// * Effect Intensity +// * Palette +// +// Change between modes by pressing a button. +// +// Dependencies +// * This usermod REQURES the ModeSortUsermod +// * This Usermod works best coupled with +// FourLineDisplayUsermod. +// +// If FourLineDisplayUsermod is used the folowing options are also inabled +// +// * main color +// * saturation of main color +// * display network (long press buttion) +// + +#ifndef ENCODER_DT_PIN +#define ENCODER_DT_PIN 18 +#endif + +#ifndef ENCODER_CLK_PIN +#define ENCODER_CLK_PIN 5 +#endif + +#ifndef ENCODER_SW_PIN +#define ENCODER_SW_PIN 19 +#endif + +// The last UI state, remove color and saturation option if diplay not active(too many options) +#ifdef USERMOD_FOUR_LINE_DISPLAY + #define LAST_UI_STATE 6 +#else + #define LAST_UI_STATE 4 +#endif + + +class RotaryEncoderUIUsermod : public Usermod { +private: + int fadeAmount = 5; // Amount to change every step (brightness) + unsigned long currentTime; + unsigned long loopTime; + unsigned long buttonHoldTIme; + 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 + unsigned char select_state = 0; // 0: brightness, 1: effect, 2: effect speed + unsigned char button_state = HIGH; + unsigned char prev_button_state = HIGH; + bool networkShown = false; + uint16_t currentHue1 = 6425; // default reboot color + byte currentSat1 = 255; + +#ifdef USERMOD_FOUR_LINE_DISPLAY + FourLineDisplayUsermod *display; +#else + void* display = nullptr; +#endif + + byte *modes_alpha_indexes = nullptr; + byte *palettes_alpha_indexes = nullptr; + + unsigned char Enc_A; + unsigned char Enc_B; + unsigned char Enc_A_prev = 0; + + bool currentEffectAndPaletteInitialized = false; + uint8_t effectCurrentIndex = 0; + uint8_t effectPaletteIndex = 0; + uint8_t knownMode = 0; + uint8_t knownPalette = 0; + + 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[]; + +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() + { + PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } }; + if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) { + // BUG: configuring this usermod with conflicting pins + // will cause it to de-allocate pins it does not own + // (at second config) + // This is the exact type of bug solved by pinManager + // tracking the owner tags.... + pinA = pinB = pinC = -1; + enabled = false; + return; + } + + pinMode(pinA, INPUT_PULLUP); + pinMode(pinB, INPUT_PULLUP); + pinMode(pinC, INPUT_PULLUP); + currentTime = millis(); + loopTime = currentTime; + + ModeSortUsermod *modeSortUsermod = (ModeSortUsermod*) usermods.lookup(USERMOD_ID_MODE_SORT); + modes_alpha_indexes = modeSortUsermod->getModesAlphaIndexes(); + palettes_alpha_indexes = modeSortUsermod->getPalettesAlphaIndexes(); + +#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); + } +#endif + + initDone = true; + Enc_A = digitalRead(pinA); // Read encoder pins + Enc_B = digitalRead(pinB); + Enc_A_prev = Enc_A; + } + + /* + * connected() is called every time the WiFi is (re)connected + * Use it to initialize network interfaces + */ + void connected() + { + //Serial.println("Connected to WiFi!"); + } + + /* + * 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() + { + 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 + { + button_state = digitalRead(pinC); + if (prev_button_state != button_state) + { + if (button_state == HIGH && (millis()-buttonHoldTIme < 3000)) + { + prev_button_state = button_state; + + char newState = select_state + 1; + if (newState > LAST_UI_STATE) newState = 0; + + bool changedState = true; + if (display != nullptr) { + switch(newState) { + case 0: + changedState = changeState(" Brightness", 1, 0, 1); + break; + case 1: + changedState = changeState(" Speed", 1, 4, 2); + break; + case 2: + changedState = changeState(" Intensity", 1 ,8, 3); + break; + case 3: + changedState = changeState(" Color Palette", 2, 0, 4); + break; + case 4: + changedState = changeState(" Effect", 3, 0, 5); + break; + case 5: + changedState = changeState(" Main Color", 255, 255, 7); + break; + case 6: + changedState = changeState(" Saturation", 255, 255, 8); + break; + } + } + if (changedState) { + select_state = newState; + } + } + else + { + prev_button_state = button_state; + networkShown = false; + if(!prev_button_state)buttonHoldTIme = millis(); + } + } + + if (!prev_button_state && (millis()-buttonHoldTIme > 3000) && !networkShown) displayNetworkInfo(); //long press for network info + + 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; + } + } + 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; + } + } + } + Enc_A_prev = Enc_A; // Store value of A for next time + loopTime = currentTime; // Updates loopTime + } + } + + void displayNetworkInfo(){ + #ifdef USERMOD_FOUR_LINE_DISPLAY + display->networkOverlay(" NETWORK INFO", 15000); + networkShown = true; + #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) { + #ifdef USERMOD_FOUR_LINE_DISPLAY + if (display != nullptr) { + if (display->wakeDisplay()) { + // Throw away wake up input + return false; + } + display->overlay(stateName, 750, glyph); + display->setMarkLine(markedLine, markedCol); + } + #endif + return true; + } + + void lampUdated() { + //bool fxChanged = strip.setEffectConfig(effectCurrent, effectSpeed, effectIntensity, effectPalette); + //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 + colorUpdated(CALL_MODE_DIRECT_CHANGE); + updateInterfaces(CALL_MODE_DIRECT_CHANGE); + } + + void changeBrightness(bool increase) { + #ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + // Throw away wake up input + return; + } + #endif + if (increase) bri = (bri + fadeAmount <= 255) ? (bri + fadeAmount) : 255; + else bri = (bri - fadeAmount >= 0) ? (bri - fadeAmount) : 0; + lampUdated(); + #ifdef USERMOD_FOUR_LINE_DISPLAY + display->updateBrightness(); + #endif + } + + + void changeEffect(bool increase) { + #ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + // Throw away wake up input + return; + } + #endif + if (increase) effectCurrentIndex = (effectCurrentIndex + 1 >= strip.getModeCount()) ? 0 : (effectCurrentIndex + 1); + else effectCurrentIndex = (effectCurrentIndex - 1 < 0) ? (strip.getModeCount() - 1) : (effectCurrentIndex - 1); + effectCurrent = modes_alpha_indexes[effectCurrentIndex]; + 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()) { + // Throw away wake up input + return; + } + #endif + if (increase) effectSpeed = (effectSpeed + fadeAmount <= 255) ? (effectSpeed + fadeAmount) : 255; + else effectSpeed = (effectSpeed - fadeAmount >= 0) ? (effectSpeed - fadeAmount) : 0; + lampUdated(); + #ifdef USERMOD_FOUR_LINE_DISPLAY + display->updateSpeed(); + #endif + } + + + void changeEffectIntensity(bool increase) { + #ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + // Throw away wake up input + return; + } + #endif + if (increase) effectIntensity = (effectIntensity + fadeAmount <= 255) ? (effectIntensity + fadeAmount) : 255; + else effectIntensity = (effectIntensity - fadeAmount >= 0) ? (effectIntensity - fadeAmount) : 0; + lampUdated(); + #ifdef USERMOD_FOUR_LINE_DISPLAY + display->updateIntensity(); + #endif + } + + + void changePalette(bool increase) { + #ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + // Throw away wake up input + return; + } + #endif + if (increase) effectPaletteIndex = (effectPaletteIndex + 1 >= strip.getPaletteCount()) ? 0 : (effectPaletteIndex + 1); + else effectPaletteIndex = (effectPaletteIndex - 1 < 0) ? (strip.getPaletteCount() - 1) : (effectPaletteIndex - 1); + effectPalette = palettes_alpha_indexes[effectPaletteIndex]; + 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()) { + // Throw away wake up input + return; + } + #endif + + if(increase) currentHue1 += 321; + else currentHue1 -= 321; + colorHStoRGB(currentHue1, currentSat1, col); + lampUdated(); + #ifdef USERMOD_FOUR_LINE_DISPLAY + display->updateRedrawTime(); + #endif + } + + void changeSat(bool increase){ + #ifdef USERMOD_FOUR_LINE_DISPLAY + if (display && display->wakeDisplay()) { + // Throw away wake up input + return; + } + #endif + + if(increase) currentSat1 = (currentSat1 + 5 <= 255 ? (currentSat1 + 5) : 255); + else currentSat1 = (currentSat1 - 5 >= 0 ? (currentSat1 - 5) : 0); + colorHStoRGB(currentHue1, currentSat1, col); + lampUdated(); + #ifdef USERMOD_FOUR_LINE_DISPLAY + display->updateRedrawTime(); + #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 + } + */ + + /* + * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ + void addToJsonState(JsonObject &root) + { + //root["user0"] = userVar0; + } + + /* + * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ + void readFromJsonState(JsonObject &root) + { + //userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value + //if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!")); + } + + /** + * addToConfig() (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; + 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 = pinA; + int8_t newCLKpin = pinB; + int8_t newSWpin = pinC; + + enabled = top[FPSTR(_enabled)] | enabled; + newDTpin = top[FPSTR(_DT_pin)] | newDTpin; + newCLKpin = top[FPSTR(_CLK_pin)] | newCLKpin; + newSWpin = top[FPSTR(_SW_pin)] | newSWpin; + + 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) { + 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(); + } + } + // use "return !top["newestParameter"].isNull();" when updating Usermod with new features + return !top[FPSTR(_enabled)].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"; +const char RotaryEncoderUIUsermod::_enabled[] PROGMEM = "enabled"; +const char RotaryEncoderUIUsermod::_DT_pin[] PROGMEM = "DT-pin"; +const char RotaryEncoderUIUsermod::_CLK_pin[] PROGMEM = "CLK-pin"; +const char RotaryEncoderUIUsermod::_SW_pin[] PROGMEM = "SW-pin"; diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index fd5ffffd..475f3434 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -44,10 +44,18 @@ #include "../usermods/BME280_v2/usermod_bme280.h" #endif #ifdef USERMOD_FOUR_LINE_DISPLAY -#include "../usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h" + #ifdef USE_ALT_DISPlAY + #include "../usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h" + #else + #include "../usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h" + #endif #endif #ifdef USERMOD_ROTARY_ENCODER_UI -#include "../usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h" + #ifdef USE_ALT_DISPlAY + #include "../usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h" + #else + #include "../usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h" + #endif #endif #ifdef USERMOD_AUTO_SAVE #include "../usermods/usermod_v2_auto_save/usermod_v2_auto_save.h" From 46b66c76ef5ce36e7dc2d2f2dbafa8036ddf3d4c Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 2 Oct 2021 10:48:48 +0200 Subject: [PATCH 03/22] Merge pbolduc/WLED/feature/upd-ddp-send into network-bus --- wled00/udp.cpp | 111 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/wled00/udp.cpp b/wled00/udp.cpp index d0b22ec5..43e7e7c9 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -1,4 +1,5 @@ #include "wled.h" +#include "src/dependencies/json/ArduinoJson-v6.h" /* * UDP sync notifier / Realtime / Hyperion / TPM2.NET @@ -89,7 +90,6 @@ void notify(byte callMode, bool followUp) notificationTwoRequired = (followUp)? false:notifyTwice; } - void realtimeLock(uint32_t timeoutMs, byte md) { if (!realtimeMode && !realtimeOverride){ @@ -101,6 +101,10 @@ void realtimeLock(uint32_t timeoutMs, byte md) realtimeTimeout = millis() + timeoutMs; if (timeoutMs == 255001 || timeoutMs == 65000) realtimeTimeout = UINT32_MAX; + // if strip is off (bri==0) and not already in RTM + if (bri == 0 && !realtimeMode) { + strip.setBrightness(scaledBri(briLast)); + } realtimeMode = md; if (arlsForceMaxBri && !realtimeOverride) strip.setBrightness(scaledBri(255)); @@ -514,3 +518,108 @@ void sendSysInfoUDP() notifier2Udp.write(data, sizeof(data)); notifier2Udp.endPacket(); } + + +/*********************************************************************************************\ + * Art-Net, DDP, E131 output - work in progress +\*********************************************************************************************/ + +#define DDP_HEADER_LEN 10 +#define DDP_SYNCPACKET_LEN 10 + +#define DDP_FLAGS1_VER 0xc0 // version mask +#define DDP_FLAGS1_VER1 0x40 // version=1 +#define DDP_FLAGS1_PUSH 0x01 +#define DDP_FLAGS1_QUERY 0x02 +#define DDP_FLAGS1_REPLY 0x04 +#define DDP_FLAGS1_STORAGE 0x08 +#define DDP_FLAGS1_TIME 0x10 + +#define DDP_ID_DISPLAY 1 +#define DDP_ID_CONFIG 250 +#define DDP_ID_STATUS 251 + +// 1440 channels per packet +#define DDP_CHANNELS_PER_PACKET 1440 // 480 leds + +// +// Send real time DDP UDP updates to the specified client +// +// client - the IP address to send to +// length - the number of pixels +// buffer - a buffer of at least length*4 bytes long +// isRGBW - true if the buffer contains 4 components per pixel + +uint8_t sequenceNumber = 0; // this needs to be shared across all outputs + +uint8_t realtimeBroadcast(IPAddress client, uint16_t length, uint8_t *buffer, bool isRGBW) { + WiFiUDP ddpUdp; + + // calclate the number of UDP packets we need to send + uint16_t channelCount = length * 3; // 1 channel for every R,G,B value + uint16_t packetCount = channelCount / DDP_CHANNELS_PER_PACKET; + if (channelCount % DDP_CHANNELS_PER_PACKET) { + packetCount++; + } + + // there are 3 channels per RGB pixel + uint16_t channel = 0; // TODO: allow specifying the start channel + // the current position in the buffer + uint16_t bufferOffset = 0; + + for (uint16_t currentPacket = 0; currentPacket < packetCount; currentPacket++) { + if (sequenceNumber > 15) sequenceNumber = 0; + + int rc = ddpUdp.beginPacket(client, DDP_PORT); + if (rc == 0) { + //DEBUG_PRINTLN("WiFiUDP.beginPacket returned an error"); + return 1; // problem + } + + // the amount of data is AFTER the header in the current packet + uint16_t packetSize = DDP_CHANNELS_PER_PACKET; + + uint8_t flags = DDP_FLAGS1_VER1; + if (currentPacket == (packetCount - 1)) { + // last packet, set the push flag + // TODO: determine if we want to send an empty push packet to each destination after sending the pixel data + flags = DDP_FLAGS1_VER1 | DDP_FLAGS1_PUSH; + if (channelCount % DDP_CHANNELS_PER_PACKET) { + packetSize = channelCount % DDP_CHANNELS_PER_PACKET; + } + } + + // write the header + /*0*/ddpUdp.write(flags); + /*1*/ddpUdp.write(sequenceNumber++ & 0xF); + /*2*/ddpUdp.write(0); + /*3*/ddpUdp.write(DDP_ID_DISPLAY); + // data offset in bytes, 32-bit number, MSB first + /*4*/ddpUdp.write((channel & 0xFF000000) >> 24); + /*5*/ddpUdp.write((channel & 0x00FF0000) >> 16); + /*6*/ddpUdp.write((channel & 0x0000FF00) >> 8); + /*7*/ddpUdp.write((channel & 0x000000FF)); + // data length in bytes, 16-bit number, MSB first + /*8*/ddpUdp.write((packetSize & 0xFF00) >> 8); + /*9*/ddpUdp.write(packetSize & 0xFF); + + // write the colors, the write write(const uint8_t *buffer, size_t size) + // function is just a loop internally too + for (uint16_t i = 0; i < packetSize; i += 3) { + ddpUdp.write(buffer[bufferOffset++]); // R + ddpUdp.write(buffer[bufferOffset++]); // G + ddpUdp.write(buffer[bufferOffset++]); // B + if (isRGBW) bufferOffset++; + } + + rc = ddpUdp.endPacket(); + if (rc == 0) { + //DEBUG_PRINTLN("WiFiUDP.endPacket returned an error"); + return 1; // problem + } + + channel += packetSize; + } + + return 0; +} From c1b08779567476636959ab633c054170bed9b9a7 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 2 Oct 2021 15:07:02 +0200 Subject: [PATCH 04/22] Bus implementation. Added separate DDP listener. LED settings overhaul. Minor fixes: - reduced LED memory - boot brightness fix - reduced debug frequency - added usermod time spent debug - mDNS glitch fix --- wled00/bus_manager.h | 166 +++- wled00/const.h | 8 +- wled00/data/settings_leds.htm | 205 +++-- wled00/data/settings_sync.htm | 2 +- wled00/fcn_declare.h | 4 +- wled00/html_settings.h | 76 +- wled00/html_ui.h | 1551 ++++++++++++++++----------------- wled00/udp.cpp | 82 +- wled00/wled.cpp | 78 +- wled00/wled.h | 19 +- wled00/xml.cpp | 4 +- 11 files changed, 1218 insertions(+), 977 deletions(-) diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 55d929e0..ec77b889 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -10,6 +10,20 @@ #include "bus_wrapper.h" #include +// enable additional debug output +#ifdef WLED_DEBUG + #ifndef ESP8266 + #include + #endif + #define DEBUG_PRINT(x) Serial.print(x) + #define DEBUG_PRINTLN(x) Serial.println(x) + #define DEBUG_PRINTF(x...) Serial.printf(x) +#else + #define DEBUG_PRINT(x) + #define DEBUG_PRINTLN(x) + #define DEBUG_PRINTF(x...) +#endif + //temporary struct for passing bus configuration to bus struct BusConfig { uint8_t type = TYPE_WS2812_RGB; @@ -23,7 +37,8 @@ struct BusConfig { type = busType; count = len; start = pstart; colorOrder = pcolorOrder; reversed = rev; skipAmount = skip; uint8_t nPins = 1; - if (type > 47) nPins = 2; + if (type >= 10 && type <= 15) nPins = 4; + else if (type > 47) nPins = 2; else if (type > 40 && type < 46) nPins = NUM_PWM_PINS(type); for (uint8_t i = 0; i < nPins; i++) pins[i] = ppins[i]; } @@ -74,7 +89,7 @@ class Bus { } virtual uint16_t getLength() { - return 1; + return 1; // is this ok? shouldn't it be 0 in virtual function? } virtual void setColorOrder() {} @@ -135,7 +150,7 @@ class BusDigital : public Bus { _busPtr = PolyBus::create(_iType, _pins, _len, nr); _valid = (_busPtr != nullptr); _colorOrder = bc.colorOrder; - //Serial.printf("Successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u)\n",nr, len, type, pins[0],pins[1],_iType); + DEBUG_PRINTF("Successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u)\n",nr, len, type, pins[0],pins[1],_iType); }; inline void show() { @@ -201,7 +216,7 @@ class BusDigital : public Bus { } void cleanup() { - //Serial.println("Digital Cleanup"); + DEBUG_PRINTLN("Digital Cleanup"); PolyBus::cleanup(_busPtr, _iType); _iType = I_NONE; _valid = false; @@ -227,6 +242,7 @@ class BusDigital : public Bus { class BusPwm : public Bus { public: BusPwm(BusConfig &bc) : Bus(bc.type, bc.start) { + _valid = false; if (!IS_PWM(bc.type)) return; uint8_t numPins = NUM_PWM_PINS(bc.type); @@ -280,10 +296,12 @@ class BusPwm : public Bus { //does no index check uint32_t getPixelColor(uint16_t pix) { + if (!_valid) return 0; return ((_data[3] << 24) | (_data[0] << 16) | (_data[1] << 8) | (_data[2])); } void show() { + if (!_valid) return; uint8_t numPins = NUM_PWM_PINS(_type); for (uint8_t i = 0; i < numPins; i++) { uint8_t scaled = (_data[i] * _bri) / 255; @@ -328,13 +346,13 @@ class BusPwm : public Bus { void deallocatePins() { uint8_t numPins = NUM_PWM_PINS(_type); for (uint8_t i = 0; i < numPins; i++) { + pinManager.deallocatePin(_pins[i], PinOwner::BusPwm); if (!pinManager.isPinOk(_pins[i])) continue; #ifdef ESP8266 digitalWrite(_pins[i], LOW); //turn off PWM interrupt #else if (_ledcStart < 16) ledcDetachPin(_pins[i]); #endif - pinManager.deallocatePin(_pins[i], PinOwner::BusPwm); } #ifdef ARDUINO_ARCH_ESP32 pinManager.deallocateLedc(_ledcStart, numPins); @@ -342,6 +360,133 @@ class BusPwm : public Bus { } }; + +class BusNetwork : public Bus { + public: + BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start) { + _valid = false; +// switch (bc.type) { +// case TYPE_NET_ARTNET_RGB: +// _rgbw = false; +// _UDPtype = 2; +// break; +// case TYPE_NET_E131_RGB: +// _rgbw = false; +// _UDPtype = 1; +// break; +// case TYPE_NET_DDP_RGB: +// _rgbw = false; +// _UDPtype = 0; +// break; +// default: + _rgbw = false; + _UDPtype = bc.type - TYPE_NET_DDP_RGB; +// break; +// } + _UDPchannels = _rgbw ? 4 : 3; + //_rgbw |= bc.rgbwOverride; // RGBW override in bit 7 or can have a special type + _data = (byte *)malloc(bc.count * _UDPchannels); + if (_data == nullptr) return; + memset(_data, 0, bc.count * _UDPchannels); + _len = bc.count; + _colorOrder = bc.colorOrder; + _client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]); + _broadcastLock = false; + _valid = true; + _data2 = (byte *)malloc(_len * _UDPchannels); + }; + + void setPixelColor(uint16_t pix, uint32_t c) { + if (!_valid || pix >= _len) return; + uint16_t offset = pix * _UDPchannels; + _data[offset] = 0xFF & (c >> 16); + _data[offset+1] = 0xFF & (c >> 8); + _data[offset+2] = 0xFF & (c ); + if (_rgbw) _data[offset+3] = 0xFF & (c >> 24); + } + + uint32_t getPixelColor(uint16_t pix) { + if (!_valid || pix >= _len) return 0; + uint16_t offset = pix * _UDPchannels; + // behave as NeoPixelBus + return ( + (_rgbw ? (scale8(_data[offset+3], _bri) << 24) : 0) + | (scale8(_data[offset] , _bri) << 16) + | (scale8(_data[offset+1], _bri) << 8) + | (scale8(_data[offset+2], _bri) ) + ); + } + + void show() { + if (!_valid || !canShow()) return; + _broadcastLock = true; + // apply brightness to second buffer + if (_data2 == nullptr) { + // but display original buffer if memory allocation failed + realtimeBroadcast(_UDPtype, _client, _len, _data, _rgbw); + } else { + for (uint16_t pix=0; pix<_len; pix++) { + uint16_t offset = pix * _UDPchannels; + _data2[offset ] = scale8(_data[offset ], _bri); + _data2[offset+1] = scale8(_data[offset+1], _bri); + _data2[offset+2] = scale8(_data[offset+2], _bri); + if (_rgbw) _data2[offset+3] = scale8(_data[offset+3], _bri); + } + realtimeBroadcast(_UDPtype, _client, _len, _data2, _rgbw); + } + _broadcastLock = false; + } + + inline bool canShow() { + // this should be a return value from UDP routine if it is still sending data out + return !_broadcastLock; + } + + inline void setBrightness(uint8_t b) { + _bri = b; + } + + uint8_t getPins(uint8_t* pinArray) { + for (uint8_t i = 0; i < 4; i++) { + pinArray[i] = _client[i]; + } + return 4; + } + + inline bool isRgbw() { + return _rgbw; + } + + inline uint16_t getLength() { + return _len; + } + + void cleanup() { + _type = I_NONE; + _valid = false; + if (_data != nullptr) free(_data); + _data = nullptr; + if (_data2 != nullptr) free(_data2); + _data2 = nullptr; + } + + ~BusNetwork() { + cleanup(); + } + + private: + IPAddress _client; + uint16_t _len = 0; + uint8_t _colorOrder; + uint8_t _bri = 255; + uint8_t _UDPtype; + uint8_t _UDPchannels; + bool _rgbw; + bool _broadcastLock; + byte *_data, *_data2; +}; + + class BusManager { public: BusManager() { @@ -352,7 +497,7 @@ class BusManager { static uint32_t memUsage(BusConfig &bc) { uint8_t type = bc.type; uint16_t len = bc.count; - if (type < 32) { + if (type > 15 && type < 32) { #ifdef ESP8266 if (bc.pins[0] == 3) { //8266 DMA uses 5x the mem if (type > 29) return len*20; //RGBW @@ -365,15 +510,17 @@ class BusManager { return len*6; #endif } - - if (type > 31 && type < 48) return 5; + if (type >= 10 && type <= 15) return len*6; // double buffer for network + if (type > 31 && type < 48) return 5; if (type == 44 || type == 45) return len*4; //RGBW return len*3; } int add(BusConfig &bc) { if (numBusses >= WLED_MAX_BUSSES) return -1; - if (IS_DIGITAL(bc.type)) { + if (bc.type>=10 && bc.type<=15) { + busses[numBusses] = new BusNetwork(bc); + } else if (IS_DIGITAL(bc.type)) { busses[numBusses] = new BusDigital(bc, numBusses); } else { busses[numBusses] = new BusPwm(bc); @@ -444,6 +591,7 @@ class BusManager { return len; } + // a workaround static inline bool isRgbw(uint8_t type) { return Bus::isRgbw(type); } diff --git a/wled00/const.h b/wled00/const.h index e5061627..ef9f085a 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -122,6 +122,10 @@ #define TYPE_NONE 0 //light is not configured #define TYPE_RESERVED 1 //unused. Might indicate a "virtual" light +//network types (master broadcast) (10-15) +#define TYPE_NET_DDP_RGB 10 //network DDP RGB bus (master broadcast bus) +#define TYPE_NET_E131_RGB 11 //network E131 RGB bus (master broadcast bus) +#define TYPE_NET_ARTNET_RGB 12 //network ArtNet RGB bus (master broadcast bus) //Digital types (data pin only) (16-31) #define TYPE_WS2812_1CH 20 //white-only chips #define TYPE_WS2812_WWA 21 //amber + warm + cold white @@ -241,7 +245,7 @@ #ifndef MAX_LED_MEMORY #ifdef ESP8266 -#define MAX_LED_MEMORY 5000 +#define MAX_LED_MEMORY 4000 #else #define MAX_LED_MEMORY 64000 #endif @@ -282,7 +286,7 @@ // Maximum size of node map (list of other WLED instances) #ifdef ESP8266 - #define WLED_MAX_NODES 15 + #define WLED_MAX_NODES 24 #else #define WLED_MAX_NODES 150 #endif diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 469c72e1..063f59d9 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -35,22 +35,36 @@ var LCs = d.getElementsByTagName("input"); for (i=0; ie==parseInt(LCs[i].value,10))) {alert(`Sorry, pins ${JSON.stringify(d.um_p)} can't be used.`);LCs[i].value="";LCs[i].focus();return false;} else if (LCs[i].value > 5 && LCs[i].value < 12) {alert("Sorry, pins 6-11 can not be used.");LCs[i].value="";LCs[i].focus();return false;} + else if (!(nm == "IR" || nm=="BT") && LCs[i].value > 33) {alert("Sorry, pins >33 are input only.");LCs[i].value="";LCs[i].focus();return false;} for (j=i+1; j 100) {var msg = "Too many LEDs for me to handle!"; if (maxM < 10000) msg += "\n\rConsider using an ESP32."; alert(msg);} @@ -89,21 +103,22 @@ UI(); } //returns mem usage - function getMem(type, len, p0) { - if (type < 32) { + function getMem(t, len, p0) { + if (t >= 10 && t <= 12) return len*6; // double buffer for network UDP bus + if (t > 15 && t < 32) { if (maxM < 10000 && p0==3) { //8266 DMA uses 5x the mem - if (type > 29) return len*20; //RGBW + if (t > 29) return len*20; //RGBW return len*15; } else if (maxM >= 10000) //ESP32 RMT uses double buffer? { - if (type > 29) return len*8; //RGBW + if (t > 29) return len*8; //RGBW return len*6; } - if (type > 29) return len*4; //RGBW + if (t > 29) return len*4; //RGBW return len*3; } - if (type > 31 && type < 48) return 5; - if (type == 44 || type == 45) return len*4; //RGBW + if (t > 31 && t < 48) return 5; + if (t == 44 || t == 45) return len*4; //RGBW return len*3; } function UI(change=false) @@ -115,86 +130,121 @@ if (d.Sf.LA.value == 255) laprev = 12; else if (d.Sf.LA.value > 0) laprev = d.Sf.LA.value; + // enable/disable LED fields var s = d.getElementsByTagName("select"); for (i=0; i 49) ? "Data:" : (type >41) ? "Pins:" : "Pin:"; - gId("p1d"+n).innerHTML = (type > 49) ? "Clk:" : ""; - var LK = d.getElementsByName("L1"+n)[0]; + var n = s[i].name.substring(2); + var t = parseInt(s[i].value,10); + gId("p0d"+n).innerHTML = (t>=10 && t<=15) ? "IP address:" : (t > 49) ? "Data GPIO:" : (t >41) ? "GPIOs:" : "GPIO:"; + gId("p1d"+n).innerHTML = (t > 49) ? "Clk GPIO:" : ""; + var LK = d.getElementsByName("L1"+n)[0]; // clock pin - memu += getMem(type, d.getElementsByName("LC"+n)[0].value, d.getElementsByName("L0"+n)[0].value); + memu += getMem(t, d.getElementsByName("LC"+n)[0].value, d.getElementsByName("L0"+n)[0].value); // calc memory + // enumerate pins for (p=1; p<5; p++) { - var LK = d.getElementsByName("L"+p+n)[0]; + var LK = d.getElementsByName("L"+p+n)[0]; // secondary pins if (!LK) continue; - if ((type>49 && p==1) || (type>41 && type < 50 && (p+40 < type))) // TYPE_xxxx values from const.h + if (((t>=10 && t<=15) && p<4) || (t>49 && p==1) || (t>41 && t < 50 && (p+40 < t))) // TYPE_xxxx values from const.h { + // display pin field LK.style.display = "inline"; LK.required = true; } else { + // hide pin field LK.style.display = "none"; LK.required = false; LK.value=""; } } - if (type == 30 || type == 31 || (type > 40 && type < 46 && type != 43)) isRGBW = true; - gId("dig"+n+"c").style.display = (type > 40 && type < 48) ? "none":"inline"; // hide count for analog - gId("dig"+n+"s").style.display = (type > 40 && type < 48) ? "none":"inline"; // hide skip 1st for virtual & analog - gId("rev"+n).innerHTML = (type > 40 && type < 48) ? "Inverted":"Reverse (rotated 180°)"; // change reverse text for analog - gId("psd"+n).innerHTML = (type > 31 && type < 48) ? "Index:":"Start:"; + if (change) { + gId("ls"+n).value = n+1; // set LED start + if (t > 31 && t < 48) d.getElementsByName("LC"+n)[0].value = 1; // for sanity change analog count just to 1 LED + } + isRGBW |= (t == 30 || t == 31 || (t > 40 && t < 46 && t != 43)); // RGBW checkbox, TYPE_xxxx values from const.h + gId("co"+n).style.display = (t<16 || t == 41 || t == 42) ? "none":"inline"; // hide color order for PWM W & WW/CW + gId("dig"+n+"c").style.display = (t > 40 && t < 48) ? "none":"inline"; // hide count for analog + gId("dig"+n+"r").style.display = (t<16) ? "none":"inline"; // hide reversed for virtual + gId("dig"+n+"s").style.display = (t<16 || (t > 40 && t < 48)) ? "none":"inline"; // hide skip 1st for virtual & analog + gId("rev"+n).innerHTML = (t > 40 && t < 48) ? "Inverted":"Reverse (rotated 180°)"; // change reverse text for analog + gId("psd"+n).innerHTML = (t > 40 && t < 48) ? "Index:":"Start:"; // change analog start description } } - + // display white channel calculation method var myC = d.querySelectorAll('.wc'), l = myC.length; for (i = 0; i < l; i++) { myC[i].style.display = (isRGBW) ? 'inline':'none'; } - + // check for pin conflicts + var LCs = d.getElementsByTagName("input"); + var sLC = 0, maxLC = 0; + for (i=0; imaxLC)maxLC=c;} // increase led count + continue; + } + // do we have led pins for digital leds + if (nm=="L0" || nm=="L1") { + var lc=d.getElementsByName("LC"+n)[0]; + lc.max=maxPB; // update max led count value + } + // ignore IP address + if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3") { + var t = parseInt(d.getElementsByName("LT"+n)[0].value, 10); // LED type SELECT + if (t<16) { + LCs[i].max = 255; + LCs[i].min = 0; + continue; // do not check conflicts + } else { + LCs[i].max = 33; + LCs[i].min = -1; + } + } + // check for pin conflicts + if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4" || nm=="RL" || nm=="BT" || nm=="IR") + if (LCs[i].value!="" && LCs[i].value!="-1") { + var p = []; // used pin array + if (d.um_p && Array.isArray(d.um_p)) for (k=0;ke==parseInt(LCs[i].value,10))) LCs[i].style.color="red"; else LCs[i].style.color=parseInt(LCs[i].value,10)>33?"orange":"#fff"; + } + } + // update total led count + if (gId("LC").readOnly) d.getElementsByName("LC")[0].value = sLC; + // if we are changing total led count update led count for 1st strip if (d.activeElement == d.getElementsByName("LC")[0]) { var o = d.getElementsByClassName("iST"); var i = o.length; if (i == 1) d.getElementsByName("LC0")[0].value = d.getElementsByName("LC")[0].value; } - - var LCs = d.getElementsByTagName("input"); - var sLC = 0, maxLC = 0; - for (i=0; imaxLC)maxLC=c;} - continue; - } - if (nm=="L0" || nm=="L1") { - var lc=d.getElementsByName("LC"+LCs[i].name.substring(2))[0]; - lc.max=maxPB; - } - if (nm=="L0" || nm=="L1" || nm=="L2" || nm=="L3" || nm=="L4" || nm=="RL" || nm=="BT" || nm=="IR") - if (LCs[i].value!="" && LCs[i].value!="-1") { - var p = []; - if (d.um_p && Array.isArray(d.um_p)) for (k=0;ke==parseInt(LCs[i].value,10))) LCs[i].style.color="red"; else LCs[i].style.color="#fff"; - } - } - + // memory usage and warnings gId('m0').innerHTML = memu; bquot = memu / maxM * 100; gId('dbar').style.background = `linear-gradient(90deg, ${bquot > 60 ? (bquot > 90 ? "red":"orange"):"#ccc"} 0 ${bquot}%%, #444 ${bquot}%% 100%%)`; gId('ledwarning').style.display = (sLC > maxPB || maxLC > 800 || bquot > 80) ? 'inline':'none'; gId('ledwarning').style.color = (sLC > maxPB || maxLC > maxPB || bquot > 100) ? 'red':'orange'; - gId('wreason').innerHTML = (bquot > 80) ? "80% of max. LED memory" +(bquot>100 ? ` (WARNING: Using over ${maxM}B!)` : "") : "800 LEDs per pin"; - + gId('wreason').innerHTML = (bquot > 80) ? "80% of max. LED memory" +(bquot>100 ? ` (WARNING: Using over ${maxM}B!)` : "") : "800 LEDs per GPIO"; + // calculate power var val = Math.ceil((100 + sLC * laprev)/500)/2; val = (val > 5) ? Math.ceil(val) : val; var s = ""; @@ -221,8 +271,8 @@ function lastEnd(i) { if (i<1) return 0; v = parseInt(d.getElementsByName("LS"+(i-1))[0].value) + parseInt(d.getElementsByName("LC"+(i-1))[0].value); - var type = parseInt(d.getElementsByName("LT"+(i-1))[0].value); - if (type > 31 && type < 48) v = 1; //PWM busses + var t = parseInt(d.getElementsByName("LT"+(i-1))[0].value); + if (t > 31 && t < 48) v = 1; //PWM busses if (isNaN(v)) return 0; return v; } @@ -239,10 +289,10 @@ if (n==1) { // npm run build has trouble minimizing spaces inside string var cn = `
-${i>0?'
':''} +
${i+1}: - + @@ -255,8 +305,11 @@ ${i+1}: + + +   -Color Order: +
Color Order:
-Pin: -Clock: - - - +

-Start:   +GPIO: + + + + +
+Start:  
Count:

-
Reverse:
  +
Reversed:
 
Skip 1st LED:
+
`; f.insertAdjacentHTML("beforeend", cn); } @@ -292,7 +347,7 @@ Color Order: var c = gId("btns").innerHTML; var bt = "BT" + i; var be = "BE" + i; - c += `Button ${i} pin:  `; + c += `Button ${i} GPIO: `; c += `
+ Total LED count:
Recommended power supply for brightest white:
?

@@ -363,6 +418,7 @@ Color Order:

Hardware setup

LED outputs:
+

LED Memory Usage: 0 / ? B
@@ -371,11 +427,12 @@ Color Order: ⚠ You might run into stability or lag issues.
Use less than 800 LEDs per pin for the best experience!
+
Make a segment for each output:

Touch threshold:
- IR pin:  
IR info
- Relay pin: Invert  ×
+ Relay GPIO: invert  ×

Defaults

Turn LEDs on after power up/reset:
diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index 2ae500a8..2d513979 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -99,7 +99,7 @@ Type:
Port:
diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 3c458a15..4621f4a3 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -195,6 +195,7 @@ bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte //udp.cpp void notify(byte callMode, bool followUp=false); +uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte *buffer, bool isRGBW=false); void realtimeLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC); void handleNotifications(); void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w); @@ -224,14 +225,11 @@ class UsermodManager { public: void loop(); - void setup(); void connected(); - void addToJsonState(JsonObject& obj); void addToJsonInfo(JsonObject& obj); void readFromJsonState(JsonObject& obj); - void addToConfig(JsonObject& obj); bool readFromConfig(JsonObject& obj); void onMqttConnect(bool sessionPresent); diff --git a/wled00/html_settings.h b/wled00/html_settings.h index e15f04fd..ac271896 100644 --- a/wled00/html_settings.h +++ b/wled00/html_settings.h @@ -77,18 +77,19 @@ onclick="B()">Back // Autogenerated from wled00/data/settings_leds.htm, do not edit!! const char PAGE_settings_leds[] PROGMEM = R"=====(LED Settings )====="; +const char PAGE_settingsCss[] PROGMEM = R"=====()====="; // Autogenerated from wled00/data/settings.htm, do not edit!! @@ -77,7 +77,7 @@ onclick="B()">Back // Autogenerated from wled00/data/settings_leds.htm, do not edit!! const char PAGE_settings_leds[] PROGMEM = R"=====(LED Settings