pull/645/head^2
Fredrik Öhrström 2022-11-01 21:15:28 +01:00
rodzic aba13bd5d9
commit ab23cc4212
10 zmienionych plików z 123 dodań i 7 usunięć

Wyświetl plik

@ -485,6 +485,18 @@ static shared_ptr<Configuration> parseNormalCommandLine(Configuration *c, int ar
i++;
continue;
}
if (!strncmp(argv[i], "--calculate_", 12))
{
// For example: --calculate_adjusted_kwh='total_kwh + 12345 kwh'
string extra_calculated_field = string(argv[i]+12);
if (extra_calculated_field == "") {
error("The calculated field command cannot be empty.\n");
}
debug("(cmdline) add calculated field %s\n", extra_calculated_field.c_str());
c->extra_calculated_fields.push_back(extra_calculated_field);
i++;
continue;
}
if (!strncmp(argv[i], "--listenvs=", 11)) {
c->list_shell_envs = true;
c->list_meter = string(argv[i]+11);

Wyświetl plik

@ -59,6 +59,7 @@ void parseMeterConfig(Configuration *c, vector<char> &buf, string file)
vector<string> telegram_shells;
vector<string> alarm_shells;
vector<string> extra_constant_fields;
vector<string> extra_calculated_fields;
vector<string> selected_fields;
debug("(config) loading meter file %s\n", file.c_str());
@ -152,6 +153,12 @@ void parseMeterConfig(Configuration *c, vector<char> &buf, string file)
string keyvalue = p.first.substr(off)+"="+p.second;
extra_constant_fields.push_back(keyvalue);
}
else
if (startsWith(p.first, "calculate_"))
{
string keyvalue = p.first.substr(10)+"="+p.second;
extra_calculated_fields.push_back(keyvalue);
}
else
warning("Found invalid key \"%s\" in meter config file\n", p.first.c_str());
@ -195,6 +202,7 @@ void parseMeterConfig(Configuration *c, vector<char> &buf, string file)
}
if (use) {
mi.extra_constant_fields = extra_constant_fields;
mi.extra_calculated_fields = extra_calculated_fields;
mi.shells = telegram_shells;
mi.idsc = toIdsCommaSeparated(mi.ids);
mi.selected_fields = selected_fields;

Wyświetl plik

@ -136,6 +136,7 @@ struct Configuration
std::vector<std::string> selected_fields;
std::vector<MeterInfo> meters;
std::vector<std::string> extra_constant_fields; // Additional constant fields to always add to json.
std::vector<std::string> extra_calculated_fields; // Additional calculated fields to always add to json.
// These extra constant fields can also be part of selected with selectfields.
std::vector<SendBusContent> send_bus_content; // Telegrams used to wake up a meter for reading or mbus read-out requests.
std::set<BusDeviceType> probe_for; // Which devices should be probed for? DEVICE_AUTO means all.

Wyświetl plik

@ -133,13 +133,15 @@ namespace
.set(DifVifKey("04FB82F53C"))
);
/*
addNumericFieldWithCalculator(
"total_apparent_energy_consumption",
"Calculated: the total apparent energy consumption.",
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
Quantity::Energy,
Quantity::Apparent_Energy,
"total_energy_consumption_kwh + 99 kwh"
);
*/
}
}

Wyświetl plik

