components - latest

This commit is contained in:
lovebird 2025-06-03 15:16:12 +02:00
parent 09a487086b
commit 752eca2587
2 changed files with 283 additions and 0 deletions

236
src/ValueWrapper.h Normal file
View File

@ -0,0 +1,236 @@
#ifndef VALUE_WRAPPER_H
#define VALUE_WRAPPER_H
#include <functional>
#include <cmath> // For std::abs
#include <type_traits> // For std::is_enum, std::underlying_type, std::enable_if, std::is_arithmetic
#include "Component.h"
#include "modbus/ModbusTypes.h"
#include "enums.h"
#include "Logger.h"
template<typename T>
class ValueWrapper {
public:
enum class ThresholdMode {
DIFFERENCE, // Trigger if abs(newVal - oldVal) >= threshold
INTERVAL_STEP // Trigger if floor(newVal / threshold) != floor(oldVal / threshold)
};
private:
// Helper for threshold comparison - DIFFERENCE mode, enum types
template<typename U = T, typename std::enable_if<std::is_enum<U>::value, int>::type = 0>
static bool checkThresholdDifference(U newVal, U oldVal, U threshold) {
if (static_cast<typename std::underlying_type<U>::type>(threshold) == 1) {
return newVal != oldVal;
} else {
return (std::abs(static_cast<long long>(static_cast<typename std::underlying_type<U>::type>(newVal)) - static_cast<long long>(static_cast<typename std::underlying_type<U>::type>(oldVal))) >=
std::abs(static_cast<long long>(static_cast<typename std::underlying_type<U>::type>(threshold))));
}
}
// Helper for threshold comparison - DIFFERENCE mode, non-enum, signed types
template<typename U = T, typename std::enable_if<!std::is_enum<U>::value && std::is_signed<U>::value, int>::type = 0>
static bool checkThresholdDifference(U newVal, U oldVal, U threshold) {
return std::abs(newVal - oldVal) >= threshold;
}
// Helper for threshold comparison - DIFFERENCE mode, non-enum, unsigned types
template<typename U = T, typename std::enable_if<!std::is_enum<U>::value && std::is_unsigned<U>::value, int>::type = 0>
static bool checkThresholdDifference(U newVal, U oldVal, U threshold) {
U diff = (newVal > oldVal) ? (newVal - oldVal) : (oldVal - newVal);
return diff >= threshold;
}
// Helper for threshold comparison - INTERVAL_STEP mode (for arithmetic types)
template<typename U = T, typename std::enable_if<std::is_arithmetic<U>::value, int>::type = 0 >
static bool checkThresholdIntervalStep(U newVal, U oldVal, U stepInterval) {
if (stepInterval == 0) return false; // Avoid division by zero
return (static_cast<long long>(newVal / stepInterval)) != (static_cast<long long>(oldVal / stepInterval));
}
// Fallback for non-arithmetic types with INTERVAL_STEP (should ideally not be chosen or error)
template<typename U = T, typename std::enable_if<!std::is_arithmetic<U>::value, int>::type = 0 >
static bool checkThresholdIntervalStep(U newVal, U oldVal, U stepInterval) {
return false; // Or throw, or static_assert(false, ...)
}
// Helper to get value for Modbus message (short) - enabled for enum types
template<typename U = T, typename std::enable_if<std::is_enum<U>::value, int>::type = 0>
static short getModbusValueHelper(U val) {
return static_cast<short>(static_cast<typename std::underlying_type<U>::type>(val));
}
// Helper to get value for Modbus message (short) - enabled for non-enum types
template<typename U = T, typename std::enable_if<!std::is_enum<U>::value, int>::type = 0>
static short getModbusValueHelper(U val) {
return static_cast<short>(val);
}
public:
ValueWrapper(
Component* owner,
ushort sourceComponentId,
std::function<uint16_t()> getModbusBaseAddressLambda,
uint16_t modbusRegisterOffset,
E_FN_CODE notificationFunctionCode,
uint8_t notificationSlaveId,
T initialValue,
T threshold, // For INTERVAL_STEP mode, this is the step interval
ThresholdMode mode = ThresholdMode::DIFFERENCE, // New mode argument
std::function<void(const T& newValue, const T& oldValue)> onNotifiedPostCallback = nullptr
)
: m_owner(owner),
m_sourceComponentId(sourceComponentId),
m_getModbusBaseAddressLambda(getModbusBaseAddressLambda),
m_modbusRegisterOffset(modbusRegisterOffset),
m_notificationFunctionCode(notificationFunctionCode),
m_notificationSlaveId(notificationSlaveId),
m_value(initialValue),
m_threshold(threshold),
m_thresholdMode(mode),
m_onNotifiedPostCallback(onNotifiedPostCallback) {}
// Copy constructor
ValueWrapper(const ValueWrapper& other)
: m_owner(other.m_owner),
m_sourceComponentId(other.m_sourceComponentId),
m_getModbusBaseAddressLambda(other.m_getModbusBaseAddressLambda),
m_modbusRegisterOffset(other.m_modbusRegisterOffset),
m_notificationFunctionCode(other.m_notificationFunctionCode),
m_notificationSlaveId(other.m_notificationSlaveId),
m_value(other.m_value),
m_threshold(other.m_threshold),
m_thresholdMode(other.m_thresholdMode),
m_onNotifiedPostCallback(other.m_onNotifiedPostCallback) {}
void update(const T& newValue) {
bool thresholdExceeded = false;
if (m_thresholdMode == ThresholdMode::DIFFERENCE) {
thresholdExceeded = checkThresholdDifference(newValue, m_value, m_threshold);
} else { // INTERVAL_STEP
thresholdExceeded = checkThresholdIntervalStep(newValue, m_value, m_threshold);
}
if (thresholdExceeded) {
T oldValue = m_value;
m_value = newValue;
if (m_owner && m_getModbusBaseAddressLambda) {
MB_UpdateData update_msg;
update_msg.address = m_getModbusBaseAddressLambda() + m_modbusRegisterOffset;
update_msg.value = getModbusValueHelper(newValue);
update_msg.slaveId = m_notificationSlaveId;
update_msg.functionCode = m_notificationFunctionCode;
update_msg.componentId = m_sourceComponentId;
Log.infoln("ValueWrapper (CompID: %u, Mode: %d): Threshold met for reg_offset %u. Old: %d, New: %d. Notifying owner.",
m_sourceComponentId, static_cast<int>(m_thresholdMode), m_modbusRegisterOffset,
static_cast<int>(oldValue),
static_cast<int>(newValue));
m_owner->onMessage(m_sourceComponentId, E_CALLS::EC_USER, E_MessageFlags::E_MF_NONE, &update_msg, m_owner );
if (m_onNotifiedPostCallback) {
m_onNotifiedPostCallback(newValue, oldValue);
}
} else {
Log.warningln("ValueWrapper (CompID: %u): Threshold met for reg_offset %u, but owner or base_addr_lambda is null. Cannot notify.",
m_sourceComponentId, m_modbusRegisterOffset);
if (m_onNotifiedPostCallback) {
m_onNotifiedPostCallback(newValue, oldValue);
}
}
} else {
m_value = newValue;
}
}
T get() const {
return m_value;
}
operator T() const {
return m_value;
}
void setValueWithoutNotification(const T& newValue) {
m_value = newValue;
}
// Public getter for the calculated Modbus address used in notifications
uint16_t getNotificationModbusAddress() const {
if (m_getModbusBaseAddressLambda) {
return m_getModbusBaseAddressLambda() + m_modbusRegisterOffset;
}
// Return a sensible default or error indicator if lambda is null, though it shouldn't be with current usage
// For now, returning offset itself might indicate an issue or be a raw offset if base is 0.
// Or, more robustly, this scenario should ideally not happen if constructed correctly.
Log.warningln("ValueWrapper (CompID: %u): getNotificationModbusAddress() called but base address lambda is null! Returning raw offset.", m_sourceComponentId);
return m_modbusRegisterOffset;
}
// Public getter for the function code used in notifications
E_FN_CODE getNotificationFunctionCode() const {
return m_notificationFunctionCode;
}
private:
Component* m_owner;
ushort m_sourceComponentId;
std::function<uint16_t()> m_getModbusBaseAddressLambda;
uint16_t m_modbusRegisterOffset;
E_FN_CODE m_notificationFunctionCode;
uint8_t m_notificationSlaveId;
T m_value;
T m_threshold;
ThresholdMode m_thresholdMode;
std::function<void(const T& newValue, const T& oldValue)> m_onNotifiedPostCallback;
};
// Helper macro to generate the standard post-notification lambda
#define DEFAULT_VW_POST_NOTIFY_LAMBDA(VW_TYPE, VW_LOG_PREFIX_STRING, VW_LOG_OLD_VAL_EXPR, VW_LOG_NEW_VAL_EXPR) \
[this](const VW_TYPE& newValue, const VW_TYPE& oldValue) { \
Log.verboseln("%s: %s changed from %d to %d. Post-callback.", \
this->name.c_str(), \
VW_LOG_PREFIX_STRING, \
(VW_LOG_OLD_VAL_EXPR), \
(VW_LOG_NEW_VAL_EXPR)); \
}
// Macro to simplify ValueWrapper initialization
#define INIT_COMPONENT_VALUE_WRAPPER(VW_TYPE, VW_REG_OFFSET, VW_NOTIF_FC, VW_INITIAL_VAL, VW_THRESHOLD_VAL, VW_THRESHOLD_MODE, VW_CALLBACK) \
ValueWrapper<VW_TYPE>( \
this->owner, \
this->id, \
[this]() -> uint16_t { return this->mb_tcp_base_address(); }, \
static_cast<uint16_t>(VW_REG_OFFSET), \
VW_NOTIF_FC, \
static_cast<uint8_t>(1), /* Default slave ID */ \
VW_INITIAL_VAL, \
VW_THRESHOLD_VAL, \
VW_THRESHOLD_MODE, \
VW_CALLBACK \
)
/* Conceptual Usage Example:
// In a class like TemperatureProfile:
// Constructor:
// _myWrappedValue([this](){ return this->owner; } // Lambda to get the owner
// this->id,
// [this](){ return this->mb_tcp_base_address(); },
// TEMP_REG_OFFSET,
// E_FN_CODE::FN_WRITE_HOLD_REGISTER,
// 1, // slaveId
// INITIAL_SENSOR_VAL,
// TEMP_THRESHOLD,
// [this](const int& newVal, const int& oldVal){
// Log.infoln("Temp specific log: %d -> %d", oldVal, newVal);
// });
//
// In loop:
// _myWrappedValue.update(readSensor());
*/
#endif // VALUE_WRAPPER_H

