Skip to content

Pin owner for out-of-tree usermod, and inter-usermod method calls #5290

Description

@blazoncek

Is your feature request related to a problem? Please describe.
Currently if one needs GPIO for usermod a new entry is added to pin_manager.h to add a new PinOwner. The same goes for usermod ID constant defined in const.h.

If one wants to create out-of-tree (i.e. not included in WLED source code, privately maintained) usermod that requires GPIO allocation there is no way to extend PinOwner enum (except in local/private repository by modifying pin_manager.h) and that defeats usability of such usermod.

The same is true for usermod ID constant defined in const.h.

Describe the solution you'd like
Add extensibility to PinManager to allow addition of custom PinOwner. Remove the need for usermod ID constants or replace it with something more versatile.

Describe alternatives you've considered
Reuse one of the existing PinOwner codes but that is not a safe approach as there might be a clash. Or, provide a patch for upstream to add new owner, but that is no better than the old approach with in-tree usermods.

Additional context
Since usermods were modified to behave like libraries (i.e. no longer included in usermod_list.cpp) it is expected that WLED API would allow extensibility without modifying WLED's source code.

Activity

  1. DedeHai commented on Jan 12, 2026

    @DedeHai
    Collaborator

    that's a good point. IMHO the UM numbering should be taken care of by the usermod manager and dynamically assign the ID's. AFAIK the usermod ID's are not used in any communication protocols and is purely internal. If that is feasible, usermods can just use their ID as a pin owner without conflict.

  2. blazoncek commented on Jan 12, 2026

    @blazoncek
    ContributorAuthor

    usermod ID's are not used in any communication protocols

    That is not entirely true. 4LD, Rotary encoder and Autosave usermods use IDs to identify if usermods are accessible (they were all written by the same author). Then they communicate with each other directly.

  3. DedeHai commented on Jan 12, 2026

    @DedeHai
    Collaborator

    maybe that can be resolved by a function to get the ID through the usermod name? see ongoing work in #5234

  4. blazoncek commented on Jan 12, 2026

    @blazoncek
    ContributorAuthor

    That will require usermod to store other usermod's name, which is IMO unfortunate waste of memory.
    There is also a necessity to know other usermod's methods (as seen in 4LD, Rotary encoder and Autosave).
    API should be enhaced to support that as well.

  5. softhack007 commented on Jan 12, 2026

    @softhack007
    Member

    "Direct calling of other usermod methods" is a very special case, I haven't seen that in other usermods than the three created by @blazoncek.

    That will require usermod to store other usermod's name, which is IMO unfortunate waste of memory.
    There is also a necessity to know other usermod's methods (as seen in 4LD, Rotary encoder and Autosave).
    API should be enhaced to support that as well.

    I'm not sure this would be possible technically. Out-of-Tree usermods are seperate .cpp files, i.e. compiled seperately. If the rotary usermod instance does not have the definition of the display usermod instance, there is no way to call a method that is not part of the base class.

    I could imagine that a hack with "weak references" could be possible. This is a trick where the linker tries to resolve a name. If the name exists in the overall binary, you'll get a valid address pointer. If it does not exist, you'll get nullptr. But this hack still requires that rotary and autosave usermods know the methods added in the display usermod instance.

    IMHO the UM numbering should be taken care of by the usermod manager and dynamically assign the ID's. AFAIK the usermod ID's are not used in any communication protocols and is purely internal. If that is feasible, usermods can just use their ID as a pin owner without conflict.

    @DedeHai I agree

  6. blazoncek commented on Jan 12, 2026

    @blazoncek
    ContributorAuthor

    @softhack007 before you "accuse" me of creating offending usermods, check commit history. Please.

  7. blazoncek commented on Jan 12, 2026

    @blazoncek
    ContributorAuthor

    They were all created using valid and public API available at the time of their creation.

  8. softhack007 commented on Jan 12, 2026

    @softhack007
    Member

    @softhack007 before you "accuse" me of creating offending usermods, check commit history. Please.

    Sorry @blazoncek I did not want to "accuse" you. I was thinking these three were your creation, maybe i'm wrong and you just maintained them for some time.

    I intended to say that these three usermods are a special case, and they make use of a "side-effect" that in the past all usermods were joined into one "compilation unit" (included by usermod_list.cpp), and the definition of all methods were visible to other usermods. This does not work any more if each usermod has its own .cpp file, unless one usermods can include another usermods ".h" file.

  9. softhack007 commented on Jan 12, 2026

    @softhack007
    Member

    The pattern used by the autosave usermod to utilize a method in 4LD usermod is like this:

    • usermod_v2_four_line_display_ALT has methods that are not part of the base class, but they are public

    bool FourLineDisplayUsermod::wakeDisplay() {

    void FourLineDisplayUsermod::overlay(const char* line1, long showHowLong, byte glyphType) {


    • usermod_v2_auto_save uses these methods to display some text on the device:

    #ifdef USERMOD_FOUR_LINE_DISPLAY
    FourLineDisplayUsermod* display;
    #endif

    #ifdef USERMOD_FOUR_LINE_DISPLAY
    // This Usermod has enhanced functionality if
    // FourLineDisplayUsermod is available.
    display = (FourLineDisplayUsermod*) UsermodManager::lookup(USERMOD_ID_FOUR_LINE_DISP);
    #endif

    void inline displayOverlay() {
    #ifdef USERMOD_FOUR_LINE_DISPLAY
    if (display != nullptr) {
    display->wakeDisplay();
    display->overlay("Settings", "Auto Saved", 1500);
    }
    #endif
    }


    Edit: I just saw that usermod_v2_four_line_display_ALT already has a separate header file, so the solution for autosave could be:

    #ifdef USERMOD_FOUR_LINE_DISPLAY
      #include "../usermod_v2_four_line_display_ALT/usermod_v2_four_line_display.h"
    #endif

    And indeed, we would need to think about a replacement for UsermodManager::lookup(USERMOD_ID_FOUR_LINE_DISP); in case that these usermods were out-of-tree without a fixed ID.

  10. changed the title [-]Pin owner for out-of-tree usermod[/-] [+]Pin owner for out-of-tree usermod, and inter-usermod method calls[/+] on Jan 12, 2026
  11. blazoncek commented on Jan 12, 2026

    @blazoncek
    ContributorAuthor

    There is also PWM_fan usermod (that I wrote) that uses Temperature usermod in a similar way.
    Its usefulness is very limited without Temperature usermod.

  12. DedeHai commented on Jan 12, 2026

    @DedeHai
    Collaborator

    That will require usermod to store other usermod's name, which is IMO unfortunate waste of memory.

    correct, it's either "look up by name" (name is fixed) or "look up by ID" (which requires the global list) can't have it both ways. Since this discussion is about getting rid of said list, I do not see any other way than look up by name.

    So I think it should go something like this:

    • the usermod manager enumerates usermods and gives each compile usermod an ID to be used for pin allocations or UM-to-UM communication (did not look at how that works)
    • the usermod manager provides a function to "get the ID" of a usermod from a usermod name
    • the usermod manager also provides a function to "get name from ID" to easily build list of UM names for the UI
    • all communication through JSON should not be by ID but by usermod name (I think that's already mostly the case)
  13. willmmiles commented on Jan 12, 2026

    @willmmiles
    Member

    Re pin manager: in an ideal world, the pin manager could store a pointer to the usermod (or its name). If we cannot afford to spend the extra 3 bytes per pin, the next best solution is to store the index in to the usermods table -- this is currently not exposed by um_manager.cpp but would be straightforward to implement. All we'd need are two functions:

    int8_t UsermodManager::getIndexOf(Usermod* m) {
       Usermod** m_entry = std::find(_usermod_table_begin, _usermod_table_end, m);
       if (m_entry== _usermod_table_end) return -1;
       return m_entry - _usermod_table_begin;
    }
    
    UsermodManager::getByIndex(int8_t index) {
     if ((index >=0) && (index < getCount())) {
       return _usermod_table_begin[index];
     } else {
       return -1;
     }
    }

    Re inter-usermod communications:
    The correct answer for modules that provide data or services that can be used by other modules is to expose an API via a header file that can be included by other modules. That header could expose an extern reference to their usermod object, or any other formal API, such as some accessor functions. There's no need for dynamic lookup at all; whether some other module is included, and where it's placed in memory, are both knowable at compile time. It's perfectly valid for usermods to include headers provided by other usermods, out of tree or otherwise, just like any other library.

    For modules that want to optionally enable interactions, eg. "if this usermod is included, pull data from it", this can be done via platformio scripting. The PWM_fan usermod demonstrates how to do this.

  14. blazoncek commented on Jan 12, 2026

    @blazoncek
    ContributorAuthor

    can't have it both ways

    You can. Get MD5 (or similar hash of the name) and store it as numeric ID. Just use a known hash algorithm.

    store a pointer to the usermod

    IMO unnecessary, just make sure usermod IDs are unique and assigned by usermod manager. In any case it is usermod's responsibility to release its pins.

  15. willmmiles commented on Jan 12, 2026

    @willmmiles
    Member

    IMO unnecessary, just make sure usermod IDs are unique and assigned by usermod manager. In any case it is usermod's responsibility to release its pins.

    Right, I see this the other way around. Any usermod already has a guaranteed unique number assigned to it for any given build: its address. Adding any code to compute some other second ID number is extra complexity. I can accept that there might be times and places that trading that extra code and maintenance to save a few bytes of RAM is worthwhile, but the burden of being "extra work" is on that second system.

  16. blazoncek commented on Jan 12, 2026

    @blazoncek
    ContributorAuthor

    its address

    Addresses change and you want static/fixed ID, preferably predetermined if you want cross linking between different usermods. Or, I misunderstood your point.

    EDIT: I say ID but that is not necessarily constant as used today.

  17. willmmiles commented on Jan 13, 2026

    @willmmiles
    Member

    Addresses change and you want static/fixed ID, preferably predetermined if you want cross linking between different usermods. Or, I misunderstood your point.

    That would be the name of the usermod object. Or the function names of the usermod's API. The linker resolves the actual address at build time. Usermods objects are just variables (global or static) and can be addressed directly as such. Usermods are free to define a public API like any other library, through functions or by making their object instance visible.

    PWM_fan example with object accesses: willmmiles@d6be26c

    PWM_fan example with function-based API: willmmiles@232d7c4

  18. blazoncek commented on Jan 13, 2026

    @blazoncek
    ContributorAuthor

    That's even better (object access).

    And if I want to be insulting rude: One more reason to move everything Audioreactive related into usermod itself.

  19. softhack007 commented on Jan 13, 2026

    @softhack007
    Member

    And if I want to be insulting: One more reason to move everything Audioreactive related into usermod itself.

    I'm sure you don't want to be insulting ;-) but if you wanted to be, I would say the comment is stupid.

  20. blazoncek commented on Jan 14, 2026

    @blazoncek
    ContributorAuthor

    Looked up translator, it's rude, not insulting. Sorry.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions