diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b0f64cf2..e377418e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## WLED changelog +#### Build 2409140 +- Configure different kinds of busses at compile (#4107 by @PaoloTK) + - BREAKING: removes LEDPIN and DEFAULT_LED_TYPE compile overrides +- Fetch LED types from Bus classes (dynamic UI) (#4129 by @netmindz, @blazoncek, @dedehai) + #### Build 2409100 - WLED 0.15.0-b5 release - Audioreactive usermod included by default in all compatible builds (including ESP8266) diff --git a/platformio.ini b/platformio.ini index 016d0d18d..4d30be322 100644 --- a/platformio.ini +++ b/platformio.ini @@ -140,7 +140,7 @@ lib_deps = IRremoteESP8266 @ 2.8.2 makuna/NeoPixelBus @ 2.8.0 #https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta - https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 + https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 # for I2C interface ;Wire # ESP-NOW library @@ -234,7 +234,7 @@ lib_deps_compat = IRremoteESP8266 @ 2.8.2 makuna/NeoPixelBus @ 2.7.9 https://github.com/blazoncek/QuickESPNow.git#optional-debug - https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 + https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 [esp32] @@ -470,7 +470,7 @@ board_build.partitions = ${esp32.extended_partitions} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_WROVER -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html - -D LEDPIN=25 + -D DATA_PINS=25 ${esp32.AR_build_flags} lib_deps = ${esp32_idf_V4.lib_deps} ${esp32.AR_lib_deps} @@ -569,7 +569,7 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME= -DLOLIN_WIFI_FIX ; seems to work much better with this -D WLED_WATCHDOG_TIMEOUT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0 - -D LEDPIN=16 + -D DATA_PINS=16 -D HW_PIN_SCL=35 -D HW_PIN_SDA=33 -D HW_PIN_CLOCKSPI=7 diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index 7f6524922..dedc8edf5 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -59,7 +59,7 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; -D WLED_ENABLE_DMX ; ; PIN defines - uncomment and change, if needed: -; -D LEDPIN=2 +; -D DATA_PINS=2 ; or use this for multiple outputs ; -D DATA_PINS=1,3 ; -D BTNPIN=0 @@ -233,7 +233,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D LEDPIN=1 -D WLED_DISABLE_INFRARED +build_flags = ${common.build_flags} ${esp8266.build_flags} -D DATA_PINS=1 -D WLED_DISABLE_INFRARED lib_deps = ${esp8266.lib_deps} [env:esp32dev_qio80] @@ -339,7 +339,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D LEDPIN=12 -D IRPIN=-1 -D RLYPIN=2 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D DATA_PINS=12 -D IRPIN=-1 -D RLYPIN=2 lib_deps = ${esp8266.lib_deps} [env:esp32c3dev_2MB] @@ -367,7 +367,7 @@ platform_packages = ${esp32.platform_packages} upload_speed = 460800 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp32.build_flags} - -D LEDPIN=16 + -D DATA_PINS=16 -D RLYPIN=19 -D BTNPIN=17 -D IRPIN=18 @@ -386,7 +386,7 @@ board_build.partitions = ${esp32.default_partitions} [env:m5atom] board = esp32dev build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32.build_flags} -D LEDPIN=27 -D BTNPIN=39 +build_flags = ${common.build_flags} ${esp32.build_flags} -D DATA_PINS=27 -D BTNPIN=39 lib_deps = ${esp32.lib_deps} platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} @@ -396,14 +396,14 @@ board_build.partitions = ${esp32.default_partitions} board = esp_wroom_02 platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_2m512k} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D LEDPIN=3 -D BTNPIN=1 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D DATA_PINS=3 -D BTNPIN=1 lib_deps = ${esp8266.lib_deps} [env:sp511e] board = esp_wroom_02 platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_2m512k} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D LEDPIN=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D DATA_PINS=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3 lib_deps = ${esp8266.lib_deps} [env:Athom_RGBCW] ;7w and 5w(GU10) bulbs @@ -432,7 +432,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_2m512k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=0 -D RLYPIN=-1 -D LEDPIN=1 -D WLED_DISABLE_INFRARED +build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=0 -D RLYPIN=-1 -D DATA_PINS=1 -D WLED_DISABLE_INFRARED lib_deps = ${esp8266.lib_deps} [env:Athom_4Pin_Controller] ; With clock and data interface @@ -441,7 +441,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_2m512k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=0 -D RLYPIN=12 -D LEDPIN=1 -D WLED_DISABLE_INFRARED +build_flags = ${common.build_flags} ${esp8266.build_flags} -D BTNPIN=0 -D RLYPIN=12 -D DATA_PINS=1 -D WLED_DISABLE_INFRARED lib_deps = ${esp8266.lib_deps} [env:Athom_5Pin_Controller] ;Analog light strip controller @@ -496,7 +496,7 @@ upload_speed = 921600 build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED -D USERMOD_RTC -D USERMOD_ELEKSTUBE_IPS - -D LEDPIN=12 + -D DATA_PINS=12 -D RLYPIN=27 -D BTNPIN=34 -D DEFAULT_LED_COUNT=6 diff --git a/tools/cdata.js b/tools/cdata.js index 12dda1cbe..d65573a8e 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -116,7 +116,8 @@ async function minify(str, type = "plain") { } else if (type == "css-minify") { return new CleanCSS({}).minify(str).styles; } else if (type == "js-minify") { - return await minifyHtml('', options).replace(/<[\/]*script>/g, ''); + let js = await minifyHtml('', options); + return js.replace(/<[\/]*script>/g, ''); } else if (type == "html-minify") { return await minifyHtml(str, options); } @@ -252,6 +253,12 @@ writeChunks( str .replace("%%", "%") }, + { + file: "common.js", + name: "JS_common", + method: "gzip", + filter: "js-minify", + }, { file: "settings.htm", name: "PAGE_settings", diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h index 5ac992f95..d7a9d82a4 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -17,6 +17,8 @@ #define USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL 60000 #endif +static uint16_t mode_temperature(); + class UsermodTemperature : public Usermod { private: @@ -60,6 +62,7 @@ class UsermodTemperature : public Usermod { static const char _sensor[]; static const char _temperature[]; static const char _Temperature[]; + static const char _data_fx[]; //Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013 float readDallas(); @@ -70,8 +73,13 @@ class UsermodTemperature : public Usermod { void publishHomeAssistantAutodiscovery(); #endif + static UsermodTemperature* _instance; // to overcome nonstatic getTemperatureC() method and avoid usermods.lookup(USERMOD_ID_TEMPERATURE); + public: + UsermodTemperature() { _instance = this; } + static UsermodTemperature *getInstance() { return UsermodTemperature::_instance; } + /* * API calls te enable data exchange between WLED modules */ @@ -234,6 +242,7 @@ void UsermodTemperature::setup() { } temperaturePin = -1; // allocation failed } + if (sensorFound && !initDone) strip.addEffect(255, &mode_temperature, _data_fx); } lastMeasurement = millis() - readingInterval + 10000; initDone = true; @@ -440,6 +449,8 @@ const char *UsermodTemperature::getTemperatureUnit() { return degC ? "°C" : "°F"; } +UsermodTemperature* UsermodTemperature::_instance = nullptr; + // strings to reduce flash memory usage (used more than twice) const char UsermodTemperature::_name[] PROGMEM = "Temperature"; const char UsermodTemperature::_enabled[] PROGMEM = "enabled"; @@ -449,4 +460,14 @@ const char UsermodTemperature::_parasitePin[] PROGMEM = "parasite-pwr-pin"; const char UsermodTemperature::_domoticzIDX[] PROGMEM = "domoticz-idx"; const char UsermodTemperature::_sensor[] PROGMEM = "sensor"; const char UsermodTemperature::_temperature[] PROGMEM = "temperature"; -const char UsermodTemperature::_Temperature[] PROGMEM = "/temperature"; \ No newline at end of file +const char UsermodTemperature::_Temperature[] PROGMEM = "/temperature"; +const char UsermodTemperature::_data_fx[] PROGMEM = "Temperature@Min,Max;;!;01;pal=54,sx=255,ix=0"; + +static uint16_t mode_temperature() { + float low = roundf(mapf((float)SEGMENT.speed, 0.f, 255.f, -150.f, 150.f)); // default: 15°C, range: -15°C to 15°C + float high = roundf(mapf((float)SEGMENT.intensity, 0.f, 255.f, 300.f, 600.f)); // default: 30°C, range 30°C to 60°C + float temp = constrain(UsermodTemperature::getInstance()->getTemperatureC()*10.f, low, high); // get a little better resolution (*10) + unsigned i = map(roundf(temp), (unsigned)low, (unsigned)high, 0, 248); + SEGMENT.fill(SEGMENT.color_from_palette(i, false, false, 255)); + return FRAMETIME; +} diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 088ac880b..fde7afded 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -149,7 +149,6 @@ static bool useBandPassFilter = false; // if true, enables a //////////////////// // some prototypes, to ensure consistent interfaces -static float mapf(float x, float in_min, float in_max, float out_min, float out_max); // map function for float static float fftAddAvg(int from, int to); // average of several FFT result bins void FFTcode(void * parameter); // audio processing task: read samples, run FFT, fill GEQ channels from FFT results static void runMicFilter(uint16_t numSamples, float *sampleBuffer); // pre-filtering of raw samples (band-pass) @@ -211,11 +210,6 @@ static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMP // Helper functions -// float version of map() -static float mapf(float x, float in_min, float in_max, float out_min, float out_max){ - return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; -} - // compute average of several FFT result bins static float fftAddAvg(int from, int to) { float result = 0.0f; diff --git a/usermods/boblight/boblight.h b/usermods/boblight/boblight.h index 32208a4fa..916f7da98 100644 --- a/usermods/boblight/boblight.h +++ b/usermods/boblight/boblight.h @@ -1,459 +1,459 @@ -#pragma once - -#include "wled.h" - -/* - * Usermod that implements BobLight "ambilight" protocol - * - * See the accompanying README.md file for more info. - */ - -#ifndef BOB_PORT - #define BOB_PORT 19333 // Default boblightd port -#endif - -class BobLightUsermod : public Usermod { - typedef struct _LIGHT { - char lightname[5]; - float hscan[2]; - float vscan[2]; - } light_t; - - private: - unsigned long lastTime = 0; - bool enabled = false; - bool initDone = false; - - light_t *lights = nullptr; - uint16_t numLights = 0; // 16 + 9 + 16 + 9 - uint16_t top, bottom, left, right; // will be filled in readFromConfig() - uint16_t pct; - - WiFiClient bobClient; - WiFiServer *bob; - uint16_t bobPort = BOB_PORT; - - static const char _name[]; - static const char _enabled[]; - - /* - # boblight - # Copyright (C) Bob 2009 - # - # makeboblight.sh created by Adam Boeglin - # - # boblight is free software: you can redistribute it and/or modify it - # under the terms of the GNU General Public License as published by the - # Free Software Foundation, either version 3 of the License, or - # (at your option) any later version. - # - # boblight is distributed in the hope that it will be useful, but - # WITHOUT ANY WARRANTY; without even the implied warranty of - # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - # See the GNU General Public License for more details. - # - # You should have received a copy of the GNU General Public License along - # with this program. If not, see . - */ - - // fills the lights[] array with position & depth of scan for each LED - void fillBobLights(int bottom, int left, int top, int right, float pct_scan) { - - int lightcount = 0; - int total = top+left+right+bottom; - int bcount; - - if (total > strip.getLengthTotal()) { - DEBUG_PRINTLN(F("BobLight: Too many lights.")); - return; - } - - // start left part of bottom strip (clockwise direction, 1st half) - if (bottom > 0) { - bcount = 1; - float brange = 100.0/bottom; - float bcurrent = 50.0; - if (bottom < top) { - int diff = top - bottom; - brange = 100.0/top; - bcurrent -= (diff/2)*brange; - } - while (bcount <= bottom/2) { - float btop = bcurrent - brange; - String name = "b"+String(bcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); - lights[lightcount].hscan[0] = btop; - lights[lightcount].hscan[1] = bcurrent; - lights[lightcount].vscan[0] = 100 - pct_scan; - lights[lightcount].vscan[1] = 100; - lightcount+=1; - bcurrent = btop; - bcount+=1; - } - } - - // left side - if (left > 0) { - int lcount = 1; - float lrange = 100.0/left; - float lcurrent = 100.0; - while (lcount <= left) { - float ltop = lcurrent - lrange; - String name = "l"+String(lcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); - lights[lightcount].hscan[0] = 0; - lights[lightcount].hscan[1] = pct_scan; - lights[lightcount].vscan[0] = ltop; - lights[lightcount].vscan[1] = lcurrent; - lightcount+=1; - lcurrent = ltop; - lcount+=1; - } - } - - // top side - if (top > 0) { - int tcount = 1; - float trange = 100.0/top; - float tcurrent = 0; - while (tcount <= top) { - float ttop = tcurrent + trange; - String name = "t"+String(tcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); - lights[lightcount].hscan[0] = tcurrent; - lights[lightcount].hscan[1] = ttop; - lights[lightcount].vscan[0] = 0; - lights[lightcount].vscan[1] = pct_scan; - lightcount+=1; - tcurrent = ttop; - tcount+=1; - } - } - - // right side - if (right > 0) { - int rcount = 1; - float rrange = 100.0/right; - float rcurrent = 0; - while (rcount <= right) { - float rtop = rcurrent + rrange; - String name = "r"+String(rcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); - lights[lightcount].hscan[0] = 100-pct_scan; - lights[lightcount].hscan[1] = 100; - lights[lightcount].vscan[0] = rcurrent; - lights[lightcount].vscan[1] = rtop; - lightcount+=1; - rcurrent = rtop; - rcount+=1; - } - } - - // right side of bottom strip (2nd half) - if (bottom > 0) { - float brange = 100.0/bottom; - float bcurrent = 100; - if (bottom < top) { - brange = 100.0/top; - } - while (bcount <= bottom) { - float btop = bcurrent - brange; - String name = "b"+String(bcount); - strncpy(lights[lightcount].lightname, name.c_str(), 4); - lights[lightcount].hscan[0] = btop; - lights[lightcount].hscan[1] = bcurrent; - lights[lightcount].vscan[0] = 100 - pct_scan; - lights[lightcount].vscan[1] = 100; - lightcount+=1; - bcurrent = btop; - bcount+=1; - } - } - - numLights = lightcount; - - #if WLED_DEBUG - DEBUG_PRINTLN(F("Fill light data: ")); - DEBUG_PRINTF_P(PSTR(" lights %d\n"), numLights); - for (int i=0; i strip.getLengthTotal() ) { - DEBUG_PRINTLN(F("BobLight: Too many lights.")); - DEBUG_PRINTF_P(PSTR("%d+%d+%d+%d>%d\n"), bottom, left, top, right, strip.getLengthTotal()); - totalLights = strip.getLengthTotal(); - top = bottom = (uint16_t) roundf((float)totalLights * 16.0f / 50.0f); - left = right = (uint16_t) roundf((float)totalLights * 9.0f / 50.0f); - } - lights = new light_t[totalLights]; - if (lights) fillBobLights(bottom, left, top, right, float(pct)); // will fill numLights - else enable(false); - initDone = true; - } - - void connected() override { - // we can only start server when WiFi is connected - if (!bob) bob = new WiFiServer(bobPort, 1); - bob->begin(); - bob->setNoDelay(true); - } - - void loop() override { - if (!enabled || strip.isUpdating()) return; - if (millis() - lastTime > 10) { - lastTime = millis(); - pollBob(); - } - } - - void enable(bool en) { enabled = en; } - -#ifndef WLED_DISABLE_MQTT - /** - * handling of MQTT message - * topic only contains stripped topic (part after /wled/MAC) - * topic should look like: /swipe with amessage of [up|down] - */ - bool onMqttMessage(char* topic, char* payload) override { - //if (strlen(topic) == 6 && strncmp_P(topic, PSTR("/subtopic"), 6) == 0) { - // String action = payload; - // if (action == "on") { - // enable(true); - // return true; - // } else if (action == "off") { - // enable(false); - // return true; - // } - //} - return false; - } - - /** - * subscribe to MQTT topic for controlling usermod - */ - void onMqttConnect(bool sessionPresent) override { - //char subuf[64]; - //if (mqttDeviceTopic[0] != 0) { - // strcpy(subuf, mqttDeviceTopic); - // strcat_P(subuf, PSTR("/subtopic")); - // mqtt->subscribe(subuf, 0); - //} - } -#endif - - void addToJsonInfo(JsonObject& root) override - { - JsonObject user = root["u"]; - if (user.isNull()) user = root.createNestedObject("u"); - - JsonArray infoArr = user.createNestedArray(FPSTR(_name)); - String uiDomString = F(""); - infoArr.add(uiDomString); - } - - /* - * 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) override - { - } - - /* - * 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) override { - if (!initDone) return; // prevent crash on boot applyPreset() - bool en = enabled; - JsonObject um = root[FPSTR(_name)]; - if (!um.isNull()) { - if (um[FPSTR(_enabled)].is()) { - en = um[FPSTR(_enabled)].as(); - } else { - String str = um[FPSTR(_enabled)]; // checkbox -> off or on - en = (bool)(str!="off"); // off is guaranteed to be present - } - if (en != enabled && lights) { - enable(en); - if (!enabled && bob && bob->hasClient()) { - if (bobClient) bobClient.stop(); - bobClient = bob->available(); - BobClear(); - exitRealtime(); - } - } - } - } - - void appendConfigData() override { - //oappend(SET_F("dd=addDropdown('usermod','selectfield');")); - //oappend(SET_F("addOption(dd,'1st value',0);")); - //oappend(SET_F("addOption(dd,'2nd value',1);")); - oappend(SET_F("addInfo('BobLight:top',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:bottom',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:left',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:right',1,'LEDs');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('BobLight:pct',1,'Depth of scan [%]');")); // 0 is field type, 1 is actual field - } - - void addToConfig(JsonObject& root) override { - JsonObject umData = root.createNestedObject(FPSTR(_name)); - umData[FPSTR(_enabled)] = enabled; - umData[ "port" ] = bobPort; - umData[F("top")] = top; - umData[F("bottom")] = bottom; - umData[F("left")] = left; - umData[F("right")] = right; - umData[F("pct")] = pct; - } - - bool readFromConfig(JsonObject& root) override { - JsonObject umData = root[FPSTR(_name)]; - bool configComplete = !umData.isNull(); - - bool en = enabled; - configComplete &= getJsonValue(umData[FPSTR(_enabled)], en); - enable(en); - - configComplete &= getJsonValue(umData[ "port" ], bobPort); - configComplete &= getJsonValue(umData[F("bottom")], bottom, 16); - configComplete &= getJsonValue(umData[F("top")], top, 16); - configComplete &= getJsonValue(umData[F("left")], left, 9); - configComplete &= getJsonValue(umData[F("right")], right, 9); - configComplete &= getJsonValue(umData[F("pct")], pct, 5); // Depth of scan [%] - pct = MIN(50,MAX(1,pct)); - - uint16_t totalLights = bottom + left + top + right; - if (initDone && numLights != totalLights) { - if (lights) delete[] lights; - setup(); - } - return configComplete; - } - - /* - * handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors. - * Use this to blank out some LEDs or set them to a different color regardless of the set effect mode. - * Commonly used for custom clocks (Cronixie, 7 segment) - */ - void handleOverlayDraw() override { - //strip.setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black - } - - uint16_t getId() override { return USERMOD_ID_BOBLIGHT; } - -}; - -// strings to reduce flash memory usage (used more than twice) -const char BobLightUsermod::_name[] PROGMEM = "BobLight"; -const char BobLightUsermod::_enabled[] PROGMEM = "enabled"; - -// main boblight handling (definition here prevents inlining) -void BobLightUsermod::pollBob() { - - //check if there are any new clients - if (bob && bob->hasClient()) { - //find free/disconnected spot - if (!bobClient || !bobClient.connected()) { - if (bobClient) bobClient.stop(); - bobClient = bob->available(); - DEBUG_PRINTLN(F("Boblight: Client connected.")); - } - //no free/disconnected spot so reject - WiFiClient bobClientTmp = bob->available(); - bobClientTmp.stop(); - BobClear(); - exitRealtime(); - } - - //check clients for data - if (bobClient && bobClient.connected()) { - realtimeLock(realtimeTimeoutMs); // lock strip as we have a client connected - - //get data from the client - while (bobClient.available()) { - String input = bobClient.readStringUntil('\n'); - // DEBUG_PRINT(F("Client: ")); DEBUG_PRINTLN(input); // may be to stressful on Serial - if (input.startsWith(F("hello"))) { - DEBUG_PRINTLN(F("hello")); - bobClient.print(F("hello\n")); - } else if (input.startsWith(F("ping"))) { - DEBUG_PRINTLN(F("ping 1")); - bobClient.print(F("ping 1\n")); - } else if (input.startsWith(F("get version"))) { - DEBUG_PRINTLN(F("version 5")); - bobClient.print(F("version 5\n")); - } else if (input.startsWith(F("get lights"))) { - char tmp[64]; - String answer = ""; - sprintf_P(tmp, PSTR("lights %d\n"), numLights); - DEBUG_PRINT(tmp); - answer.concat(tmp); - for (int i=0; i ... - input.remove(0,10); - String tmp = input.substring(0,input.indexOf(' ')); - - int light_id = -1; - for (uint16_t i=0; iavailable(); - BobClear(); - } - } - } -} +#pragma once + +#include "wled.h" + +/* + * Usermod that implements BobLight "ambilight" protocol + * + * See the accompanying README.md file for more info. + */ + +#ifndef BOB_PORT + #define BOB_PORT 19333 // Default boblightd port +#endif + +class BobLightUsermod : public Usermod { + typedef struct _LIGHT { + char lightname[5]; + float hscan[2]; + float vscan[2]; + } light_t; + + private: + unsigned long lastTime = 0; + bool enabled = false; + bool initDone = false; + + light_t *lights = nullptr; + uint16_t numLights = 0; // 16 + 9 + 16 + 9 + uint16_t top, bottom, left, right; // will be filled in readFromConfig() + uint16_t pct; + + WiFiClient bobClient; + WiFiServer *bob; + uint16_t bobPort = BOB_PORT; + + static const char _name[]; + static const char _enabled[]; + + /* + # boblight + # Copyright (C) Bob 2009 + # + # makeboblight.sh created by Adam Boeglin + # + # boblight is free software: you can redistribute it and/or modify it + # under the terms of the GNU General Public License as published by the + # Free Software Foundation, either version 3 of the License, or + # (at your option) any later version. + # + # boblight is distributed in the hope that it will be useful, but + # WITHOUT ANY WARRANTY; without even the implied warranty of + # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + # See the GNU General Public License for more details. + # + # You should have received a copy of the GNU General Public License along + # with this program. If not, see . + */ + + // fills the lights[] array with position & depth of scan for each LED + void fillBobLights(int bottom, int left, int top, int right, float pct_scan) { + + int lightcount = 0; + int total = top+left+right+bottom; + int bcount; + + if (total > strip.getLengthTotal()) { + DEBUG_PRINTLN(F("BobLight: Too many lights.")); + return; + } + + // start left part of bottom strip (clockwise direction, 1st half) + if (bottom > 0) { + bcount = 1; + float brange = 100.0/bottom; + float bcurrent = 50.0; + if (bottom < top) { + int diff = top - bottom; + brange = 100.0/top; + bcurrent -= (diff/2)*brange; + } + while (bcount <= bottom/2) { + float btop = bcurrent - brange; + String name = "b"+String(bcount); + strncpy(lights[lightcount].lightname, name.c_str(), 4); + lights[lightcount].hscan[0] = btop; + lights[lightcount].hscan[1] = bcurrent; + lights[lightcount].vscan[0] = 100 - pct_scan; + lights[lightcount].vscan[1] = 100; + lightcount+=1; + bcurrent = btop; + bcount+=1; + } + } + + // left side + if (left > 0) { + int lcount = 1; + float lrange = 100.0/left; + float lcurrent = 100.0; + while (lcount <= left) { + float ltop = lcurrent - lrange; + String name = "l"+String(lcount); + strncpy(lights[lightcount].lightname, name.c_str(), 4); + lights[lightcount].hscan[0] = 0; + lights[lightcount].hscan[1] = pct_scan; + lights[lightcount].vscan[0] = ltop; + lights[lightcount].vscan[1] = lcurrent; + lightcount+=1; + lcurrent = ltop; + lcount+=1; + } + } + + // top side + if (top > 0) { + int tcount = 1; + float trange = 100.0/top; + float tcurrent = 0; + while (tcount <= top) { + float ttop = tcurrent + trange; + String name = "t"+String(tcount); + strncpy(lights[lightcount].lightname, name.c_str(), 4); + lights[lightcount].hscan[0] = tcurrent; + lights[lightcount].hscan[1] = ttop; + lights[lightcount].vscan[0] = 0; + lights[lightcount].vscan[1] = pct_scan; + lightcount+=1; + tcurrent = ttop; + tcount+=1; + } + } + + // right side + if (right > 0) { + int rcount = 1; + float rrange = 100.0/right; + float rcurrent = 0; + while (rcount <= right) { + float rtop = rcurrent + rrange; + String name = "r"+String(rcount); + strncpy(lights[lightcount].lightname, name.c_str(), 4); + lights[lightcount].hscan[0] = 100-pct_scan; + lights[lightcount].hscan[1] = 100; + lights[lightcount].vscan[0] = rcurrent; + lights[lightcount].vscan[1] = rtop; + lightcount+=1; + rcurrent = rtop; + rcount+=1; + } + } + + // right side of bottom strip (2nd half) + if (bottom > 0) { + float brange = 100.0/bottom; + float bcurrent = 100; + if (bottom < top) { + brange = 100.0/top; + } + while (bcount <= bottom) { + float btop = bcurrent - brange; + String name = "b"+String(bcount); + strncpy(lights[lightcount].lightname, name.c_str(), 4); + lights[lightcount].hscan[0] = btop; + lights[lightcount].hscan[1] = bcurrent; + lights[lightcount].vscan[0] = 100 - pct_scan; + lights[lightcount].vscan[1] = 100; + lightcount+=1; + bcurrent = btop; + bcount+=1; + } + } + + numLights = lightcount; + + #if WLED_DEBUG + DEBUG_PRINTLN(F("Fill light data: ")); + DEBUG_PRINTF_P(PSTR(" lights %d\n"), numLights); + for (int i=0; i strip.getLengthTotal() ) { + DEBUG_PRINTLN(F("BobLight: Too many lights.")); + DEBUG_PRINTF_P(PSTR("%d+%d+%d+%d>%d\n"), bottom, left, top, right, strip.getLengthTotal()); + totalLights = strip.getLengthTotal(); + top = bottom = (uint16_t) roundf((float)totalLights * 16.0f / 50.0f); + left = right = (uint16_t) roundf((float)totalLights * 9.0f / 50.0f); + } + lights = new light_t[totalLights]; + if (lights) fillBobLights(bottom, left, top, right, float(pct)); // will fill numLights + else enable(false); + initDone = true; + } + + void connected() override { + // we can only start server when WiFi is connected + if (!bob) bob = new WiFiServer(bobPort, 1); + bob->begin(); + bob->setNoDelay(true); + } + + void loop() override { + if (!enabled || strip.isUpdating()) return; + if (millis() - lastTime > 10) { + lastTime = millis(); + pollBob(); + } + } + + void enable(bool en) { enabled = en; } + +#ifndef WLED_DISABLE_MQTT + /** + * handling of MQTT message + * topic only contains stripped topic (part after /wled/MAC) + * topic should look like: /swipe with amessage of [up|down] + */ + bool onMqttMessage(char* topic, char* payload) override { + //if (strlen(topic) == 6 && strncmp_P(topic, PSTR("/subtopic"), 6) == 0) { + // String action = payload; + // if (action == "on") { + // enable(true); + // return true; + // } else if (action == "off") { + // enable(false); + // return true; + // } + //} + return false; + } + + /** + * subscribe to MQTT topic for controlling usermod + */ + void onMqttConnect(bool sessionPresent) override { + //char subuf[64]; + //if (mqttDeviceTopic[0] != 0) { + // strcpy(subuf, mqttDeviceTopic); + // strcat_P(subuf, PSTR("/subtopic")); + // mqtt->subscribe(subuf, 0); + //} + } +#endif + + void addToJsonInfo(JsonObject& root) override + { + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray infoArr = user.createNestedArray(FPSTR(_name)); + String uiDomString = F(""); + infoArr.add(uiDomString); + } + + /* + * 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) override + { + } + + /* + * 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) override { + if (!initDone) return; // prevent crash on boot applyPreset() + bool en = enabled; + JsonObject um = root[FPSTR(_name)]; + if (!um.isNull()) { + if (um[FPSTR(_enabled)].is()) { + en = um[FPSTR(_enabled)].as(); + } else { + String str = um[FPSTR(_enabled)]; // checkbox -> off or on + en = (bool)(str!="off"); // off is guaranteed to be present + } + if (en != enabled && lights) { + enable(en); + if (!enabled && bob && bob->hasClient()) { + if (bobClient) bobClient.stop(); + bobClient = bob->available(); + BobClear(); + exitRealtime(); + } + } + } + } + + void appendConfigData() override { + //oappend(SET_F("dd=addDropdown('usermod','selectfield');")); + //oappend(SET_F("addOption(dd,'1st value',0);")); + //oappend(SET_F("addOption(dd,'2nd value',1);")); + oappend(SET_F("addInfo('BobLight:top',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('BobLight:bottom',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('BobLight:left',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('BobLight:right',1,'LEDs');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('BobLight:pct',1,'Depth of scan [%]');")); // 0 is field type, 1 is actual field + } + + void addToConfig(JsonObject& root) override { + JsonObject umData = root.createNestedObject(FPSTR(_name)); + umData[FPSTR(_enabled)] = enabled; + umData[ "port" ] = bobPort; + umData[F("top")] = top; + umData[F("bottom")] = bottom; + umData[F("left")] = left; + umData[F("right")] = right; + umData[F("pct")] = pct; + } + + bool readFromConfig(JsonObject& root) override { + JsonObject umData = root[FPSTR(_name)]; + bool configComplete = !umData.isNull(); + + bool en = enabled; + configComplete &= getJsonValue(umData[FPSTR(_enabled)], en); + enable(en); + + configComplete &= getJsonValue(umData[ "port" ], bobPort); + configComplete &= getJsonValue(umData[F("bottom")], bottom, 16); + configComplete &= getJsonValue(umData[F("top")], top, 16); + configComplete &= getJsonValue(umData[F("left")], left, 9); + configComplete &= getJsonValue(umData[F("right")], right, 9); + configComplete &= getJsonValue(umData[F("pct")], pct, 5); // Depth of scan [%] + pct = MIN(50,MAX(1,pct)); + + uint16_t totalLights = bottom + left + top + right; + if (initDone && numLights != totalLights) { + if (lights) delete[] lights; + setup(); + } + return configComplete; + } + + /* + * handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors. + * Use this to blank out some LEDs or set them to a different color regardless of the set effect mode. + * Commonly used for custom clocks (Cronixie, 7 segment) + */ + void handleOverlayDraw() override { + //strip.setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black + } + + uint16_t getId() override { return USERMOD_ID_BOBLIGHT; } + +}; + +// strings to reduce flash memory usage (used more than twice) +const char BobLightUsermod::_name[] PROGMEM = "BobLight"; +const char BobLightUsermod::_enabled[] PROGMEM = "enabled"; + +// main boblight handling (definition here prevents inlining) +void BobLightUsermod::pollBob() { + + //check if there are any new clients + if (bob && bob->hasClient()) { + //find free/disconnected spot + if (!bobClient || !bobClient.connected()) { + if (bobClient) bobClient.stop(); + bobClient = bob->available(); + DEBUG_PRINTLN(F("Boblight: Client connected.")); + } + //no free/disconnected spot so reject + WiFiClient bobClientTmp = bob->available(); + bobClientTmp.stop(); + BobClear(); + exitRealtime(); + } + + //check clients for data + if (bobClient && bobClient.connected()) { + realtimeLock(realtimeTimeoutMs); // lock strip as we have a client connected + + //get data from the client + while (bobClient.available()) { + String input = bobClient.readStringUntil('\n'); + // DEBUG_PRINT(F("Client: ")); DEBUG_PRINTLN(input); // may be to stressful on Serial + if (input.startsWith(F("hello"))) { + DEBUG_PRINTLN(F("hello")); + bobClient.print(F("hello\n")); + } else if (input.startsWith(F("ping"))) { + DEBUG_PRINTLN(F("ping 1")); + bobClient.print(F("ping 1\n")); + } else if (input.startsWith(F("get version"))) { + DEBUG_PRINTLN(F("version 5")); + bobClient.print(F("version 5\n")); + } else if (input.startsWith(F("get lights"))) { + char tmp[64]; + String answer = ""; + sprintf_P(tmp, PSTR("lights %d\n"), numLights); + DEBUG_PRINT(tmp); + answer.concat(tmp); + for (int i=0; i ... + input.remove(0,10); + String tmp = input.substring(0,input.indexOf(' ')); + + int light_id = -1; + for (uint16_t i=0; iavailable(); + BobClear(); + } + } + } +} diff --git a/usermods/mqtt_switch_v2/README.md b/usermods/mqtt_switch_v2/README.md index 744d7fe3c..4cb7ef0e8 100644 --- a/usermods/mqtt_switch_v2/README.md +++ b/usermods/mqtt_switch_v2/README.md @@ -33,7 +33,7 @@ board = esp12e platform = ${common.platform_wled_default} board_build.ldscript = ${common.ldscript_4m1m} build_flags = ${common.build_flags_esp8266} - -D LEDPIN=3 + -D DATA_PINS=3 -D BTNPIN=4 -D RLYPIN=12 -D RLYMDE=1 diff --git a/usermods/usermod_v2_HttpPullLightControl/readme.md b/usermods/usermod_v2_HttpPullLightControl/readme.md index cf7f971f7..eb56d505d 100644 --- a/usermods/usermod_v2_HttpPullLightControl/readme.md +++ b/usermods/usermod_v2_HttpPullLightControl/readme.md @@ -93,7 +93,7 @@ After getting the URL (it can be a static file like static.json or a mylogic.php - -D ABL_MILLIAMPS_DEFAULT=450 - -D DEFAULT_LED_COUNT=60 ; For a LED Ring of 60 LEDs - -D BTNPIN=41 ; The M5Stack Atom S3 Lite has a button on GPIO41 -- -D LEDPIN=2 ; The M5Stack Atom S3 Lite has a Grove connector on the front, we use this GPIO2 +- -D DATA_PINS=2 ; The M5Stack Atom S3 Lite has a Grove connector on the front, we use this GPIO2 - -D STATUSLED=35 ; The M5Stack Atom S3 Lite has a Multi-Color LED on GPIO35, although I didnt managed to control it - -D IRPIN=4 ; The M5Stack Atom S3 Lite has a IR LED on GPIO4 diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 807594e43..0084b09e0 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -6483,11 +6483,6 @@ static const char _data_FX_MODE_2DWAVERLY[] PROGMEM = "Waverly@Amplification,Sen #endif // WLED_DISABLE_2D -// float version of map() -static float mapf(float x, float in_min, float in_max, float out_min, float out_max){ - return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; -} - // Gravity struct requited for GRAV* effects typedef struct Gravity { int topLED; diff --git a/wled00/FX.h b/wled00/FX.h index a9de2a189..3c28274d6 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -573,7 +573,7 @@ typedef struct Segment { [[gnu::hot]] uint8_t currentBri(bool useCct = false) const; // current segment brightness/CCT (blended while in transition) uint8_t currentMode() const; // currently active effect/mode (while in transition) [[gnu::hot]] uint32_t currentColor(uint8_t slot) const; // currently active segment color (blended while in transition) - [[gnu::hot]] CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); + CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); void setCurrentPalette(); // 1D strip @@ -615,7 +615,7 @@ typedef struct Segment { // 2D matrix [[gnu::hot]] uint16_t virtualWidth() const; // segment width in virtual pixels (accounts for groupping and spacing) [[gnu::hot]] uint16_t virtualHeight() const; // segment height in virtual pixels (accounts for groupping and spacing) - [[gnu::hot]] uint16_t nrOfVStrips() const; // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) + uint16_t nrOfVStrips() const; // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) #ifndef WLED_DISABLE_2D [[gnu::hot]] uint16_t XY(int x, int y); // support function to get relative index within segment [[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 7e8bd6471..236f7ad4a 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -43,21 +43,16 @@ 19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25]} */ -//factory defaults LED setup -//#define PIXEL_COUNTS 30, 30, 30, 30 -//#define DATA_PINS 16, 1, 3, 4 -//#define DEFAULT_LED_TYPE TYPE_WS2812_RGB - #ifndef PIXEL_COUNTS #define PIXEL_COUNTS DEFAULT_LED_COUNT #endif #ifndef DATA_PINS - #define DATA_PINS LEDPIN + #define DATA_PINS DEFAULT_LED_PIN #endif -#ifndef DEFAULT_LED_TYPE - #define DEFAULT_LED_TYPE TYPE_WS2812_RGB +#ifndef LED_TYPES + #define LED_TYPES DEFAULT_LED_TYPE #endif #ifndef DEFAULT_LED_COLOR_ORDER @@ -69,6 +64,18 @@ #error "Max segments must be at least max number of busses!" #endif +static constexpr unsigned sumPinsRequired(const unsigned* current, size_t count) { + return (count > 0) ? (Bus::getNumberOfPins(*current) + sumPinsRequired(current+1,count-1)) : 0; +} + +static constexpr bool validatePinsAndTypes(const unsigned* types, unsigned numTypes, unsigned numPins ) { + // Pins provided < pins required -> always invalid + // Pins provided = pins required -> always valid + // Pins provided > pins required -> valid if excess pins are a product of last type pins since it will be repeated + return (sumPinsRequired(types, numTypes) > numPins) ? false : + (numPins - sumPinsRequired(types, numTypes)) % Bus::getNumberOfPins(types[numTypes-1]) == 0; +} + /////////////////////////////////////////////////////////////////////////////// // Segment class implementation @@ -202,7 +209,7 @@ void Segment::resetIfRequired() { reset = false; } -CRGBPalette16 IRAM_ATTR_YN &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { +CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // TODO remove strip dependency by moving customPalettes out of strip //default palette. Differs depending on effect @@ -417,7 +424,7 @@ uint8_t IRAM_ATTR Segment::currentBri(bool useCct) const { return (useCct ? cct : (on ? opacity : 0)); } -uint8_t IRAM_ATTR_YN Segment::currentMode() const { +uint8_t Segment::currentMode() const { #ifndef WLED_DISABLE_MODE_BLEND unsigned prog = progress(); if (modeBlending && prog < 0xFFFFU) return _t->_modeT; @@ -1222,28 +1229,82 @@ void WS2812FX::finalizeInit() { //if busses failed to load, add default (fresh install, FS issue, ...) if (BusManager::getNumBusses() == 0) { DEBUG_PRINTLN(F("No busses, init default")); - const unsigned defDataPins[] = {DATA_PINS}; - const unsigned defCounts[] = {PIXEL_COUNTS}; - const unsigned defNumPins = ((sizeof defDataPins) / (sizeof defDataPins[0])); - const unsigned defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0])); - // if number of pins is divisible by counts, use number of counts to determine number of buses, otherwise use pins - const unsigned defNumBusses = defNumPins > defNumCounts && defNumPins%defNumCounts == 0 ? defNumCounts : defNumPins; - const unsigned pinsPerBus = defNumPins / defNumBusses; + constexpr unsigned defDataTypes[] = {LED_TYPES}; + constexpr unsigned defDataPins[] = {DATA_PINS}; + constexpr unsigned defCounts[] = {PIXEL_COUNTS}; + constexpr unsigned defNumTypes = ((sizeof defDataTypes) / (sizeof defDataTypes[0])); + constexpr unsigned defNumPins = ((sizeof defDataPins) / (sizeof defDataPins[0])); + constexpr unsigned defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0])); + + static_assert(validatePinsAndTypes(defDataTypes, defNumTypes, defNumPins), + "The default pin list defined in DATA_PINS does not match the pin requirements for the default buses defined in LED_TYPES"); + unsigned prevLen = 0; - for (unsigned i = 0; i < defNumBusses && i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { - uint8_t defPin[5]; // max 5 pins - for (unsigned j = 0; j < pinsPerBus; j++) defPin[j] = defDataPins[i*pinsPerBus + j]; - // when booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware - // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), etc - if (pinManager.isPinAllocated(defPin[0])) { - defPin[0] = 1; // start with GPIO1 and work upwards - while (pinManager.isPinAllocated(defPin[0]) && defPin[0] < WLED_NUM_PINS) defPin[0]++; + unsigned pinsIndex = 0; + for (unsigned i = 0; i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { + uint8_t defPin[OUTPUT_MAX_PINS]; + // if we have less types than requested outputs and they do not align, use last known type to set current type + unsigned dataType = defDataTypes[(i < defNumTypes) ? i : defNumTypes -1]; + unsigned busPins = Bus::getNumberOfPins(dataType); + + // if we need more pins than available all outputs have been configured + if (pinsIndex + busPins > defNumPins) break; + + // Assign all pins first so we can check for conflicts on this bus + for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) defPin[j] = defDataPins[pinsIndex + j]; + + for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) { + bool validPin = true; + // When booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware + // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), read/only pins, etc. + // Pin should not be already allocated, read/only or defined for current bus + while (pinManager.isPinAllocated(defPin[j]) || !pinManager.isPinOk(defPin[j],true)) { + if (validPin) { + DEBUG_PRINTLN(F("Some of the provided pins cannot be used to configure this LED output.")); + defPin[j] = 1; // start with GPIO1 and work upwards + validPin = false; + } else if (defPin[j] < WLED_NUM_PINS) { + defPin[j]++; + } else { + DEBUG_PRINTLN(F("No available pins left! Can't configure output.")); + return; + } + // is the newly assigned pin already defined or used previously? + // try next in line until there are no clashes or we run out of pins + bool clash; + do { + clash = false; + // check for conflicts on current bus + for (const auto &pin : defPin) { + if (&pin != &defPin[j] && pin == defPin[j]) { + clash = true; + break; + } + } + // We already have a clash on current bus, no point checking next buses + if (!clash) { + // check for conflicts in defined pins + for (const auto &pin : defDataPins) { + if (pin == defPin[j]) { + clash = true; + break; + } + } + } + if (clash) defPin[j]++; + if (defPin[j] >= WLED_NUM_PINS) break; + } while (clash); + } } + pinsIndex += busPins; + unsigned start = prevLen; // if we have less counts than pins and they do not align, use last known count to set current count unsigned count = defCounts[(i < defNumCounts) ? i : defNumCounts -1]; + // analog always has length 1 + if (Bus::isPWM(dataType)) count = 1; prevLen += count; - BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0, useGlobalLedBuffer); + BusConfig defCfg = BusConfig(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0, useGlobalLedBuffer); if (BusManager::add(defCfg) == -1) break; } } @@ -1256,12 +1317,12 @@ void WS2812FX::finalizeInit() { //RGBW mode is enabled if at least one of the strips is RGBW _hasWhiteChannel |= bus->hasWhite(); //refresh is required to remain off if at least one of the strips requires the refresh. - _isOffRefreshRequired |= bus->isOffRefreshRequired(); + _isOffRefreshRequired |= bus->isOffRefreshRequired() && !bus->isPWM(); // use refresh bit for phase shift with analog unsigned busEnd = bus->getStart() + bus->getLength(); if (busEnd > _length) _length = busEnd; #ifdef ESP8266 // why do we need to reinitialise GPIO3??? - //if ((!IS_DIGITAL(bus->getType()) || IS_2PIN(bus->getType()))) continue; + //if (!bus->isDigital() || bus->is2Pin()) continue; //uint8_t pins[5]; //if (!bus->getPins(pins)) continue; //BusDigital* bd = static_cast(bus); diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 24cb7993d..b20095d4c 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -4,6 +4,18 @@ #include #include +#ifdef ARDUINO_ARCH_ESP32 +#include "driver/ledc.h" +#include "soc/ledc_struct.h" + #if !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)) + #define LEDC_MUTEX_LOCK() do {} while (xSemaphoreTake(_ledc_sys_lock, portMAX_DELAY) != pdPASS) + #define LEDC_MUTEX_UNLOCK() xSemaphoreGive(_ledc_sys_lock) + extern xSemaphoreHandle _ledc_sys_lock; + #else + #define LEDC_MUTEX_LOCK() + #define LEDC_MUTEX_UNLOCK() + #endif +#endif #include "const.h" #include "pin_manager.h" #include "bus_wrapper.h" @@ -48,38 +60,46 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte #define W(c) (byte((c) >> 24)) -void ColorOrderMap::add(uint16_t start, uint16_t len, uint8_t colorOrder) { - if (_count >= WLED_MAX_COLOR_ORDER_MAPPINGS) { - return; - } - if (len == 0) { - return; - } - // upper nibble contains W swap information - if ((colorOrder & 0x0F) > COL_ORDER_MAX) { - return; - } - _mappings[_count].start = start; - _mappings[_count].len = len; - _mappings[_count].colorOrder = colorOrder; - _count++; +bool ColorOrderMap::add(uint16_t start, uint16_t len, uint8_t colorOrder) { + if (count() >= WLED_MAX_COLOR_ORDER_MAPPINGS || len == 0 || (colorOrder & 0x0F) > COL_ORDER_MAX) return false; // upper nibble contains W swap information + _mappings.push_back({start,len,colorOrder}); + return true; } uint8_t IRAM_ATTR ColorOrderMap::getPixelColorOrder(uint16_t pix, uint8_t defaultColorOrder) const { - if (_count > 0) { - // upper nibble contains W swap information - // when ColorOrderMap's upper nibble contains value >0 then swap information is used from it, otherwise global swap is used - for (unsigned i = 0; i < _count; i++) { - if (pix >= _mappings[i].start && pix < (_mappings[i].start + _mappings[i].len)) { - return _mappings[i].colorOrder | ((_mappings[i].colorOrder >> 4) ? 0 : (defaultColorOrder & 0xF0)); - } + // upper nibble contains W swap information + // when ColorOrderMap's upper nibble contains value >0 then swap information is used from it, otherwise global swap is used + for (unsigned i = 0; i < count(); i++) { + if (pix >= _mappings[i].start && pix < (_mappings[i].start + _mappings[i].len)) { + return _mappings[i].colorOrder | ((_mappings[i].colorOrder >> 4) ? 0 : (defaultColorOrder & 0xF0)); } } return defaultColorOrder; } -uint32_t Bus::autoWhiteCalc(uint32_t c) { +void Bus::calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw) { + unsigned cct = 0; //0 - full warm white, 255 - full cold white + unsigned w = W(c); + + if (_cct > -1) { // using RGB? + if (_cct >= 1900) cct = (_cct - 1900) >> 5; // convert K in relative format + else if (_cct < 256) cct = _cct; // already relative + } else { + cct = (approximateKelvinFromRGB(c) - 1900) >> 5; // convert K (from RGB value) to relative format + } + + //0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold) + if (cct < _cctBlend) ww = 255; + else ww = ((255-cct) * 255) / (255 - _cctBlend); + if ((255-cct) < _cctBlend) cw = 255; + else cw = (cct * 255) / (255 - _cctBlend); + + ww = (w * ww) / 255; //brightness scaling + cw = (w * cw) / 255; +} + +uint32_t Bus::autoWhiteCalc(uint32_t c) const { unsigned aWM = _autoWhiteMode; if (_gAWM < AW_GLOBAL_DISABLED) aWM = _gAWM; if (aWM == RGBW_MODE_MANUAL_ONLY) return c; @@ -95,7 +115,7 @@ uint32_t Bus::autoWhiteCalc(uint32_t c) { return RGBW32(r, g, b, w); } -uint8_t *Bus::allocData(size_t size) { +uint8_t *Bus::allocateData(size_t size) { if (_data) free(_data); // should not happen, but for safety return _data = (uint8_t *)(size>0 ? calloc(size, sizeof(uint8_t)) : nullptr); } @@ -109,11 +129,11 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) , _milliAmpsMax(bc.milliAmpsMax) , _colorOrderMap(com) { - if (!IS_DIGITAL(bc.type) || !bc.count) return; + if (!isDigital(bc.type) || !bc.count) return; if (!pinManager.allocatePin(bc.pins[0], true, PinOwner::BusDigital)) return; _frequencykHz = 0U; _pins[0] = bc.pins[0]; - if (IS_2PIN(bc.type)) { + if (is2Pin(bc.type)) { if (!pinManager.allocatePin(bc.pins[1], true, PinOwner::BusDigital)) { cleanup(); return; @@ -123,13 +143,16 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) } _iType = PolyBus::getI(bc.type, _pins, nr); if (_iType == I_NONE) return; - if (bc.doubleBuffer && !allocData(bc.count * Bus::getNumberOfChannels(bc.type))) return; + _hasRgb = hasRGB(bc.type); + _hasWhite = hasWhite(bc.type); + _hasCCT = hasCCT(bc.type); + if (bc.doubleBuffer && !allocateData(bc.count * Bus::getNumberOfChannels(bc.type))) return; //_buffering = bc.doubleBuffer; uint16_t lenToCreate = bc.count; if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus _busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr, _frequencykHz); _valid = (_busPtr != nullptr); - DEBUG_PRINTF_P(PSTR("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u). mA=%d/%d\n"), _valid?"S":"Uns", nr, bc.count, bc.type, _pins[0], IS_2PIN(bc.type)?_pins[1]:255, _iType, _milliAmpsPerLed, _milliAmpsMax); + DEBUG_PRINTF_P(PSTR("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u). mA=%d/%d\n"), _valid?"S":"Uns", nr, bc.count, bc.type, _pins[0], is2Pin(bc.type)?_pins[1]:255, _iType, _milliAmpsPerLed, _milliAmpsMax); } //fine tune power estimation constants for your setup @@ -263,7 +286,7 @@ void BusDigital::show() { if (newBri < _bri) PolyBus::setBrightness(_busPtr, _iType, _bri); } -bool BusDigital::canShow() { +bool BusDigital::canShow() const { if (!_valid) return true; return PolyBus::canShow(_busPtr, _iType); } @@ -319,7 +342,7 @@ void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) { } // returns original color if global buffering is enabled, else returns lossly restored color from bus -uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) { +uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) const { if (!_valid) return 0; if (_data) { size_t offset = pix * getNumberOfChannels(); @@ -349,9 +372,9 @@ uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) { } } -uint8_t BusDigital::getPins(uint8_t* pinArray) { - unsigned numPins = IS_2PIN(_type) ? 2 : 1; - for (unsigned i = 0; i < numPins; i++) pinArray[i] = _pins[i]; +uint8_t BusDigital::getPins(uint8_t* pinArray) const { + unsigned numPins = is2Pin(_type) + 1; + if (pinArray) for (unsigned i = 0; i < numPins; i++) pinArray[i] = _pins[i]; return numPins; } @@ -361,6 +384,32 @@ void BusDigital::setColorOrder(uint8_t colorOrder) { _colorOrder = colorOrder; } +// credit @willmmiles & @netmindz https://github.com/Aircoookie/WLED/pull/4056 +std::vector BusDigital::getLEDTypes() { + return { + {TYPE_WS2812_RGB, "D", PSTR("WS281x")}, + {TYPE_SK6812_RGBW, "D", PSTR("SK6812/WS2814 RGBW")}, + {TYPE_TM1814, "D", PSTR("TM1814")}, + {TYPE_WS2811_400KHZ, "D", PSTR("400kHz")}, + {TYPE_TM1829, "D", PSTR("TM1829")}, + {TYPE_UCS8903, "D", PSTR("UCS8903")}, + {TYPE_APA106, "D", PSTR("APA106/PL9823")}, + {TYPE_TM1914, "D", PSTR("TM1914")}, + {TYPE_FW1906, "D", PSTR("FW1906 GRBCW")}, + {TYPE_UCS8904, "D", PSTR("UCS8904 RGBW")}, + {TYPE_WS2805, "D", PSTR("WS2805 RGBCW")}, + {TYPE_SM16825, "D", PSTR("SM16825 RGBCW")}, + {TYPE_WS2812_1CH_X3, "D", PSTR("WS2811 White")}, + //{TYPE_WS2812_2CH_X3, "D", PSTR("WS2811 CCT")}, // not implemented + //{TYPE_WS2812_WWA, "D", PSTR("WS2811 WWA")}, // not implemented + {TYPE_WS2801, "2P", PSTR("WS2801")}, + {TYPE_APA102, "2P", PSTR("APA102")}, + {TYPE_LPD8806, "2P", PSTR("LPD8806")}, + {TYPE_LPD6803, "2P", PSTR("LPD6803")}, + {TYPE_P9813, "2P", PSTR("PP9813")}, + }; +} + void BusDigital::reinit() { if (!_valid) return; PolyBus::begin(_busPtr, _iType, _pins); @@ -399,42 +448,54 @@ void BusDigital::cleanup() { #define MAX_BIT_WIDTH SOC_LEDC_TIMER_BIT_WIDE_NUM #else // ESP32: 20 bit (but in reality we would never go beyond 16 bit as the frequency would be to low) - #define MAX_BIT_WIDTH 20 + #define MAX_BIT_WIDTH 14 #endif #endif BusPwm::BusPwm(BusConfig &bc) -: Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed) +: Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed, bc.refreshReq) // hijack Off refresh flag to indicate usage of dithering { - if (!IS_PWM(bc.type)) return; - unsigned numPins = NUM_PWM_PINS(bc.type); + if (!isPWM(bc.type)) return; + unsigned numPins = numPWMPins(bc.type); + [[maybe_unused]] const bool dithering = _needsRefresh; _frequency = bc.frequency ? bc.frequency : WLED_PWM_FREQ; // duty cycle resolution (_depth) can be extracted from this formula: CLOCK_FREQUENCY > _frequency * 2^_depth for (_depth = MAX_BIT_WIDTH; _depth > 8; _depth--) if (((CLOCK_FREQUENCY/_frequency) >> _depth) > 0) break; + managed_pin_type pins[numPins]; + for (unsigned i = 0; i < numPins; i++) pins[i] = {(int8_t)bc.pins[i], true}; + if (!pinManager.allocateMultiplePins(pins, numPins, PinOwner::BusPwm)) return; + #ifdef ESP8266 analogWriteRange((1<<_depth)-1); analogWriteFreq(_frequency); #else + // for 2 pin PWM CCT strip pinManager will make sure both LEDC channels are in the same speed group and sharing the same timer _ledcStart = pinManager.allocateLedc(numPins); if (_ledcStart == 255) { //no more free LEDC channels - deallocatePins(); return; + pinManager.deallocateMultiplePins(pins, numPins, PinOwner::BusPwm); + return; } + // if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor) + if (dithering) _depth = 12; // fixed 8 bit depth PWM with 4 bit dithering (ESP8266 has no hardware to support dithering) #endif for (unsigned i = 0; i < numPins; i++) { - uint8_t currentPin = bc.pins[i]; - if (!pinManager.allocatePin(currentPin, true, PinOwner::BusPwm)) { - deallocatePins(); return; - } - _pins[i] = currentPin; //store only after allocatePin() succeeds + _pins[i] = bc.pins[i]; // store only after allocateMultiplePins() succeeded #ifdef ESP8266 pinMode(_pins[i], OUTPUT); #else - ledcSetup(_ledcStart + i, _frequency, _depth); - ledcAttachPin(_pins[i], _ledcStart + i); + unsigned channel = _ledcStart + i; + ledcSetup(channel, _frequency, _depth - (dithering*4)); // with dithering _frequency doesn't really matter as resolution is 8 bit + ledcAttachPin(_pins[i], channel); + // LEDC timer reset credit @dedehai + uint8_t group = (channel / 8), timer = ((channel / 2) % 4); // same fromula as in ledcSetup() + ledc_timer_rst((ledc_mode_t)group, (ledc_timer_t)timer); // reset timer so all timers are almost in sync (for phase shift) #endif } + _hasRgb = hasRGB(bc.type); + _hasWhite = hasWhite(bc.type); + _hasCCT = hasCCT(bc.type); _data = _pwmdata; // avoid malloc() and use stack _valid = true; DEBUG_PRINTF_P(PSTR("%successfully inited PWM strip with type %u, frequency %u, bit depth %u and pins %u,%u,%u,%u,%u\n"), _valid?"S":"Uns", bc.type, _frequency, _depth, _pins[0], _pins[1], _pins[2], _pins[3], _pins[4]); @@ -477,7 +538,7 @@ void BusPwm::setPixelColor(uint16_t pix, uint32_t c) { } //does no index check -uint32_t BusPwm::getPixelColor(uint16_t pix) { +uint32_t BusPwm::getPixelColor(uint16_t pix) const { if (!_valid) return 0; // TODO getting the reverse from CCT is involved (a quick approximation when CCT blending is ste to 0 implemented) switch (_type) { @@ -499,46 +560,92 @@ uint32_t BusPwm::getPixelColor(uint16_t pix) { void BusPwm::show() { if (!_valid) return; - unsigned numPins = NUM_PWM_PINS(_type); - unsigned maxBri = (1<<_depth) - 1; - // use CIE brightness formula - unsigned pwmBri = (unsigned)_bri * 100; - if(pwmBri < 2040) pwmBri = ((pwmBri << _depth) + 115043) / 230087; //adding '0.5' before division for correct rounding - else { + // if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor) + // https://github.com/Aircoookie/WLED/pull/4115 and https://github.com/zalatnaicsongor/WLED/pull/1) + const bool dithering = _needsRefresh; // avoid working with bitfield + const unsigned numPins = getPins(); + const unsigned maxBri = (1<<_depth); // possible values: 16384 (14), 8192 (13), 4096 (12), 2048 (11), 1024 (10), 512 (9) and 256 (8) + [[maybe_unused]] const unsigned bitShift = dithering * 4; // if dithering, _depth is 12 bit but LEDC channel is set to 8 bit (using 4 fractional bits) + + // use CIE brightness formula (cubic) to fit (or approximate linearity of) human eye perceived brightness + // the formula is based on 12 bit resolution as there is no need for greater precision + // see: https://en.wikipedia.org/wiki/Lightness + unsigned pwmBri = (unsigned)_bri * 100; // enlarge to use integer math for linear response + if (pwmBri < 2040) { + // linear response for values [0-20] + pwmBri = ((pwmBri << 12) + 115043) / 230087; //adding '0.5' before division for correct rounding + } else { + // cubic response for values [21-255] pwmBri += 4080; - float temp = (float)pwmBri / 29580; - temp = temp * temp * temp * (1<<_depth) - 1; - pwmBri = (unsigned)temp; + float temp = (float)pwmBri / 29580.0f; + temp = temp * temp * temp * (float)maxBri; + pwmBri = (unsigned)temp; // pwmBri is in range [0-maxBri] } + + [[maybe_unused]] unsigned hPoint = 0; // phase shift (0 - maxBri) + // we will be phase shifting every channel by previous pulse length (plus dead time if required) + // phase shifting is only mandatory when using H-bridge to drive reverse-polarity PWM CCT (2 wire) LED type + // CCT additive blending must be 0 (WW & CW will not overlap) otherwise signals *will* overlap + // for all other cases it will just try to "spread" the load on PSU + // Phase shifting requires that LEDC timers are synchronised (see setup()). For PWM CCT (and H-bridge) it is + // also mandatory that both channels use the same timer (pinManager takes care of that). for (unsigned i = 0; i < numPins; i++) { - unsigned scaled = (_data[i] * pwmBri) / 255; - if (_reversed) scaled = maxBri - scaled; + unsigned duty = (_data[i] * pwmBri) / 255; #ifdef ESP8266 - analogWrite(_pins[i], scaled); + if (_reversed) duty = maxBri - duty; + analogWrite(_pins[i], duty); #else - ledcWrite(_ledcStart + i, scaled); + int deadTime = 0; + if (_type == TYPE_ANALOG_2CH && Bus::getCCTBlend() == 0) { + // add dead time between signals (when using dithering, two full 8bit pulses are required) + deadTime = (1+dithering) << bitShift; + // we only need to take care of shortening the signal at (almost) full brightness otherwise pulses may overlap + if (_bri >= 254 && duty >= maxBri / 2 && duty < maxBri) duty -= deadTime << 1; // shorten duty of larger signal except if full on + if (_reversed) deadTime = -deadTime; // need to invert dead time to make phaseshift go the opposite way so low signals dont overlap + } + if (_reversed) duty = maxBri - duty; + unsigned channel = _ledcStart + i; + unsigned gr = channel/8; // high/low speed group + unsigned ch = channel%8; // group channel + // directly write to LEDC struct as there is no HAL exposed function for dithering + // duty has 20 bit resolution with 4 fractional bits (24 bits in total) + LEDC.channel_group[gr].channel[ch].duty.duty = duty << ((!dithering)*4); // lowest 4 bits are used for dithering, shift by 4 bits if not using dithering + LEDC.channel_group[gr].channel[ch].hpoint.hpoint = hPoint >> bitShift; // hPoint is at _depth resolution (needs shifting if dithering) + ledc_update_duty((ledc_mode_t)gr, (ledc_channel_t)ch); + hPoint += duty + deadTime; // offset to cascade the signals + if (hPoint >= maxBri) hPoint = 0; // offset it out of bounds, reset #endif } } -uint8_t BusPwm::getPins(uint8_t* pinArray) { +uint8_t BusPwm::getPins(uint8_t* pinArray) const { if (!_valid) return 0; - unsigned numPins = NUM_PWM_PINS(_type); - for (unsigned i = 0; i < numPins; i++) { - pinArray[i] = _pins[i]; - } + unsigned numPins = numPWMPins(_type); + if (pinArray) for (unsigned i = 0; i < numPins; i++) pinArray[i] = _pins[i]; return numPins; } +// credit @willmmiles & @netmindz https://github.com/Aircoookie/WLED/pull/4056 +std::vector BusPwm::getLEDTypes() { + return { + {TYPE_ANALOG_1CH, "A", PSTR("PWM White")}, + {TYPE_ANALOG_2CH, "AA", PSTR("PWM CCT")}, + {TYPE_ANALOG_3CH, "AAA", PSTR("PWM RGB")}, + {TYPE_ANALOG_4CH, "AAAA", PSTR("PWM RGBW")}, + {TYPE_ANALOG_5CH, "AAAAA", PSTR("PWM RGB+CCT")}, + //{TYPE_ANALOG_6CH, "AAAAAA", PSTR("PWM RGB+DCCT")}, // unimplementable ATM + }; +} + void BusPwm::deallocatePins() { - unsigned numPins = NUM_PWM_PINS(_type); + unsigned numPins = getPins(); for (unsigned 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]); + if (_ledcStart < WLED_MAX_ANALOG_CHANNELS) ledcDetachPin(_pins[i]); #endif } #ifdef ARDUINO_ARCH_ESP32 @@ -551,7 +658,7 @@ BusOnOff::BusOnOff(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed) , _onoffdata(0) { - if (bc.type != TYPE_ONOFF) return; + if (!Bus::isOnOff(bc.type)) return; uint8_t currentPin = bc.pins[0]; if (!pinManager.allocatePin(currentPin, true, PinOwner::BusOnOff)) { @@ -559,6 +666,9 @@ BusOnOff::BusOnOff(BusConfig &bc) } _pin = currentPin; //store only after allocatePin() succeeds pinMode(_pin, OUTPUT); + _hasRgb = false; + _hasWhite = false; + _hasCCT = false; _data = &_onoffdata; // avoid malloc() and use stack _valid = true; DEBUG_PRINTF_P(PSTR("%successfully inited On/Off strip with pin %u\n"), _valid?"S":"Uns", _pin); @@ -574,7 +684,7 @@ void BusOnOff::setPixelColor(uint16_t pix, uint32_t c) { _data[0] = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0; } -uint32_t BusOnOff::getPixelColor(uint16_t pix) { +uint32_t BusOnOff::getPixelColor(uint16_t pix) const { if (!_valid) return 0; return RGBW32(_data[0], _data[0], _data[0], _data[0]); } @@ -584,12 +694,18 @@ void BusOnOff::show() { digitalWrite(_pin, _reversed ? !(bool)_data[0] : (bool)_data[0]); } -uint8_t BusOnOff::getPins(uint8_t* pinArray) { +uint8_t BusOnOff::getPins(uint8_t* pinArray) const { if (!_valid) return 0; - pinArray[0] = _pin; + if (pinArray) pinArray[0] = _pin; return 1; } +// credit @willmmiles & @netmindz https://github.com/Aircoookie/WLED/pull/4056 +std::vector BusOnOff::getLEDTypes() { + return { + {TYPE_ONOFF, "", PSTR("On/Off")}, + }; +} BusNetwork::BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite, bc.count) @@ -597,59 +713,71 @@ BusNetwork::BusNetwork(BusConfig &bc) { switch (bc.type) { case TYPE_NET_ARTNET_RGB: - _rgbw = false; _UDPtype = 2; break; case TYPE_NET_ARTNET_RGBW: - _rgbw = true; _UDPtype = 2; break; case TYPE_NET_E131_RGB: - _rgbw = false; _UDPtype = 1; break; default: // TYPE_NET_DDP_RGB / TYPE_NET_DDP_RGBW - _rgbw = bc.type == TYPE_NET_DDP_RGBW; _UDPtype = 0; break; } - _UDPchannels = _rgbw ? 4 : 3; + _hasRgb = hasRGB(bc.type); + _hasWhite = hasWhite(bc.type); + _hasCCT = false; + _UDPchannels = _hasWhite + 3; _client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]); - _valid = (allocData(_len * _UDPchannels) != nullptr); + _valid = (allocateData(_len * _UDPchannels) != nullptr); DEBUG_PRINTF_P(PSTR("%successfully inited virtual strip with type %u and IP %u.%u.%u.%u\n"), _valid?"S":"Uns", bc.type, bc.pins[0], bc.pins[1], bc.pins[2], bc.pins[3]); } void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) { if (!_valid || pix >= _len) return; - if (_rgbw) c = autoWhiteCalc(c); + if (_hasWhite) c = autoWhiteCalc(c); if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT unsigned offset = pix * _UDPchannels; _data[offset] = R(c); _data[offset+1] = G(c); _data[offset+2] = B(c); - if (_rgbw) _data[offset+3] = W(c); + if (_hasWhite) _data[offset+3] = W(c); } -uint32_t BusNetwork::getPixelColor(uint16_t pix) { +uint32_t BusNetwork::getPixelColor(uint16_t pix) const { if (!_valid || pix >= _len) return 0; unsigned offset = pix * _UDPchannels; - return RGBW32(_data[offset], _data[offset+1], _data[offset+2], (_rgbw ? _data[offset+3] : 0)); + return RGBW32(_data[offset], _data[offset+1], _data[offset+2], (hasWhite() ? _data[offset+3] : 0)); } void BusNetwork::show() { if (!_valid || !canShow()) return; _broadcastLock = true; - realtimeBroadcast(_UDPtype, _client, _len, _data, _bri, _rgbw); + realtimeBroadcast(_UDPtype, _client, _len, _data, _bri, hasWhite()); _broadcastLock = false; } -uint8_t BusNetwork::getPins(uint8_t* pinArray) { - for (unsigned i = 0; i < 4; i++) { - pinArray[i] = _client[i]; - } +uint8_t BusNetwork::getPins(uint8_t* pinArray) const { + if (pinArray) for (unsigned i = 0; i < 4; i++) pinArray[i] = _client[i]; return 4; } +// credit @willmmiles & @netmindz https://github.com/Aircoookie/WLED/pull/4056 +std::vector BusNetwork::getLEDTypes() { + return { + {TYPE_NET_DDP_RGB, "N", PSTR("DDP RGB (network)")}, // should be "NNNN" to determine 4 "pin" fields + {TYPE_NET_ARTNET_RGB, "N", PSTR("Art-Net RGB (network)")}, + {TYPE_NET_DDP_RGBW, "N", PSTR("DDP RGBW (network)")}, + {TYPE_NET_ARTNET_RGBW, "N", PSTR("Art-Net RGBW (network)")}, + // hypothetical extensions + //{TYPE_VIRTUAL_I2C_W, "V", PSTR("I2C White (virtual)")}, // allows setting I2C address in _pin[0] + //{TYPE_VIRTUAL_I2C_CCT, "V", PSTR("I2C CCT (virtual)")}, // allows setting I2C address in _pin[0] + //{TYPE_VIRTUAL_I2C_RGB, "VVV", PSTR("I2C RGB (virtual)")}, // allows setting I2C address in _pin[0] and 2 additional values in _pin[1] & _pin[2] + //{TYPE_USERMOD, "VVVVV", PSTR("Usermod (virtual)")}, // 5 data fields (see https://github.com/Aircoookie/WLED/pull/4123) + }; +} + void BusNetwork::cleanup() { _type = I_NONE; _valid = false; @@ -659,13 +787,13 @@ void BusNetwork::cleanup() { //utility to get the approx. memory usage of a given BusConfig uint32_t BusManager::memUsage(BusConfig &bc) { - if (bc.type == TYPE_ONOFF || IS_PWM(bc.type)) return 5; + if (Bus::isOnOff(bc.type) || Bus::isPWM(bc.type)) return OUTPUT_MAX_PINS; unsigned len = bc.count + bc.skipAmount; unsigned channels = Bus::getNumberOfChannels(bc.type); unsigned multiplier = 1; - if (IS_DIGITAL(bc.type)) { // digital types - if (IS_16BIT(bc.type)) len *= 2; // 16-bit LEDs + if (Bus::isDigital(bc.type)) { // digital types + if (Bus::is16bit(bc.type)) len *= 2; // 16-bit LEDs #ifdef ESP8266 if (bc.pins[0] == 3) { //8266 DMA uses 5x the mem multiplier = 5; @@ -685,11 +813,11 @@ uint32_t BusManager::memUsage(unsigned maxChannels, unsigned maxCount, unsigned int BusManager::add(BusConfig &bc) { if (getNumBusses() - getNumVirtualBusses() >= WLED_MAX_BUSSES) return -1; - if (IS_VIRTUAL(bc.type)) { + if (Bus::isVirtual(bc.type)) { busses[numBusses] = new BusNetwork(bc); - } else if (IS_DIGITAL(bc.type)) { + } else if (Bus::isDigital(bc.type)) { busses[numBusses] = new BusDigital(bc, numBusses, colorOrderMap); - } else if (bc.type == TYPE_ONOFF) { + } else if (Bus::isOnOff(bc.type)) { busses[numBusses] = new BusOnOff(bc); } else { busses[numBusses] = new BusPwm(bc); @@ -697,7 +825,32 @@ int BusManager::add(BusConfig &bc) { return numBusses++; } -void BusManager::useParallelOutput(void) { +// credit @willmmiles +static String LEDTypesToJson(const std::vector& types) { + String json; + for (const auto &type : types) { + // capabilities follows similar pattern as JSON API + int capabilities = Bus::hasRGB(type.id) | Bus::hasWhite(type.id)<<1 | Bus::hasCCT(type.id)<<2 | Bus::is16bit(type.id)<<4; + char str[256]; + sprintf_P(str, PSTR("{i:%d,c:%d,t:\"%s\",n:\"%s\"},"), type.id, capabilities, type.type, type.name); + json += str; + } + return json; +} + +// credit @willmmiles & @netmindz https://github.com/Aircoookie/WLED/pull/4056 +String BusManager::getLEDTypesJSONString() { + String json = "["; + json += LEDTypesToJson(BusDigital::getLEDTypes()); + json += LEDTypesToJson(BusOnOff::getLEDTypes()); + json += LEDTypesToJson(BusPwm::getLEDTypes()); + json += LEDTypesToJson(BusNetwork::getLEDTypes()); + //json += LEDTypesToJson(BusVirtual::getLEDTypes()); + json.setCharAt(json.length()-1, ']'); // replace last comma with bracket + return json; +} + +void BusManager::useParallelOutput() { _parallelOutputs = 8; // hardcoded since we use NPB I2S x8 methods PolyBus::setParallelI2S1Output(); } @@ -735,7 +888,7 @@ void BusManager::esp32RMTInvertIdle() { if (u >= _parallelOutputs + 8) return; // only 8 RMT channels rmt = u - _parallelOutputs; #endif - if (busses[u]->getLength()==0 || !IS_DIGITAL(busses[u]->getType()) || IS_2PIN(busses[u]->getType())) continue; + if (busses[u]->getLength()==0 || !busses[u]->isDigital() || busses[u]->is2Pin()) continue; //assumes that bus number to rmt channel mapping stays 1:1 rmt_channel_t ch = static_cast(rmt); rmt_idle_level_t lvl; @@ -754,7 +907,7 @@ void BusManager::on() { if (pinManager.getPinOwner(LED_BUILTIN) == PinOwner::BusDigital) { for (unsigned i = 0; i < numBusses; i++) { uint8_t pins[2] = {255,255}; - if (IS_DIGITAL(busses[i]->getType()) && busses[i]->getPins(pins)) { + if (busses[i]->isDigital() && busses[i]->getPins(pins)) { if (pins[0] == LED_BUILTIN || pins[1] == LED_BUILTIN) { BusDigital *bus = static_cast(busses[i]); bus->reinit(); @@ -825,7 +978,7 @@ void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) { uint32_t BusManager::getPixelColor(uint16_t pix) { for (unsigned i = 0; i < numBusses; i++) { unsigned bstart = busses[i]->getStart(); - if (pix < bstart || pix >= bstart + busses[i]->getLength()) continue; + if (!busses[i]->containsPixel(pix)) continue; return busses[i]->getPixelColor(pix - bstart); } return 0; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 5e516d2e1..24f10f0a7 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -6,6 +6,7 @@ */ #include "const.h" +#include //colors.cpp uint16_t approximateKelvinFromRGB(uint32_t rgb); @@ -21,6 +22,296 @@ uint16_t approximateKelvinFromRGB(uint32_t rgb); #define IC_INDEX_WS2812_2CH_3X(i) ((i)*2/3) #define WS2812_2CH_3X_SPANS_2_ICS(i) ((i)&0x01) // every other LED zone is on two different ICs +struct BusConfig; // forward declaration + +// Defines an LED Strip and its color ordering. +typedef struct { + uint16_t start; + uint16_t len; + uint8_t colorOrder; +} ColorOrderMapEntry; + +struct ColorOrderMap { + bool add(uint16_t start, uint16_t len, uint8_t colorOrder); + + inline uint8_t count() const { return _mappings.size(); } + inline void reserve(size_t num) { _mappings.reserve(num); } + + void reset() { + _mappings.clear(); + _mappings.shrink_to_fit(); + } + + const ColorOrderMapEntry* get(uint8_t n) const { + if (n >= count()) return nullptr; + return &(_mappings[n]); + } + + [[gnu::hot]] uint8_t getPixelColorOrder(uint16_t pix, uint8_t defaultColorOrder) const; + + private: + std::vector _mappings; +}; + + +typedef struct { + uint8_t id; + const char *type; + const char *name; +} LEDType; + + +//parent class of BusDigital, BusPwm, and BusNetwork +class Bus { + public: + Bus(uint8_t type, uint16_t start, uint8_t aw, uint16_t len = 1, bool reversed = false, bool refresh = false) + : _type(type) + , _bri(255) + , _start(start) + , _len(len) + , _reversed(reversed) + , _valid(false) + , _needsRefresh(refresh) + , _data(nullptr) // keep data access consistent across all types of buses + { + _autoWhiteMode = Bus::hasWhite(type) ? aw : RGBW_MODE_MANUAL_ONLY; + }; + + virtual ~Bus() {} //throw the bus under the bus + + virtual void show() = 0; + virtual bool canShow() const { return true; } + virtual void setStatusPixel(uint32_t c) {} + virtual void setPixelColor(uint16_t pix, uint32_t c) = 0; + virtual void setBrightness(uint8_t b) { _bri = b; }; + virtual void setColorOrder(uint8_t co) {} + virtual uint32_t getPixelColor(uint16_t pix) const { return 0; } + virtual uint8_t getPins(uint8_t* pinArray = nullptr) const { return 0; } + virtual uint16_t getLength() const { return isOk() ? _len : 0; } + virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; } + virtual uint8_t skippedLeds() const { return 0; } + virtual uint16_t getFrequency() const { return 0U; } + virtual uint16_t getLEDCurrent() const { return 0; } + virtual uint16_t getUsedCurrent() const { return 0; } + virtual uint16_t getMaxCurrent() const { return 0; } + + inline bool hasRGB() const { return _hasRgb; } + inline bool hasWhite() const { return _hasWhite; } + inline bool hasCCT() const { return _hasCCT; } + inline bool isDigital() const { return isDigital(_type); } + inline bool is2Pin() const { return is2Pin(_type); } + inline bool isOnOff() const { return isOnOff(_type); } + inline bool isPWM() const { return isPWM(_type); } + inline bool isVirtual() const { return isVirtual(_type); } + inline bool is16bit() const { return is16bit(_type); } + inline void setReversed(bool reversed) { _reversed = reversed; } + inline void setStart(uint16_t start) { _start = start; } + inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; } + inline uint8_t getAutoWhiteMode() const { return _autoWhiteMode; } + inline uint8_t getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); } + inline uint16_t getStart() const { return _start; } + inline uint8_t getType() const { return _type; } + inline bool isOk() const { return _valid; } + inline bool isReversed() const { return _reversed; } + inline bool isOffRefreshRequired() const { return _needsRefresh; } + inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; } + + static inline std::vector getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes + static constexpr uint8_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK + static constexpr uint8_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); } + static constexpr bool hasRGB(uint8_t type) { + return !((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_ANALOG_1CH || type == TYPE_ANALOG_2CH || type == TYPE_ONOFF); + } + static constexpr bool hasWhite(uint8_t type) { + return (type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || + type == TYPE_SK6812_RGBW || type == TYPE_TM1814 || type == TYPE_UCS8904 || + type == TYPE_FW1906 || type == TYPE_WS2805 || type == TYPE_SM16825 || // digital types with white channel + (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) || // analog types with white channel + type == TYPE_NET_DDP_RGBW || type == TYPE_NET_ARTNET_RGBW; // network types with white channel + } + static constexpr bool hasCCT(uint8_t type) { + return type == TYPE_WS2812_2CH_X3 || type == TYPE_WS2812_WWA || + type == TYPE_ANALOG_2CH || type == TYPE_ANALOG_5CH || + type == TYPE_FW1906 || type == TYPE_WS2805 || + type == TYPE_SM16825; + } + static constexpr bool isTypeValid(uint8_t type) { return (type > 15 && type < 128); } + static constexpr bool isDigital(uint8_t type) { return (type >= TYPE_DIGITAL_MIN && type <= TYPE_DIGITAL_MAX) || is2Pin(type); } + static constexpr bool is2Pin(uint8_t type) { return (type >= TYPE_2PIN_MIN && type <= TYPE_2PIN_MAX); } + static constexpr bool isOnOff(uint8_t type) { return (type == TYPE_ONOFF); } + static constexpr bool isPWM(uint8_t type) { return (type >= TYPE_ANALOG_MIN && type <= TYPE_ANALOG_MAX); } + static constexpr bool isVirtual(uint8_t type) { return (type >= TYPE_VIRTUAL_MIN && type <= TYPE_VIRTUAL_MAX); } + static constexpr bool is16bit(uint8_t type) { return type == TYPE_UCS8903 || type == TYPE_UCS8904 || type == TYPE_SM16825; } + static constexpr int numPWMPins(uint8_t type) { return (type - 40); } + + static inline int16_t getCCT() { return _cct; } + static inline void setGlobalAWMode(uint8_t m) { if (m < 5) _gAWM = m; else _gAWM = AW_GLOBAL_DISABLED; } + static inline uint8_t getGlobalAWMode() { return _gAWM; } + static inline void setCCT(int16_t cct) { _cct = cct; } + static inline uint8_t getCCTBlend() { return _cctBlend; } + static inline void setCCTBlend(uint8_t b) { + _cctBlend = (std::min((int)b,100) * 127) / 100; + //compile-time limiter for hardware that can't power both white channels at max + #ifdef WLED_MAX_CCT_BLEND + if (_cctBlend > WLED_MAX_CCT_BLEND) _cctBlend = WLED_MAX_CCT_BLEND; + #endif + } + static void calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw); + + protected: + uint8_t _type; + uint8_t _bri; + uint16_t _start; + uint16_t _len; + //struct { //using bitfield struct adds abour 250 bytes to binary size + bool _reversed;// : 1; + bool _valid;// : 1; + bool _needsRefresh;// : 1; + bool _hasRgb;// : 1; + bool _hasWhite;// : 1; + bool _hasCCT;// : 1; + //} __attribute__ ((packed)); + uint8_t _autoWhiteMode; + uint8_t *_data; + // global Auto White Calculation override + static uint8_t _gAWM; + // _cct has the following menaings (see calculateCCT() & BusManager::setSegmentCCT()): + // -1 means to extract approximate CCT value in K from RGB (in calcualteCCT()) + // [0,255] is the exact CCT value where 0 means warm and 255 cold + // [1900,10060] only for color correction expressed in K (colorBalanceFromKelvin()) + static int16_t _cct; + // _cctBlend determines WW/CW blending: + // 0 - linear (CCT 127 => 50% warm, 50% cold) + // 63 - semi additive/nonlinear (CCT 127 => 66% warm, 66% cold) + // 127 - additive CCT blending (CCT 127 => 100% warm, 100% cold) + static uint8_t _cctBlend; + + uint32_t autoWhiteCalc(uint32_t c) const; + uint8_t *allocateData(size_t size = 1); + void freeData() { if (_data != nullptr) free(_data); _data = nullptr; } +}; + + +class BusDigital : public Bus { + public: + BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com); + ~BusDigital() { cleanup(); } + + void show() override; + bool canShow() const override; + void setBrightness(uint8_t b) override; + void setStatusPixel(uint32_t c) override; + [[gnu::hot]] void setPixelColor(uint16_t pix, uint32_t c) override; + void setColorOrder(uint8_t colorOrder) override; + [[gnu::hot]] uint32_t getPixelColor(uint16_t pix) const override; + uint8_t getColorOrder() const override { return _colorOrder; } + uint8_t getPins(uint8_t* pinArray = nullptr) const override; + uint8_t skippedLeds() const override { return _skip; } + uint16_t getFrequency() const override { return _frequencykHz; } + uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; } + uint16_t getUsedCurrent() const override { return _milliAmpsTotal; } + uint16_t getMaxCurrent() const override { return _milliAmpsMax; } + void reinit(); + void cleanup(); + + static std::vector getLEDTypes(); + + private: + uint8_t _skip; + uint8_t _colorOrder; + uint8_t _pins[2]; + uint8_t _iType; + uint16_t _frequencykHz; + uint8_t _milliAmpsPerLed; + uint16_t _milliAmpsMax; + void * _busPtr; + const ColorOrderMap &_colorOrderMap; + + static uint16_t _milliAmpsTotal; // is overwitten/recalculated on each show() + + inline uint32_t restoreColorLossy(uint32_t c, uint8_t restoreBri) const { + if (restoreBri < 255) { + uint8_t* chan = (uint8_t*) &c; + for (uint_fast8_t i=0; i<4; i++) { + uint_fast16_t val = chan[i]; + chan[i] = ((val << 8) + restoreBri) / (restoreBri + 1); //adding _bri slightly improves recovery / stops degradation on re-scale + } + } + return c; + } + + uint8_t estimateCurrentAndLimitBri(); +}; + + +class BusPwm : public Bus { + public: + BusPwm(BusConfig &bc); + ~BusPwm() { cleanup(); } + + void setPixelColor(uint16_t pix, uint32_t c) override; + uint32_t getPixelColor(uint16_t pix) const override; //does no index check + uint8_t getPins(uint8_t* pinArray = nullptr) const override; + uint16_t getFrequency() const override { return _frequency; } + void show() override; + void cleanup() { deallocatePins(); } + + static std::vector getLEDTypes(); + + private: + uint8_t _pins[OUTPUT_MAX_PINS]; + uint8_t _pwmdata[OUTPUT_MAX_PINS]; + #ifdef ARDUINO_ARCH_ESP32 + uint8_t _ledcStart; + #endif + uint8_t _depth; + uint16_t _frequency; + + void deallocatePins(); +}; + + +class BusOnOff : public Bus { + public: + BusOnOff(BusConfig &bc); + ~BusOnOff() { cleanup(); } + + void setPixelColor(uint16_t pix, uint32_t c) override; + uint32_t getPixelColor(uint16_t pix) const override; + uint8_t getPins(uint8_t* pinArray) const override; + void show() override; + void cleanup() { pinManager.deallocatePin(_pin, PinOwner::BusOnOff); } + + static std::vector getLEDTypes(); + + private: + uint8_t _pin; + uint8_t _onoffdata; +}; + + +class BusNetwork : public Bus { + public: + BusNetwork(BusConfig &bc); + ~BusNetwork() { cleanup(); } + + bool canShow() const override { return !_broadcastLock; } // this should be a return value from UDP routine if it is still sending data out + void setPixelColor(uint16_t pix, uint32_t c) override; + uint32_t getPixelColor(uint16_t pix) const override; + uint8_t getPins(uint8_t* pinArray = nullptr) const override; + void show() override; + void cleanup(); + + static std::vector getLEDTypes(); + + private: + IPAddress _client; + uint8_t _UDPtype; + uint8_t _UDPchannels; + bool _broadcastLock; +}; + + //temporary struct for passing bus configuration to bus struct BusConfig { uint8_t type; @@ -51,10 +342,7 @@ struct BusConfig { { refreshReq = (bool) GET_BIT(busType,7); type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh) - size_t nPins = 1; - if (IS_VIRTUAL(type)) nPins = 4; //virtual network bus. 4 "pins" store IP address - else if (IS_2PIN(type)) nPins = 2; - else if (IS_PWM(type)) nPins = NUM_PWM_PINS(type); + size_t nPins = Bus::getNumberOfPins(type); for (size_t i = 0; i < nPins; i++) pins[i] = ppins[i]; } @@ -72,285 +360,6 @@ struct BusConfig { }; -// Defines an LED Strip and its color ordering. -struct ColorOrderMapEntry { - uint16_t start; - uint16_t len; - uint8_t colorOrder; -}; - -struct ColorOrderMap { - void add(uint16_t start, uint16_t len, uint8_t colorOrder); - - uint8_t count() const { return _count; } - - void reset() { - _count = 0; - memset(_mappings, 0, sizeof(_mappings)); - } - - const ColorOrderMapEntry* get(uint8_t n) const { - if (n > _count) { - return nullptr; - } - return &(_mappings[n]); - } - - uint8_t getPixelColorOrder(uint16_t pix, uint8_t defaultColorOrder) const; - - private: - uint8_t _count; - ColorOrderMapEntry _mappings[WLED_MAX_COLOR_ORDER_MAPPINGS]; -}; - - -//parent class of BusDigital, BusPwm, and BusNetwork -class Bus { - public: - Bus(uint8_t type, uint16_t start, uint8_t aw, uint16_t len = 1, bool reversed = false, bool refresh = false) - : _type(type) - , _bri(255) - , _start(start) - , _len(len) - , _reversed(reversed) - , _valid(false) - , _needsRefresh(refresh) - , _data(nullptr) // keep data access consistent across all types of buses - { - _autoWhiteMode = Bus::hasWhite(type) ? aw : RGBW_MODE_MANUAL_ONLY; - }; - - virtual ~Bus() {} //throw the bus under the bus - - virtual void show() = 0; - virtual bool canShow() { return true; } - virtual void setStatusPixel(uint32_t c) {} - virtual void setPixelColor(uint16_t pix, uint32_t c) = 0; - virtual uint32_t getPixelColor(uint16_t pix) { return 0; } - virtual void setBrightness(uint8_t b) { _bri = b; }; - virtual uint8_t getPins(uint8_t* pinArray) { return 0; } - virtual uint16_t getLength() { return isOk() ? _len : 0; } - virtual void setColorOrder(uint8_t co) {} - virtual uint8_t getColorOrder() { return COL_ORDER_RGB; } - virtual uint8_t skippedLeds() { return 0; } - virtual uint16_t getFrequency() { return 0U; } - virtual uint16_t getLEDCurrent() { return 0; } - virtual uint16_t getUsedCurrent() { return 0; } - virtual uint16_t getMaxCurrent() { return 0; } - virtual uint8_t getNumberOfChannels() { return hasWhite(_type) + 3*hasRGB(_type) + hasCCT(_type); } - static inline uint8_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); } - inline void setReversed(bool reversed) { _reversed = reversed; } - inline uint16_t getStart() { return _start; } - inline void setStart(uint16_t start) { _start = start; } - inline uint8_t getType() { return _type; } - inline bool isOk() { return _valid; } - inline bool isReversed() { return _reversed; } - inline bool isOffRefreshRequired() { return _needsRefresh; } - bool containsPixel(uint16_t pix) { return pix >= _start && pix < _start+_len; } - - virtual bool hasRGB(void) { return Bus::hasRGB(_type); } - static bool hasRGB(uint8_t type) { - if ((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_ANALOG_1CH || type == TYPE_ANALOG_2CH || type == TYPE_ONOFF) return false; - return true; - } - virtual bool hasWhite(void) { return Bus::hasWhite(_type); } - static bool hasWhite(uint8_t type) { - if ((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || - type == TYPE_SK6812_RGBW || type == TYPE_TM1814 || type == TYPE_UCS8904 || - type == TYPE_FW1906 || type == TYPE_WS2805 || type == TYPE_SM16825) return true; // digital types with white channel - if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true; // analog types with white channel - if (type == TYPE_NET_DDP_RGBW || type == TYPE_NET_ARTNET_RGBW) return true; // network types with white channel - return false; - } - virtual bool hasCCT(void) { return Bus::hasCCT(_type); } - static bool hasCCT(uint8_t type) { - if (type == TYPE_WS2812_2CH_X3 || type == TYPE_WS2812_WWA || - type == TYPE_ANALOG_2CH || type == TYPE_ANALOG_5CH || - type == TYPE_FW1906 || type == TYPE_WS2805 || - type == TYPE_SM16825) return true; - return false; - } - static inline int16_t getCCT() { return _cct; } - static void setCCT(int16_t cct) { - _cct = cct; - } - static inline uint8_t getCCTBlend() { return _cctBlend; } - static void setCCTBlend(uint8_t b) { - if (b > 100) b = 100; - _cctBlend = (b * 127) / 100; - //compile-time limiter for hardware that can't power both white channels at max - #ifdef WLED_MAX_CCT_BLEND - if (_cctBlend > WLED_MAX_CCT_BLEND) _cctBlend = WLED_MAX_CCT_BLEND; - #endif - } - static void calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw) { - uint8_t cct = 0; //0 - full warm white, 255 - full cold white - uint8_t w = byte(c >> 24); - - if (_cct > -1) { - if (_cct >= 1900) cct = (_cct - 1900) >> 5; - else if (_cct < 256) cct = _cct; - } else { - cct = (approximateKelvinFromRGB(c) - 1900) >> 5; - } - - //0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold) - if (cct < _cctBlend) ww = 255; - else ww = ((255-cct) * 255) / (255 - _cctBlend); - if ((255-cct) < _cctBlend) cw = 255; - else cw = (cct * 255) / (255 - _cctBlend); - - ww = (w * ww) / 255; //brightness scaling - cw = (w * cw) / 255; - } - inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; } - inline uint8_t getAutoWhiteMode() { return _autoWhiteMode; } - inline static void setGlobalAWMode(uint8_t m) { if (m < 5) _gAWM = m; else _gAWM = AW_GLOBAL_DISABLED; } - inline static uint8_t getGlobalAWMode() { return _gAWM; } - - protected: - uint8_t _type; - uint8_t _bri; - uint16_t _start; - uint16_t _len; - bool _reversed; - bool _valid; - bool _needsRefresh; - uint8_t _autoWhiteMode; - uint8_t *_data; - // global Auto White Calculation override - static uint8_t _gAWM; - // _cct has the following menaings (see calculateCCT() & BusManager::setSegmentCCT()): - // -1 means to extract approximate CCT value in K from RGB (in calcualteCCT()) - // [0,255] is the exact CCT value where 0 means warm and 255 cold - // [1900,10060] only for color correction expressed in K (colorBalanceFromKelvin()) - static int16_t _cct; - // _cctBlend determines WW/CW blending: - // 0 - linear (CCT 127 => 50% warm, 50% cold) - // 63 - semi additive/nonlinear (CCT 127 => 66% warm, 66% cold) - // 127 - additive CCT blending (CCT 127 => 100% warm, 100% cold) - static uint8_t _cctBlend; - - uint32_t autoWhiteCalc(uint32_t c); - uint8_t *allocData(size_t size = 1); - void freeData() { if (_data != nullptr) free(_data); _data = nullptr; } -}; - - -class BusDigital : public Bus { - public: - BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com); - ~BusDigital() { cleanup(); } - - void show() override; - bool canShow() override; - void setBrightness(uint8_t b) override; - void setStatusPixel(uint32_t c) override; - void setPixelColor(uint16_t pix, uint32_t c) override; - void setColorOrder(uint8_t colorOrder) override; - uint32_t getPixelColor(uint16_t pix) override; - uint8_t getColorOrder() override { return _colorOrder; } - uint8_t getPins(uint8_t* pinArray) override; - uint8_t skippedLeds() override { return _skip; } - uint16_t getFrequency() override { return _frequencykHz; } - uint8_t estimateCurrentAndLimitBri(); - uint16_t getLEDCurrent() override { return _milliAmpsPerLed; } - uint16_t getUsedCurrent() override { return _milliAmpsTotal; } - uint16_t getMaxCurrent() override { return _milliAmpsMax; } - void reinit(); - void cleanup(); - - private: - uint8_t _skip; - uint8_t _colorOrder; - uint8_t _pins[2]; - uint8_t _iType; - uint16_t _frequencykHz; - uint8_t _milliAmpsPerLed; - uint16_t _milliAmpsMax; - void * _busPtr; - const ColorOrderMap &_colorOrderMap; - - static uint16_t _milliAmpsTotal; // is overwitten/recalculated on each show() - - inline uint32_t restoreColorLossy(uint32_t c, uint8_t restoreBri) { - if (restoreBri < 255) { - uint8_t* chan = (uint8_t*) &c; - for (uint_fast8_t i=0; i<4; i++) { - uint_fast16_t val = chan[i]; - chan[i] = ((val << 8) + restoreBri) / (restoreBri + 1); //adding _bri slightly improves recovery / stops degradation on re-scale - } - } - return c; - } -}; - - -class BusPwm : public Bus { - public: - BusPwm(BusConfig &bc); - ~BusPwm() { cleanup(); } - - void setPixelColor(uint16_t pix, uint32_t c) override; - uint32_t getPixelColor(uint16_t pix) override; //does no index check - uint8_t getPins(uint8_t* pinArray) override; - uint16_t getFrequency() override { return _frequency; } - void show() override; - void cleanup() { deallocatePins(); } - - private: - uint8_t _pins[5]; - uint8_t _pwmdata[5]; - #ifdef ARDUINO_ARCH_ESP32 - uint8_t _ledcStart; - #endif - uint8_t _depth; - uint16_t _frequency; - - void deallocatePins(); -}; - - -class BusOnOff : public Bus { - public: - BusOnOff(BusConfig &bc); - ~BusOnOff() { cleanup(); } - - void setPixelColor(uint16_t pix, uint32_t c) override; - uint32_t getPixelColor(uint16_t pix) override; - uint8_t getPins(uint8_t* pinArray) override; - void show() override; - void cleanup() { pinManager.deallocatePin(_pin, PinOwner::BusOnOff); } - - private: - uint8_t _pin; - uint8_t _onoffdata; -}; - - -class BusNetwork : public Bus { - public: - BusNetwork(BusConfig &bc); - ~BusNetwork() { cleanup(); } - - bool hasRGB() override { return true; } - bool hasWhite() override { return _rgbw; } - bool canShow() override { return !_broadcastLock; } // this should be a return value from UDP routine if it is still sending data out - void setPixelColor(uint16_t pix, uint32_t c) override; - uint32_t getPixelColor(uint16_t pix) override; - uint8_t getPins(uint8_t* pinArray) override; - void show() override; - void cleanup(); - - private: - IPAddress _client; - uint8_t _UDPtype; - uint8_t _UDPchannels; - bool _rgbw; - bool _broadcastLock; -}; - - class BusManager { public: BusManager() {}; @@ -358,27 +367,27 @@ class BusManager { //utility to get the approx. memory usage of a given BusConfig static uint32_t memUsage(BusConfig &bc); static uint32_t memUsage(unsigned channels, unsigned count, unsigned buses = 1); - static uint16_t currentMilliamps(void) { return _milliAmpsUsed; } - static uint16_t ablMilliampsMax(void) { return _milliAmpsMax; } + static uint16_t currentMilliamps() { return _milliAmpsUsed; } + static uint16_t ablMilliampsMax() { return _milliAmpsMax; } static int add(BusConfig &bc); - static void useParallelOutput(void); // workaround for inaccessible PolyBus + static void useParallelOutput(); // workaround for inaccessible PolyBus //do not call this method from system context (network callback) static void removeAll(); - static void on(void); - static void off(void); + static void on(); + static void off(); static void show(); static bool canAllShow(); static void setStatusPixel(uint32_t c); - static void setPixelColor(uint16_t pix, uint32_t c); + [[gnu::hot]] static void setPixelColor(uint16_t pix, uint32_t c); static void setBrightness(uint8_t b); // for setSegmentCCT(), cct can only be in [-1,255] range; allowWBCorrection will convert it to K // WARNING: setSegmentCCT() is a misleading name!!! much better would be setGlobalCCT() or just setCCT() static void setSegmentCCT(int16_t cct, bool allowWBCorrection = false); - static void setMilliampsMax(uint16_t max) { _milliAmpsMax = max;} + static inline void setMilliampsMax(uint16_t max) { _milliAmpsMax = max;} static uint32_t getPixelColor(uint16_t pix); static inline int16_t getSegmentCCT() { return Bus::getCCT(); } @@ -386,10 +395,10 @@ class BusManager { //semi-duplicate of strip.getLengthTotal() (though that just returns strip._length, calculated in finalizeInit()) static uint16_t getTotalLength(); - static uint8_t getNumBusses() { return numBusses; } + static inline uint8_t getNumBusses() { return numBusses; } + static String getLEDTypesJSONString(); - static void updateColorOrderMap(const ColorOrderMap &com) { memcpy(&colorOrderMap, &com, sizeof(ColorOrderMap)); } - static const ColorOrderMap& getColorOrderMap() { return colorOrderMap; } + static inline ColorOrderMap& getColorOrderMap() { return colorOrderMap; } private: static uint8_t numBusses; @@ -400,11 +409,11 @@ class BusManager { static uint8_t _parallelOutputs; #ifdef ESP32_DATA_IDLE_HIGH - static void esp32RMTInvertIdle(); + static void esp32RMTInvertIdle() ; #endif static uint8_t getNumVirtualBusses() { int j = 0; - for (int i=0; igetType() >= TYPE_NET_DDP_RGB && busses[i]->getType() < 96) j++; + for (int i=0; iisVirtual()) j++; return j; } }; diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index ae39adc14..bf2d30c0e 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -1314,8 +1314,8 @@ class PolyBus { //gives back the internal type index (I_XX_XXX_X above) for the input static uint8_t getI(uint8_t busType, uint8_t* pins, uint8_t num = 0) { - if (!IS_DIGITAL(busType)) return I_NONE; - if (IS_2PIN(busType)) { //SPI LED chips + if (!Bus::isDigital(busType)) return I_NONE; + if (Bus::is2Pin(busType)) { //SPI LED chips bool isHSPI = false; #ifdef ESP8266 if (pins[0] == P_8266_HS_MOSI && pins[1] == P_8266_HS_CLK) isHSPI = true; diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 978ed6eba..8983da65b 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -173,8 +173,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { for (JsonObject elm : ins) { unsigned type = elm["type"] | TYPE_WS2812_RGB; unsigned len = elm["len"] | DEFAULT_LED_COUNT; - if (!IS_DIGITAL(type)) continue; - if (!IS_2PIN(type)) { + if (!Bus::isDigital(type)) continue; + if (!Bus::is2Pin(type)) { digitalCount++; unsigned channels = Bus::getNumberOfChannels(type); if (len > maxLedsOnBus) maxLedsOnBus = len; @@ -215,7 +215,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { uint8_t maPerLed = elm[F("ledma")] | LED_MILLIAMPS_DEFAULT; uint16_t maMax = elm[F("maxpwr")] | (ablMilliampsMax * length) / total; // rough (incorrect?) per strip ABL calculation when no config exists // To disable brightness limiter we either set output max current to 0 or single LED current to 0 (we choose output max current) - if (IS_PWM(ledType) || IS_ONOFF(ledType) || IS_VIRTUAL(ledType)) { // analog and virtual + if (Bus::isPWM(ledType) || Bus::isOnOff(ledType) || Bus::isVirtual(ledType)) { // analog and virtual maPerLed = 0; maMax = 0; } @@ -244,17 +244,13 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { // read color order map configuration JsonArray hw_com = hw[F("com")]; if (!hw_com.isNull()) { - ColorOrderMap com = {}; - unsigned s = 0; + BusManager::getColorOrderMap().reserve(std::min(hw_com.size(), (size_t)WLED_MAX_COLOR_ORDER_MAPPINGS)); for (JsonObject entry : hw_com) { - if (s > WLED_MAX_COLOR_ORDER_MAPPINGS) break; uint16_t start = entry["start"] | 0; uint16_t len = entry["len"] | 0; uint8_t colorOrder = (int)entry[F("order")]; - com.add(start, len, colorOrder); - s++; + if (!BusManager::getColorOrderMap().add(start, len, colorOrder)) break; } - BusManager::updateColorOrderMap(com); } // read multiple button configuration @@ -485,6 +481,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(receiveNotificationBrightness, if_sync_recv["bri"]); CJSON(receiveNotificationColor, if_sync_recv["col"]); CJSON(receiveNotificationEffects, if_sync_recv["fx"]); + CJSON(receiveNotificationPalette, if_sync_recv["pal"]); CJSON(receiveGroups, if_sync_recv["grp"]); CJSON(receiveSegmentOptions, if_sync_recv["seg"]); CJSON(receiveSegmentBounds, if_sync_recv["sb"]); @@ -972,6 +969,7 @@ void serializeConfig() { if_sync_recv["bri"] = receiveNotificationBrightness; if_sync_recv["col"] = receiveNotificationColor; if_sync_recv["fx"] = receiveNotificationEffects; + if_sync_recv["pal"] = receiveNotificationPalette; if_sync_recv["grp"] = receiveGroups; if_sync_recv["seg"] = receiveSegmentOptions; if_sync_recv["sb"] = receiveSegmentBounds; diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 74723471c..104d25e60 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -491,14 +491,14 @@ void NeoGammaWLEDMethod::calcGammaTable(float gamma) } } -uint8_t IRAM_ATTR NeoGammaWLEDMethod::Correct(uint8_t value) +uint8_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct(uint8_t value) { if (!gammaCorrectCol) return value; return gammaT[value]; } // used for color gamma correction -uint32_t IRAM_ATTR NeoGammaWLEDMethod::Correct32(uint32_t color) +uint32_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct32(uint32_t color) { if (!gammaCorrectCol) return color; uint8_t w = W(color); diff --git a/wled00/const.h b/wled00/const.h index af6d4a70b..14ec23b58 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -51,27 +51,28 @@ #define WLED_MAX_BUSSES 4 // will allow 3 digital & 1 analog RGB #define WLED_MIN_VIRTUAL_BUSSES 2 #else + #define WLED_MAX_ANALOG_CHANNELS (LEDC_CHANNEL_MAX*LEDC_SPEED_MODE_MAX) #if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, 6 LEDC, only has 1 I2S but NPB does not support it ATM #define WLED_MAX_BUSSES 4 // will allow 2 digital & 2 analog RGB #define WLED_MAX_DIGITAL_CHANNELS 2 - #define WLED_MAX_ANALOG_CHANNELS 6 + //#define WLED_MAX_ANALOG_CHANNELS 6 #define WLED_MIN_VIRTUAL_BUSSES 3 #elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, 8 LEDC, only has 1 I2S bus, supported in NPB // the 5th bus (I2S) will prevent Audioreactive usermod from functioning (it is last used though) #define WLED_MAX_BUSSES 7 // will allow 5 digital & 2 analog RGB #define WLED_MAX_DIGITAL_CHANNELS 5 - #define WLED_MAX_ANALOG_CHANNELS 8 + //#define WLED_MAX_ANALOG_CHANNELS 8 #define WLED_MIN_VIRTUAL_BUSSES 3 #elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, 8 LEDC, has 2 I2S but NPB does not support them ATM #define WLED_MAX_BUSSES 6 // will allow 4 digital & 2 analog RGB #define WLED_MAX_DIGITAL_CHANNELS 4 - #define WLED_MAX_ANALOG_CHANNELS 8 + //#define WLED_MAX_ANALOG_CHANNELS 8 #define WLED_MIN_VIRTUAL_BUSSES 4 #else // the last digital bus (I2S0) will prevent Audioreactive usermod from functioning #define WLED_MAX_BUSSES 20 // will allow 17 digital & 3 analog RGB #define WLED_MAX_DIGITAL_CHANNELS 17 - #define WLED_MAX_ANALOG_CHANNELS 10 + //#define WLED_MAX_ANALOG_CHANNELS 16 #define WLED_MIN_VIRTUAL_BUSSES 4 #endif #endif @@ -281,6 +282,7 @@ #define TYPE_NONE 0 //light is not configured #define TYPE_RESERVED 1 //unused. Might indicate a "virtual" light //Digital types (data pin only) (16-39) +#define TYPE_DIGITAL_MIN 16 // first usable digital type #define TYPE_WS2812_1CH 18 //white-only chips (1 channel per IC) (unused) #define TYPE_WS2812_1CH_X3 19 //white-only chips (3 channels per IC) #define TYPE_WS2812_2CH_X3 20 //CCT chips (1st IC controls WW + CW of 1st zone and CW of 2nd zone, 2nd IC controls WW of 2nd zone and WW + CW of 3rd zone) @@ -298,26 +300,36 @@ #define TYPE_WS2805 32 //RGB + WW + CW #define TYPE_TM1914 33 //RGB #define TYPE_SM16825 34 //RGB + WW + CW +#define TYPE_DIGITAL_MAX 39 // last usable digital type //"Analog" types (40-47) #define TYPE_ONOFF 40 //binary output (relays etc.; NOT PWM) +#define TYPE_ANALOG_MIN 41 // first usable analog type #define TYPE_ANALOG_1CH 41 //single channel PWM. Uses value of brightest RGBW channel #define TYPE_ANALOG_2CH 42 //analog WW + CW #define TYPE_ANALOG_3CH 43 //analog RGB #define TYPE_ANALOG_4CH 44 //analog RGBW #define TYPE_ANALOG_5CH 45 //analog RGB + WW + CW +#define TYPE_ANALOG_6CH 46 //analog RGB + A + WW + CW +#define TYPE_ANALOG_MAX 47 // last usable analog type //Digital types (data + clock / SPI) (48-63) +#define TYPE_2PIN_MIN 48 #define TYPE_WS2801 50 #define TYPE_APA102 51 #define TYPE_LPD8806 52 #define TYPE_P9813 53 #define TYPE_LPD6803 54 +#define TYPE_2PIN_MAX 63 //Network types (master broadcast) (80-95) +#define TYPE_VIRTUAL_MIN 80 #define TYPE_NET_DDP_RGB 80 //network DDP RGB bus (master broadcast bus) #define TYPE_NET_E131_RGB 81 //network E131 RGB bus (master broadcast bus, unused) #define TYPE_NET_ARTNET_RGB 82 //network ArtNet RGB bus (master broadcast bus, unused) #define TYPE_NET_DDP_RGBW 88 //network DDP RGBW bus (master broadcast bus) #define TYPE_NET_ARTNET_RGBW 89 //network ArtNet RGB bus (master broadcast bus, unused) +#define TYPE_VIRTUAL_MAX 95 +/* +// old macros that have been moved to Bus class #define IS_TYPE_VALID(t) ((t) > 15 && (t) < 128) #define IS_DIGITAL(t) (((t) > 15 && (t) < 40) || ((t) > 47 && (t) < 64)) //digital are 16-39 and 48-63 #define IS_2PIN(t) ((t) > 47 && (t) < 64) @@ -326,6 +338,7 @@ #define IS_PWM(t) ((t) > 40 && (t) < 46) //does not include on/Off type #define NUM_PWM_PINS(t) ((t) - 40) //for analog PWM 41-45 only #define IS_VIRTUAL(t) ((t) >= 80 && (t) < 96) //this was a poor choice a better would be 96-111 +*/ //Color orders #define COL_ORDER_GRB 0 //GRB(w),defaut @@ -453,6 +466,9 @@ #define NTP_PACKET_SIZE 48 // size of NTP receive buffer #define NTP_MIN_PACKET_SIZE 48 // min expected size - NTP v4 allows for "extended information" appended to the standard fields +// Maximum number of pins per output. 5 for RGBCCT analog LEDs. +#define OUTPUT_MAX_PINS 5 + //maximum number of rendered LEDs - this does not have to match max. physical LEDs, e.g. if there are virtual busses #ifndef MAX_LEDS #ifdef ESP8266 @@ -480,7 +496,7 @@ // string temp buffer (now stored in stack locally) #ifdef ESP8266 -#define SETTINGS_STACK_BUF_SIZE 2048 +#define SETTINGS_STACK_BUF_SIZE 2560 #else #define SETTINGS_STACK_BUF_SIZE 3840 // warning: quite a large value for stack (640 * WLED_MAX_USERMODS) #endif @@ -520,7 +536,11 @@ #ifdef ESP8266 #define WLED_PWM_FREQ 880 //PWM frequency proven as good for LEDs #else - #define WLED_PWM_FREQ 19531 + #ifdef SOC_LEDC_SUPPORT_XTAL_CLOCK + #define WLED_PWM_FREQ 9765 // XTAL clock is 40MHz (this will allow 12 bit resolution) + #else + #define WLED_PWM_FREQ 19531 // APB clock is 80MHz + #endif #endif #endif @@ -547,26 +567,19 @@ #define WLED_MAX_NODES 150 #endif -//this is merely a default now and can be changed at runtime -#ifndef LEDPIN -#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) //|| (defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM)) || defined(ARDUINO_ESP32_PICO) - #define LEDPIN 2 // GPIO2 (D4) on Wemos D1 mini compatible boards, safe to use on any board +// Defaults pins, type and counts to configure LED output +#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) + #ifdef WLED_ENABLE_DMX + #define DEFAULT_LED_PIN 1 + #warning "Compiling with DMX. The default LED pin has been changed to pin 1." + #else + #define DEFAULT_LED_PIN 2 // GPIO2 (D4) on Wemos D1 mini compatible boards, safe to use on any board + #endif #else - #define LEDPIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards (if it is unusable it will be reassigned in WS2812FX::finalizeInit()) -#endif -#endif - -#ifdef WLED_ENABLE_DMX -#if (LEDPIN == 2) - #undef LEDPIN - #define LEDPIN 1 - #warning "Pin conflict compiling with DMX and LEDs on pin 2. The default LED pin has been changed to pin 1." -#endif -#endif - -#ifndef DEFAULT_LED_COUNT - #define DEFAULT_LED_COUNT 30 + #define DEFAULT_LED_PIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards (if it is unusable it will be reassigned in WS2812FX::finalizeInit()) #endif +#define DEFAULT_LED_TYPE TYPE_WS2812_RGB +#define DEFAULT_LED_COUNT 30 #define INTERFACE_UPDATE_COOLDOWN 1000 // time in ms to wait between websockets, alexa, and MQTT updates diff --git a/wled00/data/common.js b/wled00/data/common.js new file mode 100644 index 000000000..9378ef07a --- /dev/null +++ b/wled00/data/common.js @@ -0,0 +1,118 @@ +var d=document; +var loc = false, locip, locproto = "http:"; + +function H(pg="") { window.open("https://kno.wled.ge/"+pg); } +function GH() { window.open("https://github.com/Aircoookie/WLED"); } +function gId(c) { return d.getElementById(c); } // getElementById +function cE(e) { return d.createElement(e); } // createElement +function gEBCN(c) { return d.getElementsByClassName(c); } // getElementsByClassName +function gN(s) { return d.getElementsByName(s)[0]; } // getElementsByName +function isE(o) { return Object.keys(o).length === 0; } // isEmpty +function isO(i) { return (i && typeof i === 'object' && !Array.isArray(i)); } // isObject +function isN(n) { return !isNaN(parseFloat(n)) && isFinite(n); } // isNumber +// https://stackoverflow.com/questions/3885817/how-do-i-check-that-a-number-is-float-or-integer +function isF(n) { return n === +n && n !== (n|0); } // isFloat +function isI(n) { return n === +n && n === (n|0); } // isInteger +function toggle(el) { gId(el).classList.toggle("hide"); gId('No'+el).classList.toggle("hide"); } +function tooltip(cont=null) { + d.querySelectorAll((cont?cont+" ":"")+"[title]").forEach((element)=>{ + element.addEventListener("mouseover", ()=>{ + // save title + element.setAttribute("data-title", element.getAttribute("title")); + const tooltip = d.createElement("span"); + tooltip.className = "tooltip"; + tooltip.textContent = element.getAttribute("title"); + + // prevent default title popup + element.removeAttribute("title"); + + let { top, left, width } = element.getBoundingClientRect(); + + d.body.appendChild(tooltip); + + const { offsetHeight, offsetWidth } = tooltip; + + const offset = element.classList.contains("sliderwrap") ? 4 : 10; + top -= offsetHeight + offset; + left += (width - offsetWidth) / 2; + + tooltip.style.top = top + "px"; + tooltip.style.left = left + "px"; + tooltip.classList.add("visible"); + }); + + element.addEventListener("mouseout", ()=>{ + d.querySelectorAll('.tooltip').forEach((tooltip)=>{ + tooltip.classList.remove("visible"); + d.body.removeChild(tooltip); + }); + // restore title + element.setAttribute("title", element.getAttribute("data-title")); + }); + }); +}; +// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript +function loadJS(FILE_URL, async = true, preGetV = undefined, postGetV = undefined) { + let scE = d.createElement("script"); + scE.setAttribute("src", FILE_URL); + scE.setAttribute("type", "text/javascript"); + scE.setAttribute("async", async); + d.body.appendChild(scE); + // success event + scE.addEventListener("load", () => { + //console.log("File loaded"); + if (preGetV) preGetV(); + GetV(); + if (postGetV) postGetV(); + }); + // error event + scE.addEventListener("error", (ev) => { + console.log("Error on loading file", ev); + alert("Loading of configuration script failed.\nIncomplete page data!"); + }); +} +function getLoc() { + let l = window.location; + if (l.protocol == "file:") { + loc = true; + locip = localStorage.getItem('locIp'); + if (!locip) { + locip = prompt("File Mode. Please enter WLED IP!"); + localStorage.setItem('locIp', locip); + } + } else { + // detect reverse proxy + let path = l.pathname; + let paths = path.slice(1,path.endsWith('/')?-1:undefined).split("/"); + if (paths.length > 1) paths.pop(); // remove subpage (or "settings") + if (paths.length > 0 && paths[paths.length-1]=="settings") paths.pop(); // remove "settings" + if (paths.length > 1) { + locproto = l.protocol; + loc = true; + locip = l.hostname + (l.port ? ":" + l.port : "") + "/" + paths.join('/'); + } + } +} +function getURL(path) { return (loc ? locproto + "//" + locip : "") + path; } +function B() { window.open(getURL("/settings"),"_self"); } +var timeout; +function showToast(text, error = false) { + var x = gId("toast"); + if (!x) return; + x.innerHTML = text; + x.className = error ? "error":"show"; + clearTimeout(timeout); + x.style.animation = 'none'; + timeout = setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2900); +} +function uploadFile(fileObj, name) { + var req = new XMLHttpRequest(); + req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400)}); + req.addEventListener('error', function(e){showToast(e.stack,true);}); + req.open("POST", "/upload"); + var formData = new FormData(); + formData.append("data", fileObj.files[0], name); + req.send(formData); + fileObj.value = ''; + return false; +} diff --git a/wled00/data/cpal/cpal.htm b/wled00/data/cpal/cpal.htm index a4b913592..b58c0987a 100644 --- a/wled00/data/cpal/cpal.htm +++ b/wled00/data/cpal/cpal.htm @@ -608,8 +608,8 @@ } function generatePaletteDivs() { - const palettesDiv = d.getElementById("palettes"); - const staticPalettesDiv = d.getElementById("staticPalettes"); + const palettesDiv = gId("palettes"); + const staticPalettesDiv = gId("staticPalettes"); const paletteDivs = Array.from(palettesDiv.children).filter((child) => { return child.id.match(/^palette\d$/); // match only elements with id starting with "palette" followed by a single digit }); @@ -620,25 +620,25 @@ for (let i = 0; i < paletteArray.length; i++) { const palette = paletteArray[i]; - const paletteDiv = d.createElement("div"); + const paletteDiv = cE("div"); paletteDiv.id = `palette${i}`; paletteDiv.classList.add("palette"); const thisKey = Object.keys(palette)[0]; paletteDiv.dataset.colarray = JSON.stringify(palette[thisKey]); - const gradientDiv = d.createElement("div"); + const gradientDiv = cE("div"); gradientDiv.id = `paletteGradient${i}` - const buttonsDiv = d.createElement("div"); + const buttonsDiv = cE("div"); buttonsDiv.id = `buttonsDiv${i}`; buttonsDiv.classList.add("buttonsDiv") - const sendSpan = d.createElement("span"); + const sendSpan = cE("span"); sendSpan.id = `sendSpan${i}`; sendSpan.onclick = function() {initiateUpload(i)}; sendSpan.setAttribute('title', `Send current editor to slot ${i}`); // perhaps Save instead of Send? sendSpan.innerHTML = svgSave; sendSpan.classList.add("sendSpan") - const editSpan = d.createElement("span"); + const editSpan = cE("span"); editSpan.id = `editSpan${i}`; editSpan.onclick = function() {loadForEdit(i)}; editSpan.setAttribute('title', `Copy slot ${i} palette to editor`); diff --git a/wled00/data/index.js b/wled00/data/index.js index 447bf03a6..25ade1163 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -3201,7 +3201,7 @@ function simplifyUI() { createDropdown("palw", "Change palette"); createDropdown("fx", "Change effect", [gId("fxFind"), gId("fxlist")]); - // Hide pallete label + // Hide palette label gId("pall").style.display = "none"; gId("Colors").insertBefore(document.createElement("br"), gId("pall")); // Hide effect label diff --git a/wled00/data/pxmagic/pxmagic.htm b/wled00/data/pxmagic/pxmagic.htm index d59f924cf..8ec11f454 100644 --- a/wled00/data/pxmagic/pxmagic.htm +++ b/wled00/data/pxmagic/pxmagic.htm @@ -882,10 +882,8 @@ hostnameLabel(); })(); - function gId(id) { - return d.getElementById(id); - } - + function gId(e) {return d.getElementById(e);} + function cE(e) {return d.createElement(e);} function hostnameLabel() { const link = gId("wledEdit"); link.href = WLED_URL + "/edit"; @@ -1675,7 +1673,7 @@ } function createCanvas(width, height) { - const canvas = d.createElement("canvas"); + const canvas = cE("canvas"); canvas.width = width; canvas.height = height; @@ -1719,7 +1717,7 @@ const blob = new Blob([text], { type: mimeType }); const url = URL.createObjectURL(blob); - const anchorElement = d.createElement("a"); + const anchorElement = cE("a"); anchorElement.href = url; anchorElement.download = `${filename}.${fileExtension}`; @@ -1790,7 +1788,7 @@ hideElement = "preview" ) { const hide = gId(hideElement); - const toast = d.createElement("div"); + const toast = cE("div"); const wait = 100; toast.style.animation = "fadeIn"; @@ -1799,14 +1797,14 @@ toast.classList.add("toast", type); - const body = d.createElement("span"); + const body = cE("span"); body.classList.add("toast-body"); body.textContent = message; toast.appendChild(body); - const progress = d.createElement("div"); + const progress = cE("div"); progress.classList.add("toast-progress"); progress.style.animation = "progress"; @@ -1831,7 +1829,7 @@ function carousel(id, images, delay = 3000) { let index = 0; - const carousel = d.createElement("div"); + const carousel = cE("div"); carousel.classList.add("carousel"); images.forEach((canvas, i) => { @@ -1959,7 +1957,7 @@ let errorElement = parent.querySelector(".error-message"); if (!errorElement) { - errorElement = d.createElement("div"); + errorElement = cE("div"); errorElement.classList.add("error-message"); parent.appendChild(errorElement); } diff --git a/wled00/data/settings.htm b/wled00/data/settings.htm index 52b64006b..82c778214 100644 --- a/wled00/data/settings.htm +++ b/wled00/data/settings.htm @@ -4,53 +4,12 @@ WLED Settings +
-
+

