diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 811db619a..000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,20 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 120 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 -# Issues with these labels will never be considered stale -exemptLabels: - - pinned - - keep - - enhancement - - confirmed -# Label to use when marking an issue as stale -staleLabel: stale -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - Hey! This issue has been open for quite some time without any new comments now. - It will be closed automatically in a week if no further activity occurs. - - Thank you for using WLED! -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..1f2557160 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,30 @@ +name: 'Close stale issues and PRs' +on: + schedule: + - cron: '0 12 * * *' + workflow_dispatch: + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + days-before-stale: 120 + days-before-close: 7 + stale-issue-label: 'stale' + stale-pr-label: 'stale' + exempt-issue-labels: 'pinned,keep,enhancement,confirmed' + exempt-pr-labels: 'pinned,keep,enhancement,confirmed' + exempt-all-milestones: true + operations-per-run: 1000 + stale-issue-message: > + Hey! This issue has been open for quite some time without any new comments now. + It will be closed automatically in a week if no further activity occurs. + + Thank you for using WLED! ✨ + stale-pr-message: > + Hey! This pull request has been open for quite some time without any new comments now. + It will be closed automatically in a week if no further activity occurs. + + Thank you for contributing to WLED! ❤️ diff --git a/CHANGELOG.md b/CHANGELOG.md index e37b08b69..606d9240d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,27 @@ ## WLED changelog -#### Build 240503 +#### Build 2405180 +- Official 0.15.0-b3 release +- Merge 0.14.3 fixes +- Added Pinwheel Expand 1D->2D effect mapping mode (#3961 by @Brandon502) +- Add changeable i2c address to BME280 usermod (#3966 by @LordMike) +- Effect: Firenoise - add palette selection +- Experimental parallel I2S support for ESP32 (compile time option) + - increased outputs to 17 + - increased max possible color order overrides + - use WLED_USE_PARALLEL_I2S during compile + WARNING: Do not set up more than 256 LEDs per output when using parallel I2S with NeoPixelBus less than 2.9.0 +- Update Usermod: Battery (#3964 by @adamsthws) +- Update Usermod: BME280 (#3965 by @LordMike) +- TM1914 chip support (#3913) +- Ignore brightness in Peek +- Antialiased line & circle drawing functions +- Enabled some audioreactive effects for single pixel strips/segments (#3942 by @gaaat98) +- Usermod Battery: Added Support for different battery types, Optimized file structure (#3003 by @itCarl) +- Skip playlist entry API (#3946 by @freakintoddles2) +- various optimisations and bugfixes (#3987, #3978) + +#### Build 2405030 - Using brightness in analog clock overlay (#3944 by @paspiz85) - Add Webpage shortcuts (#3945 by @w00000dy) - ArtNet Poll reply (#3892 by @askask) @@ -32,6 +53,11 @@ - Fix for #3889 - BREAKING: Effect: modified KITT (Scanner) (#3763) +#### Build 2404040 +- WLED 0.14.3 release +- Fix for transition 0 (#3854, #3832, #3720) +- Fix for #3855 via #3873 (by @willmmiles) + #### Build 2403280 - Individual color channel control for JSON API (fixes #3860) - "col":[int|string|object|array, int|string|object|array, int|string|object|array] diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index 14508de72..6becc5d7d 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -97,6 +97,9 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; Use AHT10/AHT15/AHT20 usermod ; -D USERMOD_AHT10 ; +; Use INA226 usermod +; -D USERMOD_INA226 +; ; Use 4 Line Display usermod with SPI display ; -D USERMOD_FOUR_LINE_DISPLAY ; -D USE_ALT_DISPlAY # mandatory diff --git a/requirements.txt b/requirements.txt index d6f86e202..35c89a5e2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,7 +28,7 @@ h11==0.14.0 # via # uvicorn # wsproto -idna==3.4 +idna==3.7 # via # anyio # requests @@ -52,6 +52,8 @@ starlette==0.23.1 # via platformio tabulate==0.9.0 # via platformio +typing-extensions==4.11.0 + # via starlette urllib3==1.26.18 # via requests uvicorn==0.20.0 diff --git a/usermods/BME280_v2/README.md b/usermods/BME280_v2/README.md index 0a4afbf1f..a4fc229a3 100644 --- a/usermods/BME280_v2/README.md +++ b/usermods/BME280_v2/README.md @@ -7,6 +7,7 @@ This Usermod is designed to read a `BME280` or `BMP280` sensor and output the fo - Dew Point (`BME280` only) Configuration is performed via the Usermod menu. There are no parameters to set in code! The following settings can be configured in the Usermod Menu: +- The i2c address in decimal. Set it to either 118 (0x76, the default) or 119 (0x77). - Temperature Decimals (number of decimal places to output) - Humidity Decimals - Pressure Decimals diff --git a/usermods/BME280_v2/usermod_bme280.h b/usermods/BME280_v2/usermod_bme280.h index ae6eba89d..9168f4229 100644 --- a/usermods/BME280_v2/usermod_bme280.h +++ b/usermods/BME280_v2/usermod_bme280.h @@ -24,6 +24,7 @@ private: uint8_t PressureDecimals = 0; // Number of decimal places in published pressure values uint16_t TemperatureInterval = 5; // Interval to measure temperature (and humidity, dew point if available) in seconds uint16_t PressureInterval = 300; // Interval to measure pressure in seconds + BME280I2C::I2CAddr I2CAddress = BME280I2C::I2CAddr_0x76; // i2c address, defaults to 0x76 bool PublishAlways = false; // Publish values even when they have not changed bool UseCelsius = true; // Use Celsius for Reporting bool HomeAssistantDiscovery = false; // Publish Home Assistant Device Information @@ -35,20 +36,7 @@ private: #endif bool initDone = false; - // BME280 sensor settings - BME280I2C::Settings settings{ - BME280::OSR_X16, // Temperature oversampling x16 - BME280::OSR_X16, // Humidity oversampling x16 - BME280::OSR_X16, // Pressure oversampling x16 - // Defaults - BME280::Mode_Forced, - BME280::StandbyTime_1000ms, - BME280::Filter_Off, - BME280::SpiEnable_False, - BME280I2C::I2CAddr_0x76 // I2C address. I2C specific. Default 0x76 - }; - - BME280I2C bme{settings}; + BME280I2C bme; uint8_t sensorType; @@ -181,34 +169,52 @@ private: } } + void initializeBmeComms() + { + BME280I2C::Settings settings{ + BME280::OSR_X16, // Temperature oversampling x16 + BME280::OSR_X16, // Humidity oversampling x16 + BME280::OSR_X16, // Pressure oversampling x16 + BME280::Mode_Forced, + BME280::StandbyTime_1000ms, + BME280::Filter_Off, + BME280::SpiEnable_False, + I2CAddress + }; + + bme.setSettings(settings); + + if (!bme.begin()) + { + sensorType = 0; + DEBUG_PRINTLN(F("Could not find BME280 I2C sensor!")); + } + else + { + switch (bme.chipModel()) + { + case BME280::ChipModel_BME280: + sensorType = 1; + DEBUG_PRINTLN(F("Found BME280 sensor! Success.")); + break; + case BME280::ChipModel_BMP280: + sensorType = 2; + DEBUG_PRINTLN(F("Found BMP280 sensor! No Humidity available.")); + break; + default: + sensorType = 0; + DEBUG_PRINTLN(F("Found UNKNOWN sensor! Error!")); + } + } + } + public: void setup() { if (i2c_scl<0 || i2c_sda<0) { enabled = false; sensorType = 0; return; } - if (!bme.begin()) - { - sensorType = 0; - DEBUG_PRINTLN(F("Could not find BME280 I2C sensor!")); - } - else - { - switch (bme.chipModel()) - { - case BME280::ChipModel_BME280: - sensorType = 1; - DEBUG_PRINTLN(F("Found BME280 sensor! Success.")); - break; - case BME280::ChipModel_BMP280: - sensorType = 2; - DEBUG_PRINTLN(F("Found BMP280 sensor! No Humidity available.")); - break; - default: - sensorType = 0; - DEBUG_PRINTLN(F("Found UNKNOWN sensor! Error!")); - } - } - initDone=true; + initializeBmeComms(); + initDone = true; } void loop() @@ -365,7 +371,6 @@ public: } else if (sensorType==2) //BMP280 { - JsonArray temperature_json = user.createNestedArray(F("Temperature")); JsonArray pressure_json = user.createNestedArray(F("Pressure")); temperature_json.add(roundf(sensorTemperature * powf(10, TemperatureDecimals)) / powf(10, TemperatureDecimals)); @@ -399,6 +404,7 @@ public: { JsonObject top = root.createNestedObject(FPSTR(_name)); top[FPSTR(_enabled)] = enabled; + top[F("I2CAddress")] = static_cast(I2CAddress); top[F("TemperatureDecimals")] = TemperatureDecimals; top[F("HumidityDecimals")] = HumidityDecimals; top[F("PressureDecimals")] = PressureDecimals; @@ -426,6 +432,10 @@ public: configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled); // A 3-argument getJsonValue() assigns the 3rd argument as a default value if the Json value is missing + uint8_t tmpI2cAddress; + configComplete &= getJsonValue(top[F("I2CAddress")], tmpI2cAddress, 0x76); + I2CAddress = static_cast(tmpI2cAddress); + configComplete &= getJsonValue(top[F("TemperatureDecimals")], TemperatureDecimals, 1); configComplete &= getJsonValue(top[F("HumidityDecimals")], HumidityDecimals, 0); configComplete &= getJsonValue(top[F("PressureDecimals")], PressureDecimals, 0); @@ -440,8 +450,23 @@ public: // first run: reading from cfg.json DEBUG_PRINTLN(F(" config loaded.")); } else { - DEBUG_PRINTLN(F(" config (re)loaded.")); // changing parameters from settings page + DEBUG_PRINTLN(F(" config (re)loaded.")); + + // Reset all known values + sensorType = 0; + sensorTemperature = 0; + sensorHumidity = 0; + sensorHeatIndex = 0; + sensorDewPoint = 0; + sensorPressure = 0; + lastTemperature = 0; + lastHumidity = 0; + lastHeatIndex = 0; + lastDewPoint = 0; + lastPressure = 0; + + initializeBmeComms(); } return configComplete; diff --git a/usermods/Battery/assets/battery_connection_schematic_esp32.png b/usermods/Battery/assets/battery_connection_schematic_esp32.png new file mode 100644 index 000000000..3890f477e Binary files /dev/null and b/usermods/Battery/assets/battery_connection_schematic_esp32.png differ diff --git a/usermods/Battery/assets/battery_connection_schematic_esp32_v2.png b/usermods/Battery/assets/battery_connection_schematic_esp32_v2.png new file mode 100644 index 000000000..2dff54f88 Binary files /dev/null and b/usermods/Battery/assets/battery_connection_schematic_esp32_v2.png differ diff --git a/usermods/Battery/assets/installation_my_config_h.png b/usermods/Battery/assets/installation_my_config_h.png new file mode 100644 index 000000000..06235a224 Binary files /dev/null and b/usermods/Battery/assets/installation_my_config_h.png differ diff --git a/usermods/Battery/assets/installation_platformio_override_ini.png b/usermods/Battery/assets/installation_platformio_override_ini.png new file mode 100644 index 000000000..72cca7dc1 Binary files /dev/null and b/usermods/Battery/assets/installation_platformio_override_ini.png differ diff --git a/usermods/Battery/readme.md b/usermods/Battery/readme.md index efe25cc24..84a6f5054 100644 --- a/usermods/Battery/readme.md +++ b/usermods/Battery/readme.md @@ -6,54 +6,79 @@ Enables battery level monitoring of your project. -For this to work, the positive side of the (18650) battery must be connected to pin `A0` of the d1 mini/esp8266 with a 100k Ohm resistor (see [Useful Links](#useful-links)). - -If you have an ESP32 board, connect the positive side of the battery to ADC1 (GPIO32 - GPIO39) - -

