From 79163dd950c2c2bc8132de0c0f97ef5f8e627ef4 Mon Sep 17 00:00:00 2001 From: Joachim Dick <62520542+JoaDick@users.noreply.github.com> Date: Fri, 30 Jan 2026 21:53:00 +0100 Subject: [PATCH 01/10] Sensor: initial commit --- wled00/Sensor.cpp | 65 +++++++++++++ wled00/Sensor.h | 219 +++++++++++++++++++++++++++++++++++++++++++ wled00/fcn_declare.h | 4 + 3 files changed, 288 insertions(+) create mode 100644 wled00/Sensor.cpp create mode 100644 wled00/Sensor.h diff --git a/wled00/Sensor.cpp b/wled00/Sensor.cpp new file mode 100644 index 0000000000..165de68a3b --- /dev/null +++ b/wled00/Sensor.cpp @@ -0,0 +1,65 @@ +/** + * (c) 2026 Joachim Dick + * Licensed under the EUPL v. 1.2 or later + */ + +#include "Sensor.h" + +//-------------------------------------------------------------------------------------------------- + +void SensorValue::accept(SensorValueVisitor &visitor) const +{ + switch (_type) + { + case SensorValueType::Bool: + visitor.visit(_bool); + break; + case SensorValueType::Float: + visitor.visit(_float); + break; + case SensorValueType::Int32: + visitor.visit(_int32); + break; + case SensorValueType::UInt32: + visitor.visit(_uint32); + break; + case SensorValueType::Array: + visitor.visit(_array); + break; + case SensorValueType::Struct: + visitor.visit(_struct); + break; + } +} + +void Sensor::accept(uint8_t channelIndex, SensorChannelVisitor &visitor) +{ + if (!isReady()) + return; + + const auto &val = getValue(channelIndex); + const auto &props = getProperties(channelIndex); + switch (val.type()) + { + case SensorValueType::Bool: + visitor.visit(val._bool, props); + break; + case SensorValueType::Float: + visitor.visit(val._float, props); + break; + case SensorValueType::Int32: + visitor.visit(val._int32, props); + break; + case SensorValueType::UInt32: + visitor.visit(val._uint32, props); + break; + case SensorValueType::Array: + visitor.visit(*val._array, props); + break; + case SensorValueType::Struct: + visitor.visit(*val._struct, props); + break; + } +} + +//-------------------------------------------------------------------------------------------------- diff --git a/wled00/Sensor.h b/wled00/Sensor.h new file mode 100644 index 0000000000..c08f510795 --- /dev/null +++ b/wled00/Sensor.h @@ -0,0 +1,219 @@ +/** + * (c) 2026 Joachim Dick + * Licensed under the EUPL v. 1.2 or later + */ + +#pragma once + +#include + +//-------------------------------------------------------------------------------------------------- + +class SensorValueArray; +class SensorValueStruct; + +class SensorChannelVisitor; +class SensorValueVisitor; +class SensorValueArrayVisitor; +class SensorValueStructVisitor; + +enum class SensorValueType : uint8_t +{ + Bool = 0, + Float, + Int32, + UInt32, + Array, + Struct +}; + +class SensorValue +{ +public: + SensorValue(bool val) : _bool{val}, _type{SensorValueType::Bool} {} + SensorValue(float val) : _float{val}, _type{SensorValueType::Float} {} + SensorValue(int32_t val) : _int32{val}, _type{SensorValueType::Int32} {} + SensorValue(uint32_t val) : _uint32{val}, _type{SensorValueType::UInt32} {} + SensorValue(const SensorValueArray *val) : _array{val}, _type{SensorValueType::Array} {} + SensorValue(const SensorValueStruct *val) : _struct{val}, _type{SensorValueType::Struct} {} + + bool as_bool() const { return _type == SensorValueType::Bool ? _bool : false; } + float as_float() const { return _type == SensorValueType::Float ? _float : 0.0f; } + int32_t as_int32() const { return _type == SensorValueType::Int32 ? _int32 : 0; } + uint32_t as_uint32() const { return _type == SensorValueType::UInt32 ? _uint32 : 0U; } + const SensorValueArray *as_array() const { return _type == SensorValueType::Array ? _array : nullptr; } + const SensorValueStruct *as_struct() const { return _type == SensorValueType::Struct ? _struct : nullptr; } + + SensorValueType type() const { return _type; } + + void accept(SensorValueVisitor &visitor) const; + + operator bool() const { return as_bool(); } + operator float() const { return as_float(); } + operator int32_t() const { return as_int32(); } + operator uint32_t() const { return as_uint32(); } + operator const SensorValueArray *() const { return as_array(); } + operator const SensorValueStruct *() const { return as_struct(); } + +private: + friend class Sensor; + union + { + bool _bool; + float _float; + int32_t _int32; + uint32_t _uint32; + const SensorValueArray *_array; + const SensorValueStruct *_struct; + }; + + SensorValueType _type; +}; + +struct SensorChannelProps +{ + SensorChannelProps(const char *channelName_, + const char *unitString_, + SensorValue rangeMin_, + SensorValue rangeMax_, + const char *channelNameShort_ = "") + : channelName{channelName_}, unitString{unitString_}, rangeMin{rangeMin_}, rangeMax{rangeMax_}, channelNameShort{channelNameShort_} {} + + const char *channelName; + const char *unitString; + SensorValue rangeMin; + SensorValue rangeMax; + const char *channelNameShort; +}; + +template +using SensorChannelPropertiesArray = std::array; + +//-------------------------------------------------------------------------------------------------- + +class Sensor +{ +public: + const char *name() const { return _sensorName; } + + uint8_t channelCount() const { return _channelCount; } + + bool isReady() { return do_isSensorReady(); } + + SensorValue getValue(uint8_t channelIndex = 0) { return do_getSensorValue(channelIndex); } + + const SensorChannelProps &getProperties(uint8_t channelIndex = 0) { return do_getSensorProperties(channelIndex); } + + void accept(uint8_t channelIndex, SensorChannelVisitor &visitor); + void accept(SensorChannelVisitor &visitor) { accept(0, visitor); } + +protected: + Sensor(const char *sensorName, uint8_t channelCount) + : _sensorName{sensorName}, _channelCount{channelCount} {} + + virtual bool do_isSensorReady() = 0; + virtual SensorValue do_getSensorValue(uint8_t channelIndex) = 0; + virtual const SensorChannelProps &do_getSensorProperties(uint8_t channelIndex) = 0; + +private: + const char *_sensorName; + const uint8_t _channelCount; +}; + +class SensorChannelProxy final : public Sensor +{ +public: + SensorChannelProxy(Sensor &realSensor, const uint8_t channelIndex) + : Sensor{realSensor.name(), 1}, _realSensor{realSensor}, _channelIndex{channelIndex} {} + + Sensor &getRealSensor() { return _realSensor; } + uint8_t getRealChannelIndex() { return _channelIndex; } + +private: + bool do_isSensorReady() override { return _realSensor.isReady(); } + SensorValue do_getSensorValue(uint8_t) override { return _realSensor.getValue(_channelIndex); } + const SensorChannelProps &do_getSensorProperties(uint8_t) override { return _realSensor.getProperties(_channelIndex); } + + Sensor &_realSensor; + const uint8_t _channelIndex; +}; + +//-------------------------------------------------------------------------------------------------- + +class SensorValueVisitor +{ +public: + virtual void visit(bool val) {} + virtual void visit(float val) {} + virtual void visit(int32_t val) {} + virtual void visit(uint32_t val) {} + virtual void visit(const SensorValueArray *val) {} + virtual void visit(const SensorValueStruct *val) {} +}; + +class SensorChannelVisitor +{ +public: + virtual void visit(bool val, const SensorChannelProps &props) {} + virtual void visit(float val, const SensorChannelProps &props) {} + virtual void visit(int32_t val, const SensorChannelProps &props) {} + virtual void visit(uint32_t val, const SensorChannelProps &props) {} + virtual void visit(const SensorValueArray &val, const SensorChannelProps &props) {} + virtual void visit(const SensorValueStruct &val, const SensorChannelProps &props) {} +}; + +//-------------------------------------------------------------------------------------------------- + +class EasySensor : public Sensor +{ +public: + EasySensor(const char *sensorName, const SensorChannelProps &channelProps) + : Sensor{sensorName, 1}, _props{channelProps}, _val{channelProps.rangeMin} {} + + void set(SensorValue val) + { + if (val.type() == _val.type()) + { + _val = val; + _isReady = true; + } + } + + void suspend() { _isReady = false; } + + SensorValue get() const { return _val; } + + void operator=(SensorValue val) { set(val); } + +private: + bool do_isSensorReady() override { return _isReady; } + SensorValue do_getSensorValue(uint8_t) override { return _val; } + const SensorChannelProps &do_getSensorProperties(uint8_t) override { return _props; } + +private: + const SensorChannelProps _props; + SensorValue _val; + bool _isReady = false; +}; + +//-------------------------------------------------------------------------------------------------- + +inline SensorChannelProps makeChannelProps_Bool(const char *channelName, + const char *channelNameShort = "", + const char *unitString = "") +{ + return {channelName, unitString, false, true, channelNameShort}; +} + +inline SensorChannelProps makeChannelProps_Temperature(float rangeMin = 0.0f, + float rangeMax = 40.0f) +{ + return {"Temperature", "°C", rangeMin, rangeMax, "Temp"}; +} + +inline SensorChannelProps makeChannelProps_Humidity() +{ + return {"Humidity", "%rel", 0.0f, 100.0f, "Hum"}; +} + +//-------------------------------------------------------------------------------------------------- diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 84b5595df7..b1f7131eb3 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -2,6 +2,8 @@ #ifndef WLED_FCN_DECLARE_H #define WLED_FCN_DECLARE_H +#include "Sensor.h" + /* * All globally accessible functions are declared here */ @@ -322,6 +324,8 @@ class Usermod { virtual void onUpdateBegin(bool) {} // fired prior to and after unsuccessful firmware update virtual void onStateChange(uint8_t mode) {} // fired upon WLED state change virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;} + virtual uint8_t getSensorCount() { return 0; } + virtual Sensor *getSensor(uint8_t index) { return nullptr; } // API shims private: From 8117c7a988c5f31acdade92d8403d7385a68f08d Mon Sep 17 00:00:00 2001 From: Joachim Dick <62520542+JoaDick@users.noreply.github.com> Date: Sat, 31 Jan 2026 19:38:59 +0100 Subject: [PATCH 02/10] Sensor: added cursors --- wled00/Sensor.cpp | 81 ++++++++++++++++++++++++++-- wled00/Sensor.h | 135 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 203 insertions(+), 13 deletions(-) diff --git a/wled00/Sensor.cpp b/wled00/Sensor.cpp index 165de68a3b..8f07696efd 100644 --- a/wled00/Sensor.cpp +++ b/wled00/Sensor.cpp @@ -3,10 +3,80 @@ * Licensed under the EUPL v. 1.2 or later */ +#include "wled.h" #include "Sensor.h" //-------------------------------------------------------------------------------------------------- +void SensorCursor::reset() +{ + _sensorIndex = 0; + for (_umIter = _umBegin; _umIter < _umEnd; ++_umIter) + { + Usermod *um = *_umIter; + if (um->getSensorCount()) + { + _sensor = um->getSensor(0); + if (_sensor && _sensor->channelCount()) + return; + } + } + _sensor = nullptr; +} + +bool SensorCursor::next() +{ + if (isValid()) + { + // try next sensor of current usermod + Usermod *um = *_umIter; + if (++_sensorIndex < um->getSensorCount()) + { + _sensor = um->getSensor(_sensorIndex); + if (_sensor && _sensor->channelCount()) + return true; + } + // try next usermod + _sensorIndex = 0; + while (++_umIter < _umEnd) + { + um = *_umIter; + if (um->getSensorCount()) + { + _sensor = um->getSensor(0); + if (_sensor && _sensor->channelCount()) + return true; + } + } + // no more sensors found + _sensor = nullptr; + } + return false; +} + +//-------------------------------------------------------------------------------------------------- + +void SensorChannelCursor::reset() +{ + _sensorCursor.reset(); + _channelIndex = 0; +} + +bool SensorChannelCursor::next() +{ + while (_sensorCursor.isValid()) + { + if (++_channelIndex < _sensorCursor->channelCount()) + if (matches(_sensorCursor->getProps(_channelIndex))) + return true; + _channelIndex = 0; + _sensorCursor.next(); + } + return false; +} + +//-------------------------------------------------------------------------------------------------- + void SensorValue::accept(SensorValueVisitor &visitor) const { switch (_type) @@ -24,10 +94,12 @@ void SensorValue::accept(SensorValueVisitor &visitor) const visitor.visit(_uint32); break; case SensorValueType::Array: - visitor.visit(_array); + visitor.visit(*_array); break; case SensorValueType::Struct: - visitor.visit(_struct); + visitor.visit(*_struct); + case SensorValueType::Whatever: + visitor.visit(_whatever); break; } } @@ -38,7 +110,7 @@ void Sensor::accept(uint8_t channelIndex, SensorChannelVisitor &visitor) return; const auto &val = getValue(channelIndex); - const auto &props = getProperties(channelIndex); + const auto &props = getProps(channelIndex); switch (val.type()) { case SensorValueType::Bool: @@ -59,6 +131,9 @@ void Sensor::accept(uint8_t channelIndex, SensorChannelVisitor &visitor) case SensorValueType::Struct: visitor.visit(*val._struct, props); break; + case SensorValueType::Whatever: + visitor.visit(val._whatever, props); + break; } } diff --git a/wled00/Sensor.h b/wled00/Sensor.h index c08f510795..61c9b494b6 100644 --- a/wled00/Sensor.h +++ b/wled00/Sensor.h @@ -24,7 +24,8 @@ enum class SensorValueType : uint8_t Int32, UInt32, Array, - Struct + Struct, + Whatever }; class SensorValue @@ -34,8 +35,9 @@ class SensorValue SensorValue(float val) : _float{val}, _type{SensorValueType::Float} {} SensorValue(int32_t val) : _int32{val}, _type{SensorValueType::Int32} {} SensorValue(uint32_t val) : _uint32{val}, _type{SensorValueType::UInt32} {} - SensorValue(const SensorValueArray *val) : _array{val}, _type{SensorValueType::Array} {} - SensorValue(const SensorValueStruct *val) : _struct{val}, _type{SensorValueType::Struct} {} + explicit SensorValue(const SensorValueArray *val) : _array{val}, _type{SensorValueType::Array} {} + explicit SensorValue(const SensorValueStruct *val) : _struct{val}, _type{SensorValueType::Struct} {} + explicit SensorValue(const void *val) : _whatever{val}, _type{SensorValueType::Whatever} {} bool as_bool() const { return _type == SensorValueType::Bool ? _bool : false; } float as_float() const { return _type == SensorValueType::Float ? _float : 0.0f; } @@ -43,6 +45,7 @@ class SensorValue uint32_t as_uint32() const { return _type == SensorValueType::UInt32 ? _uint32 : 0U; } const SensorValueArray *as_array() const { return _type == SensorValueType::Array ? _array : nullptr; } const SensorValueStruct *as_struct() const { return _type == SensorValueType::Struct ? _struct : nullptr; } + const void *as_whatever() const { return _type == SensorValueType::Whatever ? _whatever : nullptr; } SensorValueType type() const { return _type; } @@ -52,8 +55,9 @@ class SensorValue operator float() const { return as_float(); } operator int32_t() const { return as_int32(); } operator uint32_t() const { return as_uint32(); } - operator const SensorValueArray *() const { return as_array(); } - operator const SensorValueStruct *() const { return as_struct(); } + explicit operator const SensorValueArray *() const { return as_array(); } + explicit operator const SensorValueStruct *() const { return as_struct(); } + explicit operator const void *() const { return as_whatever(); } private: friend class Sensor; @@ -65,6 +69,7 @@ class SensorValue uint32_t _uint32; const SensorValueArray *_array; const SensorValueStruct *_struct; + const void *_whatever; }; SensorValueType _type; @@ -87,7 +92,7 @@ struct SensorChannelProps }; template -using SensorChannelPropertiesArray = std::array; +using SensorChannelPropsArray = std::array; //-------------------------------------------------------------------------------------------------- @@ -102,7 +107,7 @@ class Sensor SensorValue getValue(uint8_t channelIndex = 0) { return do_getSensorValue(channelIndex); } - const SensorChannelProps &getProperties(uint8_t channelIndex = 0) { return do_getSensorProperties(channelIndex); } + const SensorChannelProps &getProps(uint8_t channelIndex = 0) { return do_getSensorProperties(channelIndex); } void accept(uint8_t channelIndex, SensorChannelVisitor &visitor); void accept(SensorChannelVisitor &visitor) { accept(0, visitor); } @@ -132,7 +137,7 @@ class SensorChannelProxy final : public Sensor private: bool do_isSensorReady() override { return _realSensor.isReady(); } SensorValue do_getSensorValue(uint8_t) override { return _realSensor.getValue(_channelIndex); } - const SensorChannelProps &do_getSensorProperties(uint8_t) override { return _realSensor.getProperties(_channelIndex); } + const SensorChannelProps &do_getSensorProperties(uint8_t) override { return _realSensor.getProps(_channelIndex); } Sensor &_realSensor; const uint8_t _channelIndex; @@ -147,8 +152,9 @@ class SensorValueVisitor virtual void visit(float val) {} virtual void visit(int32_t val) {} virtual void visit(uint32_t val) {} - virtual void visit(const SensorValueArray *val) {} - virtual void visit(const SensorValueStruct *val) {} + virtual void visit(const SensorValueArray &val) {} + virtual void visit(const SensorValueStruct &val) {} + virtual void visit(const void *val) {} }; class SensorChannelVisitor @@ -160,6 +166,115 @@ class SensorChannelVisitor virtual void visit(uint32_t val, const SensorChannelProps &props) {} virtual void visit(const SensorValueArray &val, const SensorChannelProps &props) {} virtual void visit(const SensorValueStruct &val, const SensorChannelProps &props) {} + virtual void visit(const void *val, const SensorChannelProps &props) {} +}; + +//-------------------------------------------------------------------------------------------------- +class Usermod; + +// A cursor to iterate over all available sensors. +class SensorCursor +{ +public: + using UmIterator = Usermod *const *; + + SensorCursor(UmIterator umBegin, UmIterator umEnd) : _umBegin{umBegin}, _umEnd{umEnd} { reset(); } + bool isValid() const { return _sensor != nullptr; } + Sensor &get() { return {*_sensor}; } + Sensor &operator*() { return *_sensor; } + Sensor *operator->() { return _sensor; } + bool next(); + void reset(); + +private: + UmIterator _umBegin; + UmIterator _umEnd; + UmIterator _umIter = nullptr; + Sensor *_sensor = nullptr; + uint8_t _sensorIndex = 0; +}; + +// Base class for cursors to iterate over specific channels of all sensors. +class SensorChannelCursor +{ +public: + bool isValid() const { return _sensorCursor.isValid(); } + SensorChannelProxy get() { return {*_sensorCursor, _channelIndex}; } + bool next(); + void reset(); + +protected: + ~SensorChannelCursor() = default; + explicit SensorChannelCursor(SensorCursor allSensors) + : _sensorCursor{allSensors} { reset(); } + + virtual bool matches(const SensorChannelProps &channelProps) = 0; + +private: + SensorCursor _sensorCursor; + uint8_t _channelIndex = 0; +}; + +// A cursor to iterate over all available channels of all sensors. +class AllSensorChannels final : public SensorChannelCursor +{ +public: + explicit AllSensorChannels(SensorCursor allSensors) + : SensorChannelCursor{allSensors} {} + +private: + bool matches(const SensorChannelProps &) override { return true; } +}; + +// A cursor to iterate over all available channels with a specific ValueType. +class SensorChannelsByType final : public SensorChannelCursor +{ +public: + SensorChannelsByType(SensorCursor allSensors, SensorValueType valueType) + : SensorChannelCursor{allSensors}, _type{valueType} {} + +private: + bool matches(const SensorChannelProps &props) override { return props.rangeMin.type() == _type; } + SensorValueType _type; +}; + +// A cursor to iterate over all available channels with a specific name. +class SensorChannelsByName final : public SensorChannelCursor +{ +public: + SensorChannelsByName(SensorCursor allSensors, const char *channelName) + : SensorChannelCursor{allSensors}, _name{channelName} {} + +private: + bool matches(const SensorChannelProps &props) override { return strcmp(props.channelName, _name) == 0; } + const char *_name; +}; + +class SensorList +{ +public: + SensorList(SensorCursor::UmIterator umBegin, SensorCursor::UmIterator umEnd) + : _umBegin{umBegin}, _umEnd{umEnd} {} + + SensorCursor getAllSensors() { return SensorCursor{_umBegin, _umEnd}; } + + Sensor *findSensorByName(const char *sensorName) + { + for (auto cursor = getAllSensors(); cursor.isValid(); cursor.next()) + if (strcmp(cursor->name(), sensorName) == 0) + return &cursor.get(); + return nullptr; + } + + AllSensorChannels getAllSensorChannels() { return AllSensorChannels{getAllSensors()}; } + + SensorChannelsByType getSensorChannelByType(SensorValueType valueType) { return {getAllSensors(), valueType}; } + + SensorChannelsByName getSensorChannelByName(const char *channelName) { return {getAllSensors(), channelName}; } + +private: + SensorCursor::UmIterator _umBegin; + SensorCursor::UmIterator _umEnd; }; //-------------------------------------------------------------------------------------------------- From 3171c651454d1e6729f9f5c912cf3eb9cc197128 Mon Sep 17 00:00:00 2001 From: Joachim Dick <62520542+JoaDick@users.noreply.github.com> Date: Sun, 1 Feb 2026 01:58:59 +0100 Subject: [PATCH 03/10] Sensor: first examples --- usermods/DHT/DHT.cpp | 23 +- .../Internal_Temperature_v2.cpp | 14 +- usermods/Temperature/Temperature.cpp | 8 +- usermods/Temperature/UsermodTemperature.h | 5 + usermods/UM_SensorDummy/README.md | 5 + usermods/UM_SensorDummy/UM_SensorDummy.cpp | 63 +++++ usermods/UM_SensorDummy/library.json | 4 + usermods/UM_SensorInfo/README.md | 5 + usermods/UM_SensorInfo/UM_SensorInfo.cpp | 232 ++++++++++++++++++ usermods/UM_SensorInfo/library.json | 4 + wled00/Sensor.h | 31 ++- wled00/fcn_declare.h | 1 + wled00/um_manager.cpp | 4 +- 13 files changed, 379 insertions(+), 20 deletions(-) create mode 100644 usermods/UM_SensorDummy/README.md create mode 100644 usermods/UM_SensorDummy/UM_SensorDummy.cpp create mode 100644 usermods/UM_SensorDummy/library.json create mode 100644 usermods/UM_SensorInfo/README.md create mode 100644 usermods/UM_SensorInfo/UM_SensorInfo.cpp create mode 100644 usermods/UM_SensorInfo/library.json diff --git a/usermods/DHT/DHT.cpp b/usermods/DHT/DHT.cpp index 2ed3dd0ace..47be99b035 100644 --- a/usermods/DHT/DHT.cpp +++ b/usermods/DHT/DHT.cpp @@ -57,13 +57,15 @@ DHT_nonblocking dht_sensor(DHTPIN, DHTTYPE); -class UsermodDHT : public Usermod { +class UsermodDHT : public Usermod, public Sensor { private: + static const char _name[]; unsigned long nextReadTime = 0; unsigned long lastReadTime = 0; - float humidity, temperature = 0; + float tempC = 0, humidity = 0, temperature = 0; bool initializing = true; bool disabled = false; + bool isSensorReady = false; #ifdef USERMOD_DHT_MQTT char dhtMqttTopic[64]; size_t dhtMqttTopicLen; @@ -79,6 +81,8 @@ class UsermodDHT : public Usermod { #endif public: + UsermodDHT() : Sensor{_name, 2} {} + void setup() { nextReadTime = millis() + USERMOD_DHT_FIRST_MEASUREMENT_AT; lastReadTime = millis(); @@ -112,13 +116,13 @@ class UsermodDHT : public Usermod { } #endif - float tempC; if (dht_sensor.measure(&tempC, &humidity)) { #ifdef USERMOD_DHT_CELSIUS temperature = tempC; #else temperature = tempC * 9 / 5 + 32; #endif + isSensorReady = true; #ifdef USERMOD_DHT_MQTT // 10^n where n is number of decimal places to display in mqtt message. Please adjust buff size together with this constant @@ -168,6 +172,7 @@ class UsermodDHT : public Usermod { if (((millis() - lastReadTime) > 10*USERMOD_DHT_MEASUREMENT_INTERVAL)) { disabled = true; + isSensorReady = false; } } @@ -242,8 +247,18 @@ class UsermodDHT : public Usermod { return USERMOD_ID_DHT; } + uint8_t getSensorCount() override { return 1; } + Sensor *getSensor(uint8_t) override { return this; } + + private: + bool do_isSensorReady() override { return isSensorReady; } + SensorValue do_getSensorChannelValue(uint8_t index) override { return index == 0 ? humidity : tempC; } + const SensorChannelProps &do_getSensorChannelProperties(uint8_t index) override { return _channelProps[index]; } + const SensorChannelPropsArray<2> _channelProps = { makeChannelProps_Humidity(), makeChannelProps_Temperature() }; }; +const char UsermodDHT::_name[] PROGMEM = "DHT"; + static UsermodDHT dht; -REGISTER_USERMOD(dht); \ No newline at end of file +REGISTER_USERMOD(dht); diff --git a/usermods/Internal_Temperature_v2/Internal_Temperature_v2.cpp b/usermods/Internal_Temperature_v2/Internal_Temperature_v2.cpp index 7c30985eea..d8a52934b1 100644 --- a/usermods/Internal_Temperature_v2/Internal_Temperature_v2.cpp +++ b/usermods/Internal_Temperature_v2/Internal_Temperature_v2.cpp @@ -1,6 +1,6 @@ #include "wled.h" -class InternalTemperatureUsermod : public Usermod +class InternalTemperatureUsermod : public Usermod, public Sensor { private: @@ -22,10 +22,17 @@ class InternalTemperatureUsermod : public Usermod static const char _activationThreshold[]; static const char _presetToActivate[]; + const SensorChannelProps sensorProps = makeChannelProps_Temperature(0.0f, 80.0f); + bool do_isSensorReady() override { return isEnabled && temperature >= 0.0f; } + SensorValue do_getSensorChannelValue(uint8_t) override { return temperature; } + const SensorChannelProps &do_getSensorChannelProperties(uint8_t) override { return sensorProps; } + // any private methods should go here (non-inline method should be defined out of class) void publishMqtt(const char *state, bool retain = false); // example for publishing MQTT message public: + InternalTemperatureUsermod() : Sensor{"CPU", 1} {} + void setup() { } @@ -171,6 +178,9 @@ class InternalTemperatureUsermod : public Usermod { return USERMOD_ID_INTERNAL_TEMPERATURE; } + + uint8_t getSensorCount() override { return 1; } + Sensor *getSensor(uint8_t) override { return this; } }; const char InternalTemperatureUsermod::_name[] PROGMEM = "Internal Temperature"; @@ -194,4 +204,4 @@ void InternalTemperatureUsermod::publishMqtt(const char *state, bool retain) } static InternalTemperatureUsermod internal_temperature_v2; -REGISTER_USERMOD(internal_temperature_v2); \ No newline at end of file +REGISTER_USERMOD(internal_temperature_v2); diff --git a/usermods/Temperature/Temperature.cpp b/usermods/Temperature/Temperature.cpp index c86b9e9842..691feb3cc8 100644 --- a/usermods/Temperature/Temperature.cpp +++ b/usermods/Temperature/Temperature.cpp @@ -165,11 +165,15 @@ void UsermodTemperature::loop() { if (now - lastTemperaturesRequest >= 750 /* 93.75ms per the datasheet but can be up to 750ms */) { readTemperature(); if (getTemperatureC() < -100.0f) { - if (++errorCount > 10) sensorFound = 0; + if (++errorCount > 10) { + sensorFound = 0; + temperatureSensor.suspend(); + } lastMeasurement = now - readingInterval + 300; // force new measurement in 300ms return; } errorCount = 0; + temperatureSensor = temperature; #ifndef WLED_DISABLE_MQTT if (WLED_MQTT_CONNECTED) { @@ -380,4 +384,4 @@ static uint16_t mode_temperature() { static UsermodTemperature temperature; -REGISTER_USERMOD(temperature); \ No newline at end of file +REGISTER_USERMOD(temperature); diff --git a/usermods/Temperature/UsermodTemperature.h b/usermods/Temperature/UsermodTemperature.h index 555b57cf7a..1e92a3b3ca 100644 --- a/usermods/Temperature/UsermodTemperature.h +++ b/usermods/Temperature/UsermodTemperature.h @@ -50,6 +50,8 @@ class UsermodTemperature : public Usermod { int16_t idx = -1; // Domoticz virtual sensor idx uint8_t resolution = 0; // 9bits=0, 10bits=1, 11bits=2, 12bits=3 + EasySensor temperatureSensor{_name, makeChannelProps_Temperature()}; + // strings to reduce flash memory usage (used more than twice) static const char _name[]; static const char _enabled[]; @@ -106,5 +108,8 @@ class UsermodTemperature : public Usermod { bool readFromConfig(JsonObject &root) override; void appendConfigData() override; + + uint8_t getSensorCount() override { return 1; } + Sensor *getSensor(uint8_t) override { return &temperatureSensor; } }; diff --git a/usermods/UM_SensorDummy/README.md b/usermods/UM_SensorDummy/README.md new file mode 100644 index 0000000000..8c61c5dd94 --- /dev/null +++ b/usermods/UM_SensorDummy/README.md @@ -0,0 +1,5 @@ +# Dummy usermod to simulate random sensor readings. + +Use `UM_SensorInfo` and `UM_SensorDummy` together as an example for how sensors are implemented, +and how its data can be retrieved. The generated sensor data can be processed by effects and other +usermods - without directly knowing the sensor and its specific type of data. diff --git a/usermods/UM_SensorDummy/UM_SensorDummy.cpp b/usermods/UM_SensorDummy/UM_SensorDummy.cpp new file mode 100644 index 0000000000..d3e991d7e6 --- /dev/null +++ b/usermods/UM_SensorDummy/UM_SensorDummy.cpp @@ -0,0 +1,63 @@ +/** + * (c) 2026 Joachim Dick + * Licensed under the EUPL v. 1.2 or later + */ + +#include "wled.h" + +//-------------------------------------------------------------------------------------------------- + +/** Dummy usermod implementation that simulates random sensor readings. + */ +class UM_SensorDummy : public Usermod +{ + + // ----- usermod functions ----- + + void setup() override {} + + void loop() override + { + const auto now = millis(); + if (now < _nextUpdateTime) + return; + + _temperatureSensor = readTemperature(); + _humiditySensor = readHumidity(); + + _nextUpdateTime = now + 20; // 50 sensor updates per second + } + + uint8_t getSensorCount() override { return 2; } + + Sensor *getSensor(uint8_t index) override { return index == 0 ? &_temperatureSensor : &_humiditySensor; } + + // ----- internal processing functions ----- + + /// The dummy implementation to simulate temperature values (based on perlin noise). + float readTemperature() + { + const int32_t raw = perlin16(strip.now * 8) - 0x8000; + // simulate some random temperature around 20°C + return 20.0f + raw / 65535.0f * 30.0f; + } + + /// The dummy implementation to simulate humidity values (a sine wave). + float readHumidity() + { + const int32_t raw = beatsin16_t(1); + // simulate some random humidity between 10% and 90% + return 10.0f + raw / 65535.0f * 80.0f; + } + + // ----- member variables ----- + + uint32_t _nextUpdateTime = 0; + EasySensor _temperatureSensor{"Dummy-Temp", makeChannelProps_Temperature()}; + EasySensor _humiditySensor{"Dummy-Hum", makeChannelProps_Humidity()}; +}; + +//-------------------------------------------------------------------------------------------------- + +static UM_SensorDummy um_SensorDummy; +REGISTER_USERMOD(um_SensorDummy); diff --git a/usermods/UM_SensorDummy/library.json b/usermods/UM_SensorDummy/library.json new file mode 100644 index 0000000000..ae97d90ecf --- /dev/null +++ b/usermods/UM_SensorDummy/library.json @@ -0,0 +1,4 @@ +{ + "name": "UM_SensorDummy", + "build": { "libArchive": false } +} diff --git a/usermods/UM_SensorInfo/README.md b/usermods/UM_SensorInfo/README.md new file mode 100644 index 0000000000..db57c1d1f1 --- /dev/null +++ b/usermods/UM_SensorInfo/README.md @@ -0,0 +1,5 @@ +# Examples for working with sensors. + +Use `UM_SensorInfo` and `UM_SensorDummy` together as an example for how sensors are implemented, +and how its data can be retrieved. The generated sensor data can be processed by effects and other +usermods - without directly knowing the sensor and its specific type of data. diff --git a/usermods/UM_SensorInfo/UM_SensorInfo.cpp b/usermods/UM_SensorInfo/UM_SensorInfo.cpp new file mode 100644 index 0000000000..e42744d0cd --- /dev/null +++ b/usermods/UM_SensorInfo/UM_SensorInfo.cpp @@ -0,0 +1,232 @@ +/** + * (c) 2026 Joachim Dick + * Licensed under the EUPL v. 1.2 or later + */ + +#include "wled.h" + +//-------------------------------------------------------------------------------------------------- + +namespace +{ + + void drawLine(int pos, int length, uint32_t color) + { + const int end = pos + length; + while (pos < end) + SEGMENT.setPixelColor(pos++, color); + } + + class SensorChannelVisualizer + { + public: + const uint8_t offset = 3; + const uint8_t size = 15; + const uint8_t space = 3; + + void showInfo(Sensor &sensor) + { + _startPos = offset + _sensorCounter * (size + space); + + showReadyState(sensor); + showChannelInfo(sensor); + + ++_sensorCounter; + } + + private: + void showReadyState(Sensor &sensor) + { + drawLine(_startPos, size, sensor.isReady() ? 0x002200 : 0x220000); + } + + void showChannelInfo(Sensor &sensor) + { + int pos = _startPos; + const uint8_t channelCount = sensor.channelCount(); + for (uint8_t ch = 0; ch < MIN(channelCount, size / 3); ++ch) + { + SensorChannelProxy channel = sensor.channel(ch); + const auto color = getChannelColor(channel); + SEGMENT.setPixelColor(pos, color); + pos += 2; + } + } + + static uint32_t getChannelColor(SensorChannelProxy &channel) + { + return channel.isReady() ? 0x008844 : 0x880044; + } + + private: + uint8_t _sensorCounter = 0; + int _startPos; + }; + +} + +//-------------------------------------------------------------------------------------------------- + +uint16_t mode_SensorInfo() +{ + SEGMENT.clear(); + + SensorChannelVisualizer visualizer; + + auto sensorList = UsermodManager::getSensors(); + for (auto cursor = sensorList.getAllSensors(); + cursor.isValid(); + cursor.next()) + { + visualizer.showInfo(cursor.get()); + } + + return FRAMETIME; +} +static const char _data_FX_MODE_SENSOR_INFO[] PROGMEM = "! SensorInfo"; + +//-------------------------------------------------------------------------------------------------- + +#if (0) + +uint16_t mode_NumberDumper() +{ + SEGMENT.clear(); + +#if (0) + auto sensorList = UsermodManager::getSensors(); + int counter = 0; + for (auto cursor = sensorList.getSensorChannelsByType(SensorValueType::Float); + cursor.isValid(); + cursor.next()) + { + auto sensor = cursor.get(); + const bool isReady = sensor.isReady(); + SEGMENT.setPixelColor(counter * 10 + 5, isReady ? 0x008800 : 0x880000); + + if (isReady) + continue; + const float sensorValue = sensor.getValue(); + const float sensorValueMax = sensor.getProps().rangeMax; + if (sensorValueMax > 0.0f) + { + const int pos = sensorValue * (SEGLEN - 1) / sensorValueMax; + SEGMENT.setPixelColor(pos, 0x000088); + // SEGMENT.setPixelColor(pos, SEGCOLOR(0)); + // SEGMENT.setPixelColor(pos, fast_color_scale(SEGCOLOR(0), 64)); + } + + ++counter; + } +#endif + +#if (1) + auto sensorList = UsermodManager::getSensors(); + int counter = 0; + for (auto cursor = sensorList.getAllSensors(); + cursor.isValid(); + cursor.next()) + { + const int basePos = 5 + 25 * counter++; + + auto &sensor = cursor.get(); + const bool isReady = sensor.isReady(); + SEGMENT.setPixelColor(basePos, isReady ? 0x004400 : 0x440000); + + int baseOffset = 2; + const uint8_t channelCount = sensor.channelCount(); + for (uint8_t ch = 0; ch < channelCount; ++ch) + { + SEGMENT.setPixelColor(basePos + baseOffset + ch * baseOffset, 0x440044); + } + } +#endif + + return FRAMETIME; +} +static const char _data_FX_MODE_EX_NUMBER_DUMPER[] PROGMEM = "! SensorNumberDumper"; +// static const char _data_FX_MODE_EX_NUMBER_DUMPER[] PROGMEM = "Ex: NumberDumper@,,,,,,Humidity Info,Temperature Info;!;;o2=1,o3=1"; + +#endif + +//-------------------------------------------------------------------------------------------------- + +class UM_SensorInfo : public Usermod +{ + void setup() override + { + strip.addEffect(255, &mode_SensorInfo, _data_FX_MODE_SENSOR_INFO); + // strip.addEffect(255, &mode_NumberDumper, _data_FX_MODE_EX_NUMBER_DUMPER); + } + + void loop() override {} + + void addToJsonInfo(JsonObject &root) + { + JsonObject user = root["u"]; + if (user.isNull()) + user = root.createNestedObject("u"); + + int sensorIndex = 0; + auto sensorList = UsermodManager::getSensors(); + for (auto cursor = sensorList.getAllSensors(); cursor.isValid(); cursor.next()) + { + Sensor &sensor = cursor.get(); + const bool isSensorReady = sensor.isReady(); + const int channelCount = sensor.channelCount(); + + String sensorName; + sensorName += sensorIndex; + sensorName += "._ "; + sensorName += sensor.name(); + String sensorChannels; + if (!isSensorReady) + sensorChannels += "[OFFLINE] - "; + sensorChannels += channelCount; + sensorChannels += " channel"; + if (channelCount > 1) + sensorChannels += 's'; + user.createNestedArray(sensorName).add(sensorChannels); + + for (int channelIndex = 0; channelIndex < channelCount; ++channelIndex) + { + SensorChannelProxy channel = sensor.channel(channelIndex); + const SensorChannelProps &channelProps = channel.getProps(channelIndex); + String key; + key += sensorIndex; + key += "."; + key += channelIndex; + key += " "; + key += channelProps.channelName; + + const bool isChannelReady = sensor.isReady(channelIndex); + String val; + if (channelProps.channelNameShort[0] != '\0') + { + val += channelProps.channelNameShort; + val += " = "; + } + if (isChannelReady) + { + const SensorValue sensorValue = sensor.getValue(channelIndex); + val += sensorValue.as_float(); + } + else + { + val += "[n/a]"; + } + val += " "; + val += channelProps.unitString; + + user.createNestedArray(key).add(val); + } + + ++sensorIndex; + } + } +}; + +//-------------------------------------------------------------------------------------------------- + +static UM_SensorInfo um_SensorInfo; +REGISTER_USERMOD(um_SensorInfo); diff --git a/usermods/UM_SensorInfo/library.json b/usermods/UM_SensorInfo/library.json new file mode 100644 index 0000000000..8d54854459 --- /dev/null +++ b/usermods/UM_SensorInfo/library.json @@ -0,0 +1,4 @@ +{ + "name": "UM_SensorInfo", + "build": { "libArchive": false } +} diff --git a/wled00/Sensor.h b/wled00/Sensor.h index 61c9b494b6..a520567b00 100644 --- a/wled00/Sensor.h +++ b/wled00/Sensor.h @@ -95,19 +95,24 @@ template using SensorChannelPropsArray = std::array; //-------------------------------------------------------------------------------------------------- +class SensorChannelProxy; class Sensor { public: const char *name() const { return _sensorName; } + bool isReady() { return do_isSensorReady(); } + uint8_t channelCount() const { return _channelCount; } - bool isReady() { return do_isSensorReady(); } + SensorChannelProxy channel(uint8_t channelIndex); - SensorValue getValue(uint8_t channelIndex = 0) { return do_getSensorValue(channelIndex); } + bool isReady(uint8_t channelIndex) { return do_isSensorReady() ? do_isSensorChannelReady(channelIndex) : false; } - const SensorChannelProps &getProps(uint8_t channelIndex = 0) { return do_getSensorProperties(channelIndex); } + SensorValue getValue(uint8_t channelIndex = 0) { return do_getSensorChannelValue(channelIndex); } + + const SensorChannelProps &getProps(uint8_t channelIndex = 0) { return do_getSensorChannelProperties(channelIndex); } void accept(uint8_t channelIndex, SensorChannelVisitor &visitor); void accept(SensorChannelVisitor &visitor) { accept(0, visitor); } @@ -117,8 +122,9 @@ class Sensor : _sensorName{sensorName}, _channelCount{channelCount} {} virtual bool do_isSensorReady() = 0; - virtual SensorValue do_getSensorValue(uint8_t channelIndex) = 0; - virtual const SensorChannelProps &do_getSensorProperties(uint8_t channelIndex) = 0; + virtual bool do_isSensorChannelReady(uint8_t channelIndex) { return true; }; + virtual SensorValue do_getSensorChannelValue(uint8_t channelIndex) = 0; + virtual const SensorChannelProps &do_getSensorChannelProperties(uint8_t channelIndex) = 0; private: const char *_sensorName; @@ -136,13 +142,16 @@ class SensorChannelProxy final : public Sensor private: bool do_isSensorReady() override { return _realSensor.isReady(); } - SensorValue do_getSensorValue(uint8_t) override { return _realSensor.getValue(_channelIndex); } - const SensorChannelProps &do_getSensorProperties(uint8_t) override { return _realSensor.getProps(_channelIndex); } + bool do_isSensorChannelReady(uint8_t) override { return _realSensor.isReady(_channelIndex); } + SensorValue do_getSensorChannelValue(uint8_t) override { return _realSensor.getValue(_channelIndex); } + const SensorChannelProps &do_getSensorChannelProperties(uint8_t) override { return _realSensor.getProps(_channelIndex); } Sensor &_realSensor; const uint8_t _channelIndex; }; +inline SensorChannelProxy Sensor::channel(uint8_t channelIndex) { return SensorChannelProxy{*this, channelIndex}; } + //-------------------------------------------------------------------------------------------------- class SensorValueVisitor @@ -268,9 +277,9 @@ class SensorList AllSensorChannels getAllSensorChannels() { return AllSensorChannels{getAllSensors()}; } - SensorChannelsByType getSensorChannelByType(SensorValueType valueType) { return {getAllSensors(), valueType}; } + SensorChannelsByType getSensorChannelsByType(SensorValueType valueType) { return {getAllSensors(), valueType}; } - SensorChannelsByName getSensorChannelByName(const char *channelName) { return {getAllSensors(), channelName}; } + SensorChannelsByName getSensorChannelsByName(const char *channelName) { return {getAllSensors(), channelName}; } private: SensorCursor::UmIterator _umBegin; @@ -302,8 +311,8 @@ class EasySensor : public Sensor private: bool do_isSensorReady() override { return _isReady; } - SensorValue do_getSensorValue(uint8_t) override { return _val; } - const SensorChannelProps &do_getSensorProperties(uint8_t) override { return _props; } + SensorValue do_getSensorChannelValue(uint8_t) override { return _val; } + const SensorChannelProps &do_getSensorChannelProperties(uint8_t) override { return _props; } private: const SensorChannelProps _props; diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index b1f7131eb3..df36f6e8fc 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -367,6 +367,7 @@ namespace UsermodManager { void onStateChange(uint8_t); Usermod* lookup(uint16_t mod_id); size_t getModCount(); + SensorList getSensors(); }; // Register usermods by building a static list via a linker section diff --git a/wled00/um_manager.cpp b/wled00/um_manager.cpp index 647757ad6f..57ca4d85ce 100644 --- a/wled00/um_manager.cpp +++ b/wled00/um_manager.cpp @@ -40,7 +40,7 @@ bool UsermodManager::getUMData(um_data_t **data, uint8_t mod_id) { } void UsermodManager::addToJsonState(JsonObject& obj) { for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) (*mod)->addToJsonState(obj); } void UsermodManager::addToJsonInfo(JsonObject& obj) { - auto um_id_list = obj.createNestedArray("um"); + auto um_id_list = obj.createNestedArray("um"); for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) { um_id_list.add((*mod)->getId()); (*mod)->addToJsonInfo(obj); @@ -98,3 +98,5 @@ void Usermod::appendConfigData(Print& settingsScript) { this->appendConfigData(); oappend_shim = nullptr; } + +SensorList UsermodManager::getSensors() { return SensorList{_usermod_table_begin, _usermod_table_end}; } From 04b7bf6c4950e702f8991f4edb5660ecce925617 Mon Sep 17 00:00:00 2001 From: Joachim Dick <62520542+JoaDick@users.noreply.github.com> Date: Sun, 1 Feb 2026 16:32:27 +0100 Subject: [PATCH 04/10] Sensor: concept of "channel quantity" (measurand) --- .../Internal_Temperature_v2.cpp | 2 +- usermods/Temperature/UsermodTemperature.h | 2 +- usermods/UM_SensorDummy/UM_SensorDummy.cpp | 4 +- usermods/UM_SensorInfo/UM_SensorInfo.cpp | 11 ++- wled00/Sensor.h | 71 +++++++++++++------ 5 files changed, 56 insertions(+), 34 deletions(-) diff --git a/usermods/Internal_Temperature_v2/Internal_Temperature_v2.cpp b/usermods/Internal_Temperature_v2/Internal_Temperature_v2.cpp index d8a52934b1..d90e73e743 100644 --- a/usermods/Internal_Temperature_v2/Internal_Temperature_v2.cpp +++ b/usermods/Internal_Temperature_v2/Internal_Temperature_v2.cpp @@ -22,7 +22,7 @@ class InternalTemperatureUsermod : public Usermod, public Sensor static const char _activationThreshold[]; static const char _presetToActivate[]; - const SensorChannelProps sensorProps = makeChannelProps_Temperature(0.0f, 80.0f); + const SensorChannelProps sensorProps = makeChannelProps_Temperature("on-chip", 0.0f, 80.0f); bool do_isSensorReady() override { return isEnabled && temperature >= 0.0f; } SensorValue do_getSensorChannelValue(uint8_t) override { return temperature; } const SensorChannelProps &do_getSensorChannelProperties(uint8_t) override { return sensorProps; } diff --git a/usermods/Temperature/UsermodTemperature.h b/usermods/Temperature/UsermodTemperature.h index 1e92a3b3ca..313f794592 100644 --- a/usermods/Temperature/UsermodTemperature.h +++ b/usermods/Temperature/UsermodTemperature.h @@ -50,7 +50,7 @@ class UsermodTemperature : public Usermod { int16_t idx = -1; // Domoticz virtual sensor idx uint8_t resolution = 0; // 9bits=0, 10bits=1, 11bits=2, 12bits=3 - EasySensor temperatureSensor{_name, makeChannelProps_Temperature()}; + EasySensor temperatureSensor{_name, makeChannelProps_Temperature("Probe_0")}; // strings to reduce flash memory usage (used more than twice) static const char _name[]; diff --git a/usermods/UM_SensorDummy/UM_SensorDummy.cpp b/usermods/UM_SensorDummy/UM_SensorDummy.cpp index d3e991d7e6..318ab8ebe8 100644 --- a/usermods/UM_SensorDummy/UM_SensorDummy.cpp +++ b/usermods/UM_SensorDummy/UM_SensorDummy.cpp @@ -53,8 +53,8 @@ class UM_SensorDummy : public Usermod // ----- member variables ----- uint32_t _nextUpdateTime = 0; - EasySensor _temperatureSensor{"Dummy-Temp", makeChannelProps_Temperature()}; - EasySensor _humiditySensor{"Dummy-Hum", makeChannelProps_Humidity()}; + EasySensor _temperatureSensor{"Dummy-Temp", makeChannelProps_Temperature("perlin")}; + EasySensor _humiditySensor{"Dummy-Hum", makeChannelProps_Humidity("beatsin")}; }; //-------------------------------------------------------------------------------------------------- diff --git a/usermods/UM_SensorInfo/UM_SensorInfo.cpp b/usermods/UM_SensorInfo/UM_SensorInfo.cpp index e42744d0cd..eeb59b6873 100644 --- a/usermods/UM_SensorInfo/UM_SensorInfo.cpp +++ b/usermods/UM_SensorInfo/UM_SensorInfo.cpp @@ -197,15 +197,12 @@ class UM_SensorInfo : public Usermod key += "."; key += channelIndex; key += " "; - key += channelProps.channelName; + key += channelProps.name; const bool isChannelReady = sensor.isReady(channelIndex); String val; - if (channelProps.channelNameShort[0] != '\0') - { - val += channelProps.channelNameShort; - val += " = "; - } + val += channelProps.quantity.name; + val += "
"; if (isChannelReady) { const SensorValue sensorValue = sensor.getValue(channelIndex); @@ -216,7 +213,7 @@ class UM_SensorInfo : public Usermod val += "[n/a]"; } val += " "; - val += channelProps.unitString; + val += channelProps.quantity.unit; user.createNestedArray(key).add(val); } diff --git a/wled00/Sensor.h b/wled00/Sensor.h index a520567b00..5913025e2d 100644 --- a/wled00/Sensor.h +++ b/wled00/Sensor.h @@ -9,13 +9,13 @@ //-------------------------------------------------------------------------------------------------- -class SensorValueArray; -class SensorValueStruct; +class SensorValueArray; // TODO(feature) Not implemented yet! +class SensorValueStruct; // TODO(feature) Not implemented yet! -class SensorChannelVisitor; class SensorValueVisitor; -class SensorValueArrayVisitor; -class SensorValueStructVisitor; +class SensorValueArrayVisitor; // TODO(feature) Not implemented yet! +class SensorValueStructVisitor; // TODO(feature) Not implemented yet! +class SensorChannelVisitor; enum class SensorValueType : uint8_t { @@ -75,20 +75,37 @@ class SensorValue SensorValueType _type; }; +struct SensorQuantity +{ + const char *const name; + const char *const unit; + + static SensorQuantity Temperature() { return {"Temperature", "°C"}; } + static SensorQuantity Humidity() { return {"Humidity", "%rel"}; } + static SensorQuantity AirPressure() { return {"AirPressure", "hPa"}; } + + static SensorQuantity Voltage() { return {"Voltage", "V"}; } + static SensorQuantity Current() { return {"Current", "A"}; } + static SensorQuantity Power() { return {"Power", "W"}; } + static SensorQuantity Energy() { return {"Energy", "kWh"}; } + + static SensorQuantity Angle() { return {"Angle", "°"}; } + + static SensorQuantity Percent() { return {"Percent", "%"}; } +}; + struct SensorChannelProps { SensorChannelProps(const char *channelName_, - const char *unitString_, + SensorQuantity quantity_, SensorValue rangeMin_, - SensorValue rangeMax_, - const char *channelNameShort_ = "") - : channelName{channelName_}, unitString{unitString_}, rangeMin{rangeMin_}, rangeMax{rangeMax_}, channelNameShort{channelNameShort_} {} - - const char *channelName; - const char *unitString; - SensorValue rangeMin; - SensorValue rangeMax; - const char *channelNameShort; + SensorValue rangeMax_) + : name{channelName_}, quantity{quantity_}, rangeMin{rangeMin_}, rangeMax{rangeMax_} {} + + const char *const name; + const SensorQuantity quantity; + const SensorValue rangeMin; + const SensorValue rangeMax; }; template @@ -255,7 +272,7 @@ class SensorChannelsByName final : public SensorChannelCursor : SensorChannelCursor{allSensors}, _name{channelName} {} private: - bool matches(const SensorChannelProps &props) override { return strcmp(props.channelName, _name) == 0; } + bool matches(const SensorChannelProps &props) override { return strcmp(props.name, _name) == 0; } const char *_name; }; @@ -322,22 +339,30 @@ class EasySensor : public Sensor //-------------------------------------------------------------------------------------------------- -inline SensorChannelProps makeChannelProps_Bool(const char *channelName, - const char *channelNameShort = "", - const char *unitString = "") +inline SensorChannelProps makeChannelProps_Bool(const char *quantityName, + const char *channelName = nullptr) +{ + return {channelName ? channelName : quantityName, {quantityName, ""}, false, true}; +} + +inline SensorChannelProps makeChannelProps_Temperature(const char *channelName, + float rangeMin = 0.0f, + float rangeMax = 40.0f) { - return {channelName, unitString, false, true, channelNameShort}; + const auto quantity = SensorQuantity::Temperature(); + return {channelName ? channelName : quantity.name, quantity, rangeMin, rangeMax}; } inline SensorChannelProps makeChannelProps_Temperature(float rangeMin = 0.0f, float rangeMax = 40.0f) { - return {"Temperature", "°C", rangeMin, rangeMax, "Temp"}; + return makeChannelProps_Temperature(nullptr, rangeMin, rangeMax); } -inline SensorChannelProps makeChannelProps_Humidity() +inline SensorChannelProps makeChannelProps_Humidity(const char *channelName = nullptr) { - return {"Humidity", "%rel", 0.0f, 100.0f, "Hum"}; + const auto quantity = SensorQuantity::Humidity(); + return {channelName ? channelName : quantity.name, quantity, 0.0f, 100.0f}; } //-------------------------------------------------------------------------------------------------- From bfea4dc030245b1ec9ad1ab0750dd3092463f575 Mon Sep 17 00:00:00 2001 From: Joachim Dick <62520542+JoaDick@users.noreply.github.com> Date: Sun, 1 Feb 2026 18:35:55 +0100 Subject: [PATCH 05/10] Sensor: helper class EasySensorArray --- usermods/Temperature/UsermodTemperature.h | 2 +- usermods/UM_SensorDummy/UM_SensorDummy.cpp | 68 ++++++++++++-- usermods/UM_SensorInfo/UM_SensorInfo.cpp | 4 +- wled00/Sensor.h | 102 +++++++++++++++++++-- 4 files changed, 157 insertions(+), 19 deletions(-) diff --git a/usermods/Temperature/UsermodTemperature.h b/usermods/Temperature/UsermodTemperature.h index 313f794592..1e92a3b3ca 100644 --- a/usermods/Temperature/UsermodTemperature.h +++ b/usermods/Temperature/UsermodTemperature.h @@ -50,7 +50,7 @@ class UsermodTemperature : public Usermod { int16_t idx = -1; // Domoticz virtual sensor idx uint8_t resolution = 0; // 9bits=0, 10bits=1, 11bits=2, 12bits=3 - EasySensor temperatureSensor{_name, makeChannelProps_Temperature("Probe_0")}; + EasySensor temperatureSensor{_name, makeChannelProps_Temperature()}; // strings to reduce flash memory usage (used more than twice) static const char _name[]; diff --git a/usermods/UM_SensorDummy/UM_SensorDummy.cpp b/usermods/UM_SensorDummy/UM_SensorDummy.cpp index 318ab8ebe8..3115d07e85 100644 --- a/usermods/UM_SensorDummy/UM_SensorDummy.cpp +++ b/usermods/UM_SensorDummy/UM_SensorDummy.cpp @@ -9,8 +9,10 @@ /** Dummy usermod implementation that simulates random sensor readings. */ -class UM_SensorDummy : public Usermod +class UM_SensorDummy : public Usermod, public Sensor { +public: + UM_SensorDummy() : Sensor{"SEF", 4} {} // ----- usermod functions ----- @@ -21,19 +23,36 @@ class UM_SensorDummy : public Usermod const auto now = millis(); if (now < _nextUpdateTime) return; - - _temperatureSensor = readTemperature(); - _humiditySensor = readHumidity(); - + readWeatherStation(); _nextUpdateTime = now + 20; // 50 sensor updates per second } uint8_t getSensorCount() override { return 2; } - Sensor *getSensor(uint8_t index) override { return index == 0 ? &_temperatureSensor : &_humiditySensor; } + Sensor *getSensor(uint8_t index) override + { + if (index != 0) + return this; + return &_sensorArray; + } + + bool do_isSensorReady() override { return true; } + SensorValue do_getSensorChannelValue(uint8_t channelIndex) override { return readSEF(channelIndex); } + const SensorChannelProps &do_getSensorChannelProperties(uint8_t channelIndex) override { return _localSensorProps[channelIndex]; } // ----- internal processing functions ----- + void readWeatherStation() + { + _sensorArray.set(0, 1012.34f); + _sensorArray.set(1, readTemperature()); + _sensorArray.set(2, readHumidity()); +#if (0) // Battery is empty :-( + _sensorArray.set(3, readTemperature() - 3.0f); + _sensorArray.set(4, readHumidity() + 5.0f); +#endif + } + /// The dummy implementation to simulate temperature values (based on perlin noise). float readTemperature() { @@ -50,11 +69,44 @@ class UM_SensorDummy : public Usermod return 10.0f + raw / 65535.0f * 80.0f; } + float readSEF(uint8_t index) + { + int32_t raw = 0; + switch (index) + { + case 0: + raw = beatsin16_t(6, 0, 0xFFFF, 0, 0); + break; + case 1: + raw = beatsin16_t(6, 0, 0xFFFF, 0, 0xFFFF / 3); + break; + case 2: + raw = beatsin16_t(6, 0, 0xFFFF, 0, 2 * 0xFFFF / 3); + break; + + default: + return -123.45f; + } + return 180.0f + raw / 65535.0f * 90.0f; + } + // ----- member variables ----- uint32_t _nextUpdateTime = 0; - EasySensor _temperatureSensor{"Dummy-Temp", makeChannelProps_Temperature("perlin")}; - EasySensor _humiditySensor{"Dummy-Hum", makeChannelProps_Humidity("beatsin")}; + + const SensorChannelPropsArray<4> _localSensorProps = + {{makeChannelProps_Float("deltaX", {"offset", "°rad"}, 0.0f, 360.0f), + makeChannelProps_Float("deltaY", {"offset", "°rad"}, 0.0f, 360.0f), + makeChannelProps_Float("deltaZ", {"offset", "°rad"}, 0.0f, 360.0f), + makeChannelProps_Float("deltaT", {"jitter", "µs"}, -1000.0f, 1000.0f)}}; + + EasySensorArray<5> _sensorArray{"Weather Station", + {{SensorChannelProps{"Barometer", + SensorQuantity::AirPressure(), 950.0f, 1050.0f}, + makeChannelProps_Temperature("Indoor Temp."), + makeChannelProps_Humidity("Indoor Hum."), + makeChannelProps_Temperature("Outdoor Temp."), + makeChannelProps_Humidity("Outdoor Hum.")}}}; }; //-------------------------------------------------------------------------------------------------- diff --git a/usermods/UM_SensorInfo/UM_SensorInfo.cpp b/usermods/UM_SensorInfo/UM_SensorInfo.cpp index eeb59b6873..ef00cef130 100644 --- a/usermods/UM_SensorInfo/UM_SensorInfo.cpp +++ b/usermods/UM_SensorInfo/UM_SensorInfo.cpp @@ -21,7 +21,7 @@ namespace { public: const uint8_t offset = 3; - const uint8_t size = 15; + const uint8_t size = 16; const uint8_t space = 3; void showInfo(Sensor &sensor) @@ -44,7 +44,7 @@ namespace { int pos = _startPos; const uint8_t channelCount = sensor.channelCount(); - for (uint8_t ch = 0; ch < MIN(channelCount, size / 3); ++ch) + for (uint8_t ch = 0; ch < MIN(channelCount, size / 2); ++ch) { SensorChannelProxy channel = sensor.channel(ch); const auto color = getChannelColor(channel); diff --git a/wled00/Sensor.h b/wled00/Sensor.h index 5913025e2d..1f1c82f879 100644 --- a/wled00/Sensor.h +++ b/wled00/Sensor.h @@ -37,7 +37,7 @@ class SensorValue SensorValue(uint32_t val) : _uint32{val}, _type{SensorValueType::UInt32} {} explicit SensorValue(const SensorValueArray *val) : _array{val}, _type{SensorValueType::Array} {} explicit SensorValue(const SensorValueStruct *val) : _struct{val}, _type{SensorValueType::Struct} {} - explicit SensorValue(const void *val) : _whatever{val}, _type{SensorValueType::Whatever} {} + explicit SensorValue(const void *val = nullptr) : _whatever{val}, _type{SensorValueType::Whatever} {} bool as_bool() const { return _type == SensorValueType::Bool ? _bool : false; } float as_float() const { return _type == SensorValueType::Float ? _float : 0.0f; } @@ -82,7 +82,7 @@ struct SensorQuantity static SensorQuantity Temperature() { return {"Temperature", "°C"}; } static SensorQuantity Humidity() { return {"Humidity", "%rel"}; } - static SensorQuantity AirPressure() { return {"AirPressure", "hPa"}; } + static SensorQuantity AirPressure() { return {"Air Pressure", "hPa"}; } static SensorQuantity Voltage() { return {"Voltage", "V"}; } static SensorQuantity Current() { return {"Current", "A"}; } @@ -158,7 +158,7 @@ class SensorChannelProxy final : public Sensor uint8_t getRealChannelIndex() { return _channelIndex; } private: - bool do_isSensorReady() override { return _realSensor.isReady(); } + bool do_isSensorReady() override { return do_isSensorChannelReady(_channelIndex); } bool do_isSensorChannelReady(uint8_t) override { return _realSensor.isReady(_channelIndex); } SensorValue do_getSensorChannelValue(uint8_t) override { return _realSensor.getValue(_channelIndex); } const SensorChannelProps &do_getSensorChannelProperties(uint8_t) override { return _realSensor.getProps(_channelIndex); } @@ -305,12 +305,92 @@ class SensorList //-------------------------------------------------------------------------------------------------- +#if (0) +class EasySensorBase : public Sensor +{ +public: + void suspendSensor() { _isSensorReady = false; } + void suspendChannel(uint8_t channelIndex) { _isChannelReady[channelIndex] = false; } + + void set(uint8_t channelIndex, SensorValue val) + { + if (val.type() == _val[channelIndex].type()) + { + _val[channelIndex] = val; + _isChannelReady[channelIndex] = true; + _isSensorReady = true; + } + } + + SensorValue get(uint8_t channelIndex) const { return _val[channelIndex]; } + +protected: + EasySensorArray(const char *sensorName, const SensorChannelPropsArray &channelProps) + : Sensor{sensorName, CHANNEL_COUNT}, _props{channelProps}, _val{channelProps.rangeMin} {} + +private: + bool do_isSensorReady() override { return _isSensorReady; } + SensorValue do_getSensorChannelValue(uint8_t) override { return _val; } + const SensorChannelProps &do_getSensorChannelProperties(uint8_t) override { return _props; } + +private: + const SensorChannelPropsArray _props; + std::array _val; + std::array _isChannelReady{}; + bool _isSensorReady = false; +}; +#endif + +template +class EasySensorArray : public Sensor +{ +public: + EasySensorArray(const char *sensorName, const SensorChannelPropsArray &channelProps) + : Sensor{sensorName, CHANNEL_COUNT}, _props{channelProps} + { + for (size_t i = 0; i < CHANNEL_COUNT; ++i) + { + _val[i] = _props[i].rangeMin; + _isChannelReady[i] = false; + } + } + + void suspend() { _isSensorReady = false; } + void suspendChannel(uint8_t channelIndex) { _isChannelReady[channelIndex] = false; } + + void set(uint8_t channelIndex, SensorValue val) + { + if (val.type() == _val[channelIndex].type()) + { + _val[channelIndex] = val; + _isChannelReady[channelIndex] = true; + _isSensorReady = true; + } + } + + SensorValue get(uint8_t channelIndex) const { return _val[channelIndex]; } + +private: + bool do_isSensorReady() override { return _isSensorReady; } + virtual bool do_isSensorChannelReady(uint8_t channelIndex) { return _isChannelReady[channelIndex]; }; + SensorValue do_getSensorChannelValue(uint8_t channelIndex) override { return _val[channelIndex]; } + const SensorChannelProps &do_getSensorChannelProperties(uint8_t channelIndex) override { return _props[channelIndex]; } + +private: + const SensorChannelPropsArray _props; + std::array _val; + std::array _isChannelReady{}; + bool _isSensorReady = false; +}; + class EasySensor : public Sensor { public: EasySensor(const char *sensorName, const SensorChannelProps &channelProps) : Sensor{sensorName, 1}, _props{channelProps}, _val{channelProps.rangeMin} {} + void suspend() { _isReady = false; } + void set(SensorValue val) { if (val.type() == _val.type()) @@ -320,8 +400,6 @@ class EasySensor : public Sensor } } - void suspend() { _isReady = false; } - SensorValue get() const { return _val; } void operator=(SensorValue val) { set(val); } @@ -339,10 +417,18 @@ class EasySensor : public Sensor //-------------------------------------------------------------------------------------------------- -inline SensorChannelProps makeChannelProps_Bool(const char *quantityName, - const char *channelName = nullptr) +inline SensorChannelProps makeChannelProps_Bool(const char *channelName, + const char *quantityName = nullptr) +{ + return {channelName, {quantityName ? quantityName : channelName, ""}, false, true}; +} + +inline SensorChannelProps makeChannelProps_Float(const char *channelName, + const SensorQuantity &channelQuantity, + float rangeMin, + float rangeMax) { - return {channelName ? channelName : quantityName, {quantityName, ""}, false, true}; + return {channelName, channelQuantity, rangeMin, rangeMax}; } inline SensorChannelProps makeChannelProps_Temperature(const char *channelName, From fe43dae418bb03c9cbab161ce5b18a8f1c51ac80 Mon Sep 17 00:00:00 2001 From: Joachim Dick <62520542+JoaDick@users.noreply.github.com> Date: Mon, 2 Feb 2026 00:40:00 +0100 Subject: [PATCH 06/10] Sensor: more examples --- usermods/UM_SensorDummy/UM_SensorDummy.cpp | 22 +--- usermods/UM_SensorInfo/UM_SensorInfo.cpp | 142 ++++++++++++++------- wled00/Sensor.cpp | 14 +- wled00/Sensor.h | 62 ++++----- 4 files changed, 139 insertions(+), 101 deletions(-) diff --git a/usermods/UM_SensorDummy/UM_SensorDummy.cpp b/usermods/UM_SensorDummy/UM_SensorDummy.cpp index 3115d07e85..bb8cabfe2e 100644 --- a/usermods/UM_SensorDummy/UM_SensorDummy.cpp +++ b/usermods/UM_SensorDummy/UM_SensorDummy.cpp @@ -49,7 +49,7 @@ class UM_SensorDummy : public Usermod, public Sensor _sensorArray.set(2, readHumidity()); #if (0) // Battery is empty :-( _sensorArray.set(3, readTemperature() - 3.0f); - _sensorArray.set(4, readHumidity() + 5.0f); + _sensorArray.set(4, readHumidity() - 5.0f); #endif } @@ -71,23 +71,13 @@ class UM_SensorDummy : public Usermod, public Sensor float readSEF(uint8_t index) { - int32_t raw = 0; - switch (index) + if (index >= 3) { - case 0: - raw = beatsin16_t(6, 0, 0xFFFF, 0, 0); - break; - case 1: - raw = beatsin16_t(6, 0, 0xFFFF, 0, 0xFFFF / 3); - break; - case 2: - raw = beatsin16_t(6, 0, 0xFFFF, 0, 2 * 0xFFFF / 3); - break; - - default: - return -123.45f; + const int32_t raw = abs(beat16(20) - 0x8000); + return raw / 32767.0f * 100.0f; } - return 180.0f + raw / 65535.0f * 90.0f; + const int32_t raw = beatsin16_t(40, 0, 0xFFFF, 0, (index * 0xFFFF) / 3); + return 90.0f + raw / 65535.0f * 90.0f; } // ----- member variables ----- diff --git a/usermods/UM_SensorInfo/UM_SensorInfo.cpp b/usermods/UM_SensorInfo/UM_SensorInfo.cpp index ef00cef130..1dfbfdb143 100644 --- a/usermods/UM_SensorInfo/UM_SensorInfo.cpp +++ b/usermods/UM_SensorInfo/UM_SensorInfo.cpp @@ -5,6 +5,8 @@ #include "wled.h" +extern uint16_t mode_static(void); + //-------------------------------------------------------------------------------------------------- namespace @@ -47,7 +49,7 @@ namespace for (uint8_t ch = 0; ch < MIN(channelCount, size / 2); ++ch) { SensorChannelProxy channel = sensor.channel(ch); - const auto color = getChannelColor(channel); + const uint32_t color = getChannelColor(channel); SEGMENT.setPixelColor(pos, color); pos += 2; } @@ -73,7 +75,7 @@ uint16_t mode_SensorInfo() SensorChannelVisualizer visualizer; - auto sensorList = UsermodManager::getSensors(); + SensorList sensorList = UsermodManager::getSensors(); for (auto cursor = sensorList.getAllSensors(); cursor.isValid(); cursor.next()) @@ -83,71 +85,118 @@ uint16_t mode_SensorInfo() return FRAMETIME; } -static const char _data_FX_MODE_SENSOR_INFO[] PROGMEM = "! SensorInfo"; +static const char _data_FX_MODE_SENSOR_INFO[] PROGMEM = "1 Sensor Info"; //-------------------------------------------------------------------------------------------------- -#if (0) - uint16_t mode_NumberDumper() { SEGMENT.clear(); -#if (0) - auto sensorList = UsermodManager::getSensors(); - int counter = 0; - for (auto cursor = sensorList.getSensorChannelsByType(SensorValueType::Float); - cursor.isValid(); - cursor.next()) + SensorList sensorList = UsermodManager::getSensors(); + uint8_t hue = 0; + for (auto cursor = sensorList.getSensorChannelsByType(SensorValueType::Float); cursor.isValid(); cursor.next()) { - auto sensor = cursor.get(); - const bool isReady = sensor.isReady(); - SEGMENT.setPixelColor(counter * 10 + 5, isReady ? 0x008800 : 0x880000); - - if (isReady) + auto channel = cursor.get(); + const bool isReady = channel.isReady(); + if (!isReady) continue; - const float sensorValue = sensor.getValue(); - const float sensorValueMax = sensor.getProps().rangeMax; + + const auto &props = channel.getProps(); + const float sensorValue = channel.getValue(); + const float sensorValueMax = channel.getProps().rangeMax; + // TODO(feature) Also take care for negative ranges, and map accordingly. if (sensorValueMax > 0.0f) { const int pos = sensorValue * (SEGLEN - 1) / sensorValueMax; - SEGMENT.setPixelColor(pos, 0x000088); - // SEGMENT.setPixelColor(pos, SEGCOLOR(0)); - // SEGMENT.setPixelColor(pos, fast_color_scale(SEGCOLOR(0), 64)); + uint32_t color; + hsv2rgb(CHSV32(hue, 255, 255), color); + SEGMENT.setPixelColor(pos, color); + hue += 74; } - - ++counter; } -#endif -#if (1) - auto sensorList = UsermodManager::getSensors(); - int counter = 0; - for (auto cursor = sensorList.getAllSensors(); - cursor.isValid(); - cursor.next()) - { - const int basePos = 5 + 25 * counter++; + return FRAMETIME; +} +static const char _data_FX_MODE_NUMBER_DUMPER[] PROGMEM = "2 Numbers"; + +//-------------------------------------------------------------------------------------------------- - auto &sensor = cursor.get(); - const bool isReady = sensor.isReady(); - SEGMENT.setPixelColor(basePos, isReady ? 0x004400 : 0x440000); +uint16_t mode_SEF_all() +{ + SensorList sensorList = UsermodManager::getSensors(); + Sensor *sensor = sensorList.findSensorByName("SEF"); + if (!sensor || !sensor->isReady()) + return mode_static(); - int baseOffset = 2; - const uint8_t channelCount = sensor.channelCount(); - for (uint8_t ch = 0; ch < channelCount; ++ch) + SEGMENT.clear(); + SEGMENT.fill(0x080800); + + uint8_t hue = 0; + for (uint8_t channelIndex = 0; channelIndex < sensor->channelCount(); ++channelIndex) + { + if (!sensor->isReady(channelIndex)) + continue; + + const auto &props = sensor->getProps(channelIndex); + const float sensorValue = sensor->getValue(channelIndex); + const float sensorValueMax = sensor->getProps(channelIndex).rangeMax; + // TODO(feature) Also take care for negative ranges, and map accordingly. + if (sensorValueMax > 0.0f) { - SEGMENT.setPixelColor(basePos + baseOffset + ch * baseOffset, 0x440044); + const int pos = sensorValue * (SEGLEN - 1) / sensorValueMax; + uint32_t color; + hsv2rgb(CHSV32(hue, 255, 255), color); + SEGMENT.setPixelColor(pos, color); + hue += 74; } } -#endif return FRAMETIME; } -static const char _data_FX_MODE_EX_NUMBER_DUMPER[] PROGMEM = "! SensorNumberDumper"; -// static const char _data_FX_MODE_EX_NUMBER_DUMPER[] PROGMEM = "Ex: NumberDumper@,,,,,,Humidity Info,Temperature Info;!;;o2=1,o3=1"; +static const char _data_FX_MODE_SEF_ALL[] PROGMEM = "3 SEF all"; + +//-------------------------------------------------------------------------------------------------- -#endif +class FluctuationChannels final : public SensorChannelCursor +{ +public: + explicit FluctuationChannels(SensorList &sensorList) : SensorChannelCursor{sensorList.getAllSensors()} {} + +private: + bool matches(const SensorChannelProps &props) override { return strcmp(props.quantity.name, "offset") == 0; } +}; + +uint16_t mode_Fluctuations() +{ + SEGMENT.clear(); + SEGMENT.fill(0x080800); + + SensorList sensorList = UsermodManager::getSensors(); + uint8_t hue = 0; + for (FluctuationChannels cursor{sensorList}; cursor.isValid(); cursor.next()) + { + auto channel = cursor.get(); + if (!channel.isReady()) + continue; + + const auto &props = channel.getProps(); + const float sensorValue = channel.getValue(); + const float sensorValueMax = channel.getProps().rangeMax; + // TODO(feature) Also take care for negative ranges, and map accordingly. + if (sensorValueMax > 0.0f) + { + const int pos = sensorValue * (SEGLEN - 1) / sensorValueMax; + uint32_t color; + hsv2rgb(CHSV32(hue, 255, 255), color); + SEGMENT.setPixelColor(pos, color); + hue += 74; + } + } + + return FRAMETIME; +} +static const char _data_FX_MODE_FLUCTUATIONS[] PROGMEM = "4 Fluct only"; //-------------------------------------------------------------------------------------------------- @@ -156,7 +205,9 @@ class UM_SensorInfo : public Usermod void setup() override { strip.addEffect(255, &mode_SensorInfo, _data_FX_MODE_SENSOR_INFO); - // strip.addEffect(255, &mode_NumberDumper, _data_FX_MODE_EX_NUMBER_DUMPER); + strip.addEffect(255, &mode_NumberDumper, _data_FX_MODE_NUMBER_DUMPER); + strip.addEffect(255, &mode_SEF_all, _data_FX_MODE_SEF_ALL); + strip.addEffect(255, &mode_Fluctuations, _data_FX_MODE_FLUCTUATIONS); } void loop() override {} @@ -168,7 +219,7 @@ class UM_SensorInfo : public Usermod user = root.createNestedObject("u"); int sensorIndex = 0; - auto sensorList = UsermodManager::getSensors(); + SensorList sensorList = UsermodManager::getSensors(); for (auto cursor = sensorList.getAllSensors(); cursor.isValid(); cursor.next()) { Sensor &sensor = cursor.get(); @@ -206,6 +257,7 @@ class UM_SensorInfo : public Usermod if (isChannelReady) { const SensorValue sensorValue = sensor.getValue(channelIndex); + // TODO(feature) Also take care for other datatypes (via visitor). val += sensorValue.as_float(); } else diff --git a/wled00/Sensor.cpp b/wled00/Sensor.cpp index 8f07696efd..3c33337356 100644 --- a/wled00/Sensor.cpp +++ b/wled00/Sensor.cpp @@ -56,6 +56,15 @@ bool SensorCursor::next() //-------------------------------------------------------------------------------------------------- +bool SensorChannelCursor::isValid() +{ + if (!_sensorCursor.isValid()) + return false; + if (matches(_sensorCursor->getProps(_channelIndex))) + return true; + return next(); +} + void SensorChannelCursor::reset() { _sensorCursor.reset(); @@ -69,8 +78,11 @@ bool SensorChannelCursor::next() if (++_channelIndex < _sensorCursor->channelCount()) if (matches(_sensorCursor->getProps(_channelIndex))) return true; + _channelIndex = 0; - _sensorCursor.next(); + if (_sensorCursor.next()) + if (matches(_sensorCursor->getProps(_channelIndex))) + return true; } return false; } diff --git a/wled00/Sensor.h b/wled00/Sensor.h index 1f1c82f879..77c1ed715b 100644 --- a/wled00/Sensor.h +++ b/wled00/Sensor.h @@ -75,11 +75,13 @@ class SensorValue SensorValueType _type; }; +// The physical (or theoretical/virtual) quantity of the readings from a sensor channel. struct SensorQuantity { const char *const name; const char *const unit; + // common physical measurements (just for convenience and consistency) static SensorQuantity Temperature() { return {"Temperature", "°C"}; } static SensorQuantity Humidity() { return {"Humidity", "%rel"}; } static SensorQuantity AirPressure() { return {"Air Pressure", "hPa"}; } @@ -224,7 +226,7 @@ class SensorCursor class SensorChannelCursor { public: - bool isValid() const { return _sensorCursor.isValid(); } + bool isValid(); SensorChannelProxy get() { return {*_sensorCursor, _channelIndex}; } bool next(); void reset(); @@ -252,6 +254,21 @@ class AllSensorChannels final : public SensorChannelCursor bool matches(const SensorChannelProps &) override { return true; } }; +// A cursor to iterate over all available channels with a specific quantity. +class SensorChannelsByQuantity final : public SensorChannelCursor +{ +public: + SensorChannelsByQuantity(SensorCursor allSensors, const char *quantityName) + : SensorChannelCursor{allSensors}, _quantityName{quantityName} {} + + SensorChannelsByQuantity(SensorCursor allSensors, SensorQuantity quantity) + : SensorChannelsByQuantity{allSensors, quantity.name} {} + +private: + bool matches(const SensorChannelProps &props) override { return strcmp(props.quantity.name, _quantityName) == 0; } + const char *const _quantityName; +}; + // A cursor to iterate over all available channels with a specific ValueType. class SensorChannelsByType final : public SensorChannelCursor { @@ -261,7 +278,7 @@ class SensorChannelsByType final : public SensorChannelCursor private: bool matches(const SensorChannelProps &props) override { return props.rangeMin.type() == _type; } - SensorValueType _type; + const SensorValueType _type; }; // A cursor to iterate over all available channels with a specific name. @@ -273,7 +290,7 @@ class SensorChannelsByName final : public SensorChannelCursor private: bool matches(const SensorChannelProps &props) override { return strcmp(props.name, _name) == 0; } - const char *_name; + const char *const _name; }; class SensorList @@ -294,6 +311,9 @@ class SensorList AllSensorChannels getAllSensorChannels() { return AllSensorChannels{getAllSensors()}; } + SensorChannelsByQuantity getSensorChannelsByQuantity(const char *quantityName) { return {getAllSensors(), quantityName}; } + SensorChannelsByQuantity getSensorChannelsByQuantity(SensorQuantity quantity) { return {getAllSensors(), quantity}; } + SensorChannelsByType getSensorChannelsByType(SensorValueType valueType) { return {getAllSensors(), valueType}; } SensorChannelsByName getSensorChannelsByName(const char *channelName) { return {getAllSensors(), channelName}; } @@ -305,42 +325,6 @@ class SensorList //-------------------------------------------------------------------------------------------------- -#if (0) -class EasySensorBase : public Sensor -{ -public: - void suspendSensor() { _isSensorReady = false; } - void suspendChannel(uint8_t channelIndex) { _isChannelReady[channelIndex] = false; } - - void set(uint8_t channelIndex, SensorValue val) - { - if (val.type() == _val[channelIndex].type()) - { - _val[channelIndex] = val; - _isChannelReady[channelIndex] = true; - _isSensorReady = true; - } - } - - SensorValue get(uint8_t channelIndex) const { return _val[channelIndex]; } - -protected: - EasySensorArray(const char *sensorName, const SensorChannelPropsArray &channelProps) - : Sensor{sensorName, CHANNEL_COUNT}, _props{channelProps}, _val{channelProps.rangeMin} {} - -private: - bool do_isSensorReady() override { return _isSensorReady; } - SensorValue do_getSensorChannelValue(uint8_t) override { return _val; } - const SensorChannelProps &do_getSensorChannelProperties(uint8_t) override { return _props; } - -private: - const SensorChannelPropsArray _props; - std::array _val; - std::array _isChannelReady{}; - bool _isSensorReady = false; -}; -#endif - template class EasySensorArray : public Sensor { From ce4cf8a802394a7036dc8fe1491d57b8bd79a3ae Mon Sep 17 00:00:00 2001 From: Joachim Dick <62520542+JoaDick@users.noreply.github.com> Date: Mon, 2 Feb 2026 22:56:13 +0100 Subject: [PATCH 07/10] Sensor: some cleanup --- usermods/UM_SensorInfo/UM_SensorInfo.cpp | 26 ++++++------ wled00/Sensor.cpp | 12 +++--- wled00/Sensor.h | 51 +++++++++++++----------- 3 files changed, 46 insertions(+), 43 deletions(-) diff --git a/usermods/UM_SensorInfo/UM_SensorInfo.cpp b/usermods/UM_SensorInfo/UM_SensorInfo.cpp index 1dfbfdb143..55497b977b 100644 --- a/usermods/UM_SensorInfo/UM_SensorInfo.cpp +++ b/usermods/UM_SensorInfo/UM_SensorInfo.cpp @@ -39,7 +39,7 @@ namespace private: void showReadyState(Sensor &sensor) { - drawLine(_startPos, size, sensor.isReady() ? 0x002200 : 0x220000); + drawLine(_startPos, size, sensor.isSensorReady() ? 0x002200 : 0x220000); } void showChannelInfo(Sensor &sensor) @@ -48,7 +48,7 @@ namespace const uint8_t channelCount = sensor.channelCount(); for (uint8_t ch = 0; ch < MIN(channelCount, size / 2); ++ch) { - SensorChannelProxy channel = sensor.channel(ch); + SensorChannelProxy channel = sensor.getChannel(ch); const uint32_t color = getChannelColor(channel); SEGMENT.setPixelColor(pos, color); pos += 2; @@ -126,7 +126,7 @@ uint16_t mode_SEF_all() { SensorList sensorList = UsermodManager::getSensors(); Sensor *sensor = sensorList.findSensorByName("SEF"); - if (!sensor || !sensor->isReady()) + if (!sensor || !sensor->isSensorReady()) return mode_static(); SEGMENT.clear(); @@ -135,12 +135,12 @@ uint16_t mode_SEF_all() uint8_t hue = 0; for (uint8_t channelIndex = 0; channelIndex < sensor->channelCount(); ++channelIndex) { - if (!sensor->isReady(channelIndex)) + if (!sensor->isChannelReady(channelIndex)) continue; - const auto &props = sensor->getProps(channelIndex); - const float sensorValue = sensor->getValue(channelIndex); - const float sensorValueMax = sensor->getProps(channelIndex).rangeMax; + const auto &props = sensor->getChannelProps(channelIndex); + const float sensorValue = sensor->getChannelValue(channelIndex); + const float sensorValueMax = sensor->getChannelProps(channelIndex).rangeMax; // TODO(feature) Also take care for negative ranges, and map accordingly. if (sensorValueMax > 0.0f) { @@ -223,7 +223,7 @@ class UM_SensorInfo : public Usermod for (auto cursor = sensorList.getAllSensors(); cursor.isValid(); cursor.next()) { Sensor &sensor = cursor.get(); - const bool isSensorReady = sensor.isReady(); + const bool isSensorReady = sensor.isSensorReady(); const int channelCount = sensor.channelCount(); String sensorName; @@ -241,22 +241,22 @@ class UM_SensorInfo : public Usermod for (int channelIndex = 0; channelIndex < channelCount; ++channelIndex) { - SensorChannelProxy channel = sensor.channel(channelIndex); - const SensorChannelProps &channelProps = channel.getProps(channelIndex); + SensorChannelProxy channel = sensor.getChannel(channelIndex); + const SensorChannelProps &channelProps = channel.getProps(); String key; key += sensorIndex; key += "."; key += channelIndex; key += " "; - key += channelProps.name; + key += channel.name(); - const bool isChannelReady = sensor.isReady(channelIndex); + const bool isChannelReady = channel.isReady(); String val; val += channelProps.quantity.name; val += "
"; if (isChannelReady) { - const SensorValue sensorValue = sensor.getValue(channelIndex); + const SensorValue sensorValue = channel.getValue(); // TODO(feature) Also take care for other datatypes (via visitor). val += sensorValue.as_float(); } diff --git a/wled00/Sensor.cpp b/wled00/Sensor.cpp index 3c33337356..e0c6a7d6d6 100644 --- a/wled00/Sensor.cpp +++ b/wled00/Sensor.cpp @@ -60,7 +60,7 @@ bool SensorChannelCursor::isValid() { if (!_sensorCursor.isValid()) return false; - if (matches(_sensorCursor->getProps(_channelIndex))) + if (matches(_sensorCursor->getChannelProps(_channelIndex))) return true; return next(); } @@ -76,12 +76,12 @@ bool SensorChannelCursor::next() while (_sensorCursor.isValid()) { if (++_channelIndex < _sensorCursor->channelCount()) - if (matches(_sensorCursor->getProps(_channelIndex))) + if (matches(_sensorCursor->getChannelProps(_channelIndex))) return true; _channelIndex = 0; if (_sensorCursor.next()) - if (matches(_sensorCursor->getProps(_channelIndex))) + if (matches(_sensorCursor->getChannelProps(_channelIndex))) return true; } return false; @@ -118,11 +118,11 @@ void SensorValue::accept(SensorValueVisitor &visitor) const void Sensor::accept(uint8_t channelIndex, SensorChannelVisitor &visitor) { - if (!isReady()) + if (!isSensorReady()) return; - const auto &val = getValue(channelIndex); - const auto &props = getProps(channelIndex); + const auto &val = getChannelValue(channelIndex); + const auto &props = getChannelProps(channelIndex); switch (val.type()) { case SensorValueType::Bool: diff --git a/wled00/Sensor.h b/wled00/Sensor.h index 77c1ed715b..cbf702cade 100644 --- a/wled00/Sensor.h +++ b/wled00/Sensor.h @@ -102,9 +102,9 @@ struct SensorChannelProps SensorQuantity quantity_, SensorValue rangeMin_, SensorValue rangeMax_) - : name{channelName_}, quantity{quantity_}, rangeMin{rangeMin_}, rangeMax{rangeMax_} {} + : channelName{channelName_}, quantity{quantity_}, rangeMin{rangeMin_}, rangeMax{rangeMax_} {} - const char *const name; + const char *const channelName; const SensorQuantity quantity; const SensorValue rangeMin; const SensorValue rangeMax; @@ -119,19 +119,19 @@ class SensorChannelProxy; class Sensor { public: - const char *name() const { return _sensorName; } + const char *name() { return _sensorName; } - bool isReady() { return do_isSensorReady(); } + uint8_t channelCount() { return _channelCount; } - uint8_t channelCount() const { return _channelCount; } + bool isSensorReady() { return do_isSensorReady(); } - SensorChannelProxy channel(uint8_t channelIndex); + SensorChannelProxy getChannel(uint8_t channelIndex); - bool isReady(uint8_t channelIndex) { return do_isSensorReady() ? do_isSensorChannelReady(channelIndex) : false; } + bool isChannelReady(uint8_t channelIndex) { return do_isSensorReady() ? do_isSensorChannelReady(channelIndex) : false; } - SensorValue getValue(uint8_t channelIndex = 0) { return do_getSensorChannelValue(channelIndex); } + SensorValue getChannelValue(uint8_t channelIndex) { return do_getSensorChannelValue(channelIndex); } - const SensorChannelProps &getProps(uint8_t channelIndex = 0) { return do_getSensorChannelProperties(channelIndex); } + const SensorChannelProps &getChannelProps(uint8_t channelIndex) { return do_getSensorChannelProperties(channelIndex); } void accept(uint8_t channelIndex, SensorChannelVisitor &visitor); void accept(SensorChannelVisitor &visitor) { accept(0, visitor); } @@ -150,26 +150,29 @@ class Sensor const uint8_t _channelCount; }; -class SensorChannelProxy final : public Sensor +class SensorChannelProxy { public: SensorChannelProxy(Sensor &realSensor, const uint8_t channelIndex) - : Sensor{realSensor.name(), 1}, _realSensor{realSensor}, _channelIndex{channelIndex} {} + : _parent{realSensor}, _index{channelIndex} {} - Sensor &getRealSensor() { return _realSensor; } - uint8_t getRealChannelIndex() { return _channelIndex; } + const char *name() { return getProps().channelName; } -private: - bool do_isSensorReady() override { return do_isSensorChannelReady(_channelIndex); } - bool do_isSensorChannelReady(uint8_t) override { return _realSensor.isReady(_channelIndex); } - SensorValue do_getSensorChannelValue(uint8_t) override { return _realSensor.getValue(_channelIndex); } - const SensorChannelProps &do_getSensorChannelProperties(uint8_t) override { return _realSensor.getProps(_channelIndex); } + bool isReady() { return _parent.isChannelReady(_index); } + + SensorValue getValue() { return _parent.getChannelValue(_index); } + + const SensorChannelProps &getProps() { return _parent.getChannelProps(_index); } - Sensor &_realSensor; - const uint8_t _channelIndex; + Sensor &getRealSensor() { return _parent; } + uint8_t getRealChannelIndex() { return _index; } + +private: + Sensor &_parent; + const uint8_t _index; }; -inline SensorChannelProxy Sensor::channel(uint8_t channelIndex) { return SensorChannelProxy{*this, channelIndex}; } +inline SensorChannelProxy Sensor::getChannel(uint8_t channelIndex) { return SensorChannelProxy{*this, channelIndex}; } //-------------------------------------------------------------------------------------------------- @@ -205,10 +208,10 @@ class SensorCursor { public: using UmIterator = Usermod *const *; - SensorCursor(UmIterator umBegin, UmIterator umEnd) : _umBegin{umBegin}, _umEnd{umEnd} { reset(); } + bool isValid() const { return _sensor != nullptr; } - Sensor &get() { return {*_sensor}; } + Sensor &get() { return *_sensor; } Sensor &operator*() { return *_sensor; } Sensor *operator->() { return _sensor; } bool next(); @@ -289,7 +292,7 @@ class SensorChannelsByName final : public SensorChannelCursor : SensorChannelCursor{allSensors}, _name{channelName} {} private: - bool matches(const SensorChannelProps &props) override { return strcmp(props.name, _name) == 0; } + bool matches(const SensorChannelProps &props) override { return strcmp(props.channelName, _name) == 0; } const char *const _name; }; From ef342d29e6a8d4e4f1d2f1c316228759b187035b Mon Sep 17 00:00:00 2001 From: Joachim Dick <62520542+JoaDick@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:08:38 +0100 Subject: [PATCH 08/10] Sensor: new EasySensorBase --- usermods/Temperature/Temperature.cpp | 2 +- wled00/Sensor.h | 105 +++++++++++++++------------ 2 files changed, 59 insertions(+), 48 deletions(-) diff --git a/usermods/Temperature/Temperature.cpp b/usermods/Temperature/Temperature.cpp index 691feb3cc8..28b7f3bbf5 100644 --- a/usermods/Temperature/Temperature.cpp +++ b/usermods/Temperature/Temperature.cpp @@ -167,7 +167,7 @@ void UsermodTemperature::loop() { if (getTemperatureC() < -100.0f) { if (++errorCount > 10) { sensorFound = 0; - temperatureSensor.suspend(); + temperatureSensor.suspendSensor(); } lastMeasurement = now - readingInterval + 300; // force new measurement in 300ms return; diff --git a/wled00/Sensor.h b/wled00/Sensor.h index cbf702cade..8cca9aae4c 100644 --- a/wled00/Sensor.h +++ b/wled00/Sensor.h @@ -328,78 +328,89 @@ class SensorList //-------------------------------------------------------------------------------------------------- -template -class EasySensorArray : public Sensor +class EasySensorBase : public Sensor { public: - EasySensorArray(const char *sensorName, const SensorChannelPropsArray &channelProps) - : Sensor{sensorName, CHANNEL_COUNT}, _props{channelProps} - { - for (size_t i = 0; i < CHANNEL_COUNT; ++i) - { - _val[i] = _props[i].rangeMin; - _isChannelReady[i] = false; - } - } - - void suspend() { _isSensorReady = false; } - void suspendChannel(uint8_t channelIndex) { _isChannelReady[channelIndex] = false; } + void suspendSensor() { _isSensorReady = false; } + void suspendChannel(uint8_t channelIndex) { _channelReadyFlags &= ~(1U << channelIndex); } void set(uint8_t channelIndex, SensorValue val) { - if (val.type() == _val[channelIndex].type()) - { - _val[channelIndex] = val; - _isChannelReady[channelIndex] = true; - _isSensorReady = true; - } + if (channelIndex >= channelCount()) + channelIndex = 0; + _channelValues[channelIndex] = val; + _channelReadyFlags |= (1U << channelIndex); + _isSensorReady = true; } - SensorValue get(uint8_t channelIndex) const { return _val[channelIndex]; } +protected: + ~EasySensorBase() = default; + EasySensorBase(const char *sensorName, uint8_t channelCount, + SensorValue *channelValues, const SensorChannelProps *channelProps) + : Sensor{sensorName, channelCount}, + _channelProps{channelProps}, _channelValues{channelValues} {} private: - bool do_isSensorReady() override { return _isSensorReady; } - virtual bool do_isSensorChannelReady(uint8_t channelIndex) { return _isChannelReady[channelIndex]; }; - SensorValue do_getSensorChannelValue(uint8_t channelIndex) override { return _val[channelIndex]; } - const SensorChannelProps &do_getSensorChannelProperties(uint8_t channelIndex) override { return _props[channelIndex]; } + bool do_isSensorReady() final { return _isSensorReady; } + + bool do_isSensorChannelReady(uint8_t channelIndex) final + { + if (channelIndex >= channelCount()) + channelIndex = 0; + return _channelReadyFlags & (1U << channelIndex); + }; + + SensorValue do_getSensorChannelValue(uint8_t channelIndex) final + { + if (channelIndex >= channelCount()) + channelIndex = 0; + return _channelValues[channelIndex]; + } + + const SensorChannelProps &do_getSensorChannelProperties(uint8_t channelIndex) final + { + if (channelIndex >= channelCount()) + channelIndex = 0; + return _channelProps[channelIndex]; + } private: - const SensorChannelPropsArray _props; - std::array _val; - std::array _isChannelReady{}; + const SensorChannelProps *_channelProps; + SensorValue *_channelValues; + uint32_t _channelReadyFlags = 0; bool _isSensorReady = false; }; -class EasySensor : public Sensor +template +class EasySensorArray : public EasySensorBase { public: - EasySensor(const char *sensorName, const SensorChannelProps &channelProps) - : Sensor{sensorName, 1}, _props{channelProps}, _val{channelProps.rangeMin} {} + static_assert(CHANNEL_COUNT <= 32, "EasySensorArray supports max. 32 sensor channels"); - void suspend() { _isReady = false; } + EasySensorArray(const char *sensorName, const SensorChannelPropsArray &channelProps) + : EasySensorBase{sensorName, CHANNEL_COUNT, _channelValues.data(), _channelProps.data()}, + _channelProps{channelProps} {} - void set(SensorValue val) - { - if (val.type() == _val.type()) - { - _val = val; - _isReady = true; - } - } +private: + const SensorChannelPropsArray _channelProps; + std::array _channelValues; +}; - SensorValue get() const { return _val; } +class EasySensor : public EasySensorBase +{ +public: + void suspendChannel(uint8_t channelIndex) = delete; + void set(uint8_t channelIndex, SensorValue val) = delete; - void operator=(SensorValue val) { set(val); } + EasySensor(const char *sensorName, const SensorChannelProps &channelProps) + : EasySensorBase{sensorName, 1, &_val, &_props}, _props{channelProps} {} -private: - bool do_isSensorReady() override { return _isReady; } - SensorValue do_getSensorChannelValue(uint8_t) override { return _val; } - const SensorChannelProps &do_getSensorChannelProperties(uint8_t) override { return _props; } + void set(SensorValue val) { EasySensorBase::set(0, val); } + void operator=(SensorValue val) { set(val); } private: const SensorChannelProps _props; SensorValue _val; - bool _isReady = false; }; //-------------------------------------------------------------------------------------------------- From 01097806a9f2d0eda2a810c00542cc36d4975dd6 Mon Sep 17 00:00:00 2001 From: Joachim Dick <62520542+JoaDick@users.noreply.github.com> Date: Tue, 3 Feb 2026 20:00:54 +0100 Subject: [PATCH 09/10] Sensor: simplified cursors --- usermods/UM_SensorInfo/UM_SensorInfo.cpp | 22 ++++------ wled00/Sensor.cpp | 11 +++++ wled00/Sensor.h | 52 ++++++------------------ wled00/fcn_declare.h | 2 +- wled00/um_manager.cpp | 4 +- 5 files changed, 35 insertions(+), 56 deletions(-) diff --git a/usermods/UM_SensorInfo/UM_SensorInfo.cpp b/usermods/UM_SensorInfo/UM_SensorInfo.cpp index 55497b977b..843edbc45a 100644 --- a/usermods/UM_SensorInfo/UM_SensorInfo.cpp +++ b/usermods/UM_SensorInfo/UM_SensorInfo.cpp @@ -75,10 +75,7 @@ uint16_t mode_SensorInfo() SensorChannelVisualizer visualizer; - SensorList sensorList = UsermodManager::getSensors(); - for (auto cursor = sensorList.getAllSensors(); - cursor.isValid(); - cursor.next()) + for (auto cursor = UsermodManager::getSensors(); cursor.isValid(); cursor.next()) { visualizer.showInfo(cursor.get()); } @@ -93,9 +90,8 @@ uint16_t mode_NumberDumper() { SEGMENT.clear(); - SensorList sensorList = UsermodManager::getSensors(); uint8_t hue = 0; - for (auto cursor = sensorList.getSensorChannelsByType(SensorValueType::Float); cursor.isValid(); cursor.next()) + for (SensorChannelsByType cursor{UsermodManager::getSensors(), SensorValueType::Float}; cursor.isValid(); cursor.next()) { auto channel = cursor.get(); const bool isReady = channel.isReady(); @@ -124,8 +120,7 @@ static const char _data_FX_MODE_NUMBER_DUMPER[] PROGMEM = "2 Numbers"; uint16_t mode_SEF_all() { - SensorList sensorList = UsermodManager::getSensors(); - Sensor *sensor = sensorList.findSensorByName("SEF"); + Sensor *sensor = findSensorByName(UsermodManager::getSensors(), "SEF"); if (!sensor || !sensor->isSensorReady()) return mode_static(); @@ -161,7 +156,8 @@ static const char _data_FX_MODE_SEF_ALL[] PROGMEM = "3 SEF all"; class FluctuationChannels final : public SensorChannelCursor { public: - explicit FluctuationChannels(SensorList &sensorList) : SensorChannelCursor{sensorList.getAllSensors()} {} + explicit FluctuationChannels(SensorCursor allSensors) + : SensorChannelCursor{allSensors} {} private: bool matches(const SensorChannelProps &props) override { return strcmp(props.quantity.name, "offset") == 0; } @@ -172,9 +168,8 @@ uint16_t mode_Fluctuations() SEGMENT.clear(); SEGMENT.fill(0x080800); - SensorList sensorList = UsermodManager::getSensors(); uint8_t hue = 0; - for (FluctuationChannels cursor{sensorList}; cursor.isValid(); cursor.next()) + for (FluctuationChannels cursor{UsermodManager::getSensors()}; cursor.isValid(); cursor.next()) { auto channel = cursor.get(); if (!channel.isReady()) @@ -182,7 +177,7 @@ uint16_t mode_Fluctuations() const auto &props = channel.getProps(); const float sensorValue = channel.getValue(); - const float sensorValueMax = channel.getProps().rangeMax; + const float sensorValueMax = props.rangeMax; // TODO(feature) Also take care for negative ranges, and map accordingly. if (sensorValueMax > 0.0f) { @@ -219,8 +214,7 @@ class UM_SensorInfo : public Usermod user = root.createNestedObject("u"); int sensorIndex = 0; - SensorList sensorList = UsermodManager::getSensors(); - for (auto cursor = sensorList.getAllSensors(); cursor.isValid(); cursor.next()) + for (auto cursor = UsermodManager::getSensors(); cursor.isValid(); cursor.next()) { Sensor &sensor = cursor.get(); const bool isSensorReady = sensor.isSensorReady(); diff --git a/wled00/Sensor.cpp b/wled00/Sensor.cpp index e0c6a7d6d6..60ff67f639 100644 --- a/wled00/Sensor.cpp +++ b/wled00/Sensor.cpp @@ -54,6 +54,17 @@ bool SensorCursor::next() return false; } +Sensor *findSensorByName(SensorCursor allSensors, const char *sensorName) +{ + while (allSensors.isValid()) + { + if (strcmp(allSensors->name(), sensorName) == 0) + return &allSensors.get(); + allSensors.next(); + } + return nullptr; +} + //-------------------------------------------------------------------------------------------------- bool SensorChannelCursor::isValid() diff --git a/wled00/Sensor.h b/wled00/Sensor.h index 8cca9aae4c..c786019ebc 100644 --- a/wled00/Sensor.h +++ b/wled00/Sensor.h @@ -134,7 +134,6 @@ class Sensor const SensorChannelProps &getChannelProps(uint8_t channelIndex) { return do_getSensorChannelProperties(channelIndex); } void accept(uint8_t channelIndex, SensorChannelVisitor &visitor); - void accept(SensorChannelVisitor &visitor) { accept(0, visitor); } protected: Sensor(const char *sensorName, uint8_t channelCount) @@ -203,7 +202,7 @@ class SensorChannelVisitor //-------------------------------------------------------------------------------------------------- class Usermod; -// A cursor to iterate over all available sensors. +// A cursor to iterate over all available sensors (provided by UsermodManager). class SensorCursor { public: @@ -214,6 +213,7 @@ class SensorCursor Sensor &get() { return *_sensor; } Sensor &operator*() { return *_sensor; } Sensor *operator->() { return _sensor; } + bool next(); void reset(); @@ -225,19 +225,23 @@ class SensorCursor uint8_t _sensorIndex = 0; }; -// Base class for cursors to iterate over specific channels of all sensors. +Sensor *findSensorByName(SensorCursor allSensors, const char *sensorName); + +//-------------------------------------------------------------------------------------------------- + +// Base class for cursors that iterate over specific channels of all sensors. class SensorChannelCursor { public: bool isValid(); SensorChannelProxy get() { return {*_sensorCursor, _channelIndex}; } + bool next(); void reset(); protected: ~SensorChannelCursor() = default; - explicit SensorChannelCursor(SensorCursor allSensors) - : _sensorCursor{allSensors} { reset(); } + explicit SensorChannelCursor(SensorCursor allSensors) : _sensorCursor{allSensors} { reset(); } virtual bool matches(const SensorChannelProps &channelProps) = 0; @@ -246,7 +250,7 @@ class SensorChannelCursor uint8_t _channelIndex = 0; }; -// A cursor to iterate over all available channels of all sensors. +// A cursor to iterate over all channels of all sensors. class AllSensorChannels final : public SensorChannelCursor { public: @@ -257,7 +261,7 @@ class AllSensorChannels final : public SensorChannelCursor bool matches(const SensorChannelProps &) override { return true; } }; -// A cursor to iterate over all available channels with a specific quantity. +// A cursor to iterate over all channels with a specific quantity. class SensorChannelsByQuantity final : public SensorChannelCursor { public: @@ -272,7 +276,7 @@ class SensorChannelsByQuantity final : public SensorChannelCursor const char *const _quantityName; }; -// A cursor to iterate over all available channels with a specific ValueType. +// A cursor to iterate over all channels with a specific ValueType. class SensorChannelsByType final : public SensorChannelCursor { public: @@ -284,7 +288,7 @@ class SensorChannelsByType final : public SensorChannelCursor const SensorValueType _type; }; -// A cursor to iterate over all available channels with a specific name. +// A cursor to iterate over all channels with a specific name. class SensorChannelsByName final : public SensorChannelCursor { public: @@ -296,36 +300,6 @@ class SensorChannelsByName final : public SensorChannelCursor const char *const _name; }; -class SensorList -{ -public: - SensorList(SensorCursor::UmIterator umBegin, SensorCursor::UmIterator umEnd) - : _umBegin{umBegin}, _umEnd{umEnd} {} - - SensorCursor getAllSensors() { return SensorCursor{_umBegin, _umEnd}; } - - Sensor *findSensorByName(const char *sensorName) - { - for (auto cursor = getAllSensors(); cursor.isValid(); cursor.next()) - if (strcmp(cursor->name(), sensorName) == 0) - return &cursor.get(); - return nullptr; - } - - AllSensorChannels getAllSensorChannels() { return AllSensorChannels{getAllSensors()}; } - - SensorChannelsByQuantity getSensorChannelsByQuantity(const char *quantityName) { return {getAllSensors(), quantityName}; } - SensorChannelsByQuantity getSensorChannelsByQuantity(SensorQuantity quantity) { return {getAllSensors(), quantity}; } - - SensorChannelsByType getSensorChannelsByType(SensorValueType valueType) { return {getAllSensors(), valueType}; } - - SensorChannelsByName getSensorChannelsByName(const char *channelName) { return {getAllSensors(), channelName}; } - -private: - SensorCursor::UmIterator _umBegin; - SensorCursor::UmIterator _umEnd; -}; - //-------------------------------------------------------------------------------------------------- class EasySensorBase : public Sensor diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index df36f6e8fc..d547ad8fb4 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -367,7 +367,7 @@ namespace UsermodManager { void onStateChange(uint8_t); Usermod* lookup(uint16_t mod_id); size_t getModCount(); - SensorList getSensors(); + SensorCursor getSensors(); }; // Register usermods by building a static list via a linker section diff --git a/wled00/um_manager.cpp b/wled00/um_manager.cpp index 57ca4d85ce..ab128c08bf 100644 --- a/wled00/um_manager.cpp +++ b/wled00/um_manager.cpp @@ -40,7 +40,7 @@ bool UsermodManager::getUMData(um_data_t **data, uint8_t mod_id) { } void UsermodManager::addToJsonState(JsonObject& obj) { for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) (*mod)->addToJsonState(obj); } void UsermodManager::addToJsonInfo(JsonObject& obj) { - auto um_id_list = obj.createNestedArray("um"); + auto um_id_list = obj.createNestedArray("um"); for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) { um_id_list.add((*mod)->getId()); (*mod)->addToJsonInfo(obj); @@ -99,4 +99,4 @@ void Usermod::appendConfigData(Print& settingsScript) { oappend_shim = nullptr; } -SensorList UsermodManager::getSensors() { return SensorList{_usermod_table_begin, _usermod_table_end}; } +SensorCursor UsermodManager::getSensors() { return SensorCursor{_usermod_table_begin, _usermod_table_end}; } From bdb060b4bde94b65b8d2affd96be0a190859572c Mon Sep 17 00:00:00 2001 From: Joachim Dick <62520542+JoaDick@users.noreply.github.com> Date: Sat, 14 Feb 2026 22:55:07 +0100 Subject: [PATCH 10/10] Added comments, incorporated rabbit findings --- usermods/UM_SensorDummy/UM_SensorDummy.cpp | 6 +- usermods/UM_SensorInfo/UM_SensorInfo.cpp | 12 +- wled00/Sensor.cpp | 1 + wled00/Sensor.h | 140 +++++++++++++++++---- wled00/fcn_declare.h | 4 +- 5 files changed, 131 insertions(+), 32 deletions(-) diff --git a/usermods/UM_SensorDummy/UM_SensorDummy.cpp b/usermods/UM_SensorDummy/UM_SensorDummy.cpp index bb8cabfe2e..21b2ccdf76 100644 --- a/usermods/UM_SensorDummy/UM_SensorDummy.cpp +++ b/usermods/UM_SensorDummy/UM_SensorDummy.cpp @@ -31,9 +31,9 @@ class UM_SensorDummy : public Usermod, public Sensor Sensor *getSensor(uint8_t index) override { - if (index != 0) - return this; - return &_sensorArray; + if (index == 0) + return &_sensorArray; + return this; } bool do_isSensorReady() override { return true; } diff --git a/usermods/UM_SensorInfo/UM_SensorInfo.cpp b/usermods/UM_SensorInfo/UM_SensorInfo.cpp index 843edbc45a..43a08cab86 100644 --- a/usermods/UM_SensorInfo/UM_SensorInfo.cpp +++ b/usermods/UM_SensorInfo/UM_SensorInfo.cpp @@ -99,8 +99,8 @@ uint16_t mode_NumberDumper() continue; const auto &props = channel.getProps(); - const float sensorValue = channel.getValue(); - const float sensorValueMax = channel.getProps().rangeMax; + const float sensorValue{channel.getValue()}; + const float sensorValueMax{props.rangeMax}; // TODO(feature) Also take care for negative ranges, and map accordingly. if (sensorValueMax > 0.0f) { @@ -134,8 +134,8 @@ uint16_t mode_SEF_all() continue; const auto &props = sensor->getChannelProps(channelIndex); - const float sensorValue = sensor->getChannelValue(channelIndex); - const float sensorValueMax = sensor->getChannelProps(channelIndex).rangeMax; + const float sensorValue{sensor->getChannelValue(channelIndex)}; + const float sensorValueMax{props.rangeMax}; // TODO(feature) Also take care for negative ranges, and map accordingly. if (sensorValueMax > 0.0f) { @@ -176,8 +176,8 @@ uint16_t mode_Fluctuations() continue; const auto &props = channel.getProps(); - const float sensorValue = channel.getValue(); - const float sensorValueMax = props.rangeMax; + const float sensorValue{channel.getValue()}; + const float sensorValueMax{props.rangeMax}; // TODO(feature) Also take care for negative ranges, and map accordingly. if (sensorValueMax > 0.0f) { diff --git a/wled00/Sensor.cpp b/wled00/Sensor.cpp index 60ff67f639..522817dcd3 100644 --- a/wled00/Sensor.cpp +++ b/wled00/Sensor.cpp @@ -121,6 +121,7 @@ void SensorValue::accept(SensorValueVisitor &visitor) const break; case SensorValueType::Struct: visitor.visit(*_struct); + break; case SensorValueType::Whatever: visitor.visit(_whatever); break; diff --git a/wled00/Sensor.h b/wled00/Sensor.h index c786019ebc..e4ddef31ab 100644 --- a/wled00/Sensor.h +++ b/wled00/Sensor.h @@ -17,9 +17,11 @@ class SensorValueArrayVisitor; // TODO(feature) Not implemented yet! class SensorValueStructVisitor; // TODO(feature) Not implemented yet! class SensorChannelVisitor; +/// Specific datatype that is contained in a SensorValue. enum class SensorValueType : uint8_t { - Bool = 0, + invalid = 0, //< SensorValue is empty. + Bool, Float, Int32, UInt32, @@ -28,16 +30,29 @@ enum class SensorValueType : uint8_t Whatever }; +/// Generic datatype that is delivered by a SensorChannel. class SensorValue { public: + /// Default constructor creates an invalid SensorValue. + SensorValue() : _type{SensorValueType::invalid} {} + SensorValue(bool val) : _bool{val}, _type{SensorValueType::Bool} {} SensorValue(float val) : _float{val}, _type{SensorValueType::Float} {} SensorValue(int32_t val) : _int32{val}, _type{SensorValueType::Int32} {} SensorValue(uint32_t val) : _uint32{val}, _type{SensorValueType::UInt32} {} explicit SensorValue(const SensorValueArray *val) : _array{val}, _type{SensorValueType::Array} {} explicit SensorValue(const SensorValueStruct *val) : _struct{val}, _type{SensorValueType::Struct} {} - explicit SensorValue(const void *val = nullptr) : _whatever{val}, _type{SensorValueType::Whatever} {} + explicit SensorValue(const void *val) : _whatever{val}, _type{SensorValueType::Whatever} {} + + /** Check if the SensorValue is valid. + * Converting an invalid SensorValue to a specific type results in undefined behaviour. + */ + bool isValid() const { return _type != SensorValueType::invalid; } + + SensorValueType type() const { return _type; } + + void accept(SensorValueVisitor &visitor) const; bool as_bool() const { return _type == SensorValueType::Bool ? _bool : false; } float as_float() const { return _type == SensorValueType::Float ? _float : 0.0f; } @@ -47,14 +62,10 @@ class SensorValue const SensorValueStruct *as_struct() const { return _type == SensorValueType::Struct ? _struct : nullptr; } const void *as_whatever() const { return _type == SensorValueType::Whatever ? _whatever : nullptr; } - SensorValueType type() const { return _type; } - - void accept(SensorValueVisitor &visitor) const; - - operator bool() const { return as_bool(); } - operator float() const { return as_float(); } - operator int32_t() const { return as_int32(); } - operator uint32_t() const { return as_uint32(); } + explicit operator bool() const { return as_bool(); } + explicit operator float() const { return as_float(); } + explicit operator int32_t() const { return as_int32(); } + explicit operator uint32_t() const { return as_uint32(); } explicit operator const SensorValueArray *() const { return as_array(); } explicit operator const SensorValueStruct *() const { return as_struct(); } explicit operator const void *() const { return as_whatever(); } @@ -75,7 +86,7 @@ class SensorValue SensorValueType _type; }; -// The physical (or theoretical/virtual) quantity of the readings from a sensor channel. +/// The physical (or theoretical/virtual) quantity of the readings from a sensor channel. struct SensorQuantity { const char *const name; @@ -96,6 +107,7 @@ struct SensorQuantity static SensorQuantity Percent() { return {"Percent", "%"}; } }; +/// Properties of a SensorChannel. struct SensorChannelProps { SensorChannelProps(const char *channelName_, @@ -104,35 +116,56 @@ struct SensorChannelProps SensorValue rangeMax_) : channelName{channelName_}, quantity{quantity_}, rangeMin{rangeMin_}, rangeMax{rangeMax_} {} - const char *const channelName; - const SensorQuantity quantity; - const SensorValue rangeMin; - const SensorValue rangeMax; + const char *const channelName; //< The channel's name. + const SensorQuantity quantity; //< The quantity of the channel's readings. + const SensorValue rangeMin; //< The readings' (typical) minimum range of operation. + const SensorValue rangeMax; //< The readings' (typical) maximum range of operation. }; +/// Helper array for multiple SensorChannelProps. template using SensorChannelPropsArray = std::array; //-------------------------------------------------------------------------------------------------- class SensorChannelProxy; +/// Interface to be implemented by all sensors. class Sensor { public: + /// Get the sensor's name. const char *name() { return _sensorName; } + /// Get the number of provided sensor channels. uint8_t channelCount() { return _channelCount; } + /// Check if the sensor is online and ready to be used. bool isSensorReady() { return do_isSensorReady(); } + /** Get a proxy object that's representing one specific sensor channel. + * channelIndex >= channelCount() results in undefined behaviour. + */ SensorChannelProxy getChannel(uint8_t channelIndex); + /** Check if a specific sensor channel is ready to deliver data. + * channelIndex >= channelCount() results in undefined behaviour. + */ bool isChannelReady(uint8_t channelIndex) { return do_isSensorReady() ? do_isSensorChannelReady(channelIndex) : false; } + /** Read a value from a specific sensor channel. + * channelIndex >= channelCount() results in undefined behaviour. + * Reading a value while isChannelReady() returns false results in undefined behaviour. + */ SensorValue getChannelValue(uint8_t channelIndex) { return do_getSensorChannelValue(channelIndex); } + /** Get the properties of a specific sensor channel. + * channelIndex >= channelCount() results in undefined behaviour. + */ const SensorChannelProps &getChannelProps(uint8_t channelIndex) { return do_getSensorChannelProperties(channelIndex); } + /** Accept the given \a visitor for a specific sensor channel. + * channelIndex >= channelCount() results in undefined behaviour. + */ void accept(uint8_t channelIndex, SensorChannelVisitor &visitor); protected: @@ -149,21 +182,31 @@ class Sensor const uint8_t _channelCount; }; +/// A proxy object that is representing one specific sensor channel. class SensorChannelProxy { public: - SensorChannelProxy(Sensor &realSensor, const uint8_t channelIndex) - : _parent{realSensor}, _index{channelIndex} {} + SensorChannelProxy(Sensor &parentSensor, const uint8_t channelIndex) + : _parent{parentSensor}, _index{channelIndex} {} + /// Get the channel's name. const char *name() { return getProps().channelName; } + /// Check if the channel is ready to deliver data. bool isReady() { return _parent.isChannelReady(_index); } + /** Read a value from the channel. + * Reading a value while isReady() returns false results in undefined behaviour. + */ SensorValue getValue() { return _parent.getChannelValue(_index); } + /// Get the channel's properties. const SensorChannelProps &getProps() { return _parent.getChannelProps(_index); } + /// Get the channel's corresponding origin sensor. Sensor &getRealSensor() { return _parent; } + + /// Get the channel's corresponding index at the origin sensor. uint8_t getRealChannelIndex() { return _index; } private: @@ -202,19 +245,29 @@ class SensorChannelVisitor //-------------------------------------------------------------------------------------------------- class Usermod; -// A cursor to iterate over all available sensors (provided by UsermodManager). +/// A cursor to iterate over all available sensors (provided by UsermodManager). class SensorCursor { public: using UmIterator = Usermod *const *; SensorCursor(UmIterator umBegin, UmIterator umEnd) : _umBegin{umBegin}, _umEnd{umEnd} { reset(); } + /// Check if the cursor has currently selected a valid sensor instance. bool isValid() const { return _sensor != nullptr; } + + /** Get the currently selected sensor instance. + * Getting the sensor while isValid() returns false results in undefined behaviour. + */ Sensor &get() { return *_sensor; } Sensor &operator*() { return *_sensor; } Sensor *operator->() { return _sensor; } + /** Select the next sensor. + * @return Same as \c isValid() + */ bool next(); + + /// Jump back to the first sensor (if any). void reset(); private: @@ -229,14 +282,24 @@ Sensor *findSensorByName(SensorCursor allSensors, const char *sensorName); //-------------------------------------------------------------------------------------------------- -// Base class for cursors that iterate over specific channels of all sensors. +/// Base class for cursors that iterate over specific channels of all sensors. class SensorChannelCursor { public: + /// Check if the cursor has currently selected a valid sensor channel instance. bool isValid(); + + /** Get the currently selected sensor channel instance. + * Getting the channel while isValid() returns false results in undefined behaviour. + */ SensorChannelProxy get() { return {*_sensorCursor, _channelIndex}; } + /** Select the next channel. + * @return Same as \c isValid() + */ bool next(); + + /// Jump back to the first channel (if any). void reset(); protected: @@ -250,7 +313,7 @@ class SensorChannelCursor uint8_t _channelIndex = 0; }; -// A cursor to iterate over all channels of all sensors. +/// A cursor to iterate over all channels of all sensors. class AllSensorChannels final : public SensorChannelCursor { public: @@ -261,7 +324,7 @@ class AllSensorChannels final : public SensorChannelCursor bool matches(const SensorChannelProps &) override { return true; } }; -// A cursor to iterate over all channels with a specific quantity. +/// A cursor to iterate over all channels with a specific quantity. class SensorChannelsByQuantity final : public SensorChannelCursor { public: @@ -302,12 +365,25 @@ class SensorChannelsByName final : public SensorChannelCursor //-------------------------------------------------------------------------------------------------- +/** Base class for simple sensor implementations. + * Supports at most 32 sensor channels. + * @see EasySensor + * @see EasySensorArray + */ class EasySensorBase : public Sensor { public: + /// Put the entire sensor offline (by usermod). void suspendSensor() { _isSensorReady = false; } + + /** Put a specific sensor channel offline (by usermod). + * channelIndex >= channelCount() results in undefined behaviour. + */ void suspendChannel(uint8_t channelIndex) { _channelReadyFlags &= ~(1U << channelIndex); } + /** Store the readings for a specific sensor channel (by usermod). + * channelIndex >= channelCount() results in undefined behaviour. + */ void set(uint8_t channelIndex, SensorValue val) { if (channelIndex >= channelCount()) @@ -355,6 +431,14 @@ class EasySensorBase : public Sensor bool _isSensorReady = false; }; +/** A simple sensor implementation that provides multiple sensor channels. + * Most of the required sensor housekeeping is provided by this helper. + * The usermod is ultimately just responsible for: + * - Initialize this helper (as member variable) with the sensor's properties. + * - Make this helper available to the UsermodManager. + * - Periodically read the physical sensor. + * - Store the readings in this helper. + */ template class EasySensorArray : public EasySensorBase { @@ -370,6 +454,14 @@ class EasySensorArray : public EasySensorBase std::array _channelValues; }; +/** A simple sensor implementation that provides one single sensor channel (at index 0). + * Most of the required sensor housekeeping is provided by this helper. + * The usermod is ultimately just responsible for: + * - Initialize this helper (as member variable) with the sensor's properties. + * - Make this helper available to the UsermodManager. + * - Periodically read the physical sensor. + * - Store the readings in this helper. + */ class EasySensor : public EasySensorBase { public: @@ -379,6 +471,7 @@ class EasySensor : public EasySensorBase EasySensor(const char *sensorName, const SensorChannelProps &channelProps) : EasySensorBase{sensorName, 1, &_val, &_props}, _props{channelProps} {} + /// Store the the readings for the sensor. void set(SensorValue val) { EasySensorBase::set(0, val); } void operator=(SensorValue val) { set(val); } @@ -389,12 +482,14 @@ class EasySensor : public EasySensorBase //-------------------------------------------------------------------------------------------------- +/// Create properties of a channel that delivers generic \c bool readings. inline SensorChannelProps makeChannelProps_Bool(const char *channelName, const char *quantityName = nullptr) { return {channelName, {quantityName ? quantityName : channelName, ""}, false, true}; } +/// Create properties of a channel that delivers generic \c float readings. inline SensorChannelProps makeChannelProps_Float(const char *channelName, const SensorQuantity &channelQuantity, float rangeMin, @@ -403,6 +498,7 @@ inline SensorChannelProps makeChannelProps_Float(const char *channelName, return {channelName, channelQuantity, rangeMin, rangeMax}; } +/// Create properties of a channel that delivers temperature readings. inline SensorChannelProps makeChannelProps_Temperature(const char *channelName, float rangeMin = 0.0f, float rangeMax = 40.0f) @@ -411,12 +507,14 @@ inline SensorChannelProps makeChannelProps_Temperature(const char *channelName, return {channelName ? channelName : quantity.name, quantity, rangeMin, rangeMax}; } +/// Create properties of a channel that delivers temperature readings. inline SensorChannelProps makeChannelProps_Temperature(float rangeMin = 0.0f, float rangeMax = 40.0f) { return makeChannelProps_Temperature(nullptr, rangeMin, rangeMax); } +/// Create properties of a channel that delivers humidity readings. inline SensorChannelProps makeChannelProps_Humidity(const char *channelName = nullptr) { const auto quantity = SensorQuantity::Humidity(); diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index d547ad8fb4..b11dca1b75 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -324,8 +324,8 @@ class Usermod { virtual void onUpdateBegin(bool) {} // fired prior to and after unsuccessful firmware update virtual void onStateChange(uint8_t mode) {} // fired upon WLED state change virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;} - virtual uint8_t getSensorCount() { return 0; } - virtual Sensor *getSensor(uint8_t index) { return nullptr; } + virtual uint8_t getSensorCount() { return 0; } // get number of provided sensors + virtual Sensor *getSensor(uint8_t index) { return nullptr; } // get a specific sensor; index >= getSensorCount() results in undefined behaviour // API shims private: