From 657541bf9b028f2da6825a7f29403bf875fd603c Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sun, 3 Aug 2025 22:42:59 +0200 Subject: [PATCH 01/33] [AutoDiscover] Add state_class support and some Value Types --- src/src/CustomBuild/define_plugin_sets.h | 12 +++++ src/src/DataStructs/Caches.cpp | 18 ++++++++ src/src/DataStructs/Caches.h | 8 ++++ src/src/DataStructs/DeviceStruct.h | 2 +- .../DataStructs/ExtraTaskSettingsStruct.cpp | 14 ++++++ src/src/DataStructs/ExtraTaskSettingsStruct.h | 6 +++ src/src/DataTypes/SensorVType.cpp | 24 ++++++++-- src/src/DataTypes/SensorVType.h | 4 ++ src/src/Helpers/_CPlugin_Helper_mqtt.cpp | 45 ++++++++++++++++--- src/src/Helpers/_CPlugin_Helper_mqtt.h | 31 +++++++------ src/src/Helpers/_Plugin_SensorTypeHelper.cpp | 18 ++++---- src/src/WebServer/DevicesPage.cpp | 30 +++++++++++++ 12 files changed, 180 insertions(+), 32 deletions(-) diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 75413d8f1..2e14a3bdf 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -3131,6 +3131,18 @@ To create/register a plugin, you have to : #define FEATURE_MQTT_DEVICECLASS 0 #endif +#if !defined(FEATURE_MQTT_STATE_CLASS) && FEATURE_MQTT + #if defined(LIMIT_BUILD_SIZE) || defined(ESP8266) // Must enable this explicitly for ESP8266 Custom build + #define FEATURE_MQTT_STATE_CLASS 0 + #else + #define FEATURE_MQTT_STATE_CLASS 1 + #endif +#endif +#if !FEATURE_MQTT_DISCOVER && FEATURE_MQTT_STATE_CLASS + #undef FEATURE_MQTT_STATE_CLASS + #define FEATURE_MQTT_STATE_CLASS 0 +#endif + #ifndef FEATURE_CUSTOM_TASKVAR_VTYPE #if defined(LIMIT_BUILD_SIZE) || defined(ESP8266) // Must enable this explicitly for ESP8266 Custom build #define FEATURE_CUSTOM_TASKVAR_VTYPE 0 diff --git a/src/src/DataStructs/Caches.cpp b/src/src/DataStructs/Caches.cpp index b3fc6ac5c..4d47b4d11 100644 --- a/src/src/DataStructs/Caches.cpp +++ b/src/src/DataStructs/Caches.cpp @@ -324,6 +324,21 @@ uint8_t Caches::getTaskVarCustomVType(taskIndex_t TaskIndex, #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE +#if FEATURE_MQTT_STATE_CLASS +uint8_t Caches::getTaskVarStateClass(taskIndex_t TaskIndex, + taskVarIndex_t taskVarIndex) { + if (validTaskIndex(TaskIndex) && (validTaskVarIndex(taskVarIndex))) { + auto it = getExtraTaskSettings(TaskIndex); + + if (it != extraTaskSettings_cache.end()) { + return it->second.mqttStateClass[taskVarIndex]; + } + } + return 0; +} + +#endif // if FEATURE_MQTT_STATE_CLASS + void Caches::updateExtraTaskSettingsCache() { const taskIndex_t TaskIndex = ExtraTaskSettings.TaskIndex; @@ -374,6 +389,9 @@ void Caches::updateExtraTaskSettingsCache() #if FEATURE_CUSTOM_TASKVAR_VTYPE tmp.customVType[i] = ExtraTaskSettings.getTaskVarCustomVType(i); #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + #if FEATURE_MQTT_STATE_CLASS + tmp.mqttStateClass[i] = ExtraTaskSettings.getTaskVarStateClass(i); + #endif // if FEATURE_MQTT_STATE_CLASS } #ifdef ESP32 tmp.TaskDevicePluginConfigLong_index_used = 0; diff --git a/src/src/DataStructs/Caches.h b/src/src/DataStructs/Caches.h index 46fcd0f64..36130dfb4 100644 --- a/src/src/DataStructs/Caches.h +++ b/src/src/DataStructs/Caches.h @@ -57,6 +57,9 @@ struct ExtraTaskSettings_cache_t { #if FEATURE_CUSTOM_TASKVAR_VTYPE uint8_t customVType[VARS_PER_TASK] = { 0 }; // single-value VType per taskValue #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + #if FEATURE_MQTT_STATE_CLASS + uint8_t mqttStateClass[VARS_PER_TASK] = { 0 }; // state_class = None, Measurement, Total, TotalIncreasing + #endif // if FEATURE_MQTT_STATE_CLASS }; typedef std::map TaskIndexNameMap; @@ -123,6 +126,11 @@ struct Caches { uint8_t getTaskVarCustomVType(taskIndex_t taskIndex, taskVarIndex_t taskVarIndex); #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + + #if FEATURE_MQTT_STATE_CLASS + uint8_t getTaskVarStateClass(taskIndex_t taskIndex, + taskVarIndex_t taskVarIndex); + #endif // if FEATURE_MQTT_STATE_CLASS // Update all cached values, except the checksum. void updateExtraTaskSettingsCache(); diff --git a/src/src/DataStructs/DeviceStruct.h b/src/src/DataStructs/DeviceStruct.h index 09385a420..be2d3c777 100644 --- a/src/src/DataStructs/DeviceStruct.h +++ b/src/src/DataStructs/DeviceStruct.h @@ -115,7 +115,7 @@ struct DeviceStruct uint32_t I2CNoBusSelection : 1; // Dis-allow I2C Bus selection in device configuration uint32_t CustomVTypeVar : 1; // User-selectable VType per value - uint32_t Dummy22 : 1; // Dummy added to force alignment, can be re-used + uint32_t MqttStateClass : 1; // MQTT StateClass setting in DevicesPage uint32_t Dummy23 : 1; // Dummy added to force alignment, can be re-used uint32_t Dummy24 : 1; // Dummy added to force alignment, can be re-used uint32_t Dummy25 : 1; // Dummy added to force alignment, can be re-used diff --git a/src/src/DataStructs/ExtraTaskSettingsStruct.cpp b/src/src/DataStructs/ExtraTaskSettingsStruct.cpp index 1a474eafd..8cfc37c6a 100644 --- a/src/src/DataStructs/ExtraTaskSettingsStruct.cpp +++ b/src/src/DataStructs/ExtraTaskSettingsStruct.cpp @@ -290,6 +290,20 @@ void ExtraTaskSettingsStruct::setTaskVarCustomVType(taskVarIndex_t taskVarIndex, } #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE +#if FEATURE_MQTT_STATE_CLASS +uint8_t ExtraTaskSettingsStruct::getTaskVarStateClass(taskVarIndex_t taskVarIndex) const { + if (!validTaskVarIndex(taskVarIndex)) { return 0u; } + return get3BitFromUL(VariousBits[taskVarIndex], 24); // 3 Bits 24, 25, 26 +} + +void ExtraTaskSettingsStruct::setTaskVarStateClass(taskVarIndex_t taskVarIndex, + uint8_t stateClass) { + if (validTaskVarIndex(taskVarIndex)) { + set3BitToUL(VariousBits[taskVarIndex], 24, stateClass); // 3 Bits 24, 25, 26 + } +} +#endif // if FEATURE_MQTT_STATE_CLASS + void ExtraTaskSettingsStruct::populateDeviceValueNamesSeq( const __FlashStringHelper *valuename, diff --git a/src/src/DataStructs/ExtraTaskSettingsStruct.h b/src/src/DataStructs/ExtraTaskSettingsStruct.h index 687ebe557..95c41cedb 100644 --- a/src/src/DataStructs/ExtraTaskSettingsStruct.h +++ b/src/src/DataStructs/ExtraTaskSettingsStruct.h @@ -84,6 +84,12 @@ struct ExtraTaskSettingsStruct uint8_t customVType); #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + #if FEATURE_MQTT_STATE_CLASS + uint8_t getTaskVarStateClass(taskVarIndex_t taskVarIndex) const; + void setTaskVarStateClass(taskVarIndex_t taskVarIndex, + uint8_t stateClass); + #endif // if FEATURE_MQTT_STATE_CLASS + void populateDeviceValueNamesSeq(const __FlashStringHelper *valuename, size_t nrValues, uint8_t defaultDecimals, diff --git a/src/src/DataTypes/SensorVType.cpp b/src/src/DataTypes/SensorVType.cpp index 5081a0b67..277f47589 100644 --- a/src/src/DataTypes/SensorVType.cpp +++ b/src/src/DataTypes/SensorVType.cpp @@ -97,6 +97,10 @@ uint8_t getValueCountFromSensorType(Sensor_VType sensorType, bool log) case Sensor_VType::SENSOR_TYPE_DATA_SIZE: case Sensor_VType::SENSOR_TYPE_SOUND_PRESSURE: case Sensor_VType::SENSOR_TYPE_SIGNAL_STRENGTH: + case Sensor_VType::SENSOR_TYPE_REACTIVE_ENERGY: + case Sensor_VType::SENSOR_TYPE_FREQUENCY: + case Sensor_VType::SENSOR_TYPE_ENERGY: + case Sensor_VType::SENSOR_TYPE_ENERGY_STORAGE: return 1; } #ifndef BUILD_NO_DEBUG @@ -183,6 +187,10 @@ const __FlashStringHelper* getSensorTypeLabel(Sensor_VType sensorType) { case Sensor_VType::SENSOR_TYPE_DATA_SIZE: return F("Data size"); case Sensor_VType::SENSOR_TYPE_SOUND_PRESSURE: return F("Sound pressure"); case Sensor_VType::SENSOR_TYPE_SIGNAL_STRENGTH: return F("Signal strength"); + case Sensor_VType::SENSOR_TYPE_REACTIVE_ENERGY: return F("Reactive Energy"); + case Sensor_VType::SENSOR_TYPE_FREQUENCY: return F("Frequency"); + case Sensor_VType::SENSOR_TYPE_ENERGY: return F("Energy"); + case Sensor_VType::SENSOR_TYPE_ENERGY_STORAGE: return F("Energy storage"); #else // if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE case Sensor_VType::SENSOR_TYPE_ANALOG_ONLY: case Sensor_VType::SENSOR_TYPE_TEMP_ONLY: @@ -223,6 +231,10 @@ const __FlashStringHelper* getSensorTypeLabel(Sensor_VType sensorType) { case Sensor_VType::SENSOR_TYPE_DATA_SIZE: case Sensor_VType::SENSOR_TYPE_SOUND_PRESSURE: case Sensor_VType::SENSOR_TYPE_SIGNAL_STRENGTH: + case Sensor_VType::SENSOR_TYPE_REACTIVE_ENERGY: + case Sensor_VType::SENSOR_TYPE_FREQUENCY: + case Sensor_VType::SENSOR_TYPE_ENERGY: + case Sensor_VType::SENSOR_TYPE_ENERGY_STORAGE: break; #endif // if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE } @@ -312,6 +324,10 @@ bool isMQTTDiscoverySensorType(Sensor_VType sensorType) case Sensor_VType::SENSOR_TYPE_DATA_SIZE: case Sensor_VType::SENSOR_TYPE_SOUND_PRESSURE: case Sensor_VType::SENSOR_TYPE_SIGNAL_STRENGTH: + case Sensor_VType::SENSOR_TYPE_REACTIVE_ENERGY: + case Sensor_VType::SENSOR_TYPE_FREQUENCY: + case Sensor_VType::SENSOR_TYPE_ENERGY: + case Sensor_VType::SENSOR_TYPE_ENERGY_STORAGE: return true; } return false; @@ -410,9 +426,10 @@ const char mqtt_valueType_ha_deviceclass_names[] PROGMEM = // !! Offset, startin "irradiance|irradiance|irradiance|mdi:scale|" // UV_ONLY .. WEIGHT_ONLY "voltage|current|power|power_factor|power|" // VOLTAGE_ONLY .. APPRNT_POWER_USG_ONLY "volatile_organic_compounds|pressure|mdi:palette|mdi:palette|mdi:palette|" // TVOC_ONLY .. COLOR_BLUE_ONLY - "mdi:temperature-kelvin|power|aqi|nitrogen_dioxide|" // COLOR_TEMP_ONLY .. NOX_ONLY + "mdi:temperature-kelvin|reactive_power|aqi|nitrogen_dioxide|" // COLOR_TEMP_ONLY .. NOX_ONLY "|wind_speed|duration|date|" // SWITCH_INVERTED .. DATE - "timestamp|data_rate|data_size|sound_pressure|signal_strength|datetime|" // TIMESTAMP .. DATETIME + "timestamp|data_rate|data_size|sound_pressure|signal_strength|reactive_energy|" // TIMESTAMP .. REACTIVE_ENERGY + "frequency|energy|energy_storage|" // FREQUENCY .. ENERGY_STORAGE ; /** @@ -446,7 +463,8 @@ const char mqtt_valueType_default_ha_uom_names[] PROGMEM = // !! Offset, startin "ppd|hPa|lx|lx|lx|" // TVOC_ONLY .. COLOR_BLUE_ONLY "K|var||µg/m³|" // COLOR_TEMP_ONLY .. NOX_ONLY "|m/s|min||" // SWITCH_INVERTED .. DATE - "|bit/s|B|dB|dBm||" // TIMESTAMP .. DATETIME + "|bit/s|B|dB|dBm|kvar|" // TIMESTAMP .. REACTIVE_ENERGY + "Hz|kWh|kWh|" // FREQUENCY .. ENERGY_STORAGE ; /** diff --git a/src/src/DataTypes/SensorVType.h b/src/src/DataTypes/SensorVType.h index 812ea0e78..46c68daca 100644 --- a/src/src/DataTypes/SensorVType.h +++ b/src/src/DataTypes/SensorVType.h @@ -75,6 +75,10 @@ enum class Sensor_VType : uint8_t { SENSOR_TYPE_DATA_SIZE = 136, SENSOR_TYPE_SOUND_PRESSURE = 137, SENSOR_TYPE_SIGNAL_STRENGTH = 138, + SENSOR_TYPE_REACTIVE_ENERGY = 139, + SENSOR_TYPE_FREQUENCY = 140, + SENSOR_TYPE_ENERGY = 141, + SENSOR_TYPE_ENERGY_STORAGE = 142, SENSOR_TYPE_NOT_SET = 255 }; diff --git a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp index 79df1977d..8d79d12d0 100644 --- a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp +++ b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp @@ -448,6 +448,21 @@ bool MQTT_binary_deviceClassTwoWay(int devClassIndex) { # endif // if FEATURE_MQTT_DEVICECLASS +# if FEATURE_MQTT_STATE_CLASS +const __FlashStringHelper* MQTT_sensor_StateClass(uint8_t index, + bool display) { + switch (index) { + case 0: return F(""); + case 1: return display ? F("Measurement") : F("measurement"); + case 2: return display ? F("Total") : F("total"); + case 3: return display ? F("Total-increasing") : F("total_increasing"); + case 4: return display ? F("Measurement-angle") : F("measurement_angle"); + } + return F(""); +} + +# endif // if FEATURE_MQTT_STATE_CLASS + bool MQTT_SendAutoDiscovery(controllerIndex_t ControllerIndex, cpluginID_t CPluginID) { bool success = true; @@ -521,7 +536,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat(F("MQTT : Start AutoDiscovery for task %d, %s max. %d value%c"), - x + 1, taskName.c_str(), valueCount, 1 == valueCount ? 's' : ' ')); + x + 1, taskName.c_str(), valueCount, 1 != valueCount ? 's' : ' ')); } String pluginDeviceClass; @@ -616,7 +631,8 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn String valueDeviceClass = parseStringKeepCase(pluginDeviceClass, v + 1); // Device classes per value if (valueDeviceClass.isEmpty()) { valueDeviceClass = F("power"); } // default - const bool twoWay = MQTT_binary_deviceClassTwoWay(MQTT_binary_deviceClassIndex(valueDeviceClass)); + const String stateClass = MQTT_sensor_StateClass(Cache.getTaskVarStateClass(x, v), false); + const bool twoWay = MQTT_binary_deviceClassTwoWay(MQTT_binary_deviceClassIndex(valueDeviceClass)); // Discover 2-way as Light const __FlashStringHelper*componentClass = twoWay && discoveryItems[s].canSet ? F("light") : F("binary_sensor"); @@ -633,6 +649,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn EMPTY_STRING, // No unit of measure used &TempEvent, deviceElement, + EMPTY_STRING, success, true, false, useGroupId ? elementName : EMPTY_STRING, elementIds, @@ -646,6 +663,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn uom, &TempEvent, deviceElement, + stateClass, success, discoveryItems[s].canSet, false, useGroupId ? elementName : EMPTY_STRING, elementIds); @@ -676,6 +694,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn const String valuename = MQTT_DiscoveryHelperGetValueName(x, v, discoveryItems[s]); const String uom = MQTT_DiscoveryHelperGetValueUoM(x, v, discoveryItems[s], getValueType2DefaultHAUoM(Sensor_VType::SENSOR_TYPE_TEMP_ONLY)); + const String stateClass = MQTT_sensor_StateClass(Cache.getTaskVarStateClass(x, v), false); success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, valuename, ControllerIndex, ControllerSettings, @@ -684,6 +703,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn uom, &TempEvent, deviceElement, + stateClass, success, discoveryItems[s].canSet, false, useGroupId ? elementName : EMPTY_STRING, elementIds); @@ -705,6 +725,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn const String valuename = MQTT_DiscoveryHelperGetValueName(x, v, discoveryItems[s]); const String uom = MQTT_DiscoveryHelperGetValueUoM(x, v, discoveryItems[s], getValueType2DefaultHAUoM(Sensor_VType::SENSOR_TYPE_HUM_ONLY)); + const String stateClass = MQTT_sensor_StateClass(Cache.getTaskVarStateClass(x, v), false); success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, valuename, ControllerIndex, ControllerSettings, @@ -713,6 +734,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn uom, &TempEvent, deviceElement, + stateClass, success, discoveryItems[s].canSet, false, useGroupId ? elementName : EMPTY_STRING, elementIds); @@ -738,6 +760,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn const String valuename = MQTT_DiscoveryHelperGetValueName(x, v, discoveryItems[s]); const String uom = MQTT_DiscoveryHelperGetValueUoM(x, v, discoveryItems[s], getValueType2DefaultHAUoM(Sensor_VType::SENSOR_TYPE_BARO_ONLY)); + const String stateClass = MQTT_sensor_StateClass(Cache.getTaskVarStateClass(x, v), false); success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, valuename, ControllerIndex, ControllerSettings, @@ -746,6 +769,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn uom, &TempEvent, deviceElement, + stateClass, success, discoveryItems[s].canSet, false, useGroupId ? elementName : EMPTY_STRING, elementIds); @@ -788,13 +812,18 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn case Sensor_VType::SENSOR_TYPE_DATA_SIZE: case Sensor_VType::SENSOR_TYPE_SOUND_PRESSURE: case Sensor_VType::SENSOR_TYPE_SIGNAL_STRENGTH: + case Sensor_VType::SENSOR_TYPE_REACTIVE_ENERGY: + case Sensor_VType::SENSOR_TYPE_FREQUENCY: + case Sensor_VType::SENSOR_TYPE_ENERGY: + case Sensor_VType::SENSOR_TYPE_ENERGY_STORAGE: { const String dev = getValueType2HADeviceClass(discoveryItems[s].VType); const String uomDef = getValueType2DefaultHAUoM(discoveryItems[s].VType); for (uint8_t v = discoveryItems[s].varIndex; v < varCount; ++v) { - const String valuename = MQTT_DiscoveryHelperGetValueName(x, v, discoveryItems[s]); - const String uom = MQTT_DiscoveryHelperGetValueUoM(x, v, discoveryItems[s], uomDef); + const String valuename = MQTT_DiscoveryHelperGetValueName(x, v, discoveryItems[s]); + const String uom = MQTT_DiscoveryHelperGetValueUoM(x, v, discoveryItems[s], uomDef); + const String stateClass = MQTT_sensor_StateClass(Cache.getTaskVarStateClass(x, v), false); success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, valuename, ControllerIndex, ControllerSettings, @@ -803,6 +832,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn uom, &TempEvent, deviceElement, + stateClass, success, discoveryItems[s].canSet, false, useGroupId ? elementName : EMPTY_STRING, elementIds); @@ -1005,6 +1035,7 @@ bool MQTT_DiscoveryPublishWithStatusAndSet(taskIndex_t taskIndex, String unitOfMeasure, struct EventStruct *event, const String deviceElement, + const String stateClass, bool success, bool hasSet, bool hasIcon, @@ -1018,6 +1049,8 @@ bool MQTT_DiscoveryPublishWithStatusAndSet(taskIndex_t taskIndex, const String withUoM = !unitOfMeasure.isEmpty() ? strformat(F(",\"unit_of_meas\":\"%s\""), unitOfMeasure.c_str()) : EMPTY_STRING; + const String stateJson = stateClass.isEmpty() ? EMPTY_STRING : + strformat(F(",\"stat_cla\":\"%s\""), stateClass.c_str()); const String taskName = makeHomeAssistantCompliantName(getTaskDeviceName(taskIndex)); const String valName = makeHomeAssistantCompliantName(valueName); const bool retainDsc = ControllerSettings.mqtt_retainDiscovery(); @@ -1042,10 +1075,10 @@ bool MQTT_DiscoveryPublishWithStatusAndSet(taskIndex_t taskIndex, elementId, valName); const String discoveryMessage = strformat(F("{\"~\":\"%s\",\"name\":\"%s %s\",\"uniq_id\":\"%s\",%s" - "\"%s\":\"%s\"%s%s,\"stat_t\":\"~\"" + "\"%s\":\"%s\"%s%s%s,\"stat_t\":\"~\"" "%s}"), // deviceElement last publish.c_str(), taskName.c_str(), valName.c_str(), uniqueId.c_str(), schema.c_str(), - devOrIcon.c_str(), deviceClass.c_str(), withUoM.c_str(), withSet.c_str(), + devOrIcon.c_str(), deviceClass.c_str(), withUoM.c_str(), stateJson.c_str(), withSet.c_str(), deviceElement.c_str()); const String triggerMessage = strformat(F("{\"atype\":\"trigger\",\"t\":\"%s\"," "\"p\":\"device_automation\"," diff --git a/src/src/Helpers/_CPlugin_Helper_mqtt.h b/src/src/Helpers/_CPlugin_Helper_mqtt.h index c34d7512b..c636efa71 100644 --- a/src/src/Helpers/_CPlugin_Helper_mqtt.h +++ b/src/src/Helpers/_CPlugin_Helper_mqtt.h @@ -23,23 +23,27 @@ bool getDiscoveryVType(struct EventStruct *event, uint8_t pConfigOffset, uint8_t nrVars); -int Plugin_QueryVType_BinarySensor(uint8_t value_nr); -int Plugin_QueryVType_BinarySensorInv(uint8_t value_nr); -int Plugin_QueryVType_Analog(uint8_t value_nr); -int Plugin_QueryVType_CO2(uint8_t value_nr); -int Plugin_QueryVType_Distance(uint8_t value_nr); -int Plugin_QueryVType_DustPM2_5(uint8_t value_nr); -int Plugin_QueryVType_Lux(uint8_t value_nr); -int Plugin_QueryVType_Temperature(uint8_t value_nr); -int Plugin_QueryVType_Weight(uint8_t value_nr); +int Plugin_QueryVType_BinarySensor(uint8_t value_nr); +int Plugin_QueryVType_BinarySensorInv(uint8_t value_nr); +int Plugin_QueryVType_Analog(uint8_t value_nr); +int Plugin_QueryVType_CO2(uint8_t value_nr); +int Plugin_QueryVType_Distance(uint8_t value_nr); +int Plugin_QueryVType_DustPM2_5(uint8_t value_nr); +int Plugin_QueryVType_Lux(uint8_t value_nr); +int Plugin_QueryVType_Temperature(uint8_t value_nr); +int Plugin_QueryVType_Weight(uint8_t value_nr); -String makeHomeAssistantCompliantName(const String& name); +String makeHomeAssistantCompliantName(const String& name); # if FEATURE_MQTT_DEVICECLASS -String MQTT_binary_deviceClassName(int devClassIndex); -bool MQTT_binary_deviceClassTwoWay(int devClassIndex); -int MQTT_binary_deviceClassIndex(const String& deviceClassName); +String MQTT_binary_deviceClassName(int devClassIndex); +bool MQTT_binary_deviceClassTwoWay(int devClassIndex); +int MQTT_binary_deviceClassIndex(const String& deviceClassName); # endif // if FEATURE_MQTT_DEVICECLASS +# if FEATURE_MQTT_STATE_CLASS +const __FlashStringHelper* MQTT_sensor_StateClass(uint8_t index, + bool display = true); +# endif // if FEATURE_MQTT_STATE_CLASS struct DiscoveryItem { DiscoveryItem(Sensor_VType _VType, int _valueCount, taskVarIndex_t _varIndex, const bool _canSet = false) : VType(_VType), valueCount(_valueCount), varIndex(_varIndex), canSet(_canSet) {} @@ -96,6 +100,7 @@ bool MQTT_DiscoveryPublishWithStatusAndSet(taskIndex_t taskIndex, String unitOfMeasure, struct EventStruct *event, const String deviceElement, + const String stateClass, bool success, bool hasSet, bool hasIcon, diff --git a/src/src/Helpers/_Plugin_SensorTypeHelper.cpp b/src/src/Helpers/_Plugin_SensorTypeHelper.cpp index 95be958da..cf7cc1558 100644 --- a/src/src/Helpers/_Plugin_SensorTypeHelper.cpp +++ b/src/src/Helpers/_Plugin_SensorTypeHelper.cpp @@ -97,15 +97,15 @@ String toValueTypeCategory(const uint32_t valueType) { /* *INDENT-OFF* */ const uint16_t value_type_map[] PROGMEM = { - 0, // None - 1024, 1, 5, 6, 7, // Basic - 1025, 2, 3, 4, 8, 101, 102, 122, 21, 131, // Environment - 1026, 106, 107, 108, 110, 121, 128, 129, // Gases - 1027, 116, 117, 118, 119, 120, 127, // Energy - 1028, 132, 133, 134, // Time - 1029, 100, 104, 105, 109, 111, 115, 135, 136, 137, 138, // Size - 1030, 103, 112, 113, 114, 123, 124, 125, 126, // Light - 1031, 10, 130, 11, 22, 20, 31, 32, 33, 40, 41, 42, 43, 50, 51, 60, 61, 70, 71, // Other + 0, // None + 1024, 1, 5, 6, 7, // Basic + 1025, 2, 3, 4, 8, 101, 102, 122, 21, 131, // Environment + 1026, 106, 107, 108, 110, 121, 128, 129, // Gases + 1027, 116, 117, 118, 119, 120, 127, 139, 141, 142, // Energy + 1028, 132, 133, 134, // Time + 1029, 100, 104, 105, 109, 111, 115, 135, 136, 137, 138, // Size + 1030, 103, 112, 113, 114, 123, 124, 125, 126, // Light + 1031, 10, 130, 11, 22, 20, 31, 32, 33, 40, 41, 42, 43, 50, 51, 60, 61, 70, 71, 140, // Other }; /* *INDENT-ON* */ diff --git a/src/src/WebServer/DevicesPage.cpp b/src/src/WebServer/DevicesPage.cpp index 35c3b0435..9b6e9e0f8 100644 --- a/src/src/WebServer/DevicesPage.cpp +++ b/src/src/WebServer/DevicesPage.cpp @@ -457,6 +457,10 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task #if FEATURE_CUSTOM_TASKVAR_VTYPE ExtraTaskSettings.setTaskVarCustomVType(varNr, getFormItemInt(getPluginCustomArgName(F("TDTV"), varNr))); #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + + #if FEATURE_MQTT_STATE_CLASS + ExtraTaskSettings.setTaskVarStateClass(varNr, getFormItemInt(getPluginCustomArgName(F("TDSC"), varNr))); + #endif // if FEATURE_MQTT_STATE_CLASS } ExtraTaskSettings.clearUnusedValueNames(valueCount); @@ -1720,6 +1724,13 @@ void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceInde } #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + #if FEATURE_MQTT_STATE_CLASS + if (device.MqttStateClass) { + html_table_header(F("MQTT State Class"), 350); + ++colCount; + } + #endif // if FEATURE_MQTT_STATE_CLASS + // placeholder header html_table_header(F("")); @@ -1822,6 +1833,25 @@ void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceInde static_cast(Cache.getTaskVarCustomVType(taskIndex, varNr))); } #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + + #if FEATURE_MQTT_STATE_CLASS + if (device.MqttStateClass) { + html_TD(); + const __FlashStringHelper *stateClasses[] = { + MQTT_sensor_StateClass(0), + MQTT_sensor_StateClass(1), + MQTT_sensor_StateClass(4), + MQTT_sensor_StateClass(2), + MQTT_sensor_StateClass(3), + }; + + constexpr size_t stateCount = NR_ELEMENTS(stateClasses); + const FormSelectorOptions selectorSC(stateCount, stateClasses); + selectorSC.addSelector( + getPluginCustomArgName(F("TDSC"), varNr), + Cache.getTaskVarStateClass(taskIndex, varNr)); + } + #endif // if FEATURE_MQTT_STATE_CLASS } addFormSeparator(colCount); } From b89a1828c20e8bdf680f1296bf4c6158aa845b69 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sun, 3 Aug 2025 22:46:54 +0200 Subject: [PATCH 02/33] [AutoDiscover] Enable state_class support for plugins and correct a few Value Types --- src/_P026_Sysinfo.ino | 1 + src/_P033_Dummy.ino | 1 + src/_P076_HLW8012.ino | 3 ++- src/_P077_CSE7766.ino | 3 ++- src/_P078_Eastron.ino | 5 +++-- src/_P085_AcuDC243.ino | 3 ++- src/_P093_MitsubishiHP.ino | 1 + src/_P102_PZEM004Tv3.ino | 5 +++-- src/_P108_DDS238.ino | 1 + src/_P176_VE_Direct.ino | 1 + src/src/PluginStructs/P077_data_struct.cpp | 2 +- src/src/PluginStructs/P078_data_struct.cpp | 6 +++--- src/src/PluginStructs/P085_data_struct.cpp | 12 ++++++------ src/src/PluginStructs/P108_data_struct.cpp | 8 ++++---- 14 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/_P026_Sysinfo.ino b/src/_P026_Sysinfo.ino index a70a5ab9c..6c17fbe5e 100644 --- a/src/_P026_Sysinfo.ino +++ b/src/_P026_Sysinfo.ino @@ -44,6 +44,7 @@ boolean Plugin_026(uint8_t function, struct EventStruct *event, String& string) dev.OutputDataType = Output_Data_type_t::Simple; dev.PluginStats = true; dev.CustomVTypeVar = true; + dev.MqttStateClass = true; break; } diff --git a/src/_P033_Dummy.ino b/src/_P033_Dummy.ino index 70559aade..79fc3336b 100644 --- a/src/_P033_Dummy.ino +++ b/src/_P033_Dummy.ino @@ -34,6 +34,7 @@ boolean Plugin_033(uint8_t function, struct EventStruct *event, String& string) dev.OutputDataType = Output_Data_type_t::All; dev.PluginStats = true; dev.CustomVTypeVar = true; + dev.MqttStateClass = true; break; } diff --git a/src/_P076_HLW8012.ino b/src/_P076_HLW8012.ino index 6c54ab19a..5d01c277f 100644 --- a/src/_P076_HLW8012.ino +++ b/src/_P076_HLW8012.ino @@ -230,6 +230,7 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string) dev.PluginStats = true; dev.setPin1Direction(gpio_direction::gpio_output); dev.OutputDataType = Output_Data_type_t::Simple; + dev.MqttStateClass = true; // dev.TaskLogsOwnPeaks = true; break; @@ -953,7 +954,7 @@ int Plugin_076_QueryVType(uint8_t value_nr) { case P076_INDEX_PF: result = Sensor_VType::SENSOR_TYPE_POWER_FACT_ONLY; break; case P076_INDEX_ENER: - result = Sensor_VType::SENSOR_TYPE_NONE; break; // FIXME Add support for Energy + Unit of Measure + result = Sensor_VType::SENSOR_TYPE_ENERGY; break; } return static_cast(result); } diff --git a/src/_P077_CSE7766.ino b/src/_P077_CSE7766.ino index d707cef9b..4d79ba126 100644 --- a/src/_P077_CSE7766.ino +++ b/src/_P077_CSE7766.ino @@ -55,6 +55,7 @@ boolean Plugin_077(uint8_t function, struct EventStruct *event, String& string) dev.TimerOptional = true; dev.PluginStats = true; dev.TaskLogsOwnPeaks = true; + dev.MqttStateClass = true; break; } @@ -96,7 +97,7 @@ boolean Plugin_077(uint8_t function, struct EventStruct *event, String& string) # if FEATURE_MQTT_DISCOVER case PLUGIN_GET_DISCOVERY_VTYPES: { - success = getDiscoveryVType(event, Plugin_077_QueryVType, P077_QUERY1_CONFIG_POS, event->Par5);; + success = getDiscoveryVType(event, Plugin_077_QueryVType, P077_QUERY1_CONFIG_POS, event->Par5); break; } # endif // if FEATURE_MQTT_DISCOVER diff --git a/src/_P078_Eastron.ino b/src/_P078_Eastron.ino index bd5510abb..cda6e2d39 100644 --- a/src/_P078_Eastron.ino +++ b/src/_P078_Eastron.ino @@ -56,6 +56,7 @@ boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string) dev.TimerOption = true; dev.PluginStats = true; dev.TaskLogsOwnPeaks = true; + dev.MqttStateClass = true; break; } @@ -194,9 +195,9 @@ boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string) F("SDM320C") }; constexpr size_t nrOptions = NR_ELEMENTS(options_model); - const FormSelectorOptions selector(nrOptions, options_model); + FormSelectorOptions selector(nrOptions, options_model); + selector.reloadonchange = true; selector.addFormSelector(F("Model Type"), P078_MODEL_LABEL, P078_MODEL); - addFormNote(F("Submit after changing the modell to update Output Configuration.")); } success = true; break; diff --git a/src/_P085_AcuDC243.ino b/src/_P085_AcuDC243.ino index 476577592..54b40c232 100644 --- a/src/_P085_AcuDC243.ino +++ b/src/_P085_AcuDC243.ino @@ -43,6 +43,7 @@ boolean Plugin_085(uint8_t function, struct EventStruct *event, String& string) dev.TimerOption = true; dev.ExitTaskBeforeSave = false; dev.PluginStats = true; + dev.MqttStateClass = true; dev.setPin3Direction(gpio_direction::gpio_output); break; } @@ -74,7 +75,7 @@ boolean Plugin_085(uint8_t function, struct EventStruct *event, String& string) # if FEATURE_MQTT_DISCOVER case PLUGIN_GET_DISCOVERY_VTYPES: { - success = getDiscoveryVType(event, Plugin_085_QueryVType, P085_QUERY1_CONFIG_POS, event->Par5);; + success = getDiscoveryVType(event, Plugin_085_QueryVType, P085_QUERY1_CONFIG_POS, event->Par5); break; } # endif // if FEATURE_MQTT_DISCOVER diff --git a/src/_P093_MitsubishiHP.ino b/src/_P093_MitsubishiHP.ino index bf32a9f4f..364ddca82 100644 --- a/src/_P093_MitsubishiHP.ino +++ b/src/_P093_MitsubishiHP.ino @@ -55,6 +55,7 @@ boolean Plugin_093(uint8_t function, struct EventStruct *event, String& string) dev.TimerOption = true; dev.TimerOptional = true; dev.CustomVTypeVar = true; + dev.MqttStateClass = true; break; } diff --git a/src/_P102_PZEM004Tv3.ino b/src/_P102_PZEM004Tv3.ino index 545d5471e..acb36395b 100644 --- a/src/_P102_PZEM004Tv3.ino +++ b/src/_P102_PZEM004Tv3.ino @@ -71,6 +71,7 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even dev.OutputDataType = Output_Data_type_t::Simple; dev.SendDataOption = true; dev.TimerOption = true; + dev.MqttStateClass = true; break; } @@ -405,9 +406,9 @@ int Plugin_102_QueryVType(uint8_t query) { case 0: result = Sensor_VType::SENSOR_TYPE_VOLTAGE_ONLY; break; case 1: result = Sensor_VType::SENSOR_TYPE_CURRENT_ONLY; break; case 2: result = Sensor_VType::SENSOR_TYPE_POWER_USG_ONLY; break; - case 3: result = Sensor_VType::SENSOR_TYPE_NONE; break; // FIXME + case 3: result = Sensor_VType::SENSOR_TYPE_ENERGY; break; case 4: result = Sensor_VType::SENSOR_TYPE_POWER_FACT_ONLY; break; - case 5: result = Sensor_VType::SENSOR_TYPE_NONE; break; + case 5: result = Sensor_VType::SENSOR_TYPE_FREQUENCY; break; } return static_cast(result); } diff --git a/src/_P108_DDS238.ino b/src/_P108_DDS238.ino index c4d9052a0..761154f5e 100644 --- a/src/_P108_DDS238.ino +++ b/src/_P108_DDS238.ino @@ -51,6 +51,7 @@ boolean Plugin_108(uint8_t function, struct EventStruct *event, String& string) dev.SendDataOption = true; dev.TimerOption = true; dev.PluginStats = true; + dev.MqttStateClass = true; dev.setPin3Direction(gpio_direction::gpio_output); break; } diff --git a/src/_P176_VE_Direct.ino b/src/_P176_VE_Direct.ino index a48d601bc..c603767a5 100644 --- a/src/_P176_VE_Direct.ino +++ b/src/_P176_VE_Direct.ino @@ -58,6 +58,7 @@ boolean Plugin_176(uint8_t function, struct EventStruct *event, String& string) dev.TimerOptional = true; dev.PluginStats = true; dev.CustomVTypeVar = true; + dev.MqttStateClass = true; break; } diff --git a/src/src/PluginStructs/P077_data_struct.cpp b/src/src/PluginStructs/P077_data_struct.cpp index d2dcc1616..6e10b4dc4 100644 --- a/src/src/PluginStructs/P077_data_struct.cpp +++ b/src/src/PluginStructs/P077_data_struct.cpp @@ -33,7 +33,7 @@ int Plugin_077_QueryVType(uint8_t value_nr) { Sensor_VType::SENSOR_TYPE_POWER_USG_ONLY, Sensor_VType::SENSOR_TYPE_CURRENT_ONLY, Sensor_VType::SENSOR_TYPE_NONE, - Sensor_VType::SENSOR_TYPE_POWER_USG_ONLY, + Sensor_VType::SENSOR_TYPE_ENERGY, Sensor_VType::SENSOR_TYPE_APPRNT_POWER_USG_ONLY, Sensor_VType::SENSOR_TYPE_POWER_FACT_ONLY, Sensor_VType::SENSOR_TYPE_REACTIVE_POWER_ONLY, diff --git a/src/src/PluginStructs/P078_data_struct.cpp b/src/src/PluginStructs/P078_data_struct.cpp index 1f24ec9a6..eb60408ce 100644 --- a/src/src/PluginStructs/P078_data_struct.cpp +++ b/src/src/PluginStructs/P078_data_struct.cpp @@ -218,15 +218,15 @@ Sensor_VType Plugin_078_QueryVType(SDM_MODEL model, int choice) { Sensor_VType::SENSOR_TYPE_VOLTAGE_ONLY, Sensor_VType::SENSOR_TYPE_CURRENT_ONLY, Sensor_VType::SENSOR_TYPE_POWER_USG_ONLY, - Sensor_VType::SENSOR_TYPE_NONE, // FIXME Active energy + Sensor_VType::SENSOR_TYPE_ENERGY, // Active energy Sensor_VType::SENSOR_TYPE_NONE, // FIXME Ah - Sensor_VType::SENSOR_TYPE_NONE, // FIXME Frequency + Sensor_VType::SENSOR_TYPE_FREQUENCY, // Frequency Sensor_VType::SENSOR_TYPE_NONE, // FIXME Phase angle Sensor_VType::SENSOR_TYPE_POWER_FACT_ONLY, Sensor_VType::SENSOR_TYPE_APPRNT_POWER_USG_ONLY, Sensor_VType::SENSOR_TYPE_REACTIVE_POWER_ONLY, Sensor_VType::SENSOR_TYPE_NONE, // FIXME Apparent energy - Sensor_VType::SENSOR_TYPE_NONE, // FIXME Reactive energy + Sensor_VType::SENSOR_TYPE_REACTIVE_ENERGY, // Reactive energy }; constexpr uint8_t valueCount = NR_ELEMENTS(vtypes); diff --git a/src/src/PluginStructs/P085_data_struct.cpp b/src/src/PluginStructs/P085_data_struct.cpp index d08afb79d..8d6102900 100644 --- a/src/src/PluginStructs/P085_data_struct.cpp +++ b/src/src/PluginStructs/P085_data_struct.cpp @@ -38,12 +38,12 @@ int Plugin_085_QueryVType(uint8_t value_nr) { case P085_QUERY_V: result = Sensor_VType::SENSOR_TYPE_VOLTAGE_ONLY; break; case P085_QUERY_A: result = Sensor_VType::SENSOR_TYPE_CURRENT_ONLY; break; case P085_QUERY_W: result = Sensor_VType::SENSOR_TYPE_POWER_USG_ONLY; break; - case P085_QUERY_Wh_imp: // return Sensor_VType::SENSOR_TYPE_NONE; // FIXME - case P085_QUERY_Wh_exp: // return Sensor_VType::SENSOR_TYPE_NONE; // FIXME - case P085_QUERY_Wh_tot: // return Sensor_VType::SENSOR_TYPE_NONE; // FIXME - case P085_QUERY_Wh_net: // return Sensor_VType::SENSOR_TYPE_NONE; // FIXME - case P085_QUERY_h_tot: // return Sensor_VType::SENSOR_TYPE_NONE; // FIXME - case P085_QUERY_h_load: result = Sensor_VType::SENSOR_TYPE_NONE; break; // FIXME + case P085_QUERY_Wh_imp: + case P085_QUERY_Wh_exp: + case P085_QUERY_Wh_tot: // Fall through + case P085_QUERY_Wh_net: result = Sensor_VType::SENSOR_TYPE_ENERGY; break; + case P085_QUERY_h_tot: // Fall through + case P085_QUERY_h_load: result = Sensor_VType::SENSOR_TYPE_DURATION; break; } return static_cast(result); } diff --git a/src/src/PluginStructs/P108_data_struct.cpp b/src/src/PluginStructs/P108_data_struct.cpp index afbe42e92..c68575369 100644 --- a/src/src/PluginStructs/P108_data_struct.cpp +++ b/src/src/PluginStructs/P108_data_struct.cpp @@ -39,10 +39,10 @@ int Plugin_108_QueryVType(uint8_t value_nr) { Sensor_VType::SENSOR_TYPE_POWER_USG_ONLY, Sensor_VType::SENSOR_TYPE_REACTIVE_POWER_ONLY, Sensor_VType::SENSOR_TYPE_POWER_FACT_ONLY, - Sensor_VType::SENSOR_TYPE_NONE, // FIXME To implement Frequency and Energy options - Sensor_VType::SENSOR_TYPE_NONE, - Sensor_VType::SENSOR_TYPE_NONE, - Sensor_VType::SENSOR_TYPE_NONE, + Sensor_VType::SENSOR_TYPE_FREQUENCY, + Sensor_VType::SENSOR_TYPE_ENERGY, + Sensor_VType::SENSOR_TYPE_ENERGY, + Sensor_VType::SENSOR_TYPE_ENERGY, }; constexpr uint8_t valueCount = NR_ELEMENTS(vtypes); Sensor_VType result = Sensor_VType::SENSOR_TYPE_NONE; From f087621bd8cc642f192e285b7a5d3e1ad3f7c311 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sun, 3 Aug 2025 22:47:14 +0200 Subject: [PATCH 03/33] [AutoDiscover] Update documentation --- .../Task_config_page_Values_section.png | Bin 15858 -> 18402 bytes docs/source/Plugin/_Plugin.rst | 14 ++++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/source/Plugin/Task_config_page_Values_section.png b/docs/source/Plugin/Task_config_page_Values_section.png index 7d14c29588dc871a283141e56418f61048ff4c8f..da2bb36edf3279831d26b0f0877eb3e9f79347ed 100644 GIT binary patch literal 18402 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
L!z>%R$oR*e{MwmL5R$@#YfUh<#C{CgRPO4< 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 diff --git a/docs/source/Plugin/_Plugin.rst b/docs/source/Plugin/_Plugin.rst index 99217cd36..58655ca0f 100644 --- a/docs/source/Plugin/_Plugin.rst +++ b/docs/source/Plugin/_Plugin.rst @@ -279,7 +279,7 @@ Available options, grouped per category: **Dust/Gases**: ``Dust PM2.5¹, Dust PM1.0¹, Dust PM10¹, (e)CO2¹, TVOC¹, AQI¹, NOx¹`` -**Energy**: ``Voltage¹, Current¹, Power Usage¹, Power Factor¹, Apparent Power Usage¹, Reactive Power¹`` +**Energy**: ``Voltage¹, Current¹, Power Usage¹, Power Factor¹, Apparent Power Usage¹,`` ``Reactive Power¹, Reactive Energy¹, Energy¹, Energy storage¹`` **Time**: ``Duration¹, Date¹, Timestamp¹`` @@ -287,7 +287,7 @@ Available options, grouped per category: **Light**: ``Lux¹, UV¹, UV Index¹, IR¹, Red¹, Green¹, Blue¹, Color temperature¹`` -**Other**: ``Switch¹, Switch (inv.)¹, Dimmer, String, UInt32 (1x), Int32 (1x), UInt64 (1x), Int64 (1x), Double (1x)`` +**Other**: ``Switch¹, Switch (inv.)¹, Dimmer, String, UInt32 (1x), Int32 (1x), UInt64 (1x), Int64 (1x), Double (1x), Frequency¹`` Value Types marked with ``¹`` are supported for use in MQTT AutoDiscovery. @@ -299,6 +299,16 @@ Here's a partial preview (the 'None' value is selected): .. image:: Task_config_page_ValueType_selector_part.png +MQTT State Class +^^^^^^^^^^^^^^^^ + +For some Value Types a State Class can be provided in the MQTT Discovery configuration. By default this setting is empty, but for (mostly Energy related) this can be configured for: + +* *Measurement* +* *Measurement-angle* +* *Total* +* *Total-increasing* + | .. _Plugin List: From c9df24c2842a92c4c89ab95d860312d3f56a8ac9 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Mon, 4 Aug 2025 13:04:08 +0200 Subject: [PATCH 04/33] [AutoDiscover] Fix compilation when state_class support not enabled --- src/src/Helpers/_CPlugin_Helper_mqtt.cpp | 26 +++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp index 8d79d12d0..f260c22c9 100644 --- a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp +++ b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp @@ -631,8 +631,12 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn String valueDeviceClass = parseStringKeepCase(pluginDeviceClass, v + 1); // Device classes per value if (valueDeviceClass.isEmpty()) { valueDeviceClass = F("power"); } // default + # if FEATURE_MQTT_STATE_CLASS const String stateClass = MQTT_sensor_StateClass(Cache.getTaskVarStateClass(x, v), false); - const bool twoWay = MQTT_binary_deviceClassTwoWay(MQTT_binary_deviceClassIndex(valueDeviceClass)); + # else // if FEATURE_MQTT_STATE_CLASS + const String stateClass = EMPTY_STRING; + # endif // if FEATURE_MQTT_STATE_CLASS + const bool twoWay = MQTT_binary_deviceClassTwoWay(MQTT_binary_deviceClassIndex(valueDeviceClass)); // Discover 2-way as Light const __FlashStringHelper*componentClass = twoWay && discoveryItems[s].canSet ? F("light") : F("binary_sensor"); @@ -694,7 +698,11 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn const String valuename = MQTT_DiscoveryHelperGetValueName(x, v, discoveryItems[s]); const String uom = MQTT_DiscoveryHelperGetValueUoM(x, v, discoveryItems[s], getValueType2DefaultHAUoM(Sensor_VType::SENSOR_TYPE_TEMP_ONLY)); + # if FEATURE_MQTT_STATE_CLASS const String stateClass = MQTT_sensor_StateClass(Cache.getTaskVarStateClass(x, v), false); + # else // if FEATURE_MQTT_STATE_CLASS + const String stateClass = EMPTY_STRING; + # endif // if FEATURE_MQTT_STATE_CLASS success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, valuename, ControllerIndex, ControllerSettings, @@ -725,7 +733,11 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn const String valuename = MQTT_DiscoveryHelperGetValueName(x, v, discoveryItems[s]); const String uom = MQTT_DiscoveryHelperGetValueUoM(x, v, discoveryItems[s], getValueType2DefaultHAUoM(Sensor_VType::SENSOR_TYPE_HUM_ONLY)); + # if FEATURE_MQTT_STATE_CLASS const String stateClass = MQTT_sensor_StateClass(Cache.getTaskVarStateClass(x, v), false); + # else // if FEATURE_MQTT_STATE_CLASS + const String stateClass = EMPTY_STRING; + # endif // if FEATURE_MQTT_STATE_CLASS success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, valuename, ControllerIndex, ControllerSettings, @@ -760,7 +772,11 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn const String valuename = MQTT_DiscoveryHelperGetValueName(x, v, discoveryItems[s]); const String uom = MQTT_DiscoveryHelperGetValueUoM(x, v, discoveryItems[s], getValueType2DefaultHAUoM(Sensor_VType::SENSOR_TYPE_BARO_ONLY)); + # if FEATURE_MQTT_STATE_CLASS const String stateClass = MQTT_sensor_StateClass(Cache.getTaskVarStateClass(x, v), false); + # else // if FEATURE_MQTT_STATE_CLASS + const String stateClass = EMPTY_STRING; + # endif // if FEATURE_MQTT_STATE_CLASS success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, valuename, ControllerIndex, ControllerSettings, @@ -821,9 +837,13 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn const String uomDef = getValueType2DefaultHAUoM(discoveryItems[s].VType); for (uint8_t v = discoveryItems[s].varIndex; v < varCount; ++v) { - const String valuename = MQTT_DiscoveryHelperGetValueName(x, v, discoveryItems[s]); - const String uom = MQTT_DiscoveryHelperGetValueUoM(x, v, discoveryItems[s], uomDef); + const String valuename = MQTT_DiscoveryHelperGetValueName(x, v, discoveryItems[s]); + const String uom = MQTT_DiscoveryHelperGetValueUoM(x, v, discoveryItems[s], uomDef); + # if FEATURE_MQTT_STATE_CLASS const String stateClass = MQTT_sensor_StateClass(Cache.getTaskVarStateClass(x, v), false); + # else // if FEATURE_MQTT_STATE_CLASS + const String stateClass = EMPTY_STRING; + # endif // if FEATURE_MQTT_STATE_CLASS success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, valuename, ControllerIndex, ControllerSettings, From c8c313a7741ab0539071a7d06edf9b8c48f6fb7f Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Mon, 4 Aug 2025 14:36:44 +0200 Subject: [PATCH 05/33] [AutoDiscover] Small code improvement --- src/src/Helpers/_CPlugin_Helper_mqtt.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp index f260c22c9..1ef2960d8 100644 --- a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp +++ b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp @@ -1066,9 +1066,8 @@ bool MQTT_DiscoveryPublishWithStatusAndSet(taskIndex_t taskIndex, const String withSet = hasSet ? F(",\"cmd_t\":\"~/set\"") : EMPTY_STRING; const String schema = hasSet ? EMPTY_STRING : "\"schema\":\"basic\","; const String devOrIcon = hasIcon ? F("ic") : F("dev_cla"); - const String withUoM = !unitOfMeasure.isEmpty() ? - strformat(F(",\"unit_of_meas\":\"%s\""), unitOfMeasure.c_str()) : - EMPTY_STRING; + const String withUoM = unitOfMeasure.isEmpty() ? EMPTY_STRING : + strformat(F(",\"unit_of_meas\":\"%s\""), unitOfMeasure.c_str()); const String stateJson = stateClass.isEmpty() ? EMPTY_STRING : strformat(F(",\"stat_cla\":\"%s\""), stateClass.c_str()); const String taskName = makeHomeAssistantCompliantName(getTaskDeviceName(taskIndex)); From f1cf9602d409f4315fb15f5ecfea5dc16c6b430e Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Mon, 4 Aug 2025 21:45:28 +0200 Subject: [PATCH 06/33] [P078] Improved value descriptions and register appended, small code improvements --- src/_P078_Eastron.ino | 2 +- src/src/PluginStructs/P078_data_struct.cpp | 26 +++++++++++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/_P078_Eastron.ino b/src/_P078_Eastron.ino index 3b3baf0a5..daa37dce1 100644 --- a/src/_P078_Eastron.ino +++ b/src/_P078_Eastron.ino @@ -195,7 +195,7 @@ boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string) F("SDM630"), F("SDM72_V2"), F("SDM320C"), - F("TAC2100") + F("TAC2100"), }; constexpr size_t nrOptions = NR_ELEMENTS(options_model); FormSelectorOptions selector(nrOptions, options_model); diff --git a/src/src/PluginStructs/P078_data_struct.cpp b/src/src/PluginStructs/P078_data_struct.cpp index 73a6cf5c8..34f65d49e 100644 --- a/src/src/PluginStructs/P078_data_struct.cpp +++ b/src/src/PluginStructs/P078_data_struct.cpp @@ -118,7 +118,7 @@ constexpr p078_register_description register_description_list[] = { { SDM_NET_KVARH /* 0x018E */ , SDM_UOM::kVArh , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 }, { SDM_IMPORT_POWER /* 0x0500 */ , SDM_UOM::W , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 , 0 }, { SDM_EXPORT_POWER /* 0x0502 */ , SDM_UOM::W , 0 , 2 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 , 0 }, - + { DDM_PHASE_1_VOLTAGE /* 0x0000 */ , SDM_UOM::V , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 }, { DDM_PHASE_1_CURRENT /* 0x0008 */ , SDM_UOM::A , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 }, { DDM_PHASE_1_POWER /* 0x0012 */ , SDM_UOM::W , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 }, @@ -132,7 +132,7 @@ constexpr p078_register_description register_description_list[] = { { TAC2100_APPARENT_POWER_DEMAND /* 0x0090 */ , SDM_UOM::VA , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 }, { TAC2100_MAX_REACTIVE_POWER_DEMAND /* 0x00A4 */ , SDM_UOM::VAr , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 }, { TAC2100_MAX_APPARENT_POWER_DEMAND /* 0x00A6 */ , SDM_UOM::VA , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 }, -{ TAC2100_NATURE_OF_LOAD /* 0x004E */ , SDM_UOM::nat_load , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 } +{ TAC2100_NATURE_OF_LOAD /* 0x004E */ , SDM_UOM::nat_load , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 }, }; // *INDENT-ON* @@ -152,8 +152,8 @@ const __FlashStringHelper* SDM_UOMtoString(SDM_UOM uom, bool display) { F("Apparent Power"), F("VA"), F("Reactive Power"), F("VAr"), F("Apparent Energy"), F("kVAh"), - F("Reactive Energy"), F("kVArh"), - F("Nature of Load"), F("nat_load"), + F("Reactive Energy"), F("kVArh"), + F("Nature of Load"), F("nat_load"), }; constexpr size_t nrStrings = NR_ELEMENTS(strings); size_t index = 2 * static_cast(uom); @@ -225,15 +225,16 @@ Sensor_VType Plugin_078_QueryVType(SDM_MODEL model, int choice) { Sensor_VType::SENSOR_TYPE_VOLTAGE_ONLY, Sensor_VType::SENSOR_TYPE_CURRENT_ONLY, Sensor_VType::SENSOR_TYPE_POWER_USG_ONLY, - Sensor_VType::SENSOR_TYPE_ENERGY, // Active energy - Sensor_VType::SENSOR_TYPE_NONE, // FIXME Ah + Sensor_VType::SENSOR_TYPE_ENERGY, // Active energy + Sensor_VType::SENSOR_TYPE_NONE, // FIXME Ah Sensor_VType::SENSOR_TYPE_FREQUENCY, // Frequency - Sensor_VType::SENSOR_TYPE_NONE, // FIXME Phase angle + Sensor_VType::SENSOR_TYPE_NONE, // FIXME Phase angle Sensor_VType::SENSOR_TYPE_POWER_FACT_ONLY, Sensor_VType::SENSOR_TYPE_APPRNT_POWER_USG_ONLY, Sensor_VType::SENSOR_TYPE_REACTIVE_POWER_ONLY, - Sensor_VType::SENSOR_TYPE_NONE, // FIXME Apparent energy + Sensor_VType::SENSOR_TYPE_NONE, // FIXME Apparent energy Sensor_VType::SENSOR_TYPE_REACTIVE_ENERGY, // Reactive energy + Sensor_VType::SENSOR_TYPE_NONE, // FIXME Nature of load }; constexpr uint8_t valueCount = NR_ELEMENTS(vtypes); @@ -327,6 +328,8 @@ String p078_register_description::getDescription(SDM_MODEL model) const case SDM_MAXIMUM_PHASE_2_CURRENT_DEMAND: case SDM_MAXIMUM_PHASE_3_CURRENT_DEMAND: case SDM_MAXIMUM_REACTIVE_POWER_DEMAND: + case TAC2100_MAX_REACTIVE_POWER_DEMAND: + case TAC2100_MAX_APPARENT_POWER_DEMAND: res = F("Maximum "); break; @@ -426,6 +429,10 @@ String p078_register_description::getDescription(SDM_MODEL model) const case SDM_MAXIMUM_PHASE_1_CURRENT_DEMAND: case SDM_MAXIMUM_PHASE_2_CURRENT_DEMAND: case SDM_MAXIMUM_PHASE_3_CURRENT_DEMAND: + case TAC2100_REACTIVE_POWER_DEMAND: + case TAC2100_APPARENT_POWER_DEMAND: + case TAC2100_MAX_REACTIVE_POWER_DEMAND: + case TAC2100_MAX_APPARENT_POWER_DEMAND: res += F(" demand"); break; @@ -442,6 +449,9 @@ String p078_register_description::getDescription(SDM_MODEL model) const res += concat(F(" ("), SDM_UOMtoString(uom, false)); res += concat(F(")"), getPhaseDescription(model, ' ')); + + res += concat(F(" ["), formatToHex(reg, 4)); // Append register for clarity + res += ']'; return res; } From eabb3679efbea065801a911909c3bcd9907534ee Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Mon, 4 Aug 2025 22:09:58 +0200 Subject: [PATCH 07/33] [Caches] Formatting (minor) --- src/src/DataStructs/Caches.h | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/src/DataStructs/Caches.h b/src/src/DataStructs/Caches.h index 36130dfb4..31f9e5b60 100644 --- a/src/src/DataStructs/Caches.h +++ b/src/src/DataStructs/Caches.h @@ -50,15 +50,15 @@ struct ExtraTaskSettings_cache_t { #if FEATURE_PLUGIN_STATS PluginStats_Config_t pluginStatsConfig[VARS_PER_TASK] = {}; #endif // if FEATURE_PLUGIN_STATS - uint8_t hasFormula = 0; // Bitmap which task value has formula and whether a formula needs previous value + uint8_t hasFormula = 0; // Bitmap which task value has formula and whether a formula needs previous value #if FEATURE_TASKVALUE_UNIT_OF_MEASURE - uint8_t unitOfMeasure[VARS_PER_TASK] = { 0 }; // Value for unit of measure per taskValue + uint8_t unitOfMeasure[VARS_PER_TASK] = { 0 }; // Value for unit of measure per taskValue #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE #if FEATURE_CUSTOM_TASKVAR_VTYPE - uint8_t customVType[VARS_PER_TASK] = { 0 }; // single-value VType per taskValue + uint8_t customVType[VARS_PER_TASK] = { 0 }; // single-value VType per taskValue #endif // if FEATURE_CUSTOM_TASKVAR_VTYPE #if FEATURE_MQTT_STATE_CLASS - uint8_t mqttStateClass[VARS_PER_TASK] = { 0 }; // state_class = None, Measurement, Total, TotalIncreasing + uint8_t mqttStateClass[VARS_PER_TASK] = { 0 }; // state_class = None, Measurement, Measurement Angle, Total, Total Increasing #endif // if FEATURE_MQTT_STATE_CLASS }; @@ -94,9 +94,11 @@ struct Caches { uint8_t rel_index); // Check to see if at least one of the taskvalues has a non-empty formula field. - bool hasFormula(taskIndex_t TaskIndex, uint8_t rel_index); + bool hasFormula(taskIndex_t TaskIndex, + uint8_t rel_index); bool hasFormula(taskIndex_t TaskIndex); - bool hasFormula_with_prevValue(taskIndex_t TaskIndex, uint8_t rel_index); + bool hasFormula_with_prevValue(taskIndex_t TaskIndex, + uint8_t rel_index); String getTaskDeviceFormula(taskIndex_t TaskIndex, @@ -119,9 +121,9 @@ struct Caches { #if FEATURE_TASKVALUE_UNIT_OF_MEASURE uint8_t getTaskVarUnitOfMeasure(taskIndex_t taskIndex, - taskVarIndex_t taskVarIndex); + taskVarIndex_t taskVarIndex); #endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE - + #if FEATURE_CUSTOM_TASKVAR_VTYPE uint8_t getTaskVarCustomVType(taskIndex_t taskIndex, taskVarIndex_t taskVarIndex); @@ -129,9 +131,9 @@ struct Caches { #if FEATURE_MQTT_STATE_CLASS uint8_t getTaskVarStateClass(taskIndex_t taskIndex, - taskVarIndex_t taskVarIndex); + taskVarIndex_t taskVarIndex); #endif // if FEATURE_MQTT_STATE_CLASS - + // Update all cached values, except the checksum. void updateExtraTaskSettingsCache(); @@ -160,7 +162,7 @@ public: TaskIndexNameMap taskIndexName; TaskIndexValueNameMap taskIndexValueName; - FilePresenceMap fileExistsMap; // Filesize. -1 if not present + FilePresenceMap fileExistsMap; // Filesize. -1 if not present RulesHelperClass rulesHelper; private: From 0c95ece2aab642b9be3d4fec34fc06bee1a27bfe Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 5 Aug 2025 13:54:56 +0200 Subject: [PATCH 08/33] [P056] Add multi-instance use --- src/_P056_SDS011-Dust.ino | 70 ++++++++++------------ src/src/PluginStructs/P056_data_struct.cpp | 62 +++++++++++++++++++ src/src/PluginStructs/P056_data_struct.h | 28 +++++++++ 3 files changed, 122 insertions(+), 38 deletions(-) create mode 100644 src/src/PluginStructs/P056_data_struct.cpp create mode 100644 src/src/PluginStructs/P056_data_struct.h diff --git a/src/_P056_SDS011-Dust.ino b/src/_P056_SDS011-Dust.ino index dff2c2cbd..cbf781540 100644 --- a/src/_P056_SDS011-Dust.ino +++ b/src/_P056_SDS011-Dust.ino @@ -11,26 +11,21 @@ This plugin reads the particle concentration from SDS011 Sensor DevicePin1 - RX on ESP, TX on SDS + DevicePin2 - TX on ESP, RX on SDS, optional, for setting the sleep time */ /** Changelog: + * 2025-08-05 tonhuisman: Introduce multi-instance use * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery */ -// #ifdef ESP8266 // Needed for precompile issues. - # define PLUGIN_056 # define PLUGIN_ID_056 56 # define PLUGIN_NAME_056 "Dust - SDS011/018/198" # define PLUGIN_VALUENAME1_056 "PM2.5" // Dust <2.5µm in µg/m³ SDS198:<100µm in µg/m³ # define PLUGIN_VALUENAME2_056 "PM10" // Dust <10µm in µg/m³ -# include - -# include "ESPEasy-Globals.h" - - -CjkSDS011 *Plugin_056_SDS = nullptr; +# include "src/PluginStructs/P056_data_struct.h" boolean Plugin_056(uint8_t function, struct EventStruct *event, String& string) @@ -50,6 +45,9 @@ boolean Plugin_056(uint8_t function, struct EventStruct *event, String& string) dev.SendDataOption = true; dev.TimerOption = true; dev.PluginStats = true; + + // Keep alive for setting the sleep time on save, only when changed + dev.ExitTaskBeforeSave = false; break; } @@ -109,7 +107,7 @@ boolean Plugin_056(uint8_t function, struct EventStruct *event, String& string) if (PCONFIG(0) != newsleeptime) { PCONFIG(0) = newsleeptime; - Plugin_056_setWorkingPeriod(newsleeptime); + Plugin_056_setWorkingPeriod(event, newsleeptime); } } success = true; @@ -118,45 +116,34 @@ boolean Plugin_056(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_INIT: { - if (Plugin_056_SDS) { - delete Plugin_056_SDS; - } + initPluginTaskData(event->TaskIndex, new (std::nothrow) P056_data_struct(event)); + P056_data_struct *P056_data = + static_cast(getPluginTaskData(event->TaskIndex)); + success = (nullptr != P056_data && P056_data->isInitialized()); - const ESPEasySerialPort port = static_cast(CONFIG_PORT); - Plugin_056_SDS = new (std::nothrow) CjkSDS011(port, CONFIG_PIN1, CONFIG_PIN2); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, strformat(F("SDS : Init OK ESP GPIO-pin RX:%d TX:%d"), CONFIG_PIN1, CONFIG_PIN2)); - } - - success = true; break; } case PLUGIN_EXIT: { - // //FIXME: if this plugin is used more than once at the same time, things go horribly wrong :) - // FIXME TD-er: Must implement plugin_data_struct for this - // - // if (Plugin_056_SDS) - // delete Plugin_056_SDS; - // addLog(LOG_LEVEL_INFO, F("SDS : Exit")); - shouldReboot = true; break; } case PLUGIN_FIFTY_PER_SECOND: { - if (!Plugin_056_SDS) { + P056_data_struct *P056_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr == P056_data) || !P056_data->isInitialized()) { break; } - Plugin_056_SDS->Process(); + P056_data->Process(); - if (Plugin_056_SDS->available()) + if (P056_data->available()) { - const float pm2_5 = Plugin_056_SDS->GetPM2_5(); - const float pm10 = Plugin_056_SDS->GetPM10_(); + const float pm2_5 = P056_data->GetPM2_5(); + const float pm10 = P056_data->GetPM10_(); # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { @@ -179,13 +166,17 @@ boolean Plugin_056(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_READ: { - if (!Plugin_056_SDS) { + P056_data_struct *P056_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr == P056_data) || !P056_data->isInitialized()) { break; } - float pm25, pm10; + float pm25{}; + float pm10{}; - if (Plugin_056_SDS->ReadAverage(pm25, pm10)) { + if (P056_data->ReadAverage(pm25, pm10)) { UserVar.setFloat(event->TaskIndex, 0, pm25); UserVar.setFloat(event->TaskIndex, 1, pm10); success = true; @@ -220,11 +211,14 @@ String Plugin_056_WorkingPeriodToString(int workingPeriod) { return log; } -void Plugin_056_setWorkingPeriod(int minutes) { - if (!Plugin_056_SDS) { +void Plugin_056_setWorkingPeriod(EventStruct *event, int minutes) { + P056_data_struct *P056_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr == P056_data) || !P056_data->isInitialized()) { return; } - Plugin_056_SDS->SetWorkingPeriod(minutes); + P056_data->SetWorkingPeriod(minutes); if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, concat(F("SDS : Working Period set to: "), Plugin_056_WorkingPeriodToString(minutes))); diff --git a/src/src/PluginStructs/P056_data_struct.cpp b/src/src/PluginStructs/P056_data_struct.cpp new file mode 100644 index 000000000..86dc9eed4 --- /dev/null +++ b/src/src/PluginStructs/P056_data_struct.cpp @@ -0,0 +1,62 @@ + +#include "../PluginStructs/P056_data_struct.h" + +P056_data_struct::P056_data_struct(EventStruct *event) { + delete SDS; + + const ESPEasySerialPort port = static_cast(CONFIG_PORT); + SDS = new (std::nothrow) CjkSDS011(port, CONFIG_PIN1, CONFIG_PIN2); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("SDS : Init OK ESP GPIO-pin RX:%d TX:%d"), CONFIG_PIN1, CONFIG_PIN2)); + } +} + +P056_data_struct::~P056_data_struct() { + delete SDS; + SDS = nullptr; +} + +void P056_data_struct::Process() { + if (isInitialized()) { + this->SDS->Process(); + } +} + +bool P056_data_struct::available() { + if (isInitialized()) { + return this->SDS->available(); + } else { + return false; + } +} + +float P056_data_struct::GetPM2_5() { + if (isInitialized()) { + return this->SDS->GetPM2_5(); + } else { + return 0.0f; + } +} + +float P056_data_struct::GetPM10_() { + if (isInitialized()) { + return this->SDS->GetPM10_(); + } else { + return 0.0f; + } +} + +bool P056_data_struct::ReadAverage(float pm25, float pm10) { + if (isInitialized()) { + return this->SDS->ReadAverage(pm25, pm10); + } else { + return false; + } +} + +void P056_data_struct::SetWorkingPeriod(int minutes) { + if (isInitialized()) { + this->SDS->SetWorkingPeriod(minutes); + } +} diff --git a/src/src/PluginStructs/P056_data_struct.h b/src/src/PluginStructs/P056_data_struct.h new file mode 100644 index 000000000..2d408ba16 --- /dev/null +++ b/src/src/PluginStructs/P056_data_struct.h @@ -0,0 +1,28 @@ +#pragma once + +#include "../../_Plugin_Helper.h" +#ifdef USES_P056 + +# include + +struct P056_data_struct : public PluginTaskData_base { + P056_data_struct(EventStruct *event); + ~P056_data_struct(); + + bool isInitialized() { + return nullptr != SDS; + } + + void Process(); + bool available(); + float GetPM2_5(); + float GetPM10_(); + bool ReadAverage(float pm25, + float pm10); + void SetWorkingPeriod(int minutes); + +private: + + CjkSDS011 *SDS = nullptr; +}; +#endif // ifdef USES_P056 From 86f27a9ca578361e73f111e61549f32fac308723 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 5 Aug 2025 15:37:26 +0200 Subject: [PATCH 09/33] [P078] Fix docs parser warning --- docs/source/Plugin/_plugin_substitutions_p07x.repl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/Plugin/_plugin_substitutions_p07x.repl b/docs/source/Plugin/_plugin_substitutions_p07x.repl index 7ef052716..c6be0725b 100644 --- a/docs/source/Plugin/_plugin_substitutions_p07x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p07x.repl @@ -110,7 +110,7 @@ .. |P078_status| replace:: :yellow:`ENERGY` .. |P078_github| replace:: P078_Eastron.ino .. _P078_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P078_Eastron.ino -.. |P078_usedby| replace:: `Modbus variants of Eastron SDM120M, SDM120CT, SDM220M, SDM230M, SDM72M, SDM72CTM, SDM630, SDM630MCT, SDM630-EV, SDM54-M, SDM320Y, DDM18SD, SMART X-96, MCS-U22, TAiYEDQ TAC2100 ` +.. |P078_usedby| replace:: `Modbus variants of Eastron SDM120M, SDM120CT, SDM220M, SDM230M, SDM72M, SDM72CTM, SDM630, SDM630MCT, SDM630-EV, SDM54-M, SDM320Y, DDM18SD, SMART X-96, MCS-U22, TAiYEDQ TAC2100` .. |P078_shortinfo| replace:: `.` .. |P078_maintainer| replace:: TD-er .. |P078_compileinfo| replace:: `.` From db5c96407e1f1f013b054f6b52208b858e92f6a6 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 5 Aug 2025 15:52:34 +0200 Subject: [PATCH 10/33] [P056] Update documentation --- docs/source/Plugin/P056.rst | 53 +++++++++++++----- .../Plugin/P056_DeviceConfiguration.png | Bin 0 -> 54131 bytes 2 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 docs/source/Plugin/P056_DeviceConfiguration.png diff --git a/docs/source/Plugin/P056.rst b/docs/source/Plugin/P056.rst index 3458d22b7..1be2d1896 100644 --- a/docs/source/Plugin/P056.rst +++ b/docs/source/Plugin/P056.rst @@ -23,28 +23,52 @@ Maintainer: |P056_maintainer| Used libraries: |P056_usedlibraries| -Supported hardware ------------------- +Introduction +------------ -|P056_usedby| +A dust sensor will measure the dust concentration of the surrounding air. Depending on the device, different particle sizes can be measured, and the SDS series provides values for up to 2.5 and up to 10 micrometer particle size. + +Multiple sensors can be used independently on a single ESP unit, as long as the serial port setup is uniquely configured. + + +Device +------ + +.. image:: P056_DeviceConfiguration.png + + +Task settings +~~~~~~~~~~~~~ + +* **Name** The name of the task. This should be unique for all devices that are configured. (Initially empty) + +* **Enabled** For the device to work it has to be enabled. When checked, the device will be started as soon as the ESP starts. If desired, the device can also be enabled from f.e. a rule by using the ``TaskEnable,`` or ``TaskEnable,`` command, or disabled using the corresponding ``TaskDisable,|`` commands. Sensor -^^^^^^ +~~~~~~ See: :ref:`SerialHelper_page` +Device Settings +~~~~~~~~~~~~~~~ -**TODO**: Complete this documentation... +* **Sleep time**: Numeric value to set the sleep time between measurements. The laser used in the sensor has an aproximated life time of 8000 hours of on-time. When enabled continuously, this number will be reached in about 333 days, so less than a year! To prolong the lifetime, a sleep interval can be configured, during which the laser will be turned on, and then turned off for ``*60-30`` seconds. Range = 0 (continuous) or 1 .. 30 seconds. -.. Commands available -.. ^^^^^^^^^^^^^^^^^^ +This setting is only available if the optional **ESP TX GPIO** pin is configured correctly, as it involves sending a command to the sensor. -.. .. include:: P056_commands.repl -.. Events -.. ~~~~~~ +.. include:: DataAcquisition.repl -.. .. include:: P056_events.repl +* **Interval**: By default, Interval will be set to 60 sec for this plugin. It is the frequency used to read sensor values and send these to any Controllers configured for this device. Values will *only* be sent if data is successfully received from the sensor. + +Values +~~~~~~ + +The measured values are available in ``PM2.5`` and ``PM10``. + +For a complete description of all available columns see the `Plugin - Values <_Plugin.html#values>`_ section. + +| Change log ---------- @@ -52,6 +76,9 @@ Change log .. versionchanged:: 2.0 ... + |changed| + 2025-08-05: Enable multiple instances of the plugin, and update the documentation. + |added| Major overhaul for 2.0 release. @@ -61,7 +88,3 @@ Change log |added| Initial release version. - - - - diff --git a/docs/source/Plugin/P056_DeviceConfiguration.png b/docs/source/Plugin/P056_DeviceConfiguration.png new file mode 100644 index 0000000000000000000000000000000000000000..808adc200086eed507e8b2376d57a45e02f25d5b GIT binary patch literal 54131 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 ZY+p86SvnP==D+o9VQPJ;?!t}x{~v#tej@+? literal 0 HcmV?d00001 From e3440594be751dd8cf77f71d09e18d87b798a834 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Fri, 8 Aug 2025 13:12:41 +0200 Subject: [PATCH 11/33] [AutoDiscover] Use %sysname% instead of group_nr if Controller Group = UnitNr --- src/src/Helpers/_CPlugin_Helper_mqtt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp index 6b040f439..89858a686 100644 --- a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp +++ b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp @@ -551,7 +551,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn const bool useGroupId = groupId != 0 && !usesControllerIDX; const String elementName = useGroupId ? - strformat(F("Group %u"), groupId) : + (Settings.Unit == groupId ? hostName : strformat(F("Group %u"), groupId)) : strformat(F("%s %s"), hostName.c_str(), taskName.c_str()); const String elementIds = useGroupId ? strformat(F("group_%u"), groupId) : From 352d861b3a52e724e25d3f4b1b231f9de9e3ba4f Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Fri, 8 Aug 2025 21:44:12 +0200 Subject: [PATCH 12/33] [AutoDiscover] Update documentation for Group setting --- docs/source/Plugin/DataAcquisition.repl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/Plugin/DataAcquisition.repl b/docs/source/Plugin/DataAcquisition.repl index 78a0c48bd..c6b90c1c1 100644 --- a/docs/source/Plugin/DataAcquisition.repl +++ b/docs/source/Plugin/DataAcquisition.repl @@ -30,7 +30,7 @@ For the Domoticz controllers the value index (IDX) has to be configured. For some controllers, like Home Assistant/openHAB, there are extra options available. -* **Group**: This represents the group id to combine all values from multiple tasks into a single grouped-device during MQTT AutoDiscovery. Groups, by design, can span multiple ESPEasy devices, if desired, as long as the Task/Valuename combinations are unique. If a group should only combine Tasks from a single ESPEasy unit, the group id should be unique across multiple ESPEasy units. The group description, default *Group *, can be adjusted in Home Assistant. +* **Group**: This represents the group id to combine all values from multiple tasks into a single grouped-device during MQTT AutoDiscovery. Groups, by design, can span multiple ESPEasy devices, if desired, as long as the Task/Valuename combinations are unique. If a group should only combine Tasks from a single ESPEasy unit, the group id should be unique across multiple ESPEasy units. The group description, default *Group *, can be adjusted in Home Assistant. If the Group value matches the current Unit nr, the Unit name, ``%sysname%``, is used instead of Group . * **Send derived**: This checkbox determines if any configured Derived values should also be sent to the controller (and included in the AutoDiscovery if that's available and enabled). From 3519c2000747e85f31ff7151323907dec29c832f Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 9 Aug 2025 19:49:01 +0200 Subject: [PATCH 13/33] [AutoDiscover] Add 15 more Value types and 7 UoM values, fix some default UoM --- src/src/DataTypes/SensorVType.cpp | 80 +++++++++++++++++++- src/src/DataTypes/SensorVType.h | 16 ++++ src/src/Helpers/_CPlugin_Helper_mqtt.cpp | 16 ++++ src/src/Helpers/_Plugin_SensorTypeHelper.cpp | 9 ++- src/src/WebServer/Markup.cpp | 5 +- 5 files changed, 117 insertions(+), 9 deletions(-) diff --git a/src/src/DataTypes/SensorVType.cpp b/src/src/DataTypes/SensorVType.cpp index 277f47589..4e269fc92 100644 --- a/src/src/DataTypes/SensorVType.cpp +++ b/src/src/DataTypes/SensorVType.cpp @@ -101,6 +101,22 @@ uint8_t getValueCountFromSensorType(Sensor_VType sensorType, bool log) case Sensor_VType::SENSOR_TYPE_FREQUENCY: case Sensor_VType::SENSOR_TYPE_ENERGY: case Sensor_VType::SENSOR_TYPE_ENERGY_STORAGE: + case Sensor_VType::SENSOR_TYPE_ABS_HUMIDITY: + case Sensor_VType::SENSOR_TYPE_ATMOS_PRESSURE: + case Sensor_VType::SENSOR_TYPE_BLOOD_GLUCOSE_C: + case Sensor_VType::SENSOR_TYPE_CO_ONLY: + case Sensor_VType::SENSOR_TYPE_ENERGY_DISTANCE: + case Sensor_VType::SENSOR_TYPE_GAS_ONLY: + case Sensor_VType::SENSOR_TYPE_NITROUS_OXIDE: + case Sensor_VType::SENSOR_TYPE_OZONE_ONLY: + case Sensor_VType::SENSOR_TYPE_PRICIPITATION: + case Sensor_VType::SENSOR_TYPE_PRICIPITATION_INTEN: + case Sensor_VType::SENSOR_TYPE_SULPHUR_DIOXIDE: + case Sensor_VType::SENSOR_TYPE_VOC_PARTS: + case Sensor_VType::SENSOR_TYPE_VOLUME: + case Sensor_VType::SENSOR_TYPE_VOLUME_FLOW_RATE: + case Sensor_VType::SENSOR_TYPE_VOLUME_STORAGE: + case Sensor_VType::SENSOR_TYPE_WATER: return 1; } #ifndef BUILD_NO_DEBUG @@ -191,6 +207,22 @@ const __FlashStringHelper* getSensorTypeLabel(Sensor_VType sensorType) { case Sensor_VType::SENSOR_TYPE_FREQUENCY: return F("Frequency"); case Sensor_VType::SENSOR_TYPE_ENERGY: return F("Energy"); case Sensor_VType::SENSOR_TYPE_ENERGY_STORAGE: return F("Energy storage"); + case Sensor_VType::SENSOR_TYPE_ABS_HUMIDITY: return F("Absolute humidity"); + case Sensor_VType::SENSOR_TYPE_ATMOS_PRESSURE: return F("Atmospheric pressure"); + case Sensor_VType::SENSOR_TYPE_BLOOD_GLUCOSE_C: return F("Blood glucose conc."); + case Sensor_VType::SENSOR_TYPE_CO_ONLY: return F("CO"); + case Sensor_VType::SENSOR_TYPE_ENERGY_DISTANCE: return F("Energy distance"); + case Sensor_VType::SENSOR_TYPE_GAS_ONLY: return F("Gas"); + case Sensor_VType::SENSOR_TYPE_NITROUS_OXIDE: return F("N2O"); + case Sensor_VType::SENSOR_TYPE_OZONE_ONLY: return F("Ozone"); + case Sensor_VType::SENSOR_TYPE_PRICIPITATION: return F("Pricipitation"); + case Sensor_VType::SENSOR_TYPE_PRICIPITATION_INTEN: return F("Pricipitation intensity"); + case Sensor_VType::SENSOR_TYPE_SULPHUR_DIOXIDE: return F("SO2"); + case Sensor_VType::SENSOR_TYPE_VOC_PARTS: return F("VOC parts"); + case Sensor_VType::SENSOR_TYPE_VOLUME: return F("Volume"); + case Sensor_VType::SENSOR_TYPE_VOLUME_FLOW_RATE: return F("Volume flow rate"); + case Sensor_VType::SENSOR_TYPE_VOLUME_STORAGE: return F("Volume storage"); + case Sensor_VType::SENSOR_TYPE_WATER: return F("Water cons."); #else // if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE case Sensor_VType::SENSOR_TYPE_ANALOG_ONLY: case Sensor_VType::SENSOR_TYPE_TEMP_ONLY: @@ -235,6 +267,22 @@ const __FlashStringHelper* getSensorTypeLabel(Sensor_VType sensorType) { case Sensor_VType::SENSOR_TYPE_FREQUENCY: case Sensor_VType::SENSOR_TYPE_ENERGY: case Sensor_VType::SENSOR_TYPE_ENERGY_STORAGE: + case Sensor_VType::SENSOR_TYPE_ABS_HUMIDITY: + case Sensor_VType::SENSOR_TYPE_ATMOS_PRESSURE: + case Sensor_VType::SENSOR_TYPE_BLOOD_GLUCOSE_C: + case Sensor_VType::SENSOR_TYPE_CO_ONLY: + case Sensor_VType::SENSOR_TYPE_ENERGY_DISTANCE: + case Sensor_VType::SENSOR_TYPE_GAS_ONLY: + case Sensor_VType::SENSOR_TYPE_NITROUS_OXIDE: + case Sensor_VType::SENSOR_TYPE_OZONE_ONLY: + case Sensor_VType::SENSOR_TYPE_PRICIPITATION: + case Sensor_VType::SENSOR_TYPE_PRICIPITATION_INTEN: + case Sensor_VType::SENSOR_TYPE_SULPHUR_DIOXIDE: + case Sensor_VType::SENSOR_TYPE_VOC_PARTS: + case Sensor_VType::SENSOR_TYPE_VOLUME: + case Sensor_VType::SENSOR_TYPE_VOLUME_FLOW_RATE: + case Sensor_VType::SENSOR_TYPE_VOLUME_STORAGE: + case Sensor_VType::SENSOR_TYPE_WATER: break; #endif // if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE } @@ -328,6 +376,22 @@ bool isMQTTDiscoverySensorType(Sensor_VType sensorType) case Sensor_VType::SENSOR_TYPE_FREQUENCY: case Sensor_VType::SENSOR_TYPE_ENERGY: case Sensor_VType::SENSOR_TYPE_ENERGY_STORAGE: + case Sensor_VType::SENSOR_TYPE_ABS_HUMIDITY: + case Sensor_VType::SENSOR_TYPE_ATMOS_PRESSURE: + case Sensor_VType::SENSOR_TYPE_BLOOD_GLUCOSE_C: + case Sensor_VType::SENSOR_TYPE_CO_ONLY: + case Sensor_VType::SENSOR_TYPE_ENERGY_DISTANCE: + case Sensor_VType::SENSOR_TYPE_GAS_ONLY: + case Sensor_VType::SENSOR_TYPE_NITROUS_OXIDE: + case Sensor_VType::SENSOR_TYPE_OZONE_ONLY: + case Sensor_VType::SENSOR_TYPE_PRICIPITATION: + case Sensor_VType::SENSOR_TYPE_PRICIPITATION_INTEN: + case Sensor_VType::SENSOR_TYPE_SULPHUR_DIOXIDE: + case Sensor_VType::SENSOR_TYPE_VOC_PARTS: + case Sensor_VType::SENSOR_TYPE_VOLUME: + case Sensor_VType::SENSOR_TYPE_VOLUME_FLOW_RATE: + case Sensor_VType::SENSOR_TYPE_VOLUME_STORAGE: + case Sensor_VType::SENSOR_TYPE_WATER: return true; } return false; @@ -430,18 +494,22 @@ const char mqtt_valueType_ha_deviceclass_names[] PROGMEM = // !! Offset, startin "|wind_speed|duration|date|" // SWITCH_INVERTED .. DATE "timestamp|data_rate|data_size|sound_pressure|signal_strength|reactive_energy|" // TIMESTAMP .. REACTIVE_ENERGY "frequency|energy|energy_storage|" // FREQUENCY .. ENERGY_STORAGE + "absolute_humidity|atmospheric_pressure|blood_glucose_concentration|" // ABS_HUMIDITY .. ATMOSPH_PRESSURE + "carbon_monoxide|energy_distance|gas|nitrous_oxide|ozone|" // CO_ONLY .. OZONE_ONLY + "precipitation|precipitation_intensity|sulphur_dioxide|volatile_organic_compounds_parts|" // PRECIPITATION .. VOC_PARTS + "volume|volume_flow_rate|volume_storage|water|" // VOLUME .. WATER ; /** * getValueType2HADeviceClass: Convert the ValueType to a HA Device Class */ String getValueType2HADeviceClass(Sensor_VType sensorType) { - char tmp[27]{}; // length: volatile_organic_compounds + \0 + char tmp[33]{}; // length: volatile_organic_compounds_parts + \0 int devClassIndex = static_cast(sensorType); if (sensorType >= Sensor_VType::SENSOR_TYPE_ANALOG_ONLY) { devClassIndex -= static_cast(Sensor_VType::SENSOR_TYPE_ANALOG_ONLY); // Subtract offset // } else if (sensorType == Sensor_VType::SENSOR_TYPE_SWITCH) { - // return F(""); + // return EMPTY_STRING; } else if (sensorType == Sensor_VType::SENSOR_TYPE_WIND) { return F("wind_speed"); // } else if (sensorType == Sensor_VType::SENSOR_TYPE_DIMMER) { @@ -459,12 +527,16 @@ const char mqtt_valueType_default_ha_uom_names[] PROGMEM = // !! Offset, startin "|°C|%|lx|cm|°|" // ANALOG_ONLY .. DIRECTION_ONLY "µg/m³|µg/m³|µg/m³|%|ppm||" // DUSTPM2_5_ONLY .. GPS_ONLY "W/m²|UV Index|W/m²|kg|" // UV_ONLY .. WEIGHT_ONLY - "V|A|kWh|Cos φ|VA|" // VOLTAGE_ONLY .. APPRNT_POWER_USG_ONLY + "V|A|W|%|VA|" // VOLTAGE_ONLY .. APPRNT_POWER_USG_ONLY "ppd|hPa|lx|lx|lx|" // TVOC_ONLY .. COLOR_BLUE_ONLY "K|var||µg/m³|" // COLOR_TEMP_ONLY .. NOX_ONLY "|m/s|min||" // SWITCH_INVERTED .. DATE "|bit/s|B|dB|dBm|kvar|" // TIMESTAMP .. REACTIVE_ENERGY "Hz|kWh|kWh|" // FREQUENCY .. ENERGY_STORAGE + "g/m³|mbar|mmol/L" // ABS_HUMIDITY .. BLOOD_GLUCOSE_C + "ppm|kWh/100km|L|µg/m³|µg/m³" // CO_ONLY .. OZONE_ONLY + "mm|mm/h|µg/m³|ppm|" // PRICIPIATION .. VOC_PARTS + "L|m³/h|L|L|" // VOLUME .. WATER ; /** @@ -480,7 +552,7 @@ String getValueType2DefaultHAUoM(Sensor_VType sensorType) { } else if (sensorType == Sensor_VType::SENSOR_TYPE_WIND) { return F("m/s"); // } else if (sensorType == Sensor_VType::SENSOR_TYPE_DIMMER) { - // return F(""); + // return EMPTY_STRING; } else { return EMPTY_STRING; } diff --git a/src/src/DataTypes/SensorVType.h b/src/src/DataTypes/SensorVType.h index 46c68daca..f4903d725 100644 --- a/src/src/DataTypes/SensorVType.h +++ b/src/src/DataTypes/SensorVType.h @@ -79,6 +79,22 @@ enum class Sensor_VType : uint8_t { SENSOR_TYPE_FREQUENCY = 140, SENSOR_TYPE_ENERGY = 141, SENSOR_TYPE_ENERGY_STORAGE = 142, + SENSOR_TYPE_ABS_HUMIDITY = 143, + SENSOR_TYPE_ATMOS_PRESSURE = 144, + SENSOR_TYPE_BLOOD_GLUCOSE_C = 145, + SENSOR_TYPE_CO_ONLY = 146, + SENSOR_TYPE_ENERGY_DISTANCE = 147, + SENSOR_TYPE_GAS_ONLY = 148, + SENSOR_TYPE_NITROUS_OXIDE = 149, + SENSOR_TYPE_OZONE_ONLY = 150, + SENSOR_TYPE_PRICIPITATION = 151, + SENSOR_TYPE_PRICIPITATION_INTEN = 152, + SENSOR_TYPE_SULPHUR_DIOXIDE = 153, + SENSOR_TYPE_VOC_PARTS = 154, + SENSOR_TYPE_VOLUME = 155, + SENSOR_TYPE_VOLUME_FLOW_RATE = 156, + SENSOR_TYPE_VOLUME_STORAGE = 157, + SENSOR_TYPE_WATER = 158, SENSOR_TYPE_NOT_SET = 255 }; diff --git a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp index 89858a686..63e450657 100644 --- a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp +++ b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp @@ -836,6 +836,22 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn case Sensor_VType::SENSOR_TYPE_FREQUENCY: case Sensor_VType::SENSOR_TYPE_ENERGY: case Sensor_VType::SENSOR_TYPE_ENERGY_STORAGE: + case Sensor_VType::SENSOR_TYPE_ABS_HUMIDITY: + case Sensor_VType::SENSOR_TYPE_ATMOS_PRESSURE: + case Sensor_VType::SENSOR_TYPE_BLOOD_GLUCOSE_C: + case Sensor_VType::SENSOR_TYPE_CO_ONLY: + case Sensor_VType::SENSOR_TYPE_ENERGY_DISTANCE: + case Sensor_VType::SENSOR_TYPE_GAS_ONLY: + case Sensor_VType::SENSOR_TYPE_NITROUS_OXIDE: + case Sensor_VType::SENSOR_TYPE_OZONE_ONLY: + case Sensor_VType::SENSOR_TYPE_PRICIPITATION: + case Sensor_VType::SENSOR_TYPE_PRICIPITATION_INTEN: + case Sensor_VType::SENSOR_TYPE_SULPHUR_DIOXIDE: + case Sensor_VType::SENSOR_TYPE_VOC_PARTS: + case Sensor_VType::SENSOR_TYPE_VOLUME: + case Sensor_VType::SENSOR_TYPE_VOLUME_FLOW_RATE: + case Sensor_VType::SENSOR_TYPE_VOLUME_STORAGE: + case Sensor_VType::SENSOR_TYPE_WATER: { const String dev = getValueType2HADeviceClass(discoveryItems[s].VType); const String uomDef = getValueType2DefaultHAUoM(discoveryItems[s].VType); diff --git a/src/src/Helpers/_Plugin_SensorTypeHelper.cpp b/src/src/Helpers/_Plugin_SensorTypeHelper.cpp index cf7cc1558..3ac7b7e7b 100644 --- a/src/src/Helpers/_Plugin_SensorTypeHelper.cpp +++ b/src/src/Helpers/_Plugin_SensorTypeHelper.cpp @@ -99,13 +99,14 @@ String toValueTypeCategory(const uint32_t valueType) { const uint16_t value_type_map[] PROGMEM = { 0, // None 1024, 1, 5, 6, 7, // Basic - 1025, 2, 3, 4, 8, 101, 102, 122, 21, 131, // Environment - 1026, 106, 107, 108, 110, 121, 128, 129, // Gases - 1027, 116, 117, 118, 119, 120, 127, 139, 141, 142, // Energy + 1025, 2, 3, 4, 8, 101, 102, 122, 21, 131, 143, 144, 151, 152, // Environment + 1026, 106, 107, 108, 110, 146, 121, 154, 128, 129, 148, 149, 150, 153, // Gases + 1027, 116, 117, 118, 119, 120, 127, 139, 141, 142, 147, // Energy 1028, 132, 133, 134, // Time - 1029, 100, 104, 105, 109, 111, 115, 135, 136, 137, 138, // Size + 1029, 100, 104, 105, 109, 111, 115, 135, 136, 137, 138, 155, 156, 157, 158, // Size 1030, 103, 112, 113, 114, 123, 124, 125, 126, // Light 1031, 10, 130, 11, 22, 20, 31, 32, 33, 40, 41, 42, 43, 50, 51, 60, 61, 70, 71, 140, // Other + 145, // Other }; /* *INDENT-ON* */ diff --git a/src/src/WebServer/Markup.cpp b/src/src/WebServer/Markup.cpp index 05d68d211..579d2da4c 100644 --- a/src/src/WebServer/Markup.cpp +++ b/src/src/WebServer/Markup.cpp @@ -396,6 +396,8 @@ const char unit_of_measure_list[] PROGMEM = // *** DO NOT CHANGE ORDER, SAVED IN "mg/dL|mmol/L|" // 150..151 "μSv|μSv/h|" // 152..153 "m³/s|ft³/min|L/h|L/min|L/s|gal/min|mL/s|" // 154..160 + "g/m³|kWh/100km|Wh/km|mi/kWh|km/kWh|" // 161..165 + "in/d|mm/d" // 166 .. 167 ; // *** DO NOT CHANGE ORDER, SAVED IN TASK SETTINGS! *** @@ -435,12 +437,13 @@ const uint16_t unit_of_measure_map[] PROGMEM = { 1054, 26, 27, 153, 154, 155, 156, 157, 158, 159, 160, // Volume flow rate 1032, 39, 40, 41, 42, 43, 44, 45, 46, 47, // Duration 1034, 14, 15, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, // Energy(-storage) + 1033, 162, 163, 164, 165, // Energy distance 1050, 66, 67, 68, 69, 70, 71, 65, 106, 107, // Speed 1056, 35, // (Wind) direction 1038, 28, // Illuminance 1039, 64, 112, // Irradiance 1046, 152, 153, // Radiation - 1057, 29, 63, // Various units + 1057, 29, 63, 161, 166, 167, // Various units 1035, 52, 53, 131, 132, // Frequency 1043, 113, // Potential hydrogen 1026, 56, 122, 123, 124, 125, 126, 127, 128, 129, 130, // Area From 429948a07370b5e728d9018f009e6e7c0b43e2f9 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 9 Aug 2025 19:49:30 +0200 Subject: [PATCH 14/33] [AutoDiscover] Update Value type and UoM documentation --- docs/source/Plugin/_Plugin.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/source/Plugin/_Plugin.rst b/docs/source/Plugin/_Plugin.rst index 58655ca0f..720a2f9cd 100644 --- a/docs/source/Plugin/_Plugin.rst +++ b/docs/source/Plugin/_Plugin.rst @@ -247,13 +247,13 @@ On selected builds (ESP32 only, can be enabled in ESP8266 Custom builds) per val If set the UoM will be space-appended to the value when displayed on the Devices page, used in the labels for the Stats display, and later used in the MQTT AutoDiscovery messages so the receiving server can use that for presentation. When set it is also included in the JSON output as ``UoM`` per taskvalue, available at the ``/json`` endpoint of the ESP, to be used by external systems like EasyFetch. -A list of 150+ Unit of Measure values is available (derived of what's supported by Home Assistant): +A list of 165+ Unit of Measure values is available (derived of what's supported/expected by Home Assistant): ``°C, °F, K, %, Pa, hPa, bar, mbar, inHg, psi, W, kW, V, Wh, kWh, A, VA, mm, cm, m, km,`` ``L, mL, m³, ft³, m³/h, ft³/h, lx, UV index, µg/m³, mg/m³, p/m³, ppm, ppb,`` ``°, €, $, ¢, µs, ms, s, min, h, d, w, m, y, in, ft, yd, mi, Hz, GHz, gal, fl. oz, m²,`` ``g, kg, mg, µg, oz, lb, µS/cm, W/m², mm/h, mm/s, in/s, m/s, in/h, km/h, mph, db, dBm,`` ``bit, kbit, Mbit, Gbit, B, kB, MB, GB, TB, PB, EB, ZB, YB, KiB, MiB, GiB, TiB, PiB, EiB, ZiB, YiB,`` ``bit/s, kbit/s, Mbit/s, Gbit/s, B/s, kB/s, MB/s, GB/s, KiB/s, MiB/s, GiB/s,`` ``ft/s, kn, mW, MW, GW, TW, BTU/(h·ft²), pH, cbar, mmHg, kPa, mA, µA, mV, µV, kV, cm², km², mm²,`` ``in², ft², yd², mi², ac, ha, kHz, MHz, mWh, MWh, GWh, TWh, cal, kcal, Mcal,`` -``Gcal, J, kJ, MJ, GJ, var, kvar, varh, kvarh, st, mg/dL, mmol/L, µSv, µSv/h, m³/s, ft³/min,`` ``L/h, L/min, L/s, gal/min, mL/s`` +``Gcal, J, kJ, MJ, GJ, var, kvar, varh, kvarh, st, mg/dL, mmol/L, µSv, µSv/h, m³/s, ft³/min,`` ``L/h, L/min, L/s, gal/min, mL/s, g/m³, kWh/100km, Wh/km, mi/kWh, km/kWh, in/d, mm/d`` Displaying the Unit of Measure in the Devices overview page can be disabled by unchecking the **Show Unit of Measure** checkbox on the Tools/Advanced page. @@ -275,15 +275,15 @@ Available options, grouped per category: **Basic**: ``Single`` -**Environment**: ``Temp¹, Hum¹, Baro¹, Wind speed¹`` +**Environment**: ``Temp¹, Hum¹, Baro¹, Wind speed¹, Absolute humidity¹, Atmospheric pressure¹, Pricipitation¹,`` ``Pricipitation intensity¹`` -**Dust/Gases**: ``Dust PM2.5¹, Dust PM1.0¹, Dust PM10¹, (e)CO2¹, TVOC¹, AQI¹, NOx¹`` +**Dust/Gases**: ``Dust PM2.5¹, Dust PM1.0¹, Dust PM10¹, (e)CO2¹, CO¹, TVOC¹, VOC parts¹, AQI¹, NOx¹,`` ``Gas¹, N2O¹, Ozone¹, SO2¹`` -**Energy**: ``Voltage¹, Current¹, Power Usage¹, Power Factor¹, Apparent Power Usage¹,`` ``Reactive Power¹, Reactive Energy¹, Energy¹, Energy storage¹`` +**Energy**: ``Voltage¹, Current¹, Power Usage¹, Power Factor¹, Apparent Power Usage¹,`` ``Reactive Power¹, Reactive Energy¹, Energy¹, Energy storage¹, Energy distance¹`` **Time**: ``Duration¹, Date¹, Timestamp¹`` -**Size**: ``Analog, Distance¹, Direction¹, Moisture¹, GPS, Weight¹, Data rate¹, Data size¹, Sound pressure¹, Signal strength¹`` +**Size**: ``Analog, Distance¹, Direction¹, Moisture¹, GPS, Weight¹, Data rate¹, Data size¹, Sound pressure¹, Signal strength¹,`` ``Volume¹, Volume flow rate¹, Volume storage¹, Water¹`` **Light**: ``Lux¹, UV¹, UV Index¹, IR¹, Red¹, Green¹, Blue¹, Color temperature¹`` From dce9985e63ee8f85ad63b81d72de41a549a8f804 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 9 Aug 2025 22:36:17 +0200 Subject: [PATCH 15/33] [MQTT] Add Retain option per task --- src/_C002.cpp | 8 +++++++- src/_C005.cpp | 4 ++-- src/_C006.cpp | 11 +++++++---- src/_C014.cpp | 4 +++- src/src/DataStructs/SettingsStruct.h | 5 +++++ src/src/DataStructs_templ/SettingsStruct.cpp | 17 +++++++++++++++++ src/src/WebServer/DevicesPage.cpp | 15 +++++++++++++++ 7 files changed, 56 insertions(+), 8 deletions(-) diff --git a/src/_C002.cpp b/src/_C002.cpp index db832c2fc..8ea6af9f9 100644 --- a/src/_C002.cpp +++ b/src/_C002.cpp @@ -214,8 +214,14 @@ bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String& String pubname = CPlugin_002_pubname; parseControllerVariables(pubname, event, false); + const bool taskRetained = Settings.SendRetainedTaskValues(event->TaskIndex, event->ControllerIndex); + // Publish using move operator, thus pubname and json are empty after this call - success = MQTTpublish(event->ControllerIndex, event->TaskIndex, std::move(pubname), std::move(json), CPlugin_002_mqtt_retainFlag); + success = MQTTpublish(event->ControllerIndex, + event->TaskIndex, + std::move(pubname), + std::move(json), + CPlugin_002_mqtt_retainFlag || taskRetained); } // if idx !=0 else { diff --git a/src/_C005.cpp b/src/_C005.cpp index 85dede67c..e5bd86622 100644 --- a/src/_C005.cpp +++ b/src/_C005.cpp @@ -191,9 +191,9 @@ bool CPlugin_005(CPlugin::Function function, struct EventStruct *event, String& break; } - // String pubname = CPlugin_005_pubname; + const bool taskRetained = Settings.SendRetainedTaskValues(event->TaskIndex, event->ControllerIndex); - success = MQTT_protocol_send(event, CPlugin_005_pubname, CPlugin_005_mqtt_retainFlag); + success = MQTT_protocol_send(event, CPlugin_005_pubname, CPlugin_005_mqtt_retainFlag || taskRetained); break; } diff --git a/src/_C006.cpp b/src/_C006.cpp index ed248312c..24279077a 100644 --- a/src/_C006.cpp +++ b/src/_C006.cpp @@ -11,6 +11,7 @@ * 2023-03-15 tonhuisman: Handle setting payload to (Dummy) Devices via topic SysName/TaskName/ValueName/set * 2023-03 Changelog started */ + // # include "src/Commands/InternalCommands.h" # include "src/Commands/ExecuteCommand.h" # include "src/ESPEasyCore/Controller.h" @@ -44,9 +45,9 @@ bool CPlugin_006(CPlugin::Function function, struct EventStruct *event, String& proto.usesExtCreds = true; proto.defaultPort = 1883; proto.usesID = false; - #if FEATURE_MQTT_TLS - proto.usesTLS = true; - #endif + # if FEATURE_MQTT_TLS + proto.usesTLS = true; + # endif // if FEATURE_MQTT_TLS # if FEATURE_STRING_VARIABLES proto.allowSendDerived = true; # endif // if FEATURE_STRING_VARIABLES @@ -130,7 +131,9 @@ bool CPlugin_006(CPlugin::Function function, struct EventStruct *event, String& break; } - success = MQTT_protocol_send(event, CPlugin_006_pubname, CPlugin_006_mqtt_retainFlag); + const bool taskRetained = Settings.SendRetainedTaskValues(event->TaskIndex, event->ControllerIndex); + + success = MQTT_protocol_send(event, CPlugin_006_pubname, CPlugin_006_mqtt_retainFlag || taskRetained); break; } diff --git a/src/_C014.cpp b/src/_C014.cpp index ff73bc5b6..3dc1e176d 100644 --- a/src/_C014.cpp +++ b/src/_C014.cpp @@ -809,7 +809,9 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& break; } - success = MQTT_protocol_send(event, CPlugin_014_pubname, CPlugin_014_mqtt_retainFlag); + const bool taskRetained = Settings.SendRetainedTaskValues(event->TaskIndex, event->ControllerIndex); + + success = MQTT_protocol_send(event, CPlugin_014_pubname, CPlugin_014_mqtt_retainFlag || taskRetained); break; } diff --git a/src/src/DataStructs/SettingsStruct.h b/src/src/DataStructs/SettingsStruct.h index 8c2a31699..c3507bf91 100644 --- a/src/src/DataStructs/SettingsStruct.h +++ b/src/src/DataStructs/SettingsStruct.h @@ -264,6 +264,11 @@ class SettingsStruct_tmpl bool isPriorityTask(taskIndex_t taskIndex) const; #endif // if FEATURE_PLUGIN_PRIORITY + #if FEATURE_MQTT + bool SendRetainedTaskValues(taskIndex_t taskIndex, controllerIndex_t controllerIndex) const; + void SendRetainedTaskValues(taskIndex_t taskIndex, controllerIndex_t controllerIndex, bool value); + #endif // if FEATURE_MQTT + void validate(); bool networkSettingsEmpty() const; diff --git a/src/src/DataStructs_templ/SettingsStruct.cpp b/src/src/DataStructs_templ/SettingsStruct.cpp index 060b70278..d2473f5ef 100644 --- a/src/src/DataStructs_templ/SettingsStruct.cpp +++ b/src/src/DataStructs_templ/SettingsStruct.cpp @@ -479,6 +479,23 @@ bool SettingsStruct_tmpl::isPriorityTask(taskIndex_t taskIndex) const { } #endif // if FEATURE_PLUGIN_PRIORITY +#if FEATURE_MQTT +template +bool SettingsStruct_tmpl::SendRetainedTaskValues(taskIndex_t taskIndex, controllerIndex_t controllerIndex) const { + if (validTaskIndex(taskIndex) && validControllerIndex(controllerIndex)) { + return bitRead(VariousTaskBits[taskIndex], 2 + controllerIndex); // ATTENTION: uses bits 2..5!!! + } + return false; +} + +template +void SettingsStruct_tmpl::SendRetainedTaskValues(taskIndex_t taskIndex, controllerIndex_t controllerIndex, bool value) { + if (validTaskIndex(taskIndex) && validControllerIndex(controllerIndex)) { + bitWrite(VariousTaskBits[taskIndex], 2 + controllerIndex, value); // ATTENTION: uses bits 2..5!!! + } +} +#endif // if FEATURE_MQTT + template ExtTimeSource_e SettingsStruct_tmpl::ExtTimeSource() const { return static_cast(ExternalTimeSource >> 1); diff --git a/src/src/WebServer/DevicesPage.cpp b/src/src/WebServer/DevicesPage.cpp index 35c3b0435..449c39b6d 100644 --- a/src/src/WebServer/DevicesPage.cpp +++ b/src/src/WebServer/DevicesPage.cpp @@ -408,6 +408,9 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task #if FEATURE_STRING_VARIABLES Settings.SendDerivedTaskValues(taskIndex, controllerNr, isFormItemChecked(getPluginCustomArgName(F("TSND"), controllerNr))); #endif // if FEATURE_STRING_VARIABLES + #if FEATURE_MQTT + Settings.SendRetainedTaskValues(taskIndex, controllerNr, isFormItemChecked(getPluginCustomArgName(F("TSRT"), controllerNr))); + #endif // if FEATURE_MQTT } if (device.PullUpOption) { @@ -1613,6 +1616,18 @@ void devicePage_show_controller_config(taskIndex_t taskIndex, deviceIndex_t Devi getPluginCustomArgName(F("TDID"), controllerNr), // ="taskdeviceid" Settings.TaskDeviceID[controllerNr][taskIndex], 0, DOMOTICZ_MAX_IDX); } + #if FEATURE_MQTT + if (showMqttGroup) { + html_TD(); + addHtml(F("Retained:")); + html_TD(); + addCheckBox(getPluginCustomArgName(F("TSRT"), controllerNr), Settings.SendRetainedTaskValues(taskIndex, controllerNr), false + # if FEATURE_TOOLTIPS + , F("Send values with Retain flag") + # endif // if FEATURE_TOOLTIPS + ); + } + #endif // if FEATURE_MQTT # if FEATURE_STRING_VARIABLES if (allowSendDerived) { html_TD(); From b3d4b21ae532439f7f2095bb4b30a821f56f03eb Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sat, 9 Aug 2025 23:11:25 +0200 Subject: [PATCH 16/33] [MQTT] Update documentation for Retained setting per task --- docs/source/Controller/C005.rst | 2 ++ docs/source/Plugin/DataAcquisition.repl | 4 +++- .../Task_config_page_Controllers_section.png | Bin 13136 -> 17372 bytes 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/source/Controller/C005.rst b/docs/source/Controller/C005.rst index f16adf6cf..70aa7e465 100644 --- a/docs/source/Controller/C005.rst +++ b/docs/source/Controller/C005.rst @@ -85,6 +85,8 @@ A group of MQTT specific settings: * **Publish Retain Flag**: When publishing data accompanied by the Retain flag, the server will store the last sent data, and publish that when reconnecting. +This will override the **Retained** setting per Task that can be set for a MQTT Controller. + * **Controller Subscribe**: The topic(s) to subscribe to, so the MQTT Server can send data and optionally commands to this client-instance. * **Controller Publish**: The topic to publish data to. This field supports variables so each value sent to the server can have a separate topic for its data. diff --git a/docs/source/Plugin/DataAcquisition.repl b/docs/source/Plugin/DataAcquisition.repl index 78a0c48bd..c3631bbde 100644 --- a/docs/source/Plugin/DataAcquisition.repl +++ b/docs/source/Plugin/DataAcquisition.repl @@ -32,9 +32,11 @@ For some controllers, like Home Assistant/openHAB, there are extra options avail * **Group**: This represents the group id to combine all values from multiple tasks into a single grouped-device during MQTT AutoDiscovery. Groups, by design, can span multiple ESPEasy devices, if desired, as long as the Task/Valuename combinations are unique. If a group should only combine Tasks from a single ESPEasy unit, the group id should be unique across multiple ESPEasy units. The group description, default *Group *, can be adjusted in Home Assistant. +* **Retained**: For MQTT Controllers, this setting can be enabled to send the values for the current task with the Retain flag set. The **Publish Retain flag** in the Controller settings will override this by sending *all* task values with Retain flag enabled. + * **Send derived**: This checkbox determines if any configured Derived values should also be sent to the controller (and included in the AutoDiscovery if that's available and enabled). -* **Resend MQTT Discovery**: When checked, will start a resend of the MQTT Discovery process for this task after a random delay, when Submit is clicked, so any chenged settings can be updated in the MQTT server. This setting is only available if the controller is enabled, the Auto Discovery feature is available and enabled for the controller. This setting is not stored. +* **Resend MQTT Discovery**: When checked, will start a resend of the MQTT Discovery process for this task after a random delay, when Submit is clicked, so any chenged settings can be updated in the MQTT broker. This setting is only available if the controller is enabled, the Auto Discovery feature is available and enabled for the controller. This setting is not stored. Other controllers, like FHEM HTTP, do not support additional settings besides the checkbox to enable sending the data. diff --git a/docs/source/Plugin/Task_config_page_Controllers_section.png b/docs/source/Plugin/Task_config_page_Controllers_section.png index 8866628ab4c52c7ee2a9f685ab17ef2e5377be56..d9333203ee7bc4efd2263874c51be411e4904ae0 100644 GIT binary patch literal 17372 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 IXBzZB088BRe*gdg literal 13136 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եm|uC%a7uH zqxV_(WAvQ{ZQkHEz(hA0uC|tdsG-UX@H+tyl<>?n)?Y9oEaZ5%<&x&5=!hv8$+#m3D ze05xK79)Z~bB>snpKqU{!QP)MLMb||X?+4VR|4#v8EcUK%Ee5Os{XA*21_lvFREUj zFW)mY(BKx1pX6JsK+>Z@9d_U8ZJbm|Zc#aSLvAm8r#m`lXlO1;$2amsvj!O7Wlhs$ zOT_&24tam75^8Xz`ec;%trzM09b}~F&6QlGLmpAYBuXyQhK4$y*%+-D)X#J>R3I+D)- From d343f60d23974f0ba0443358f1f2800e6e159f1b Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sun, 10 Aug 2025 22:44:55 +0200 Subject: [PATCH 17/33] [MQTT] Some documentation improvements --- docs/source/Plugin/DataAcquisition.repl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/Plugin/DataAcquisition.repl b/docs/source/Plugin/DataAcquisition.repl index c3631bbde..f087b313f 100644 --- a/docs/source/Plugin/DataAcquisition.repl +++ b/docs/source/Plugin/DataAcquisition.repl @@ -22,7 +22,7 @@ Depending on the controller capabilities, some configuration settings may be sho .. image:: Task_config_page_Controllers_section.png -All configured Controllers are shown here, including their enabled or disabled state (only a single MQTT Controller can be enabled at one time). +All configured Controllers are shown here, including the enabled or disabled state (multiple Controllers can be enabled, only a single MQTT Controller can be enabled at one time!). For each controller the user can select wether the data should be sent on each Interval (or explicit TaskRun). @@ -36,8 +36,8 @@ For some controllers, like Home Assistant/openHAB, there are extra options avail * **Send derived**: This checkbox determines if any configured Derived values should also be sent to the controller (and included in the AutoDiscovery if that's available and enabled). -* **Resend MQTT Discovery**: When checked, will start a resend of the MQTT Discovery process for this task after a random delay, when Submit is clicked, so any chenged settings can be updated in the MQTT broker. This setting is only available if the controller is enabled, the Auto Discovery feature is available and enabled for the controller. This setting is not stored. +* **Resend MQTT Discovery**: When checked, will start a resend of the MQTT Discovery process for this task after a random delay, when Submit is clicked, so any changed settings will be updated in the MQTT broker. This setting is only available if the controller is enabled, the Auto Discovery feature is available and enabled for the controller. This setting is not stored. -Other controllers, like FHEM HTTP, do not support additional settings besides the checkbox to enable sending the data. +Other controllers, like f.e. FHEM HTTP, do not support additional settings besides the checkbox to enable sending the data. | From d1f7a2a0b93dae28c2e54c14c61affb566650c68 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Thu, 14 Aug 2025 22:47:03 +0200 Subject: [PATCH 18/33] [P089] Add support for ESP32 --- lib/ESPping/LICENSE | 201 ++++++++++ lib/ESPping/README.md | 41 ++ lib/ESPping/examples/test_ping/test_ping.ino | 61 +++ lib/ESPping/keywords.txt | 29 ++ lib/ESPping/library.properties | 9 + lib/ESPping/src/ESPping.cpp | 161 ++++++++ lib/ESPping/src/ESPping.h | 74 ++++ lib/ESPping/src/ping32.cpp | 384 +++++++++++++++++++ lib/ESPping/src/ping32.h | 37 ++ src/_P089_Ping.ino | 57 ++- src/src/CustomBuild/define_plugin_sets.h | 12 + src/src/Helpers/_Plugin_init.cpp | 8 - src/src/Helpers/_Plugin_init.h | 3 - src/src/PluginStructs/P089_data_struct.cpp | 110 +++++- src/src/PluginStructs/P089_data_struct.h | 70 +++- 15 files changed, 1229 insertions(+), 28 deletions(-) create mode 100644 lib/ESPping/LICENSE create mode 100644 lib/ESPping/README.md create mode 100644 lib/ESPping/examples/test_ping/test_ping.ino create mode 100644 lib/ESPping/keywords.txt create mode 100644 lib/ESPping/library.properties create mode 100644 lib/ESPping/src/ESPping.cpp create mode 100644 lib/ESPping/src/ESPping.h create mode 100644 lib/ESPping/src/ping32.cpp create mode 100644 lib/ESPping/src/ping32.h diff --git a/lib/ESPping/LICENSE b/lib/ESPping/LICENSE new file mode 100644 index 000000000..29f81d812 --- /dev/null +++ b/lib/ESPping/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/lib/ESPping/README.md b/lib/ESPping/README.md new file mode 100644 index 000000000..6b24c51d6 --- /dev/null +++ b/lib/ESPping/README.md @@ -0,0 +1,41 @@ +# ESPping +Let the ESP32/ESP8266 ping a remote machine. + +With this library an ESP32/ESP8266 can ping a remote machine and know if it's reachable. +It provide some basic measurements on ping messages (avg response time). + +thanks to Daniele Colanardi and Marian Craciunescu + +## Usage + +First, include the library in your sketch along with WiFi library: + +```Arduino +#include +``` + +Next, simply call the `Ping.ping()` function + +```Arduino +IPAddress ip (192, 168, 0, 1); // The remote ip to ping +bool ret = Ping.ping(ip); +``` + +`ret` will be true if the remote responded to pings, false if not reachable. +The library supports hostname too, just pass a string instead of the ip address: + +```Arduino +bool ret = Ping.ping("www.google.com"); +``` + +Additionally, the function accept a second integer parameter `count` that specify how many pings has to be sent: + +```Arduino +bool ret = Ping.ping(ip_or_host, 10); +``` + +After `Ping.ping()` has been called, the average response time (in milliseconds) can be retrieved with + +```Arduino +float avg_time_ms = Ping.averageTime(); +``` diff --git a/lib/ESPping/examples/test_ping/test_ping.ino b/lib/ESPping/examples/test_ping/test_ping.ino new file mode 100644 index 000000000..c471ed630 --- /dev/null +++ b/lib/ESPping/examples/test_ping/test_ping.ino @@ -0,0 +1,61 @@ +/****************************************************** + test Ping +******************************************************/ + +#include + +void setup() +{ + Serial.begin(115200); + delay(100); + + WiFi.begin("ssid","password"); + + // attente connexion + Serial.print("\nConnection"); + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + Serial.print("."); + } + Serial.print("\nConnection OK, IP: "); + Serial.print(WiFi.localIP()); + Serial.print(" Gateway: "); + Serial.println(WiFi.gatewayIP()); + Serial.println("You can try to ping me ;-)"); + +} + +void loop() +{ + + // Ping IP + const IPAddress remote_ip(9,9,9,9); + Serial.print(remote_ip); + if (Ping.ping(remote_ip) > 0){ + Serial.printf(" response time : %d/%.2f/%d ms\n", Ping.minTime(), Ping.averageTime(), Ping.maxTime()); + } else { + Serial.println(" Error !"); + } + delay(1000); + + // Ping Host + const char* remote_host = "quad9.net"; + Serial.print(remote_host); + if (Ping.ping(remote_host) > 0){ + Serial.printf(" response time : %d/%.2f/%d ms\n", Ping.minTime(), Ping.averageTime(), Ping.maxTime()); + } else { + Serial.println(" Ping Error !"); + } + delay(1000); + + // Ping local IP + Serial.print(WiFi.gatewayIP()); + if (Ping.ping(WiFi.gatewayIP()) > 0){ + Serial.printf(" response time : %d/%.2f/%d ms\n", Ping.minTime(), Ping.averageTime(), Ping.maxTime()); + } else { + Serial.println(" Ping Error !"); + } + delay(1000); + +} diff --git a/lib/ESPping/keywords.txt b/lib/ESPping/keywords.txt new file mode 100644 index 000000000..08d3e0b21 --- /dev/null +++ b/lib/ESPping/keywords.txt @@ -0,0 +1,29 @@ +####################################### +# Syntax Coloring Map For ESPping +####################################### + +####################################### +# Library (KEYWORD3) +####################################### + +ESPping KEYWORD3 + +####################################### +# Class (KEYWORD1) +####################################### + +Ping KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +ping KEYWORD2 +minTime KEYWORD2 +averageTime KEYWORD2 +maxTime KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + diff --git a/lib/ESPping/library.properties b/lib/ESPping/library.properties new file mode 100644 index 000000000..d5b459d76 --- /dev/null +++ b/lib/ESPping/library.properties @@ -0,0 +1,9 @@ +name=ESPping +version=1.0.5 +author=dvarrel, Daniele Colanardi, Marian Craciunescu +maintainer=dvarrel +sentence=Let the ESP32/ESP8266 ping a remote machine. +paragraph=With this library an ESP32/ESP8266 can ping a remote machine and know if reachable. It provide some basic measurements on ping messages (avg response time). +category=Communication +url=https://github.com/dvarrel/ESPping.git +architectures=esp8266,esp32 diff --git a/lib/ESPping/src/ESPping.cpp b/lib/ESPping/src/ESPping.cpp new file mode 100644 index 000000000..2d151fd6e --- /dev/null +++ b/lib/ESPping/src/ESPping.cpp @@ -0,0 +1,161 @@ +/* + ESP8266Ping - Ping library for ESP8266 + Copyright (c) 2015 Daniele Colanardi. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "ESPping.h" +#include + +#ifdef ESP8266 +extern "C" void esp_schedule(); +extern "C" void esp_yield(); +#endif +#ifdef ESP32 +extern "C" void esp_schedule(void) {}; +extern "C" void esp_yield(void) {}; +#endif + +PingClass::PingClass() {} + +bool PingClass::ping(IPAddress dest, int16_t count) { + _expected_count = count; + _errors = 0; + _success = 0; + + _min_time = INT_MAX; + _avg_time = 0.0f; + _max_time = 0; + + + memset(&_options, 0, sizeof(struct ping_option)); + + // Repeat count (how many time send a ping message to destination) + _options.count = count; + // Time interval between two ping (seconds??) + _options.coarse_time = 1; + // Destination machine + _options.ip = dest; + + // Callbacks + _options.recv_function = reinterpret_cast(&PingClass::_ping_recv_cb); + _options.sent_function = NULL; //reinterpret_cast(&_ping_sent_cb); + + // Let's go! + if(ping_start(&_options)) { + #ifdef ESP8266 + //wait until finished + while((_success + _errors) < _expected_count){ + delay(1); + esp_yield(); + }; + #else + // Suspend till the process end + esp_yield(); + #endif + } + return (_success > 0); +} + +bool PingClass::ping(const char* host, int16_t count) { + IPAddress remote_addr; + + if (WiFi.hostByName(host, remote_addr)) + return ping(remote_addr, count); + + return false; +} + +float PingClass::averageTime() { + return _avg_time; +} + +float PingClass::minTime() { + return _min_time; +} + +float PingClass::maxTime() { + return _max_time; +} + +void PingClass::_ping_recv_cb(void *opt, void *resp) { + // Cast the parameters to get some usable info + ping_resp* ping_resp = reinterpret_cast(resp); + //ping_option* ping_opt = reinterpret_cast(opt); + + // Error or success? + #ifdef ESP8266 + if (ping_resp->ping_err == -1) + _errors++; + else { + _success++; + _avg_time += ping_resp->resp_time; + if(ping_resp->resp_time < _min_time) + _min_time = ping_resp->resp_time; + if(ping_resp->resp_time > _max_time) + _max_time = ping_resp->resp_time; + + } + #else + _errors = ping_resp->ping_err; + _success = _expected_count - _errors; + _avg_time = ping_resp->resp_time; + #endif + + // Some debug info + DEBUG_PING( + "DEBUG: ping reply" + "\ttotal_count = %d" + "\tresp_time = %d" + "\tseqno = %d" + "\ttimeout_count = %d" + "\tbytes = %d" + "\ttotal_bytes = %d" + "\ttotal_time = %d" + "\tping_err = %d \n", + (int)ping_resp->total_count, (int)ping_resp->resp_time, (int)ping_resp->seqno, + (int)ping_resp->timeout_count, (int)ping_resp->bytes, (int)ping_resp->total_bytes, + (int)ping_resp->total_time, (int)ping_resp->ping_err + ); + + // Is it time to end? + // Don't using seqno because it does not increase on error + #ifdef ESP8266 + if (_success + _errors == _expected_count ) { + _avg_time = _success > 0 ? _avg_time / _success : 0; + #else + if (ping_resp->total_count== _expected_count) { + + #endif + + DEBUG_PING("success %d errors %d\n", _success, _errors); + DEBUG_PING("Resp times min %d, avg %.2f, max %d ms\n", _min_time, _avg_time, _max_time); + + #ifdef ESP32 + // Done, return to main functiom + esp_schedule(); + #endif + } +} + +byte PingClass::_expected_count = 0; +byte PingClass::_errors = 0; +byte PingClass::_success = 0; +float PingClass::_avg_time = 0; +float PingClass::_min_time = std::numeric_limits::max(); +float PingClass::_max_time = 0; + +PingClass Ping; + diff --git a/lib/ESPping/src/ESPping.h b/lib/ESPping/src/ESPping.h new file mode 100644 index 000000000..22ff396e0 --- /dev/null +++ b/lib/ESPping/src/ESPping.h @@ -0,0 +1,74 @@ +/* + ESP32Ping - Ping library for ESP32 or ESP8266 + Copyright (c) 2018 Marian Craciunescu. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ESPping_H +#define ESPping_H + +#include +#include +#ifdef ESP32 +#include +#include +extern "C" { + void esp_schedule(void); + void esp_yield(void); +} +#endif + +#ifdef ESP8266 +#include +extern "C" { + #include +} +#endif + +//#define ENABLE_DEBUG_PING 0 +#ifdef ENABLE_DEBUG_PING + #define DEBUG_PING(...) Serial.printf(__VA_ARGS__) +#else + #define DEBUG_PING(...) +#endif + +class PingClass { + public: + PingClass(); + + bool ping(IPAddress dest, int16_t count = 5); + bool ping(const char* host, int16_t count = 5); + + float averageTime(); + float minTime(); + float maxTime(); + + protected: + static void _ping_sent_cb(void *opt, void *pdata); + static void _ping_recv_cb(void *opt, void *pdata); + + IPAddress _dest; + ping_option _options; + + static byte _expected_count, _errors, _success; + static float _min_time, _max_time; + static float _avg_time; +}; + +extern PingClass Ping; + +#endif + diff --git a/lib/ESPping/src/ping32.cpp b/lib/ESPping/src/ping32.cpp new file mode 100644 index 000000000..2c7f05ab0 --- /dev/null +++ b/lib/ESPping/src/ping32.cpp @@ -0,0 +1,384 @@ +/* +* ESP32 Ping library +* +* All rights reserved. +* +* Permission to use, copy, modify, and distribute this software +* and its documentation for any purpose and without fee is hereby +* granted, provided that the above copyright notice appear in all +* copies and that both that the copyright notice and this +* permission notice and warranty disclaimer appear in supporting +* documentation, and that the name of the author not be used in +* advertising or publicity pertaining to distribution of the +* software without specific, written prior permission. +* +* The author disclaim all warranties with regard to this +* software, including all implied warranties of merchantability +* and fitness. In no event shall the author be liable for any +* special, indirect or consequential damages or any damages +* whatsoever resulting from loss of use, data or profits, whether +* in an action of contract, negligence or other tortious action, +* arising out of or in connection with the use or performance of +* this software. +* +* -------------------------------------------------------------------------------- +* Ping Library is based on the following source code: +* +* Lua RTOS, ping utility +* +* +* Author: Jaume Oliv� (jolive@iberoxarxa.com / jolive@whitecatboard.org) +* +* -------------------------------------------------------------------------------- +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* 3. The name of the author may not be used to endorse or promote products +* derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +* SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +* OF SUCH DAMAGE. +* +* This file is part of the lwIP TCP/IP stack. +* +*/ +#ifdef ESP32 +#include + +#include +#include +#include +#include +#include +#include + +#include "ping32.h" + +#include "lwip/inet_chksum.h" +#include "lwip/ip.h" +#include "lwip/ip4.h" +#include "lwip/err.h" +#include "lwip/icmp.h" +#include "lwip/sockets.h" +#include "lwip/sys.h" +#include "lwip/netdb.h" +#include "lwip/dns.h" + +static uint16_t ping_seq_num; +static uint8_t stopped = 0; + +/* +* Statistics +*/ +static uint32_t transmitted = 0; +static uint32_t received = 0; +static float min_time = 0; +static float max_time = 0; +static float mean_time = 0; +static float last_mean_time = 0; +static float var_time = 0; + +#define PING_ID 0xAFAF + +#ifndef PING_DEFAULT_COUNT +#define PING_DEFAULT_COUNT 10 +#endif +#ifndef PING_DEFAULT_INTERVAL +#define PING_DEFAULT_INTERVAL 1 +#endif +#ifndef PING_DEFAULT_SIZE +#define PING_DEFAULT_SIZE 32 +#endif +#ifndef PING_DEFAULT_TIMEOUT +#define PING_DEFAULT_TIMEOUT 1 +#endif + +/* +* Helper functions +* +*/ +static void ping_prepare_echo(struct icmp_echo_hdr *iecho, uint16_t len) { + size_t i; + size_t data_len = len - sizeof(struct icmp_echo_hdr); + + ICMPH_TYPE_SET(iecho, ICMP_ECHO); + ICMPH_CODE_SET(iecho, 0); + iecho->chksum = 0; + iecho->id = PING_ID; + iecho->seqno = htons(++ping_seq_num); + + /* fill the additional data buffer with some data */ + for (i = 0; i < data_len; i++) { + ((char*)iecho)[sizeof(struct icmp_echo_hdr) + i] = (char)i; + } + + iecho->chksum = inet_chksum(iecho, len); +} + +static err_t ping_send(int s, ip4_addr_t *addr, int size) { + struct icmp_echo_hdr *iecho; + struct sockaddr_in to; + size_t ping_size = sizeof(struct icmp_echo_hdr) + size; + int err; + + iecho = (struct icmp_echo_hdr *)mem_malloc((mem_size_t)ping_size); + if (!iecho) { + mem_free(iecho); + return ERR_MEM; + } + + ping_prepare_echo(iecho, (uint16_t)ping_size); + + to.sin_len = sizeof(to); + to.sin_family = AF_INET; + inet_addr_from_ip4addr(&to.sin_addr, addr); + + if ((err = sendto(s, iecho, ping_size, 0, (struct sockaddr*)&to, sizeof(to)))) { + transmitted++; + } + mem_free(iecho); + return (err ? ERR_OK : ERR_VAL); +} + +static void ping_recv(int s) { + char buf[64]; + int fromlen, len; + struct sockaddr_in from; + struct ip_hdr *iphdr; + struct icmp_echo_hdr *iecho = NULL; + char ipa[16]; + struct timeval begin; + struct timeval end; + uint64_t micros_begin; + uint64_t micros_end; + float elapsed; + + // Register begin time + gettimeofday(&begin, NULL); + + // Send + while ((len = recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr*)&from, (socklen_t*)&fromlen)) > 0) { + if (len >= (int)(sizeof(struct ip_hdr) + sizeof(struct icmp_echo_hdr))) { + // Register end time + gettimeofday(&end, NULL); + + /// Get from IP address + ip4_addr_t fromaddr; + inet_addr_to_ip4addr(&fromaddr, &from.sin_addr); + + strcpy(ipa, inet_ntoa(fromaddr)); + + // Get echo + iphdr = (struct ip_hdr *)buf; + iecho = (struct icmp_echo_hdr *)(buf + (IPH_HL(iphdr) * 4)); + + // Print .... + if ((iecho->id == PING_ID) && (iecho->seqno == htons(ping_seq_num))) { + received++; + + // Get elapsed time in milliseconds + micros_begin = begin.tv_sec * 1000000; + micros_begin += begin.tv_usec; + + micros_end = end.tv_sec * 1000000; + micros_end += end.tv_usec; + + elapsed = (float)(micros_end - micros_begin) / (float)1000.0; + + // Update statistics + // Mean and variance are computed in an incremental way + if (elapsed < min_time) { + min_time = elapsed; + } + + if (elapsed > max_time) { + max_time = elapsed; + } + + last_mean_time = mean_time; + mean_time = (((received - 1) * mean_time) + elapsed) / received; + + if (received > 1) { + var_time = var_time + ((elapsed - last_mean_time) * (elapsed - mean_time)); + } + + // Print ... + log_d("%d bytes from %s: icmp_seq=%d time=%.3f ms", len, ipa, + ntohs(iecho->seqno), elapsed + ); + + return; + } + else { + // TODO + } + } + } + + if (len < 0) { + log_d("Request timeout for icmp_seq %d", ping_seq_num); + } +} +/* +static void stop_action(int i) { + signal(i, SIG_DFL); + + stopped = 1; +} +*/ +/* +* Operation functions +* +*/ +void ping(const char *name, int16_t count, int interval, int size, int timeout) { + // Resolve name + hostent * target = gethostbyname(name); + IPAddress adr = *target->h_addr_list[0]; + if (target->h_length == 0) { + // TODO: error not found target????? + return; + } + ping_start(adr, count, interval, size, timeout); +} + +bool ping_start(struct ping_option *ping_o) { + + + return ping_start(ping_o->ip,ping_o->count,0,0,0,ping_o); + +} +bool ping_start(IPAddress adr, int16_t count=0, int interval=0, int size=0, int timeout=0, struct ping_option *ping_o) { +// driver_error_t *error; + struct sockaddr_in address; + ip4_addr_t ping_target; + int s; + // Get default values if argument are not provided + if (count == 0) { + count = PING_DEFAULT_COUNT; + } + + if (interval == 0) { + interval = PING_DEFAULT_INTERVAL; + } + + if (size == 0) { + size = PING_DEFAULT_SIZE; + } + + if (timeout == 0) { + timeout = PING_DEFAULT_TIMEOUT; + } + + // Create socket + if ((s = socket(AF_INET, SOCK_RAW, IP_PROTO_ICMP)) < 0) { + // TODO: error + return false; + } + + + address.sin_addr.s_addr = adr; + ping_target.addr = address.sin_addr.s_addr; + + // Setup socket + struct timeval tout; + + // Timeout + tout.tv_sec = timeout; + tout.tv_usec = 0; + + if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tout, sizeof(tout)) < 0) { + closesocket(s); + // TODO: error + return false; + } + + stopped = 0; + transmitted = 0; + received = 0; + min_time = 1.E+9;// FLT_MAX; + max_time = 0.0; + mean_time = 0.0; + var_time = 0.0; + + // Register signal for stop ping + //signal(SIGINT, stop_action); + + // Begin ping ... + char ipa[16]; + + strcpy(ipa, inet_ntoa(ping_target)); + log_i("PING %s: %d data bytes", ipa, size); + + ping_seq_num = 0; + + unsigned long ping_started_time = millis(); + while ((ping_seq_num < count) && (!stopped)) { + if (ping_send(s, &ping_target, size) == ERR_OK) { + ping_recv(s); + } + if(ping_seq_num < count){ + delay( interval*1000L); + } + } + + closesocket(s); + + log_i("%d packets transmitted, %d packets received, %.1f%% packet loss", + transmitted, + received, + ((((float)transmitted - (float)received) / (float)transmitted) * 100.0) + ); + + + if (ping_o) { + ping_resp pingresp; + log_i("round-trip min/avg/max/stddev = %.3f/%.3f/%.3f/%.3f ms", min_time, mean_time, max_time, sqrt(var_time / received)); + pingresp.total_count = count; //Number of pings + pingresp.resp_time = mean_time; //Average time for the pings + pingresp.seqno = 0; //not relevant + pingresp.timeout_count = transmitted - received; //number of pings which failed + pingresp.bytes = size; //number of bytes received for 1 ping + pingresp.total_bytes = size * count; //number of bytes for all pings + pingresp.total_time = (millis() - ping_started_time) / 1000.0; //Time consumed for all pings; it takes into account also timeout pings + pingresp.ping_err = transmitted - received; //number of pings failed + // Call the callback function + ping_o->recv_function(ping_o, &pingresp); + } + + // Return true if at least one ping had a successfull "pong" + return (received > 0); +} + +bool ping_regist_recv(struct ping_option *ping_opt, ping_recv_function ping_recv) +{ + if (ping_opt == NULL) + return false; + + ping_opt->recv_function = ping_recv; + return true; +} + +bool ping_regist_sent(struct ping_option *ping_opt, ping_sent_function ping_sent) +{ + if (ping_opt == NULL) + return false; + + ping_opt->sent_function = ping_sent; + return true; +} + +#endif diff --git a/lib/ESPping/src/ping32.h b/lib/ESPping/src/ping32.h new file mode 100644 index 000000000..b0d5a3c34 --- /dev/null +++ b/lib/ESPping/src/ping32.h @@ -0,0 +1,37 @@ +#ifdef ESP32 + +#ifndef PING32_H +#define PING32_H + +#include +#include + +typedef void(*ping_recv_function)(void* arg, void *pdata); +typedef void(*ping_sent_function)(void* arg, void *pdata); + +struct ping_option { + int16_t count; + uint32_t ip; + uint32_t coarse_time; + ping_recv_function recv_function; + ping_sent_function sent_function; + void* reverse; +}; + +struct ping_resp { + uint32_t total_count; + float resp_time; + uint32_t seqno; + uint32_t timeout_count; + uint32_t bytes; + uint32_t total_bytes; + float total_time; + int8_t ping_err; +}; + +bool ping_start(struct ping_option *ping_opt); +void ping(const char *name, int16_t count, int interval, int size, int timeout); +bool ping_start(IPAddress adr, int16_t count, int interval, int size, int timeout, struct ping_option *ping_o = NULL); + +#endif +#endif // PING_H diff --git a/src/_P089_Ping.ino b/src/_P089_Ping.ino index ae7a4b577..580e890d1 100644 --- a/src/_P089_Ping.ino +++ b/src/_P089_Ping.ino @@ -1,5 +1,5 @@ #include "_Plugin_Helper.h" -#if defined(USES_P089) && defined(ESP8266) +#ifdef USES_P089 # include "src/PluginStructs/P089_data_struct.h" @@ -19,6 +19,8 @@ Maintainer: Denys Fedoryshchenko, denys AT nuclearcat.com */ /** Changelog: + * 2025-08-14 tonhuisman: Add code to plugin to use dvarrel/ESPping library that works for ESP32. + * ESP32 handles the ping in an independent RTOS task, to make it non-blocking. * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported for Ping) * 2023-03-19 tonhuisman: Show hostname in GPIO column of Devices page * 2023-03-14 tonhuisman: Change command handling to not require the taskname as the second argument if no 3rd argument is given. @@ -30,6 +32,7 @@ # define PLUGIN_089 # define PLUGIN_NAME_089 "Communication - Ping" # define PLUGIN_VALUENAME1_089 "Fails" +# define PLUGIN_VALUENAME2_089 "Avg_ms" boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string) @@ -60,14 +63,34 @@ boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_GET_DEVICEVALUENAMES: { strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_089)); + # ifdef ESP32 + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_089)); + # endif // ifdef ESP32 break; } + # ifdef ESP32 + case PLUGIN_GET_DEVICEVALUECOUNT: + { + int vCount = P089_VALUE_COUNT; + + if (0 == vCount) { + vCount = 1; + } + event->Par1 = vCount; + success = true; + break; + } + # endif // ifdef ESP32 + # if FEATURE_MQTT_DISCOVER case PLUGIN_GET_DISCOVERY_VTYPES: { event->Par1 = static_cast(Sensor_VType::SENSOR_TYPE_NONE); // Not yet supported - success = true; + # ifdef ESP32 + event->Par2 = static_cast(Sensor_VType::SENSOR_TYPE_DURATION); + # endif // ifdef ESP32 + success = true; break; } # endif // if FEATURE_MQTT_DISCOVER @@ -92,12 +115,35 @@ boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string) char hostname[PLUGIN_089_HOSTNAME_SIZE]{}; LoadCustomTaskSettings(event->TaskIndex, (uint8_t *)&hostname, PLUGIN_089_HOSTNAME_SIZE); addFormTextBox(F("Hostname"), F("host"), hostname, PLUGIN_089_HOSTNAME_SIZE - 2); + + # ifdef ESP32 + + if (P089_PING_COUNT < 1) { + P089_PING_COUNT = 5; + } + addFormNumericBox(F("Ping count"), F("pcount"), P089_PING_COUNT, 1, 100); + + const __FlashStringHelper*countOptions[] = { + F("Fails"), + F("Fails, Avg_ms"), + }; + const int countValues[] = { 1, 2 }; + FormSelectorOptions selector(2, countOptions, countValues); + selector.reloadonchange = true; + selector.addFormSelector(F("Available Values"), F("pvals"), P089_VALUE_COUNT); + # endif // ifdef ESP32 + success = true; break; } case PLUGIN_WEBFORM_SAVE: { + # ifdef ESP32 + P089_PING_COUNT = getFormItemInt(F("pcount"), 5); + P089_VALUE_COUNT = getFormItemInt(F("pvals"), 1); + # endif // ifdef ESP32 + char hostname[PLUGIN_089_HOSTNAME_SIZE]{}; // Reset "Fails" if settings updated @@ -112,6 +158,9 @@ boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string) { success = initPluginTaskData(event->TaskIndex, new (std::nothrow) P089_data_struct()); UserVar.setFloat(event->TaskIndex, 0, 0); + # ifdef ESP32 + UserVar.setFloat(event->TaskIndex, 1, 0); + # endif // ifdef ESP32 break; } @@ -120,7 +169,7 @@ boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string) P089_data_struct *P089_taskdata = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr == P089_taskdata) { + if ((nullptr == P089_taskdata) || !P089_taskdata->isInitialized()) { break; } @@ -165,4 +214,4 @@ boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string) return success; } -#endif // if defined(USES_P089) && defined(ESP8266) +#endif // ifdef USES_P089 diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 9a6a2bc2f..5fc15eca6 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -1753,6 +1753,9 @@ To create/register a plugin, you have to : #ifndef USES_P085 #define USES_P085 // AcuDC24x #endif + #ifndef USES_P089 + #define USES_P089 // Ping + #endif #ifndef USES_P093 #define USES_P093 // Mitsubishi Heat Pump #endif @@ -1977,6 +1980,9 @@ To create/register a plugin, you have to : #ifndef USES_P083 #define USES_P083 // SGP30 #endif + #ifndef USES_P089 + #define USES_P089 // Ping + #endif #ifndef USES_P090 #define USES_P090 // CCS811 #endif @@ -2094,6 +2100,9 @@ To create/register a plugin, you have to : #ifndef USES_P070 #define USES_P070 // NeoPixel_Clock #endif + #ifndef USES_P089 + #define USES_P089 // Ping + #endif #ifndef USES_P128 #define USES_P128 // NeoPixelBusFX #endif @@ -2326,6 +2335,9 @@ To create/register a plugin, you have to : #ifndef USES_P088 #define USES_P088 // ToniA IR plugin #endif + #ifndef USES_P089 + #define USES_P089 // Ping + #endif #ifndef USES_P094 #define USES_P094 // CUL Reader #endif diff --git a/src/src/Helpers/_Plugin_init.cpp b/src/src/Helpers/_Plugin_init.cpp index 9365b8d79..874df65f9 100644 --- a/src/src/Helpers/_Plugin_init.cpp +++ b/src/src/Helpers/_Plugin_init.cpp @@ -371,11 +371,7 @@ constexpr /*pluginID_t*/ uint8_t DeviceIndex_to_Plugin_id[] PROGMEM = #endif // ifdef USES_P088 #ifdef USES_P089 - # ifdef ESP8266 - - // FIXME TD-er: Support Ping plugin for ESP32 89, - # endif // ifdef ESP8266 #endif // ifdef USES_P089 #ifdef USES_P090 @@ -1404,11 +1400,7 @@ constexpr const Plugin_ptr_t PROGMEM Plugin_ptr[] = #endif // ifdef USES_P088 #ifdef USES_P089 - # ifdef ESP8266 - - // FIXME TD-er: Support Ping plugin for ESP32 &Plugin_089, - # endif // ifdef ESP8266 #endif // ifdef USES_P089 #ifdef USES_P090 diff --git a/src/src/Helpers/_Plugin_init.h b/src/src/Helpers/_Plugin_init.h index 512c61038..79436fd04 100644 --- a/src/src/Helpers/_Plugin_init.h +++ b/src/src/Helpers/_Plugin_init.h @@ -390,10 +390,7 @@ void PluginInit(bool priorityOnly = false); #endif #ifdef USES_P089 - #ifdef ESP8266 - // FIXME TD-er: Support Ping plugin for ESP32 ADDPLUGIN_H(089) - #endif #endif #ifdef USES_P090 diff --git a/src/src/PluginStructs/P089_data_struct.cpp b/src/src/PluginStructs/P089_data_struct.cpp index 6da7f59ed..d36d67a90 100644 --- a/src/src/PluginStructs/P089_data_struct.cpp +++ b/src/src/PluginStructs/P089_data_struct.cpp @@ -1,13 +1,114 @@ #include "../PluginStructs/P089_data_struct.h" -#if defined(USES_P089) && defined(ESP8266) +#ifdef USES_P089 -#include "../Helpers/Networking.h" +# include "../Helpers/Networking.h" -#include "../Helpers/_Plugin_init.h" +# include "../Helpers/_Plugin_init.h" +# ifdef ESP32 +P089_data_struct::P089_data_struct() { + espPing = new (std::nothrow) PingClass(); + _ping_task_data.initialized = isInitialized(); +} +P089_data_struct::~P089_data_struct() { + if (_ping_task_data.taskHandle) { + vTaskDelete(_ping_task_data.taskHandle); + } + delete espPing; +} + +void P089_execute_ping_task(void *parameter) +{ + P089_ping_task_data*ping_task_data = static_cast(parameter); + + if ((ping_task_data->status == P089_status::Working) && (nullptr != ping_task_data->espPing)) { + // Blocking operation + ping_task_data->result = ping_task_data->espPing->ping(ping_task_data->pingIp, ping_task_data->count); + + // Results are in + ping_task_data->avgTime = ping_task_data->espPing->averageTime(); + ping_task_data->status = P089_status::Ready; + } + + ping_task_data->taskHandle = NULL; + vTaskDelete(ping_task_data->taskHandle); +} + +bool P089_data_struct::send_ping(struct EventStruct *event) { + /* This ping lost for sure */ + if (!isInitialized() || !NetworkConnected()) { + return true; + } + + if (!_ping_task_data.initialized) { + // addLog(LOG_LEVEL_ERROR, F("PING : Not initialized.")); + return true; // Not (yet?) initialized + } + + if (_ping_task_data.status == P089_status::Working) { + // addLog(LOG_LEVEL_INFO, F("PING : Still working.")); + return false; // Busy, but not a failure + } + + if (_ping_task_data.status == P089_status::Ready) { + if (_ping_task_data.result) { + addLog(LOG_LEVEL_INFO, strformat(F("PING : Successfully pinged %s (%s) count: %d avg: %.03f ms"), + _ping_task_data.hostname.c_str(), + _ping_task_data.pingIp.toString().c_str(), + _ping_task_data.count, + _ping_task_data.avgTime)); + } else { + addLog(LOG_LEVEL_ERROR, strformat(F("PING : Error pinging %s (%s)"), + _ping_task_data.hostname.c_str(), + _ping_task_data.pingIp.toString().c_str())); + } + UserVar.setFloat(event->TaskIndex, 1, _ping_task_data.avgTime); // Set always, even when not specifically enabled + + _ping_task_data.status = P089_status::Initial; // Ready for new work + return !_ping_task_data.result; // Inverted result to report back! + } + + IPAddress ip; + char hostname[PLUGIN_089_HOSTNAME_SIZE]{}; + + LoadCustomTaskSettings(event->TaskIndex, (uint8_t *)&hostname, PLUGIN_089_HOSTNAME_SIZE); + + /* This one lost as well, DNS dead? */ + if (!resolveHostByName(hostname, ip)) { + return true; + } + + int16_t pingCount = P089_PING_COUNT; + + if ((pingCount < 1) || (pingCount > 100)) { + pingCount = 5; + } + + _ping_task_data.status = P089_status::Working; + _ping_task_data.count = pingCount; + _ping_task_data.pingIp = ip; + _ping_task_data.hostname = hostname; // Bring all data to the task + _ping_task_data.espPing = espPing; + + xTaskCreatePinnedToCore( + P089_execute_ping_task, // Function that should be called + "PingClass.ping()", // Name of the task (for debugging) + 4000, // Stack size (bytes) + &_ping_task_data, // Parameter to pass + 1, // Task priority + &_ping_task_data.taskHandle, // Task handle + xPortGetCoreID() // Core you want to run the task on (0 or 1) + ); + + return false; // Started work, not a failure +} + +# endif // ifdef ESP32 + +# ifdef ESP8266 P089_data_struct::P089_data_struct() { destIPAddress.addr = 0; idseq = 0; @@ -159,4 +260,5 @@ uint8_t PingReceiver(void *origin, struct raw_pcb *pcb, struct pbuf *packetBuffe return 1; } -#endif // if defined(USES_P089) && defined(ESP8266) +# endif // ifdef ESP8266 +#endif // ifdef USES_P089 diff --git a/src/src/PluginStructs/P089_data_struct.h b/src/src/PluginStructs/P089_data_struct.h index 8e8cefa23..5947a1d1f 100644 --- a/src/src/PluginStructs/P089_data_struct.h +++ b/src/src/PluginStructs/P089_data_struct.h @@ -2,7 +2,7 @@ #define PLUGINSTRUCTS_P089_DATA_STRUCT_H #include "../../_Plugin_Helper.h" -#if defined(USES_P089) && defined(ESP8266) +#ifdef USES_P089 # define PLUGIN_ID_089 89 @@ -12,16 +12,64 @@ # define ICMP_PAYLOAD_LEN 32 +# ifdef ESP32 +# define P089_PING_COUNT PCONFIG(0) +# define P089_VALUE_COUNT PCONFIG(1) + +# include + +enum class P089_status : uint8_t { + Initial = 0, + Working = 1, + Ready = 2, +}; + +struct P089_ping_task_data { + bool initialized{}; + P089_status status = P089_status::Initial; + IPAddress pingIp; + String hostname; + bool result{}; + uint16_t count{}; + float avgTime{}; + PingClass *espPing = nullptr; + + // This is C-code, so not set to nullptr, but to NULL + TaskHandle_t taskHandle = NULL; +}; + +struct P089_data_struct : public PluginTaskData_base { +public: + + P089_data_struct(); + + virtual ~P089_data_struct(); + + bool isInitialized() const { + return nullptr != espPing; + } + + bool send_ping(struct EventStruct *event); + +private: + + PingClass *espPing = nullptr; + P089_ping_task_data _ping_task_data; +}; +# endif // ifdef ESP32 + +# ifdef ESP8266 + extern "C" { -# include -# include // needed for icmp packet definitions -# include // needed for inet_chksum() -# include // needed for sys_now() -# include +# include +# include // needed for icmp packet definitions +# include // needed for inet_chksum() +# include // needed for sys_now() +# include } -# include "src/ESPEasyCore/ESPEasyNetwork.h" +# include "src/ESPEasyCore/ESPEasyNetwork.h" struct P089_icmp_pcb { @@ -39,6 +87,10 @@ public: bool send_ping(struct EventStruct *event); + bool isInitialized() const { + return nullptr != P089_data; + } + struct P089_icmp_pcb *P089_data = nullptr; ip_addr_t destIPAddress; uint32_t idseq; @@ -50,6 +102,6 @@ uint8_t PingReceiver(void *origin, struct pbuf *packetBuffer, const ip_addr_t *addr); - -#endif // if defined(USES_P089) && defined(ESP8266) +# endif // ifdef ESP8266 +#endif // ifdef USES_P089 #endif // ifndef PLUGINSTRUCTS_P089_DATA_STRUCT_H From c6db7fc500a4d5aedaf570adebae444a58a3a83f Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Thu, 14 Aug 2025 23:24:26 +0200 Subject: [PATCH 19/33] [P089] Remove unneeded attribute --- src/src/PluginStructs/P089_data_struct.cpp | 8 +------- src/src/PluginStructs/P089_data_struct.h | 1 - 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/src/PluginStructs/P089_data_struct.cpp b/src/src/PluginStructs/P089_data_struct.cpp index d36d67a90..89a1ff326 100644 --- a/src/src/PluginStructs/P089_data_struct.cpp +++ b/src/src/PluginStructs/P089_data_struct.cpp @@ -9,8 +9,7 @@ # ifdef ESP32 P089_data_struct::P089_data_struct() { - espPing = new (std::nothrow) PingClass(); - _ping_task_data.initialized = isInitialized(); + espPing = new (std::nothrow) PingClass(); } P089_data_struct::~P089_data_struct() { @@ -43,11 +42,6 @@ bool P089_data_struct::send_ping(struct EventStruct *event) { return true; } - if (!_ping_task_data.initialized) { - // addLog(LOG_LEVEL_ERROR, F("PING : Not initialized.")); - return true; // Not (yet?) initialized - } - if (_ping_task_data.status == P089_status::Working) { // addLog(LOG_LEVEL_INFO, F("PING : Still working.")); return false; // Busy, but not a failure diff --git a/src/src/PluginStructs/P089_data_struct.h b/src/src/PluginStructs/P089_data_struct.h index 5947a1d1f..ba5183ceb 100644 --- a/src/src/PluginStructs/P089_data_struct.h +++ b/src/src/PluginStructs/P089_data_struct.h @@ -25,7 +25,6 @@ enum class P089_status : uint8_t { }; struct P089_ping_task_data { - bool initialized{}; P089_status status = P089_status::Initial; IPAddress pingIp; String hostname; From 87267bc64ca15d2177f000f3e9181d4f7824548f Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Fri, 15 Aug 2025 20:23:52 +0200 Subject: [PATCH 20/33] [P089] Create separate Ping Service to handle single-instance Ping function sequentially --- src/_P089_Ping.ino | 21 ++- src/src/PluginStructs/P089_data_struct.cpp | 118 ++++++++------- src/src/PluginStructs/P089_data_struct.h | 30 +--- .../P089_ping_service_struct.cpp | 142 ++++++++++++++++++ .../PluginStructs/P089_ping_service_struct.h | 88 +++++++++++ 5 files changed, 319 insertions(+), 80 deletions(-) create mode 100644 src/src/PluginStructs/P089_ping_service_struct.cpp create mode 100644 src/src/PluginStructs/P089_ping_service_struct.h diff --git a/src/_P089_Ping.ino b/src/_P089_Ping.ino index 580e890d1..9bde02327 100644 --- a/src/_P089_Ping.ino +++ b/src/_P089_Ping.ino @@ -19,6 +19,10 @@ Maintainer: Denys Fedoryshchenko, denys AT nuclearcat.com */ /** Changelog: + * 2025-08-15 tonhuisman: Move actual ping action to a service, instantiated by the first task instance, and deleted by the last stopped + * task instance. Service is looped in 10 per second function. + * Enable PluginStats for ESP32, so the trend of average ping-time can be displayed. + * 2025-08-15 tonhuisman: Reduce max. ping count from 100 to 25 * 2025-08-14 tonhuisman: Add code to plugin to use dvarrel/ESPping library that works for ESP32. * ESP32 handles the ping in an independent RTOS task, to make it non-blocking. * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported for Ping) @@ -51,6 +55,9 @@ boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string) dev.DecimalsOnly = true; dev.SendDataOption = true; dev.TimerOption = true; + # ifdef ESP32 + dev.PluginStats = true; + # endif // ifdef ESP32 break; } @@ -121,7 +128,7 @@ boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string) if (P089_PING_COUNT < 1) { P089_PING_COUNT = 5; } - addFormNumericBox(F("Ping count"), F("pcount"), P089_PING_COUNT, 1, 100); + addFormNumericBox(F("Ping count"), F("pcount"), P089_PING_COUNT, 1, P089_MAX_PING_COUNT); const __FlashStringHelper*countOptions[] = { F("Fails"), @@ -164,6 +171,18 @@ boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string) break; } + # ifdef ESP32 + case PLUGIN_TEN_PER_SECOND: + { + P089_data_struct *P089_taskdata = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P089_taskdata) && P089_taskdata->isInitialized()) { + success = P089_taskdata->loop(); + } + break; + } + # endif // ifdef ESP32 case PLUGIN_READ: { P089_data_struct *P089_taskdata = diff --git a/src/src/PluginStructs/P089_data_struct.cpp b/src/src/PluginStructs/P089_data_struct.cpp index 89a1ff326..0652b6da0 100644 --- a/src/src/PluginStructs/P089_data_struct.cpp +++ b/src/src/PluginStructs/P089_data_struct.cpp @@ -9,31 +9,27 @@ # ifdef ESP32 P089_data_struct::P089_data_struct() { - espPing = new (std::nothrow) PingClass(); + if (nullptr == P089_ping_service) { + P089_ping_service = new (std::nothrow) P089_ping_service_struct(); + addLog(LOG_LEVEL_INFO, F("PING : Starting ping service.")); + } + + if ((nullptr != P089_ping_service) && P089_ping_service->isInitialized()) { + addLog(LOG_LEVEL_INFO, F("PING : Increment task instance counter.")); + P089_ping_service->increment(); + } } P089_data_struct::~P089_data_struct() { - if (_ping_task_data.taskHandle) { - vTaskDelete(_ping_task_data.taskHandle); + if (nullptr != P089_ping_service) { + addLog(LOG_LEVEL_INFO, F("PING : Decrement task instance counter.")); + + if (0 == P089_ping_service->decrement()) { + addLog(LOG_LEVEL_INFO, F("PING : Stopping ping service.")); + delete P089_ping_service; + P089_ping_service = nullptr; + } } - delete espPing; -} - -void P089_execute_ping_task(void *parameter) -{ - P089_ping_task_data*ping_task_data = static_cast(parameter); - - if ((ping_task_data->status == P089_status::Working) && (nullptr != ping_task_data->espPing)) { - // Blocking operation - ping_task_data->result = ping_task_data->espPing->ping(ping_task_data->pingIp, ping_task_data->count); - - // Results are in - ping_task_data->avgTime = ping_task_data->espPing->averageTime(); - ping_task_data->status = P089_status::Ready; - } - - ping_task_data->taskHandle = NULL; - vTaskDelete(ping_task_data->taskHandle); } bool P089_data_struct::send_ping(struct EventStruct *event) { @@ -42,28 +38,41 @@ bool P089_data_struct::send_ping(struct EventStruct *event) { return true; } - if (_ping_task_data.status == P089_status::Working) { + if ((nullptr == P089_ping_service) || + !P089_ping_service->isInitialized()) { + return true; // Service not available + } + + if (P089_ping_service->getPingResult(event->TaskIndex, _ping_request)) { + // Get requested result + + if (_ping_request.status == P089_request_status::Result) { + if (_ping_request.result) { + addLog(LOG_LEVEL_INFO, strformat(F("PING : Successfully pinged %s (%s) count: %d avg: %.03f ms for task: %d"), + _ping_request.hostname.c_str(), + _ping_request.ip.toString().c_str(), + _ping_request.count, + _ping_request.avgTime, + event->TaskIndex + 1)); + } else { + addLog(LOG_LEVEL_ERROR, strformat(F("PING : Error pinging %s (%s) for task: %d"), + _ping_request.hostname.c_str(), + _ping_request.ip.toString().c_str(), + event->TaskIndex + 1)); + } + UserVar.setFloat(event->TaskIndex, 1, _ping_request.avgTime); // Set always, even when not specifically enabled + + _ping_request.status = P089_request_status::Finished; // Ready for new work + return !_ping_request.result; // Inverted result to report back! + } + } + + if ((_ping_request.status > P089_request_status::Request) && (_ping_request.status < P089_request_status::Result)) { // addLog(LOG_LEVEL_INFO, F("PING : Still working.")); return false; // Busy, but not a failure } - if (_ping_task_data.status == P089_status::Ready) { - if (_ping_task_data.result) { - addLog(LOG_LEVEL_INFO, strformat(F("PING : Successfully pinged %s (%s) count: %d avg: %.03f ms"), - _ping_task_data.hostname.c_str(), - _ping_task_data.pingIp.toString().c_str(), - _ping_task_data.count, - _ping_task_data.avgTime)); - } else { - addLog(LOG_LEVEL_ERROR, strformat(F("PING : Error pinging %s (%s)"), - _ping_task_data.hostname.c_str(), - _ping_task_data.pingIp.toString().c_str())); - } - UserVar.setFloat(event->TaskIndex, 1, _ping_task_data.avgTime); // Set always, even when not specifically enabled - - _ping_task_data.status = P089_status::Initial; // Ready for new work - return !_ping_task_data.result; // Inverted result to report back! - } + // New request IPAddress ip; char hostname[PLUGIN_089_HOSTNAME_SIZE]{}; @@ -77,27 +86,24 @@ bool P089_data_struct::send_ping(struct EventStruct *event) { int16_t pingCount = P089_PING_COUNT; - if ((pingCount < 1) || (pingCount > 100)) { + if ((pingCount < 1) || (pingCount > P089_MAX_PING_COUNT)) { pingCount = 5; } - _ping_task_data.status = P089_status::Working; - _ping_task_data.count = pingCount; - _ping_task_data.pingIp = ip; - _ping_task_data.hostname = hostname; // Bring all data to the task - _ping_task_data.espPing = espPing; + _ping_request.status = P089_request_status::Request; + _ping_request.count = pingCount; + _ping_request.ip = ip; + _ping_request.hostname = hostname; // Bring all data to the request + const bool result = !P089_ping_service->addPingRequest(event->TaskIndex, _ping_request); + P089_ping_service->loop(); // Kick-off if not already working + return result; +} - xTaskCreatePinnedToCore( - P089_execute_ping_task, // Function that should be called - "PingClass.ping()", // Name of the task (for debugging) - 4000, // Stack size (bytes) - &_ping_task_data, // Parameter to pass - 1, // Task priority - &_ping_task_data.taskHandle, // Task handle - xPortGetCoreID() // Core you want to run the task on (0 or 1) - ); - - return false; // Started work, not a failure +bool P089_data_struct::loop() { + if ((nullptr != P089_ping_service) && P089_ping_service->isInitialized()) { + return P089_ping_service->loop(); + } + return false; // We did nothing } # endif // ifdef ESP32 diff --git a/src/src/PluginStructs/P089_data_struct.h b/src/src/PluginStructs/P089_data_struct.h index ba5183ceb..40bd838c0 100644 --- a/src/src/PluginStructs/P089_data_struct.h +++ b/src/src/PluginStructs/P089_data_struct.h @@ -13,29 +13,13 @@ # define ICMP_PAYLOAD_LEN 32 # ifdef ESP32 + +# include "../PluginStructs/P089_ping_service_struct.h" + # define P089_PING_COUNT PCONFIG(0) # define P089_VALUE_COUNT PCONFIG(1) -# include - -enum class P089_status : uint8_t { - Initial = 0, - Working = 1, - Ready = 2, -}; - -struct P089_ping_task_data { - P089_status status = P089_status::Initial; - IPAddress pingIp; - String hostname; - bool result{}; - uint16_t count{}; - float avgTime{}; - PingClass *espPing = nullptr; - - // This is C-code, so not set to nullptr, but to NULL - TaskHandle_t taskHandle = NULL; -}; +# define P089_MAX_PING_COUNT 25 struct P089_data_struct : public PluginTaskData_base { public: @@ -45,15 +29,15 @@ public: virtual ~P089_data_struct(); bool isInitialized() const { - return nullptr != espPing; + return true; } bool send_ping(struct EventStruct *event); + bool loop(); private: - PingClass *espPing = nullptr; - P089_ping_task_data _ping_task_data; + P089_ping_request _ping_request; }; # endif // ifdef ESP32 diff --git a/src/src/PluginStructs/P089_ping_service_struct.cpp b/src/src/PluginStructs/P089_ping_service_struct.cpp new file mode 100644 index 000000000..8bb612c19 --- /dev/null +++ b/src/src/PluginStructs/P089_ping_service_struct.cpp @@ -0,0 +1,142 @@ + +#include "../PluginStructs/P089_ping_service_struct.h" + +#ifdef ESP32 + +P089_ping_service_struct*P089_ping_service = nullptr; + +/** + * Ping execute handler task + */ +void P089_execute_ping_task(void *parameter) +{ + P089_ping_task_data*ping_task_data = static_cast(parameter); + + if ((ping_task_data->status == P089_task_status::Working) && (nullptr != ping_task_data->espPing)) { + // Blocking operation + ping_task_data->result = ping_task_data->espPing->ping(ping_task_data->ip, ping_task_data->count); + + // Results are in + ping_task_data->avgTime = ping_task_data->espPing->averageTime(); + ping_task_data->status = P089_task_status::Ready; + } + + ping_task_data->taskHandle = NULL; + vTaskDelete(ping_task_data->taskHandle); +} + +/** + * P089_ping_service_struct + */ +P089_ping_service_struct::P089_ping_service_struct() { + espPing = new (std::nothrow) PingClass(); +} + +P089_ping_service_struct::~P089_ping_service_struct() { + if (_ping_task_data.taskHandle) { + vTaskDelete(_ping_task_data.taskHandle); + _ping_task_data.taskHandle = NULL; + } + _requests.clear(); + delete espPing; + espPing = nullptr; +} + +bool P089_ping_service_struct::addPingRequest(taskIndex_t TaskIndex, + P089_ping_request request) { + bool result = false; // Something is not ok + auto it = _requests.find(TaskIndex); + + if ((request.status == P089_request_status::Request) && + ((it == _requests.end()) || + ((it->first == TaskIndex) && (it->second.status == P089_request_status::Finished)))) { // No request or Finished for this task + _requests[TaskIndex] = request; + _requests[TaskIndex].status = P089_request_status::Accepted; + _requests[TaskIndex].lastStarted = millis(); + result = true; + } + + return result; +} + +bool P089_ping_service_struct::getPingResult(taskIndex_t TaskIndex, + P089_ping_request& request) { + bool result = false; + auto it = _requests.find(TaskIndex); + + if (it != _requests.end()) { + request = it->second; + + if (it->second.status == P089_request_status::Result) { + it->second.status = P089_request_status::Finished; // Can be removed + result = true; + } + } + + // Check if we need to clean outdated requests + it = _requests.begin(); + + while (it != _requests.end()) { + // Cleanup when counter reached + if (timePassedSince(it->second.lastStarted) >= P089_FINISHED_REQUEST_TIMEOUT_MS) { + addLog(LOG_LEVEL_INFO, strformat(F("PING : Clean last request for task %d (%u s)"), + it->first + 1, timePassedSince(it->second.lastStarted) / 1000)); + _requests.erase(it); // Cleanup + it = _requests.begin(); // Restart loop + } else { + ++it; + } + } + return result; +} + +bool P089_ping_service_struct::loop() { + bool result = false; + + if (timePassedSince(lastLooped) < 100) { // Some call/load throtteling + return result; + } + lastLooped = millis(); + + // addLog(LOG_LEVEL_INFO, F("PING : Running P089_ping_service->loop()")); // FIXME + + if (_ping_task_data.status == P089_task_status::Ready) { + auto it = _requests.find(_ping_task_data.taskIndex); + + if (it != _requests.end()) { + it->second.result = _ping_task_data.result; + it->second.avgTime = _ping_task_data.avgTime; + it->second.status = P089_request_status::Result; // Can be retrieved + result = true; + } + _ping_task_data.status = P089_task_status::Initial; // Available for new job + } + + if (_ping_task_data.status == P089_task_status::Initial) { // Find next job + auto it = _requests.begin(); + + for (; it != _requests.end(); ++it) { + if (it->second.status == P089_request_status::Accepted) { + _ping_task_data.taskIndex = it->first; + _ping_task_data.status = P089_task_status::Working; + _ping_task_data.count = it->second.count; + _ping_task_data.ip = it->second.ip; + _ping_task_data.espPing = espPing; + + xTaskCreatePinnedToCore( + P089_execute_ping_task, // Function that should be called + "PingClass.ping()", // Name of the task (for debugging) + 4000, // Stack size (bytes) + &_ping_task_data, // Parameter to pass + 1, // Task priority + &_ping_task_data.taskHandle, // Task handle + xPortGetCoreID() // Core you want to run the task on (0 or 1) + ); + break; + } + } + } + return result; +} + +#endif // ifdef ESP32 diff --git a/src/src/PluginStructs/P089_ping_service_struct.h b/src/src/PluginStructs/P089_ping_service_struct.h new file mode 100644 index 000000000..e15edc00a --- /dev/null +++ b/src/src/PluginStructs/P089_ping_service_struct.h @@ -0,0 +1,88 @@ +#pragma once + +#include "../../_Plugin_Helper.h" + +#ifdef ESP32 + +# include +# include + +constexpr uint32_t P089_FINISHED_REQUEST_TIMEOUT_MS = 1000 * 60 * 10; // Delete entry after unused for this number of milliseconds (10 min.) + +struct P089_ping_service_struct; // Forward declaration + +extern P089_ping_service_struct*P089_ping_service; + +enum class P089_task_status : uint8_t { + Initial = 0, // Ready to be start a task + Working = 1, // Working on a task + Ready = 2, // Ready, data is available +}; + +enum class P089_request_status : uint8_t { + Request = 0, // Initial request + Accepted = 1, // Can be processed + Processing = 2, // Actively being processed + Ready = 3, // Results in + Result = 4, // Results retrieved + Finished = 5, // Can be removed or reused +}; + +struct P089_ping_task_data { + taskIndex_t taskIndex{}; + P089_task_status status = P089_task_status::Initial; + IPAddress ip; + bool result{}; + uint16_t count{}; + float avgTime{}; + PingClass *espPing = nullptr; + + // This is C-code, so not set to nullptr, but to NULL + TaskHandle_t taskHandle = NULL; +}; + +struct P089_ping_request { + P089_request_status status = P089_request_status::Request; + IPAddress ip; + String hostname; + uint16_t count{}; + bool result{}; + float avgTime{}; + uint32_t lastStarted{}; +}; + +struct P089_ping_service_struct { +public: + + P089_ping_service_struct(); + virtual ~P089_ping_service_struct(); + + bool isInitialized() { + return nullptr != espPing; + } + + bool addPingRequest(taskIndex_t TaskIndex, + P089_ping_request request); + bool getPingResult(taskIndex_t TaskIndex, + P089_ping_request& request); + bool loop(); + + uint8_t increment() { + return ++taskCounter; // No checks needed? + } + + uint8_t decrement() { + return --taskCounter; // No checks needed? + } + +private: + + uint32_t lastLooped{}; + uint8_t taskCounter{}; + + std::map_requests; + PingClass *espPing = nullptr; + P089_ping_task_data _ping_task_data; +}; + +#endif // ifdef ESP32 From 90ac8a8f0c7663d86eb69fe6eafc65f5e952ba47 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 20 Aug 2025 20:00:29 +0200 Subject: [PATCH 21/33] [P037] Use configured decimals when generating events --- src/_P037_MQTTImport.ino | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/_P037_MQTTImport.ino b/src/_P037_MQTTImport.ino index f6085759f..c5687b4db 100644 --- a/src/_P037_MQTTImport.ino +++ b/src/_P037_MQTTImport.ino @@ -11,6 +11,7 @@ // This task reads data from the MQTT Import input stream and saves the value /** + * 2025-08-20 tonhuisman: Generate events with numeric values using the configured decimals setting. * 2025-06-14 tonhuisman: Add support for Custom Value Type per task value * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported for MQTT Import) * Update changelog @@ -643,10 +644,10 @@ boolean Plugin_037(uint8_t function, struct EventStruct *event, String& string) # if !defined(P037_LIMIT_BUILD_SIZE) || defined(P037_OVERRIDE) if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, strformat(F("IMPT : [%s#%s] : %.3f"), + addLog(LOG_LEVEL_INFO, strformat(F("IMPT : [%s#%s] : %s"), getTaskDeviceName(event->TaskIndex).c_str(), checkJson ? key.c_str() : getTaskValueName(event->TaskIndex, x).c_str(), - doublePayload)); + toString(doublePayload, ExtraTaskSettings.TaskDeviceValueDecimals[x]).c_str())); } # endif // if !defined(P037_LIMIT_BUILD_SIZE) || defined(P037_OVERRIDE) @@ -687,7 +688,7 @@ boolean Plugin_037(uint8_t function, struct EventStruct *event, String& string) RuleEvent += '='; if (numericPayload) { - RuleEvent += doublePayload; + RuleEvent += toString(doublePayload, ExtraTaskSettings.TaskDeviceValueDecimals[x]); } else { RuleEvent += wrapWithQuotesIfContainsParameterSeparatorChar(Payload); } From d765093c74e868df1054a1a609215b6b1846833d Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 20 Aug 2025 20:04:21 +0200 Subject: [PATCH 22/33] [P037] Update documentation for: use configured decimals when generating events --- docs/source/Plugin/P037.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/Plugin/P037.rst b/docs/source/Plugin/P037.rst index 5e3fa5a3c..6b53535bf 100644 --- a/docs/source/Plugin/P037.rst +++ b/docs/source/Plugin/P037.rst @@ -179,7 +179,7 @@ The number of mappings is currently limited to 25, but only the number of used m Option: Generate events for accepted topics ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* **Generate events for accepted topics**: Enabling this option will generate an event for every incoming, and accepted if Filtering is used, non-JSON payload even when that has a non-numeric value. The event generated is: ``#=``, for example ``MQTT_Import#Value1=on``. +* **Generate events for accepted topics**: Enabling this option will generate an event for every incoming, and accepted if Filtering is used, non-JSON payload even when that has a non-numeric value. The event generated is: ``#=``, for example ``MQTT_Import#Value1=on``. When the ```` is numeric, the configured number of decimals is applied to the value. When this option is disabled it has the backward-compatible behavior of discarding that message as invalid. From a7f0c6b77839af09555e7b19bdb0b1e078edbc94 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Thu, 21 Aug 2025 22:07:00 +0200 Subject: [PATCH 23/33] [P168] Disable blocking Lux Read-Method options --- src/_P168_VEML6030_7700.ino | 28 +++++++++++++++------- src/src/PluginStructs/P168_data_struct.cpp | 18 ++++++++++---- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/_P168_VEML6030_7700.ino b/src/_P168_VEML6030_7700.ino index dd761a868..b4b324350 100644 --- a/src/_P168_VEML6030_7700.ino +++ b/src/_P168_VEML6030_7700.ino @@ -6,6 +6,8 @@ // ####################################################################################################### /** + * 2025-08-21 tonhuisman: Remove Lux Read-Method options without 'no wait' as that causes massive delays, disturbing other ESPEasy + * operations * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery * 2024-06-21 tonhuisman: Fix support for VEML6030, using by default the alternate I2C address, by modifying the VEML7700 library * 2024-05-18 tonhuisman: Implement AutoLux feature, and Get Config Value for automatically determined gain and integration @@ -95,7 +97,7 @@ boolean Plugin_168(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_SET_DEFAULTS: { - P168_READLUX_MODE = VEML_LUX_AUTO; + P168_READLUX_MODE = VEML_LUX_CORRECTED_NOWAIT; P168_PSM_MODE = static_cast(P168_power_save_mode_e::Disabled); ExtraTaskSettings.TaskDeviceValueDecimals[1] = 0; // White @@ -108,24 +110,34 @@ boolean Plugin_168(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_LOAD: { { + uint8_t _readMethod = P168_READLUX_MODE; + + if (static_cast(VEML_LUX_NORMAL) == _readMethod) { + _readMethod = static_cast(VEML_LUX_NORMAL_NOWAIT); + } else if ((static_cast(VEML_LUX_CORRECTED) == _readMethod) || (static_cast(VEML_LUX_AUTO) == _readMethod)) { + _readMethod = static_cast(VEML_LUX_CORRECTED_NOWAIT); + } + P168_READLUX_MODE = _readMethod; + const __FlashStringHelper *readMethod[] = { - F("Normal"), - F("Corrected"), - F("Auto"), + // F("Normal"), + // F("Corrected"), + // F("Auto"), F("Normal (no wait)"), F("Corrected (no wait)"), }; const int readMethodOptions[] = { - VEML_LUX_NORMAL, - VEML_LUX_CORRECTED, - VEML_LUX_AUTO, + // VEML_LUX_NORMAL, + // VEML_LUX_CORRECTED, + // VEML_LUX_AUTO, VEML_LUX_NORMAL_NOWAIT, VEML_LUX_CORRECTED_NOWAIT, }; constexpr size_t optionCount = NR_ELEMENTS(readMethodOptions); const FormSelectorOptions selector(optionCount, readMethod, readMethodOptions); selector.addFormSelector(F("Lux Read-method"), F("rmth"), P168_READLUX_MODE); - addFormNote(F("For 'Auto' Read-method, the Gain factor and Integration time settings are ignored.")); + + // addFormNote(F("For 'Auto' Read-method, the Gain factor and Integration time settings are ignored.")); } { const __FlashStringHelper *alsGain[] = { diff --git a/src/src/PluginStructs/P168_data_struct.cpp b/src/src/PluginStructs/P168_data_struct.cpp index 07ff87d71..5d71cf803 100644 --- a/src/src/PluginStructs/P168_data_struct.cpp +++ b/src/src/PluginStructs/P168_data_struct.cpp @@ -12,7 +12,15 @@ P168_data_struct::P168_data_struct(uint8_t alsGain, uint8_t psmMode, uint8_t readMethod) : _als_gain(alsGain), _als_integration(alsIntegration), _psm_mode(psmMode), _readMethod(readMethod), initialized(false) -{} +{ + // We already apply the correct WAIT for a read, so no need or desire to wait any longer. + // AUTO enforces multiple WAIT options sequentially, so that's inhibited + if (static_cast(VEML_LUX_NORMAL) == _readMethod) { + _readMethod = static_cast(VEML_LUX_NORMAL_NOWAIT); + } else if ((static_cast(VEML_LUX_CORRECTED) == _readMethod) || (static_cast(VEML_LUX_AUTO) == _readMethod)) { + _readMethod = static_cast(VEML_LUX_CORRECTED_NOWAIT); + } +} P168_data_struct::~P168_data_struct() { delete veml; @@ -51,10 +59,10 @@ bool P168_data_struct::plugin_read(struct EventStruct *event) { float lux = veml->readLux(static_cast(_readMethod)); uint16_t whi = veml->readWhite(); - if (luxMethod::VEML_LUX_AUTO == static_cast(_readMethod)) { - addLog(LOG_LEVEL_INFO, strformat(F("VEML : 6030/7700 AutoLux, Lux: %.2f, Gain: %.3f, Integration: %d"), - lux, veml->getGainValue(), veml->getIntegrationTimeValue())); - } + // if (luxMethod::VEML_LUX_AUTO == static_cast(_readMethod)) { + // addLog(LOG_LEVEL_INFO, strformat(F("VEML : 6030/7700 AutoLux, Lux: %.2f, Gain: %.3f, Integration: %d"), + // lux, veml->getGainValue(), veml->getIntegrationTimeValue())); + // } UserVar.setFloat(event->TaskIndex, 0, lux); UserVar.setFloat(event->TaskIndex, 1, whi); UserVar.setFloat(event->TaskIndex, 2, amb); From d4434bdef0d682c86b721e6afd5b6cff922df9b9 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Thu, 21 Aug 2025 22:08:54 +0200 Subject: [PATCH 24/33] [P168] Update documentation --- docs/source/Plugin/P168.rst | 12 ++---------- .../Plugin/P168_DeviceConfiguration.png | Bin 55949 -> 58351 bytes .../Plugin/P168_LuxReadMethodOptions.png | Bin 16644 -> 9366 bytes 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/docs/source/Plugin/P168.rst b/docs/source/Plugin/P168.rst index 66e8eaf9a..9a6320868 100644 --- a/docs/source/Plugin/P168.rst +++ b/docs/source/Plugin/P168.rst @@ -53,15 +53,9 @@ Device Settings .. image:: P168_LuxReadMethodOptions.png :alt: Lux read-method options -* *Normal*: Normal read mode, will report the uncorrected ``Lux`` value. +* *Normal (no wait)*: Normal read mode, will report the uncorrected ``Lux`` value, doesn't wait for the sensor to complete a measurement, but retrieves it later after starting the read. -* *Corrected*: Corrected read mode, Raw calculated to ``Lux`` and corrected for non-linearity. - -* *Auto*: Show the corrected value, auto-scaled with Gain factor and Integration time to avoid measuring errors because of over-exposing the sensor. This is the default setting. - -* *Normal (no wait)*: Normal read mode, will report the uncorrected ``Lux`` value, doesn't wait for the sensor to complete a measurement. - -* *Corrected (no wait)*: Corrected read mode, Raw calculated to ``Lux`` and corrected for non-linearity, doesn't wait for the sensor to complete a measurement. +* *Corrected (no wait)*: Corrected read mode, Raw calculated to ``Lux`` and corrected for non-linearity, doesn't wait for the sensor to complete a measurement, but retrieves it later after starting the read. | @@ -75,8 +69,6 @@ Device Settings .. image:: P168_IntegrationTimeOptions.png -.. warning:: When the **Lux Read-method** is set to *Auto* the Gain factor and Integration time settings are ignored. - * **Power Save Mode**: The sensor can go into Power Save Mode on several levels, to conserve power when used in a battery operated device. When in power save mode, the integration time increases. .. image:: P168_PowerSaveModeOptions.png diff --git a/docs/source/Plugin/P168_DeviceConfiguration.png b/docs/source/Plugin/P168_DeviceConfiguration.png index 802c0abe7d23b941c3fd9aa0aaf45ef82b49f51f..c1076fe3a346dcdf70d4e0dd8e84145e2d612451 100644 GIT binary patch literal 58351 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

`&gR_iv8=Yv+QGaiW2L(@wiLi7Oe{sWOv~_ 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 ZjX_ao1>T3>;)@`L7fj9<>tDb3KLC&%pA`TA literal 55949 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

(E6cr$Dc-&d(U*yBD2 zp1g_Lh6|_-%XkS8FRk0k2HaMLRU9xq#$FUol4y0@wwTn28zU> z&|i6MWp0?|@|ZEGW8rkn&#K9kchL}nwFlMzzI1}s=!fwh!RMdS4-TAjRt9kD!ONlP zy(5be!)06n&mrl=i_PzRbWznLB8(P*g8lKT>&Q(5rPO zSD#tHd3wG(4{=3LZ?x0hby=aRMSnbkA*6e(!Lpw@fSK`r#?~B+T&SA&hT3P9%~1@l zeA2Pep2@Vd#lK4Fs=E+MY{86pt0%oQ)FXckax9<%O*!T^pVd**D6vI;biRh}o6_kd z--Ja2z?O_wwMN5DRr~A07^@?7GS9h$lwu+$x|0(v)S1|yxW?`UGRN_K<{dRB#u+89 zXn$Xh^(FT!SrJ{OX%Fx{dRW%2?yd~DL!EQT&C3pF%Tw|GSBmYuq+MyoR~xKb=SmgL z&G-&Hw0(n4>)0GCvf$z*^BwU$LVhKYnitLA9Q>+W*91(~CV7)J>zwi&7}^{q;1F)- z84m9+1+Ojg8b=Y3bpL+qHRk&|S@kb;V=2H7>7NF&t3}jW0{z!^getfwe0H=oY3h!{ znbEAA$b=(|0;zS!y?RzyL@pgQAKbaE`tT}ycD7MoBM0gx_1WRCXysn+^WU7`{4-j{ zfTa3#R%-1=0*_cJO~GI z6n{Ce?X^Sd9QH%q)Icg!>ceVc{+ANP|3$?7-~KO)e_ugq@?4zC9_~$tDnsdlgV_AJ zscfg_bSPbWI70pnPwg#(6?tty8^34#=4@wK&~o=jvgKQ7ZAX^yzPTgPNn>ZaMWmm_ z%Vlv;Z8?Q&YLUDQGef%Ei4T$pM~M?5=E)D~s=icBJb7u9=s2xNdY<5mbsR98zEcqF z`>k|{eW!1{{Ow=AaKN3A~Qc+!;&GY2P!}_0mKIyaL+kWB&HW?om7%d zc)x53-_5|^0+=dD%H+ut%gr|=`!$X*zXa;KZB|My{#5=q+}{hRoB`A3aNp2qBcsgT zK9M^gRsAbEzdll8PLfUQz_3e4RF|WRM4#{1ium)BXOC7Z9w!amPuF3P zT#Fp|D;aF|BY#22K+ysi7N_T*Ay5`SVU0FjIWl9w*dG#%=gJd&gvg zUS&s{u{>UO#4&ZLIVzrH{gA@c-d4z@AhEr_8z<8sCk*XSRY zExH4asBoq60^ubk4nHyCYb)Tw84J29<;E%~hKj+>?E_iv0^D8@Xs-?YY7X*bi3V90 z66|)HIKI;5HU2u@;MlQmU6U^wM=6qdsmh4Y7LkK$%HJ3FEsME*%gBxA3ARvnTHn(~ zX&^_t#NsL2*n=<;l8k#8`Z*Ndgj!mw;mJ0f8~7%4gVYA8r+0)dR6G&i3NIz(X;s38 z2k^+Zk?K#P_B+|mEd?=Lu_-kPs*d+-w)V0O+}%{eXJUOzX7;2*obLA$h?T;&A4|O& zuR7=m$joV$v0Fkd4nCcC88aKcgVYyW3UqHRslqvG6ssZLjyi;GJapXHl~r$2!A^pb zde}K-DMA;3i4+=Ko}xuuJztO+7ONB#^YyuFQ%dbr8>J)Chp%9)F?3MmTy}X zy!&H1i9VF@%U*DbeACBy#kT&KvC(}+q1@LR`r%7^el0WK^ZCq3)cg{Da4j1?SwSG`0|5ivw(#)aMG9mlJFhUv;_t z-cdxURNFzyey!FwCcmxcjL6?1;aMhTVWd>H@L}R;!+{^_7edDw7NIe}?gQA5Ip$CA zqz(reioHc5!z419#M3vn&K^IV>1;x@A2j?Z1q*zghib zK5;iyD+->6v4I61W3>9HU0}ZUO&_mFQE7f%YQ)AKM*76iZ;e}=Z?wqk1!X`HY2KM* zN-SYmWybxq_X}ImNDqy;O7|cq1FTQPh^k}AvTF*ltX8kf$1RSJeo21MSI4F8-+kZ( zz-K7^1oz67f8E-6laKsg?7ewdlIzJNzog~Tkmn}Ec)KUiQ~t_Vv}>SU)u&YY%%Zm#)~Thj^fG% z_-rxGFgD)o(jsmXjKvOrKAdp7-)aeeksy{GP4JmJeVVd2T3o=)n$aPT-Ps(dy z|CHgDDOBdDVOQ<&1m!TKabL3-iDGZj81|uReRq^NUtd=}YDbCb&VGVYB z6_VZ@Fm9<0m`6b`&9E9pjdhJ>c?ZV&O5Qf02|E3IAo*cQNA08&tPii9?OiSWW;U=( z-11fEl`5`8BaPDt)zn|ARG* zYv82co6Wj@M)HF`XQC=vNp-vzGrJ!`A!rIGwt z4_eIU2*%OSq@$;Rr*lZW|KjSOKm~Sno#gqLN?FDE7F)n{OupN6sCtOyLtR)e4tVoEhVWKi@!r+>^Ql+Beq<&cqXvq<(<`=*lA6vX-3B9Rz5qf&$RRwg1=a075i@3e{ zK1<8v#+d?a(DstCL)$4Ye6E>pK|?`fqsP~?whLmyEUV035qob3Tz&09B5I5o5|hT1 z+K{{ENhACse^>N@tgB98uD1YsMgKN{oHpV3DDHw2;TJpPu~>boO4N${K4GD{cw6F@ z=mYJdmg6x=Gsg>NnTi$%M(%f=y0ONdk#-b4zuB<$LgsASd}2m_%>2hYmEU(;#0C;; zuV|YrpT~GSVVX{G(v$X#u62j!IvjrLTPf%>+5+Q94c```+inN> z7(gh4EA0dQl}x84dTKf8YXiSXek^;PL2E`}G4L{_nxhPOFPDM^4Nlvq$(L51hYeQA zw|$DCCoxk|^=qQLq|qCyKDUJhv330A@nbV4TVM$KUFEa=>$%B;mEez2MU>qGyy-Gk7-g?3l*ATN7hnRfwm3Xk(wuj1b9tBO3D(P@-*;Bg1`*F4O% z+-~12L0Co_Gj7JiYG$z~pTgY{W1dCyWyh%SO}4NBCOU`4!Z?8hT5$xO@6F5AttNnwsBZf9s(FQ2*JC`GUm z=BMf5y22Rem9$@{vjOJFe`Cb;|9)Qozv;e!c8o;Z3m}QU$Znoo_B9!)2FkrHA0~n; zT29Bj6CC$2Zpa_S=7!|?oY1@4z4z~Qql5^9eehXAC7HtlieYPS4%a7KpajYXMGN=l zX{aRlT*YbAkJ(@ST(1HUX%e-kWg$QfCugr@7X;$s?Voiw+|^WCADN&3FgRcuMB6BD zOkO8m=B07d^!|Neb-X=97n0srkSfG(KYq40ral_;jYoHe4k6(PLfk{)fPS{YqM-VN z*P?Sa9;(J|?L)vHcUA!@oWgmp6(149OG6#qUmtz5W^*m}jAm_xtu!IBX4nUT>?wA5Ap zY~u=@rq9vFaMsW20|Qpq2UE{+vBLjJX7QXHJhm2I{j1hT1KNcOfC3Uy)NmL-gJ|fk zRLcvVC!4Cdf=`k~PnX1AeD?BnUm(wwQPLgDgKyMvHdhx!11NX((=O!4%0Uw}tBL;Htf zF}q6r&xyc&G6=hR8>?tXBxBw*b_2-0l_moch$EdEsMAZYIi+&&1zDP6knRhBVbu#g z3DY~PDG}ZD{jzOTnN^F(z!dh>{KymmKXh)ZogDn+>NXh=$Te4Gp~`gq=%@xtGrcxe z8m4Oo+mc7Iz$*iao=*;XO^83L;4ivAHFx^KHjrpgo$aaaOuHD~?MgEsfV2Bulet`adS>TIJzyS34tz&Va{c zpwJ@swcdqJFG{X;H|1%xh73-f6#YQ*hu}DBMyDhu-89!HJkl6718^ErfT_Am6Tog} z-^IP075Yo-E~TKY#sNR}RhTsT_pyq=rxA;YhDw~_8ZW|tU9Y$>TO`(u)uw00?tZ*# zI=*V%&X^IBfY1s`hl{w&w@LjIIsTWL7}n%e1Bvv8Flx#E36M?;k+4kWu$ce_`1Ajt zUi-hDJ@B730M49&_<2;Owehi3q?R7UR&#V=$ zF?^ps%`4R9fmyw4?o3jzGe;aSJ*hi@Z%JP%PaAdl3(QcR%VJzD5Nz#m^LehKN4956qB0P$fF~zLr*k#*L}Vs zeZN+pviS6H=++*kdxYwPUw?5R=&NFK&c1ka{a4ZZ$-)b-41aqWm^c$lYtJ#@ zfXMBcq4PP$FoNa+ao)_Q6Gt}YSQzlPrHt&70X0;*Kmr03yWr241y&_FH1SDiZbO7L z3O(Bgev*x9X^FfbO-?b=Ao`nhXs^_>TQ?{z&=@6Nfwo#MaaDh6WH5(C!8W|wc%ytY zQlT-t7Z>dxk!BicihJO{Z{jeYXdz}tZB(#G_%@A29zg>2hNcSFf_Qpj2w0k|()#K| z`Rh07uWn~&M_Bg<4Ej0*>Vnrqy`(4FD>HD9wh*B3hCS|6)Z;=J={ZuI7L`MNkCwr? z7Xg_`)#e&G_=&P01d(%SZz_GFIiW?%oc(a|IbxqIkJA7JIt-mb(DudHF;G72^3A$_uZlV&- zjN(@jhBo0?fvAQQXrbT?0M{S}FJ2^mo(IZT;{(aiCe`VB=WVWp67`IBMQyHr8f6ezeJ{n&y9xlU27f?{ zMT!`xB?k{_i56d_Ju-R>c+#GlK-oNKF5*?ZEL&&xi_xay_{7iVKjzD{k(TZI_n!b^ zKnjonbCT4vjR%#s4yN7ZzgYkv1w;T+upXdHeSm`DQ zAm1S+0d&;?K4Zv7?Y))S^^#f`ps%ycl$S~Y2=B6!OH0*oz=!=(ViRbo^2Gf-Tu+X5 z;U&T%cI7ydvM#;=(2SAZ2>#g2{64`y(k#)^8T9@b<50&OPJ>B32i6b3&8`D(P)f?? zXQ^o;u)(CHj!H{#aZ(3<)~9{}wv3e2^PgJl+ka}U|95=p`;l>Qx!X@D%ho3+o=TSE z$0sQ#r&9uGluDAY%%$T0pET)?7ho>1qit*8U+p?w6nLT0t#>^fla6u zA2eu#>A*E(2qqgObIwr_XkYW!%dnRH(pEafjvy_eZ0Q>{#UkN@l zwV_)Fr~l)Q`%OIHfT#p^lix;I{`#bl;E&%3GR-y(M`(WJ@aLB=bumFIK zWat3C0OX*q+h=+=6E*oElG0exrtel4t*3FzdR_@kifV8PBX?Q0Fs*%>-5T6;B0DlD z;dj~ZP5ZRRQIE1!dOWc0E!iyvH$=6>?FyAdprcHLskJvBh9_q+Y&G zf6FATpgnZ2zVi=5wYV-%`pq*4Kd7H% zK**(}HHMe3Lka>Hhet>+A3$~I51@A*ldJpObyi|V%D_#D?|zE_VO+h>nhL#6anF9_ z>D>j(h-Q1M1P=)Z-{ccq+?azJCEqFKbHx?%@U_r=K$v0N@WbMA0H{Oy%yK$6szL|0 zmh#ApRmnW?J5}zA5FUcYo;dvX>qS=B%Rk zW%fhygBtc&e;+wGXk=KF!GS1*)^0LV3VKq(p#kMz025`Up@53zmOyPa6n>avKa~Yu zXfJcPW1GurC%3e!L(aR3(0dQYz8Y3&Qr}^Q_rm3QyC{2bLIx|2gl1|_GAaEWFZ>m| z3_#-Z2Cm*z{^N4QGk|D5a9$c!ei0)Fr_F{t*Q&~x zitBZII5QoIVFf$DEm6lt0f7m0J-o_%2A%%WI2W!~Kl9*oeaPwxck!ffO3T6-K{p-C zw9pV+fRyuuL;d)49Kibhu{b5S4fUec!S_|rXshp&j*HF{xXW%8KO&x|OkeW} zcpYy!jk(g4G1{FAOapXy`a)*srtQgEI^0Bd`~5b(OUA55j`rS?-`%Fg%R~!vy@?6> z;|4@Oss(*Xa=4M?;a8lfufY!u;yz#<+Fhp&pW&<42>tL;~DEm`=Y!}owr8th_rQxGx3u$7M z461am%k@6ypXnq`el}S-+>fa;qF?8&%`AD7j>UkWqKvrfyZ4sR{^d>7A^(#%k*=~J z4L(%`PPyZK!QX9gbF*GKPq%ztSMHYEQ;IQE8ukD^1)i?C(vYhCspGa9^q!{vm@%{4 zy_`Sz$%jCPB%AAuXWkxuK2{zwG7Mp>Y-YYjdl>mfx&fcLcPkaY*WG9N?>^S7tF*CTI-v)o*EKOl2uy2Q{t{B={7qJQAtfr?8EG98d#~<L%zA>Ul8Q`D$ULv?PA48mgaY2cMV*isw^zki`Qv%A0#+L!OS%Qg1=ij7RV#y z7JD|RH!rIPJ?Wsij;-`{FRK6W2wD$~hrjhp@0#V!SJ%}NjR`U)0Z*&84c)xY6tt<( zE_;vO>Qb5v5p_)L!o0>5_tfZ`s^O%1YY%J(rbJ7vImJh`V=V%qFM5blCf0a~j1F-XLZ` zMoKZl9QimmxExSp1~WX%!?eInu4{b}=Pw6Q>EuU9hE-Us{K`nfZlBhzDhrRJUe4!% z*8rGP;5o^%bCrXxS(E~+bEXK;-e{@-lIc=FKwA0JG66@J1n9%tBXOvJ^56Ow|MHps zzwIQwI5IAgNc`EeWAEPVf8pZwPyI`yY~#PQoos#s9@3AG=k0&Nb+`Yku}0>3uz2`S@-n`cq}DBGwbLmI1!Ud>?cvshBU^sQ6gWz*(R4iiik=UIG3{e8e&-; zlhZW#NAh%#o(c@Ff;#?Ckd?R=C=$&u5LRYdLACN&nE<$x6u~8j*G7-l0|KR;&GHT?PWi@eNT&6aTbGz|;$O%j) zc+9Rf!@NItB{BY}f;HNN*h5Z=Gf&Y+E5ll;b9F1AwLf3&@Lcf_TLM3$TC2aM{Ywb z`s!TNREGR%g;g40;uS$FqFvM8%D-SYxp)vhhm!$S4YU0!8QPKZ(fSJYQLrS7%mmT# znEsldo@tJDf${<^w2WW_Sk9sc-bCXUibXPpmkk^XU8Tsw71;nPu5L5TY;>z1 z*}W;7(`Cm}U(a3&`d>cDIr;kGbt030$YfRk9i&q;nfE?sYK3KuFPrB|7jl1*!bc?V*~$!--~5%Xn(!6 z#;K(f^-m6rv|l-DeoR_o0wkZJ&hd4lX=k08 zVr$x-!OC4BPFvQ3Y3NvTs?NR4S3@>MzthF9;AX2UUqa|d%O+Mbs;c7ojpeMAzm~1y zcElB(k0i$w`1?SBra(U*jlVgt@XT!)s^-r=M;-HjhIMi-{o=zw)z(;f(`wmKhdMe= zpD&m*wDxlIHoznhIlKtR*QZ}`LkyGaJ{8G;vVe!D(nD5UP*~}+troE}RM`y-xI5@? z5WPM!T`{Ol#rJ2-@uc@vyxzZr*gVciL%03&YPSBXY1IGfI}Dtp(m*8R|LjLV^FN5L zg8s-ek9EOuI!z@~q>r@0<)yg`E2fe2isU@AL@nS4N&z|2^%9)qFI$3y~&z(Nu2(PFOdhwf}d#YdwCbT4G5;_fcmHOubA8f?Xln} zjeUPd5`df|fP!5A8`4LJ!{kQpPcll9P#*zd-YtJDH~R($YZzP?0Y51KNYql2IB@_d z`T=$x2p=QH7T1Zpn3LW2yGG}-`X68u8Za^_r{dTl4lAgd4=*Vg%su?W)&(B zx+_?{kWp+VUJ_K?ojJn$%hq2wpx842lSHR;c=0+U>y~pJbuyH7>)<3fv4i)rL+Nwb z8~o=7{nIy}vSER%hsLpza4>$P-?fSKDmTEa>d)$jA(;2|>*|#*oYHXC7k89d$RPNV z2=~1(T5h?tX<8m|iG{akd@5Y$0k|bR43R z@hxm;k1x4=B~r(_QPd^sr3UEu1V(d@cEJr$I(S9jc8>@Vm&5laU9~Xs%=JPtUl@~` z<(I#5T{mYOIjuv8t1&v&Q2uoK0ev6RyrE>DX|B4YgxNIDI5gfd*)SKuu{m{1Dev92 z6C@kA=d}HmC4gF6t;4JP!*Ukc8LeUOy04%jd5GgFA?CTf4D#-n~1!txY|mAR^& zM|>0SlTl@?7kqsz^yiWm${_aOZR?yNPDjpXI;L;xIs80$kL!1_8TS>|)9aDH8*e2s z&LJ6#1cCnq11{=~=(i2Ygy_;nNP^PBGAY6T81uI{!P7B(y%%g2O3>-mZ!fNokY;6_ z3(W<5`?#0YaX&Ytw&KG5hJ$6UkDpdP-+2Jsls$dK_1j(!Whm5Hk>k{sVin`|CuJul zo_s&k*3<^$##^iy4Er+*@~maE(!!fxGN_5IjV=IxiO^P?YkSmnh|`0Xy#UCDHF`hn zzs{-0PY-zgyd$n0e5*>mlDwm%vO)8d9DMQi{->4wFOOLSpJL+HjXu?W>8H5R3jc^` zzh9=ASX`00E-zM`BZGsj(l8FVM({A<-2~N(1hgMdY2?7TZ5f6y5H|PDq8%`3#S;j_5H(jd2zO} zDt{n_v~;t+Mr4)HDpm;(TYH38%rxs8jqKL*Pw!w<(jzne8A>I1PvFhr!)B?6&IiAJ zl<3c+A8mXC*WGZixa-K3`n}42DpkAV!mD*N>!)+e5?aHaR%UAlGL#ks(dWxQ67Pj< zm%+t2XJZ1XM&p(GQk0M$jI3E$Nl0Ceicw*V0cd9QRD6p0h*u48VW(l)UEz zhuIW7JCt%^;>h1-LvS><2c|r(e?7Z~Ai7!{&o3Jr=K02LeQZ~-npHLNRPWxGzi3bm z&;FQ7n2%hPDC_Un&lS?7Rc(JyHlj^pTY+k@R!daUUG{x80570d6|T!0wS!nFLn~y` zv7vN3Hc*#&4MIH;@>wb@yf6@S0Z5^pbv-EDc;rjxmjf$L_E{IA))3o~$JUTmqoJ)^97nn?UJmX89*r z$)Cb;Bz{WmA!gmX))lT+OO#C}-p4vR}Bv1_m4t6bN!;=wJ~bJVG( z849r64$=j-*jIEW$ORYI-KggGwy)!PL3?(G@B;Ef=S1^lp9u%b+Mb+b0gE-PF*n?& zwoe;P&8=O}Ab|R^h1Y0x`E$#gstgumtY_dTa&jp-B;T1`bt|K6!PupooXSt^86|(- zYwZ1{|E$mvNqA=tjp5e}iid|ca0ItDWBfiMjCk4aQyOSd$hq)RYM=Q4$nN8D69}M? z!ueK{{0P^q;?uV1Js;gf# z4gH2LKe-yApPm}i@l?SbP8!dEws>=byAPsElHw94KdPrxq(tSV z)wpNndcdQoS<-ymL+Y9`(I3DmU_Y2VYnouExB>4&H{$ zn_Rv1cmleh_6V z>ipbKMVbRlii&|&qM!er!C8%qHqpH1sZYgElQ1wf4joI2`)s%oND!!*=VGR**2L6y z*gSmmB)4lg|8$7Z!()X_E`;Y;)8pbjbDs2ZPNT@u!kNmbe3oqQSFX@){y8(JwUTPE zYFsnKt`A!a#1$E@F}u)_h=@34#hHkCvrd|x70?7I)_^0f4HJY$^X`SC&{Hy1zIO$_ z<;BtB=ze$74G`PIy~m#kWh3~{VVs35&*7@oVk9ifqS0SePcI2@((O5yPv{rhbQad+ zba01iQYgY}AvwQ8)5EUTj^%}}&1%HqSVYb)O%KwT#bo$V3-4^wV;o(_w2w2y`oIp} z9#A!muSfuv-nGN%+rk>t==ESWeX?SizH;Ylz3Cm6D|*(Qn%E+1 zfB89W--EamdWnmXRmXSR)A>}e0J(u5UDR%HjJrrI zd>z}3TQU6ABEKWDQlX}o>W(`yq&v7hceu?#lr`LTdQ7Be_lHS`{c&ZEfvLmI@)BuE zolDVsiwIcvu;>gY=ZFu^rnfoM#z zg@XEa>AG0fd+2sK?44Z2L?7L0IA~`^qi&);B)XSVs@R^GQ)|nvBQ})?5y->%UoQeUJmUc4y$(3-D@arTJ%xVXRhHA7E`?^|BiU;U9<{0Va`pED4SHGo z1?CIiup?k#kTf%MEI$nnZO^@{JL+TFXN!yT{HE2;uJf5>)B5bGCmC6gYH^lu;p;R^ zwN{g_0d=Pw2fMuz+|0-vHhIY1w4H1z3VrK0*6|RXrWWf(PyPPK@I|v!I^uEAJteCp0_4Rmj%4f&`a9{C)pB zaLrr3c{X0%!69Eq)7KL#{KX{Ap4iuAj5Ec-IhzMtr>`YW&-7#!x0@d|J@v)&*vq~U zACgVz^S?L*H~W{LKbHxY8V}~?n>1=6Y}tz^ij6&Y)vq4>?4Mn80!4l)jO6`Z%{Hgh z1V*FC_xgk`p}(`?p^Rc!s!f)Z7}-5K@anx`?*A?%we4F>uH^;cH7wI z>b_tk5NXZCTsr5cdUw_P*vQebDhEqp&lf6r&aWmJ1dVO$$)Jf9QJ_J+f{CYeQw_iK zWa5xXJ}lYDg23$e9`!Tjua&Pxv8Jp}!Y8=%2q-j&1%IV9F8EdoN4~9o)obZ`B%59tU ze?GPT)(j)D^ZI0X79w)Z?ZVN z->U!IvRi|O_8|KN@7^58U3d`_wUuRh2-V*lPc6V++~g*{N(|d(Mg#}u+f1iSy9GPz z>oqnXul2x@J7s6!pn}GZW}M+@GuF!1SPVBd*fo6!+hHg|40toi_jx9nEP}c-uC~!V zRLHIL@EH{Ma1k-M^*r?QKnlive`Hce|8-DG`%(5HjYc?OLo^>*gJiBH+42WlkE-_! zS^`L=LXj$X_2tS$yb*ah*)*!49e;stKP&8@oe*YCHSeAsGq3Khdg z>r-?37_SsaaC~3xxv`bmXTtIF6BbR2%{7?S2Fi>=+n1GA1nSWiAZrV0yW?scH z_tf;q>zV{x7nxq$RUc~rNw_mre4;0hyoX|JZftkItvuU{=!4t*&Qi7K7K|!dm@#yz z6u-KD|47_k)7*9uR}DJ2Y6L+6@6r7W=9Z!QJIg7G))0kQj>tyn%eER&I%-BOuDf1n%hp}-joMYG4tZHo|MwaxT z`YG3%d@itJB6vIIC>zca{U*alkgRqzQL-t`AK7nGwtuE+H4V-6$6weFo10OkK32#L zdA^(+6F;bmJWlARhJJ`~Hr; zU_O_goop&TYF)fD|6qHqZ3Ar6(1v7odr=Q$^bAhZiE`MvFM8}etzgH|ls%k$)dP;X z19!(X^I*ACpS+q3JM(sjzvU&gn_MU|gIaPM9;6>L-l9x}fDEsRWlLJ~O*eMDAXi*21TA0Msl-m(gX6Jw=!Pk*{w7bg=tV2b!H=Bs|SSF`VDXC z+9ME~u8J>%a@(VG&4!{7g+ebJlN#>!kY<4i*v8hkq)`r65!&-n^nKcKZfR>%17-8n z-D2b7MkJ?aaEen!e8q1b^{78I1a+b88%=0F&GrGhmye}e81afd*Q`)odxT(pn|^pq zO2x2wR?OPXxb#mXP0cm-vFDknGY#&k{I@rU>Z8WMz)mh$?!dp)#kOT%=Kgh23wbGr zP#%8cJ$$=P)l~CK@bT4QkY|RrApVf1KN-wv8wPn<{E>dd)i?N~G8yLh;I^k&)~3M0 z&!3;PshE*(YbpMfwjNy|Z*TN-lqm*(8y~zWYppe+ zjr<4o?(6!TdgfO`a_aUX2)Ay7jn<}~VRWijDZi_AVKDY>oH&%q>>0)AQ%N1CdCN&e zC358Zx!7xi$fMVGky6OF)^=1QH)!OugX*c}u{kYTgb&d~pUPDlH3?!7v|3B2`Y$UO zXnh-ueeEZ}9vMWrHhb2FejVy#H+(6hhKV`(1y52|8=X-52xXJ}>lPb-`v(i~m(8r8 z%^y|$yp>erDe`IV6`nU49n^=d2xkA9d~N*4fX{3`gE>$|J&tkpQ}yl}9PjQQf*DYZ zh6qE{BuujnE>5SYVn>`X*4(U*l7H&-sKpS=x2-`>woPr#I)z*M{i*_|*KLgHOQ*%g z0VII0+wvbvd3?@m*UVMIS6|1bBb@&J3ZvG1??d+k7j9djNXO983S0q}Gg)j`?<2%6c#Vc+0D3#j>5F+YWxZw>S>LU|jew2-U%k0b4@ z5GFuMH7E)A$|bL@f$q*Jk6`ntk`@`$T?Y<*=Uji+f7#0vKRL2uwg}% zX#E7;e284(ov9n=Kc+Dnb)|7vk>4(+-uhVznW{Yl{NS);4rSUMOv)$gE|#xecX(nn zf&mBFh=fxP`e3RL`Ou`4)Ng<&H%7V8v|imz&^VXLQ)0cTwgeMPWL2Bn<=_Zmlm)n1 zTIvN5HIHT~O}c7ExUZ(blu+eCmV}brXUi)U04SomSMUDJA$EyUB#;^g0nezS@;5C^ zoJywlcp`;}6`yw%3CFomQ&_U8?f_fUs|0Dc)>c{f3`lFRfg3^XU;F(kt=kb28jv?Pwe+Y6&~tR?XOEj#}I-CH2?;EJOZZE#`k$zQja+IZYe@!@9y!qKf1s6HIP z);Zg*%{DGSynob&B~>B-&_r*}Lv&hIW9A^i;m^2HhIy^ghcm7}V|ydk{9Wo7SG$WJ z**7X$h~HL4g{P9ZP#idDo{d4nU1SrJ8v(6T|XKyo=v`^|9cu8d;229$f6iJBz z8@XGlrYkR?9#JwnX~i+|O6^wuZf=Y}&As~}%2f5;a_ zv&&St_^s2j_=|{3viWD~m=Tx2&du=V<(+}4&g0ThxhIE@l3NZOT;e^Rs9>#=!oDbu*9&aa2$?pj z|5I%Y4^d&gzF1Tk73>x3@ZK~rcs}v=Etf?0<>sZdg%$bR%WPfi;);U<9B1ovw2N$q z;9dVEzrPs4+|n@soZ{TCj)vB5=^jHtUt@qAaLl=?nUBSvO1*n1r|%Q-@=|lRN7rdg z$$>){QN&)VQ~eQnXxNn#(e94td=xrK?hv;oYCa=@!?~KR;}a)v17dB_QMP`og-`#b z`HIB}nqQ0@oLEVRteoE?pAqlXeUDITgV)t| zdVSsnjJx>ho~hUvlV?INyV4F|U)C55A_dKwR9+gXb)0bBM>%{)cY$l+T-G1DaSXc;7?SQZ8NBgmmQ?*gBvnN z-9wShwZwu+T1!Glw`E=AMEoA1{8A z4{@ub=7%!ghOYA5J^)>WXk4B3@^D`Mp6tS^c%jed0|uoFW%&VV&(nbNN#4b`a*)AifMHa@E%s%!_0ykm%V8WUa861Pl)MjWaqFDKxg>T<@ z=1+&Ba}?1CU8Lxe5A_?qSpVWx*gT{m!7hekvay?Ezxn%($pEbr zbjQ!eR%RKu=_Gm!*%7D85_}aK+$xe1E|gnc!6fsPn{9RmLS}Alsam>g8k&8}L8%w6 zk#6VlX&nTG6~Gr;&e%)q$wDh*odC^KiK%;fXd+mEoNgRTgtl$id>BFhTx-?-4p^Qw zm0A9?`Ti8&ue^&*T5YvFcd7WI(RGk_k09EE+bIoGI!~4R_RHYd0|+&YLGvdXXW-=9 zl-5+YviYrgmil8gqrRK8$2)?b>5$;##q&+LOP){mPxqwFwp< z4bT4ENdPS9kNZk^F0lEk9Hq%-8KRE6H^dChUffkx(|GxZ={8$!nd7H|@pys8-R3Y`Y zd)z<}QhNp0j^+@5#gQk2{ zSLZM3I{RK;GKa|d;X^>zl*i9d$3oV`tCN6WShCgy1sOyn01lJFUb_c$9}H#y1(j63 zy{iR5gFdzTO5+AN2OsA+yb{FbBw#veRf47gbKI3OlQs2ybu14I$$~znU(bq{0HZ>5&=8MWetJw^2$Hn5};ueWi8>GUV1pv&s*Fis# z=Xr|O2gfBnnWY9~?#RJ8R@WI7{tj7bxVq;s>%dW?fQWmmeS^`TRT_@kYq8?sPExAm zj1~h61W*2P`Wu^wogv{-q;weiFH*y0k=G}G99@H%^6O6&dt~it_En<^?nG@U7~fyj zHrAFg!RE_L#k*Gk^0??tJZM_S$fUcuA%SVE0j0k4zOpSFd^;PREbqvNmqg`QfMcB> zD~_a0d^B*!L`5Fq+qloYy6rAcQh{jFJk0azHWf7x77o%{8d%&9{%V;~yKW#|SC6z+ z)4=7~n6Jeg$|!`*b9=^^56E){lW1iRYQ1(`r;#2EBjxE8|8Y9i@pKr!XshyqQ%Z{h3_#19f|JZN4IuZUWJuaW*`nd z?lQ6&3*8(`>MPbobG+J~^`HGvX3dWe&u-_x!eMHosy+T%$9 zsf1L$W-1HNX2jmOAZ;%{H#bptvgSmhlN-|emFqi3$>;@RhOT2Xk&;LDZo8GiwxXR# zB+4pQw>G>b@!X74TCPm4)XyP4HPA{BhcUW0eD`R)rp~TZ>N(Szk7R{;iwjn@P0MaR zr?DRRB%o+Imp;?aS{M;Xwd+g219IC1t9JNJ_8gYVT|y#&vXEbqhZ8kFxi~E=w`d>! zGm(vs+*7F4G^SoynTX;-Mb}5YOA6iPWZk1|)4i@=4W1+T zy|fNAZP(1VH{hSOy%Hi!*VZLwuI|obO+)8jQ+B%>3H0`D2#~$neD&Wq{st3|c7!PJ z@;ah>HIy0rx~I*yz~OtJI*b#ZOFf^~a^%vIW9Cf%_FsD#2UAWzf=0G!TrI7dj>x+B$o>c7IN)4_J-<0a^BZ&G1h(bxTGN8zsP+h!|u{f&8 z9j6bV3LF{AbM1bQMDXl;@2)6@_B|#&u&@ zGv&!{i*JBF5eC$?R0)mvgw8IzbEUOv9@oK3y@}jVZV3=5!pq#6u%v4)aFQi;60Ds2 z=JmN2Exi7&Pt=ETcCFag--@P7SE9^2-~Cp8(Ir(-4l}jL#N3oa!Hnz=g49mlzT#=+=dd|HGq83zG6{16?N#w5d7n$(Iq4FZnSJ-A z&pm=p#j(2MwU?WXLPDO_yO^rPJ^cEzU`atYm(y)x&DdW*-DV4P85!78HbW+bhpJZ- zv&+X8Kg>sED|BBi_F4bCFq|i62Edl;qUD!gDOOy`9@^s5!b$Hl79|4Fe|+e7fYj7` z9=t)JcSeZurz}jLLtFu(`fKac6&D73KP>mbwlq1jK`YFLx2Ezy*rT7WEGf^Ai~cuM z>49!|ZTW8FUg zvXQa!PmtLGynZ^sQT#x{v(?hllJp`^1V9@qK*R2gjobRTfVS%+Noz9TbYgo0=^Nu8 z9GOM+FOhCryoT;>o3{c0W$r%^aJ3wOQlooUKR^=Djivy5(HQvU_=T9yfu>`~kA}Ze z0AcNrAD5#3YVo>QoBCb{$b(4$aWCRoc9OMjSONmQKSt44C%5z>W?`I|%Q>nQB1C7;4|_*o(2 z*Dw5V;HbT(EtzMt-15j|2<;JngyeH}VR5AIs0z&d)+WxIx~OAFn+Ay&^3|(m9VDGm zwH>oam|>Z(5q+KS{6{4d6%;O9PbqHAT9Dck0!kL1piq;c+f^2h<~|JM z-`%+RFz{x~?*~_EjvUeT4m8$7--hCxL5~tLeg_c5T`_)(2;=i(F}}uU1m>6>Meemq zK~nKd=Yn{Qy_}EMo#fQ|cBKW;(T=@t`A1uuKWSw!*X8ClIZ-oFCw0ezp5Iqoh;E5EVC+sy708{lw5v+ya zY;XF(8Wm23I^|H#8gs73%HY|EHMmkxO7r7&kkMNjR*@X52$AWy+;V+YpfuDaNfwuJ zfHk<&x)?}BwU#P^Wzdm|+JXb5mFMWpA`{LgBbZeQMeFnJr;{6Q>obL3`ucF7S zDUq@|N8>1SwIP(15*kNCad%APAnQ|u+0)wQ`U18%^pI}om_H{M20 zp*#6NCuAe4(!#6gxUx|T;8wTnECzCD@6CQtHWN!u7A~JKlR+5b2LsZ}=mv4j$YASe zGaZ>6>d`mx7AR6gEEl_)@zn>V^QeJB29p{9oNp5KdOAMCdsKzWpBfGW07ad zN~w?ey=Q+Z<9+980di%@7v~Hd4-!_xKLD;h2^Jd;WZPfu|KX}iOI-yh691REiqB+3 zcNEMI{*z-2{g*92@V^K|Hj@!GPIhGIDXQq^O!s19^5X;@e0{QIl!Pa6v|L_763B>x z{2X{HzUNYt#lg`S&a|^MVc`}#LL_>3FS>W)?}KH_L4Z2zkfo=H#*h3T)xBp_(@Xa@ zEF3+G4FweuByMjL_knVL_kzZ1O!4!!tto6^cs5XSbzu!p(aQRp{gjM zhaOsJAqgSv`9;rN&i&rc^PaWd5AXlU;tN@snLRUm_P%Cj?`x3$Zkg_H(fc4&CA>Xa)r+6)ArGfk;jp0m1 z-lxqlDXi*bMJvGVJr??@`k@ul@gG?k+obL5vR|!zckHH!K~pJtfrw#Ie5(U(%3;mH zP3=$4_-DW-I}=>eQ&W<&Z>D+gvce`4R`q!1KpV9?M{2%rG0NL2{Frr>^`FvpdtCzr zTJ}fx7Yz{5dzU6yP0pk}j6*S$Q=mKBYW|dOA8-VN%g%{z49{h`vP%%SY)1&JU3;Jn)kZQj9Any`?6MWYD3!o`58I33%s{oG zW1t$sQQ-*L$vtD(?FlaG$ocPr=W7vY>|QXx8LZBqM-OZ8JIH+U7$kDkeVK?~LZu6K zfCZIgf-QFqEto4EMV89)OTkC`cAw{@m{T1$Sr4=5*&S{N9)eC&rTEMYc- zb;i>9Z~Q5={~p1Dlpz1*MQjR)J0@`Awo5^tbVV}3Hb80}@TpGwv4Z-eX z2)2<1+@)xW7E(j6AT|Y-s!{1O*JMzY$5}8)CO`Joh(YMXnEc|ox5E_uV+8O@D==ov zNmS0B8Ty1af(_aWUdOpXUI(wT3t>{FkfI#0CZ6Y8Nf0VwkQ8>z}X>@!TqnP}r!&7SVc^vY0YV2~ic0_P~vW{lU4 zwPO5H97;plrrD~ry>V{6!nDQ<`aFt7?FMr(dkEY#s5bvQ+0@!{HEab-U_M=IGC;mq zTNUS->vp`bfr((GBV=HK0+N^GR`?~nPe<1qHq)srD&H)$WHlXs7yl5cFMe=AK3O*U zy}tksfb??P9XMe#Xj=Simg=F4BjmUnF#?4>Y=|LED`Mqkb6spdjl@T00 z{F!EQ1kcufC%yJUfP=4Aqb}C0l)*EOp=fcq7!do4<0t1C6L*+fl5^?ydGnALQQGmD zY;+Ud;E(1i@I6t}W6{fQMw)cQYW3)FG@}Ros72uwAxgVC#{n)mqq*sn+2h7&0V{#X(DQ`gHoRRApO{SR<1>XE* z$@zHEy!OS}MHH9;EC<0SG@tX*By`_WKZr+Z0!JFl73B(+tU_Zj+%#PBExF)izk&8T zTFwA^+<@A1bOtW9X+nmk(e!;TFyY8o0X|QAq{HLdjqixyvX^Z+son70j=YQnrLb_k z$xO+fIMx#&4{6=lJDm0GW0W_=W6TfEbU{|!ynN0l_u!NU^KH8Lk@6yn5iXXX`=k5w-A}{O?>07OA!pa5{SUn9dH`Q>~dIved z$1s&+8P2%xC)!RrsAIa0bj@chpj=H7d|row4qU3M#<{@XwyDJ8c;RSO(Zb{tyA`J?&q6joE$3UX)EhG$ z%V_CjD7zIah2n10-~w-y*u=2-PqRL!fXy+m2}li-fF=M>W7FTH_zr|*Ild0^Qb+g} z4*}1Kq8Gt2?pd!i=}z3dAv!*9&r-#br(0=~=Qhue!+P{`Cuj^Y*~_y04ACQZazU1) z<@gTBq1s1kx)YP|t%(vrI#~|0z9gYZt#u(tc4?RPw$kf~m!nwe^~!?QWNZ$^mZ%%t=WQw`FLM5%=ZC{an~eH`^}ACnC)|GkG)HC{&)!wX}7D{5xxz6 zqR=6}>C77qnNH7C;FhuO;aL3D}qe@@`CvGYTN>JAlt z;%&FgJb~pr_m=|dFD8da;>62D5*UlWvL&63IvY8@`i$R5Ly`gRHZj)7iqQlwsfaLo=X8@O#9v?pO!jQd4k7dbbO-(*EPO1tkoAn24KWLp26wyT_m zlIMFLUa66X%D27bK#H8iBYfGM@bZyhfXEOTFbq6|>qw1&eQ~=~XH15y31Aa}iKb*z5KZ5A{&l<4{RdyeH0#ST#TnR#iN|dsAezBr4GxVm~I%#+M`+ zv!$1ubmfxbxA!|%Sy~4MrtQnT^7Z=3)#7>XkzD`riZ&N77eIbGw$_0g0bK_H%j~uK z2ymDNo1%ba79oTXrOYW?wZ8I>vy*Q_d*P9pV@DxXu$N8Yy&h+V$<!}&F7MEfEuAo)j+sWc9iECC#vz2#yn{oA2&5@W}TBfUC zC6$u~kr`GuD8BfRTcisyjL*ccw~onWi%UTa`0UcYuSb=^%Q1v2+y`WeMJs(jyB$qA zjO0q8ggwcp-bWOR&9U&!q=+fRv0}F1`;~c3VFWr1^A#+ZSrJ}rAtM5g zGUthZE}vT>Sg@?K+P+#fvhHP|KWod)LA8`)YHFgD;vezEudr7>?^DH%xOZu#0aYxr z%kS~5(vME!$b#Ud#wwfHDwg0{n#ag_Xr&8xLI$QbY1C;&;qvE0>jcxBCK5Kki@IV5 z8`RX|Q_(4aozF7MKjXs*rh(#;KUXd?BLntG%{@y9w$XSLP1=KN{FQK&b~X4^Is8|5 zIEiC&yDK{qOYR=rO@cbLKKYnS3Iqn&5a7b*j6O2i*M zip>F%v6ymwc!^o(rd!DzdF>Q7v*vgt5AO1E9OD?KX(Vzx$NsVX%%FFWg^l63WXH#r zk09u!^3sGRHDB*8gp%powhOobU8j?B=vN8=X5*b*eh*~>AP64ru7WFf90v+u-g&7eVp*Cb!Wb^<{X&a!71s_K5i^~ z_;$(&p&2w^UhV}Ind>c@Q?*;)Cw;ljIl-~ZE82=lPC|RtYnEG)bw6%nJx`z(J#w7G zc{uwAJf72Cs`?Ti+*jcM}-u`1mX08~vHdIA7s zT`$wn;vYB@wP3ZV;uW6DWoz?nmP09P9b6 zV8~X6I=dUnrFxIED(jK={a6%fgQ`fygyj&1Ejlph!vz_{APUkB`9WQq>tgO?y}ax!Q({A<&!4 z#)44eRwTopP*ezu=YKQh^>yt9bJ#Tq-8>X5>Uz{%ErF)Trtbv55t3YSPR zZuHvPe6`4$e_d^Bg}7)R%X(SCWa{zL7JXd@k${4UQW67%U)nNfk~g(qj9&d# zd&j!$nC_-*7eEqy+~E(4(t=fH!*__x(N>VW`| zzg>hIA7&D#BiRftJLB^DT6bG;dtZfN=I;v0X;HW_|3jRvj5$>I9HKq!;Os8+;ojB2 zgF)qG7k=mI)@rdJcdNPRQW0rD&uLlknin5+Cq8aLdI9w~ zO*)0$rC$#it$&~{6_cIt)qSUtN0GpxhjT(Y$SwK=FtI((Mc=y`7ToX`U252e!nld0 zg??M-(Z37%0Lx5H0o$vGp8sCu{_ukKR@UU0Ke3CL2z2K&1MlCQLw>e1AoCt0-KUKH zVZH6hCRzEi8sLz%zv47NH?f`63#xC3mrUZd0>f~0-l@{r#isSse9% zN9Wu{p=yM;9bYvmI8bx~X7YOcu&49Do?dOfd6)CmUuvlu-Thwb2a(Z~ zU}b2DK7W6%UPy8TuUVcu+nHGmz8`&1O_7sUoRD(ubMYml{!3gA+L3k@vpYH$koYMB z86RteJ#!FHrnw{JDEfyUBZmd6B3^4Y41dKWJQdhV%)f{7Zm+;w%F6*g1CCD`_y8pd zweQHK8H#o$v(~8AN^e5Dg}=34 z6D&E=-@)|;`hf*xR4Ocy6wMPFkz~l2b{R-+^oeY{o9<5m=vV(v_}-4 z-6uCyYf(mv;T0!-R)q(iGt-aIJDDeWBvao5wME?6<}6dg1ElupsYesYT|;kb9rRyh zblb6pG*Nw5mP z^x3TWuS&h@^jNSr)}xwd;@Qps->t=F|Hq)Z?_GNK(=s4|%-zCq+Q)4nJh)+T7Ggtc z7a5m#9wZm4O>SuJ-g5=~mtjyRTJl)G?ZZgV9F5KaG)#RCk^67zTSxy8w&zZi)*su} zUKapntzyF**ZK|GHvb6JWjnBYxtw&bfcuHp6rJXq^P$a9rHQa1OBRyi$&=Qa0)JaCcNgDMM7wBy7d=Bc)iP?_3sPKnm%7N3xmgdKM7x z9p(4nf(bjU;DbL>z(RrD{A71iUB^A2bRkEU=Ff_VH?RNb#ybivD5u5L`<>A4zZ9Dr z!=X4hwJQRlJZ!zmhy!;FLtg*5GE@-WT>Sq;9e8eeb*6hPH+1;0kXO;AGSCYM9{r&u zK2?7M>$R{E??S*>PN3{+Xj^D9tH^F})-@}L0&w)(f7lluS z8J;{Dr*=FOFh(OnV(6QIJH6%K{Q6IuBsRO}7Z0yg1+$|2dP=5xi)?(M!oK-)(N; zaae5{EIT30(XVtN=J@#oro^{a*aJYhgfJ)YW?y4~gyH3fUh@o)KaBEi=vVg|ckbUV zf+U*cHD`#8WBUK%_PyqWeUiQY_dGvrhhsF-*6$q z5gKTKq6qx+BDCT|%lC-oqg1_#KSqGK!CO##wh4cFswpr}YdA91B$Tz%=c6^ODR+|K z>|!c2wK4>xPQGjytc?U>84tcd8gEwsqyd8rwjuu^RoBO z_Q{~G7NMsWCSRCHr~GhHiGW+B{BWx$FW;Et1P0*7sU9r=NSF?X8vM(llmW~T)xQO{ z{IIB2p(29mkbs%`S7Xc?MAu;TfwYyN1FNSJk2F4W!ki3Y-3ZV@F4)3>c;u}W)))XE zYzFW_M7j_k^cH{RCYK|9FCUG6do;F^A6>{|=>5yVp4kQ;A2Sb(JqT<@0q_NIa~KOj z$SQCr_q;7xw&?Q9t}?9Wx2`U}F=6NfL!ksXv`KGdMxPH;LGp9%X$=8p4h~r35~wuC zQ0&)(8k6XcGZAn$A2VnhTmjfw@vDo}mVXW64uHsv?Us{G3lc%mI}BpZCwyi9lWFj$ z`*^acrs@cyG2(MHV%JL;ck=NiM$na5nXWQaQB3H#=X=e?j<0fQYruW)6}JCiUPkO0 z&!qo=M7jb|9B-|7=%MKk=PD2|7hu8hO2#7<`jA8*s?&G=e2dk_yW`OQ%bsk_ ziUi;J%??%V^ZGTl9fPrXluXQ;dqkZmFxKXs&^*YosaqVb9l$q707R-;+W58WnxTRa zuc8GWi{i-Z491A|qP@h{GLgS5C~`d&x$myM>%?zq*+#M!m5Y3Z*vi4pY_#Po=2Szr zT!+|(V+^lEJlI$hmpq3lTTnYo0w`G;g93YU(UC*PqNn37!D zks@s3s7l|80Y&Sft^hacnrc~xrJ6|xgs0-SP!}cd^+HmI)ogm)Y$>U;_rUe#Zar0) zT`CF3$-BEqS3=!Zg1cR0l|({EB^JnT5rsUEhqsk zc$3`31o-GkI#bvKbO9&pP*_1_Jlv+UioW6|CH)L4o)q=VZ`Q{|uwFwCqU5!VEur`O z)EalcSTzmk)nEgK76&W40is&_4qL^+3V&`tPGC9x3lt+ZmyvU49kCdRxqyEteTkQSxbEghIcVlJgt!FLEU#JhOXm1BLVcPla7f znJxOC@wz0})kMw}h2`5V~_%d>S9f5b| zMHvIMTpCOnjeCaY*9f#Ag7n~N5=Bv>(dc;(>{_2j$o9F#9)Q0UpVanHqkgVP*LJ*L zd<3qgC=zPP`_YXuhi<5Lu?BTdEt1<@XEgVRTOZSrZ^*i8p~SSISE%oeXmg%AuQX`k?I%sjJ*2 z)DJD;^0`^uBwTS}w&A=X@&InV{C6309CQG%Ykeyug3Th+g016H$H2B7UafJKO|O$MouQFFoCe;R@w zUc6IR<3D$VfcYOuMrieo%5(I{;Skz4dV1J&Xcd4M$&)= z<`~XC@UyFT(P4MM%7xA0O)ceoiiESeQ1cbrU|-G8Rv~}Hxa7njpxPl&NJ81Q#Hp2J_ykhyI*KP0PAT$FEi`)w^C|)*M!Rl_wT( z<{mPPF`tT?YhFW5<2`EOruQ;p{?fUA4chV3C?88#tDSNxWbj|SHg5wb zsg6SdLI1KQ9I3gt33b{@L^g#l`ZHCnicsiHwNDd5epO#$7 zHmJRCydoMWANYLL%n*jz)rfx%IGPfs19UdKdq-`MFs!JZB=}(TwDRyhW&Xvm0+f zTQ3EEuO;?BI;%Y52WZD%n=8@~30(#rGN0-&p!h<;_~m99zY0{fuD~t;{1e_i5QBH4 z4!A97Kcl*CDNy4tkcPOmZcCg+)0uRr70WHE(zBX3F_2a^9!y_m1YP$Nq81u}S{#3( z76lO)p!eQ|9I=1ty;to{t0unW9Vsm_ki?<_JH)GGXxGoh8!x#8BVsYplS$>eA|TY} zg?`rLnLf1@gRzapnt-3C3%$IC5J&i6-w=NJx(RjP?>~l65nK2?Aa>*61FH&X1g(sz zG4pm}py>yWlz`L`_#eVJ-&69rEc0Dfe^JQv`LyK=z4}FqVTAv* zQ^zBF-J`iZ3Sx#E8aq@+oSj1zjOHJA$?F7AQDW9hwc$&Ds2&f>;5a+Lg^3j==Knvc6&ctNH%2=r?%y@@X4~j@@04 zPnNi*4%=;rpZVWmnwY{K3gY%WzKRC05w!H*;SD){>|z`)Uc5D^B-#D{SPVZY)ouHYVR42XZjka1!@IoPmXT?TAxU=t;yPnN3Q zTYsxy{$iLHV_uhg=?6?ZTS#FR{|iD?l&!6eC%i8JYk?vrR^PAzpk}D0`V%OPw3rJ| zvo>sa6ZI#i+WLRr7xGzA1kOVB##6ig@mH{A8vx_~`R|^cR>yw!cf(|&@DLA(XcYmp zUHIAif8Q6M%v45DEN#IMcNq%y<1c9weEnn z1m@Zo%*k6w9+r(!E_vAY2k&r5dI}{mVd3x1&!pTt43$5j3`=mq*yVXWqIMV z_baVl@`$(G3e4*U40L|1mZ%`ZbE0{c>it!REfhoyo?qBbWB8CY*1m8wzp?n zvaS!$Q=p*3{{~HY+LdU?#Bg`^bB=0mTZmcKnC2l&TaB4c6Un@NMC)X~+Gn3@MRdhD zSH&uv>|iEF>4J`jL0}}Xz@vQbL~R_jJUzBsHSvrh6;ZEYs)HuIN?QsgSzV8nR7B6? zG+)2;TbP^b6RXfGO5#4@9Ho5ow%qBJiDQl#j9jj-QUwM(#zR#EP0_zd#Udr-L7esX z^Gpvt>kl;j<#87Y;aIA2Aa&l!2c%Dl-cnZ5;x^i-nyD+&7qyI1F*)U{VN|t|=dcep zCv3lwUd`*Z2MumCF(LTJO~@#Yj_X+0(TMN%pEsRSBHBmz{S^AUSVis!6S0(3WF#?5 z>EuIO{82y*H8X+O^v#8Y-Ya4i>lVMU4#Uw~u99AHkFg%(Vc5eW=*NLan=dY4Gx;}~ zcYWe1G}opQwE^kw>;MjGoF6+Y+Kc+D+)-yIVRp7{RDJCxs>Dfqve4}tfDi02ucM#N zOL!zNoesOVQ9=xEtt*o7mbLa%YegiM*3?j!Yc1;vm6g~tl*HIj#uuHffEHt&9=!}e zh%TrNDUZBcI9TjIdqG|!cB4yS^%gx5uwWcWOWaWL>t^#h%$t{|$z~EQ=7he+PQigY zp4;_oU3XV{5>%2*C?gtOA@x0Jk2ku+ODSWNM@oZNn7ck5xbJfV8!hPg;~9YkPs4HE z@ctA>M`9^-ed3I&M8hKP1;IT}pfP>9{;*`Ozd!bZX4q9>t!C~8-MePHeTz*!pqWvd z$NNy5095*j@4WXqZ&F4kd}tzTCg9sR*}Ot~bXl0rU*C= z9@zgG+P8!FnO+nJDDuh&)EVF0^A&Q~p3{+*rpmNWSsO{Otd;XiRK(U&xdX&Z8KB{& z(SulJK|g!fO0hxpjWZIa$q5dNNajMyKNYfIBR49^im9}sgq}a&E=v?wf+hKSm4H)Z-;$myR;`ys> zQ_aSZEF74peI+=HV&X@v4Pd>W^0|Wh>`j1wjXU1lJ*C$-eR>BG$uTH-tydt3gVrQE z)ZOqbIT&Mh4-eGmR7q0~t2tr6c}Vq*{;Y?`t2g*_EvAN_xa*zY4NCZ%zdxtqb{`Ln z{cx3he(EuK2LFzvMGDeA%}iu`Iz3&r<`l#zdhB1Cao~)8%g1R$p1Q4pgUUK-g{xy1 z91un;^dOCbAaVga&uuL9o-LIgZz=Y4-G8b($)9y5F!#ti!jr)e_xr2xKy*tgxEJ`9$SAd>Rl;Ng=4D7+SQ$KqhdnE2e zpKA2{dqtdU#jbOR0>7*U4JDlGeua~H${Y^uQt-eodQ)p@yhVBU_`5*c#a|tR5r={p zwd{?9s4x8a_fvjX%B8BU>Rjs&Np!gLAm*M#2>uVwXRB6&3K;D^ymnG-?GDmwfx>${ zKY5z9`NUKT)Bf-*SM4St^QM$Z(wx3q*9YvDq2@XDiI386jP8YbWxvIk3~TaxOLlsZ z$}Y3!25FhZbHEK%EkE2k%sF+3S@Hpj&kQ=j>>RLDr0%0)A<*YN=Y|*9w04fguzpW3 z=!Hh60x=aA#O6(K1A#IHwxB93nMgSKaSx&cS*#QN?&T|5M32e2Oq5C3>VAD}ZrQN= zxFI2de0aZA9JtKRPtt4F^gvtpb-JqA)G}3^O;mjs{;7Dg$Mj0?ey^5Fq#;e`5ND$Y zZj&A^&S)Y-iGU>!f6x?xwW_{zaP<_`GL4fXhg;$ss)}mCMeFKey1&a!>EW9`tFo#h zt#3qcN!EwejCTBP`f$RDu}FqnKL_Won{ghU4Y(reA327iJ@)?HAn@rXNY&#ie4|F` zK%dl;%HsLYv~|TZHrc0+CKSv!HFpyYlst-(C_P!w?_mtLOtSlJYTCKC#SJm zh!b`JHrM-Ye~)1Rl7Ls1C4$KF9v$yFZ-SNwx;hp5TdH!xgRPOq*emyBjif_I0 zTEg3y4f`#u`kjgLP0f1o$K^R!XkIdXHXm87Jt-bBY`v+Ah-iLEL0tLicBMI*4ri-_ z99S3X#u+i3F!{12fh=QP#!$}>#M;L&psL3oYGV4s+!cIpH-!Fz3I&vc? zjcy_-*L<%`B|K}bP7IdRDSuAfI?$?180tOrG~SA-GbP4x3Ys=8fg0vtmKVWTDMr7v zK|LQ(vFvs_rF$4$=p*?s-(6JNV*LbCf^$0_q9?h8cTIdFO{#W_wr~^mSAIAiMwD1e zw8T#aSZyPG+I0{$4>F3@ui}`ibSsKrv=3Xk=9H7bVJ!n4@hw8q`$SiM2Zc#JnTs8)Zc)J8?Z~(qF`1-OCHlP+)-Cl$ z-~9?#^qN@wrzanZ-=SYuJw4V(^lVr0C?9e`kmY@~*F9~=I~#jD-uQUP_P&uEZ_&L% zcqrRY?N_EF&6r*5e}qGORbi*~2KV{>at``k^%OHZ29H+9T$hEb$CyB0k^+J^7CIm< z(ym$up9**mdDlrTo7+}{)U9W}({lE#DZUzCNc|j)KwX()*-g5($GvS`UkMAN(D)?C+tu+}k`@Xs; zzSCsysANqwALHKzg?O!)GB`7w`V)@y<+Pk2ehnkCVLwww)dr3_FPD%+th@>&LKaBD zraf_87wRTu?1E@sh`Xi}iIA$|Y3B&9AG2W-|7v~Jv)bho{IX{`<~^ofM$?SZ?JgO_ zMtFATndpZ_T)yLSCMn0UJPFri*h4EfM?Z)SkkVO?2(syj)qT+^=TS7QmAxJtW0H2E zc3s`W#8&R?XLTFK?(r6VeL{kxdNUAnJdM{~>F@d;F)B_CZ~ulXsEjq`8@hv}8|t?0 zr%$#&YpXuts!yhFrki-bSWHPLBM*dXe&)sM3gGuDU)wxSpfo>v=3L_U`H=e+jJ-1o zNv<;;2J)#MCmzf(ubuUINdELhb~sJeLRIAzk8)^U24TIP+wKLVLv|xnK`mu?4stg&ti5*(bkEw<95E7^|Qul zBVDm+DN*OaGY-^UkYj%wrz!RKBt3>cu@;SB=C>iQ8_R~eiOLn&_T)G#k2H@r8L$k| z#3LZ=3iB&-rF>93h_O_7nDTlbYApeFj`;ViVG2|stwrwuA>g7vF^k`eg~rNzAbO(#RY0#dQQ8i!yPl{ z{7!*sX3eBrkM&L?8QmK|a*8pih?xxcT%#M{b64FWP{a7&SbD8ok8M-BFKEx2GhQ{5 zXc|--t1mPr+SR;-L;Hc~tFT zbM~gw47GPf}pu=poKz6PCJ5BKCu zgyCX|G2YAoEs*odvN}xLPaD=Yp8Fn+bKo>boN=G(*m9N)saQYkXnLz{0HI;@5M|74 z)Z(n#S6c_-y}0AFUk&|CAQL8gzjuOPKXt!T;9je_sF;1?*E#+!`T_bT_jrZoTd*es zfqA~8T=%pbr9fX~^+;kvap9K!*deY_L2+RTUUk}d-&C5)bU>{~#(|EH%l>L)()(-` zs*`y~wox9X>yX~StFJw}Vy26J(Dty8;S{M-9y2b zDa}iYs2f^J?nhIeCncS2d3pY#MOju5TyM0k$RwoTf}3bj%2_|HFW;slI$9hFwVqIw zTc5byGAg4oYBRu&zMvzhElck!`31Sh@O9KOe7WOf=cI>~X~+ca6>ZY55;`?I>GypY z)(yr>CRp_*L&|3#8%G8%$js>2gU9QyeH3f^_C@(^@$=X)*S8tu+oexXHYJ@(RPlAQ z6Yor)YkntBuw-H;WCMM}29D|#Y1q-%7{vj52?^gV;t#{xbal?%u@0HmnWSX~(ee+x zDR+H#8&+=hna0TAo0K9H=kl9#%AO_|;Z0QE$0-;NKf9?uXBDfP0B&}gz+v7)TQao0 z1XZ)AO6AE<9-;?*gqPT(D=<0SWT z?%e*R!RUyHqhIXVW~+Bq_dP7%+&#UI-BFl!^pS36Vl*y)qh9)+gvYl$O$eID<0W}_ z@jC+I)n6;?tfjgPiMxH(JX6MKnxfT%Dbx9@y2Wi5>ISA;JuFtaIm`GzfIEQbB$rlB z1bnPy9TIn26Qs zJob4U_o@UluiX}>^ZK=2t>f+0xCHkvP%$57bBa0!IjKjn#lv3Pv{1YqsF`cvbzx%6AGVQ3>5w}mEqhjcHSrPL(BQL4e ztl+z%TFv$eU*dBNWWmdZ(5#Evev}pi)H)##e?mkm;!LgWb@0B#*LkUhTOy{z`}7aZ zMTB{oeutKIdtC8^YD^6{Gwk}*Z|~ITe|V0)=WX%nw^h>o3Xde&HV|t1Cf3{h8wXdo zKH;=)NNqdzRYBW9j|25LwQomFP(i8g;_b=S#4d%RaeeM@K3%f6osXa^tLF^g!(Bsi^4U65( z6puH^B+`-F{NmFcGN8*q>U&vgaHWU!nON?6##zywUgmcd#biJDG($E;%}qi*y@oP3 zN+Dlp-rT;Su2PzOBr!@`CvxjsQ|Zk+uPtP6s?+Dj1Sxvr3Y(_ViCzyW&y~BxZEqdS*ZQkM^q1;8<-ZvxvEv!G_a`UoN0G!Wmt#HOEwhsZJ{LSm@4FLV8lz*=W zP$Ug5_*z#dw z;$T`@A6w1(r#WhxIR^(#PJ`Rei$`a6s$i;SpWDq?;`o0@eMz?rXoHi}<~vcLY2DgS zkE%-@#)tT3d%WUe2fmeDd@iVLM)GA8TlC3x(|V9Cj(-|$*h)h8l%u7Ve60hDPnx$K zfzam~USw#0TTKZ+F8$&I?vOO(IUX9yx+01ZH`dK<><|5ilzAxdn}%&rIn4Dr8G4W} ze8=DMY{`fR8M5~Sw-gu@lb!2u9>8UG?2*YGc#lw`S^z{Dr~=)1qtiiVE|IDjX<+$kbRqQc zfZ{Y>)zlpyj~;c_P@4dt(P3jP6rIsyiPD$WP#^V-_gyy*slN*{RNZs(XUr6Gly+fw z+qAP~*e;!@cgzxuKTSh}3>gLMz{cO(@@vkfk|6`2FQ*&)AiQ@< zJDV_ZuTa{)enw-FVXN}V?(8<%*>f5YgP)F*sQAh0r@ax0=vbW=dEg#&EkhUprQzCVprs1+M(BO*u2AKyWFL`wzf9beL`1PcgcYSl$oVLhNc`Rap{)T@#M$CIA^H|wc^+bM8Q5^((ysI%gLm?#i& zBsS}tetB{=lydnbDk^Hg{e)-IYPR9>pirMJ(9-*~1@rXQKfVTQ-{0#=Kn8-6dAgg6 zm}^Ih#HA%A*1}iWWTo7nQ*M7~ND<-O@*Fiz6s=!y)nTg}3ACpY?$*^=EQ`EFSXIpc zYD-l(C%`=e)oI%)Sa1WLNQ zG8B?V!OGCy*kO>|`);Aepp9iR+tucEJP+I~CjG}|_u}?yQElULVMld)GxZo#ANm$n z!EzgcOfZ^$*B*||7$a7^N;DTwSR6?WIA35Jk#i0>Zf1*KEO8LZdzXq6_1`6dY0l4t z^Q8OJg8GRPU210U!VJdF8C4gjs^Nz(|OyGUb z@s}`#?mZHhnta) zI@mXc2pDiwGn%){3_LS09jw7GsrS?h& z*mdXku_F5T9+l20B^Ro4Y_WRBK30et-(H<9Ap1ICT)ez6E0& z@-EA)&%kKi6|`JG%9PT=eyP9;Y((TqIneMRgtwRR;)TkDcSB*r@awiCsr zYGL{?aiZ#n%N@M@4-Ev+TVNuA+j7b~#28R<8xk`$UicF?PY(O|sffJKE3=R@a%tsT zD*2OvX|9B-3}^BW0Y&}p9BY!86{fKWj}_S?eqwv2tLMJl%A6_9qy?p_&L_;RQy5VB z@|+A^ISZi2^rUn_RsCg0c>n>yL&xmHb7F8}?7s}&@}FVyii7;v#>_S-q& zj0ranQ^Wt}Ku~|H`$fuRU@iL(_VwWPkY*-G!R=3v0H98EQTD;F3Pu{oV}I zHGG_5-JIQW=4rID+&6{hz+S54z+=B3p)A;e`b;o7p5j3|+`OzJAd(r5<0Quaq{8TiyZ`}IlpZx};zDizw3A5A zB9XqIh@CDZLElX7yaicFMnh0p;$%6nw-0-qUUYG>zVN|^r?Vkig{8lnnJKIK6Gvno zPNo+C%_s1`+iVwTNho#qNo!*ZaVn31Z#PD^afj!I&t2^a!%cM{ny!WTP=~?q{fndS zSHQ$d!G=L|FRAe*7BLs!Hy72IPehUy;#3s>DNEceQ0aI4{63I@*I61QXaP^MpJ$Du zaZ%-L$F+@1c#3yATtm_%I2jbe^U8lO+^ssx%@q)@)l?ji2J%3mV85rYaUK0vRo96%R`LX0z4w9;3Vmp4(bB35^Lm!+N_Ftbq1lh!5$`rsm z6$d8ipYTDaKSNdf-{5pNTrVW8A;p6=R^;3Cy!2NlEtQS6dyX!`r)Eqvg_4xuM7jSO zgb&1u8$}H)5SP(=i8bka6lmGs9-7jlYPxw&ca8Iuq4^GJt|@0<7C>H zs>v^D5sFRg<=PyKLv3eta0{$>`LwGvck@%VfsqDgLUYj*Q&M1L*XXP4a}$JTot`9 zv=x@gOvcgID#vE#-Yj z-*0(?jgXmEn7GkKL3hqmWUzE{bPWJh(2q&HHz{GvZ9_j+`XObS^NkP*O)y&hZzMw# zn1-i8c9Wrzo_Myd=1YY$|H{wvL~I{+g=bqLprO)J)tR6FH%rCYwmg6jz)MZ21|ZM< z;I1>6{~U!4jJqsI)p5NIt^3lg%6j(oaB)#8CM3|FW(Km_yPV|Mr66U`aW0o?Wdxov<(PIIXv0I3y}M{!I=dNzjNmn zn5)Cp0iq9!>2V;RRJ_TMw`tcHO;2XXk7a;KWJSRxmv$mG)WyTJC8ZFb{JH@QFe&(s z>}r?oFAyhoK@eaO;Zwl#5+9%|!Kl;iaAI&6bf1v+?cdNXC-i$)5+n^aM&H55hh5qK z^$#W0p`EiwSb2r09TFZQDmpsBR9aP~I4LDn+CJy$`6WoR*)VhPj=`KNk51>OkPWSP zSd4rQ4eDk*o*IQ4@au9GFbtS~R#RG1qGmKX$rYAVCxg<%8>5!g8NmYAZ-}G17cE7U z-VdEf^zH12!gr-33H?O?Y+L}DMSVu*5oVZ^Q*Fn?eI;;C(5(^0>`%C0)1va`wF%|G z7g&|7`}VN|@aj9HyrDg?vP$0Gp1ZWR^vPRt^3t209~$@z3kDe#7U-IP@DVID&40)h z%vDJd43Hz+xcukm{ol=JuY8DQ@USK321Z<=#VtHB^(E9!H5xTi38nSe6CR}O+qX)r z3BZV&(wcTJQeoELo?4F4pMLC~Jkh()krE<9(KMM*T2!UJ#$<__=S9s;o1o0`+RfGF z7qwc!v2*vykGZmed7l}~h4bFtgstF%zJrZPaT1Vq+rTw;?cmpGgDR?ch??R}0+W4d zaqZK+J*2RZkQy%J5|)s77Ns2}EFz;OghUGWeJig`3wjxar<&_)GsY=YC=!8F`9zg!{$l>Jo}C+c`O*Y-|v=-cGSLM4@qUcGiyJO&|6qM@NQ| zrrAeX2{J7VBJPM&ASvUTG6ZuArh(>~dUPAjRPwJmBu*|Ht{7kAjJZ8d^=LnXsnzAd9 z4GuG+a&mo`~`qZtKO_`n#jsl)`MyF_GS<-V=Vq zV%w-0V5EyWFg!{dScLB z**+Am_PpPUv^h7p`l;%a$1}_Pe6TrM&rT;d-d^iXG&&QeOXO;OyCwRW#nFd?H~LO zREwY*f&@^#ax2wVHR2YjDA%+tFJmyNP z=90XNqbhQx7jSU!N*gg||6=)*BMWohTlm4D<`w%g{|V6^6T*`@u0DukG#Vy^AdF^s zpf_skhfHukq^FD2fLZTsEJ}G7=AvEe?iJ{Sl9~Ngkg&A4B>vw&&qE_9-Rp#v1`!W76XI8ZBV-E6kgi;WT(~DRm@o7WOlfGLD|>W(<+V|8^f2UA_S(K zuiV|W;sQ;6IjwH4&eva%ZKIUi>?~iP=Ix*H+7}iS^mpyz_hO%eVTZ^`_ZLo1UrzN7 zU`Fi)0g49{;luso&%@PvhlhtBtMz^z9{fHxhOKgQIp36B-C)At<;ap%x?U1oBsy38 z;@V&mSCSbKamjB}df~8G%VCCU+niz5sas#$8rlO+^+6=T$4X5r^a?9IQ~11v1w%`R zd>d@6LvLWTv>^X~_~j-W0ED#f{~w~yw&1pa>ag4n?ijy>$g;EajYct0X514$eh5&! z4HGcaS^sB4c$*G$odBjf5 z;5Mfh4whm_IaH@EUZ>OQ28-1(-4;~Nm@bY{eJRPOnU}5!w+@e*? z-X{91Z^>w8P3kjBqVE5golbjY;GP`+rD)&-0C=!lBp9%{wUwTn%vW~K?J_<*)iQU8 zUZpQE6=aqT&{d3yq1~_ic}vFISUr-9RlBuMZYZjYE7$w*d6V(M+qkK6)C*I=j*(HJ zY=LMI;XE#LWo4>^1FC_J6+*SX9^x?(Z2xczsE%Phb*wOfLDZfBKSg_FC~N;qF52H; zE2qu#JUh6HO*SpX-%`%^`C?@9c0B?Zj-Lz6>~)-Kh^n^oGSHEgxO$lLTvy0NHbbTJ zVnW;d^ru8!w%0R}17JmXVRmYc{E1FFjMYr@f}41T7&R3u`r=L{3w`Fm8-~w%m~3uz z79u_aagog&cn@~`SC8X7nZ?r<10r;@cf1!Q_A>$1zP7Wo*emz?%+XQ07gjF0d3o>^ zpH9H>JDh5U5)u4t4x?IWCz0!?l0$C|Mao?~k&6)$*9C)REzI-1Gjsjv4SqqM5grb5*?4%s&L_ zHd*qH)a7aySko8ed`*oLIj<;4ZYqj5H1n{7CMIUQUKIH;HIp84ze;I`*M!(ZcEN@& zTi?wGD*Z)*dugW!m1R;NzgI7we&t?nHe?bfy39X`>RdCyEoZ1SGP2T6iqXsXM ziml+g_wd_=+1IHJF}yL%QNd}Ys_Y^&MQ%v&K?{dZVW^O{p^eUJ&+y-bUb4Cu_?e)yd{(` z;|ZZ+#f9rU@^T~ZL1R=Z96K=u56a6+ht|r~iJMGU^;G*7gk^YD`^Z;ciX!m~;R?gE zV~QJ&#plLZ1tGqt!hx;Ii~DmyFq(|do{@KVSQQo`-L8}k@C(xrefBw~kfOSf>gt-2 zaIxw6ULIg_*(?6vM9n{ih+7aNeFtSRKvbEQnnU)OSPhtdjH}xNYgFK~zM0kZ;l}6B zMqgqMcz;aDeOVY1l4d30QkkZwVM`hin0QY=8`n2d(npSV(kU?CNZKNPWlGa|K36Mp zy)EZPo8Fh(5i_XB_K6vc&a1ehKm!+rx5f$Cu}KUxHIgGHy}Z1=!~GbGA|)rlt;UZp zm=zLuuC+ zPE671g#&5$Y93@#gUM0Y^;2 zSx7D{tA#days3EUUE>=N0mwo`D4I%ZUGz_`--uVBH8W#08o_3rcw_rZ={8mmGUdhnW8}s(Zawi*V=sJqaA#n z;_l|+<;M>7>_>Yq&HW>-tjVizq-|EnBZuQme0|5^u6~@F2V`JA+|A20R9M){+K0uu zq{wA0kKn&>O)?{BHY8Utt-%-dI<#;u^mpj(Pq08iE~FLfe$`@F8|&)%w&G0F%SNnG ztqe0_Dhj~2GIb_o`Uyt+y~1T>A}|wP59#i*j~XMpheKRd_YIw_KD?aFHVG`v(lGIO zL^aOpD+IaN{glXBMPSI9HKfA()ita%5FT)WSP`* z|In7dlozAtmXEjnM8l?QNEOw@&Y-`Ac=f_T6J|U4R{5MZJZ@2BuFDO)E^Bm<&JK z21Q2MeNdk1_$XLYWgtUulx#OX@JO}ZZ%Tu;t@pi%pb{J&B`hJJIHWBE%q)>k<`Ge) zeW_N5c2=I@s4G#MG-4{PfA|eF#;u-B;zL#cJ8P!?Ti2s8rY=E21^e{Cymzx{n_ptr zScz0dDix$Rv!$OHnc~sQvKw!%#GdTutbu(9@(`Sq`#CRirRi)To9D4%VcnBPztq64 zCB3z^&W3Vwnz?W0?azl<`T&aA9d1I)h3^k&o{G z3;AgOpU6kobujet>Rg#!Nso56A&SH~Ww7%@<4;OQ$NZtyErZlkt_|^ywXw;xz31!m+(jlMcuck^aF*RU zO6+@wJxP1j*V-(@HcB(z4&=up>F%2RENj_a;yXpZ9&Jn%hx6{mco=tclOq<(LK2TY z)-HSdMU}e~-j>iz;j`|uL@E^%?9M_k~zgP7~#zLkBH z<9ePeY*MD`pF+lNa^FjIzK^u@%etL>`g!RG!~voeRU^HgQ&0bm!SG&3Rd0gt1SuYH zH5{#KO59o2GLR;lDXCR6O284nK6Az~e-`N=;T`6>oxE%iB#sBBuqMl=qkC58VK7sE zZ%f&x@yArO-u^~EWyt6zmUUxebKr7DRw1=!RjO5W*Y7BjAYB!)F@q~yysbMIbAKDQm^t11#j%3Wv5rX zu4lt)1-8oBb<=3FwD)8)=ST`!kcFuJNdMSUUrl(nD;Ots_+!109ZBf>y07F)&*TLX zo!f;WBf&JAC&WnE{9yoW#A!Q-~J1liR{C(=tDru-EFzQsx!T*UhQPB8dT-Kr zfO|FZJpy_Ee2A|-!4D_g`0i+;YqmvySX9Xh`hg6dFa47NIgKQG%O=+!cBExuW$x=E ze`gurR%?X%JvlS%RFGBsD%62N2J{cQiMDmGJmFmn#Bp|G~hlgs6 z4ic?*_&TPA^tSvP=&hAZ%nAwSxc(;NIn<8VjFYId|>uoe4 z%6JPT21+7gT>PsR>T8Xqm_FAjHbaDq^?z2kI&2;@FJ)3T?L-^R%&)d4yy(;H|# z9Z8Gh*z0sA0ZrMx${T^4_@V?qeZk9{SOEJ?WpeeuXu=%M#K7i*s268qujgs5li-w~7ZZ(e1x@DnQ;rf2A;Y{IEmvRM z+mC+8Cf!u9pETu}(&x^1bAR$CWJky#*SFR1CHGQQlh<{szM2t- zAqC^((a5D(KN~Z3;`3E48#Wn4&6oa7z5jnZB{|*g}rd*vPPP@%6(=P3C zM`6U-c$YyEdh)+XaY)CXb2DDJu=HQYYHF|1vOlM4OfLfHa+W)SZggnlP7nc}DXKpy Icx)d09|QiQj{pDw literal 16644 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 Date: Tue, 26 Aug 2025 12:01:56 +0200 Subject: [PATCH 25/33] [P089] Update documentation --- docs/source/Plugin/P089.rst | 16 +++++++++++++++- .../Plugin/P089_AvailableValuesOptions.png | Bin 0 -> 12251 bytes .../Plugin/P089_DeviceConfiguration.png | Bin 29424 -> 42319 bytes .../Plugin/_plugin_substitutions_p08x.repl | 6 +++--- 4 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 docs/source/Plugin/P089_AvailableValuesOptions.png diff --git a/docs/source/Plugin/P089.rst b/docs/source/Plugin/P089.rst index ea690971f..dbe0bc90e 100644 --- a/docs/source/Plugin/P089.rst +++ b/docs/source/Plugin/P089.rst @@ -40,6 +40,16 @@ Device Settings * **Hostname**: The hostname or IP-address for the device or host to monitor. +**ESP32 only:** + +* **Ping count**: The number of pings that should be tried before reporting a failure. (Only available on ESP32 builds) + +* **Available Values**: The default is to only have a **Fails** value available, but for ESP32 there is also the Average ping-time in msec. available. + +.. image:: P089_AvailableValuesOptions.png + +When changing this setting, the page will save and reload to adjust for the change in **Values**, below. + .. include:: DataAcquisition.repl * **Interval** By default, Interval will be set to 60 sec. Every Interval setting, the host will be pinged and the state will be updated in Fails. If the ping responds as intended, then Fails is reset to 0. @@ -47,7 +57,9 @@ Device Settings Values ^^^^^^ -The failure count value is available in ``Fails``. No other options are available for Values. +The failure count value is available in ``Fails``. On ESP32 there is an option to also show the average ping-time in msec. via ``Avg_ms``. + +On ESP32, per Value is a **Stats** checkbox available, that when checked, gathers the data and presents recent data in a graph, as described here: :ref:`Task Value Statistics: ` @@ -64,6 +76,8 @@ Change log .. versionchanged:: 2.0 ... + |added| 2025-08-26 Enable support for ESP32 + |changed| 2023-03-14 Extended command handling to not require the taskname argument. |added| 2020-02-22 diff --git a/docs/source/Plugin/P089_AvailableValuesOptions.png b/docs/source/Plugin/P089_AvailableValuesOptions.png new file mode 100644 index 0000000000000000000000000000000000000000..ad5e8ba35dfea444f37a0375a36d8ab31d6da1f0 GIT binary patch literal 12251 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 UPi*W)&j0`b07*qoM6N<$f@dioqyPW_ literal 29424 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 Kb* Date: Sun, 7 Sep 2025 16:52:04 +0200 Subject: [PATCH 26/33] [AutoDiscover] Fix small typos in default UoM list --- src/src/DataTypes/SensorVType.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/src/DataTypes/SensorVType.cpp b/src/src/DataTypes/SensorVType.cpp index 4e269fc92..5d08c4e72 100644 --- a/src/src/DataTypes/SensorVType.cpp +++ b/src/src/DataTypes/SensorVType.cpp @@ -533,8 +533,8 @@ const char mqtt_valueType_default_ha_uom_names[] PROGMEM = // !! Offset, startin "|m/s|min||" // SWITCH_INVERTED .. DATE "|bit/s|B|dB|dBm|kvar|" // TIMESTAMP .. REACTIVE_ENERGY "Hz|kWh|kWh|" // FREQUENCY .. ENERGY_STORAGE - "g/m³|mbar|mmol/L" // ABS_HUMIDITY .. BLOOD_GLUCOSE_C - "ppm|kWh/100km|L|µg/m³|µg/m³" // CO_ONLY .. OZONE_ONLY + "g/m³|mbar|mmol/L|" // ABS_HUMIDITY .. BLOOD_GLUCOSE_C + "ppm|kWh/100km|L|µg/m³|µg/m³|" // CO_ONLY .. OZONE_ONLY "mm|mm/h|µg/m³|ppm|" // PRICIPIATION .. VOC_PARTS "L|m³/h|L|L|" // VOLUME .. WATER ; From 8d501a4ded3c8b9c2b60873c5af958bbbb293b05 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sun, 7 Sep 2025 17:03:29 +0200 Subject: [PATCH 27/33] [AutoDiscover] Fix spelling error in precipitation --- src/src/DataTypes/SensorVType.cpp | 16 ++++++++-------- src/src/DataTypes/SensorVType.h | 4 ++-- src/src/Helpers/_CPlugin_Helper_mqtt.cpp | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/src/DataTypes/SensorVType.cpp b/src/src/DataTypes/SensorVType.cpp index 5d08c4e72..066115fc1 100644 --- a/src/src/DataTypes/SensorVType.cpp +++ b/src/src/DataTypes/SensorVType.cpp @@ -109,8 +109,8 @@ uint8_t getValueCountFromSensorType(Sensor_VType sensorType, bool log) case Sensor_VType::SENSOR_TYPE_GAS_ONLY: case Sensor_VType::SENSOR_TYPE_NITROUS_OXIDE: case Sensor_VType::SENSOR_TYPE_OZONE_ONLY: - case Sensor_VType::SENSOR_TYPE_PRICIPITATION: - case Sensor_VType::SENSOR_TYPE_PRICIPITATION_INTEN: + case Sensor_VType::SENSOR_TYPE_PRECIPITATION: + case Sensor_VType::SENSOR_TYPE_PRECIPITATION_INTEN: case Sensor_VType::SENSOR_TYPE_SULPHUR_DIOXIDE: case Sensor_VType::SENSOR_TYPE_VOC_PARTS: case Sensor_VType::SENSOR_TYPE_VOLUME: @@ -215,8 +215,8 @@ const __FlashStringHelper* getSensorTypeLabel(Sensor_VType sensorType) { case Sensor_VType::SENSOR_TYPE_GAS_ONLY: return F("Gas"); case Sensor_VType::SENSOR_TYPE_NITROUS_OXIDE: return F("N2O"); case Sensor_VType::SENSOR_TYPE_OZONE_ONLY: return F("Ozone"); - case Sensor_VType::SENSOR_TYPE_PRICIPITATION: return F("Pricipitation"); - case Sensor_VType::SENSOR_TYPE_PRICIPITATION_INTEN: return F("Pricipitation intensity"); + case Sensor_VType::SENSOR_TYPE_PRECIPITATION: return F("Precipitation"); + case Sensor_VType::SENSOR_TYPE_PRECIPITATION_INTEN: return F("Precipitation intensity"); case Sensor_VType::SENSOR_TYPE_SULPHUR_DIOXIDE: return F("SO2"); case Sensor_VType::SENSOR_TYPE_VOC_PARTS: return F("VOC parts"); case Sensor_VType::SENSOR_TYPE_VOLUME: return F("Volume"); @@ -275,8 +275,8 @@ const __FlashStringHelper* getSensorTypeLabel(Sensor_VType sensorType) { case Sensor_VType::SENSOR_TYPE_GAS_ONLY: case Sensor_VType::SENSOR_TYPE_NITROUS_OXIDE: case Sensor_VType::SENSOR_TYPE_OZONE_ONLY: - case Sensor_VType::SENSOR_TYPE_PRICIPITATION: - case Sensor_VType::SENSOR_TYPE_PRICIPITATION_INTEN: + case Sensor_VType::SENSOR_TYPE_PRECIPITATION: + case Sensor_VType::SENSOR_TYPE_PRECIPITATION_INTEN: case Sensor_VType::SENSOR_TYPE_SULPHUR_DIOXIDE: case Sensor_VType::SENSOR_TYPE_VOC_PARTS: case Sensor_VType::SENSOR_TYPE_VOLUME: @@ -384,8 +384,8 @@ bool isMQTTDiscoverySensorType(Sensor_VType sensorType) case Sensor_VType::SENSOR_TYPE_GAS_ONLY: case Sensor_VType::SENSOR_TYPE_NITROUS_OXIDE: case Sensor_VType::SENSOR_TYPE_OZONE_ONLY: - case Sensor_VType::SENSOR_TYPE_PRICIPITATION: - case Sensor_VType::SENSOR_TYPE_PRICIPITATION_INTEN: + case Sensor_VType::SENSOR_TYPE_PRECIPITATION: + case Sensor_VType::SENSOR_TYPE_PRECIPITATION_INTEN: case Sensor_VType::SENSOR_TYPE_SULPHUR_DIOXIDE: case Sensor_VType::SENSOR_TYPE_VOC_PARTS: case Sensor_VType::SENSOR_TYPE_VOLUME: diff --git a/src/src/DataTypes/SensorVType.h b/src/src/DataTypes/SensorVType.h index f4903d725..db6a75a93 100644 --- a/src/src/DataTypes/SensorVType.h +++ b/src/src/DataTypes/SensorVType.h @@ -87,8 +87,8 @@ enum class Sensor_VType : uint8_t { SENSOR_TYPE_GAS_ONLY = 148, SENSOR_TYPE_NITROUS_OXIDE = 149, SENSOR_TYPE_OZONE_ONLY = 150, - SENSOR_TYPE_PRICIPITATION = 151, - SENSOR_TYPE_PRICIPITATION_INTEN = 152, + SENSOR_TYPE_PRECIPITATION = 151, + SENSOR_TYPE_PRECIPITATION_INTEN = 152, SENSOR_TYPE_SULPHUR_DIOXIDE = 153, SENSOR_TYPE_VOC_PARTS = 154, SENSOR_TYPE_VOLUME = 155, diff --git a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp index 63e450657..a4f327bc1 100644 --- a/src/src/Helpers/_CPlugin_Helper_mqtt.cpp +++ b/src/src/Helpers/_CPlugin_Helper_mqtt.cpp @@ -844,8 +844,8 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn case Sensor_VType::SENSOR_TYPE_GAS_ONLY: case Sensor_VType::SENSOR_TYPE_NITROUS_OXIDE: case Sensor_VType::SENSOR_TYPE_OZONE_ONLY: - case Sensor_VType::SENSOR_TYPE_PRICIPITATION: - case Sensor_VType::SENSOR_TYPE_PRICIPITATION_INTEN: + case Sensor_VType::SENSOR_TYPE_PRECIPITATION: + case Sensor_VType::SENSOR_TYPE_PRECIPITATION_INTEN: case Sensor_VType::SENSOR_TYPE_SULPHUR_DIOXIDE: case Sensor_VType::SENSOR_TYPE_VOC_PARTS: case Sensor_VType::SENSOR_TYPE_VOLUME: From 49868d9a1bbf562771880bd59d73c54d586a110a Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sun, 7 Sep 2025 20:50:55 +0200 Subject: [PATCH 28/33] [AutoDiscover] Fix spelling error in precipitation --- docs/source/Plugin/_Plugin.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/Plugin/_Plugin.rst b/docs/source/Plugin/_Plugin.rst index 720a2f9cd..d7d695379 100644 --- a/docs/source/Plugin/_Plugin.rst +++ b/docs/source/Plugin/_Plugin.rst @@ -275,7 +275,7 @@ Available options, grouped per category: **Basic**: ``Single`` -**Environment**: ``Temp¹, Hum¹, Baro¹, Wind speed¹, Absolute humidity¹, Atmospheric pressure¹, Pricipitation¹,`` ``Pricipitation intensity¹`` +**Environment**: ``Temp¹, Hum¹, Baro¹, Wind speed¹, Absolute humidity¹, Atmospheric pressure¹, Precipitation¹,`` ``Precipitation intensity¹`` **Dust/Gases**: ``Dust PM2.5¹, Dust PM1.0¹, Dust PM10¹, (e)CO2¹, CO¹, TVOC¹, VOC parts¹, AQI¹, NOx¹,`` ``Gas¹, N2O¹, Ozone¹, SO2¹`` From bd326621a69de22681669919cc13536a0326a5a5 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Sun, 7 Sep 2025 23:14:43 +0200 Subject: [PATCH 29/33] [MQTT] Fix compilation on ESP8266 (Retain per task) --- src/src/WebServer/DevicesPage.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/src/WebServer/DevicesPage.cpp b/src/src/WebServer/DevicesPage.cpp index 45d8518a7..1253bc6a5 100644 --- a/src/src/WebServer/DevicesPage.cpp +++ b/src/src/WebServer/DevicesPage.cpp @@ -408,9 +408,9 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task #if FEATURE_STRING_VARIABLES Settings.SendDerivedTaskValues(taskIndex, controllerNr, isFormItemChecked(getPluginCustomArgName(F("TSND"), controllerNr))); #endif // if FEATURE_STRING_VARIABLES - #if FEATURE_MQTT + #if FEATURE_MQTT && FEATURE_MQTT_DISCOVER Settings.SendRetainedTaskValues(taskIndex, controllerNr, isFormItemChecked(getPluginCustomArgName(F("TSRT"), controllerNr))); - #endif // if FEATURE_MQTT + #endif // if FEATURE_MQTT && FEATURE_MQTT_DISCOVER } if (device.PullUpOption) { @@ -1620,7 +1620,7 @@ void devicePage_show_controller_config(taskIndex_t taskIndex, deviceIndex_t Devi getPluginCustomArgName(F("TDID"), controllerNr), // ="taskdeviceid" Settings.TaskDeviceID[controllerNr][taskIndex], 0, DOMOTICZ_MAX_IDX); } - #if FEATURE_MQTT + #if FEATURE_MQTT && FEATURE_MQTT_DISCOVER if (showMqttGroup) { html_TD(); addHtml(F("Retained:")); @@ -1631,7 +1631,7 @@ void devicePage_show_controller_config(taskIndex_t taskIndex, deviceIndex_t Devi # endif // if FEATURE_TOOLTIPS ); } - #endif // if FEATURE_MQTT + #endif // if FEATURE_MQTT && FEATURE_MQTT_DISCOVER # if FEATURE_STRING_VARIABLES if (allowSendDerived) { html_TD(); From 089d8bb9690c490a6615daf954c5384de02973bf Mon Sep 17 00:00:00 2001 From: TD-er Date: Mon, 8 Sep 2025 10:56:38 +0200 Subject: [PATCH 30/33] Fix missing #ifdef USES_P056 --- src/src/PluginStructs/P056_data_struct.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/src/PluginStructs/P056_data_struct.cpp b/src/src/PluginStructs/P056_data_struct.cpp index 86dc9eed4..3402a7432 100644 --- a/src/src/PluginStructs/P056_data_struct.cpp +++ b/src/src/PluginStructs/P056_data_struct.cpp @@ -1,6 +1,8 @@ #include "../PluginStructs/P056_data_struct.h" +#ifdef USES_P056 + P056_data_struct::P056_data_struct(EventStruct *event) { delete SDS; @@ -60,3 +62,5 @@ void P056_data_struct::SetWorkingPeriod(int minutes) { this->SDS->SetWorkingPeriod(minutes); } } + +#endif From d31c6062d011bc1540d823a898f351d4bd3ee378 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:04:37 +0000 Subject: [PATCH 31/33] Bump actions/setup-python from 5 to 6 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 8 ++++---- .github/workflows/release.yml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 48bb769e2..f2884eac2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.x' - name: Build documentation @@ -47,7 +47,7 @@ jobs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.x' - id: set-matrix @@ -63,7 +63,7 @@ jobs: matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.x' - name: Dependencies @@ -102,7 +102,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.x' - name: Get current date diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f744461ae..f407ea859 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.x' - id: set-matrix @@ -38,7 +38,7 @@ jobs: matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 - name: Dependencies run: | sudo apt install binutils build-essential libffi-dev libgit2-dev @@ -68,7 +68,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 - name: Build documentation run: | cd docs @@ -119,7 +119,7 @@ jobs: needs: [build, prepare-dist, prepare-notes] runs-on: ubuntu-latest steps: - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.x' - name: Get current date From 2dc0363543b2ebe425c1555587e2b4a4f2918dc7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:04:40 +0000 Subject: [PATCH 32/33] Bump actions/github-script from 7 to 8 Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 8. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f744461ae..0c09e07e7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -97,7 +97,7 @@ jobs: notes: ${{ steps.release-notes.outputs.result }} steps: - id: release-notes - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: result-encoding: string script: | From 55f754283bdccaad69e0c14d49a698dd87eb44cd Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Mon, 8 Sep 2025 20:55:04 +0200 Subject: [PATCH 33/33] [P089] Bugfix: Putt include in scope --- src/src/PluginStructs/P089_data_struct.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/src/PluginStructs/P089_data_struct.h b/src/src/PluginStructs/P089_data_struct.h index 40bd838c0..60b91461a 100644 --- a/src/src/PluginStructs/P089_data_struct.h +++ b/src/src/PluginStructs/P089_data_struct.h @@ -4,6 +4,8 @@ #include "../../_Plugin_Helper.h" #ifdef USES_P089 +# include "src/ESPEasyCore/ESPEasyNetwork.h" + # define PLUGIN_ID_089 89 @@ -52,8 +54,6 @@ extern "C" # include } -# include "src/ESPEasyCore/ESPEasyNetwork.h" - struct P089_icmp_pcb { P089_icmp_pcb() = default;