- +

+

+
+ ## ⚙️ Features -- 💯 Displays current battery voltage +- 💯 Displays current battery voltage - 🚥 Displays battery level -- 🚫 Auto-off with configurable Threshold +- 🚫 Auto-off with configurable threshold - 🚨 Low power indicator with many configuration possibilities +

+ ## 🎈 Installation -define `USERMOD_BATTERY` in `wled00/my_config.h` +| **Option 1** | **Option 2** | +|--------------|--------------| +| In `wled00/my_config.h`
Add the line: `#define USERMOD_BATTERY`

[Example: my_config.h](assets/installation_my_config_h.png) | In `platformio_override.ini` (or `platformio.ini`)
Under: `build_flags =`, add the line: `-D USERMOD_BATTERY`

[Example: platformio_override.ini](assets/installation_platformio_override_ini.png) | -### Example wiring +

-

- -

+## 🔌 Example wiring -### Define Your Options +- (see [Useful Links](#useful-links)). + + + + + + + + +
+ +

ESP8266
+ With a 100k Ohm resistor, connect the positive
+ side of the battery to pin `A0`.

+
+ +

ESP32 (+S2, S3, C3 etc...)
+ Use a voltage divider (two resistors of equal value).
+ Connect to ADC1 (GPIO32 - GPIO39). GPIO35 is Default.

+
+ +

+ +## Define Your Options | Name | Unit | Description | | ----------------------------------------------- | ----------- |-------------------------------------------------------------------------------------- | -| `USERMOD_BATTERY` | | define this (in `my_config.h`) to have this usermod included wled00\usermods_list.cpp | -| `USERMOD_BATTERY_MEASUREMENT_PIN` | | defaults to A0 on ESP8266 and GPIO35 on ESP32 | -| `USERMOD_BATTERY_INITIAL_DELAY` | ms | delay before initial reading. defaults to 10 seconds to allow voltage stabilization -| `USERMOD_BATTERY_MEASUREMENT_INTERVAL` | ms | battery check interval. defaults to 30 seconds | -| `USERMOD_BATTERY_{TYPE}_MIN_VOLTAGE` | v | minimum battery voltage. default is 2.6 (18650 battery standard) | -| `USERMOD_BATTERY_{TYPE}_MAX_VOLTAGE` | v | maximum battery voltage. default is 4.2 (18650 battery standard) | -| `USERMOD_BATTERY_{TYPE}_TOTAL_CAPACITY` | mAh | the capacity of all cells in parallel summed up | -| `USERMOD_BATTERY_{TYPE}_CALIBRATION` | | offset / calibration number, fine tune the measured voltage by the microcontroller | +| `USERMOD_BATTERY` | | Define this (in `my_config.h`) to have this usermod included wled00\usermods_list.cpp | +| `USERMOD_BATTERY_MEASUREMENT_PIN` | | Defaults to A0 on ESP8266 and GPIO35 on ESP32 | +| `USERMOD_BATTERY_MEASUREMENT_INTERVAL` | ms | Battery check interval. defaults to 30 seconds | +| `USERMOD_BATTERY_INITIAL_DELAY` | ms | Delay before initial reading. defaults to 10 seconds to allow voltage stabilization | +| `USERMOD_BATTERY_{TYPE}_MIN_VOLTAGE` | v | Minimum battery voltage. default is 2.6 (18650 battery standard) | +| `USERMOD_BATTERY_{TYPE}_MAX_VOLTAGE` | v | Maximum battery voltage. default is 4.2 (18650 battery standard) | +| `USERMOD_BATTERY_{TYPE}_TOTAL_CAPACITY` | mAh | The capacity of all cells in parallel summed up | +| `USERMOD_BATTERY_{TYPE}_CALIBRATION` | | Offset / calibration number, fine tune the measured voltage by the microcontroller | | Auto-Off | --- | --- | -| `USERMOD_BATTERY_AUTO_OFF_ENABLED` | true/false | enables auto-off | -| `USERMOD_BATTERY_AUTO_OFF_THRESHOLD` | % (0-100) | when this threshold is reached master power turns off | +| `USERMOD_BATTERY_AUTO_OFF_ENABLED` | true/false | Enables auto-off | +| `USERMOD_BATTERY_AUTO_OFF_THRESHOLD` | % (0-100) | When this threshold is reached master power turns off | | Low-Power-Indicator | --- | --- | -| `USERMOD_BATTERY_LOW_POWER_INDICATOR_ENABLED` | true/false | enables low power indication | -| `USERMOD_BATTERY_LOW_POWER_INDICATOR_PRESET` | preset id | when low power is detected then use this preset to indicate low power | -| `USERMOD_BATTERY_LOW_POWER_INDICATOR_THRESHOLD` | % (0-100) | when this threshold is reached low power gets indicated | -| `USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION` | seconds | for this long the configured preset is played | +| `USERMOD_BATTERY_LOW_POWER_INDICATOR_ENABLED` | true/false | Enables low power indication | +| `USERMOD_BATTERY_LOW_POWER_INDICATOR_PRESET` | preset id | When low power is detected then use this preset to indicate low power | +| `USERMOD_BATTERY_LOW_POWER_INDICATOR_THRESHOLD` | % (0-100) | When this threshold is reached low power gets indicated | +| `USERMOD_BATTERY_LOW_POWER_INDICATOR_DURATION` | seconds | For this long the configured preset is played | All parameters can be configured at runtime via the Usermods settings page. +
+ **NOTICE:** Each Battery type can be pre-configured individualy (in `my_config.h`) | Name | Alias | `my_config.h` example | @@ -61,66 +86,86 @@ All parameters can be configured at runtime via the Usermods settings page. | Lithium Polymer | lipo (Li-Po) | `USERMOD_BATTERY_lipo_MIN_VOLTAGE` | | Lithium Ionen | lion (Li-Ion) | `USERMOD_BATTERY_lion_TOTAL_CAPACITY` | +

+ +## 🔧 Calibration + +The calibration number is a value that is added to the final computed voltage after it has been scaled by the voltage multiplier. + +It fine-tunes the voltage reading so that it more closely matches the actual battery voltage, compensating for inaccuracies inherent in the voltage divider resistors or the ESP's ADC measurements. + +Set calibration either in the Usermods settings page or at compile time in `my_config.h` or `platformio_override.ini`. + +It can be either a positive or negative number. + +

