[Formula] Apply formula when updating taskvalues

This commit is contained in:
TD-er
2023-12-09 18:02:57 +01:00
parent 1b6e16df3b
commit ef79b3bb7c
10 changed files with 223 additions and 107 deletions
+2 -1
View File
@@ -19,6 +19,7 @@
#define PLUGIN_VALUENAME1_152 "Output"
#define P152_DAC_VALUE UserVar[event->BaseVarIndex]
#define P152_SET_DAC_VALUE(x) UserVar.setFloat(event->TaskIndex, 0, x)
#if !(defined(ESP32_CLASSIC) || defined(ESP32S2))
# error P152 ESP32 DAC not supported on this CPU type!
@@ -94,7 +95,7 @@ boolean Plugin_152(uint8_t function, struct EventStruct *event, String& string)
if (getDAC_gpio_info(CONFIG_PIN1, dac) && (dac == event->Par1)) {
int value = min(255, max(0, event->Par2)); // Limit value
P152_DAC_VALUE = value;
P152_SET_DAC_VALUE(value);
dacWrite(CONFIG_PIN1, value); // Set output value
addLog(LOG_LEVEL_INFO,
formatGpioName_DAC(CONFIG_PIN1) +
+17 -3
View File
@@ -151,6 +151,20 @@ String Caches::getTaskDeviceValueName(taskIndex_t TaskIndex, uint8_t rel_index)
return EMPTY_STRING;
}
bool Caches::hasFormula(taskIndex_t TaskIndex, uint8_t rel_index)
{
if (validTaskIndex(TaskIndex)) {
// Just a quick test to see if we do have a formula present.
// Task Formula are not used very often, so we will probably almost always have to return an empty string.
auto it = getExtraTaskSettings(TaskIndex);
if (it != extraTaskSettings_cache.end()) {
return bitRead(it->second.hasFormula, rel_index);
}
}
return false;
}
bool Caches::hasFormula(taskIndex_t TaskIndex)
{
if (validTaskIndex(TaskIndex)) {
@@ -159,7 +173,7 @@ bool Caches::hasFormula(taskIndex_t TaskIndex)
auto it = getExtraTaskSettings(TaskIndex);
if (it != extraTaskSettings_cache.end()) {
return it->second.hasFormula;
return it->second.hasFormula != 0;
}
}
return false;
@@ -167,7 +181,7 @@ bool Caches::hasFormula(taskIndex_t TaskIndex)
String Caches::getTaskDeviceFormula(taskIndex_t TaskIndex, uint8_t rel_index)
{
if ((rel_index < VARS_PER_TASK) && hasFormula(TaskIndex)) {
if ((rel_index < VARS_PER_TASK) && bitRead(hasFormula(TaskIndex), rel_index)) {
#ifdef ESP32
auto it = getExtraTaskSettings(TaskIndex);
@@ -297,7 +311,7 @@ void Caches::updateExtraTaskSettingsCache()
#ifdef ESP32
move_special(tmp.TaskDeviceFormula[i], String(ExtraTaskSettings.TaskDeviceFormula[i]));
#endif
tmp.hasFormula = true;
bitSet(tmp.hasFormula, i);
}
tmp.decimals[i] = ExtraTaskSettings.TaskDeviceValueDecimals[i];
#if FEATURE_PLUGIN_STATS
+3 -2
View File
@@ -51,7 +51,7 @@ struct ExtraTaskSettings_cache_t {
#if FEATURE_PLUGIN_STATS
PluginStats_Config_t pluginStatsConfig[VARS_PER_TASK] = {};
#endif // if FEATURE_PLUGIN_STATS
bool hasFormula = false;
uint8_t hasFormula = 0; // Bitmap which task value has formula
};
typedef std::map<String, taskIndex_t> TaskIndexNameMap;
@@ -86,7 +86,8 @@ 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);
bool hasFormula(taskIndex_t TaskIndex, uint8_t rel_index);
bool hasFormula(taskIndex_t TaskIndex);
String getTaskDeviceFormula(taskIndex_t TaskIndex,
+182 -52
View File
@@ -1,9 +1,16 @@
#include "../DataStructs/UserVarStruct.h"
#include "../DataStructs/TimingStats.h"
#include "../ESPEasyCore/ESPEasy_Log.h"
#include "../Globals/Cache.h"
#include "../Globals/Plugins.h"
#include "../Globals/RulesCalculate.h"
#include "../Helpers/_Plugin_SensorTypeHelper.h"
#include "../Helpers/CRC_functions.h"
#include "../Helpers/StringParser.h"
UserVarStruct::UserVarStruct()
{
@@ -23,38 +30,43 @@ void UserVarStruct::clear()
// Implementation of [] operator. This function must return a
// reference as array element can be put on left side
/*
float& UserVarStruct::operator[](unsigned int index)
float& UserVarStruct::operator[](unsigned int index)
{
const unsigned int taskIndex = index / VARS_PER_TASK;
const unsigned int varNr = index % VARS_PER_TASK;
if (taskIndex >= _data.size()) {
static float errorvalue = NAN;
addLog(LOG_LEVEL_ERROR, F("UserVar index out of range"));
return errorvalue;
}
return _data.at(taskIndex).floats[varNr];
}
*/
float UserVarStruct::operator[](unsigned int index) const
{
const unsigned int taskIndex = index / VARS_PER_TASK;
const unsigned int varNr = index % VARS_PER_TASK;
if (taskIndex >= _data.size()) {
const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr);
if (data != nullptr) {
return data->getFloat(varNr);
} else {
static float errorvalue = NAN;
addLog(LOG_LEVEL_ERROR, F("UserVar index out of range"));
return errorvalue;
}
return _data.at(taskIndex).floats[varNr];
}
*/
const float& UserVarStruct::operator[](unsigned int index) const
{
const unsigned int taskIndex = index / VARS_PER_TASK;
const unsigned int varNr = index % VARS_PER_TASK;
if (taskIndex >= _data.size()) {
static float errorvalue = NAN;
addLog(LOG_LEVEL_ERROR, F("UserVar index out of range"));
return errorvalue;
}
return _data.at(taskIndex).floats[varNr];
}
unsigned long UserVarStruct::getSensorTypeLong(taskIndex_t taskIndex) const
{
if (taskIndex < _data.size()) {
return _data[taskIndex].getSensorTypeLong();
const TaskValues_Data_t *data = getRawOrComputed(taskIndex, 0);
if (data != nullptr) {
return data->getSensorTypeLong();
}
return 0u;
}
@@ -62,7 +74,12 @@ unsigned long UserVarStruct::getSensorTypeLong(taskIndex_t taskIndex) const
void UserVarStruct::setSensorTypeLong(taskIndex_t taskIndex, unsigned long value)
{
if (taskIndex < _data.size()) {
_data[taskIndex].setSensorTypeLong(value);
if (Cache.hasFormula(taskIndex, 0)) {
const ESPEASY_RULES_FLOAT_TYPE tmp = value;
applyFormula(taskIndex, 0, tmp, Sensor_VType::SENSOR_TYPE_ULONG);
} else {
_data[taskIndex].setSensorTypeLong(value);
}
}
}
@@ -71,8 +88,10 @@ void UserVarStruct::setSensorTypeLong(taskIndex_t taskIndex, unsigned long value
int32_t UserVarStruct::getInt32(taskIndex_t taskIndex,
uint8_t varNr) const
{
if (taskIndex < _data.size()) {
return _data[taskIndex].getInt32(varNr);
const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr);
if (data != nullptr) {
return data->getInt32(varNr);
}
return 0;
}
@@ -82,7 +101,12 @@ void UserVarStruct::setInt32(taskIndex_t taskIndex,
int32_t value)
{
if (taskIndex < _data.size()) {
_data[taskIndex].setInt32(varNr, value);
if (Cache.hasFormula(taskIndex, varNr)) {
const ESPEASY_RULES_FLOAT_TYPE tmp = value;
applyFormula(taskIndex, varNr, tmp, Sensor_VType::SENSOR_TYPE_INT32_QUAD);
} else {
_data[taskIndex].setInt32(varNr, value);
}
}
}
@@ -90,8 +114,10 @@ void UserVarStruct::setInt32(taskIndex_t taskIndex,
uint32_t UserVarStruct::getUint32(taskIndex_t taskIndex, uint8_t varNr) const
{
if (taskIndex < _data.size()) {
return _data[taskIndex].getUint32(varNr);
const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr);
if (data != nullptr) {
return data->getUint32(varNr);
}
return 0u;
}
@@ -99,7 +125,12 @@ uint32_t UserVarStruct::getUint32(taskIndex_t taskIndex, uint8_t varNr) const
void UserVarStruct::setUint32(taskIndex_t taskIndex, uint8_t varNr, uint32_t value)
{
if (taskIndex < _data.size()) {
_data[taskIndex].setUint32(varNr, value);
if (Cache.hasFormula(taskIndex, varNr)) {
const ESPEASY_RULES_FLOAT_TYPE tmp = value;
applyFormula(taskIndex, varNr, tmp, Sensor_VType::SENSOR_TYPE_UINT32_QUAD);
} else {
_data[taskIndex].setUint32(varNr, value);
}
}
}
@@ -108,8 +139,10 @@ void UserVarStruct::setUint32(taskIndex_t taskIndex, uint8_t varNr, uint32_t val
int64_t UserVarStruct::getInt64(taskIndex_t taskIndex,
uint8_t varNr) const
{
if (taskIndex < _data.size()) {
return _data[taskIndex].getInt64(varNr);
const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr);
if (data != nullptr) {
return data->getInt64(varNr);
}
return 0;
}
@@ -119,15 +152,22 @@ void UserVarStruct::setInt64(taskIndex_t taskIndex,
int64_t value)
{
if (taskIndex < _data.size()) {
_data[taskIndex].setInt64(varNr, value);
if (Cache.hasFormula(taskIndex, varNr)) {
const ESPEASY_RULES_FLOAT_TYPE tmp = value;
applyFormula(taskIndex, varNr, tmp, Sensor_VType::SENSOR_TYPE_INT64_DUAL);
} else {
_data[taskIndex].setInt64(varNr, value);
}
}
}
uint64_t UserVarStruct::getUint64(taskIndex_t taskIndex,
uint8_t varNr) const
{
if (taskIndex < _data.size()) {
return _data[taskIndex].getUint64(varNr);
const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr);
if (data != nullptr) {
return data->getUint64(varNr);
}
return 0u;
}
@@ -137,7 +177,12 @@ void UserVarStruct::setUint64(taskIndex_t taskIndex,
uint64_t value)
{
if (taskIndex < _data.size()) {
_data[taskIndex].setUint64(varNr, value);
if (Cache.hasFormula(taskIndex, varNr)) {
const ESPEASY_RULES_FLOAT_TYPE tmp = value;
applyFormula(taskIndex, varNr, tmp, Sensor_VType::SENSOR_TYPE_UINT64_DUAL);
} else {
_data[taskIndex].setUint64(varNr, value);
}
}
}
@@ -146,8 +191,10 @@ void UserVarStruct::setUint64(taskIndex_t taskIndex,
float UserVarStruct::getFloat(taskIndex_t taskIndex,
uint8_t varNr) const
{
if (taskIndex < _data.size()) {
return _data[taskIndex].getFloat(varNr);
const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr);
if (data != nullptr) {
return data->getFloat(varNr);
}
return 0.0f;
}
@@ -157,17 +204,24 @@ void UserVarStruct::setFloat(taskIndex_t taskIndex,
float value)
{
if (taskIndex < _data.size()) {
_data[taskIndex].setFloat(varNr, value);
if (Cache.hasFormula(taskIndex, varNr)) {
const ESPEASY_RULES_FLOAT_TYPE tmp = value;
applyFormula(taskIndex, varNr, tmp, Sensor_VType::SENSOR_TYPE_QUAD);
} else {
_data[taskIndex].setFloat(varNr, value);
}
}
}
#if FEATURE_EXTENDED_TASK_VALUE_TYPES
#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
double UserVarStruct::getDouble(taskIndex_t taskIndex,
uint8_t varNr) const
{
if (taskIndex < _data.size()) {
return _data[taskIndex].getDouble(varNr);
const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr);
if (data != nullptr) {
return data->getDouble(varNr);
}
return 0.0;
}
@@ -177,43 +231,52 @@ void UserVarStruct::setDouble(taskIndex_t taskIndex,
double value)
{
if (taskIndex < _data.size()) {
_data[taskIndex].setDouble(varNr, value);
if (Cache.hasFormula(taskIndex, varNr)) {
applyFormula(taskIndex, varNr, value, Sensor_VType::SENSOR_TYPE_DOUBLE_DUAL);
} else {
_data[taskIndex].setDouble(varNr, value);
}
}
}
#endif
#endif // if FEATURE_EXTENDED_TASK_VALUE_TYPES
# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
#endif // if FEATURE_EXTENDED_TASK_VALUE_TYPES
ESPEASY_RULES_FLOAT_TYPE UserVarStruct::getAsDouble(taskIndex_t taskIndex,
uint8_t varNr,
Sensor_VType sensorType) const
uint8_t varNr,
Sensor_VType sensorType) const
{
if (taskIndex < _data.size()) {
return _data[taskIndex].getAsDouble(varNr, sensorType);
const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr);
if (data != nullptr) {
return data->getAsDouble(varNr, sensorType);
}
return 0.0;
}
String UserVarStruct::getAsString(taskIndex_t taskIndex, uint8_t varNr, Sensor_VType sensorType, uint8_t nrDecimals) const
{
if (taskIndex < _data.size()) {
return _data[taskIndex].getAsString(varNr, sensorType, nrDecimals);
const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr);
if (data != nullptr) {
return data->getAsString(varNr, sensorType, nrDecimals);
}
return EMPTY_STRING;
}
void UserVarStruct::set(taskIndex_t taskIndex, uint8_t varNr, const ESPEASY_RULES_FLOAT_TYPE& value, Sensor_VType sensorType)
{
if (taskIndex < _data.size()) {
_data[taskIndex].set(varNr, value, sensorType);
}
applyFormula(taskIndex, varNr, value, sensorType);
}
bool UserVarStruct::isValid(taskIndex_t taskIndex,
uint8_t varNr,
Sensor_VType sensorType) const
{
if (taskIndex < _data.size()) {
return _data[taskIndex].isValid(varNr, sensorType);
const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr);
if (data != nullptr) {
return data->isValid(varNr, sensorType);
}
return false;
}
@@ -247,3 +310,70 @@ uint32_t UserVarStruct::compute_CRC32() const
return calc_CRC32(buffer, size);
}
void UserVarStruct::clear_computed(taskIndex_t taskIndex)
{
if (!Cache.hasFormula(taskIndex)) {
auto it = _computed.find(taskIndex);
if (it != _computed.end()) {
_computed.erase(it);
}
}
}
const TaskValues_Data_t * UserVarStruct::getRawOrComputed(taskIndex_t taskIndex, uint8_t varNr) const
{
if (Cache.hasFormula(taskIndex, varNr)) {
auto it = _computed.find(taskIndex);
if (it != _computed.end()) {
return &(it->second);
}
}
return getTaskValues_Data(taskIndex);
}
void UserVarStruct::applyFormula(taskIndex_t taskIndex,
uint8_t varNr,
const ESPEASY_RULES_FLOAT_TYPE& value,
Sensor_VType sensorType)
{
if ((taskIndex >= _data.size()) || (varNr >= VARS_PER_TASK)) {
return;
}
String formula = Cache.getTaskDeviceFormula(taskIndex, varNr);
if (!formula.isEmpty())
{
START_TIMER;
// TD-er: Should we use the set nr of decimals here, or not round at all?
// See: https://github.com/letscontrolit/ESPEasy/issues/3721#issuecomment-889649437
const int nrDecimals = Cache.getTaskDeviceValueDecimals(taskIndex, varNr);
if (formula.indexOf(F("%pvalue%")) != -1) {
formula.replace(F("%pvalue%"), getAsString(taskIndex, varNr, sensorType, nrDecimals));
}
if (formula.indexOf(F("%value%")) != -1)
{
// Use a temporary TaskValues_Data_t object to have uniform formatting
TaskValues_Data_t tmp;
tmp.set(varNr, value, sensorType);
formula.replace(F("%value%"), tmp.getAsString(varNr, sensorType, nrDecimals));
}
ESPEASY_RULES_FLOAT_TYPE result{};
if (!isError(Calculate(parseTemplate(formula), result))) {
_computed[taskIndex].set(varNr, result, sensorType);
} else {
// FIXME TD-er: What to do now? Just copy the raw value, set error value or don't update?
}
STOP_TIMER(COMPUTE_FORMULA_STATS);
}
_data[taskIndex].set(varNr, value, sensorType);
}
+10 -1
View File
@@ -20,7 +20,7 @@ struct UserVarStruct {
/*
float & operator[](unsigned int index);
*/
const float & operator[](unsigned int index) const;
float operator[](unsigned int index) const;
// Legacy "long" type, which was spread over several floats.
unsigned long getSensorTypeLong(taskIndex_t taskIndex) const;
@@ -104,8 +104,17 @@ struct UserVarStruct {
uint32_t compute_CRC32() const;
void clear_computed(taskIndex_t taskIndex);
private:
const TaskValues_Data_t* getRawOrComputed(taskIndex_t taskIndex, uint8_t varNr) const;
void applyFormula(taskIndex_t taskIndex,
uint8_t varNr,
const ESPEASY_RULES_FLOAT_TYPE& value,
Sensor_VType sensorType);
// Raw TaskValues data as stored in RTC
std::vector<TaskValues_Data_t>_data;
+2 -47
View File
@@ -634,53 +634,8 @@ void SensorSendTask(struct EventStruct *event, unsigned long timestampUnixTime,
TempEvent.timestamp = timestampUnixTime;
checkDeviceVTypeForTask(&TempEvent);
const uint8_t valueCount = getValueCountForTask(event->TaskIndex);
// Store the previous value, in case %pvalue% is used in the formula
String preValue[VARS_PER_TASK];
const bool processFormula = Device[DeviceIndex].FormulaOption && Cache.hasFormula(event->TaskIndex);
if (processFormula) {
for (uint8_t varNr = 0; varNr < valueCount; varNr++)
{
const String formula = Cache.getTaskDeviceFormula(event->TaskIndex, varNr);
if (!formula.isEmpty())
{
if (formula.indexOf(F("%pvalue%")) != -1) {
preValue[varNr] = formatUserVarNoCheck(&TempEvent, varNr);
}
}
}
}
{
String dummy;
success = PluginCall(PLUGIN_READ, &TempEvent, dummy);
}
if (success)
{
if (processFormula) {
for (uint8_t varNr = 0; varNr < valueCount; varNr++)
{
String formula = Cache.getTaskDeviceFormula(event->TaskIndex, varNr);
if (!formula.isEmpty())
{
START_TIMER;
// TD-er: Should we use the set nr of decimals here, or not round at all?
// See: https://github.com/letscontrolit/ESPEasy/issues/3721#issuecomment-889649437
formula.replace(F("%pvalue%"), preValue[varNr]);
formula.replace(F("%value%"), formatUserVarNoCheck(&TempEvent, varNr));
ESPEASY_RULES_FLOAT_TYPE result{};
if (!isError(Calculate(parseTemplate(formula), result))) {
UserVar.set(event->TaskIndex, varNr, result, TempEvent.sensorType);
}
STOP_TIMER(COMPUTE_FORMULA_STATS);
}
}
}
String dummy;
if (PluginCall(PLUGIN_READ, &TempEvent, dummy)) {
sendData(&TempEvent);
}
}
+1
View File
@@ -896,6 +896,7 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str)
// Each of these may update ExtraTaskSettings, but it may not have been saved yet.
// Thus update the cache just in case something from it is requested from the cache.
Cache.updateExtraTaskSettingsCache();
UserVar.clear_computed(event->TaskIndex);
}
if (Function == PLUGIN_SET_DEFAULTS) {
saveUserVarToRTC();
+3
View File
@@ -31,6 +31,7 @@
#include "../Globals/Plugins.h"
#include "../Globals/RTC.h"
#include "../Globals/ResetFactoryDefaultPref.h"
#include "../Globals/RuntimeData.h"
#include "../Globals/SecuritySettings.h"
#include "../Globals/Settings.h"
#include "../Globals/WiFi_AP_Candidates.h"
@@ -1093,6 +1094,7 @@ String SaveTaskSettings(taskIndex_t TaskIndex)
err = checkTaskSettings(TaskIndex);
}
#endif
UserVar.clear_computed(ExtraTaskSettings.TaskIndex);
}
#ifndef LIMIT_BUILD_SIZE
else {
@@ -1152,6 +1154,7 @@ String LoadTaskSettings(taskIndex_t TaskIndex)
ExtraTaskSettings.validate();
Cache.updateExtraTaskSettingsCache_afterLoad_Save();
UserVar.clear_computed(ExtraTaskSettings.TaskIndex);
STOP_TIMER(LOAD_TASK_SETTINGS);
return result;
+1 -1
View File
@@ -293,7 +293,7 @@ void P109_data_struct::saveThermoSettings(struct EventStruct *event) {
fs::File f = tryOpenFile(fileName, F("w"));
if (f) {
f.write(reinterpret_cast<const uint8_t *>(&UserVar[event->BaseVarIndex]), 16);
f.write(reinterpret_cast<const uint8_t *>(UserVar.getTaskValues_Data(event->TaskIndex)), 16);
f.close();
flashCount();
}
+2
View File
@@ -169,6 +169,7 @@ void handle_devices() {
// N.B. When calling delete, the settings were already saved.
if (nosave) {
Cache.updateExtraTaskSettingsCache();
UserVar.clear_computed(taskIndex);
} else {
addHtmlError(SaveTaskSettings(taskIndex));
addHtmlError(SaveSettings());
@@ -457,6 +458,7 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task
CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION, &TempEvent, dummy);
}
}
UserVar.clear_computed(taskIndex);
}