Conversation
Plugins: MVP for effects to get data from unknown usermods Plugins: implemented class PluginManager + plugin API PinUser and HumiditySensor Plugins: plugins can be accessed directly from other plugins and effects Plugins: optimized datastructures inside PluginManager Plugins: documentation Plugins: added PluginManager to UI info page Plugins: added development notes ... and some final polishing
WalkthroughAdds a Plugin API and PluginManager with sensor and pin interfaces, plugin macros and docs; integrates plugin info into WLED JSON; and updates/introduces usermods (DHT, Temperature) plus new example usermods (UM_DummySensor, UM_PluginDemo) to use the new APIs. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Fix all issues with AI agents
In `@usermods/UM_DummySensor/UM_DummySensor.cpp`:
- Around line 20-28: The setup() function currently calls
pluginManager.registerTemperatureSensor(*this, _name) and then
enableTemperatureSensor(), which leads to double registration because
enableTemperatureSensor() will register again when _isTemperatureSensorEnabled
is false; remove the explicit call to
pluginManager.registerTemperatureSensor(*this, _name) from setup() and rely on
enableTemperatureSensor() to perform registration, keeping the existing
_isTemperatureValid/_isHumidityValid assignments unchanged (alternatively, if
you prefer the other approach, set _isTemperatureSensorEnabled = true before
calling enableTemperatureSensor() so it doesn't re-register).
In `@usermods/UM_PluginDemo/README.md`:
- Around line 1-3: Update the top-level heading text "Examples custom plugins."
to "Examples of custom plugins." in the README: replace the exact string
"Examples custom plugins" with "Examples of custom plugins" (ensure the period
and capitalization match the surrounding lines) so the heading reads correctly
and improves grammar.
In `@usermods/UM_PluginDemo/UM_PluginDemo.cpp`:
- Around line 140-148: The code reads TemperatureSensor::temperatureC() without
checking readiness, so guard the relay logic by first verifying the sensor is
ready via TemperatureSensor::isReady() (after
pluginManager.getTemperatureSensor() returns non-null); only call temperatureC()
and compute isHot when tempSensor->isReady() is true, otherwise skip or abort
relay control (e.g., return or leave the relay unchanged) to avoid toggling on
invalid startup/failure readings.
In `@wled00/PluginAPI/custom/DummySensor/DummySensor.h`:
- Around line 12-15: The comment in DummySensor.h contains a typo ("runtinme");
update the top-of-file API comment in DummySensor.h (the block describing the
plugin API for DummySensor) to replace "runtinme" with "runtime" so the sentence
reads "...They can determine at runtime if the usermod is included in the WLED
binary or not." Ensure only the spelling is corrected and no other text is
changed.
In `@wled00/PluginAPI/DevNotes.md`:
- Around line 11-42: Tighten wording and fix typos across DevNotes.md: run a
pass to correct misspellings and grammar (e.g. "accesible"→"accessible",
"happiliy"→"happily", "demontration"→"demonstration", "bloatyness"→"bloatiness"
or reword to "bloat", "Regardles"→"Regardless", "PluginManger"→"PluginManager",
change "I2C-driver" to "I2C driver"), normalize spacing/phrasing (e.g. "user
mod" vs "usermod") and simplify long sentences for clarity in paragraphs
discussing dependency issues, weak symbols, and the PluginManager info section;
ensure consistent term usage and run a spell/grammar check before committing.
In `@wled00/PluginAPI/PluginManager.cpp`:
- Around line 109-117: The code currently assigns synthetic pin numbers when
itr->isPinValid() is false (setting itr->pinNr to ++counter) which can collide
with real pins; instead, remove the synthetic assignment and make the function
fail fast by calling rollbackPinRegistration(user, pinCount, pinConfig) when
itr->isPinValid() returns false so no unallocated pins are registered; locate
the check around itr->isPinValid() and replace the auto-assign branch with a
rollback path that relies on the PinManager allocation flow rather than creating
phantom pins.
- Around line 139-153: The current loop in PluginManager.cpp deallocates all
pins from _pinUserConfigs before removing the target user, freeing other
plugins' pins; change the logic to only deallocate pins whose owner matches the
unregistering user: iterate _pinUserConfigs and for each entry check entry.first
== &user before calling PinManager::deallocatePin(entry.second.pinNr,
PinOwner::PluginMgr), then erase only those entries (the same predicate used for
_pinUserConfigs removal). Apply the same owner-check approach when removing from
_pinUsers (use pred1/pred2 style lambdas but ensure they test entry.first ==
&user) so only the target user's data and pins are removed.
- Around line 6-38: The map DataMap in InfoDataBuilder currently uses
std::map<const char*, String> which orders by pointer value, not by the
pointed-to plugin names; change DataMap to use a string-comparing comparator
(e.g., define a comparator struct that calls strcmp/strncmp on the char* keys)
so _dict orders entries by name, and update the typedef/using for DataMap
accordingly (ensure the comparator type is passed as the third template
parameter to std::map and include <cstring> if needed); keep the rest of
InfoDataBuilder (add, begin, end, _dict) unchanged.
In `@wled00/PluginAPI/README.md`:
- Around line 8-174: Correct spelling and grammar issues in the README text that
documents plugin design and usage: edit the descriptive paragraphs and headings
(references: "Upgrading Usermods to Plugins", "Design Description",
"Administrated Plugin APIs", "Custom Plugin APIs", "GPIO Pin Handling for
Plugins") and the example prose mentioning UM_PluginDemo, TemperatureSensor,
DHT, UM_DummySensor, DummySensor, PluginManager, PinUser, PinConfig, setup(),
PinManager and pin_manager.h to fix typos (e.g., "usermods aquire" -> "usermods
acquire", "is is defined" -> "is defined", remove awkward phrasing like "Please,
anyone??", ensure consistent capitalization and punctuation) while preserving
technical terms and code identifiers and applying the same fixes to the other
affected sections noted (around the other indicated ranges).
🧹 Nitpick comments (5)
wled00/PluginAPI/custom/DummySensor/DummySensor.h (1)
16-26: Virtual destructor is not necessary for this pattern.
DummySensoris accessed via a raw pointer (GET_PLUGIN_API) that points to a static instance managed by the plugin system, which lives for the entire program duration. The pointer is never owned by the caller and is never deleted. While addingvirtual ~DummySensor() = default;is a reasonable defensive practice for interface classes in general, it is not required in this specific use case.usermods/Temperature/Temperature.cpp (1)
144-144: Consider gating registration on sensor availability.The sensor is registered with
pluginManager.registerTemperatureSensor()unconditionally, even whensensorFoundis 0 (no sensor detected). Other usermods like DHT and UM_DummySensor also register unconditionally, but for a real hardware sensor like DS18B20, registering a non-existent sensor may expose invalid readings to consumers.Suggested conditional registration
lastMeasurement = millis() - readingInterval + 10000; initDone = true; - pluginManager.registerTemperatureSensor(*this, _name); + if (sensorFound) { + pluginManager.registerTemperatureSensor(*this, _name); + }usermods/DHT/DHT.cpp (1)
62-66: Consider initializingtempC.The new
tempCvariable is not explicitly initialized. Whiletemperatureandhumidityare initialized to0,tempCrelies on default initialization. For consistency and to avoid potential undefined behavior on firstdo_getTemperatureC()call before measurement, consider explicit initialization.Suggested fix
- float tempC, humidity, temperature = 0; + float tempC = 0, humidity = 0, temperature = 0;usermods/UM_DummySensor/UM_DummySensor.cpp (1)
73-74: Typo in documentation comment.The
@copydocreference should beHumiditySensor::do_getHumidity(), notdo_getHumidityC().Suggested fix
- /// `@copydoc` HumiditySensor::do_getHumidityC() + /// `@copydoc` HumiditySensor::do_getHumidity()usermods/UM_PluginDemo/UM_PluginDemo.cpp (1)
68-70: Clamp sensor-derived pixel positions to segment bounds.At Line 68 and Line 84, computed indices can exceed
[0, SEGLEN-1]if sensor values are out of range. Clamping avoids potential out-of-range writes.♻️ Proposed safe clamping
- int tempPos = temp * (SEGLEN - 1) / 40.0f; // 20° shall be in the middle + int tempPos = temp * (SEGLEN - 1) / 40.0f; // 20° shall be in the middle + if (tempPos < 0) tempPos = 0; + else if (tempPos > (int)SEGLEN - 1) tempPos = (int)SEGLEN - 1; @@ - const int humPos = hum * (SEGLEN - 1) / 100.0f; - SEGMENT.setPixelColor(humPos, 0xFF00FF); + int humPos = hum * (SEGLEN - 1) / 100.0f; + if (humPos < 0) humPos = 0; + else if (humPos > (int)SEGLEN - 1) humPos = (int)SEGLEN - 1; + SEGMENT.setPixelColor(humPos, 0xFF00FF);Also applies to: 84-85
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@usermods/UM_PluginDemo/UM_PluginDemo.cpp`:
- Around line 62-86: The temp and humidity positions can fall outside [0,
SEGLEN-1], causing invalid indices or long loops; clamp tempPos and humPos after
computing them (from tempSensor->temperatureC() and humSensor->humidity()) to
the range 0..SEGLEN-1 and replace the risky while(tempPos >= 0) pattern with a
bounded decrement loop (e.g., for or a guarded while) that uses the clamped
tempPos, then call SEGMENT.setPixelColor safely; likewise clamp humPos before
calling SEGMENT.setPixelColor(humPos, ...). Use SEGLEN and the existing symbols
(tempSensor->temperatureC, humSensor->humidity, tempPos, humPos,
SEGMENT.setPixelColor, fast_color_scale, SEGCOLOR) to locate and update the
logic.
In `@wled00/PluginAPI/PluginManager.cpp`:
- Around line 99-105: In registerPinUser(PinUser &user, uint8_t pinCount,
PinConfig *pinConfig, const char *pluginName) guard against null or zero inputs
before performing pointer arithmetic: check that pinCount > 0 and pinConfig !=
nullptr (or treat pinCount == 0 as a no-op) and return/exit early (e.g., return
false) if the inputs are invalid; only then compute begin/end and iterate over
pinConfig. This prevents undefined behavior when dereferencing pinConfig or
doing begin + pinCount.
♻️ Duplicate comments (3)
wled00/PluginAPI/DevNotes.md (1)
11-33: Polish remaining typos/wording in DevNotes.
A few spelling/wording issues remain; please clean these for readability.✏️ Suggested edits
- The class definition (yet not the implementation) of that other usermod would have to be inside its headerfile, which your usermod must include. <br> + The class definition (yet not the implementation) of that other usermod would have to be inside its header file, which your usermod must include. <br> - ... and have an 'I2C-driver' as member variable. + ... and have an 'I2C driver' as member variable. - You'll end up with a compiler error: your usermod includes the other's headerfile, which in turn includes the I2C driver's headerfile. + You'll end up with a compiler error: your usermod includes the other's header file, which in turn includes the I2C driver's header file. - Whenever your sourcecode includes a headerfile from the directory of another usermod, that usermod's cpp files will be compiled automatically. + Whenever your source code includes a header file from the directory of another usermod, that usermod's cpp files will be compiled automatically. - Nevertheless, this idea was with weak linking was brought up by `@softhack007` in the issue's thread + Nevertheless, this idea of weak linking was brought up by `@softhack007` in the issue's threadwled00/PluginAPI/README.md (1)
4-8: Quick doc polish pass still needed.
A few wording/typo issues remain; please run a spell/grammar pass.✏️ Suggested edits
- These interfaces here are very high-level by explicit design choice - and they are administrated + These interfaces here are very high-level by explicit design choice - and they are administered - ... can draw whatever it feels to, based on the data from the sensor. + ... can draw whatever it feels like, based on the data from the sensor. - Usermods can acquire their desired GPIO pins through the `PluginManager`. For an example, have a + Usermods can acquire their desired GPIO pins through the `PluginManager`. For example, take a - Project Environment selection list of the statusbar. + Project Environment selection list of the status bar.Also applies to: 47-49, 162-185
usermods/UM_DummySensor/UM_DummySensor.cpp (1)
20-28: Ensure enabled flags match initial registration.
setup()registers sensors but leaves_isTemperatureSensorEnabled/_isHumiditySensorEnabledfalse, sois*Enabled()reports disabled anddisable*()won’t unregister untilenable*()is called. Prefer enabling via the helpers (or set the flags true here).🐛 Proposed fix (keep flags in sync)
void setup() override { - // register sensor plugins - pluginManager.registerTemperatureSensor(*this, _name); - pluginManager.registerHumiditySensor(*this, _name); + // register sensor plugins + enableTemperatureSensor(); + enableHumiditySensor(); // this dummy can deliver readings instantly _isTemperatureValid = true; _isHumidityValid = true; }
🧹 Nitpick comments (1)
wled00/PluginAPI/PinUser.h (1)
42-64: Avoid a magic number for invalid pins.
Consider centralizing the sentinel value so it’s self-documenting and consistent across helpers.♻️ Suggested tweak
struct PinConfig { + static constexpr uint8_t kInvalidPin = 0xFF; PinConfig(PinType pinType_ = PinType::undefined, const char *pinName_ = nullptr) : pinType{pinType_}, pinName{pinName_} {} /// The designated pin number. - uint8_t pinNr = 0xFF; + uint8_t pinNr = kInvalidPin; @@ - bool isPinValid() const { return pinNr != 0xFF; } - void invalidatePin() { pinNr = 0xFF; } + bool isPinValid() const { return pinNr != kInvalidPin; } + void invalidatePin() { pinNr = kInvalidPin; } };Based on learnings, consider replacing meaningful magic numbers with named constants.
|
I'm not sure if "plugin API" is required as there is FYI BTW I suggested to move all audio effects (that need |
@blazoncek I think these topics are two but not one. We need some code in the core that reacts on |
|
@JoaDick I like your proposal of a "Usermod OS" that provides a generic infrastructure for usermods, and exchange of data between usermods or between usermods and the WLED core. Even when not everybody says "its perfect", I'm happy that you contributed your PR to support discussions about the future design of WLED. |
or even have a "resources manager" it is not only I2S but also SPI, ADC, DMA, RMT etc. that currently have a single purpose but might not in the future, for example I have written a driver that uses SPI in a "parralel I2S output" fashion with 4 outputs (C3) as well as the DMA-ADC driver for AR (C3, S2, S3) which may clash with "analog read" of some usermods etc. @JoaDick I agree with @softhack007 , this is a push in the right direction exploring some concepts - I also agree with @blazoncek that this sounds like a task for the usermod-manager. |
Palettes are already part of usermod itself and are added upon its start. And yes, move all audio effects into usermod. It has no implications on anything if usermod is included and reduces the overal code in both cases. Done that quite a while ago in my fork with no ill effect. |
Why would USERMOD_AUDIOREACTIVE be defined if usermod is moved out of tree as is the purpose of library-like usermods? You should not have any #ifdef USERMOD_... in WLED source. |
|
Thank you guys, I really appreciate your feedback! It's great to see that you're interested in this PR and already started a discussion. Regarding the suggestion from @blazoncek and @DedeHai that this new
So I decided to make it this way:
From a technical perspective, it doesn't really matter. We can decide to keep the |
@DedeHai Please have a look at the note inside |
@DedeHai That is the core purpose of the PluginManager: to serve as one central interface from the (external) plugins into the WLED framework. That doesn't necessarily mean that it must implement all functionality by itself - it can of course delegate to other internal components. Those are doing the real work, but shall not be exposed to the usermods directly.
Let me respond with an example. Imagine such an interface: The AudioReactive usermod of course would implement this interface. Now, would your magnetic field sensor be able to provide the data that is specified by that hypothetical interface? Could it provide something like a "volumeSmth"? If yes, then your sensor could hypothetically implement that interface. |
|
Just out of curiosity: what are your intentions, @DedeHai ?
If you plan the latter, please follow the example with the specific |
|
I strongly agree with the API design concerns about having multiple apparent "component" interfaces. There should be one and exactly one place for defining new components. While it might seem a trivial implementation detail to an implementor, naming and code organization are of paramount importance for long term project maintenance, and make a huge difference to novice users. I haven't had time to thoroughly digest the code, but from a quick review standpoint:
If you're interested in pursuing this general idea further, I'd suggest trying to construct a "SensorValue" base class that can be instantiated/used by usermods:
I've got a generalization of the usermod static list code in another branch - I'll link it here when I get a moment later. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@wled00/PluginAPI/PluginManager.cpp`:
- Around line 305-320: The dead `#if` (0) alternative for TemperatureSensor (and
the similar humidity block) references val before it's declared, causing compile
errors if enabled; either remove these dead blocks or fix them by declaring a
String val (and ensure notAvailable exists) before it's used (as done in the
active implementation around user.createNestedArray and
sensor.temperature()/sensor.humidity()), and mirror that same fix for the
humidity block so both alternative implementations compile cleanly if toggled
on.
|
Dynamic compile-time array generalization is here: https://github.com/willmmiles/WLED/blob/dynamic-array-linker-section/wled00/dynarray.h (I originally wrote that to extend compile-time table support to ESP-IDF v5+ and other platforms; sorry I haven't got around to PR'ing it yet.) That particular implementation still depends on some single source file "instantiating" the array. While thinking about it today, it occurred to me that it's possible to redesign it so that array accessors use locally defined symbols, as they take no space; so that no translation unit need explicitly instantiate anything. I'll follow up with that later. A more concrete example of why I'm postulating a general sensor framework, to further the discussion: consider the Scrolling Text effect. I think it would be cool let it be able to embed sensor data, eg. "$TEMP.2f" or "$HUM.d". To do that, we'd need some kind of "name lookup for sensor data" API. Given such, it would also cover the use case of "I'm a fan control module and I want a temperature measurement". |
exactly this:
|
Correct. UMData is a poor design, but that principle of allowing data to be shared between usermods and the core or between usermods is a fair requirement. Forcing Audio Reactive effects to only live inside the AR usermod is an unnecessary restriction that goes against our overall design of a modular system and doesn't provide a solution for when one usermod needs to exchange dara with another
Yeah now we don't have that define, we do need to replace pinmanager with a more general resource manager that can handle tracking I2S and also HardwareSerial (needed for dmx) |
He's meaning we need a replacement for that, if you actually read what he's talking about. It's just a reference to how historically we handled knowing if it was included |
|
Please let me first clarify some topics that came up already, so that this discussion doesn't get too much confused: Topics about the design itself and possible optimizations: Be aware of the difference between usermod interfaces that are administrated by WLED and custom usermod interfaces, as they are handled completely different.
Potential usecases of your desired "GenericSensor" interface:
@willmmiles This looks really interesting, i'll definitely have a closer look at that later.
Exactly that usecase is implemented in the examples. The distinction is done via C++ interface type; no string/name "$TEMP.2f" or "$HUM.d" lookup required. Look at the "Thermometer-effect". Imaginary replace the "drawing a bar" with "insert into scrolling text".
Exactly that usecase is also implemented in the examples. Look at the "fan-control" method. |
@DedeHai Should be possible; just give me some time to extend the examples. There are some more aspects to consider; it is not as simple as "just a number"... |
Your example does not implement what I want at all: it requires the "reader" to know at compile time about all possible sensor types it may access. The intent is that the FX or other reader does not know at compile time about what sensors types are possible - it should be able to draw data from any sensor with a compatible numeric type, with no changes to its source code, even ones that don't exist at the time it was written (eg. subspace ether fluctuations). The implication is that "temperature" and "humidity" must be a single, common C++ type to such code; and they must have a "name" field so that readers can find them. At this point, one might think: OK, I can create a common base class for all of these sensors, so generic readers can access any of them in a common way. Say something like: template<typename data_t> class SensorData {
public:
const char* getName() const;
const data_t& getValue() const; // explicit specializations for POD types can return just data_t
};
template<typename data_t> std::span<const SensorData<data_t>> getSensorsByName(const char* name);
template<typename data_t> std::span<const SensorData<data_t>> getAllSensors();
// etc.(apologies for modern C++ usage that isn't yet supported by our ESP32 toolchain) However: given such an interface, the point I and my colleagues are trying to make is that we don't expect to need any other API for cross-usermod data access. That API is sufficient for the overwhelming majority of control-type usermods; and, as you've noted, other more specialized use cases are well handled by For a convincing demo, try implementing a global system to replace |
|
@willmmiles , @DedeHai Well... what you want is something completely different than this PR was aimed at. But I'm going off topic; enough rambling. Although this PR most likely won't make it, I am still curious about that generic sensor handling idea, and would like to know more about that. So, if you're seriously interested in contributions on that topic, please share a bit more details about your usecases with those generic sensors, and also your expectations or ideas about its design. Or is it already in such a mature state that just the coding is missing? |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@wled00/PluginAPI/PluginManager.cpp`:
- Around line 99-185: Several registration methods store pluginName unchecked
(e.g., registerPinUser, registerTemperatureSensor, registerHumiditySensor) which
can lead to null JSON/map keys; guard pluginName against nullptr and normalize
it to a safe literal like "unknownName" before storing. Update each emplace_back
call to use a local const char* normalizedName = (pluginName != nullptr) ?
pluginName : "unknownName" (or equivalent) and pass normalizedName to
_pinUsers.emplace_back, _temperatureSensors.emplace_back and
_humiditySensors.emplace_back so no nullptr is stored.
- Around line 111-118: The demo auto-assignment uses a static uint8_t counter in
the PluginManager loop (see itr->isPinValid() and itr->pinNr) which will wrap
and create pin collisions; change the counter to a wider type (e.g.,
uint16_t/uint32_t) and add a guard/cutoff so it never wraps into the valid GPIO
range—if the counter exceeds a defined max or would wrap, call the existing
rollbackPinRegistration(user, pinCount, pinConfig) (or return an error) instead
of assigning an invalid pin; ensure the fix references the same static counter
and the rollback path in PluginManager.cpp.
My apologies, I'm a very far behind on catching up with PRs and questions; I've been picking away at a few (oldest and newest) as time permits.
The ultimate goal is to provide a system to route sensor data in to FX parameters. An example would be using a temperature measurement as the "intensity" value for the Percentage effect, to create a fancy thermometer display. At the limit, any value that is represented by a number -- effect parameters, colors, brightness -- could in theory be routed to accept an expression based on real-time sensor value(s). (I say expression because to be practical such an engine would need to be able to perform some math: the thermometer example might need to be configured as something like There are a lot of open questions about the right shape of such an expression system -- expression languages are cumbersome, so what would the UI want to look like? How would someone configure this? Right now that's all still up in the air. What we do know is that, at a minimum, we'd need some approach for tying a measurement name to a value readable in a standard format. In practice, there are already two such implementations in the current code, though sadly both use ASCII text as that standard: So what we had in mind was adding some common "sensor" registry API that would simplify sensor usermods by (a) wrapping up the common tasks of publishing measurements to info and MQTT, and (b) introducing an internal API to get sensor data that can be used by both other usermods to retrieve sensor data in a source-agnostic way, as well as a future dynamic FX system. (And of course "Scrolling Text" is low hanging fruit for such a system, as it's already doing text substitution; given an ability to look up sensor data by name, it's an easy feature to add.) I haven't personally written any code in this direction yet; it's pretty far down my priority list (I'm mostly busy these days with system stability problems). Contributions in this direction would be welcome. There's a fair number of details to pin down how to wrap up MQTT data formatting and topic assignment in a common way while still preserving an "a number is a number" API for future effect integration. |
@willmmiles Are these name standards already documented somewhere? Or can you point me to some examples? |
Specifically I mean that MM uses a null pointer as the default for compatibility if a usermod doesn't supply it; ideally what I think we want here is a separate |
This PR implements the feature requests from #5290 - and even a bit more.
PinOwnerenum inpin_manager.h).getId()and specify a custom ID inconst.h.UsermodManager::getUMData()is not usedUsermod::addToJsonInfo()- which might just need minimal polishing (but thats out of scope of this PR)In a nutshell, you can maintain your own usermods inside a private branch, only by adding the corresponding files. WLED will integrate them automatically without the need for any modifications inside existing WLED code. And these private usermods can even interact with each other (or with existing usermods) via simple function calls.
All this was implemented with absolute minimal changes of existing WLED code; everything is built on top of current WLED features.
DHTandTemperatureusermods have just been extended for demonstration.For more details, please look at the readme in the
PluginAPIdirectory and the comments inside the new sourcefiles there. Also have a look at the development notes, which address some questions that will surely come up during the review.Examples for using these new features are implemented in the
UM_PluginDemoandUM_DummySensorusermods.Have fun and Enjoy!
Summary by CodeRabbit
New Features
Updates
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.