+ ## ⚠️ Important -- Make sure you know your battery specifications! All batteries are **NOT** the same! -- Example: +Make sure you know your battery specifications! All batteries are **NOT** the same! -| Your battery specification table | | Options you can define | -| :-------------------------------- |:--------------- | :---------------------------- | -| Capacity | 3500mAh 12,5 Wh | | -| Minimum capacity | 3350mAh 11,9 Wh | | +Example: + +| Your battery specification table | | Options you can define | +| --------------------------------- | --------------- | ----------------------------- | +| Capacity | 3500mAh 12.5Wh | | +| Minimum capacity | 3350mAh 11.9Wh | | | Rated voltage | 3.6V - 3.7V | | -| **Charging end voltage** | **4,2V ± 0,05** | `USERMOD_BATTERY_MAX_VOLTAGE` | -| **Discharge voltage** | **2,5V** | `USERMOD_BATTERY_MIN_VOLTAGE` | +| **Charging end voltage** | **4.2V ± 0.05** | `USERMOD_BATTERY_MAX_VOLTAGE` | +| **Discharge voltage** | **2.5V** | `USERMOD_BATTERY_MIN_VOLTAGE` | | Max. discharge current (constant) | 10A (10000mA) | | | max. charging current | 1.7A (1700mA) | | | ... | ... | ... | | .. | .. | .. | -Specification from: [Molicel INR18650-M35A, 3500mAh 10A Lithium-ion battery, 3.6V - 3.7V](https://www.akkuteile.de/lithium-ionen-akkus/18650/molicel/molicel-inr18650-m35a-3500mah-10a-lithium-ionen-akku-3-6v-3-7v_100833) +Specification from: [Molicel INR18650-M35A, 3500mAh 10A Lithium-ion battery, 3.6V - 3.7V](https://www.akkuteile.de/lithium-ionen-akkus/18650/molicel/molicel-inr18650-m35a-3500mah-10a-lithium-ionen-akku-3-6v-3-7v_100833) + +

## 🌐 Useful Links - https://lazyzero.de/elektronik/esp8266/wemos_d1_mini_a0/start - https://arduinodiy.wordpress.com/2016/12/25/monitoring-lipo-battery-voltage-with-wemos-d1-minibattery-shield-and-thingspeak/ +

