diff --git a/platformio.ini b/platformio.ini index c65805622..97082693e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -163,6 +163,9 @@ lib_deps = #For ADS1115 sensor uncomment following ;adafruit/Adafruit BusIO @ 1.13.2 ;adafruit/Adafruit ADS1X15 @ 2.4.0 + #For MAX1704x Lipo Monitor / Fuel Gauge uncomment following + ; https://github.com/adafruit/Adafruit_BusIO @ 1.14.5 + ; https://github.com/adafruit/Adafruit_MAX1704X @ 1.0.2 #For MPU6050 IMU uncomment follwoing ;electroniccats/MPU6050 @1.0.1 # For -D USERMOD_ANIMARTRIX diff --git a/tools/cdata-test.js b/tools/cdata-test.js index 55f068073..6f27fb717 100644 --- a/tools/cdata-test.js +++ b/tools/cdata-test.js @@ -83,6 +83,7 @@ describe('Script', () => { // Backup files fs.cpSync("wled00/data", "wled00Backup", { recursive: true }); fs.cpSync("tools/cdata.js", "cdata.bak.js"); + fs.cpSync("package.json", "package.bak.json"); }); after(() => { // Restore backup @@ -90,6 +91,8 @@ describe('Script', () => { fs.renameSync("wled00Backup", "wled00/data"); fs.rmSync("tools/cdata.js"); fs.renameSync("cdata.bak.js", "tools/cdata.js"); + fs.rmSync("package.json"); + fs.renameSync("package.bak.json", "package.json"); }); // delete all html_*.h files @@ -131,7 +134,7 @@ describe('Script', () => { // run script cdata.js again and wait for it to finish await execPromise('node tools/cdata.js'); - checkIfFileWasNewlyCreated(path.join(folderPath, resultFile)); + await checkIfFileWasNewlyCreated(path.join(folderPath, resultFile)); } describe('should build if', () => { @@ -182,6 +185,10 @@ describe('Script', () => { it('cdata.js changes', async () => { await testFileModification('tools/cdata.js', 'html_ui.h'); }); + + it('package.json changes', async () => { + await testFileModification('package.json', 'html_ui.h'); + }); }); describe('should not build if', () => { diff --git a/tools/cdata.js b/tools/cdata.js index 3b8af46da..12dda1cbe 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -2,7 +2,7 @@ * Writes compressed C arrays of data files (web interface) * How to use it? * - * 1) Install Node 11+ and npm + * 1) Install Node 20+ and npm * 2) npm install * 3) npm run build * @@ -207,7 +207,7 @@ function isAnyFileInFolderNewerThan(folderPath, time) { } // Check if the web UI is already built -function isAlreadyBuilt(folderPath) { +function isAlreadyBuilt(webUIPath, packageJsonPath = "package.json") { let lastBuildTime = Infinity; for (const file of output) { @@ -220,7 +220,7 @@ function isAlreadyBuilt(folderPath) { } } - return !isAnyFileInFolderNewerThan(folderPath, lastBuildTime) && !isFileNewerThan("tools/cdata.js", lastBuildTime); + return !isAnyFileInFolderNewerThan(webUIPath, lastBuildTime) && !isFileNewerThan(packageJsonPath, lastBuildTime) && !isFileNewerThan(__filename, lastBuildTime); } // Don't run this script if we're in a test environment diff --git a/usermods/MAX17048_v2/readme.md b/usermods/MAX17048_v2/readme.md new file mode 100644 index 000000000..958e6def2 --- /dev/null +++ b/usermods/MAX17048_v2/readme.md @@ -0,0 +1,64 @@ +# Adafruit MAX17048 Usermod (LiPo & LiIon Battery Monitor & Fuel Gauge) +This usermod reads information from an Adafruit MAX17048 and outputs the following: +- Battery Voltage +- Battery Level Percentage + + +## Dependencies +Libraries: +- `Adafruit_BusIO@~1.14.5` (by [adafruit](https://github.com/adafruit/Adafruit_BusIO)) +- `Adafruit_MAX1704X@~1.0.2` (by [adafruit](https://github.com/adafruit/Adafruit_MAX1704X)) + +These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`). +Data is published over MQTT - make sure you've enabled the MQTT sync interface. + +## Compilation + +To enable, compile with `USERMOD_MAX17048` define in the build_flags (e.g. in `platformio.ini` or `platformio_override.ini`) such as in the example below: +```ini +[env:usermod_max17048_d1_mini] +extends = env:d1_mini +build_flags = + ${common.build_flags_esp8266} + -D USERMOD_MAX17048 +lib_deps = + ${esp8266.lib_deps} + https://github.com/adafruit/Adafruit_BusIO @ 1.14.5 + https://github.com/adafruit/Adafruit_MAX1704X @ 1.0.2 +``` + +### Configuration Options +The following settings can be set at compile-time but are configurable on the usermod menu (except First Monitor time): +- USERMOD_MAX17048_MIN_MONITOR_INTERVAL (the min number of milliseconds between checks, defaults to 10,000 ms) +- USERMOD_MAX17048_MAX_MONITOR_INTERVAL (the max number of milliseconds between checks, defaults to 10,000 ms) +- USERMOD_MAX17048_FIRST_MONITOR_AT + + +Additionally, the Usermod Menu allows you to: +- Enable or Disable the usermod +- Enable or Disable Home Assistant Discovery (turn on/off to sent MQTT Discovery entries for Home Assistant) +- Configure SCL/SDA GPIO Pins + +## API +The following method is available to interact with the usermod from other code modules: +- `getBatteryVoltageV` read the last battery voltage (in Volt) obtained from the sensor +- `getBatteryPercent` reads the last battery percentage obtained from the sensor + +## MQTT +MQTT topics are as follows (`` is set in MQTT section of Sync Setup menu): +Measurement type | MQTT topic +--- | --- +Battery Voltage | `/batteryVoltage` +Battery Percent | `/batteryPercent` + +## Authors +Carlos Cruz [@ccruz09](https://github.com/ccruz09) + + +## Revision History +Jan 2024 +- Added Home Assistant Discovery +- Implemented PinManager to register pins +- Added API call for other modules to read battery voltage and percentage +- Added info-screen outputs +- Updated `readme.md` \ No newline at end of file diff --git a/usermods/MAX17048_v2/usermod_max17048.h b/usermods/MAX17048_v2/usermod_max17048.h new file mode 100644 index 000000000..c3a2664ab --- /dev/null +++ b/usermods/MAX17048_v2/usermod_max17048.h @@ -0,0 +1,281 @@ +// force the compiler to show a warning to confirm that this file is included +#warning **** Included USERMOD_MAX17048 V2.0 **** + +#pragma once + +#include "wled.h" +#include "Adafruit_MAX1704X.h" + + +// the max interval to check battery level, 10 seconds +#ifndef USERMOD_MAX17048_MAX_MONITOR_INTERVAL +#define USERMOD_MAX17048_MAX_MONITOR_INTERVAL 10000 +#endif + +// the min interval to check battery level, 500 ms +#ifndef USERMOD_MAX17048_MIN_MONITOR_INTERVAL +#define USERMOD_MAX17048_MIN_MONITOR_INTERVAL 500 +#endif + +// how many seconds after boot to perform the first check, 10 seconds +#ifndef USERMOD_MAX17048_FIRST_MONITOR_AT +#define USERMOD_MAX17048_FIRST_MONITOR_AT 10000 +#endif + +/* + * Usermod to display Battery Life using Adafruit's MAX17048 LiPoly/ LiIon Fuel Gauge and Battery Monitor. + */ +class Usermod_MAX17048 : public Usermod { + + private: + + bool enabled = true; + + unsigned long maxReadingInterval = USERMOD_MAX17048_MAX_MONITOR_INTERVAL; + unsigned long minReadingInterval = USERMOD_MAX17048_MIN_MONITOR_INTERVAL; + unsigned long lastCheck = UINT32_MAX - (USERMOD_MAX17048_MAX_MONITOR_INTERVAL - USERMOD_MAX17048_FIRST_MONITOR_AT); + unsigned long lastSend = UINT32_MAX - (USERMOD_MAX17048_MAX_MONITOR_INTERVAL - USERMOD_MAX17048_FIRST_MONITOR_AT); + + + uint8_t VoltageDecimals = 3; // Number of decimal places in published voltage values + uint8_t PercentDecimals = 1; // Number of decimal places in published percent values + + // string that are used multiple time (this will save some flash memory) + static const char _name[]; + static const char _enabled[]; + static const char _maxReadInterval[]; + static const char _minReadInterval[]; + static const char _HomeAssistantDiscovery[]; + + bool monitorFound = false; + bool firstReadComplete = false; + bool initDone = false; + + Adafruit_MAX17048 maxLipo; + float lastBattVoltage = -10; + float lastBattPercent = -1; + + // MQTT and Home Assistant Variables + bool HomeAssistantDiscovery = false; // Publish Home Assistant Device Information + bool mqttInitialized = false; + + void _mqttInitialize() + { + char mqttBatteryVoltageTopic[128]; + char mqttBatteryPercentTopic[128]; + + snprintf_P(mqttBatteryVoltageTopic, 127, PSTR("%s/batteryVoltage"), mqttDeviceTopic); + snprintf_P(mqttBatteryPercentTopic, 127, PSTR("%s/batteryPercent"), mqttDeviceTopic); + + if (HomeAssistantDiscovery) { + _createMqttSensor(F("BatteryVoltage"), mqttBatteryVoltageTopic, "voltage", F("V")); + _createMqttSensor(F("BatteryPercent"), mqttBatteryPercentTopic, "battery", F("%")); + } + } + + void _createMqttSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement) + { + String t = String(F("homeassistant/sensor/")) + mqttClientID + F("/") + name + F("/config"); + + StaticJsonDocument<600> doc; + + doc[F("name")] = String(serverDescription) + " " + name; + doc[F("state_topic")] = topic; + doc[F("unique_id")] = String(mqttClientID) + name; + if (unitOfMeasurement != "") + doc[F("unit_of_measurement")] = unitOfMeasurement; + if (deviceClass != "") + doc[F("device_class")] = deviceClass; + doc[F("expire_after")] = 1800; + + JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device + device[F("name")] = serverDescription; + device[F("identifiers")] = "wled-sensor-" + String(mqttClientID); + device[F("manufacturer")] = F("WLED"); + device[F("model")] = F("FOSS"); + device[F("sw_version")] = versionString; + + String temp; + serializeJson(doc, temp); + DEBUG_PRINTLN(t); + DEBUG_PRINTLN(temp); + + mqtt->publish(t.c_str(), 0, true, temp.c_str()); + } + + void publishMqtt(const char *topic, const char* state) { + #ifndef WLED_DISABLE_MQTT + //Check if MQTT Connected, otherwise it will crash the 8266 + if (WLED_MQTT_CONNECTED){ + char subuf[128]; + snprintf_P(subuf, 127, PSTR("%s/%s"), mqttDeviceTopic, topic); + mqtt->publish(subuf, 0, false, state); + } + #endif + } + + public: + + inline void enable(bool enable) { enabled = enable; } + + inline bool isEnabled() { return enabled; } + + void setup() { + // do your set-up here + if (i2c_scl<0 || i2c_sda<0) { enabled = false; return; } + monitorFound = maxLipo.begin(); + initDone = true; + } + + void loop() { + // if usermod is disabled or called during strip updating just exit + // NOTE: on very long strips strip.isUpdating() may always return true so update accordingly + if (!enabled || strip.isUpdating()) return; + + unsigned long now = millis(); + + if (now - lastCheck < minReadingInterval) { return; } + + bool shouldUpdate = now - lastSend > maxReadingInterval; + + float battVoltage = maxLipo.cellVoltage(); + float battPercent = maxLipo.cellPercent(); + lastCheck = millis(); + firstReadComplete = true; + + if (shouldUpdate) + { + lastBattVoltage = roundf(battVoltage * powf(10, VoltageDecimals)) / powf(10, VoltageDecimals); + lastBattPercent = roundf(battPercent * powf(10, PercentDecimals)) / powf(10, PercentDecimals); + lastSend = millis(); + + publishMqtt("batteryVoltage", String(lastBattVoltage, VoltageDecimals).c_str()); + publishMqtt("batteryPercent", String(lastBattPercent, PercentDecimals).c_str()); + DEBUG_PRINTLN(F("Battery Voltage: ") + String(lastBattVoltage, VoltageDecimals) + F("V")); + DEBUG_PRINTLN(F("Battery Percent: ") + String(lastBattPercent, PercentDecimals) + F("%")); + } + } + + void onMqttConnect(bool sessionPresent) + { + if (WLED_MQTT_CONNECTED && !mqttInitialized) + { + _mqttInitialize(); + mqttInitialized = true; + } + } + + inline float getBatteryVoltageV() { + return (float) lastBattVoltage; + } + + inline float getBatteryPercent() { + return (float) lastBattPercent; + } + + void addToJsonInfo(JsonObject& root) + { + // if "u" object does not exist yet wee need to create it + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + + JsonArray battery_json = user.createNestedArray(F("Battery Monitor")); + if (!enabled) { + battery_json.add(F("Disabled")); + } + else if(!monitorFound) { + battery_json.add(F("MAX17048 Not Found")); + } + else if (!firstReadComplete) { + // if we haven't read the sensor yet, let the user know + // that we are still waiting for the first measurement + battery_json.add((USERMOD_MAX17048_FIRST_MONITOR_AT - millis()) / 1000); + battery_json.add(F(" sec until read")); + } else { + battery_json.add(F("Enabled")); + JsonArray voltage_json = user.createNestedArray(F("Battery Voltage")); + voltage_json.add(lastBattVoltage); + voltage_json.add(F("V")); + JsonArray percent_json = user.createNestedArray(F("Battery Percent")); + percent_json.add(lastBattPercent); + percent_json.add(F("%")); + } + } + + void addToJsonState(JsonObject& root) + { + JsonObject usermod = root[FPSTR(_name)]; + if (usermod.isNull()) + { + usermod = root.createNestedObject(FPSTR(_name)); + } + usermod[FPSTR(_enabled)] = enabled; + } + + void readFromJsonState(JsonObject& root) + { + JsonObject usermod = root[FPSTR(_name)]; + if (!usermod.isNull()) + { + if (usermod[FPSTR(_enabled)].is()) + { + enabled = usermod[FPSTR(_enabled)].as(); + } + } + } + + void addToConfig(JsonObject& root) + { + JsonObject top = root.createNestedObject(FPSTR(_name)); + top[FPSTR(_enabled)] = enabled; + top[FPSTR(_maxReadInterval)] = maxReadingInterval; + top[FPSTR(_minReadInterval)] = minReadingInterval; + top[FPSTR(_HomeAssistantDiscovery)] = HomeAssistantDiscovery; + DEBUG_PRINT(F(_name)); + DEBUG_PRINTLN(F(" config saved.")); + } + + bool readFromConfig(JsonObject& root) + { + JsonObject top = root[FPSTR(_name)]; + + if (top.isNull()) { + DEBUG_PRINT(F(_name)); + DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + return false; + } + + bool configComplete = !top.isNull(); + + configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled); + configComplete &= getJsonValue(top[FPSTR(_maxReadInterval)], maxReadingInterval, USERMOD_MAX17048_MAX_MONITOR_INTERVAL); + configComplete &= getJsonValue(top[FPSTR(_minReadInterval)], minReadingInterval, USERMOD_MAX17048_MIN_MONITOR_INTERVAL); + configComplete &= getJsonValue(top[FPSTR(_HomeAssistantDiscovery)], HomeAssistantDiscovery, false); + + DEBUG_PRINT(FPSTR(_name)); + if (!initDone) { + // first run: reading from cfg.json + DEBUG_PRINTLN(F(" config loaded.")); + } else { + DEBUG_PRINTLN(F(" config (re)loaded.")); + // changing parameters from settings page + } + + return configComplete; + } + + uint16_t getId() + { + return USERMOD_ID_MAX17048; + } + +}; + + +// add more strings here to reduce flash memory usage +const char Usermod_MAX17048::_name[] PROGMEM = "Adafruit MAX17048 Battery Monitor"; +const char Usermod_MAX17048::_enabled[] PROGMEM = "enabled"; +const char Usermod_MAX17048::_maxReadInterval[] PROGMEM = "max-read-interval-ms"; +const char Usermod_MAX17048::_minReadInterval[] PROGMEM = "min-read-interval-ms"; +const char Usermod_MAX17048::_HomeAssistantDiscovery[] PROGMEM = "HomeAssistantDiscovery"; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index bd5758e31..00196e38e 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -473,18 +473,18 @@ CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, u // relies on WS2812FX::service() to call it for each frame void Segment::handleRandomPalette() { // is it time to generate a new palette? - if ((millis()/1000U) - _lastPaletteChange > randomPaletteChangeTime) { - _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); - _lastPaletteChange = millis()/1000U; - _lastPaletteBlend = (uint16_t)(millis() & 0xFFFF)-512; // starts blending immediately + if ((uint16_t)(millis() / 1000U) - _lastPaletteChange > randomPaletteChangeTime){ + _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); + _lastPaletteChange = (uint16_t)(millis() / 1000U); + _lastPaletteBlend = (uint16_t)millis() - 512; // starts blending immediately } // if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls) if (strip.paletteFade) { // assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less) // in reality there need to be 255 blends to fully blend two entirely different palettes - if ((millis() & 0xFFFF) - _lastPaletteBlend < strip.getTransition() >> 7) return; // not yet time to fade, delay the update - _lastPaletteBlend = millis(); + if ((uint16_t)((uint16_t)millis() - _lastPaletteBlend) < strip.getTransition() >> 7) return; // not yet time to fade, delay the update + _lastPaletteBlend = (uint16_t)millis(); } nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48); } diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 764ab6e2b..82e81a387 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -505,7 +505,7 @@ void BusPwm::show() { uint8_t numPins = NUM_PWM_PINS(_type); unsigned maxBri = (1<<_depth) - 1; #ifdef ESP8266 - unsigned pwmBri = (unsigned)(roundf(powf((float)_bri / 255.0f, 1.7f) * (float)maxBri + 0.5f)); // using gamma 1.7 to extrapolate PWM duty cycle + unsigned pwmBri = (unsigned)(roundf(powf((float)_bri / 255.0f, 1.7f) * (float)maxBri)); // using gamma 1.7 to extrapolate PWM duty cycle #else unsigned pwmBri = cieLUT[_bri] >> (12 - _depth); // use CIE LUT #endif diff --git a/wled00/button.cpp b/wled00/button.cpp index cf4599834..ce47a17ac 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -96,9 +96,13 @@ bool isButtonPressed(uint8_t i) case BTN_TYPE_TOUCH: case BTN_TYPE_TOUCH_SWITCH: #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) - if (digitalPinToTouchChannel(btnPin[i]) >= 0 && touchRead(pin) <= touchThreshold) return true; + #ifdef SOC_TOUCH_VERSION_2 //ESP32 S2 and S3 provide a function to check touch state (state is updated in interrupt) + if (touchInterruptGetLastStatus(pin)) return true; + #else + if (digitalPinToTouchChannel(btnPin[i]) >= 0 && touchRead(pin) <= touchThreshold) return true; + #endif #endif - break; + break; } return false; } @@ -403,3 +407,8 @@ void handleIO() offMode = true; } } + +void IRAM_ATTR touchButtonISR() +{ + // used for ESP32 S2 and S3: nothing to do, ISR is just used to update registers of HAL driver +} diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 4dd1d133a..530777ab5 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -229,6 +229,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { // read multiple button configuration JsonObject btn_obj = hw["btn"]; + CJSON(touchThreshold, btn_obj[F("tt")]); bool pull = btn_obj[F("pull")] | (!disablePullUp); // if true, pullup is enabled disablePullUp = !pull; JsonArray hw_btn_ins = btn_obj["ins"]; @@ -253,6 +254,13 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { btnPin[s] = -1; pinManager.deallocatePin(pin,PinOwner::Button); } + //if touch pin, enable the touch interrupt on ESP32 S2 & S3 + #ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a fucntion to check touch state but need to attach an interrupt to do so + if ((buttonType[s] == BTN_TYPE_TOUCH || buttonType[s] == BTN_TYPE_TOUCH_SWITCH)) + { + touchAttachInterrupt(btnPin[s], touchButtonISR, 256 + (touchThreshold << 4)); // threshold on Touch V2 is much higher (1500 is a value given by Espressif example, I measured changes of over 5000) + } + #endif else #endif { @@ -309,7 +317,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { } } } - CJSON(touchThreshold,btn_obj[F("tt")]); + CJSON(buttonPublishMqtt,btn_obj["mqtt"]); #ifndef WLED_DISABLE_INFRARED diff --git a/wled00/const.h b/wled00/const.h index c8a4b7ffe..0ce7b27d5 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -173,6 +173,7 @@ #define USERMOD_ID_ANIMARTRIX 45 //Usermod "usermod_v2_animartrix.h" #define USERMOD_ID_HTTP_PULL_LIGHT_CONTROL 46 //usermod "usermod_v2_HttpPullLightControl.h" #define USERMOD_ID_TETRISAI 47 //Usermod "usermod_v2_tetris.h" +#define USERMOD_ID_MAX17048 48 //Usermod "usermod_max17048.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot @@ -507,11 +508,11 @@ //this is merely a default now and can be changed at runtime #ifndef LEDPIN -//#if defined(ESP8266) || (defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM)) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ARDUINO_ESP32_PICO) +#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 -//#else -// #define LEDPIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards -//#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 diff --git a/wled00/data/index.js b/wled00/data/index.js index 1a50b6a3b..f6d86d007 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -694,8 +694,6 @@ function parseInfo(i) { function populateInfo(i) { var cn=""; - var heap = i.freeheap/1024; - heap = heap.toFixed(1); var pwr = i.leds.pwr; var pwru = "Not calculated"; if (pwr > 1000) {pwr /= 1000; pwr = pwr.toFixed((pwr > 10) ? 0 : 1); pwru = pwr + " A";} @@ -720,11 +718,13 @@ ${inforow("Build",i.vid)} ${inforow("Signal strength",i.wifi.signal +"% ("+ i.wifi.rssi, " dBm)")} ${inforow("Uptime",getRuntimeStr(i.uptime))} ${inforow("Time",i.time)} -${inforow("Free heap",heap," kB")} +${inforow("Free heap",(i.freeheap/1024).toFixed(1)," kB")} ${i.psram?inforow("Free PSRAM",(i.psram/1024).toFixed(1)," kB"):""} ${inforow("Estimated current",pwru)} ${inforow("Average FPS",i.leds.fps)} ${inforow("MAC address",i.mac)} +${inforow("CPU clock",i.clock," MHz")} +${inforow("Flash size",i.flash," MB")} ${inforow("Filesystem",i.fs.u + "/" + i.fs.t + " kB (" +Math.round(i.fs.u*100/i.fs.t) + "%)")} ${inforow("Environment",i.arch + " " + i.core + " (" + i.lwip + ")")} `; diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 9a843dbf4..010ad3a53 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -20,6 +20,7 @@ void doublePressAction(uint8_t b=0); bool isButtonPressed(uint8_t b=0); void handleButton(); void handleIO(); +void IRAM_ATTR touchButtonISR(); //cfg.cpp bool deserializeConfig(JsonObject doc, bool fromFS = false); diff --git a/wled00/json.cpp b/wled00/json.cpp index bde6c36c7..269d8a7f6 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -757,6 +757,8 @@ void serializeInfo(JsonObject root) root[F("arch")] = ESP.getChipModel(); #endif root[F("core")] = ESP.getSdkVersion(); + root[F("clock")] = ESP.getCpuFreqMHz(); + root[F("flash")] = (ESP.getFlashChipSize()/1024)/1024; #ifdef WLED_DEBUG root[F("maxalloc")] = ESP.getMaxAllocHeap(); root[F("resetReason0")] = (int)rtc_get_reset_reason(0); @@ -766,6 +768,8 @@ void serializeInfo(JsonObject root) #else root[F("arch")] = "esp8266"; root[F("core")] = ESP.getCoreVersion(); + root[F("clock")] = ESP.getCpuFreqMHz(); + root[F("flash")] = (ESP.getFlashChipSize()/1024)/1024; #ifdef WLED_DEBUG root[F("maxalloc")] = ESP.getMaxFreeBlockSize(); root[F("resetReason")] = (int)ESP.getResetInfoPtr()->reason; diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 34705ee94..dd00943d8 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -248,7 +248,7 @@ bool PinManagerClass::isPinOk(byte gpio, bool output) const // 00 to 18 are for general use. Be careful about straping pins GPIO0 and GPIO3 - these may be pulled-up or pulled-down on your board. if (gpio > 18 && gpio < 21) return false; // 19 + 20 = USB-JTAG. Not recommended for other uses. if (gpio > 21 && gpio < 33) return false; // 22 to 32: not connected + SPI FLASH - //if (gpio > 32 && gpio < 38) return false; // 33 to 37: not available if using _octal_ SPI Flash or _octal_ PSRAM + if (gpio > 32 && gpio < 38) return !psramFound(); // 33 to 37: not available if using _octal_ SPI Flash or _octal_ PSRAM // 38 to 48 are for general use. Be careful about straping pins GPIO45 and GPIO46 - these may be pull-up or pulled-down on your board. #elif defined(CONFIG_IDF_TARGET_ESP32S2) // strapping pins: 0, 45 & 46 @@ -257,6 +257,7 @@ bool PinManagerClass::isPinOk(byte gpio, bool output) const // GPIO46 is input only and pulled down #else if (gpio > 5 && gpio < 12) return false; //SPI flash pins + if (strncmp_P(PSTR("ESP32-PICO"), ESP.getChipModel(), 10) == 0 && (gpio == 16 || gpio == 17)) return false; // PICO-D4: gpio16+17 are in use for onboard SPI FLASH if (gpio == 16 || gpio == 17) return !psramFound(); //PSRAM pins on ESP32 (these are IO) #endif if (output) return digitalPinCanOutput(gpio); diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index 464bd54ae..398e9bdc0 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -61,7 +61,8 @@ enum struct PinOwner : uint8_t { UM_Audioreactive = USERMOD_ID_AUDIOREACTIVE, // 0x20 // Usermod "audio_reactive.h" UM_SdCard = USERMOD_ID_SD_CARD, // 0x25 // Usermod "usermod_sd_card.h" UM_PWM_OUTPUTS = USERMOD_ID_PWM_OUTPUTS, // 0x26 // Usermod "usermod_pwm_outputs.h" - UM_LDR_DUSK_DAWN = USERMOD_ID_LDR_DUSK_DAWN // 0x2B // Usermod "usermod_LDR_Dusk_Dawn_v2.h" + 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" }; static_assert(0u == static_cast(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected"); diff --git a/wled00/set.cpp b/wled00/set.cpp index 9b3b6bea7..a2e884c81 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -110,6 +110,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) for (uint8_t s=0; s=0 && pinManager.isPinAllocated(btnPin[s], PinOwner::Button)) { pinManager.deallocatePin(btnPin[s], PinOwner::Button); + #ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state, detach interrupt + if (digitalPinToTouchChannel(btnPin[s]) >= 0) // if touch capable pin + touchDetachInterrupt(btnPin[s]); // if not assigned previously, this will do nothing + #endif } } @@ -241,6 +245,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) rlyMde = (bool)request->hasArg(F("RM")); disablePullUp = (bool)request->hasArg(F("IP")); + touchThreshold = request->arg(F("TT")).toInt(); for (uint8_t i=0; i10) char be[4] = "BE"; be[2] = (i<10?48:55)+i; be[3] = 0; // button type (use A,B,C,... if WLED_MAX_BUTTONS>10) @@ -257,12 +262,21 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) btnPin[i] = -1; pinManager.deallocatePin(hw_btn_pin,PinOwner::Button); } - else if ((buttonType[i] == BTN_TYPE_TOUCH || buttonType[i] == BTN_TYPE_TOUCH_SWITCH) && digitalPinToTouchChannel(btnPin[i]) < 0) + else if ((buttonType[i] == BTN_TYPE_TOUCH || buttonType[i] == BTN_TYPE_TOUCH_SWITCH)) { - // not a touch pin - DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not an touch pin!\n"), btnPin[i], i); - btnPin[i] = -1; - pinManager.deallocatePin(hw_btn_pin,PinOwner::Button); + if (digitalPinToTouchChannel(btnPin[i]) < 0) + { + // not a touch pin + DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not an touch pin!\n"), btnPin[i], i); + btnPin[i] = -1; + pinManager.deallocatePin(hw_btn_pin,PinOwner::Button); + } + #ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a fucntion to check touch state but need to attach an interrupt to do so + else + { + touchAttachInterrupt(btnPin[i], touchButtonISR, 256 + (touchThreshold << 4)); // threshold on Touch V2 is much higher (1500 is a value given by Espressif example, I measured changes of over 5000) + } + #endif } else #endif @@ -282,7 +296,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) buttonType[i] = BTN_TYPE_NONE; } } - touchThreshold = request->arg(F("TT")).toInt(); briS = request->arg(F("CA")).toInt(); diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index f76606aae..3f34d18fe 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -209,8 +209,12 @@ #include "../usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h" #endif +#ifdef USERMOD_MAX17048 + #include "../usermods/MAX17048_v2/usermod_max17048.h" +#endif + #ifdef USERMOD_TETRISAI -#include "../usermods/TetrisAI_v2/usermod_v2_tetrisai.h" + #include "../usermods/TetrisAI_v2/usermod_v2_tetrisai.h" #endif void registerUsermods() @@ -410,6 +414,10 @@ void registerUsermods() usermods.add(new StairwayWipeUsermod()); #endif + #ifdef USERMOD_MAX17048 + usermods.add(new Usermod_MAX17048()); + #endif + #ifdef USERMOD_TETRISAI usermods.add(new TetrisAIUsermod()); #endif diff --git a/wled00/wled.cpp b/wled00/wled.cpp index c64ea935c..eb7860851 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -362,13 +362,15 @@ void WLED::setup() DEBUG_PRINT(F(", speed ")); DEBUG_PRINT(ESP.getFlashChipSpeed()/1000000);DEBUG_PRINTLN(F("MHz.")); #else - DEBUG_PRINT(F("esp8266 ")); + DEBUG_PRINT(F("esp8266 @ ")); DEBUG_PRINT(ESP.getCpuFreqMHz()); DEBUG_PRINT(F("MHz.\nCore: ")); DEBUG_PRINTLN(ESP.getCoreVersion()); + DEBUG_PRINT(F("FLASH: ")); DEBUG_PRINT((ESP.getFlashChipSize()/1024)/1024); DEBUG_PRINTLN(F(" MB")); #endif DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); #if defined(ARDUINO_ARCH_ESP32) - #ifndef BOARD_HAS_PSRAM + // BOARD_HAS_PSRAM also means that a compiler flag "-mfix-esp32-psram-cache-issue" was used and so PSRAM is safe to use on rev.1 ESP32 + #if !defined(BOARD_HAS_PSRAM) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)) if (psramFound() && ESP.getChipRevision() < 3) psramSafe = false; if (!psramSafe) DEBUG_PRINTLN(F("Not using PSRAM.")); #endif @@ -380,11 +382,6 @@ void WLED::setup() DEBUG_PRINT(F("Free PSRAM : ")); DEBUG_PRINT(ESP.getFreePsram()/1024); DEBUG_PRINTLN("kB"); } #endif -#if defined(ARDUINO_ESP32_PICO) - // special handling for PICO-D4: gpio16+17 are in use for onboard SPI FLASH (not PSRAM) - managed_pin_type pins[] = { {16, true}, {17, true} }; - pinManager.allocateMultiplePins(pins, sizeof(pins)/sizeof(managed_pin_type), PinOwner::SPI_RAM); -#endif #if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST) pinManager.allocatePin(hardwareTX, true, PinOwner::DebugOut); // TX (GPIO1 on ESP32) reserved for debug output diff --git a/wled00/wled.h b/wled00/wled.h index a58e1ceab..5a9b8dcf5 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2404030 +#define VERSION 2404050 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG diff --git a/wled00/xml.cpp b/wled00/xml.cpp index c91f0dd7e..3915d9b0e 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -145,10 +145,13 @@ void appendGPIOinfo() { 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 @@ -163,14 +166,6 @@ void appendGPIOinfo() { //Note: Using pin 3 (RX) disables Adalight / Serial JSON - #if defined(ARDUINO_ARCH_ESP32) - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32C3) - if (psramFound()) oappend(SET_F(",16,17")); // GPIO16 & GPIO17 reserved for SPI RAM on ESP32 (not on S2, S3 or C3) - #elif defined(CONFIG_IDF_TARGET_ESP32S3) - 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. - #endif - #endif - #ifdef WLED_USE_ETHERNET if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) { for (uint8_t p=0; p