Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert usermods to static libraries #4480

Draft
wants to merge 65 commits into
base: main
Choose a base branch
from

Conversation

willmmiles
Copy link
Member

Redesign the usermod intergration so that usermods are implemented as PlatformIO libraries instead of headers. This permits them to call for dependencies, and eliminates the compiler flags for enabling each one, allowing the build cache to operate consistently regardless of the selected modules.

The usermod list is built using some linker magic to construct a static list in ROM memory. This eliminates the need for wasting SRAM on something well known at build time.

To use this integration:

  • Each usermod .h should be moved to a .cpp, with a static instantiation of the module class at the end
  • The module object is then passed to REGISTER_USERMOD() to add it to the compiled-in usermod list
  • Each usermod must add a 'library.json':
    • The library name must match the folder name. (This PR includes special case handling for removing the 'usermod_v2_' prefix)
    • The library.json must call for local build integration ("includeDir": "../../wled00", "libLDFMode": "chain+", and "libArchive": false)
    • The library.json can now list any library dependencies, or custom build flags that apply to only that module.
  • PlatformIO target environments now support a new 'custom_usermods' property to enable usermods, space delimited
    • Example: custom_usermods = audioreactive animartrix builds in the "audioreactive" and "animartrix" usermods

The custom_usermods property is implemented via a platformio hook script that constructs the required symlink lines and injects them in to the build environment.