47
src/json.h Normal file
View File

@ -0,0 +1,47 @@
#ifndef JSON_UTILS_H
#define JSON_UTILS_H
#include <ArduinoJson.h>
#include <ArduinoLog.h>
namespace JsonUtils {
inline void parseJsonFieldUint32(const JsonObject& json, const char* key, uint32_t& targetValue, const char* fieldName, const char* componentName) {
JsonVariantConst value = json[key];
if (value.is<uint32_t>()) {
targetValue = value.as<uint32_t>();
} else {
if (!value.isNull()) {
Log.traceln(F("[%s] WARN: '%s' in JSON is not uint32_t. Using default: %lu"),
componentName, fieldName, targetValue);
}
}
}
inline void parseJsonFieldUint8(const JsonObject& json, const char* key, uint8_t& targetValue, const char* fieldName, const char* componentName) {
JsonVariantConst value = json[key];
if (value.is<uint8_t>()) {
targetValue = value.as<uint8_t>();
} else {
if (!value.isNull()) {
Log.traceln(F("[%s] WARN: '%s' in JSON is not uint8_t. Using default: %u"),
componentName, fieldName, targetValue);
}
}
}
inline void parseJsonFieldBool(const JsonObject& json, const char* key, bool& targetValue, const char* fieldName, const char* componentName) {
JsonVariantConst value = json[key];
if (value.is<bool>()) {
targetValue = value.as<bool>();
} else {
if (!value.isNull()) {
Log.traceln(F("[%s] WARN: '%s' in JSON is not bool. Using default: %s"),
componentName, fieldName, targetValue ? "true" : "false");
}
}
}
} // namespace JsonUtils
#endif // JSON_UTILS_H