Added Enercal F2 meter.

pull/587/head
Fredrik Öhrström 2022-08-09 15:22:47 +02:00
rodzic a80471689b
commit 09fa9ae7d4
4 zmienionych plików z 305 dodań i 31 usunięć

Wyświetl plik

@ -1,4 +1,6 @@
Added Enercal F2 heat meter.
Paulo Rossi added support for the AMB3665-M wmbus dongle for N-mode 169 MHz telegrams. Thanks Paulo!
Version 1.8.0: 2022-06-25

Wyświetl plik

@ -472,6 +472,7 @@ Heat and Cooling meter Kamstrup Multical 602 (multical602) (in C1 mode)
Heat and Cooling meter Kamstrup Multical 603 (multical603) (in C1 mode)
Heat and Cooling meter Kamstrup Multical 803 (multical803) (in C1 mode)
Heat meter Apator Elf (elf)
Heat meter Enercal F2 (enercal)
Heat meter Diehl Sharky 775 (sharky)
Heat meter Diehl Sharky 774 (sharky774)
Heat meter Maddelena microClima (microclima)

Wyświetl plik

@ -0,0 +1,248 @@
/*
Copyright (C) 2022 Fredrik Öhrström (gpl-3.0-or-later)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include"meters_common_implementation.h"
namespace
{
struct Driver : public virtual MeterCommonImplementation {
Driver(MeterInfo &mi, DriverInfo &di);
};
static bool ok = registerDriver([](DriverInfo&di)
{
di.setName("enercal");
di.setMeterType(MeterType::HeatMeter);
di.addLinkMode(LinkMode::MBUS);
di.addDetection(MANUFACTURER_GWF, 0x04, 0x08);
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
});
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
{
addStringField(
"status",
"Meter error flags.",
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT |
PrintProperty::STATUS | PrintProperty::JOIN_TPL_STATUS);
addNumericFieldWithExtractor(
"total",
"The total energy consumption recorded by this meter.",
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
Quantity::Energy,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::AnyEnergyVIF)
);
addNumericFieldWithExtractor(
"target",
"The energy consumption recorded by this meter at the set date.",
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT,
Quantity::Energy,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::AnyEnergyVIF)
.set(StorageNr(1))
);
addNumericFieldWithExtractor(
"power",
"The active power consumption.",
PrintProperty::JSON | PrintProperty::OPTIONAL,
Quantity::Power,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::AnyPowerVIF)
);
addNumericFieldWithExtractor(
"flow",
"The flow of water.",
PrintProperty::JSON | PrintProperty::OPTIONAL,
Quantity::Flow,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::VolumeFlow)
);
addNumericFieldWithExtractor(
"flow_max",
"The maximum forward flow of water since the last set date?",
PrintProperty::JSON | PrintProperty::OPTIONAL,
Quantity::Flow,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Maximum)
.set(VIFRange::VolumeFlow)
);
addNumericFieldWithExtractor(
"forward",
"The forward temperature of the water.",
PrintProperty::JSON | PrintProperty::OPTIONAL,
Quantity::Temperature,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::FlowTemperature)
);
addNumericFieldWithExtractor(
"return",
"The return temperature of the water.",
PrintProperty::JSON | PrintProperty::OPTIONAL,
Quantity::Temperature,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::ReturnTemperature)
);
addNumericFieldWithExtractor(
"difference",
"The temperature difference forward-return for the water.",
PrintProperty::JSON | PrintProperty::OPTIONAL,
Quantity::Temperature,
VifScaling::AutoSigned,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::TemperatureDifference)
);
addNumericFieldWithExtractor(
"total",
"The total amount of water that has passed through this meter.",
PrintProperty::JSON | PrintProperty::FIELD,
Quantity::Volume,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::Volume)
);
addNumericFieldWithExtractor(
"target",
"The amount of water that had passed through this meter at the set date.",
PrintProperty::JSON | PrintProperty::FIELD,
Quantity::Volume,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::Volume)
.set(StorageNr(1))
);
addNumericFieldWithExtractor(
"subunit1",
"The amount of water that has passed through subunit 1.",
PrintProperty::JSON,
Quantity::Volume,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::Volume)
.set(SubUnitNr(1))
);
addNumericFieldWithExtractor(
"subunit1_target",
"The amount of water that had passed through the subunit 1 at the set date.",
PrintProperty::JSON,
Quantity::Volume,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::Volume)
.set(StorageNr(1))
.set(SubUnitNr(1))
);
addNumericFieldWithExtractor(
"subunit1",
"The current heat cost allocation for subunit 1.",
PrintProperty::JSON | PrintProperty::OPTIONAL,
Quantity::HCA,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::HeatCostAllocation)
.set(SubUnitNr(1))
);
addNumericFieldWithExtractor(
"subunit1_target",
"The heat cost allocation for subunit 1 at the target date.",
PrintProperty::JSON | PrintProperty::OPTIONAL,
Quantity::HCA,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::HeatCostAllocation)
.set(SubUnitNr(1))
.set(StorageNr(1))
);
addNumericFieldWithExtractor(
"subunit2",
"The current heat cost allocation for subunit 2.",
PrintProperty::JSON | PrintProperty::OPTIONAL,
Quantity::HCA,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::HeatCostAllocation)
.set(SubUnitNr(2))
);
addNumericFieldWithExtractor(
"subunit2_target",
"The heat cost allocation for subunit 2 at the target date.",
PrintProperty::JSON | PrintProperty::OPTIONAL,
Quantity::HCA,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::HeatCostAllocation)
.set(SubUnitNr(2))
.set(StorageNr(1))
);
addStringFieldWithExtractor(
"target_date",
"The most recent billing period date.",
PrintProperty::JSON | PrintProperty::OPTIONAL,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::Date)
.set(StorageNr(1))
);
addOptionalCommonFields();
}
}
// Test: Heat enercal 00309924 NOKEY
// Comment:
// telegram=|688e8e6808017224993000e61e080406000000040681460600041488f2350084401426e02600025B2600025f220002622a00042247880200042647880200043B00000000042c00000000046d2d0ede2784406e000000008480406e00000000c4800006B1450600c48000143Be43500c4c000142fd22600c4c0006e00000000c480406e00000000c280006cc1271f00000000f416|
// {"media":"heat","meter":"enercal","name":"Heat","id":"00309924","status":"OK","total_kwh":411265,"target_kwh":411057,"power_kw":0,"flow_m3h":0,"forward_c":38,"return_c":34,"difference_c":4.2,"total_m3":35354.96,"target_m3":35318.35,"subunit1_m3":25477.5,"subunit1_target_m3":25441.75,"subunit1_hca":0,"subunit1_target_hca":0,"subunit2_hca":0,"subunit2_target_hca":0,"target_date":"2022-07-01","operating_time_h":46.099722,"on_time_h":46.099722,"meter_datetime":"2022-07-30 14:45","timestamp":"1111-11-11T11:11:11Z"}
// |Heat;00309924;OK;411265.000000;411057.000000;35354.960000;35318.350000;1111-11-11 11:11.11

Wyświetl plik

@ -1728,20 +1728,23 @@ bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<ucha
void MeterCommonImplementation::processFieldExtractors(Telegram *t)
{
// Iterate through the dv_entries in the telegram and trigger any
// field specification with a matcher.
// Iterate through the data content (dv_entries9 in the telegram.
for (auto &p : t->dv_entries)
{
DVEntry *dve = &p.second.second;
// We have telegram content, a dif-vif-value entry.
// Now check for a field info that wants to handle this telegram content entry.
for (FieldInfo &fi : field_infos_)
{
if (fi.hasMatcher() && fi.matches(dve))
{
// We have field that wants to handle this entry!
debug("(meters) using field info %s(%s)[%d] to extract %s\n",
fi.vname().c_str(),
toString(fi.xuantity()),
fi.index(),
dve->dif_vif_key.str().c_str());
dve->addFieldInfo(&fi);
fi.performExtraction(this, t, dve);
}
@ -2503,6 +2506,31 @@ bool FieldInfo::extractNumeric(Meter *m, Telegram *t, DVEntry *dve)
return found;
}
static string add_tpl_status(string existing_status, Meter *m, Telegram *t)
{
string status = m->decodeTPLStatusByte(t->tpl_sts);
t->addMoreExplanation(t->tpl_sts_offset, "(%s)", status.c_str());
if (status != "OK")
{
if (existing_status != "OK")
{
// Join the statuses.
existing_status += " "+status;
}
else
{
// Overwrite OK.
existing_status = status;
}
}
else
{
// No change to the existing_status
}
return existing_status;
}
bool FieldInfo::extractString(Meter *m, Telegram *t, DVEntry *dve)
{
bool found = false;
@ -2512,16 +2540,29 @@ bool FieldInfo::extractString(Meter *m, Telegram *t, DVEntry *dve)
{
if (key == "")
{
// Search for key.
bool ok = findKeyWithNr(matcher_.measurement_type,
matcher_.vif_range,
matcher_.storage_nr_from.intValue(),
matcher_.tariff_nr_from.intValue(),
matcher_.index_nr.intValue(),
&key,
&t->dv_entries);
// No entry was found.
if (!ok) return false;
if (!hasMatcher())
{
// There is no matcher, only use case is to capture JOIN_TPL_STATUS.
if (print_properties_.hasJOINTPLSTATUS())
{
string status = add_tpl_status("OK", m, t);
m->setStringValue(this, status);
return true;
}
}
else
{
// Search for key.
bool ok = findKeyWithNr(matcher_.measurement_type,
matcher_.vif_range,
matcher_.storage_nr_from.intValue(),
matcher_.tariff_nr_from.intValue(),
matcher_.index_nr.intValue(),
&key,
&t->dv_entries);
// No entry was found.
if (!ok) return false;
}
}
// No entry with this key was found.
if (t->dv_entries.count(key) == 0) return false;
@ -2547,25 +2588,7 @@ bool FieldInfo::extractString(Meter *m, Telegram *t, DVEntry *dve)
if (print_properties_.hasJOINTPLSTATUS())
{
string status = m->decodeTPLStatusByte(t->tpl_sts);
t->addMoreExplanation(t->tpl_sts_offset, "(%s)", status.c_str());
if (status != "OK")
{
if (translated_bits != "OK")
{
// Join the statuses.
translated_bits += " "+status;
}
else
{
// Overwrite the translated bits.
translated_bits = status;
}
}
else
{
// No change to the translated_bits.
}
translated_bits = add_tpl_status(translated_bits, m, t);
}
if (found)