Imma firin ma lazer (if it has DMX support)

diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 06bcf3e6e..54ba9d8ba 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -4,59 +4,41 @@ LED Settings +
-
+

LED & Hardware setup

@@ -860,7 +800,7 @@ Swap:  ✕
Apply IR change to main segment only:
- + IR info

Relay GPIO:  ✕
@@ -908,7 +848,8 @@ Swap:
CCT IC used (Athom 15W):
- CCT additive blending: % + CCT additive blending: %
+ WARNING: When using H-bridge for reverse polarity (2-wire) CCT LED strip
make sure this value is 0.
(ESP32 variants only, ESP8266 does not support H-bridges)

Advanced

Palette wrapping: diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index ff8231ccb..ce9bd8aa3 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -4,55 +4,9 @@ Misc Settings +
-
+


diff --git a/wled00/data/settings_um.htm b/wled00/data/settings_um.htm index 686c881d9..c2f0ffbf2 100644 --- a/wled00/data/settings_um.htm +++ b/wled00/data/settings_um.htm @@ -4,75 +4,55 @@ Usermod Settings +
-
+

WiFi setup

diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 0ebcd64da..ac941dc97 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -380,6 +380,7 @@ uint16_t crc16(const unsigned char* data_p, size_t length); um_data_t* simulateSound(uint8_t simulationId); void enumerateLedmaps(); uint8_t get_random_wheel_index(uint8_t pos); +float mapf(float x, float in_min, float in_max, float out_min, float out_max); // RAII guard class for the JSON Buffer lock // Modeled after std::lock_guard diff --git a/wled00/ir.cpp b/wled00/ir.cpp index 9e1974366..e4541cd90 100644 --- a/wled00/ir.cpp +++ b/wled00/ir.cpp @@ -714,9 +714,8 @@ void handleIR() if (strip.isUpdating() && timeDiff < 240) return; // be nice, but not too nice irCheckedTime = currentTime; if (irrecv->decode(&results)) { - if (results.value != 0) { // only print results if anything is received ( != 0 ) - if (!pinManager.isPinAllocated(hardwareTX) || pinManager.getPinOwner(hardwareTX) == PinOwner::DebugOut) // Serial TX pin (GPIO 1 on ESP32 and ESP8266) - Serial.printf_P(PSTR("IR recv: 0x%lX\n"), (unsigned long)results.value); + if (results.value != 0 && serialCanTX) { // only print results if anything is received ( != 0 ) + Serial.printf_P(PSTR("IR recv: 0x%lX\n"), (unsigned long)results.value); } decodeIR(results.value); irrecv->resume(); diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 84101e7cf..be2a4f977 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -1,6 +1,18 @@ #include "pin_manager.h" #include "wled.h" +#ifdef ARDUINO_ARCH_ESP32 + #ifdef bitRead + // Arduino variants assume 32 bit values + #undef bitRead + #undef bitSet + #undef bitClear + #define bitRead(var,bit) (((unsigned long long)(var)>>(bit))&0x1ULL) + #define bitSet(var,bit) ((var)|=(1ULL<<(bit))) + #define bitClear(var,bit) ((var)&=(~(1ULL<<(bit)))) + #endif +#endif + #ifdef WLED_DEBUG static void DebugPrintOwnerTag(PinOwner tag) { @@ -32,9 +44,7 @@ bool PinManagerClass::deallocatePin(byte gpio, PinOwner tag) return false; } - byte by = gpio >> 3; - byte bi = gpio - 8*by; - bitWrite(pinAlloc[by], bi, false); + bitWrite(pinAlloc, gpio, false); ownerTag[gpio] = PinOwner::None; return true; } @@ -146,9 +156,7 @@ bool PinManagerClass::allocateMultiplePins(const managed_pin_type * mptArray, by if (gpio >= WLED_NUM_PINS) continue; // other unexpected GPIO => avoid array bounds violation - byte by = gpio >> 3; - byte bi = gpio - 8*by; - bitWrite(pinAlloc[by], bi, true); + bitWrite(pinAlloc, gpio, true); ownerTag[gpio] = tag; #ifdef WLED_DEBUG DEBUG_PRINT(F("PIN ALLOC: Pin ")); @@ -192,9 +200,7 @@ bool PinManagerClass::allocatePin(byte gpio, bool output, PinOwner tag) return false; } - byte by = gpio >> 3; - byte bi = gpio - 8*by; - bitWrite(pinAlloc[by], bi, true); + bitWrite(pinAlloc, gpio, true); ownerTag[gpio] = tag; #ifdef WLED_DEBUG DEBUG_PRINT(F("PIN ALLOC: Pin ")); @@ -213,9 +219,7 @@ bool PinManagerClass::isPinAllocated(byte gpio, PinOwner tag) const { if (!isPinOk(gpio, false)) return true; if ((tag != PinOwner::None) && (ownerTag[gpio] != tag)) return false; - byte by = gpio >> 3; - byte bi = gpio - (by<<3); - return bitRead(pinAlloc[by], bi); + return bitRead(pinAlloc, gpio); } /* see https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/gpio.html @@ -237,7 +241,7 @@ bool PinManagerClass::isPinAllocated(byte gpio, PinOwner tag) const // Check if supplied GPIO is ok to use bool PinManagerClass::isPinOk(byte gpio, bool output) const { - if (gpio >= WLED_NUM_PINS) return false; // catch error case, to avoid array out-of-bounds access + if (gpio >= WLED_NUM_PINS) return false; // catch error case, to avoid array out-of-bounds access #ifdef ARDUINO_ARCH_ESP32 if (digitalPinIsValid(gpio)) { #if defined(CONFIG_IDF_TARGET_ESP32C3) @@ -275,6 +279,14 @@ bool PinManagerClass::isPinOk(byte gpio, bool output) const return false; } +bool PinManagerClass::isReadOnlyPin(byte gpio) +{ +#ifdef ARDUINO_ARCH_ESP32 + if (gpio < WLED_NUM_PINS) return (digitalPinIsValid(gpio) && !digitalPinCanOutput(gpio)); +#endif + return false; +} + PinOwner PinManagerClass::getPinOwner(byte gpio) const { if (!isPinOk(gpio, false)) return PinOwner::None; @@ -282,34 +294,26 @@ PinOwner PinManagerClass::getPinOwner(byte gpio) const } #ifdef ARDUINO_ARCH_ESP32 -#if defined(CONFIG_IDF_TARGET_ESP32C3) - #define MAX_LED_CHANNELS 6 -#else - #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) - #define MAX_LED_CHANNELS 8 - #else - #define MAX_LED_CHANNELS 16 - #endif -#endif byte PinManagerClass::allocateLedc(byte channels) { - if (channels > MAX_LED_CHANNELS || channels == 0) return 255; - byte ca = 0; - for (unsigned i = 0; i < MAX_LED_CHANNELS; i++) { - byte by = i >> 3; - byte bi = i - 8*by; - if (bitRead(ledcAlloc[by], bi)) { //found occupied pin + if (channels > WLED_MAX_ANALOG_CHANNELS || channels == 0) return 255; + unsigned ca = 0; + for (unsigned i = 0; i < WLED_MAX_ANALOG_CHANNELS; i++) { + if (bitRead(ledcAlloc, i)) { //found occupied pin ca = 0; } else { - ca++; + // if we have PWM CCT bus allocation (2 channels) we need to make sure both channels share the same timer + // for phase shifting purposes (otherwise phase shifts may not be accurate) + if (channels == 2) { // will skip odd channel for first channel for phase shifting + if (ca == 0 && i % 2 == 0) ca++; // even LEDC channels is 1st PWM channel + if (ca == 1 && i % 2 == 1) ca++; // odd LEDC channel is 2nd PWM channel + } else + ca++; } if (ca >= channels) { //enough free channels - byte in = (i + 1) - ca; + unsigned in = (i + 1) - ca; for (unsigned j = 0; j < ca; j++) { - byte bChan = in + j; - byte byChan = bChan >> 3; - byte biChan = bChan - 8*byChan; - bitWrite(ledcAlloc[byChan], biChan, true); + bitWrite(ledcAlloc, in+j, true); } return in; } @@ -319,11 +323,8 @@ byte PinManagerClass::allocateLedc(byte channels) void PinManagerClass::deallocateLedc(byte pos, byte channels) { - for (unsigned j = pos; j < pos + channels; j++) { - if (j > MAX_LED_CHANNELS) return; - byte by = j >> 3; - byte bi = j - 8*by; - bitWrite(ledcAlloc[by], bi, false); + for (unsigned j = pos; j < pos + channels && j < WLED_MAX_ANALOG_CHANNELS; j++) { + bitWrite(ledcAlloc, j, false); } } #endif diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index 2fc003880..a64900c89 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -4,6 +4,9 @@ * Registers pins so there is no attempt for two interfaces to use the same pin */ #include +#ifdef ARDUINO_ARCH_ESP32 +#include "driver/ledc.h" // needed for analog/LEDC channel counts +#endif #include "const.h" // for USERMOD_* values typedef struct PinManagerPinType { @@ -46,7 +49,6 @@ enum struct PinOwner : uint8_t { UM_RotaryEncoderUI = USERMOD_ID_ROTARY_ENC_UI, // 0x08 // Usermod "usermod_v2_rotary_encoder_ui.h" // #define USERMOD_ID_AUTO_SAVE // 0x09 // Usermod "usermod_v2_auto_save.h" -- Does not allocate pins // #define USERMOD_ID_DHT // 0x0A // Usermod "usermod_dht.h" -- Statically allocates pins, not compatible with pinManager? - // #define USERMOD_ID_MODE_SORT // 0x0B // Usermod "usermod_v2_mode_sort.h" -- Does not allocate pins // #define USERMOD_ID_VL53L0X // 0x0C // Usermod "usermod_vl53l0x_gestures.h" -- Uses "standard" HW_I2C pins UM_MultiRelay = USERMOD_ID_MULTI_RELAY, // 0x0D // Usermod "usermod_multi_relay.h" UM_AnimatedStaircase = USERMOD_ID_ANIMATED_STAIRCASE, // 0x0E // Usermod "Animated_Staircase.h" @@ -64,29 +66,32 @@ enum struct PinOwner : uint8_t { UM_LDR_DUSK_DAWN = USERMOD_ID_LDR_DUSK_DAWN, // 0x2B // Usermod "usermod_LDR_Dusk_Dawn_v2.h" UM_MAX17048 = USERMOD_ID_MAX17048, // 0x2F // Usermod "usermod_max17048.h" UM_BME68X = USERMOD_ID_BME68X, // 0x31 // Usermod "usermod_bme68x.h -- Uses "standard" HW_I2C pins - UM_PIXELS_DICE_TRAY = USERMOD_ID_PIXELS_DICE_TRAY, // 0x35 // Usermod "pixels_dice_tray.h" -- Needs compile time specified 6 pins for display including SPI. + UM_PIXELS_DICE_TRAY = USERMOD_ID_PIXELS_DICE_TRAY // 0x35 // Usermod "pixels_dice_tray.h" -- Needs compile time specified 6 pins for display including SPI. }; static_assert(0u == static_cast(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected"); class PinManagerClass { private: - #ifdef ESP8266 - #define WLED_NUM_PINS 17 - uint8_t pinAlloc[3] = {0x00, 0x00, 0x00}; //24bit, 1 bit per pin, we use first 17bits - PinOwner ownerTag[WLED_NUM_PINS] = { PinOwner::None }; - #else - #define WLED_NUM_PINS 50 - uint8_t pinAlloc[7] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // 56bit, 1 bit per pin, we use 50 bits on ESP32-S3 - uint8_t ledcAlloc[2] = {0x00, 0x00}; //16 LEDC channels - PinOwner ownerTag[WLED_NUM_PINS] = { PinOwner::None }; // new MCU's have up to 50 GPIO - #endif - struct { - uint8_t i2cAllocCount : 4; // allow multiple allocation of I2C bus pins but keep track of allocations - uint8_t spiAllocCount : 4; // allow multiple allocation of SPI bus pins but keep track of allocations - }; + struct { + #ifdef ESP8266 + #define WLED_NUM_PINS (GPIO_PIN_COUNT+1) // somehow they forgot GPIO 16 (0-16==17) + uint32_t pinAlloc : 24; // 24bit, 1 bit per pin, we use first 17bits + #else + #define WLED_NUM_PINS (GPIO_PIN_COUNT) + uint64_t pinAlloc : 56; // 56 bits, 1 bit per pin, we use 50 bits on ESP32-S3 + uint16_t ledcAlloc : 16; // up to 16 LEDC channels (WLED_MAX_ANALOG_CHANNELS) + #endif + uint8_t i2cAllocCount : 4; // allow multiple allocation of I2C bus pins but keep track of allocations + uint8_t spiAllocCount : 4; // allow multiple allocation of SPI bus pins but keep track of allocations + } __attribute__ ((packed)); + PinOwner ownerTag[WLED_NUM_PINS] = { PinOwner::None }; public: - PinManagerClass() : i2cAllocCount(0), spiAllocCount(0) {} + PinManagerClass() : pinAlloc(0ULL), i2cAllocCount(0), spiAllocCount(0) { + #ifdef ARDUINO_ARCH_ESP32 + ledcAlloc = 0; + #endif + } // De-allocates a single pin bool deallocatePin(byte gpio, PinOwner tag); // De-allocates multiple pins but only if all can be deallocated (PinOwner has to be specified) @@ -101,19 +106,17 @@ class PinManagerClass { // ethernet, etc.. bool allocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag ); - #if !defined(ESP8266) // ESP8266 compiler doesn't understand deprecated attribute [[deprecated("Replaced by three-parameter allocatePin(gpio, output, ownerTag), for improved debugging")]] - #endif inline bool allocatePin(byte gpio, bool output = true) { return allocatePin(gpio, output, PinOwner::None); } - #if !defined(ESP8266) // ESP8266 compiler doesn't understand deprecated attribute [[deprecated("Replaced by two-parameter deallocatePin(gpio, ownerTag), for improved debugging")]] - #endif inline void deallocatePin(byte gpio) { deallocatePin(gpio, PinOwner::None); } // will return true for reserved pins bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None) const; // will return false for reserved pins bool isPinOk(byte gpio, bool output = true) const; + + static bool isReadOnlyPin(byte gpio); PinOwner getPinOwner(byte gpio) const; diff --git a/wled00/set.cpp b/wled00/set.cpp index 2fe01a54c..a44f95ab1 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -176,7 +176,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) } awmode = request->arg(aw).toInt(); uint16_t freq = request->arg(sp).toInt(); - if (IS_PWM(type)) { + if (Bus::isPWM(type)) { switch (freq) { case 0 : freq = WLED_PWM_FREQ/2; break; case 1 : freq = WLED_PWM_FREQ*2/3; break; @@ -185,7 +185,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) case 3 : freq = WLED_PWM_FREQ*2; break; case 4 : freq = WLED_PWM_FREQ*10/3; break; // uint16_t max (19531 * 3.333) } - } else if (IS_DIGITAL(type) && IS_2PIN(type)) { + } else if (Bus::is2Pin(type)) { switch (freq) { default: case 0 : freq = 1000; break; @@ -198,7 +198,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) freq = 0; } channelSwap = Bus::hasWhite(type) ? request->arg(wo).toInt() : 0; - if (type == TYPE_ONOFF || IS_PWM(type) || IS_VIRTUAL(type)) { // analog and virtual + if (Bus::isOnOff(type) || Bus::isPWM(type) || Bus::isVirtual(type)) { // analog and virtual maPerLed = 0; maMax = 0; } else { @@ -214,7 +214,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) } //doInitBusses = busesChanged; // we will do that below to ensure all input data is processed - ColorOrderMap com = {}; + // we will not bother with pre-allocating ColorOrderMappings vector for (int s = 0; s < WLED_MAX_COLOR_ORDER_MAPPINGS; s++) { int offset = s < 10 ? 48 : 55; char xs[4] = "XS"; xs[2] = offset+s; xs[3] = 0; //start LED @@ -226,10 +226,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) length = request->arg(xc).toInt(); colorOrder = request->arg(xo).toInt() & 0x0F; colorOrder |= (request->arg(xw).toInt() & 0x0F) << 4; // add W swap information - com.add(start, length, colorOrder); + if (!BusManager::getColorOrderMap().add(start, length, colorOrder)) break; } } - BusManager::updateColorOrderMap(com); // update other pins #ifndef WLED_DISABLE_INFRARED @@ -379,6 +378,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) receiveNotificationBrightness = request->hasArg(F("RB")); receiveNotificationColor = request->hasArg(F("RC")); receiveNotificationEffects = request->hasArg(F("RX")); + receiveNotificationPalette = request->hasArg(F("RP")); receiveSegmentOptions = request->hasArg(F("SO")); receiveSegmentBounds = request->hasArg(F("SG")); sendNotifications = request->hasArg(F("SS")); diff --git a/wled00/udp.cpp b/wled00/udp.cpp index d030b6655..8cf733dff 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -221,7 +221,7 @@ void parseNotifyPacket(uint8_t *udpIn) { if (!(receiveGroups & 0x01)) return; } else if (!(receiveGroups & udpIn[36])) return; - bool someSel = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects); + bool someSel = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects || receiveNotificationPalette); // set transition time before making any segment changes if (version > 3) { @@ -311,6 +311,9 @@ void parseNotifyPacket(uint8_t *udpIn) { selseg.setMode(udpIn[11+ofs]); selseg.speed = udpIn[12+ofs]; selseg.intensity = udpIn[13+ofs]; + } + if (receiveNotificationPalette || !someSel) { + DEBUG_PRINTF_P(PSTR("Apply palette: %u\n"), id); selseg.palette = udpIn[14+ofs]; } if (receiveNotificationColor || !someSel) { @@ -352,14 +355,16 @@ void parseNotifyPacket(uint8_t *udpIn) { } // simple effect sync, applies to all selected segments - if (applyEffects && (version < 11 || !receiveSegmentOptions)) { + if ((applyEffects || receiveNotificationPalette) && (version < 11 || !receiveSegmentOptions)) { for (size_t i = 0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); if (!seg.isActive() || !seg.isSelected()) continue; - seg.setMode(udpIn[8]); - seg.speed = udpIn[9]; - if (version > 2) seg.intensity = udpIn[16]; - if (version > 4) seg.setPalette(udpIn[19]); + if (applyEffects) { + seg.setMode(udpIn[8]); + seg.speed = udpIn[9]; + if (version > 2) seg.intensity = udpIn[16]; + } + if (version > 4 && receiveNotificationPalette) seg.setPalette(udpIn[19]); } stateChanged = true; } diff --git a/wled00/util.cpp b/wled00/util.cpp index 2e7056448..99a75bdd3 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -612,3 +612,8 @@ uint8_t get_random_wheel_index(uint8_t pos) { } return r; } + +// float version of map() +float mapf(float x, float in_min, float in_max, float out_min, float out_max) { + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 840c06bf7..bc1cc7b73 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -54,17 +54,19 @@ void WLED::loop() #endif handleTime(); -#ifndef WLED_DISABLE_INFRARED + #ifndef WLED_DISABLE_INFRARED handleIR(); // 2nd call to function needed for ESP32 to return valid results -- should be good for ESP8266, too -#endif + #endif handleConnection(); + #ifdef WLED_ENABLE_ADALIGHT handleSerial(); + #endif handleImprovWifiScan(); handleNotifications(); handleTransitions(); -#ifdef WLED_ENABLE_DMX + #ifdef WLED_ENABLE_DMX handleDMX(); -#endif + #endif #ifdef WLED_DEBUG unsigned long usermodMillis = millis(); @@ -186,8 +188,8 @@ void WLED::loop() unsigned maxChannels = 0; for (unsigned i = 0; i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { if (busConfigs[i] == nullptr) break; - if (!IS_DIGITAL(busConfigs[i]->type)) continue; - if (!IS_2PIN(busConfigs[i]->type)) { + if (!Bus::isDigital(busConfigs[i]->type)) continue; + if (!Bus::is2Pin(busConfigs[i]->type)) { digitalCount++; unsigned channels = Bus::getNumberOfChannels(busConfigs[i]->type); if (busConfigs[i]->count > maxLedsOnBus) maxLedsOnBus = busConfigs[i]->count; @@ -264,7 +266,7 @@ void WLED::loop() if (loopMillis > maxLoopMillis) maxLoopMillis = loopMillis; if (millis() - debugTime > 29999) { DEBUG_PRINTLN(F("---DEBUG INFO---")); - DEBUG_PRINTF_P(PSTR("Runtime: %u\n"), millis()); + DEBUG_PRINTF_P(PSTR("Runtime: %lu\n"), millis()); DEBUG_PRINTF_P(PSTR("Unix time: %u,%03u\n"), toki.getTime().sec, toki.getTime().ms); DEBUG_PRINTF_P(PSTR("Free heap: %u\n"), ESP.getFreeHeap()); #if defined(ARDUINO_ARCH_ESP32) @@ -283,14 +285,14 @@ void WLED::loop() wifiStateChangedTime = millis(); } lastWifiState = WiFi.status(); - DEBUG_PRINTF_P(PSTR("State time: %u\n"), wifiStateChangedTime); - DEBUG_PRINTF_P(PSTR("NTP last sync: %u\n"), ntpLastSyncTime); + DEBUG_PRINTF_P(PSTR("State time: %lu\n"), wifiStateChangedTime); + DEBUG_PRINTF_P(PSTR("NTP last sync: %lu\n"), ntpLastSyncTime); DEBUG_PRINTF_P(PSTR("Client IP: %u.%u.%u.%u\n"), Network.localIP()[0], Network.localIP()[1], Network.localIP()[2], Network.localIP()[3]); if (loops > 0) { // avoid division by zero - DEBUG_PRINTF_P(PSTR("Loops/sec: %u\n"), loops / 30); - DEBUG_PRINTF_P(PSTR("Loop time[ms]: %u/%u\n"), avgLoopMillis/loops, maxLoopMillis); - DEBUG_PRINTF_P(PSTR("UM time[ms]: %u/%u\n"), avgUsermodMillis/loops, maxUsermodMillis); - DEBUG_PRINTF_P(PSTR("Strip time[ms]:%u/%u\n"), avgStripMillis/loops, maxStripMillis); + DEBUG_PRINTF_P(PSTR("Loops/sec: %u\n"), loops / 30); + DEBUG_PRINTF_P(PSTR("Loop time[ms]: %u/%lu\n"), avgLoopMillis/loops, maxLoopMillis); + DEBUG_PRINTF_P(PSTR("UM time[ms]: %u/%lu\n"), avgUsermodMillis/loops, maxUsermodMillis); + DEBUG_PRINTF_P(PSTR("Strip time[ms]:%u/%lu\n"), avgStripMillis/loops, maxStripMillis); } strip.printSize(); loops = 0; @@ -368,11 +370,8 @@ void WLED::setup() DEBUG_PRINTLN(F("arduino-esp32 v1.0.x\n")); // we can't say in more detail. #endif - DEBUG_PRINTF_P(PSTR("CPU: "), ESP.getChipModel()); - DEBUG_PRINTF_P(PSTR(" rev."), ESP.getChipRevision()); - DEBUG_PRINTF_P(PSTR(", %d core(s)"), ESP.getChipCores()); - DEBUG_PRINTF_P(PSTR(", %d MHz.\n"), ESP.getCpuFreqMHz()); - DEBUG_PRINTF_P(PSTR("FLASH: %dMB, Mode %d "), (ESP.getFlashChipSize()/1024)/1024, ESP.getFlashChipMode()); + DEBUG_PRINTF_P(PSTR("CPU: %s rev.%d, %d core(s), %d MHz.\n"), ESP.getChipModel(), (int)ESP.getChipRevision(), ESP.getChipCores(), ESP.getCpuFreqMHz()); + DEBUG_PRINTF_P(PSTR("FLASH: %d MB, Mode %d "), (ESP.getFlashChipSize()/1024)/1024, (int)ESP.getFlashChipMode()); #ifdef WLED_DEBUG switch (ESP.getFlashChipMode()) { // missing: Octal modes @@ -479,10 +478,14 @@ void WLED::setup() WiFi.mode(WIFI_STA); // enable scanning findWiFi(true); // start scanning for available WiFi-s + // all GPIOs are allocated at this point + serialCanRX = !pinManager.isPinAllocated(hardwareRX); // Serial RX pin (GPIO 3 on ESP32 and ESP8266) + serialCanTX = !pinManager.isPinAllocated(hardwareTX) || pinManager.getPinOwner(hardwareTX) == PinOwner::DebugOut; // Serial TX pin (GPIO 1 on ESP32 and ESP8266) + #ifdef WLED_ENABLE_ADALIGHT //Serial RX (Adalight, Improv, Serial JSON) only possible if GPIO3 unused //Serial TX (Debug, Improv, Serial JSON) only possible if GPIO1 unused - if (!pinManager.isPinAllocated(hardwareRX) && !pinManager.isPinAllocated(hardwareTX)) { + if (serialCanRX && serialCanTX) { Serial.println(F("Ada")); } #endif @@ -494,10 +497,6 @@ void WLED::setup() if (mqttClientID[0] == 0) sprintf_P(mqttClientID, PSTR("WLED-%*s"), 6, escapedMac.c_str() + 6); #endif -#ifdef WLED_ENABLE_ADALIGHT - if (Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket(); -#endif - #ifndef WLED_DISABLE_OTA if (aOtaEnabled) { ArduinoOTA.onStart([]() { @@ -524,7 +523,7 @@ void WLED::setup() #endif #ifdef WLED_ENABLE_ADALIGHT - if (Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket(); + if (serialCanRX && Serial.available() > 0 && Serial.peek() == 'I') handleImprovPacket(); #endif // HTTP server page init diff --git a/wled00/wled.h b/wled00/wled.h index 99c5f4226..31a612858 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2409100 +#define VERSION 2409170 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG @@ -35,7 +35,7 @@ #else #undef WLED_ENABLE_ADALIGHT // disable has priority over enable #endif -//#define WLED_ENABLE_DMX // uses 3.5kb (use LEDPIN other than 2) +//#define WLED_ENABLE_DMX // uses 3.5kb //#define WLED_ENABLE_JSONLIVE // peek LED output via /json/live (WS binary peek is always enabled) #ifndef WLED_DISABLE_LOXONE #define WLED_ENABLE_LOXONE // uses 1.2kb @@ -510,6 +510,8 @@ WLED_GLOBAL bool hueApplyColor _INIT(true); #endif WLED_GLOBAL uint16_t serialBaud _INIT(1152); // serial baud rate, multiply by 100 +WLED_GLOBAL bool serialCanRX _INIT(false); +WLED_GLOBAL bool serialCanTX _INIT(false); #ifndef WLED_DISABLE_ESPNOW WLED_GLOBAL bool enableESPNow _INIT(false); // global on/off for ESP-NOW @@ -639,17 +641,19 @@ typedef class Receive { bool SegmentOptions : 1; bool SegmentBounds : 1; bool Direct : 1; - uint8_t reserved : 2; + bool Palette : 1; + uint8_t reserved : 1; }; }; Receive(int i) { Options = i; } - Receive(bool b, bool c, bool e, bool sO, bool sB) { - Brightness = b; - Color = c; - Effects = e; - SegmentOptions = sO; - SegmentBounds = sB; - }; + Receive(bool b, bool c, bool e, bool sO, bool sB, bool p) + : Brightness(b) + , Color(c) + , Effects(e) + , SegmentOptions(sO) + , SegmentBounds(sB) + , Palette(p) + {}; } __attribute__ ((aligned(1), packed)) receive_notification_t; typedef class Send { public: @@ -671,11 +675,12 @@ typedef class Send { Hue = h; } } __attribute__ ((aligned(1), packed)) send_notification_t; -WLED_GLOBAL receive_notification_t receiveN _INIT(0b00100111); +WLED_GLOBAL receive_notification_t receiveN _INIT(0b01100111); WLED_GLOBAL send_notification_t notifyG _INIT(0b00001111); #define receiveNotificationBrightness receiveN.Brightness #define receiveNotificationColor receiveN.Color #define receiveNotificationEffects receiveN.Effects +#define receiveNotificationPalette receiveN.Palette #define receiveSegmentOptions receiveN.SegmentOptions #define receiveSegmentBounds receiveN.SegmentBounds #define receiveDirect receiveN.Direct @@ -687,6 +692,7 @@ WLED_GLOBAL send_notification_t notifyG _INIT(0b00001111); WLED_GLOBAL bool receiveNotificationBrightness _INIT(true); // apply brightness from incoming notifications WLED_GLOBAL bool receiveNotificationColor _INIT(true); // apply color WLED_GLOBAL bool receiveNotificationEffects _INIT(true); // apply effects setup +WLED_GLOBAL bool receiveNotificationPalette _INIT(true); // apply palette WLED_GLOBAL bool receiveSegmentOptions _INIT(false); // apply segment options WLED_GLOBAL bool receiveSegmentBounds _INIT(false); // apply segment bounds (start, stop, offset) WLED_GLOBAL bool receiveDirect _INIT(true); // receive UDP/Hyperion realtime diff --git a/wled00/wled_serial.cpp b/wled00/wled_serial.cpp index 3ca7c7f2f..ad9bb1413 100644 --- a/wled00/wled_serial.cpp +++ b/wled00/wled_serial.cpp @@ -28,7 +28,7 @@ void updateBaudRate(uint32_t rate){ if (rate100 == currentBaud || rate100 < 96) return; currentBaud = rate100; - if (!pinManager.isPinAllocated(hardwareTX) || pinManager.getPinOwner(hardwareTX) == PinOwner::DebugOut){ + if (serialCanTX){ Serial.print(F("Baud is now ")); Serial.println(rate); } @@ -38,7 +38,7 @@ void updateBaudRate(uint32_t rate){ // RGB LED data return as JSON array. Slow, but easy to use on the other end. void sendJSON(){ - if (!pinManager.isPinAllocated(hardwareTX) || pinManager.getPinOwner(hardwareTX) == PinOwner::DebugOut) { + if (serialCanTX) { unsigned used = strip.getLengthTotal(); Serial.write('['); for (unsigned i=0; ias()); - //only send response if TX pin is unused for other purposes - if (verboseResponse && (!pinManager.isPinAllocated(hardwareTX) || pinManager.getPinOwner(hardwareTX) == PinOwner::DebugOut)) { - pDoc->clear(); - JsonObject state = pDoc->createNestedObject("state"); - serializeState(state); - JsonObject info = pDoc->createNestedObject("info"); - serializeInfo(info); + if (!error) { + verboseResponse = deserializeState(pDoc->as()); + //only send response if TX pin is unused for other purposes + if (verboseResponse && serialCanTX) { + pDoc->clear(); + JsonObject state = pDoc->createNestedObject("state"); + serializeState(state); + JsonObject info = pDoc->createNestedObject("info"); + serializeInfo(info); - serializeJson(*pDoc, Serial); - Serial.println(); + serializeJson(*pDoc, Serial); + Serial.println(); + } } releaseJSONBufferLock(); } @@ -199,11 +186,10 @@ void handleSerial() // All other received bytes will disable Continuous Serial Streaming if (continuousSendLED && next != 'O'){ continuousSendLED = false; - } + } Serial.read(); //discard the byte } - #endif // If Continuous Serial Streaming is enabled, send new LED data as bytes if (continuousSendLED && (lastUpdate != strip.getLastShow())){ diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 291f6f5fc..9d4e4c85b 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -18,6 +18,7 @@ static const char s_unlock_ota [] PROGMEM = "Please unlock OTA in security setti static const char s_unlock_cfg [] PROGMEM = "Please unlock settings using PIN code!"; static const char s_notimplemented[] PROGMEM = "Not implemented"; static const char s_accessdenied[] PROGMEM = "Access Denied"; +static const char _common_js[] PROGMEM = "/common.js"; //Is this an IP? static bool isIp(String str) { @@ -237,6 +238,10 @@ void initServer() handleStaticContent(request, "", 200, FPSTR(CONTENT_TYPE_HTML), PAGE_liveview, PAGE_liveview_length); }); + server.on(_common_js, HTTP_GET, [](AsyncWebServerRequest *request) { + handleStaticContent(request, FPSTR(_common_js), 200, FPSTR(CONTENT_TYPE_JAVASCRIPT), JS_common, JS_common_length); + }); + //settings page server.on(F("/settings"), HTTP_GET, [](AsyncWebServerRequest *request){ serveSettings(request); @@ -511,6 +516,10 @@ void serveJsonError(AsyncWebServerRequest* request, uint16_t code, uint16_t erro void serveSettingsJS(AsyncWebServerRequest* request) { + if (request->url().indexOf(FPSTR(_common_js)) > 0) { + handleStaticContent(request, FPSTR(_common_js), 200, FPSTR(CONTENT_TYPE_JAVASCRIPT), JS_common, JS_common_length); + return; + } char buf[SETTINGS_STACK_BUF_SIZE+37]; buf[0] = 0; byte subPage = request->arg(F("p")).toInt(); diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 6c3ee83dc..71d66d002 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -121,6 +121,7 @@ void fillUMPins(JsonObject &mods) void appendGPIOinfo() { char nS[8]; + // add usermod pins as d.um_p array oappend(SET_F("d.um_p=[-1")); // has to have 1 element if (i2c_sda > -1 && i2c_scl > -1) { oappend(","); oappend(itoa(i2c_sda,nS,10)); @@ -140,83 +141,58 @@ void appendGPIOinfo() { } oappend(SET_F("];")); - // add reserved and usermod pins as d.um_p array - #if defined(CONFIG_IDF_TARGET_ESP32S2) - oappend(SET_F("d.rsvd=[22,23,24,25,26,27,28,29,30,31,32")); - #elif defined(CONFIG_IDF_TARGET_ESP32S3) - oappend(SET_F("d.rsvd=[19,20,22,23,24,25,26,27,28,29,30,31,32")); // includes 19+20 for USB OTG (JTAG) - if (psramFound()) oappend(SET_F(",33,34,35,36,37")); // in use for "octal" PSRAM or "octal" FLASH -seems that octal PSRAM is very common on S3. - #elif defined(CONFIG_IDF_TARGET_ESP32C3) - oappend(SET_F("d.rsvd=[11,12,13,14,15,16,17")); - #elif defined(ESP32) - oappend(SET_F("d.rsvd=[6,7,8,9,10,11,24,28,29,30,31,37,38")); - if (!pinManager.isPinOk(16,false)) oappend(SET_F(",16")); // covers PICO & WROVER - if (!pinManager.isPinOk(17,false)) oappend(SET_F(",17")); // covers PICO & WROVER - #else - oappend(SET_F("d.rsvd=[6,7,8,9,10,11")); - #endif - + // add reserved (unusable) pins + oappend(SET_F("d.rsvd=[")); + for (unsigned i = 0; i < WLED_NUM_PINS; i++) { + if (!pinManager.isPinOk(i, false)) { // include readonly pins + oappendi(i); oappend(","); + } + } #ifdef WLED_ENABLE_DMX - oappend(SET_F(",2")); // DMX hardcoded pin + oappend(SET_F("2,")); // DMX hardcoded pin #endif - #if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST) - oappend(SET_F(",")); oappend(itoa(hardwareTX,nS,10)); // debug output (TX) pin + oappend(itoa(hardwareTX,nS,10)); oappend(","); // debug output (TX) pin #endif - //Note: Using pin 3 (RX) disables Adalight / Serial JSON - #ifdef WLED_USE_ETHERNET if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) { - for (unsigned p=0; p=0) { oappend(","); oappend(itoa(ethernetBoards[ethernetType].eth_power,nS,10)); } - if (ethernetBoards[ethernetType].eth_mdc>=0) { oappend(","); oappend(itoa(ethernetBoards[ethernetType].eth_mdc,nS,10)); } - if (ethernetBoards[ethernetType].eth_mdio>=0) { oappend(","); oappend(itoa(ethernetBoards[ethernetType].eth_mdio,nS,10)); } - switch (ethernetBoards[ethernetType].eth_clk_mode) { + for (unsigned p=0; p=0) { oappend(itoa(ethernetBoards[ethernetType].eth_power,nS,10)); oappend(","); } + if (ethernetBoards[ethernetType].eth_mdc>=0) { oappend(itoa(ethernetBoards[ethernetType].eth_mdc,nS,10)); oappend(","); } + if (ethernetBoards[ethernetType].eth_mdio>=0) { oappend(itoa(ethernetBoards[ethernetType].eth_mdio,nS,10)); oappend(","); } + switch (ethernetBoards[ethernetType].eth_clk_mode) { case ETH_CLOCK_GPIO0_IN: case ETH_CLOCK_GPIO0_OUT: - oappend(SET_F(",0")); + oappend(SET_F("0")); break; case ETH_CLOCK_GPIO16_OUT: - oappend(SET_F(",16")); + oappend(SET_F("16")); break; case ETH_CLOCK_GPIO17_OUT: - oappend(SET_F(",17")); + oappend(SET_F("17")); break; } } #endif - - oappend(SET_F("];")); + oappend(SET_F("];")); // rsvd // add info for read-only GPIO oappend(SET_F("d.ro_gpio=[")); - #if defined(CONFIG_IDF_TARGET_ESP32S2) - oappendi(46); - #elif defined(CONFIG_IDF_TARGET_ESP32S3) - // none for S3 - #elif defined(CONFIG_IDF_TARGET_ESP32C3) - // none for C3 - #elif defined(ESP32) - oappend(SET_F("34,35,36,37,38,39")); - #else - // none for ESP8266 - #endif + bool firstPin = true; + for (unsigned i = 0; i < WLED_NUM_PINS; i++) { + if (pinManager.isReadOnlyPin(i)) { + // No comma before the first pin + if (!firstPin) oappend(SET_F(",")); + oappendi(i); + firstPin = false; + } + } oappend(SET_F("];")); // add info about max. # of pins oappend(SET_F("d.max_gpio=")); - #if defined(CONFIG_IDF_TARGET_ESP32S2) - oappendi(46); - #elif defined(CONFIG_IDF_TARGET_ESP32S3) - oappendi(48); - #elif defined(CONFIG_IDF_TARGET_ESP32C3) - oappendi(21); - #elif defined(ESP32) - oappendi(39); - #else - oappendi(16); - #endif + oappendi(WLED_NUM_PINS); oappend(SET_F(";")); } @@ -348,6 +324,8 @@ void getSettingsJS(byte subPage, char* dest) appendGPIOinfo(); + oappend(SET_F("d.ledTypes=")); oappend(BusManager::getLEDTypesJSONString().c_str()); oappend(";"); + // set limits oappend(SET_F("bLimits(")); oappend(itoa(WLED_MAX_BUSSES,nS,10)); oappend(","); @@ -392,7 +370,7 @@ void getSettingsJS(byte subPage, char* dest) int nPins = bus->getPins(pins); for (int i = 0; i < nPins; i++) { lp[1] = offset+i; - if (pinManager.isPinOk(pins[i]) || IS_VIRTUAL(bus->getType())) sappend('v',lp,pins[i]); + if (pinManager.isPinOk(pins[i]) || bus->isVirtual()) sappend('v',lp,pins[i]); } sappend('v',lc,bus->getLength()); sappend('v',lt,bus->getType()); @@ -404,7 +382,7 @@ void getSettingsJS(byte subPage, char* dest) sappend('v',aw,bus->getAutoWhiteMode()); sappend('v',wo,bus->getColorOrder() >> 4); unsigned speed = bus->getFrequency(); - if (IS_PWM(bus->getType())) { + if (bus->isPWM()) { switch (speed) { case WLED_PWM_FREQ/2 : speed = 0; break; case WLED_PWM_FREQ*2/3 : speed = 1; break; @@ -413,7 +391,7 @@ void getSettingsJS(byte subPage, char* dest) case WLED_PWM_FREQ*2 : speed = 3; break; case WLED_PWM_FREQ*10/3 : speed = 4; break; // uint16_t max (19531 * 3.333) } - } else if (IS_DIGITAL(bus->getType()) && IS_2PIN(bus->getType())) { + } else if (bus->is2Pin()) { switch (speed) { case 1000 : speed = 0; break; case 2000 : speed = 1; break; @@ -506,6 +484,7 @@ void getSettingsJS(byte subPage, char* dest) sappend('c',SET_F("RB"),receiveNotificationBrightness); sappend('c',SET_F("RC"),receiveNotificationColor); sappend('c',SET_F("RX"),receiveNotificationEffects); + sappend('c',SET_F("RP"),receiveNotificationPalette); sappend('c',SET_F("SO"),receiveSegmentOptions); sappend('c',SET_F("SG"),receiveSegmentBounds); sappend('c',SET_F("SS"),sendNotifications); @@ -592,6 +571,9 @@ void getSettingsJS(byte subPage, char* dest) oappend(SET_F("toggle('Hue');")); // hide Hue Sync settings #endif sappend('v',SET_F("BD"),serialBaud); + #ifndef WLED_ENABLE_ADALIGHT + oappend(SET_F("toggle('Serial);")); + #endif } if (subPage == SUBPAGE_TIME)