Currently I have converted 'auto_save', 'audioreactive', and 'animartrix' (based on #4476) to library-type as proof-of-concept.

Redesign the usermod system so that usermods are implemented as
PlatformIO libraries instead of headers.  This permits them to call for
dependencies, and eliminates the compiler flags for enabling each one,
allowing the build cache to behave better.

The usermod list is built using some linker magic to construct a static
list in ROM memory.  This eliminates the need for wasting SRAM on
something fixed at build time.
Look for 'usermod_v2_x' as well.  This could be removed later if we
clean up the folder names.
Borrowed library definition from @netmindz's work on wled-dev#4476.
@willmmiles willmmiles requested a review from netmindz January 11, 2025 18:48
@willmmiles willmmiles self-assigned this Jan 11, 2025
@willmmiles
Copy link
Member Author

Annoyingly, PlatformIO requires locally-defined properties to be prefixed with 'custom_' ; I'd've liked it to just be 'usermods' but no such luck. :(

platformio.ini Outdated
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
# additional build flags for audioreactive - must be applied globally
AR_build_flags = -D sqrt_internal=sqrtf ;; -fsingle-precision-constant ;; forces ArduinoFFT to use float math (2x faster)
AR_lib_deps = kosme/arduinoFFT @ 2.0.1 ;; for pre-usermod-library platformio_override compatibility
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are updating to be a library, why do we still need the old AR_lib_deps info as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compatibility with old platformio_override files that reference it. If we remove it, git pull breaks platformIO completely until you clean all references out of your overrides.

Copy link
Member

@netmindz netmindz Jan 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If folk have platform_override that is trying to use AR, is having AR_build_flags that that doesn't have the old define just going to cause confusion? They will think they have the flags, but now that's not enough to actually enable

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this would be a breaking change that will need to be staged appropriately. Unfortunately if your platformio file is outright invalid, PlatformIO in VSCode doesn't tell you what's wrong, it just blankly refuses to operate with no error message. I found it more useful to at least keep the tool operating while I was correcting my build definitions, but that's just my $0.02.

Copy link
Member

@netmindz netmindz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have a bit of a play around with this over the next few days

It looks like a great start, the key things in my mind however are what extra changes we can make both in terms of code, documentation and release management to help make the transition as painless as possible

One quick question - how essential is the rename from .h to .cpp ?

usermods/audioreactive/library.json Outdated Show resolved Hide resolved
@@ -1,482 +0,0 @@
#include "wled.h"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it better to delete this file or retain but with message about the new format - What would be the best way to highlight the changes to developers of the may forks?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! I'd figured the deletion will create an obvious merge conflict, so it's clear that something must be done. Personally I'd rather not leave a .cpp file that the build tooling will have to grovel over only for it to do nothing.

@willmmiles
Copy link
Member Author

willmmiles commented Jan 11, 2025

It looks like a great start, the key things in my mind however are what extra changes we can make both in terms of code, documentation and release management to help make the transition as painless as possible

Yes. Unfortunately it looks like the docs aren't in the code proper (beyond the examples), which is a little frustrating when different versions might have different approaches. :(

One quick question - how essential is the rename from .h to .cpp ?

Some .cpp file has to instantiate the module object; and this must be in the library, or you're back to all the problems with usermod_list.cpp. You could add a .cpp file to each that just does #include <my_module_header.h>; static MyModule my_module; REGISTER_USERMOD(my_module); , but personally I'd rather see all the code together in the one file rather than a seemingly bureaucratic split. My $0.02 is that the rename is the lesser evil -- git handles merges across renames pretty well these days.

@netmindz
Copy link
Member

One quick question - how essential is the rename from .h to .cpp ?

Some .cpp file has to instantiate the module object; and this must be in the library, or you're back to all the problems with usermod_list.cpp. You could add a .cpp file to each that just does #include <my_module_header.h>; static MyModule my_module; REGISTER_USERMOD(my_module); , but personally I'd rather see all the code together in the one file rather than a seemingly bureaucratic split. My $0.02 is that the rename is the lesser evil -- git handles merges across renames pretty well these days.

How about a migration script that looks for an existing .h of a usermod, moves with git and then automatically added the extra static field and call to register?

@netmindz
Copy link
Member

Are you aware of the build failure when using the audioreactive usermod? @willmmiles

In file included from usermods/audioreactive/audio_reactive.cpp:2:0:
wled00/wled.h:95:26: fatal error: LITTLEFS.h: No such file or directory

@willmmiles
Copy link
Member Author

One quick question - how essential is the rename from .h to .cpp ?

Some .cpp file has to instantiate the module object; and this must be in the library, or you're back to all the problems with usermod_list.cpp. You could add a .cpp file to each that just does #include <my_module_header.h>; static MyModule my_module; REGISTER_USERMOD(my_module); , but personally I'd rather see all the code together in the one file rather than a seemingly bureaucratic split. My $0.02 is that the rename is the lesser evil -- git handles merges across renames pretty well these days.

How about a migration script that looks for an existing .h of a usermod, moves with git and then automatically added the extra static field and call to register?

Not impossible. but nontrivial - the script would have to pick out the class name and any constructor arguments. We'd probably need to call on clang or something else that understands C++ syntax to be able to do a reliable job of it.

platformio.ini Outdated
@@ -424,23 +420,23 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=
board = esp32dev
platform = ${esp32.platform}
platform_packages = ${esp32.platform_packages}
custom_usermods = audioreactive
build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32\" #-D WLED_DISABLE_BROWNOUT_DET
${esp32.AR_build_flags}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any way to move the build_flags used for the usermod into it's library.json?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In most cases, yes.

In the specific case of AR, the build flags aren't actually for the AR module, they're for the FFT library dependency. Unfortunately, the only way to affect some dependent library's build is to do it at the project level. PlaformIO doesn't let one library define flags for one of its dependencies. https://community.platformio.org/t/setting-flags-defines-for-building-a-librarys-dependency/36744

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, maybe there is a way, using platformio hook scripts in the library. Working on it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are certainly usermods that use more typical build_flags that affect the usermod itself, but perhaps these still need to stay in the main platformio.ini if they are actual optional flags the user chooses to set

Copy link
Member Author

@willmmiles willmmiles Jan 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed! It turns out there is a way though platformio hook scripts. 8527d23

@willmmiles
Copy link
Member Author

Are you aware of the build failure when using the audioreactive usermod? @willmmiles

In file included from usermods/audioreactive/audio_reactive.cpp:2:0:
wled00/wled.h:95:26: fatal error: LITTLEFS.h: No such file or directory

Yup, looking in to it - seems like some sort of case sensitivity thing.

@netmindz
Copy link
Member

How about a migration script that looks for an existing .h of a usermod, moves with git and then automatically added the extra static field and call to register?

Not impossible. but nontrivial - the script would have to pick out the class name and any constructor arguments. We'd probably need to call on clang or something else that understands C++ syntax to be able to do a reliable job of it.

I'm just knocking up a quick PoC of a script. If nothing else it will help show how many of the usermods in the official AC repo do not follow proper naming convention etc

@willmmiles
Copy link
Member Author

I'm just knocking up a quick PoC of a script. If nothing else it will help show how many of the usermods in the official AC repo do not follow proper naming convention etc

Yup, a breaking API change like this is a good opportunity for cleanup. Honestly I'd figured on just going through them by hand, there aren't so many it's a huge problem.

With a solution like this in hand, I was hoping to ultimately create a "core" modules folder with the modules that this team is directly supporting; and then work towards migrating anything with a "WLED_DISABLE_X" flag in to becoming a core module instead. (Yes, I recognize that will likely require some expansion of the module API to handle specialized timing and polling requirements; I think that's probably a good thing, as if we have one use case for it, there may be others.)

Further, the linker section approach demonstrated here for the static usermod list could also be used later to make static library-based FX or bus type lists, too, allowing modules to include new FX or bus types without needing any RAM to build a runtime data structure. One step at a time, though!

@netmindz
Copy link
Member

I didn't want to mess up your main PR with my attempts at automatic migration, so I've popped into it's own PR for now #4481

These are the ones that don't follow any common naming

WARNING: usermods/ADS1115_v2/ADS1115_v2.h missing
WARNING: usermods/AHT10_v2/AHT10_v2.h missing
WARNING: Artemis_reciever possibly still v1 usermod
WARNING: usermods/BH1750_v2/BH1750_v2.h missing
WARNING: usermods/BME280_v2/BME280_v2.h missing
WARNING: usermods/BME68X_v2/BME68X_v2.h missing
WARNING: usermods/EXAMPLE_v2/EXAMPLE_v2.h missing
WARNING: Enclosure_with_OLED_temp_ESP07 possibly still v1 usermod
WARNING: usermods/Fix_unreachable_netservices_v2/Fix_unreachable_netservices_v2.h missing
WARNING: usermods/INA226_v2/INA226_v2.h missing
WARNING: usermods/Internal_Temperature_v2/Internal_Temperature_v2.h missing
WARNING: usermods/JSON_IR_remote/JSON_IR_remote.h missing
WARNING: usermods/LD2410_v2/LD2410_v2.h missing
WARNING: usermods/MAX17048_v2/MAX17048_v2.h missing
WARNING: usermods/MY9291/MY9291.h missing
WARNING: RelayBlinds possibly still v1 usermod
WARNING: TTGO-T-Display possibly still v1 usermod
WARNING: usermods/TetrisAI_v2/TetrisAI_v2.h missing
WARNING: Wemos_D1_mini+Wemos32_mini_shield possibly still v1 usermod
WARNING: usermods/audioreactive/audioreactive.h missing
WARNING: usermods/battery_keypad_controller/battery_keypad_controller.h missing
WARNING: usermods/mqtt_switch_v2/mqtt_switch_v2.h missing
WARNING: photoresistor_sensor_mqtt_v1 possibly still v1 usermod
WARNING: usermods/project_cars_shiftlight/project_cars_shiftlight.h missing
WARNING: usermods/rotary_encoder_change_effect/rotary_encoder_change_effect.h missing
WARNING: usermods/sensors_to_mqtt/sensors_to_mqtt.h missing
WARNING: usermods/seven_segment_display_reloaded/seven_segment_display_reloaded.h missing
WARNING: usermods/stairway_wipe_basic/stairway_wipe_basic.h missing
WARNING: usermods/word-clock-matrix/word-clock-matrix.h missing

@netmindz
Copy link
Member

Fixed the naming of a few, so now just

WARNING: Artemis_reciever possibly still v1 usermod
WARNING: usermods/EXAMPLE_v2/EXAMPLE_v2.h missing
WARNING: Enclosure_with_OLED_temp_ESP07 possibly still v1 usermod
WARNING: usermods/Fix_unreachable_netservices_v2/Fix_unreachable_netservices_v2.h missing
WARNING: usermods/JSON_IR_remote/JSON_IR_remote.h missing
WARNING: RelayBlinds possibly still v1 usermod
WARNING: TTGO-T-Display possibly still v1 usermod
WARNING: Wemos_D1_mini+Wemos32_mini_shield possibly still v1 usermod
WARNING: usermods/battery_keypad_controller/battery_keypad_controller.h missing
WARNING: photoresistor_sensor_mqtt_v1 possibly still v1 usermod
WARNING: usermods/project_cars_shiftlight/project_cars_shiftlight.h missing
WARNING: usermods/rotary_encoder_change_effect/rotary_encoder_change_effect.h missing

@netmindz
Copy link
Member

Are you aware of the build failure when using the audioreactive usermod? @willmmiles

In file included from usermods/audioreactive/audio_reactive.cpp:2:0:
wled00/wled.h:95:26: fatal error: LITTLEFS.h: No such file or directory

Yup, looking in to it - seems like some sort of case sensitivity thing.

I think it might be because we are missing the -D LOROL_LITTLEFS that we normally apply to esp32 builds

@netmindz
Copy link
Member

Are you aware of the build failure when using the audioreactive usermod? @willmmiles

In file included from usermods/audioreactive/audio_reactive.cpp:2:0:
wled00/wled.h:95:26: fatal error: LITTLEFS.h: No such file or directory

Yup, looking in to it - seems like some sort of case sensitivity thing.

I think it might be because we are missing the -D LOROL_LITTLEFS that we normally apply to esp32 builds

Confirmed. If I hack wled.h to define LOROL_LITTLEFS the problem goes away. Do you have a wled_custom.h or something locally?

- Check after including wled.h
- Use WLED_DISABLE_MQTT instead of WLED_ENABLE_MQTT
A better solution for cross-module communication is required!
Define a couple pins, leave a note where the usermod list comes from.
This is now managed centrally.
@willmmiles
Copy link
Member Author

Known issues in the current state:

  • Some usermods have OR dependencies on other usermods, eg "requires usermod A OR usermod B". Previously they were using the defines to perform compile-time checks for this. It's not clear how to replicate this via library.json -- platformio doesn't have a "provides" system to allow packages to export extra abstract names.
    • It's easy to make these runtime checks, though unhelpful for users
    • Maybe another 'extraScript' could help here? Run a validation and bail out?
    • Longer term, a cleanup/wider application of getUMData instead of directly calling in to other usermods might help, too
  • The "blast all build deps in to every usermod" approach is overkill, and this can weird include issues when there are conflicting downstream requirements. AsyncTCP is the current example: AWS calls for a different version than WLED, so both are being added to the build environment, and usermods sometimes being built with the "wrong" one. For this example, we can work around this by cleaning up the WLED build environment (update AWS and AsyncTCP, see Draft: AsyncWebServer queue support #4119), but I think load_usermods.py could still do a better job.
  • PlatformIO's LDF still makes mistakes on some libraries: for example, TFT_eSPI sometimes barfs on missing SPIFFS.h. This is an area of active work in PlatformIO, and newer versions seem better, but we would have to be prepared for complaints or trouble. (Note this is not a new issue with this branch, merely a consequence of actually test building usermods!)

Don't blast the path of any mentioned library - parse only the tree of
the actual build deps.
Only include paths for the base system deps, not those of other
usermods.
...it's been 3 years, and it's easier than cleaning up the readme.
It'd be better to not propagate the 'v2' suffix any further.  This is
the standard flavor of usermods now.
Describe the new usermod enable process, and update sample
platformio_override.ini stubs.
Use a custom setup script to check for the dependencies and pass along
the required compile flags to the module; also split the object
definitions for the target modules from their source so as to allow
#including them.
@netmindz
Copy link
Member

netmindz commented Feb 1, 2025

Good to see you are still chipping away at this tricky problem. We will definitely be in a much better situation once you complete the work.

With regards to your question regarding inter module communication, it is true that they are are some issues with that design of UM data, which we may choose to address at some point, but as it's the best we have currently, then all usermods should comply with the interface and use UM data rather than any other unsupported mechanism

@willmmiles
Copy link
Member Author

With regards to your question regarding inter module communication, it is true that they are are some issues with that design of UM data, which we may choose to address at some point, but as it's the best we have currently, then all usermods should comply with the interface and use UM data rather than any other unsupported mechanism

It wasn't so much a qustion as an observation of the current state of affairs. If mod developers are finding the existing UM data approach too complex or unworkable for their use cases, I'm not going to judge for finding a local escape vs attempting an ecosystem-wide API upgrade. (Which, if we want to take on, we should do soon-ish -- since this PR is already a breaking change, might as well get 'em all done at once!)

I consider such changes out of scope for this PR, though. Better to support all the existing code as cleanly as possible, and update the APIs as something that can be reviewed separately.

This mod includes a header from the Adafruit Unified Sensor library
inherited by its target sensor libraries.  This isn't reliably
picked up by PlatformIO's dependency finder.  Add an explicit dep to
ensure build stability.
The "arduino-pixels-dice" library needs the ESP32 BLE subsystem, but
doesn't explicitly depend on it.
@blazoncek
Copy link
Collaborator

@willmmiles & @netmindz for usermods that interact (i.e. call each other) it would be best to include simple API to exchange a few variables or provide events. Perhaps best using JSON as it is flexible.
So far I am aware of interactions between:

  • Rotary encoder and Four Line Display
  • Auto-save and Four Line Display
  • PWM_fan & Temperature or SHT
  • Seven segment clock and SN_Photoresistor

No other usermods interact.

As (almost) all usermods employ readFromJsonState() and/or addToJsonState() which are also intended for sharing internal data they may be used for such purpose.
UM_Exchange_Data structure was invented to overcome cumbersome processing of JSON format used in JsonState() methods in effect function.

@blazoncek
Copy link
Collaborator

blazoncek commented Feb 4, 2025

One more thing regarding UM_Exchange_Data.
It was invented solely for Audioreactive usermod before I implemented effect array as a vector (dynamically added effects). If audio effects are moved from FX.cpp into Audioreactive sources then there is no need for it.

EDIT: I've prepared a git patch that moves all audio effects into usermod file if you are interested. It gets rid of getAudioData() and simulateSound() and reduces code size by 2kB.

@willmmiles
Copy link
Member Author

for usermods that interact (i.e. call each other) it would be best to include simple API to exchange a few variables or provide events.

I agree. It also seems like there's a common pattern in "sensors to MQTT" type usermods too. I think putting in a simpler framework for publishing and subscribing to sensor data might be a good plan, and maybe even some common code for routing things from the data API to MQTT.

..but ultimately where I'd like to go is a general facility for routing sensor data (and yes, even audio data) to FX parameters, maybe even with a little formula parser to set min/max/scale/combine/etc. This is still a half-baked idea and would need a lot of fleshing out to handle corner cases, but I want to keep it in mind as a long term goal.

I don't think we want to round-trip through JSON for all sensor data, though. That's enough of a hassle (and CPU cost) that I'd expect people to find shortcuts to avoid it, just like you did for the audio data.

No other usermods interact.

I did put in the "gyro surge" partner for MPU6050; it basically routes the net angular rate to the global brightness. They use the um_data API to communicate, though.

This allows the common newline syntax in platformio
Minor compile correctness tweak
For some reason, building it seems to consume 300kb of SRAM??
Probably there's still something wrong with the configuration.
These can be static locals instead; allows these usermods to build and
link together.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants