Merge branch '0_15' into blending-styles

This commit is contained in:
Blaz Kristan 2024-04-08 15:28:57 +02:00
commit c6805271e9
20 changed files with 439 additions and 46 deletions

View File

@ -163,6 +163,9 @@ lib_deps =
#For ADS1115 sensor uncomment following #For ADS1115 sensor uncomment following
;adafruit/Adafruit BusIO @ 1.13.2 ;adafruit/Adafruit BusIO @ 1.13.2
;adafruit/Adafruit ADS1X15 @ 2.4.0 ;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 #For MPU6050 IMU uncomment follwoing
;electroniccats/MPU6050 @1.0.1 ;electroniccats/MPU6050 @1.0.1
# For -D USERMOD_ANIMARTRIX # For -D USERMOD_ANIMARTRIX

View File

@ -83,6 +83,7 @@ describe('Script', () => {
// Backup files // Backup files
fs.cpSync("wled00/data", "wled00Backup", { recursive: true }); fs.cpSync("wled00/data", "wled00Backup", { recursive: true });
fs.cpSync("tools/cdata.js", "cdata.bak.js"); fs.cpSync("tools/cdata.js", "cdata.bak.js");
fs.cpSync("package.json", "package.bak.json");
}); });
after(() => { after(() => {
// Restore backup // Restore backup
@ -90,6 +91,8 @@ describe('Script', () => {
fs.renameSync("wled00Backup", "wled00/data"); fs.renameSync("wled00Backup", "wled00/data");
fs.rmSync("tools/cdata.js"); fs.rmSync("tools/cdata.js");
fs.renameSync("cdata.bak.js", "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 // delete all html_*.h files
@ -131,7 +134,7 @@ describe('Script', () => {
// run script cdata.js again and wait for it to finish // run script cdata.js again and wait for it to finish
await execPromise('node tools/cdata.js'); await execPromise('node tools/cdata.js');
checkIfFileWasNewlyCreated(path.join(folderPath, resultFile)); await checkIfFileWasNewlyCreated(path.join(folderPath, resultFile));
} }
describe('should build if', () => { describe('should build if', () => {
@ -182,6 +185,10 @@ describe('Script', () => {
it('cdata.js changes', async () => { it('cdata.js changes', async () => {
await testFileModification('tools/cdata.js', 'html_ui.h'); 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', () => { describe('should not build if', () => {

View File

@ -2,7 +2,7 @@
* Writes compressed C arrays of data files (web interface) * Writes compressed C arrays of data files (web interface)
* How to use it? * How to use it?
* *
* 1) Install Node 11+ and npm * 1) Install Node 20+ and npm
* 2) npm install * 2) npm install
* 3) npm run build * 3) npm run build
* *
@ -207,7 +207,7 @@ function isAnyFileInFolderNewerThan(folderPath, time) {
} }
// Check if the web UI is already built // Check if the web UI is already built
function isAlreadyBuilt(folderPath) { function isAlreadyBuilt(webUIPath, packageJsonPath = "package.json") {
let lastBuildTime = Infinity; let lastBuildTime = Infinity;
for (const file of output) { 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 // Don't run this script if we're in a test environment

View File

@ -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 (`<deviceTopic>` is set in MQTT section of Sync Setup menu):
Measurement type | MQTT topic
--- | ---
Battery Voltage | `<deviceTopic>/batteryVoltage`
Battery Percent | `<deviceTopic>/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`

View File

@ -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<bool>())
{
enabled = usermod[FPSTR(_enabled)].as<bool>();
}
}
}
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";

View File

@ -473,18 +473,18 @@ CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, u
// relies on WS2812FX::service() to call it for each frame // relies on WS2812FX::service() to call it for each frame
void Segment::handleRandomPalette() { void Segment::handleRandomPalette() {
// is it time to generate a new palette? // is it time to generate a new palette?
if ((millis()/1000U) - _lastPaletteChange > randomPaletteChangeTime) { if ((uint16_t)(millis() / 1000U) - _lastPaletteChange > randomPaletteChangeTime){
_newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette();
_lastPaletteChange = millis()/1000U; _lastPaletteChange = (uint16_t)(millis() / 1000U);
_lastPaletteBlend = (uint16_t)(millis() & 0xFFFF)-512; // starts blending immediately _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 palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls)
if (strip.paletteFade) { if (strip.paletteFade) {
// assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less) // 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 // 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 if ((uint16_t)((uint16_t)millis() - _lastPaletteBlend) < strip.getTransition() >> 7) return; // not yet time to fade, delay the update
_lastPaletteBlend = millis(); _lastPaletteBlend = (uint16_t)millis();
} }
nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48); nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48);
} }

View File

@ -505,7 +505,7 @@ void BusPwm::show() {
uint8_t numPins = NUM_PWM_PINS(_type); uint8_t numPins = NUM_PWM_PINS(_type);
unsigned maxBri = (1<<_depth) - 1; unsigned maxBri = (1<<_depth) - 1;
#ifdef ESP8266 #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 #else
unsigned pwmBri = cieLUT[_bri] >> (12 - _depth); // use CIE LUT unsigned pwmBri = cieLUT[_bri] >> (12 - _depth); // use CIE LUT
#endif #endif

View File

@ -96,9 +96,13 @@ bool isButtonPressed(uint8_t i)
case BTN_TYPE_TOUCH: case BTN_TYPE_TOUCH:
case BTN_TYPE_TOUCH_SWITCH: case BTN_TYPE_TOUCH_SWITCH:
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) #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 #endif
break; break;
} }
return false; return false;
} }
@ -403,3 +407,8 @@ void handleIO()
offMode = true; 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
}

View File

@ -229,6 +229,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
// read multiple button configuration // read multiple button configuration
JsonObject btn_obj = hw["btn"]; JsonObject btn_obj = hw["btn"];
CJSON(touchThreshold, btn_obj[F("tt")]);
bool pull = btn_obj[F("pull")] | (!disablePullUp); // if true, pullup is enabled bool pull = btn_obj[F("pull")] | (!disablePullUp); // if true, pullup is enabled
disablePullUp = !pull; disablePullUp = !pull;
JsonArray hw_btn_ins = btn_obj["ins"]; JsonArray hw_btn_ins = btn_obj["ins"];
@ -253,6 +254,13 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
btnPin[s] = -1; btnPin[s] = -1;
pinManager.deallocatePin(pin,PinOwner::Button); 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 else
#endif #endif
{ {
@ -309,7 +317,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
} }
} }
} }
CJSON(touchThreshold,btn_obj[F("tt")]);
CJSON(buttonPublishMqtt,btn_obj["mqtt"]); CJSON(buttonPublishMqtt,btn_obj["mqtt"]);
#ifndef WLED_DISABLE_INFRARED #ifndef WLED_DISABLE_INFRARED

View File

@ -173,6 +173,7 @@
#define USERMOD_ID_ANIMARTRIX 45 //Usermod "usermod_v2_animartrix.h" #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_HTTP_PULL_LIGHT_CONTROL 46 //usermod "usermod_v2_HttpPullLightControl.h"
#define USERMOD_ID_TETRISAI 47 //Usermod "usermod_v2_tetris.h" #define USERMOD_ID_TETRISAI 47 //Usermod "usermod_v2_tetris.h"
#define USERMOD_ID_MAX17048 48 //Usermod "usermod_max17048.h"
//Access point behavior //Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot #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 //this is merely a default now and can be changed at runtime
#ifndef LEDPIN #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 #define LEDPIN 2 // GPIO2 (D4) on Wemos D1 mini compatible boards, safe to use on any board
//#else #else
// #define LEDPIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards #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
#endif #endif
#ifdef WLED_ENABLE_DMX #ifdef WLED_ENABLE_DMX

View File

@ -694,8 +694,6 @@ function parseInfo(i) {
function populateInfo(i) function populateInfo(i)
{ {
var cn=""; var cn="";
var heap = i.freeheap/1024;
heap = heap.toFixed(1);
var pwr = i.leds.pwr; var pwr = i.leds.pwr;
var pwru = "Not calculated"; var pwru = "Not calculated";
if (pwr > 1000) {pwr /= 1000; pwr = pwr.toFixed((pwr > 10) ? 0 : 1); pwru = pwr + " A";} 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("Signal strength",i.wifi.signal +"% ("+ i.wifi.rssi, " dBm)")}
${inforow("Uptime",getRuntimeStr(i.uptime))} ${inforow("Uptime",getRuntimeStr(i.uptime))}
${inforow("Time",i.time)} ${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"):""} ${i.psram?inforow("Free PSRAM",(i.psram/1024).toFixed(1)," kB"):""}
${inforow("Estimated current",pwru)} ${inforow("Estimated current",pwru)}
${inforow("Average FPS",i.leds.fps)} ${inforow("Average FPS",i.leds.fps)}
${inforow("MAC address",i.mac)} ${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("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 + ")")} ${inforow("Environment",i.arch + " " + i.core + " (" + i.lwip + ")")}
</table>`; </table>`;

View File

@ -20,6 +20,7 @@ void doublePressAction(uint8_t b=0);
bool isButtonPressed(uint8_t b=0); bool isButtonPressed(uint8_t b=0);
void handleButton(); void handleButton();
void handleIO(); void handleIO();
void IRAM_ATTR touchButtonISR();
//cfg.cpp //cfg.cpp
bool deserializeConfig(JsonObject doc, bool fromFS = false); bool deserializeConfig(JsonObject doc, bool fromFS = false);

View File

@ -757,6 +757,8 @@ void serializeInfo(JsonObject root)
root[F("arch")] = ESP.getChipModel(); root[F("arch")] = ESP.getChipModel();
#endif #endif
root[F("core")] = ESP.getSdkVersion(); root[F("core")] = ESP.getSdkVersion();
root[F("clock")] = ESP.getCpuFreqMHz();
root[F("flash")] = (ESP.getFlashChipSize()/1024)/1024;
#ifdef WLED_DEBUG #ifdef WLED_DEBUG
root[F("maxalloc")] = ESP.getMaxAllocHeap(); root[F("maxalloc")] = ESP.getMaxAllocHeap();
root[F("resetReason0")] = (int)rtc_get_reset_reason(0); root[F("resetReason0")] = (int)rtc_get_reset_reason(0);
@ -766,6 +768,8 @@ void serializeInfo(JsonObject root)
#else #else
root[F("arch")] = "esp8266"; root[F("arch")] = "esp8266";
root[F("core")] = ESP.getCoreVersion(); root[F("core")] = ESP.getCoreVersion();
root[F("clock")] = ESP.getCpuFreqMHz();
root[F("flash")] = (ESP.getFlashChipSize()/1024)/1024;
#ifdef WLED_DEBUG #ifdef WLED_DEBUG
root[F("maxalloc")] = ESP.getMaxFreeBlockSize(); root[F("maxalloc")] = ESP.getMaxFreeBlockSize();
root[F("resetReason")] = (int)ESP.getResetInfoPtr()->reason; root[F("resetReason")] = (int)ESP.getResetInfoPtr()->reason;

View File

@ -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. // 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 > 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 > 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. // 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) #elif defined(CONFIG_IDF_TARGET_ESP32S2)
// strapping pins: 0, 45 & 46 // strapping pins: 0, 45 & 46
@ -257,6 +257,7 @@ bool PinManagerClass::isPinOk(byte gpio, bool output) const
// GPIO46 is input only and pulled down // GPIO46 is input only and pulled down
#else #else
if (gpio > 5 && gpio < 12) return false; //SPI flash pins 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) if (gpio == 16 || gpio == 17) return !psramFound(); //PSRAM pins on ESP32 (these are IO)
#endif #endif
if (output) return digitalPinCanOutput(gpio); if (output) return digitalPinCanOutput(gpio);

View File

@ -61,7 +61,8 @@ enum struct PinOwner : uint8_t {
UM_Audioreactive = USERMOD_ID_AUDIOREACTIVE, // 0x20 // Usermod "audio_reactive.h" UM_Audioreactive = USERMOD_ID_AUDIOREACTIVE, // 0x20 // Usermod "audio_reactive.h"
UM_SdCard = USERMOD_ID_SD_CARD, // 0x25 // Usermod "usermod_sd_card.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_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<uint8_t>(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected"); static_assert(0u == static_cast<uint8_t>(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected");

View File

@ -110,6 +110,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
for (uint8_t s=0; s<WLED_MAX_BUTTONS; s++) { for (uint8_t s=0; s<WLED_MAX_BUTTONS; s++) {
if (btnPin[s]>=0 && pinManager.isPinAllocated(btnPin[s], PinOwner::Button)) { if (btnPin[s]>=0 && pinManager.isPinAllocated(btnPin[s], PinOwner::Button)) {
pinManager.deallocatePin(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")); rlyMde = (bool)request->hasArg(F("RM"));
disablePullUp = (bool)request->hasArg(F("IP")); disablePullUp = (bool)request->hasArg(F("IP"));
touchThreshold = request->arg(F("TT")).toInt();
for (uint8_t i=0; i<WLED_MAX_BUTTONS; i++) { for (uint8_t i=0; i<WLED_MAX_BUTTONS; i++) {
char bt[4] = "BT"; bt[2] = (i<10?48:55)+i; bt[3] = 0; // button pin (use A,B,C,... if WLED_MAX_BUTTONS>10) char bt[4] = "BT"; bt[2] = (i<10?48:55)+i; bt[3] = 0; // button pin (use A,B,C,... if WLED_MAX_BUTTONS>10)
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) 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; btnPin[i] = -1;
pinManager.deallocatePin(hw_btn_pin,PinOwner::Button); 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 if (digitalPinToTouchChannel(btnPin[i]) < 0)
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; // not a touch pin
pinManager.deallocatePin(hw_btn_pin,PinOwner::Button); 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 else
#endif #endif
@ -282,7 +296,6 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
buttonType[i] = BTN_TYPE_NONE; buttonType[i] = BTN_TYPE_NONE;
} }
} }
touchThreshold = request->arg(F("TT")).toInt();
briS = request->arg(F("CA")).toInt(); briS = request->arg(F("CA")).toInt();

View File

@ -209,8 +209,12 @@
#include "../usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h" #include "../usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h"
#endif #endif
#ifdef USERMOD_MAX17048
#include "../usermods/MAX17048_v2/usermod_max17048.h"
#endif
#ifdef USERMOD_TETRISAI #ifdef USERMOD_TETRISAI
#include "../usermods/TetrisAI_v2/usermod_v2_tetrisai.h" #include "../usermods/TetrisAI_v2/usermod_v2_tetrisai.h"
#endif #endif
void registerUsermods() void registerUsermods()
@ -410,6 +414,10 @@ void registerUsermods()
usermods.add(new StairwayWipeUsermod()); usermods.add(new StairwayWipeUsermod());
#endif #endif
#ifdef USERMOD_MAX17048
usermods.add(new Usermod_MAX17048());
#endif
#ifdef USERMOD_TETRISAI #ifdef USERMOD_TETRISAI
usermods.add(new TetrisAIUsermod()); usermods.add(new TetrisAIUsermod());
#endif #endif

View File

@ -362,13 +362,15 @@ void WLED::setup()
DEBUG_PRINT(F(", speed ")); DEBUG_PRINT(ESP.getFlashChipSpeed()/1000000);DEBUG_PRINTLN(F("MHz.")); DEBUG_PRINT(F(", speed ")); DEBUG_PRINT(ESP.getFlashChipSpeed()/1000000);DEBUG_PRINTLN(F("MHz."));
#else #else
DEBUG_PRINT(F("esp8266 ")); DEBUG_PRINT(F("esp8266 @ ")); DEBUG_PRINT(ESP.getCpuFreqMHz()); DEBUG_PRINT(F("MHz.\nCore: "));
DEBUG_PRINTLN(ESP.getCoreVersion()); DEBUG_PRINTLN(ESP.getCoreVersion());
DEBUG_PRINT(F("FLASH: ")); DEBUG_PRINT((ESP.getFlashChipSize()/1024)/1024); DEBUG_PRINTLN(F(" MB"));
#endif #endif
DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap());
#if defined(ARDUINO_ARCH_ESP32) #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 (psramFound() && ESP.getChipRevision() < 3) psramSafe = false;
if (!psramSafe) DEBUG_PRINTLN(F("Not using PSRAM.")); if (!psramSafe) DEBUG_PRINTLN(F("Not using PSRAM."));
#endif #endif
@ -380,11 +382,6 @@ void WLED::setup()
DEBUG_PRINT(F("Free PSRAM : ")); DEBUG_PRINT(ESP.getFreePsram()/1024); DEBUG_PRINTLN("kB"); DEBUG_PRINT(F("Free PSRAM : ")); DEBUG_PRINT(ESP.getFreePsram()/1024); DEBUG_PRINTLN("kB");
} }
#endif #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) #if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST)
pinManager.allocatePin(hardwareTX, true, PinOwner::DebugOut); // TX (GPIO1 on ESP32) reserved for debug output pinManager.allocatePin(hardwareTX, true, PinOwner::DebugOut); // TX (GPIO1 on ESP32) reserved for debug output

View File

@ -8,7 +8,7 @@
*/ */
// version code in format yymmddb (b = daily build) // 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 //uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG //#define WLED_USE_MY_CONFIG

View File

@ -145,10 +145,13 @@ void appendGPIOinfo() {
oappend(SET_F("d.rsvd=[22,23,24,25,26,27,28,29,30,31,32")); oappend(SET_F("d.rsvd=[22,23,24,25,26,27,28,29,30,31,32"));
#elif defined(CONFIG_IDF_TARGET_ESP32S3) #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) 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) #elif defined(CONFIG_IDF_TARGET_ESP32C3)
oappend(SET_F("d.rsvd=[11,12,13,14,15,16,17")); oappend(SET_F("d.rsvd=[11,12,13,14,15,16,17"));
#elif defined(ESP32) #elif defined(ESP32)
oappend(SET_F("d.rsvd=[6,7,8,9,10,11,24,28,29,30,31,37,38")); 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 #else
oappend(SET_F("d.rsvd=[6,7,8,9,10,11")); oappend(SET_F("d.rsvd=[6,7,8,9,10,11"));
#endif #endif
@ -163,14 +166,6 @@ void appendGPIOinfo() {
//Note: Using pin 3 (RX) disables Adalight / Serial JSON //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 #ifdef WLED_USE_ETHERNET
if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) { if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) {
for (uint8_t p=0; p<WLED_ETH_RSVD_PINS_COUNT; p++) { oappend(","); oappend(itoa(esp32_nonconfigurable_ethernet_pins[p].pin,nS,10)); } for (uint8_t p=0; p<WLED_ETH_RSVD_PINS_COUNT; p++) { oappend(","); oappend(itoa(esp32_nonconfigurable_ethernet_pins[p].pin,nS,10)); }