From 79e1395fdc60de248712be2ed649c2f316ba772a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20=C3=96hrstr=C3=B6m?= Date: Sat, 10 Sep 2022 08:44:55 +0200 Subject: [PATCH] Print OPTIONAL fields that have previously received a value, even if the field is not part of the current telegram. Merge driver whe5x into qcaloric. --- CHANGES | 7 ++ simulations/simulation_c1.txt | 2 +- simulations/simulation_s1.txt | 2 +- src/driver_c5isf.cc | 4 +- src/driver_lse_07_17.cc | 2 +- src/driver_minomess.cc | 2 +- src/driver_qcaloric.cc | 18 ++++- src/driver_whe5x.cc | 126 ----------------------------- src/dvparser.cc | 60 ++++++++++++++ src/dvparser.h | 1 + src/meters.cc | 101 +++++++++++++++++++---- src/meters.h | 7 +- src/meters_common_implementation.h | 6 ++ 13 files changed, 187 insertions(+), 151 deletions(-) delete mode 100644 src/driver_whe5x.cc diff --git a/CHANGES b/CHANGES index f1b0490..90a26df 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ + +OPTIONAL fields were only printed in the json, if they appeared in the telegram, +even if the field had received a value before. Now a field will be printed +in the json whenever there is a value stored in the meter object. +I.e. an OPTIONAL field that has never received a value will not be printed. +A NON-OPTIONAL field that has never received a value will be printed with the value null. + Version: 1.9.0 2022-09-04 ATTENTION! The multical21 and flowiq drivers have been refactored to the new driver style. diff --git a/simulations/simulation_c1.txt b/simulations/simulation_c1.txt index 41cb7b2..0632e50 100644 --- a/simulations/simulation_c1.txt +++ b/simulations/simulation_c1.txt @@ -44,7 +44,7 @@ telegram=|314493441234567835087a740000200b6e2701004b6e450100426c5f2ccb086e790000 # Test another pair of QCalric C1 telegrams telegram=|49449344939291903408780DFF5F350082180000800007B06EFFFF970000009F2C70020000BE26970000000000010018002E001F002E0023FF210008000500020000002F046D220FA227| -{"media":"heat cost allocation","meter":"qcaloric","name":"MyElement2","id":"90919293","status":"OK","current_consumption_hca":null,"set_date":null,"consumption_at_set_date_hca":null,"set_date_1":null,"consumption_at_set_date_1_hca":null,"set_date_17":null,"consumption_at_set_date_17_hca":null,"error_date":null,"device_date_time":"2021-07-02 15:34","timestamp":"1111-11-11T11:11:11Z"} +{"media":"heat cost allocation","meter":"qcaloric","name":"MyElement2","id":"90919293","status":"OK","current_consumption_hca":null,"set_date":null,"consumption_at_set_date_hca":null,"set_date_1":null,"consumption_at_set_date_1_hca":null,"device_date_time":"2021-07-02 15:34","timestamp":"1111-11-11T11:11:11Z"} telegram=|314493449392919034087a520000200b6e9700004b6e700200426c9f2ccb086e970000c2086cbe26326cffff046d2d16a227| {"media":"heat cost allocation","meter":"qcaloric","name":"MyElement2","id":"90919293","status":"OK","current_consumption_hca":97,"set_date":"2020-12-31","consumption_at_set_date_hca":270,"set_date_1":"2020-12-31","consumption_at_set_date_1_hca":270,"set_date_17":"2021-06-30","consumption_at_set_date_17_hca":97,"error_date":"2127-15-31","device_date_time":"2021-07-02 22:45","timestamp":"1111-11-11T11:11:11Z"} diff --git a/simulations/simulation_s1.txt b/simulations/simulation_s1.txt index a7051ae..77c0a01 100644 --- a/simulations/simulation_s1.txt +++ b/simulations/simulation_s1.txt @@ -2,7 +2,7 @@ # Test a HCA WHE465 Qundis telegram=|244465323251839134087a4f0000000b6e0403004b6e660300426c9e29326cffff046d1416b921dd2f| -{"media":"heat cost allocation","meter":"whe5x","name":"HCA","id":"91835132","status":"OK","current_consumption_hca":304,"set_date":"2020-09-30","consumption_at_set_date_hca":366,"set_date_1":"2020-09-30","consumption_at_set_date_1_hca":366,"error_date":"2127-15-31","device_date_time":"2021-01-25 22:20","timestamp":"1111-11-11T11:11:11Z"} +{"media":"heat cost allocation","meter":"qcaloric","name":"HCA","id":"91835132","status":"OK","current_consumption_hca":304,"set_date":"2020-09-30","consumption_at_set_date_hca":366,"set_date_1":"2020-09-30","consumption_at_set_date_1_hca":366,"error_date":"2127-15-31","device_date_time":"2021-01-25 22:20","timestamp":"1111-11-11T11:11:11Z"} |HCA;91835132;304;2020-09-30;366;1111-11-11 11:11.11 # Test another HCA from Qundis diff --git a/src/driver_c5isf.cc b/src/driver_c5isf.cc index a4fc92d..41817c9 100644 --- a/src/driver_c5isf.cc +++ b/src/driver_c5isf.cc @@ -288,12 +288,12 @@ namespace // Type T1A2 telegram: // telegram=|DA44496A5555445588077A320200002F2F_04140000000084800114000000008280016C2124C480011400000080C280016CFFFF84810114000000808281016CFFFFC481011400000080C281016CFFFF84820114000000808282016CFFFFC482011400000080C282016CFFFF84830114000000808283016CFFFFC483011400000080C283016CFFFF84840114000000808284016CFFFFC484011400000080C284016CFFFF84850114000000808285016CFFFFC485011400000080C285016CFFFF84860114000000808286016CFFFFC486011400000080C286016CFFFF| -// {"media":"water","meter":"c5isf","name":"Heat","id":"55445555","total_energy_consumption_kwh":0,"total_volume_m3":0,"status":"ERROR REVERSE_FLOW SUPPLY_SENSOR_INTERRUPTED","prev_1_month":"2017-04-01","prev_2_month":"2127-15-31","prev_3_month":"2127-15-31","prev_4_month":"2127-15-31","prev_5_month":"2127-15-31","prev_6_month":"2127-15-31","prev_7_month":"2127-15-31","prev_8_month":"2127-15-31","prev_9_month":"2127-15-31","prev_10_month":"2127-15-31","prev_11_month":"2127-15-31","prev_12_month":"2127-15-31","prev_13_month":"2127-15-31","prev_14_month":"2127-15-31","prev_1_month_m3":0,"prev_2_month_m3":21474836.48,"prev_3_month_m3":21474836.48,"prev_4_month_m3":21474836.48,"prev_5_month_m3":21474836.48,"prev_6_month_m3":21474836.48,"prev_7_month_m3":21474836.48,"prev_8_month_m3":21474836.48,"prev_9_month_m3":21474836.48,"prev_10_month_m3":21474836.48,"prev_11_month_m3":21474836.48,"prev_12_month_m3":21474836.48,"prev_13_month_m3":21474836.48,"prev_14_month_m3":21474836.48,"timestamp":"1111-11-11T11:11:11Z"} +// {"media":"water","meter":"c5isf","name":"Heat","id":"55445555","total_energy_consumption_kwh":0,"total_volume_m3":0,"status":"ERROR REVERSE_FLOW SUPPLY_SENSOR_INTERRUPTED","prev_1_month":"2017-04-01","prev_2_month":"2127-15-31","prev_3_month":"2127-15-31","prev_4_month":"2127-15-31","prev_5_month":"2127-15-31","prev_6_month":"2127-15-31","prev_7_month":"2127-15-31","prev_8_month":"2127-15-31","prev_9_month":"2127-15-31","prev_10_month":"2127-15-31","prev_11_month":"2127-15-31","prev_12_month":"2127-15-31","prev_13_month":"2127-15-31","prev_14_month":"2127-15-31","prev_1_month_m3":0,"prev_2_month_m3":21474836.48,"prev_3_month_m3":21474836.48,"prev_4_month_m3":21474836.48,"prev_5_month_m3":21474836.48,"prev_6_month_m3":21474836.48,"prev_7_month_m3":21474836.48,"prev_8_month_m3":21474836.48,"prev_9_month_m3":21474836.48,"prev_10_month_m3":21474836.48,"prev_11_month_m3":21474836.48,"prev_12_month_m3":21474836.48,"prev_13_month_m3":21474836.48,"prev_14_month_m3":21474836.48,"total_energy_consumption_last_month_kwh":0,"timestamp":"1111-11-11T11:11:11Z"} // |Heat;55445555;0.000000;0.000000;ERROR REVERSE_FLOW SUPPLY_SENSOR_INTERRUPTED;1111-11-11 11:11.11 // Type T1B telegram: // telegram=|5E44496A5555445588047A0A0050052F2F_04061A0000000413C20800008404060000000082046CC121043BA4000000042D1900000002591216025DE21002FD17000084800106000000008280016CC121948001AE25000000002F2F2F2F2F2F| -// {"media":"heat","meter":"c5isf","name":"Heat","id":"55445555","total_energy_consumption_kwh":26,"total_volume_m3":2.242,"status":"OK","prev_1_month":"2022-01-01","prev_1_month_kwh":0,"due_energy_consumption_kwh":0,"due_date":"2022-01-01","volume_flow_m3h":0.164,"power_kw":2.5,"total_energy_consumption_last_month_kwh":0,"max_power_last_month_kw":0,"flow_temperature_c":56.5,"return_temperature_c":43.22,"timestamp":"1111-11-11T11:11:11Z"} +// {"media":"heat","meter":"c5isf","name":"Heat","id":"55445555","total_energy_consumption_kwh":26,"total_volume_m3":2.242,"status":"OK","prev_1_month":"2022-01-01","prev_2_month":"2127-15-31","prev_3_month":"2127-15-31","prev_4_month":"2127-15-31","prev_5_month":"2127-15-31","prev_6_month":"2127-15-31","prev_7_month":"2127-15-31","prev_8_month":"2127-15-31","prev_9_month":"2127-15-31","prev_10_month":"2127-15-31","prev_11_month":"2127-15-31","prev_12_month":"2127-15-31","prev_13_month":"2127-15-31","prev_14_month":"2127-15-31","prev_1_month_kwh":0,"prev_2_month_kwh":2147483648,"prev_3_month_kwh":2147483648,"prev_4_month_kwh":2147483648,"prev_5_month_kwh":2147483648,"prev_6_month_kwh":2147483648,"prev_7_month_kwh":2147483648,"prev_8_month_kwh":2147483648,"prev_9_month_kwh":2147483648,"prev_10_month_kwh":2147483648,"prev_11_month_kwh":2147483648,"prev_12_month_kwh":2147483648,"prev_13_month_kwh":2147483648,"prev_14_month_kwh":2147483648,"prev_2_month_m3":21474836.48,"prev_3_month_m3":21474836.48,"prev_4_month_m3":21474836.48,"prev_5_month_m3":21474836.48,"prev_6_month_m3":21474836.48,"prev_7_month_m3":21474836.48,"prev_8_month_m3":21474836.48,"prev_9_month_m3":21474836.48,"prev_10_month_m3":21474836.48,"prev_11_month_m3":21474836.48,"prev_12_month_m3":21474836.48,"prev_13_month_m3":21474836.48,"prev_14_month_m3":21474836.48,"due_energy_consumption_kwh":0,"due_date":"2022-01-01","volume_flow_m3h":0.164,"power_kw":2.5,"total_energy_consumption_last_month_kwh":0,"max_power_last_month_kw":0,"flow_temperature_c":56.5,"return_temperature_c":43.22,"timestamp":"1111-11-11T11:11:11Z"} // |Heat;55445555;26.000000;2.242000;OK;1111-11-11 11:11.11 // Test: Heat c5isf 32002044 NOKEY diff --git a/src/driver_lse_07_17.cc b/src/driver_lse_07_17.cc index 0b3bf37..3febbc8 100644 --- a/src/driver_lse_07_17.cc +++ b/src/driver_lse_07_17.cc @@ -204,5 +204,5 @@ namespace // |Water;13963399;nan;nan;null;null;null;2021-12-01 00:24;1111-11-11 11:11.11 // telegram=|2D4465329933961318067ADA000000_0C13567100004C1300000000426CFFFF02BB560000326CFFFF046D2307A12C| -// {"media":"warm water","meter":"lse_07_17","name":"Water","id":"13963399","total_m3":7.156,"due_date_m3":0,"due_date":"2127-15-31","error_code":"OK","error_date":"2127-15-31","device_date_time":"2021-12-01 07:35","timestamp":"1111-11-11T11:11:11Z"} +// {"media":"warm water","meter":"lse_07_17","name":"Water","id":"13963399","total_m3":7.156,"due_date_m3":0,"due_date":"2127-15-31","what_date_m3":7,"what_date":"2021-11-30","error_code":"OK","error_date":"2127-15-31","device_date_time":"2021-12-01 07:35","meter_version":"11","timestamp":"1111-11-11T11:11:11Z"} // |Water;13963399;7.156000;0.000000;2127-15-31;OK;2127-15-31;2021-12-01 07:35;1111-11-11 11:11.11 diff --git a/src/driver_minomess.cc b/src/driver_minomess.cc index cb54a07..4bda09b 100644 --- a/src/driver_minomess.cc +++ b/src/driver_minomess.cc @@ -66,7 +66,7 @@ namespace .set(StorageNr(8)) ); - /* The wire mbus telegram contains 4 totals and dates. For the moment we only print nr 1 which is the latest. */ + // The wire mbus telegram contains 4 totals and dates. For the moment we only print nr 1 which is the latest. addNumericFieldWithExtractor( "target", "The total water consumption recorded at the beginning of this month.", diff --git a/src/driver_qcaloric.cc b/src/driver_qcaloric.cc index 10d188b..e3019a8 100644 --- a/src/driver_qcaloric.cc +++ b/src/driver_qcaloric.cc @@ -27,10 +27,13 @@ namespace static bool ok = registerDriver([](DriverInfo&di) { di.setName("qcaloric"); + di.addNameAlias("whe5x"); di.setDefaultFields("name,id,current_consumption_hca,set_date,consumption_at_set_date_hca,timestamp"); di.setMeterType(MeterType::HeatCostAllocationMeter); di.addLinkMode(LinkMode::C1); di.addLinkMode(LinkMode::T1); + di.addLinkMode(LinkMode::S1); + di.addDetection(MANUFACTURER_LSE, 0x08, 0x34); di.addDetection(MANUFACTURER_LSE, 0x08, 0x35); di.addDetection(MANUFACTURER_QDS, 0x08, 0x35); di.addDetection(MANUFACTURER_QDS, 0x08, 0x34); @@ -104,7 +107,7 @@ namespace addStringFieldWithExtractor( "set_date_17", "The 17 billing period date.", - PrintProperty::JSON | PrintProperty::FIELD, + PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::OPTIONAL, FieldMatcher::build() .set(MeasurementType::Instantaneous) .set(VIFRange::Date) @@ -114,7 +117,7 @@ namespace addNumericFieldWithExtractor( "consumption_at_set_date_17", "Heat cost allocation at the 17 billing period date.", - PrintProperty::JSON | PrintProperty::FIELD, + PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::OPTIONAL, Quantity::HCA, VifScaling::Auto, FieldMatcher::build() @@ -126,7 +129,7 @@ namespace addStringFieldWithExtractor( "error_date", "Date when the meter entered an error state.", - PrintProperty::JSON, + PrintProperty::JSON | PrintProperty::OPTIONAL, FieldMatcher::build() .set(MeasurementType::AtError) .set(VIFRange::Date) @@ -153,7 +156,7 @@ namespace // Test: MyElement2 qcaloric 90919293 NOKEY // Comment: Test mostly proprietary telegram without values // telegram=|49449344939291903408780DFF5F350082180000800007B06EFFFF970000009F2C70020000BE26970000000000010018002E001F002E0023FF210008000500020000002F046D220FA227| -// {"media":"heat cost allocation","meter":"qcaloric","name":"MyElement2","id":"90919293","status":"OK","current_consumption_hca":null,"set_date":null,"consumption_at_set_date_hca":null,"set_date_1":null,"consumption_at_set_date_1_hca":null,"set_date_17":null,"consumption_at_set_date_17_hca":null,"error_date":null,"device_date_time":"2021-07-02 15:34","timestamp":"1111-11-11T11:11:11Z"} +// {"media":"heat cost allocation","meter":"qcaloric","name":"MyElement2","id":"90919293","status":"OK","current_consumption_hca":null,"set_date":null,"consumption_at_set_date_hca":null,"set_date_1":null,"consumption_at_set_date_1_hca":null,"device_date_time":"2021-07-02 15:34","timestamp":"1111-11-11T11:11:11Z"} // |MyElement2;90919293;null;null;null;1111-11-11 11:11.11 // Comment: Normal telegram that fills in values. @@ -165,3 +168,10 @@ namespace // telegram=|49449344939291903408780DFF5F350082180000800007B06EFFFF970000009F2C70020000BE26970000000000010018002E001F002E0023FF210008000500020000002F046D220FA228| // {"media":"heat cost allocation","meter":"qcaloric","name":"MyElement2","id":"90919293","status":"OK","current_consumption_hca":97,"set_date":"2020-12-31","consumption_at_set_date_hca":270,"set_date_1":"2020-12-31","consumption_at_set_date_1_hca":270,"set_date_17":"2021-06-30","consumption_at_set_date_17_hca":97,"error_date":"2127-15-31","device_date_time":"2021-08-02 15:34","timestamp":"1111-11-11T11:11:11Z"} // |MyElement2;90919293;97;2020-12-31;270;1111-11-11 11:11.11 + +// Comment: Another version of the heat cost allocator. But for historical reasons got its +// own driver name, which is now aliased to qcaloric. +// Test: HCA whe5x 91835132 NOKEY +// telegram=|244465323251839134087a4f0000000b6e0403004b6e660300426c9e29326cffff046d1416b921dd2f| +// {"media":"heat cost allocation","meter":"qcaloric","name":"HCA","id":"91835132","status":"OK","current_consumption_hca":304,"set_date":"2020-09-30","consumption_at_set_date_hca":366,"set_date_1":"2020-09-30","consumption_at_set_date_1_hca":366,"error_date":"2127-15-31","device_date_time":"2021-01-25 22:20","timestamp":"1111-11-11T11:11:11Z"} +// |HCA;91835132;304;2020-09-30;366;1111-11-11 11:11.11 diff --git a/src/driver_whe5x.cc b/src/driver_whe5x.cc deleted file mode 100644 index b7d3082..0000000 --- a/src/driver_whe5x.cc +++ /dev/null @@ -1,126 +0,0 @@ -/* - Copyright (C) 2019-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 . -*/ - -#include"meters_common_implementation.h" - -namespace -{ - struct Driver : public virtual MeterCommonImplementation - { - Driver(MeterInfo &mi, DriverInfo &di); - }; - - static bool ok = registerDriver([](DriverInfo&di) - { - di.setName("whe5x"); - di.setDefaultFields("name,id,current_consumption_hca,set_date,consumption_at_set_date_hca,timestamp"); - di.setMeterType(MeterType::HeatCostAllocationMeter); - di.addLinkMode(LinkMode::S1); - di.addDetection(MANUFACTURER_LSE, 0x08, 0x34); - - di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr(new Driver(mi, di)); }); - }); - - Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di) - { - addStringField( - "status", - "Meter status from tpl status field.", - PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT | - PrintProperty::STATUS | PrintProperty::JOIN_TPL_STATUS); - - addNumericFieldWithExtractor( - "current_consumption", - "The current temperature.", - PrintProperty::JSON | PrintProperty::FIELD, - Quantity::HCA, - VifScaling::Auto, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::HeatCostAllocation) - ); - - addStringFieldWithExtractor( - "set_date", - "The most recent billing period date.", - PrintProperty::JSON | PrintProperty::FIELD, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::Date) - .set(StorageNr(1)) - ); - - addNumericFieldWithExtractor( - "consumption_at_set_date", - "Heat cost allocation at the most recent billing period date.", - PrintProperty::JSON | PrintProperty::FIELD, - Quantity::HCA, - VifScaling::Auto, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::HeatCostAllocation) - .set(StorageNr(1)) - ); - - addStringFieldWithExtractor( - "set_date_1", - "The most recent billing period date.", - PrintProperty::JSON | PrintProperty::FIELD, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::Date) - .set(StorageNr(1)) - ); - - addNumericFieldWithExtractor( - "consumption_at_set_date_1", - "Heat cost allocation at the most recent billing period date.", - PrintProperty::JSON | PrintProperty::FIELD, - Quantity::HCA, - VifScaling::Auto, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::HeatCostAllocation) - .set(StorageNr(1)) - ); - - addStringFieldWithExtractor( - "error_date", - "Date when the meter entered an error state.", - PrintProperty::JSON, - FieldMatcher::build() - .set(MeasurementType::AtError) - .set(VIFRange::Date) - ); - - addStringFieldWithExtractor( - "device_date_time", - "Date and time when the meter sent the telegram.", - PrintProperty::JSON, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::DateTime) - ); - - } -} - - -// Test: HCA whe5x 91835132 NOKEY -// telegram=|244465323251839134087a4f0000000b6e0403004b6e660300426c9e29326cffff046d1416b921dd2f| -// {"media":"heat cost allocation","meter":"whe5x","name":"HCA","id":"91835132","status":"OK","current_consumption_hca":304,"set_date":"2020-09-30","consumption_at_set_date_hca":366,"set_date_1":"2020-09-30","consumption_at_set_date_1_hca":366,"error_date":"2127-15-31","device_date_time":"2021-01-25 22:20","timestamp":"1111-11-11T11:11:11Z"} -// |HCA;91835132;304;2020-09-30;366;1111-11-11 11:11.11 diff --git a/src/dvparser.cc b/src/dvparser.cc index 1e37d59..39d40fb 100644 --- a/src/dvparser.cc +++ b/src/dvparser.cc @@ -1225,3 +1225,63 @@ const char *toString(MeasurementType mt) } return "?"; } + +string FieldMatcher::str() +{ + string s = ""; + + if (match_dif_vif_key) + { + s = s+"DVK("+dif_vif_key.str()+") "; + } + + if (match_measurement_type) + { + s = s+"MT("+toString(measurement_type)+") "; + } + + if (match_vif_range) + { + s = s+"VR("+toString(vif_range)+") "; + } + + if (vif_combinables.size() > 0) + { + s += "Comb("; + + for (auto vc : vif_combinables) + { + s = s+toString(vc)+" "; + } + + s.pop_back(); + s += ")"; + } + + if (match_storage_nr) + { + s = s+"S("+to_string(storage_nr_from.intValue())+"-"+to_string(storage_nr_to.intValue())+") "; + } + + if (match_tariff_nr) + { + s = s+"T("+to_string(tariff_nr_from.intValue())+"-"+to_string(tariff_nr_to.intValue())+") "; + } + + if (match_subunit_nr) + { + s += "U("+to_string(subunit_nr_from.intValue())+"-"+to_string(subunit_nr_to.intValue())+") "; + } + + if (index_nr.intValue() != 1) + { + s += "I("+to_string(index_nr.intValue())+")"; + } + + if (s.size() > 0) + { + s.pop_back(); + } + + return s; +} diff --git a/src/dvparser.h b/src/dvparser.h index 6b5fbf4..9307914 100644 --- a/src/dvparser.h +++ b/src/dvparser.h @@ -430,6 +430,7 @@ struct FieldMatcher FieldMatcher &set(IndexNr i) { index_nr = i; return *this; } bool matches(DVEntry &dv_entry); + std::string str(); }; bool loadFormatBytesFromSignature(uint16_t format_signature, std::vector *format_bytes); diff --git a/src/meters.cc b/src/meters.cc index 7afafed..a403c4b 100644 --- a/src/meters.cc +++ b/src/meters.cc @@ -1820,6 +1820,27 @@ void MeterCommonImplementation::setNumericValue(FieldInfo *fi, Unit u, double v) numeric_values_[pair(field_name_no_unit,fi->xuantity())] = NumericField(u, v, fi); } +bool MeterCommonImplementation::hasValue(FieldInfo *fi) +{ + return hasStringValue(fi) || hasNumericValue(fi); +} + +bool MeterCommonImplementation::hasNumericValue(FieldInfo *fi) +{ + if (fi->hasGetNumericValueOverride()) return true; + + pair key(fi->vname(),fi->xuantity()); + + return numeric_values_.count(key) != 0; +} + +bool MeterCommonImplementation::hasStringValue(FieldInfo *fi) +{ + if (fi->hasGetStringValueOverride()) return true; + + return string_values_.count(fi->vname()) != 0; +} + double MeterCommonImplementation::getNumericValue(FieldInfo *fi, Unit to) { if (fi->hasGetNumericValueOverride()) @@ -2033,12 +2054,14 @@ void MeterCommonImplementation::printMeter(Telegram *t, } // Iterate over the meter field infos... + map found; // Found from the telegram + set found_vnames; + for (FieldInfo& fi : field_infos_) { if (fi.printProperties().hasJSON()) { // The field should be printed in the json. (Most usually should.) - bool found = false; for (auto& i : t->dv_entries) { // Check each telegram dv entry. @@ -2046,24 +2069,50 @@ void MeterCommonImplementation::printMeter(Telegram *t, // Has the entry been matches to this field, then print it as json. if (dve->hasFieldInfo(&fi)) { - debug("(meters) render field %s(%s)[%d] with dventry @%d key %s data %s\n", - fi.vname().c_str(), toString(fi.xuantity()), fi.index(), - dve->offset, - dve->dif_vif_key.str().c_str(), - dve->value.c_str()); + assert(found.count(&fi) == 0); + found[&fi] = dve; + found_vnames.insert(fi.vname()); + } + } + } + } + + for (FieldInfo& fi : field_infos_) + { + if (fi.printProperties().hasJSON()) + { + if (found.count(&fi) != 0) + { + DVEntry *dve = found[&fi]; + debug("(meters) render field %s(%s)[%d] with dventry @%d key %s data %s\n", + fi.vname().c_str(), toString(fi.xuantity()), fi.index(), + dve->offset, + dve->dif_vif_key.str().c_str(), + dve->value.c_str()); + string out = fi.renderJson(this, &conversions()); + debug("(meters) %s\n", out.c_str()); + s += indent+out+","+newline; + } + else + { + // Ok, no value found in received telegram. + // Print field anyway, if it is not OPTIONAL + // or if a value has been received before and this field has not been received using a different rule. + // Why this complicated rule? + // E.g. the minmoess mbus seems to use storage 1 for target_m3 but the wmbus version uses storage 8. + // I.e. we have two rules that store into target_m3, this check will prevent target_m3 from being printed twice. + if (!fi.printProperties().hasOPTIONAL() || (found_vnames.count(fi.vname()) == 0 && hasValue(&fi))) + { + // No telegram entries found, but this field should be printed anyway. + // It will be printed with any value received from a previous telegram. + // Or if no value has been received, null. + debug("(meters) render field %s(%s)[%d] without dventry\n", + fi.vname().c_str(), toString(fi.xuantity()), fi.index()); string out = fi.renderJson(this, &conversions()); debug("(meters) %s\n", out.c_str()); s += indent+out+","+newline; - found = true; } } - if (!found && !fi.printProperties().hasOPTIONAL()) - { - // No telegram entries found, but this field should be printed anyway. - // It will be printed with any value received from a previous telegram. - // Or if no value has been received, null. - s += indent+fi.renderJson(this, &conversions())+","+newline; - } } } @@ -2492,6 +2541,19 @@ bool FieldInfo::matches(DVEntry *dve) return matcher_.matches(*dve); } +string FieldInfo::str() +{ + // 15 target Volume x� :� Auto XUZ "The total water consumption recorded at the beginning of this month." + return tostrprintf("%d %s_%s (%s) %s [%s] \"%s\"", + index_, + vname_.c_str(), + unitToStringLowerCase(default_unit_).c_str(), + toString(xuantity_), + toString(vif_scaling_), + matcher_.str().c_str(), + help_.c_str()); +} + DriverName MeterInfo::driverName() { if (driver_name.str() == "") @@ -2923,6 +2985,17 @@ void MeterCommonImplementation::addOptionalFlowRelatedFields() .set(MeasurementType::Instantaneous) .set(VIFRange::VolumeFlow) ); +} +const char *toString(VifScaling s) +{ + switch (s) + { + case VifScaling::None: return "None"; + case VifScaling::Auto: return "Auto"; + case VifScaling::NoneSigned: return "NoneSigned"; + case VifScaling::AutoSigned: return "AutoSigned"; } + return "?"; +} diff --git a/src/meters.h b/src/meters.h index cf5cc4e..9b48b0d 100644 --- a/src/meters.h +++ b/src/meters.h @@ -288,6 +288,8 @@ enum class VifScaling AutoSigned // Scale and assume the value is signed. }; +const char* toString(VifScaling s); + enum PrintProperty { JSON = 1, // This field should be printed when using --format=json @@ -382,10 +384,13 @@ struct FieldInfo // The vname is then a pattern total_at_month_{storagenr-32} that gets translated into // total_at_month_2 (for the dventry with storage nr 34.) string generateFieldName(DVEntry *dve); - + // Check if the meter object stores a value for this field. + bool hasValue(Meter *m); Translate::Lookup& lookup() { return lookup_; } + string str(); + private: int index_; // The field infos for a meter are ordered. diff --git a/src/meters_common_implementation.h b/src/meters_common_implementation.h index 4ac0c00..ad4dcef 100644 --- a/src/meters_common_implementation.h +++ b/src/meters_common_implementation.h @@ -231,6 +231,12 @@ protected: double getNumericValue(FieldInfo *fi, Unit u); void setStringValue(FieldInfo *fi, std::string v); std::string getStringValue(FieldInfo *fi); + + // Check if the meter has received a value for this field. + bool hasValue(FieldInfo *fi); + bool hasNumericValue(FieldInfo *fi); + bool hasStringValue(FieldInfo *fi); + std::string decodeTPLStatusByte(uchar sts); void addOptionalCommonFields();