diff --git a/src/meter_hydrocalm3.cc b/src/meter_hydrocalm3.cc new file mode 100644 index 0000000..e89572f --- /dev/null +++ b/src/meter_hydrocalm3.cc @@ -0,0 +1,298 @@ +/* + Copyright (C) 2018-2020 Fredrik Öhrström + 2020 Eric Bus + + 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 . +*/ + +#include"meters.h" +#include"meters_common_implementation.h" +#include"dvparser.h" +#include"wmbus.h" +#include"wmbus_utils.h" +#include"util.h" + +#define INFO_CODE_VOLTAGE_INTERRUPTED 1 +#define INFO_CODE_LOW_BATTERY_LEVEL 2 +#define INFO_CODE_EXTERNAL_ALARM 4 +#define INFO_CODE_SENSOR_T1_ABOVE_MEASURING_RANGE 8 +#define INFO_CODE_SENSOR_T2_ABOVE_MEASURING_RANGE 16 +#define INFO_CODE_SENSOR_T1_BELOW_MEASURING_RANGE 32 +#define INFO_CODE_SENSOR_T2_BELOW_MEASURING_RANGE 64 +#define INFO_CODE_TEMP_DIFF_WRONG_POLARITY 128 + +struct MeterHydrocalM3 : public virtual HeatMeter, public virtual MeterCommonImplementation { + MeterHydrocalM3(MeterInfo &mi); + + double totalEnergyConsumption(Unit u); + string status(); + double totalVolume(Unit u); + double volumeFlow(Unit u); + + // Water temperatures + double t1Temperature(Unit u); + bool hasT1Temperature(); + double t2Temperature(Unit u); + bool hasT2Temperature(); + +private: + void processContent(Telegram *t); + + uchar info_codes_ {}; + double total_energy_kwh_ {}; + double total_volume_m3_ {}; + double volume_flow_m3h_ {}; + double t1_temperature_c_ { 127 }; + bool has_t1_temperature_ {}; + double t2_temperature_c_ { 127 }; + bool has_t2_temperature_ {}; + string target_date_ {}; + + uint32_t energy_forward_kwh_ {}; + uint32_t energy_returned_kwh_ {}; +}; + +MeterHydrocalM3::MeterHydrocalM3(MeterInfo &mi) : + MeterCommonImplementation(mi, MeterType::HYDROCALM3) +{ + setExpectedELLSecurityMode(ELLSecurityMode::AES_CTR); + + addLinkMode(LinkMode::T1); + + addPrint("total_energy_consumption", Quantity::Energy, + [&](Unit u){ return totalEnergyConsumption(u); }, + "The total energy consumption recorded by this meter.", + true, true); + + addPrint("total_volume", Quantity::Volume, + [&](Unit u){ return totalVolume(u); }, + "Total volume of media.", + true, true); + + addPrint("volume_flow", Quantity::Flow, + [&](Unit u){ return volumeFlow(u); }, + "The current flow.", + true, true); + + addPrint("t1_temperature", Quantity::Temperature, + [&](Unit u){ return t1Temperature(u); }, + "The T1 temperature.", + true, true); + + addPrint("t2_temperature", Quantity::Temperature, + [&](Unit u){ return t2Temperature(u); }, + "The T2 temperature.", + true, true); + + addPrint("at_date", Quantity::Text, + [&](){ return target_date_; }, + "Date when total energy consumption was recorded.", + false, true); + + addPrint("current_status", Quantity::Text, + [&](){ return status(); }, + "Status of meter.", + true, true); + + addPrint("energy_forward_kwh", Quantity::Text, + [&](){ return to_string(energy_forward_kwh_); }, + "Energy forward.", + false, true); + + addPrint("energy_returned_kwh", Quantity::Text, + [&](){ return to_string(energy_returned_kwh_); }, + "Energy returned.", + false, true); + +} + +shared_ptr createHydrocalM3(MeterInfo &mi) { + return shared_ptr(new MeterHydrocalM3(mi)); +} + +double MeterHydrocalM3::totalEnergyConsumption(Unit u) +{ + assertQuantity(u, Quantity::Energy); + return convert(total_energy_kwh_, Unit::KWH, u); +} + +double MeterHydrocalM3::totalVolume(Unit u) +{ + assertQuantity(u, Quantity::Volume); + return convert(total_volume_m3_, Unit::M3, u); +} + +double MeterHydrocalM3::t1Temperature(Unit u) +{ + assertQuantity(u, Quantity::Temperature); + return convert(t1_temperature_c_, Unit::C, u); +} + +bool MeterHydrocalM3::hasT1Temperature() +{ + return has_t1_temperature_; +} + +double MeterHydrocalM3::t2Temperature(Unit u) +{ + assertQuantity(u, Quantity::Temperature); + return convert(t2_temperature_c_, Unit::C, u); +} + +bool MeterHydrocalM3::hasT2Temperature() +{ + return has_t2_temperature_; +} + +double MeterHydrocalM3::volumeFlow(Unit u) +{ + assertQuantity(u, Quantity::Flow); + return convert(volume_flow_m3h_, Unit::M3H, u); +} + +void MeterHydrocalM3::processContent(Telegram *t) +{ + /* + (multical603) 13: 78 tpl-ci-field (EN 13757-3 Application Layer (no tplh)) + (multical603) 14: 04 dif (32 Bit Integer/Binary Instantaneous value) + (multical603) 15: 06 vif (Energy kWh) + (multical603) 16: * A5000000 total energy consumption (165.000000 kWh) + (multical603) 1a: 04 dif (32 Bit Integer/Binary Instantaneous value) + (multical603) 1b: FF vif (Vendor extension) + (multical603) 1c: 07 vife (?) + (multical603) 1d: 2B010000 + (multical603) 21: 04 dif (32 Bit Integer/Binary Instantaneous value) + (multical603) 22: FF vif (Vendor extension) + (multical603) 23: 08 vife (?) + (multical603) 24: 9C000000 + (multical603) 28: 04 dif (32 Bit Integer/Binary Instantaneous value) + (multical603) 29: 14 vif (Volume 10⁻² m³) + (multical603) 2a: * 21020000 total volume (5.450000 m3) + (multical603) 2e: 04 dif (32 Bit Integer/Binary Instantaneous value) + (multical603) 2f: 3B vif (Volume flow l/h) + (multical603) 30: * 12000000 volume flow (0.018000 m3/h) + (multical603) 34: 02 dif (16 Bit Integer/Binary Instantaneous value) + (multical603) 35: 59 vif (Flow temperature 10⁻² °C) + (multical603) 36: * D014 T1 flow temperature (53.280000 °C) + (multical603) 38: 02 dif (16 Bit Integer/Binary Instantaneous value) + (multical603) 39: 5D vif (Return temperature 10⁻² °C) + (multical603) 3a: * 0009 T2 flow temperature (23.040000 °C) + (multical603) 3c: 04 dif (32 Bit Integer/Binary Instantaneous value) + (multical603) 3d: FF vif (Vendor extension) + (multical603) 3e: 22 vife (per hour) + (multical603) 3f: * 00000000 info codes () + + (supercom587) 00: 8e length (142 bytes) + (supercom587) 01: 44 dll-c (from meter SND_NR) + (supercom587) 02: b409 dll-mfct (BMT) + (supercom587) 04: 71493602 dll-id (02364971) + (supercom587) 08: 0b dll-version + (supercom587) 09: 0d dll-type (Heat/Cooling load meter) + (supercom587) 0a: 7a tpl-ci-field (EN 13757-3 Application Layer (short tplh)) + (supercom587) 0b: b6 tpl-acc-field + (supercom587) 0c: 00 tpl-sts-field + (supercom587) 0d: 8005 tpl-cfg 0580 (AES_CBC_IV nb=8 cntn=0 ra=0 hc=0 ) + (supercom587) 0f: 2f2f decrypt check bytes + (supercom587) 11: 0C dif (8 digit BCD Instantaneous value) + (supercom587) 12: 0E vif (Energy MJ) + (supercom587) 13: 00000000 + (supercom587) 17: 04 dif (32 Bit Integer/Binary Instantaneous value) + (supercom587) 18: 6D vif (Date and time type) + (supercom587) 19: 112D872C + (supercom587) 1d: 0C dif (8 digit BCD Instantaneous value) + (supercom587) 1e: 13 vif (Volume l) + (supercom587) 1f: * 00000000 total consumption (0.000000 m3) + (supercom587) 23: 0C dif (8 digit BCD Instantaneous value) + (supercom587) 24: 0E vif (Energy MJ) + (supercom587) 25: 00000000 + (supercom587) 29: 0C dif (8 digit BCD Instantaneous value) + (supercom587) 2a: 13 vif (Volume l) + (supercom587) 2b: 00000000 + (supercom587) 2f: 0C dif (8 digit BCD Instantaneous value) + (supercom587) 30: 13 vif (Volume l) + (supercom587) 31: 00000000 + (supercom587) 35: 0C dif (8 digit BCD Instantaneous value) + (supercom587) 36: 13 vif (Volume l) + (supercom587) 37: 00000000 + (supercom587) 3b: 0A dif (4 digit BCD Instantaneous value) + (supercom587) 3c: 5A vif (Flow temperature 10⁻¹ °C) + (supercom587) 3d: 9400 + (supercom587) 3f: 0A dif (4 digit BCD Instantaneous value) + (supercom587) 40: 5E vif (Return temperature 10⁻¹ °C) + (supercom587) 41: 9500 + (supercom587) 43: 0F manufacturer specific data + +*/ + int offset; + string key; + + extractDVuint8(&t->values, "04FF22", &offset, &info_codes_); + t->addMoreExplanation(offset, " info codes (%s)", status().c_str()); + + extractDVuint32(&t->values, "04FF07", &offset, &energy_forward_kwh_); + t->addMoreExplanation(offset, " something A (%zu)", energy_forward_kwh_); + + extractDVuint32(&t->values, "04FF08", &offset, &energy_returned_kwh_); + t->addMoreExplanation(offset, " something B (%zu)", energy_returned_kwh_); + + if(findKey(MeasurementType::Instantaneous, ValueInformation::EnergyWh, 0, 0, &key, &t->values)) { + extractDVdouble(&t->values, key, &offset, &total_energy_kwh_); + t->addMoreExplanation(offset, " total energy consumption (%f kWh)", total_energy_kwh_); + } + + if(findKey(MeasurementType::Instantaneous, ValueInformation::Volume, 0, 0, &key, &t->values)) { + extractDVdouble(&t->values, key, &offset, &total_volume_m3_); + t->addMoreExplanation(offset, " total volume (%f m3)", total_volume_m3_); + } + + if(findKey(MeasurementType::Unknown, ValueInformation::VolumeFlow, 0, 0, &key, &t->values)) { + extractDVdouble(&t->values, key, &offset, &volume_flow_m3h_); + t->addMoreExplanation(offset, " volume flow (%f m3/h)", volume_flow_m3h_); + } + + if(findKey(MeasurementType::Instantaneous, ValueInformation::FlowTemperature, 0, 0, &key, &t->values)) { + has_t1_temperature_ = extractDVdouble(&t->values, key, &offset, &t1_temperature_c_); + t->addMoreExplanation(offset, " T1 flow temperature (%f °C)", t1_temperature_c_); + } + + if(findKey(MeasurementType::Instantaneous, ValueInformation::ReturnTemperature, 0, 0, &key, &t->values)) { + has_t2_temperature_ = extractDVdouble(&t->values, key, &offset, &t2_temperature_c_); + t->addMoreExplanation(offset, " T2 flow temperature (%f °C)", t2_temperature_c_); + } + + if (findKey(MeasurementType::Unknown, ValueInformation::Date, 0, 0, &key, &t->values)) { + struct tm datetime; + extractDVdate(&t->values, key, &offset, &datetime); + target_date_ = strdatetime(&datetime); + t->addMoreExplanation(offset, " target date (%s)", target_date_.c_str()); + } +} + +string MeterHydrocalM3::status() +{ + string s; + if (info_codes_ & INFO_CODE_VOLTAGE_INTERRUPTED) s.append("VOLTAGE_INTERRUPTED "); + if (info_codes_ & INFO_CODE_LOW_BATTERY_LEVEL) s.append("LOW_BATTERY_LEVEL "); + if (info_codes_ & INFO_CODE_EXTERNAL_ALARM) s.append("EXTERNAL_ALARM "); + if (info_codes_ & INFO_CODE_SENSOR_T1_ABOVE_MEASURING_RANGE) s.append("SENSOR_T1_ABOVE_MEASURING_RANGE "); + if (info_codes_ & INFO_CODE_SENSOR_T2_ABOVE_MEASURING_RANGE) s.append("SENSOR_T2_ABOVE_MEASURING_RANGE "); + if (info_codes_ & INFO_CODE_SENSOR_T1_BELOW_MEASURING_RANGE) s.append("SENSOR_T1_BELOW_MEASURING_RANGE "); + if (info_codes_ & INFO_CODE_SENSOR_T2_BELOW_MEASURING_RANGE) s.append("SENSOR_T2_BELOW_MEASURING_RANGE "); + if (info_codes_ & INFO_CODE_TEMP_DIFF_WRONG_POLARITY) s.append("TEMP_DIFF_WRONG_POLARITY "); + if (s.length() > 0) { + s.pop_back(); // Remove final space + return s; + } + return s; +}