+ ## 📝 Change Log -2024-04-30 +2024-05-11 -- improved initial reading accuracy by delaying initial measurement to allow voltage to stabilize at power-on +- Documentation updated 2024-04-30 -- integrate factory pattern to make it easier to add other / custom battery types -- update readme +- Integrate factory pattern to make it easier to add other / custom battery types +- Update readme +- Improved initial reading accuracy by delaying initial measurement to allow voltage to stabilize at power-on 2023-01-04 -- basic support for LiPo rechargeable batteries ( `-D USERMOD_BATTERY_USE_LIPO`) -- improved support for esp32 (read calibrated voltage) -- corrected config saving (measurement pin, and battery min/max were lost) -- various bugfixes +- Basic support for LiPo rechargeable batteries (`-D USERMOD_BATTERY_USE_LIPO`) +- Improved support for ESP32 (read calibrated voltage) +- Corrected config saving (measurement pin, and battery min/max were lost) +- Various bugfixes 2022-12-25 -- added "auto-off" feature -- added "low-power-indication" feature -- added "calibration/offset" field to configuration page -- added getter and setter, so that user usermods could interact with this one -- update readme (added new options, made it markdownlint compliant) +- Added "auto-off" feature +- Added "low-power-indication" feature +- Added "calibration/offset" field to configuration page +- Added getter and setter, so that user usermods could interact with this one +- Update readme (added new options, made it markdownlint compliant) 2021-09-02 -- added "Battery voltage" to info -- added circuit diagram to readme -- added MQTT support, sending battery voltage -- minor fixes +- Added "Battery voltage" to info +- Added circuit diagram to readme +- Added MQTT support, sending battery voltage +- Minor fixes 2021-08-15 -- changed `USERMOD_BATTERY_MIN_VOLTAGE` to 2.6 volt as default for 18650 batteries +- Changed `USERMOD_BATTERY_MIN_VOLTAGE` to 2.6 volt as default for 18650 batteries - Updated readme, added specification table 2021-08-10 diff --git a/usermods/INA226_v2/README.md b/usermods/INA226_v2/README.md new file mode 100644 index 000000000..b1e691618 --- /dev/null +++ b/usermods/INA226_v2/README.md @@ -0,0 +1,77 @@ +# Usermod INA226 + +This Usermod is designed to read values from an INA226 sensor and output the following: +- Current +- Voltage +- Power +- Shunt Voltage +- Overflow status + +## Configuration + +The following settings can be configured in the Usermod Menu: +- **Enabled**: Enable or disable the usermod. +- **I2CAddress**: The I2C address in decimal. Default is 64 (0x40). +- **CheckInterval**: Number of seconds between readings. This should be higher than the time it takes to make a reading, determined by the two next options. +- **INASamples**: The number of samples to configure the INA226 to use for a measurement. Higher counts provide more accuracy. See the 'Understanding Samples and Conversion Times' section for more details. +- **INAConversionTime**: The time to use on converting and preparing readings on the INA226. Higher times provide more precision. See the 'Understanding Samples and Conversion Times' section for more details. +- **Decimals**: Number of decimals in the output. +- **ShuntResistor**: Shunt resistor value in milliohms. An R100 shunt resistor should be written as "100", while R010 should be "10". +- **CurrentRange**: Expected maximum current in milliamps (e.g., 5 A = 5000 mA). +- **MqttPublish**: Enable or disable MQTT publishing. +- **MqttPublishAlways**: Publish always, regardless if there is a change. +- **MqttHomeAssistantDiscovery**: Enable Home Assistant discovery. + +## Dependencies + +These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`). + +- Libraries + - `wollewald/INA226_WE@~1.2.9` (by [wollewald](https://registry.platformio.org/libraries/wollewald/INA226_WE)) + - `Wire` + +## Understanding Samples and Conversion Times + +The INA226 uses a programmable ADC with configurable conversion times and averaging to optimize the measurement accuracy and speed. The conversion time and number of samples are determined based on the `INASamples` and `INAConversionTime` settings. The following table outlines the possible combinations: + +| Conversion Time (μs) | 1 Sample | 4 Samples | 16 Samples | 64 Samples | 128 Samples | 256 Samples | 512 Samples | 1024 Samples | +|----------------------|----------|-----------|------------|------------|-------------|-------------|-------------|--------------| +| 140 | 0.28 ms | 1.12 ms | 4.48 ms | 17.92 ms | 35.84 ms | 71.68 ms | 143.36 ms | 286.72 ms | +| 204 | 0.408 ms | 1.632 ms | 6.528 ms | 26.112 ms | 52.224 ms | 104.448 ms | 208.896 ms | 417.792 ms | +| 332 | 0.664 ms | 2.656 ms | 10.624 ms | 42.496 ms | 84.992 ms | 169.984 ms | 339.968 ms | 679.936 ms | +| 588 | 1.176 ms | 4.704 ms | 18.816 ms | 75.264 ms | 150.528 ms | 301.056 ms | 602.112 ms | 1204.224 ms | +| 1100 | 2.2 ms | 8.8 ms | 35.2 ms | 140.8 ms | 281.6 ms | 563.2 ms | 1126.4 ms | 2252.8 ms | +| 2116 | 4.232 ms | 16.928 ms | 67.712 ms | 270.848 ms | 541.696 ms | 1083.392 ms | 2166.784 ms | 4333.568 ms | +| 4156 | 8.312 ms | 33.248 ms | 132.992 ms | 531.968 ms | 1063.936 ms | 2127.872 ms | 4255.744 ms | 8511.488 ms | +| 8244 | 16.488 ms| 65.952 ms | 263.808 ms | 1055.232 ms| 2110.464 ms | 4220.928 ms | 8441.856 ms | 16883.712 ms | + +It is important to pick a combination that provides the needed balance between accuracy and precision while ensuring new readings within the `CheckInterval` setting. When `USERMOD_INA226_DEBUG` is defined, the info pane contains the expected time to make a reading, which can be seen in the table above. + +As an example, if you want a new reading every 5 seconds (`CheckInterval`), a valid combination is `256 samples` and `4156 μs` which would provide new values every 2.1 seconds. + +The picked values also slightly affect power usage. If the `CheckInterval` is set to more than 20 seconds, the INA226 is configured in `triggered` reading mode, where it only uses power as long as it's working. Then the conversion time and average samples counts determine how long the chip stays turned on every `CheckInterval` time. + +### Calculating Current and Power + +The INA226 calculates current by measuring the differential voltage across a shunt resistor and using the calibration register value to convert this measurement into current. Power is calculated by multiplying the current by the bus voltage. + +For detailed programming information and register configurations, refer to the [INA226 datasheet](https://www.ti.com/product/INA226). + +## Author +[@LordMike](https://github.com/LordMike) + +## Compiling + +To enable, compile with `USERMOD_INA226` defined (e.g. in `platformio_override.ini`). + +```ini +[env:ina226_example] +extends = env:esp32dev +build_flags = + ${common.build_flags} ${esp32.build_flags} + -D USERMOD_INA226 + ; -D USERMOD_INA226_DEBUG ; -- add a debug status to the info modal +lib_deps = + ${esp32.lib_deps} + wollewald/INA226_WE@~1.2.9 +``` \ No newline at end of file diff --git a/usermods/INA226_v2/platformio_override.ini b/usermods/INA226_v2/platformio_override.ini new file mode 100644 index 000000000..885b2dd1e --- /dev/null +++ b/usermods/INA226_v2/platformio_override.ini @@ -0,0 +1,9 @@ +[env:ina226_example] +extends = env:esp32dev +build_flags = + ${common.build_flags} ${esp32.build_flags} + -D USERMOD_INA226 + ; -D USERMOD_INA226_DEBUG ; -- add a debug status to the info modal +lib_deps = + ${esp32.lib_deps} + wollewald/INA226_WE@~1.2.9 \ No newline at end of file diff --git a/usermods/INA226_v2/usermod_ina226.h b/usermods/INA226_v2/usermod_ina226.h new file mode 100644 index 000000000..52bc3d83f --- /dev/null +++ b/usermods/INA226_v2/usermod_ina226.h @@ -0,0 +1,556 @@ +#pragma once + +#include "wled.h" +#include + +#define INA226_ADDRESS 0x40 // Default I2C address for INA226 + +#define DEFAULT_CHECKINTERVAL 60000 +#define DEFAULT_INASAMPLES 128 +#define DEFAULT_INASAMPLESENUM AVERAGE_128 +#define DEFAULT_INACONVERSIONTIME 1100 +#define DEFAULT_INACONVERSIONTIMEENUM CONV_TIME_1100 + +// A packed version of all INA settings enums and their human friendly counterparts packed into a 32 bit structure +// Some values are shifted and need to be preprocessed before usage +struct InaSettingLookup +{ + uint16_t avgSamples : 11; // Max 1024, which could be in 10 bits if we shifted by 1; if we somehow handle the edge case with "1" + uint8_t avgEnum : 4; // Shift by 8 to get the INA226_AVERAGES value, accepts 0x00 to 0x0F, we need 0x00 to 0x0E + uint16_t convTimeUs : 14; // We could save 2 bits by shifting this, but we won't save anything at present. + INA226_CONV_TIME convTimeEnum : 3; // Only the lowest 3 bits are defined in the conversion time enumerations +}; + +const InaSettingLookup _inaSettingsLookup[] = { + {1024, AVERAGE_1024 >> 8, 8244, CONV_TIME_8244}, + {512, AVERAGE_512 >> 8, 4156, CONV_TIME_4156}, + {256, AVERAGE_256 >> 8, 2116, CONV_TIME_2116}, + {128, AVERAGE_128 >> 8, 1100, CONV_TIME_1100}, + {64, AVERAGE_64 >> 8, 588, CONV_TIME_588}, + {16, AVERAGE_16 >> 8, 332, CONV_TIME_332}, + {4, AVERAGE_4 >> 8, 204, CONV_TIME_204}, + {1, AVERAGE_1 >> 8, 140, CONV_TIME_140}}; + +// Note: Will update the provided arg to be the correct value +INA226_AVERAGES getAverageEnum(uint16_t &samples) +{ + for (const auto &setting : _inaSettingsLookup) + { + // If a user supplies 2000 samples, we serve up the highest possible value + if (samples >= setting.avgSamples) + { + samples = setting.avgSamples; + return static_cast(setting.avgEnum << 8); + } + } + // Default value if not found + samples = DEFAULT_INASAMPLES; + return DEFAULT_INASAMPLESENUM; +} + +INA226_CONV_TIME getConversionTimeEnum(uint16_t &timeUs) +{ + for (const auto &setting : _inaSettingsLookup) + { + // If a user supplies 9000 μs, we serve up the highest possible value + if (timeUs >= setting.convTimeUs) + { + timeUs = setting.convTimeUs; + return setting.convTimeEnum; + } + } + // Default value if not found + timeUs = DEFAULT_INACONVERSIONTIME; + return DEFAULT_INACONVERSIONTIMEENUM; +} + +class UsermodINA226 : public Usermod +{ +private: + static const char _name[]; + + unsigned long _lastLoopCheck = 0; + unsigned long _lastTriggerTime = 0; + + bool _settingEnabled : 1; // Enable the usermod + bool _mqttPublish : 1; // Publish MQTT values + bool _mqttPublishAlways : 1; // Publish always, regardless if there is a change + bool _mqttHomeAssistant : 1; // Enable Home Assistant docs + bool _initDone : 1; // Initialization is done + bool _isTriggeredOperationMode : 1; // false = continuous, true = triggered + bool _measurementTriggered : 1; // if triggered mode, then true indicates we're waiting for measurements + uint16_t _settingInaConversionTimeUs : 12; // Conversion time, shift by 2 + uint16_t _settingInaSamples : 11; // Number of samples for averaging, max 1024 + + uint8_t _i2cAddress; + uint16_t _checkInterval; // milliseconds, user settings is in seconds + float _decimalFactor; // a power of 10 factor. 1 would be no change, 10 is one decimal, 100 is two etc. User sees a power of 10 (0, 1, 2, ..) + uint16_t _shuntResistor; // Shunt resistor value in milliohms + uint16_t _currentRange; // Expected maximum current in milliamps + + uint8_t _lastStatus = 0; + float _lastCurrent = 0; + float _lastVoltage = 0; + float _lastPower = 0; + float _lastShuntVoltage = 0; + bool _lastOverflow = false; + +#ifndef WLED_MQTT_DISABLE + float _lastCurrentSent = 0; + float _lastVoltageSent = 0; + float _lastPowerSent = 0; + float _lastShuntVoltageSent = 0; + bool _lastOverflowSent = false; +#endif + + INA226_WE *_ina226 = nullptr; + + float truncateDecimals(float val) + { + return roundf(val * _decimalFactor) / _decimalFactor; + } + + void initializeINA226() + { + if (_ina226 != nullptr) + { + delete _ina226; + } + + _ina226 = new INA226_WE(_i2cAddress); + if (!_ina226->init()) + { + DEBUG_PRINTLN(F("INA226 initialization failed!")); + return; + } + _ina226->setCorrectionFactor(1.0); + + uint16_t tmpShort = _settingInaSamples; + _ina226->setAverage(getAverageEnum(tmpShort)); + + tmpShort = _settingInaConversionTimeUs << 2; + _ina226->setConversionTime(getConversionTimeEnum(tmpShort)); + + if (_checkInterval >= 20000) + { + _isTriggeredOperationMode = true; + _ina226->setMeasureMode(TRIGGERED); + } + else + { + _isTriggeredOperationMode = false; + _ina226->setMeasureMode(CONTINUOUS); + } + + _ina226->setResistorRange(static_cast(_shuntResistor) / 1000.0, static_cast(_currentRange) / 1000.0); + } + + void fetchAndPushValues() + { + _lastStatus = _ina226->getI2cErrorCode(); + + if (_lastStatus != 0) + return; + + float current = truncateDecimals(_ina226->getCurrent_mA() / 1000.0); + float voltage = truncateDecimals(_ina226->getBusVoltage_V()); + float power = truncateDecimals(_ina226->getBusPower() / 1000.0); + float shuntVoltage = truncateDecimals(_ina226->getShuntVoltage_V()); + bool overflow = _ina226->overflow; + +#ifndef WLED_DISABLE_MQTT + mqttPublishIfChanged(F("current"), _lastCurrentSent, current, 0.01f); + mqttPublishIfChanged(F("voltage"), _lastVoltageSent, voltage, 0.01f); + mqttPublishIfChanged(F("power"), _lastPowerSent, power, 0.1f); + mqttPublishIfChanged(F("shunt_voltage"), _lastShuntVoltageSent, shuntVoltage, 0.01f); + mqttPublishIfChanged(F("overflow"), _lastOverflowSent, overflow); +#endif + + _lastCurrent = current; + _lastVoltage = voltage; + _lastPower = power; + _lastShuntVoltage = shuntVoltage; + _lastOverflow = overflow; + } + + void handleTriggeredMode(unsigned long currentTime) + { + if (_measurementTriggered) + { + // Test if we have a measurement every 400ms + if (currentTime - _lastTriggerTime >= 400) + { + _lastTriggerTime = currentTime; + if (_ina226->isBusy()) + return; + + fetchAndPushValues(); + _measurementTriggered = false; + } + } + else + { + if (currentTime - _lastLoopCheck >= _checkInterval) + { + // Start a measurement and use isBusy() later to determine when it is done + _ina226->startSingleMeasurementNoWait(); + _lastLoopCheck = currentTime; + _lastTriggerTime = currentTime; + _measurementTriggered = true; + } + } + } + + void handleContinuousMode(unsigned long currentTime) + { + if (currentTime - _lastLoopCheck >= _checkInterval) + { + _lastLoopCheck = currentTime; + fetchAndPushValues(); + } + } + + ~UsermodINA226() + { + delete _ina226; + _ina226 = nullptr; + } + +#ifndef WLED_DISABLE_MQTT + void mqttInitialize() + { + if (!WLED_MQTT_CONNECTED || !_mqttPublish || !_mqttHomeAssistant) + return; + + char topic[128]; + snprintf_P(topic, 127, "%s/current", mqttDeviceTopic); + mqttCreateHassSensor(F("Current"), topic, F("current"), F("A")); + + snprintf_P(topic, 127, "%s/voltage", mqttDeviceTopic); + mqttCreateHassSensor(F("Voltage"), topic, F("voltage"), F("V")); + + snprintf_P(topic, 127, "%s/power", mqttDeviceTopic); + mqttCreateHassSensor(F("Power"), topic, F("power"), F("W")); + + snprintf_P(topic, 127, "%s/shunt_voltage", mqttDeviceTopic); + mqttCreateHassSensor(F("Shunt Voltage"), topic, F("voltage"), F("V")); + + snprintf_P(topic, 127, "%s/overflow", mqttDeviceTopic); + mqttCreateHassBinarySensor(F("Overflow"), topic); + } + + void mqttPublishIfChanged(const __FlashStringHelper *topic, float &lastState, float state, float minChange) + { + if (WLED_MQTT_CONNECTED && _mqttPublish && (_mqttPublishAlways || fabsf(lastState - state) > minChange)) + { + char subuf[128]; + snprintf_P(subuf, 127, PSTR("%s/%s"), mqttDeviceTopic, (const char *)topic); + mqtt->publish(subuf, 0, false, String(state).c_str()); + + lastState = state; + } + } + + void mqttPublishIfChanged(const __FlashStringHelper *topic, bool &lastState, bool state) + { + if (WLED_MQTT_CONNECTED && _mqttPublish && (_mqttPublishAlways || lastState != state)) + { + char subuf[128]; + snprintf_P(subuf, 127, PSTR("%s/%s"), mqttDeviceTopic, (const char *)topic); + mqtt->publish(subuf, 0, false, state ? "true" : "false"); + + lastState = state; + } + } + + void mqttCreateHassSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement) + { + String t = String(F("homeassistant/sensor/")) + mqttClientID + "/" + name + F("/config"); + + StaticJsonDocument<600> doc; + + doc[F("name")] = 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")); + device[F("name")] = serverDescription; + device[F("identifiers")] = "wled-sensor-" + String(mqttClientID); + device[F("manufacturer")] = F(WLED_BRAND); + device[F("model")] = F(WLED_PRODUCT_NAME); + 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 mqttCreateHassBinarySensor(const String &name, const String &topic) + { + String t = String(F("homeassistant/binary_sensor/")) + mqttClientID + "/" + name + F("/config"); + + StaticJsonDocument<600> doc; + + doc[F("name")] = name; + doc[F("state_topic")] = topic; + doc[F("unique_id")] = String(mqttClientID) + name; + + JsonObject device = doc.createNestedObject(F("device")); + device[F("name")] = serverDescription; + device[F("identifiers")] = "wled-sensor-" + String(mqttClientID); + device[F("manufacturer")] = F(WLED_BRAND); + device[F("model")] = F(WLED_PRODUCT_NAME); + 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()); + } +#endif + +public: + UsermodINA226() + { + // Default values + _settingInaSamples = DEFAULT_INASAMPLES; + _settingInaConversionTimeUs = DEFAULT_INACONVERSIONTIME; + + _i2cAddress = INA226_ADDRESS; + _checkInterval = DEFAULT_CHECKINTERVAL; + _decimalFactor = 100; + _shuntResistor = 1000; + _currentRange = 1000; + } + + void setup() + { + initializeINA226(); + } + + void loop() + { + if (!_settingEnabled || strip.isUpdating()) + return; + + unsigned long currentTime = millis(); + + if (_isTriggeredOperationMode) + { + handleTriggeredMode(currentTime); + } + else + { + handleContinuousMode(currentTime); + } + } + +#ifndef WLED_DISABLE_MQTT + void onMqttConnect(bool sessionPresent) + { + mqttInitialize(); + } +#endif + + uint16_t getId() + { + return USERMOD_ID_INA226; + } + + void addToJsonInfo(JsonObject &root) override + { + JsonObject user = root["u"]; + if (user.isNull()) + user = root.createNestedObject("u"); + +#ifdef USERMOD_INA226_DEBUG + JsonArray temp = user.createNestedArray(F("INA226 last loop")); + temp.add(_lastLoopCheck); + + temp = user.createNestedArray(F("INA226 last status")); + temp.add(_lastStatus); + + temp = user.createNestedArray(F("INA226 average samples")); + temp.add(_settingInaSamples); + temp.add(F("samples")); + + temp = user.createNestedArray(F("INA226 conversion time")); + temp.add(_settingInaConversionTimeUs << 2); + temp.add(F("μs")); + + // INA226 uses (2 * conversion time * samples) time to take a reading. + temp = user.createNestedArray(F("INA226 expected sample time")); + uint32_t sampleTimeNeededUs = (static_cast(_settingInaConversionTimeUs) << 2) * _settingInaSamples * 2; + temp.add(truncateDecimals(sampleTimeNeededUs / 1000.0)); + temp.add(F("ms")); + + temp = user.createNestedArray(F("INA226 mode")); + temp.add(_isTriggeredOperationMode ? F("triggered") : F("continuous")); + + if (_isTriggeredOperationMode) + { + temp = user.createNestedArray(F("INA226 triggered")); + temp.add(_measurementTriggered ? F("waiting for measurement") : F("")); + } +#endif + + JsonArray jsonCurrent = user.createNestedArray(F("Current")); + JsonArray jsonVoltage = user.createNestedArray(F("Voltage")); + JsonArray jsonPower = user.createNestedArray(F("Power")); + JsonArray jsonShuntVoltage = user.createNestedArray(F("Shunt Voltage")); + JsonArray jsonOverflow = user.createNestedArray(F("Overflow")); + + if (_lastLoopCheck == 0) + { + jsonCurrent.add(F("Not read yet")); + jsonVoltage.add(F("Not read yet")); + jsonPower.add(F("Not read yet")); + jsonShuntVoltage.add(F("Not read yet")); + jsonOverflow.add(F("Not read yet")); + return; + } + + if (_lastStatus != 0) + { + jsonCurrent.add(F("An error occurred")); + jsonVoltage.add(F("An error occurred")); + jsonPower.add(F("An error occurred")); + jsonShuntVoltage.add(F("An error occurred")); + jsonOverflow.add(F("An error occurred")); + return; + } + + jsonCurrent.add(_lastCurrent); + jsonCurrent.add(F("A")); + + jsonVoltage.add(_lastVoltage); + jsonVoltage.add(F("V")); + + jsonPower.add(_lastPower); + jsonPower.add(F("W")); + + jsonShuntVoltage.add(_lastShuntVoltage); + jsonShuntVoltage.add(F("V")); + + jsonOverflow.add(_lastOverflow ? F("true") : F("false")); + } + + void addToConfig(JsonObject &root) + { + JsonObject top = root.createNestedObject(FPSTR(_name)); + top[F("Enabled")] = _settingEnabled; + top[F("I2CAddress")] = static_cast(_i2cAddress); + top[F("CheckInterval")] = _checkInterval / 1000; + top[F("INASamples")] = _settingInaSamples; + top[F("INAConversionTime")] = _settingInaConversionTimeUs << 2; + top[F("Decimals")] = log10f(_decimalFactor); + top[F("ShuntResistor")] = _shuntResistor; + top[F("CurrentRange")] = _currentRange; +#ifndef WLED_DISABLE_MQTT + top[F("MqttPublish")] = _mqttPublish; + top[F("MqttPublishAlways")] = _mqttPublishAlways; + top[F("MqttHomeAssistantDiscovery")] = _mqttHomeAssistant; +#endif + + DEBUG_PRINTLN(F("INA226 config saved.")); + } + + bool readFromConfig(JsonObject &root) override + { + JsonObject top = root[FPSTR(_name)]; + + bool configComplete = !top.isNull(); + if (!configComplete) + return false; + + bool tmpBool; + if (getJsonValue(top[F("Enabled")], tmpBool)) + _settingEnabled = tmpBool; + else + configComplete = false; + + configComplete &= getJsonValue(top[F("I2CAddress")], _i2cAddress); + if (getJsonValue(top[F("CheckInterval")], _checkInterval)) + { + if (1 <= _checkInterval && _checkInterval <= 600) + _checkInterval *= 1000; + else + _checkInterval = DEFAULT_CHECKINTERVAL; + } + else + configComplete = false; + + uint16_t tmpShort; + if (getJsonValue(top[F("INASamples")], tmpShort)) + { + // The method below will fix the provided value to a valid one + getAverageEnum(tmpShort); + _settingInaSamples = tmpShort; + } + else + configComplete = false; + + if (getJsonValue(top[F("INAConversionTime")], tmpShort)) + { + // The method below will fix the provided value to a valid one + getConversionTimeEnum(tmpShort); + _settingInaConversionTimeUs = tmpShort >> 2; + } + else + configComplete = false; + + if (getJsonValue(top[F("Decimals")], _decimalFactor)) + { + if (0 <= _decimalFactor && _decimalFactor <= 5) + _decimalFactor = pow10f(_decimalFactor); + else + _decimalFactor = 100; + } + else + configComplete = false; + + configComplete &= getJsonValue(top[F("ShuntResistor")], _shuntResistor); + configComplete &= getJsonValue(top[F("CurrentRange")], _currentRange); + +#ifndef WLED_DISABLE_MQTT + if (getJsonValue(top[F("MqttPublish")], tmpBool)) + _mqttPublish = tmpBool; + else + configComplete = false; + + if (getJsonValue(top[F("MqttPublishAlways")], tmpBool)) + _mqttPublishAlways = tmpBool; + else + configComplete = false; + + if (getJsonValue(top[F("MqttHomeAssistantDiscovery")], tmpBool)) + _mqttHomeAssistant = tmpBool; + else + configComplete = false; +#endif + + if (_initDone) + { + initializeINA226(); + +#ifndef WLED_DISABLE_MQTT + mqttInitialize(); +#endif + } + + _initDone = true; + return configComplete; + } +}; + +const char UsermodINA226::_name[] PROGMEM = "INA226"; diff --git a/wled00/FX.cpp b/wled00/FX.cpp index d4566976d..7c43767f4 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3195,23 +3195,30 @@ static const char _data_FX_MODE_SINELON_RAINBOW[] PROGMEM = "Sinelon Rainbow@!,T // utility function that will add random glitter to SEGMENT void glitter_base(uint8_t intensity, uint32_t col = ULTRAWHITE) { - if (intensity > random8()) { - if (SEGMENT.is2D()) { - SEGMENT.setPixelColorXY(random16(SEGMENT.virtualWidth()),random16(SEGMENT.virtualHeight()), col); - } else { - SEGMENT.setPixelColor(random16(SEGLEN), col); - } - } + if (intensity > random8()) SEGMENT.setPixelColor(random16(SEGLEN), col); } //Glitter with palette background, inspired by https://gist.github.com/kriegsman/062e10f7f07ba8518af6 uint16_t mode_glitter() { - if (!SEGMENT.check2) mode_palette(); // use "* Color 1" palette for solid background (replacing "Solid glitter") + if (!SEGMENT.check2) { // use "* Color 1" palette for solid background (replacing "Solid glitter") + unsigned counter = 0; + if (SEGMENT.speed != 0) { + counter = (strip.now * ((SEGMENT.speed >> 3) +1)) & 0xFFFF; + counter = counter >> 8; + } + + bool noWrap = (strip.paletteBlend == 2 || (strip.paletteBlend == 0 && SEGMENT.speed == 0)); + for (unsigned i = 0; i < SEGLEN; i++) { + unsigned colorIndex = (i * 255 / SEGLEN) - counter; + if (noWrap) colorIndex = map(colorIndex, 0, 255, 0, 240); //cut off blend at palette "end" + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(colorIndex, false, true, 255)); + } + } glitter_base(SEGMENT.intensity, SEGCOLOR(2) ? SEGCOLOR(2) : ULTRAWHITE); return FRAMETIME; } -static const char _data_FX_MODE_GLITTER[] PROGMEM = "Glitter@!,!,,,,,Overlay;1,2,Glitter color;!;;pal=0,m12=0"; //pixels +static const char _data_FX_MODE_GLITTER[] PROGMEM = "Glitter@!,!,,,,,Overlay;,,Glitter color;!;;pal=0,m12=0"; //pixels //Solid colour background with glitter (can be replaced by Glitter) @@ -5054,25 +5061,25 @@ uint16_t mode_2Dfirenoise(void) { // firenoise2d. By Andrew Tuline SEGMENT.fill(BLACK); } - uint16_t xscale = SEGMENT.intensity*4; - uint32_t yscale = SEGMENT.speed*8; - uint8_t indexx = 0; + unsigned xscale = SEGMENT.intensity*4; + unsigned yscale = SEGMENT.speed*8; + unsigned indexx = 0; - SEGPALETTE = CRGBPalette16( CRGB(0,0,0), CRGB(0,0,0), CRGB(0,0,0), CRGB(0,0,0), - CRGB::Red, CRGB::Red, CRGB::Red, CRGB::DarkOrange, - CRGB::DarkOrange,CRGB::DarkOrange, CRGB::Orange, CRGB::Orange, - CRGB::Yellow, CRGB::Orange, CRGB::Yellow, CRGB::Yellow); + CRGBPalette16 pal = SEGMENT.check1 ? SEGPALETTE : CRGBPalette16(CRGB::Black, CRGB::Black, CRGB::Black, CRGB::Black, + CRGB::Red, CRGB::Red, CRGB::Red, CRGB::DarkOrange, + CRGB::DarkOrange,CRGB::DarkOrange, CRGB::Orange, CRGB::Orange, + CRGB::Yellow, CRGB::Orange, CRGB::Yellow, CRGB::Yellow); for (int j=0; j < cols; j++) { for (int i=0; i < rows; i++) { - indexx = inoise8(j*yscale*rows/255, i*xscale+strip.now/4); // We're moving along our Perlin map. - SEGMENT.setPixelColorXY(j, i, ColorFromPalette(SEGPALETTE, min(i*(indexx)>>4, 255), i*255/cols, LINEARBLEND)); // With that value, look up the 8 bit colour palette value and assign it to the current LED. + indexx = inoise8(j*yscale*rows/255, i*xscale+strip.now/4); // We're moving along our Perlin map. + SEGMENT.setPixelColorXY(j, i, ColorFromPalette(pal, min(i*(indexx)>>4, 255U), i*255/cols, LINEARBLEND)); // With that value, look up the 8 bit colour palette value and assign it to the current LED. } // for i } // for j return FRAMETIME; } // mode_2Dfirenoise() -static const char _data_FX_MODE_2DFIRENOISE[] PROGMEM = "Firenoise@X scale,Y scale;;!;2"; +static const char _data_FX_MODE_2DFIRENOISE[] PROGMEM = "Firenoise@X scale,Y scale,,,,Palette;;!;2;pal=66"; ////////////////////////////// diff --git a/wled00/FX.h b/wled00/FX.h index 6e458fcea..33c17a19b 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -90,7 +90,7 @@ //#define SEGCOLOR(x) strip._segments[strip.getCurrSegmentId()].currentColor(x, strip._segments[strip.getCurrSegmentId()].colors[x]) //#define SEGLEN strip._segments[strip.getCurrSegmentId()].virtualLength() #define SEGCOLOR(x) strip.segColor(x) /* saves us a few kbytes of code */ -#define SEGPALETTE strip._currentPalette +#define SEGPALETTE Segment::getCurrentPalette() #define SEGLEN strip._virtualSegmentLength /* saves us a few kbytes of code */ #define SPEED_FORMULA_L (5U + (50U*(255U - SEGMENT.speed))/SEGLEN) @@ -324,7 +324,8 @@ typedef enum mapping1D2D { M12_Pixels = 0, M12_pBar = 1, M12_pArc = 2, - M12_pCorner = 3 + M12_pCorner = 3, + M12_sPinwheel = 4 } mapping1D2D_t; // segment, 80 bytes @@ -417,6 +418,7 @@ typedef struct Segment { static uint16_t _usedSegmentData; // perhaps this should be per segment, not static + static CRGBPalette16 _currentPalette; // palette used for current effect (includes transition, used in color_from_palette()) static CRGBPalette16 _randomPalette; // actual random palette static CRGBPalette16 _newRandomPalette; // target random palette static uint16_t _lastPaletteChange; // last random palette change time in millis()/1000 @@ -534,6 +536,7 @@ typedef struct Segment { static void modeBlend(bool blend) { _modeBlend = blend; } #endif static void handleRandomPalette(); + inline static const CRGBPalette16 &getCurrentPalette(void) { return Segment::_currentPalette; } void setUp(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1); bool setColor(uint8_t slot, uint32_t c); //returns true if changed @@ -571,7 +574,7 @@ typedef struct Segment { uint8_t currentMode(void); // currently active effect/mode (while in transition) uint32_t currentColor(uint8_t slot); // currently active segment color (blended while in transition) CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); - CRGBPalette16 ¤tPalette(CRGBPalette16 &tgt, uint8_t paletteID); + void setCurrentPalette(void); // 1D strip uint16_t virtualLength(void) const; @@ -706,7 +709,6 @@ class WS2812FX { // 96 bytes panels(1), #endif // semi-private (just obscured) used in effect functions through macros - _currentPalette(CRGBPalette16(CRGB::Black)), _colors_t{0,0,0}, _virtualSegmentLength(0), // true private variables @@ -901,7 +903,6 @@ class WS2812FX { // 96 bytes // end 2D support void loadCustomPalettes(void); // loads custom palettes from JSON - CRGBPalette16 _currentPalette; // palette used for current effect (includes transition) std::vector customPalettes; // TODO: move custom palettes out of WS2812FX class // using public variables to reduce code size increase due to inline function getSegment() (with bounds checking) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index ce510f16e..96df692cf 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -77,6 +77,7 @@ uint16_t Segment::_usedSegmentData = 0U; // amount of RAM all segments use for t uint16_t Segment::maxWidth = DEFAULT_LED_COUNT; uint16_t Segment::maxHeight = 1; +CRGBPalette16 Segment::_currentPalette = CRGBPalette16(CRGB::Black); CRGBPalette16 Segment::_randomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR); CRGBPalette16 Segment::_newRandomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR); uint16_t Segment::_lastPaletteChange = 0; // perhaps it should be per segment @@ -203,7 +204,7 @@ void Segment::resetIfRequired() { CRGBPalette16 IRAM_ATTR &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; + 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 if (pal == 0) switch (mode) { case FX_MODE_FIRE_2012 : pal = 35; break; // heat palette @@ -334,8 +335,8 @@ void Segment::handleTransition() { // transition progression between 0-65535 uint16_t IRAM_ATTR Segment::progress() { if (isInTransition()) { - unsigned long timeNow = millis(); - if (_t->_dur > 0 && timeNow - _t->_start < _t->_dur) return (timeNow - _t->_start) * 0xFFFFU / _t->_dur; + unsigned diff = millis() - _t->_start; + if (_t->_dur > 0 && diff < _t->_dur) return diff * 0xFFFFU / _t->_dur; } return 0xFFFFU; } @@ -438,18 +439,17 @@ uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { #endif } -CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, uint8_t pal) { - loadPalette(targetPalette, pal); +void Segment::setCurrentPalette() { + loadPalette(_currentPalette, palette); unsigned prog = progress(); if (strip.paletteFade && prog < 0xFFFFU) { // blend palettes // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) // minimum blend time is 100ms maximum is 65535ms unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends; - for (unsigned i=0; i_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, targetPalette, 48); - targetPalette = _t->_palT; // copy transitioning/temporary palette + for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, _currentPalette, 48); + _currentPalette = _t->_palT; // copy transitioning/temporary palette } - return targetPalette; } // relies on WS2812FX::service() to call it for each frame @@ -637,6 +637,42 @@ uint16_t IRAM_ATTR Segment::nrOfVStrips() const { return vLen; } +// Constants for mapping mode "Pinwheel" +#ifndef WLED_DISABLE_2D +constexpr int Pinwheel_Steps_Small = 72; // no holes up to 16x16 +constexpr int Pinwheel_Size_Small = 16; // larger than this -> use "Medium" +constexpr int Pinwheel_Steps_Medium = 192; // no holes up to 32x32 +constexpr int Pinwheel_Size_Medium = 32; // larger than this -> use "Big" +constexpr int Pinwheel_Steps_Big = 304; // no holes up to 50x50 +constexpr int Pinwheel_Size_Big = 50; // larger than this -> use "XL" +constexpr int Pinwheel_Steps_XL = 368; +constexpr float Int_to_Rad_Small = (DEG_TO_RAD * 360) / Pinwheel_Steps_Small; // conversion: from 0...72 to Radians +constexpr float Int_to_Rad_Med = (DEG_TO_RAD * 360) / Pinwheel_Steps_Medium; // conversion: from 0...192 to Radians +constexpr float Int_to_Rad_Big = (DEG_TO_RAD * 360) / Pinwheel_Steps_Big; // conversion: from 0...304 to Radians +constexpr float Int_to_Rad_XL = (DEG_TO_RAD * 360) / Pinwheel_Steps_XL; // conversion: from 0...368 to Radians + +constexpr int Fixed_Scale = 512; // fixpoint scaling factor (9bit for fraction) + +// Pinwheel helper function: pixel index to radians +static float getPinwheelAngle(int i, int vW, int vH) { + int maxXY = max(vW, vH); + if (maxXY <= Pinwheel_Size_Small) return float(i) * Int_to_Rad_Small; + if (maxXY <= Pinwheel_Size_Medium) return float(i) * Int_to_Rad_Med; + if (maxXY <= Pinwheel_Size_Big) return float(i) * Int_to_Rad_Big; + // else + return float(i) * Int_to_Rad_XL; +} +// Pinwheel helper function: matrix dimensions to number of rays +static int getPinwheelLength(int vW, int vH) { + int maxXY = max(vW, vH); + if (maxXY <= Pinwheel_Size_Small) return Pinwheel_Steps_Small; + if (maxXY <= Pinwheel_Size_Medium) return Pinwheel_Steps_Medium; + if (maxXY <= Pinwheel_Size_Big) return Pinwheel_Steps_Big; + // else + return Pinwheel_Steps_XL; +} +#endif + // 1D strip uint16_t IRAM_ATTR Segment::virtualLength() const { #ifndef WLED_DISABLE_2D @@ -652,6 +688,9 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { case M12_pArc: vLen = max(vW,vH); // get the longest dimension break; + case M12_sPinwheel: + vLen = getPinwheelLength(vW, vH); + break; } return vLen; } @@ -718,6 +757,52 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) for (int x = 0; x <= i; x++) setPixelColorXY(x, i, col); for (int y = 0; y < i; y++) setPixelColorXY(i, y, col); break; + case M12_sPinwheel: { + // i = angle --> 0 - 296 (Big), 0 - 192 (Medium), 0 - 72 (Small) + float centerX = roundf((vW-1) / 2.0f); + float centerY = roundf((vH-1) / 2.0f); + float angleRad = getPinwheelAngle(i, vW, vH); // angle in radians + float cosVal = cos_t(angleRad); + float sinVal = sin_t(angleRad); + + // avoid re-painting the same pixel + int lastX = INT_MIN; // impossible position + int lastY = INT_MIN; // impossible position + // draw line at angle, starting at center and ending at the segment edge + // we use fixed point math for better speed. Starting distance is 0.5 for better rounding + // int_fast16_t and int_fast32_t types changed to int, minimum bits commented + int posx = (centerX + 0.5f * cosVal) * Fixed_Scale; // X starting position in fixed point 18 bit + int posy = (centerY + 0.5f * sinVal) * Fixed_Scale; // Y starting position in fixed point 18 bit + int inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) 10 bit + int inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) 10 bit + + int32_t maxX = vW * Fixed_Scale; // X edge in fixedpoint + int32_t maxY = vH * Fixed_Scale; // Y edge in fixedpoint + + // Odd rays start further from center if prevRay started at center. + static int prevRay = INT_MIN; // previous ray number + if ((i % 2 == 1) && (i - 1 == prevRay || i + 1 == prevRay)) { + int jump = min(vW/3, vH/3); // can add 2 if using medium pinwheel + posx += inc_x * jump; + posy += inc_y * jump; + } + prevRay = i; + + // draw ray until we hit any edge + while ((posx >= 0) && (posy >= 0) && (posx < maxX) && (posy < maxY)) { + // scale down to integer (compiler will replace division with appropriate bitshift) + int x = posx / Fixed_Scale; + int y = posy / Fixed_Scale; + // set pixel + if (x != lastX || y != lastY) setPixelColorXY(x, y, col); // only paint if pixel position is different + lastX = x; + lastY = y; + // advance to next position + posx += inc_x; + posy += inc_y; + } + break; + } } return; } else if (Segment::maxHeight!=1 && (width()==1 || height()==1)) { @@ -833,7 +918,36 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i) // use longest dimension return vW>vH ? getPixelColorXY(i, 0) : getPixelColorXY(0, i); break; - } + case M12_sPinwheel: + // not 100% accurate, returns pixel at outer edge + // i = angle --> 0 - 296 (Big), 0 - 192 (Medium), 0 - 72 (Small) + float centerX = roundf((vW-1) / 2.0f); + float centerY = roundf((vH-1) / 2.0f); + float angleRad = getPinwheelAngle(i, vW, vH); // angle in radians + float cosVal = cos_t(angleRad); + float sinVal = sin_t(angleRad); + + int posx = (centerX + 0.5f * cosVal) * Fixed_Scale; // X starting position in fixed point 18 bit + int posy = (centerY + 0.5f * sinVal) * Fixed_Scale; // Y starting position in fixed point 18 bit + int inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) 10 bit + int inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) 10 bit + int32_t maxX = vW * Fixed_Scale; // X edge in fixedpoint + int32_t maxY = vH * Fixed_Scale; // Y edge in fixedpoint + + // trace ray from center until we hit any edge - to avoid rounding problems, we use the same method as in setPixelColor + int x = INT_MIN; + int y = INT_MIN; + while ((posx >= 0) && (posy >= 0) && (posx < maxX) && (posy < maxY)) { + // scale down to integer (compiler will replace division with appropriate bitshift) + x = posx / Fixed_Scale; + y = posy / Fixed_Scale; + // advance to next position + posx += inc_x; + posy += inc_y; + } + return getPixelColorXY(x, y); + break; + } return 0; } #endif @@ -1069,9 +1183,7 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_ if (mapping && virtualLength() > 1) paletteIndex = (i*255)/(virtualLength() -1); // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" - CRGBPalette16 curPal; - curPal = currentPalette(curPal, palette); - CRGB fastled_col = ColorFromPalette(curPal, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global + CRGB fastled_col = ColorFromPalette(_currentPalette, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global return RGBW32(fastled_col.r, fastled_col.g, fastled_col.b, W(color)); } @@ -1184,7 +1296,7 @@ void WS2812FX::service() { _colors_t[0] = gamma32(seg.currentColor(0)); _colors_t[1] = gamma32(seg.currentColor(1)); _colors_t[2] = gamma32(seg.currentColor(2)); - seg.currentPalette(_currentPalette, seg.palette); // we need to pass reference + seg.setCurrentPalette(); // load actual palette // when correctWB is true we need to correct/adjust RGB value according to desired CCT value, but it will also affect actual WW/CW ratio // when cctFromRgb is true we implicitly calculate WW and CW from RGB values if (cctFromRgb) BusManager::setSegmentCCT(-1); @@ -1227,7 +1339,7 @@ void WS2812FX::service() { #endif if (doShow) { yield(); - Segment::handleRandomPalette(); // slowly transtion random palette; move it into for loop when each segment has individual random palette + Segment::handleRandomPalette(); // slowly transition random palette; move it into for loop when each segment has individual random palette show(); } #ifdef WLED_DEBUG @@ -1481,7 +1593,7 @@ void WS2812FX::makeAutoSegments(bool forceReset) { segStops[s] = segStarts[s] + b->getLength(); #ifndef WLED_DISABLE_2D - if (isMatrix && segStops[s] < Segment::maxWidth*Segment::maxHeight) continue; // ignore buses comprising matrix + if (isMatrix && segStops[s] <= Segment::maxWidth*Segment::maxHeight) continue; // ignore buses comprising matrix if (isMatrix && segStarts[s] < Segment::maxWidth*Segment::maxHeight) segStarts[s] = Segment::maxWidth*Segment::maxHeight; #endif @@ -1509,6 +1621,7 @@ void WS2812FX::makeAutoSegments(bool forceReset) { for (size_t i = 1; i < s; i++) { _segments.push_back(Segment(segStarts[i], segStops[i])); } + DEBUG_PRINTF_P(PSTR("%d auto segments created.\n"), _segments.size()); } else { @@ -1554,6 +1667,8 @@ void WS2812FX::fixInvalidSegments() { if (_segments[i].stop > _length) _segments[i].stop = _length; } } + // if any segments were deleted free memory + purgeSegments(); // this is always called as the last step after finalizeInit(), update covered bus types for (segment &seg : _segments) seg.refreshLightCapabilities(); @@ -1584,7 +1699,8 @@ void WS2812FX::setRange(uint16_t i, uint16_t i2, uint32_t col) { void WS2812FX::printSize() { size_t size = 0; for (const Segment &seg : _segments) size += seg.getSize(); - DEBUG_PRINTF_P(PSTR("Segments: %d -> %uB\n"), _segments.size(), size); + DEBUG_PRINTF_P(PSTR("Segments: %d -> %u/%dB\n"), _segments.size(), size, Segment::getUsedSegmentData()); + for (const Segment &seg : _segments) DEBUG_PRINTF_P(PSTR(" Seg: %d,%d [A=%d, 2D=%d, RGB=%d, W=%d, CCT=%d]\n"), seg.width(), seg.height(), seg.isActive(), seg.is2D(), seg.hasRGB(), seg.hasWhite(), seg.isCCT()); DEBUG_PRINTF_P(PSTR("Modes: %d*%d=%uB\n"), sizeof(mode_ptr), _mode.size(), (_mode.capacity()*sizeof(mode_ptr))); DEBUG_PRINTF_P(PSTR("Data: %d*%d=%uB\n"), sizeof(const char *), _modeData.size(), (_modeData.capacity()*sizeof(const char *))); DEBUG_PRINTF_P(PSTR("Map: %d*%d=%uB\n"), sizeof(uint16_t), (int)customMappingSize, customMappingSize*sizeof(uint16_t)); diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index ac6923f40..bcca2e83f 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -425,6 +425,7 @@ BusPwm::BusPwm(BusConfig &bc) } _data = _pwmdata; // avoid malloc() and use stack _valid = true; + DEBUG_PRINTF_P(PSTR("%successfully inited PWM strip with type %u and pins %u,%u,%u,%u,%u\n"), _valid?"S":"Uns", bc.type, _pins[0], _pins[1], _pins[2], _pins[3], _pins[4]); } void BusPwm::setPixelColor(uint16_t pix, uint32_t c) { @@ -575,6 +576,7 @@ BusOnOff::BusOnOff(BusConfig &bc) pinMode(_pin, OUTPUT); _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); } void BusOnOff::setPixelColor(uint16_t pix, uint32_t c) { @@ -629,6 +631,7 @@ BusNetwork::BusNetwork(BusConfig &bc) _UDPchannels = _rgbw ? 4 : 3; _client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]); _valid = (allocData(_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) { @@ -729,9 +732,8 @@ void BusManager::setStatusPixel(uint32_t c) { void IRAM_ATTR BusManager::setPixelColor(uint16_t pix, uint32_t c) { for (unsigned i = 0; i < numBusses; i++) { - Bus* b = busses[i]; - uint16_t bstart = b->getStart(); - if (pix < bstart || pix >= bstart + b->getLength()) continue; + unsigned bstart = busses[i]->getStart(); + if (pix < bstart || pix >= bstart + busses[i]->getLength()) continue; busses[i]->setPixelColor(pix - bstart, c); } } @@ -753,10 +755,9 @@ void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) { uint32_t BusManager::getPixelColor(uint16_t pix) { for (unsigned i = 0; i < numBusses; i++) { - Bus* b = busses[i]; - uint16_t bstart = b->getStart(); - if (pix < bstart || pix >= bstart + b->getLength()) continue; - return b->getPixelColor(pix - bstart); + unsigned bstart = busses[i]->getStart(); + if (pix < bstart || pix >= bstart + busses[i]->getLength()) continue; + return busses[i]->getPixelColor(pix - bstart); } return 0; } diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index addd3fc5e..1ab1cba72 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -790,7 +790,7 @@ void serializeConfig() { JsonObject matrix = hw_led.createNestedObject(F("matrix")); matrix[F("mpc")] = strip.panels; JsonArray panels = matrix.createNestedArray(F("panels")); - for (int i = 0; i < strip.panel.size(); i++) { + for (size_t i = 0; i < strip.panel.size(); i++) { JsonObject pnl = panels.createNestedObject(); pnl["b"] = strip.panel[i].bottomStart; pnl["r"] = strip.panel[i].rightStart; @@ -806,7 +806,7 @@ void serializeConfig() { JsonArray hw_led_ins = hw_led.createNestedArray("ins"); - for (int s = 0; s < BusManager::getNumBusses(); s++) { + for (size_t s = 0; s < BusManager::getNumBusses(); s++) { Bus *bus = BusManager::getBus(s); if (!bus || bus->getLength()==0) break; JsonObject ins = hw_led_ins.createNestedObject(); @@ -829,7 +829,7 @@ void serializeConfig() { JsonArray hw_com = hw.createNestedArray(F("com")); const ColorOrderMap& com = BusManager::getColorOrderMap(); - for (int s = 0; s < com.count(); s++) { + for (size_t s = 0; s < com.count(); s++) { const ColorOrderMapEntry *entry = com.get(s); if (!entry) break; diff --git a/wled00/const.h b/wled00/const.h index 5173afd24..6f0605ada 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -178,7 +178,8 @@ #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" -#define USERMOD_ID_AHT10 49 //Usermod "usermod_aht10.h" +#define USERMOD_ID_INA226 50 //Usermod "usermod_ina226.h" +#define USERMOD_ID_AHT10 51 //Usermod "usermod_aht10.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot diff --git a/wled00/data/index.js b/wled00/data/index.js index d33fb63f7..26d78b284 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -801,6 +801,7 @@ function populateSegments(s) ``+ ``+ ``+ + ``+ ``+ ``; let sndSim = `
Sound sim
`+ diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index 0ea9d5a45..ff8231ccb 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -137,7 +137,7 @@ Contributors, dependencies and special thanks
A huge thank you to everyone who helped me create WLED!

(c) 2016-2024 Christian Schwinne
- Licensed under the MIT license

+ Licensed under the MIT license

Server message: Response error!
diff --git a/wled00/json.cpp b/wled00/json.cpp index f306eb323..1f9b0fae6 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -416,7 +416,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) //bool didSet = false; for (size_t s = 0; s < strip.getSegmentsNum(); s++) { Segment &sg = strip.getSegment(s); - if (sg.isSelected()) { + if (sg.isActive() && sg.isSelected()) { deserializeSegment(segVar, s, presetId); //didSet = true; } @@ -598,7 +598,7 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme nl["dur"] = nightlightDelayMins; nl["mode"] = nightlightMode; nl[F("tbri")] = nightlightTargetBri; - nl[F("rem")] = nightlightActive ? (nightlightDelayMs - (millis() - nightlightStartTime)) / 1000 : -1; // seconds remaining + nl[F("rem")] = nightlightActive ? (int)(nightlightDelayMs - (millis() - nightlightStartTime)) / 1000 : -1; // seconds remaining JsonObject udpn = root.createNestedObject("udpn"); udpn[F("send")] = sendNotificationsRT; diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index 91455e986..18ae9f42f 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -221,6 +221,10 @@ #include "../usermods/AHT10_v2/usermod_aht10.h" #endif +#ifdef USERMOD_INA226 + #include "../usermods/INA226_v2/usermod_ina226.h" +#endif + void registerUsermods() { /* @@ -429,4 +433,8 @@ void registerUsermods() #ifdef USERMOD_AHT10 usermods.add(new UsermodAHT10()); #endif + + #ifdef USERMOD_INA226 + usermods.add(new UsermodINA226()); + #endif } diff --git a/wled00/wled.h b/wled00/wled.h index dd90e1d74..cb56a0df4 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2405030 +#define VERSION 2405180 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG