feat(P070): Enhance NeoPixel Clock functionality#5516
feat(P070): Enhance NeoPixel Clock functionality#5516Capitanguex wants to merge 1 commit intoletscontrolit:megafrom
Conversation
Capitanguex
commented
Mar 19, 2026
- Improved NeoPixel Clock plugin with new features:
- Added predefined color selection for hands/marks and hourly lightshow.
- Implemented automatic brightness control via BH1750 lux sensor.
- Introduced selectable LED ring mode: 60-LED (standard) and 16/12/24-LED.
- Refactored P070_data_struct to manage new configurations and features:
- Added support for RGBW LEDs and various lightshow modes.
- Implemented auto brightness adjustment based on lux readings.
- Enhanced clock update logic to include lightshow functionality.
- Updated web interface for configuration options related to LED modes, brightness, colors, and lightshow settings.
- Updated platformio.ini to change default environment for custom ESP8266. - Improved NeoPixel Clock plugin with new features: - Added predefined color selection for hands/marks and hourly lightshow. - Implemented automatic brightness control via BH1750 lux sensor. - Introduced selectable LED ring mode: 60-LED (standard) or 24-LED. - Refactored P070_data_struct to manage new configurations and features: - Added support for RGBW LEDs and various lightshow modes. - Implemented auto brightness adjustment based on lux readings. - Enhanced clock update logic to include lightshow functionality. - Updated web interface for configuration options related to LED modes, brightness, colors, and lightshow settings. - Improved memory management for NeoPixelBus_wrapper instances.
|
I notice quite a lot of seemingly unrelated docs changes. |
| auto_brightness = PCONFIG(6); | ||
| lightshow_mode = PCONFIG(7); | ||
| lightshow_duration = PCONFIG(8); | ||
| led_type = PCONFIG(9); |
There was a problem hiding this comment.
PCONFIG can only go upto 7.
With these you're accessing unrelated values (likely in the next task)
| if (led_type == P070_LED_TYPE_RGBW) { | ||
| // If all channels equal → pure white via W channel | ||
| if (r == g && g == b) { | ||
| Plugin_070_pixels->setPixelColor(pos, Plugin_070_pixels->Color(0, 0, 0, r)); |
There was a problem hiding this comment.
There is no check for the pointer to be valid.
If the function can be called from anywhere without checking this pointer, then please add a check for it in this function.
| for (taskIndex_t i = 0; i < TASKS_MAX; i++) { | ||
| if (Settings.TaskDeviceEnabled[i]) { | ||
| LoadTaskSettings(i); | ||
| if (String(ExtraTaskSettings.TaskDeviceName).equalsIgnoreCase(F(P070_LUX_TASK_NAME))) { |
There was a problem hiding this comment.
This is a really strange way of checking for a task.
You could check for pluginID, which doesn't even require to load Task Settings. (which is an expensive operation)
Also we do have task names cached so actually nowhere else the LoadTaskSettings has to be called anymore (I think...)
Anyway, the taskname is not guaranteed to be the same as the default once set. Better check for the PluginID as that remains constant and is 70 for this plugin.
| const uint8_t bright = brightness > 0 ? brightness : 128; | ||
|
|
||
| // ----------------------------------------------------------------------- | ||
| // Mode 1: Colour-chase (Lauflicht) |
There was a problem hiding this comment.
Please use English also in the comments.
| const uint8_t trail = 3; | ||
| for (uint8_t t = 0; t <= trail; t++) { | ||
| int pos = (int)lightshow_step - (int)t; | ||
| if (pos < 0) { pos += number_leds; } |
There was a problem hiding this comment.
This still will fail when number_leds < trial.
Simple fix is to change the if by a while
| // Each "flash cycle" = 2 seconds: sec 0 flash-on, sec 1 flash-off+hands | ||
| // ----------------------------------------------------------------------- | ||
| if (lightshow_mode == P070_LIGHTSHOW_FLASH) { | ||
| const uint8_t cycle = sec % 2; // 0 = flash all, 1 = show hands briefly |
There was a problem hiding this comment.
Not sure how 'expensive' the modulo operator is, but since you're essentially checking for 0 or 1, you can also do:
const uint8_t cycle = sec & 1;| Device[deviceCount].SendDataOption = false; | ||
| Device[deviceCount].TimerOption = false; | ||
| Device[deviceCount].GlobalSyncOption = false; | ||
| Device[deviceCount].ExitTaskBeforeSave = false; |
There was a problem hiding this comment.
Ton recently reduced all these by no longer explicitly setting the flags to their defaults in order to reduce the build size.
So please check which ones are now set to their defaults.
Also this part was quite explicitly made to operate on the result of Device[++deviceCount]
auto& dev = Device[++deviceCount];| success = true; | ||
| break; | ||
| } | ||
| # endif // if FEATURE_MQTT_DISCOVER |
There was a problem hiding this comment.
Why is this section removed?
It looks like you started working on a rather old version of the plugin code and didn't take in recent changes.
|
|
||
| ;default_envs = normal_ESP32c6_4M316k_LittleFS_CDC | ||
| ; default_envs = custom_ESP8266_4M1M | ||
| ; default_envs = custom_274_ESP8266_4M1M |
There was a problem hiding this comment.
Please remove this from the PR as it is not related to the code changes
| if (Plugin_070_pixels != nullptr) { | ||
| delete Plugin_070_pixels; | ||
| Plugin_070_pixels = nullptr; | ||
| } |
There was a problem hiding this comment.
delete does that null-check himself, no need to waste an if on it (== smaller code, every byte is important in our sometimes quite cramped builds)
| if (Plugin_070_pixels != nullptr) { | ||
| delete Plugin_070_pixels; | ||
| Plugin_070_pixels = nullptr; | ||
| } |
| // ####################################################################################################### | ||
|
|
||
| /** Changelog: | ||
| * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported for Neopixel) |
There was a problem hiding this comment.
Can you please keep original changelog items in place?
| * 2024-xx-xx: Add automatic brightness control via BH1750 lux sensor (PCONFIG(6)) | ||
| * 2024-xx-xx: Add selectable LED ring mode: 60-LED (standard) or 24-LED via web interface (PCONFIG(5)) |
There was a problem hiding this comment.
Your changes are new to us, would be better to date them accordingly, so 2026-03-19, I'd say
| @@ -40,6 +41,7 @@ | |||
| "-DUSES_P052", # SenseAir | |||
| "-DUSES_P056", # SDS011-Dust | |||
| "-DUSES_P059", # Encoder | |||
| "-DUSES_P070", # Neopixel Clock | |||
| "-DUSES_P080", # Dallas iButton | |||
There was a problem hiding this comment.
Please do not commit these pre_custom files.
| @@ -51,7 +54,7 @@ | |||
| # "-DUSES_P095", # TFT ILI9341 | |||
| # "-DUSES_P106", # BME680 | |||
| # "-DUSES_P107", # SI1145 UV index | |||
|
|
|||
| # "-DUSES_P131", # NeoPixel Matrix | |||
| "-DUSES_P146", # Cache Reader | |||
| { | ||
| String radioHtml = F("<input type='radio' name='led_mode' value='0'"); | ||
| if (PCONFIG(5) == P070_LED_MODE_60) { radioHtml += F(" checked"); } | ||
| radioHtml += F("> 60 LEDs "); | ||
| radioHtml += F("<input type='radio' name='led_mode' value='1'"); | ||
| if (PCONFIG(5) == P070_LED_MODE_24) { radioHtml += F(" checked"); } | ||
| radioHtml += F("> 24 LEDs "); | ||
| radioHtml += F("<input type='radio' name='led_mode' value='2'"); | ||
| if (PCONFIG(5) == P070_LED_MODE_16) { radioHtml += F(" checked"); } | ||
| radioHtml += F("> 16 LEDs "); | ||
| radioHtml += F("<input type='radio' name='led_mode' value='3'"); | ||
| if (PCONFIG(5) == P070_LED_MODE_12) { radioHtml += F(" checked"); } | ||
| radioHtml += F("> 12 LEDs"); | ||
| addRowLabel(F("LED Ring size")); | ||
| addHtml(radioHtml); | ||
| } |
There was a problem hiding this comment.
Instead of collecting a lot of html string in memory and then sending it out all at the end, you'd better just use addHtml() directly, that's much more efficient.
The same goes for other places where you use this same technique.
| addFormCheckBox(F("Auto brightness (BH1750)"), F("auto_brightness"), PCONFIG(6)); | ||
| addFormNote(F("Automatically adjust brightness via BH1750 sensor (Task name must be 'BH1750')")); |
There was a problem hiding this comment.
How to use addTaskSelect() can be found in plugin P021 - Regulator - Level Control.
| lsHtml += F("> Aus "); | ||
| lsHtml += F("<input type='radio' name='lightshow_mode' value='1'"); | ||
| if (PCONFIG(7) == P070_LIGHTSHOW_CHASE) { lsHtml += F(" checked"); } | ||
| lsHtml += F("> Lauflicht (Farbe zufällig) "); |
There was a problem hiding this comment.
Please use English terms. Any translation can be done by the webbrowser, if desired. Aus, Lauflicht, Zeiger-Blink etc. won't mean much to non-German speaking people.
There was a problem hiding this comment.
This file, while present in .gitignore, doesn't seem to be ignored by git (for unknown reasons). It doesn't make much sense to be committed, as it is re-generated during the Github merge-build, when the documentation is published to ReadTheDocs.
| html += F("' value='"); | ||
| html += String(i); | ||
| html += F("'"); | ||
| if (currentValue == i) { html += F(" checked"); } | ||
| html += F("> "); |
There was a problem hiding this comment.
When migrating this to addHtml():
- No need to convert int to String, there are
addHtml()overloads that take numeric values - A single-character flash-string (using the
F()macro) is quite expensive, using a char ('\'') is more efficient - There is also an
addHtml()overload that takes 2 char arguments to easily handle line 59
NB: Using F-Strings for other textual parts is preferred, like you already have.
|
|
||
| // Helper: emit a row of color radio buttons for one hand/element | ||
| static void addColorRadioRow(const __FlashStringHelper *label, | ||
| const char *fieldName, |
There was a problem hiding this comment.
fieldName can either be a const __FlashStringHelper * (or const String & if you have to assemble a name where this function is called), avoiding the use of regular C-strings in the code.
|
|
||
| // A clock that uses a strip/ring of 60 or 24 WS2812 NeoPixel LEDs as display for a classic clock. | ||
| // Hand/mark colors are configurable. An optional hourly colour-chase lightshow is available. | ||
| // Brightness can be set manually or automatically via a BH1750 lux sensor (Task name: "BH1750"). |
There was a problem hiding this comment.
Any light-sensor that reports a brightness value in Lux should be usable here, right? Then you might need to be able to select the Task value also. That's also used in P021, using addTaskValueSelect(), so similar code can be used for this plugin.
| PCONFIG(3) = getFormItemInt(F("offset")); | ||
| PCONFIG(4) = isFormItemChecked(F("thick_12_mark")); | ||
| PCONFIG(5) = new_led_mode; | ||
| PCONFIG(6) = isFormItemChecked(F("auto_brightness")); | ||
| PCONFIG(7) = getFormItemInt(F("lightshow_mode")); | ||
| PCONFIG(8) = getFormItemInt(F("lightshow_duration")); | ||
| PCONFIG(9) = new_led_type; |
There was a problem hiding this comment.
Boolean and small numeric values need only a few bits to be stored, so can be combined in a 16 or 32 bit PCONFIG setting, examples can be found in f.e. P159, P165 and P176.
There are support-functions to get/set 2, 3, 4, 8 and 16 bit values, and bools of course.
That should avoid you needing PCONFIG(8) and up.
NB: Be aware that PCONFIG_LONG() and PCONFIG_ULONG() use the same storage space (in a union), so you can only use one or the other!
|
Thanks for all these suggestions. I’ll revise it (though that will take some time), make sure everything is in proper English, and then resubmit it later. |