@ -454,9 +454,10 @@ void setup_log_file(Configuration *config)
void setup_meters(Configuration *config, MeterManager *manager)
{
for (auto &m : config->meters)
for (MeterInfo &m : config->meters)
{
m.conversions = config->conversions;
m.extra_calculated_fields = config->extra_calculated_fields;
if (m.usesPolling() || driverNeedsPolling(m.driver, m.driver_name))
{

Wyświetl plik

@ -738,10 +738,12 @@ MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi,
{
hex2bin(mi.key, &meter_keys_.confidentiality_key);
}
for (auto s : mi.shells) {
for (auto s : mi.shells)
{
addShell(s);
}
for (auto j : mi.extra_constant_fields) {
for (auto j : mi.extra_constant_fields)
{
addExtraConstantField(j);
}
}
@ -765,10 +767,12 @@ MeterCommonImplementation::MeterCommonImplementation(MeterInfo &mi,
{
hex2bin(mi.key, &meter_keys_.confidentiality_key);
}
for (auto s : mi.shells) {
for (auto s : mi.shells)
{
addShell(s);
}
for (auto j : mi.extra_constant_fields) {
for (auto j : mi.extra_constant_fields)
{
addExtraConstantField(j);
}
@ -794,6 +798,39 @@ void MeterCommonImplementation::addExtraConstantField(string ecf)
extra_constant_fields_.push_back(ecf);
}
void MeterCommonImplementation::addExtraCalculatedField(string ecf)
{
verbose("(meter) Adding calculated field: %s\n", ecf.c_str());
vector<string> parts = splitString(ecf, '=');
if (parts.size() != 2)
{
warning("Invalid formula for calculated field. %s\n", ecf.c_str());
return;
}
string vname;
Unit unit;
bool ok = extractUnit(parts[0], &vname, &unit);
if (!ok)
{
warning("Could not extract a valid unit from calculated field name %s\n", parts[0].c_str());
return;
}
Quantity quantity = toQuantity(unit);
addNumericFieldWithCalculator(
vname,
"Calculated: "+ecf,
PrintProperty::JSON | PrintProperty::FIELD,
quantity,
parts[1]
);
}
vector<string> &MeterCommonImplementation::shellCmdlines()
{
return shell_cmdlines_;
@ -965,6 +1002,15 @@ void MeterCommonImplementation::addNumericFieldWithCalculator(string vname,
{
Formula *f = newFormula();
bool ok = f->parse(this, formula);
if (!ok)
{
string err = f->errors();
warning("Warning! Ignoring calculated field %s because parse failed:\n%s",
vname.c_str(),
err.c_str());
delete f;
return;
}
assert(ok);
field_infos_.push_back(
@ -2417,6 +2463,10 @@ shared_ptr<Meter> createMeter(MeterInfo *mi)
{
shared_ptr<Meter> newm = di->construct(*mi);
newm->addConversions(mi->conversions);
for (string &j : mi->extra_calculated_fields)
{
newm->addExtraCalculatedField(j);
}
newm->setPollInterval(mi->poll_interval);
if (mi->selected_fields.size() > 0)
{

Wyświetl plik

@ -159,6 +159,7 @@ struct MeterInfo
int bps {}; // For mbus communication you need to know the baud rate.
vector<string> shells;
vector<string> extra_constant_fields; // Additional static fields that are added to each message.
vector<string> extra_calculated_fields; // Additional field calculated using formulas.
vector<Unit> conversions; // Additional units desired in json.
vector<string> selected_fields; // Usually set to the default fields, but can be override in meter config.
@ -172,7 +173,7 @@ struct MeterInfo
string str();
DriverName driverName();
MeterInfo(string b, string n, MeterDriver d, string e, vector<string> i, string k, LinkModeSet lms, int baud, vector<string> &s, vector<string> &j)
MeterInfo(string b, string n, MeterDriver d, string e, vector<string> i, string k, LinkModeSet lms, int baud, vector<string> &s, vector<string> &j, vector<string> &calcfs)
{
bus = b;
name = n;
@ -183,6 +184,7 @@ struct MeterInfo
key = k;
shells = s;
extra_constant_fields = j;
extra_calculated_fields = calcfs;
link_modes = lms;
bps = baud;
}
@ -197,6 +199,7 @@ struct MeterInfo
key = "";
shells.clear();
extra_constant_fields.clear();
extra_calculated_fields.clear();
link_modes.clear();
bps = 0;
}
@ -471,6 +474,7 @@ struct Meter
virtual MeterKeys *meterKeys() = 0;
virtual void addConversions(std::vector<Unit> cs) = 0;
virtual void addExtraCalculatedField(std::string ecf) = 0;
virtual vector<Unit>& conversions() = 0;
virtual void addShell(std::string cmdline) = 0;
virtual vector<string> &shellCmdlines() = 0;

Wyświetl plik

@ -67,6 +67,7 @@ struct MeterCommonImplementation : public virtual Meter
void setPollInterval(time_t interval);
time_t pollInterval();
bool usesPolling();
void addExtraCalculatedField(std::string ef);
void onUpdate(function<void(Telegram*,Meter*)> cb);
int numUpdates();

Wyświetl plik

@ -84,6 +84,9 @@ if [ "$?" != "0" ]; then RC="1"; fi
tests/test_conversions.sh $PROG
if [ "$?" != "0" ]; then RC="1"; fi
tests/test_formulas.sh $PROG
if [ "$?" != "0" ]; then RC="1"; fi
tests/test_fields.sh $PROG
if [ "$?" != "0" ]; then RC="1"; fi

Wyświetl plik

@ -0,0 +1,34 @@
#!/bin/sh
PROG="$1"
rm -rf testoutput
mkdir -p testoutput
TEST=testoutput
TESTNAME="Test formulas"
TESTRESULT="ERROR"
$PROG --format=json \
--calculate_sumtemp_c='external_temperature_c+flow_temperature_c' \
--calculate_addtemp_c='external_temperature_c + 1100 c' \
23442D2C998734761B168D2087D19EAD217F1779EDA86AB6_710008190000081900007F13 \
MyTapWater multical21 76348799 "" \
> $TEST/test_output.txt
cat > $TEST/test_expected.txt <<EOF
{"media":"cold water","meter":"multical21","name":"MyTapWater","id":"76348799","status":"DRY","total_m3":6.408,"target_m3":6.408,"flow_temperature_c":127,"external_temperature_c":19,"current_status":"DRY","time_dry":"22-31 days","time_reversed":"","time_leaking":"","time_bursting":"","sumtemp_c":146,"addtemp_c":1119,"timestamp":"1111-11-11T11:11:11Z"}
EOF
if [ "$?" = "0" ]
then
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
diff $TEST/test_expected.txt $TEST/test_responses.txt
if [ "$?" = "0" ]
then
echo "OK: $TESTNAME"
TESTRESULT="OK"
fi
fi
if [ "$TESTRESULT" = "ERROR" ]; then echo ERROR: $TESTNAME; exit 1; fi