diff --git a/CHANGES b/CHANGES index 033e961..ee1fc49 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,15 @@ +ATTENTION! When values are missing in the fields output, they were previously +reported as "nan" but they are now reported as "null". + +ATTENTION! The unismart driver has been refactored to the new driver format. +Several fields were unknown before and are still unknown and their content +have slightly changed: status and version. + +The field "other_counter" is replaced with just "other" and its content changed. +The field "suppler_info" is replaced with "supplier_info". +The field "device_date_time" is replaced with "device_timestamp" and now contains seconds +and a timezone. + ATTENTION! A bug was fixed where the on_time_h and operating_time_h (and related error times) were wrong. diff --git a/simulations/simulation_t1.txt b/simulations/simulation_t1.txt index d6dc789..012250c 100644 --- a/simulations/simulation_t1.txt +++ b/simulations/simulation_t1.txt @@ -29,14 +29,14 @@ telegram=|1844AE4C4455223368077A55000000_041389E20100023B0000| # Test amiplus/apator electricity meter telegram=|4E4401061010101002027A00004005_2F2F0E035040691500000B2B300300066D00790C7423400C78371204860BABC8FC100000000E833C8074000000000BAB3C0000000AFDC9FC0136022F2F2F2F2F| -{"media":"electricity","meter":"amiplus","name":"MyElectricity1","id":"10101010","total_energy_consumption_kwh":15694.05,"current_power_consumption_kw":0.33,"total_energy_production_kwh":7.48,"current_power_production_kw":0,"voltage_at_phase_1_v":236,"voltage_at_phase_2_v":null,"voltage_at_phase_3_v":null,"device_date_time":"2019-03-20 12:57","total_energy_consumption_tariff_1_kwh":null,"total_energy_consumption_tariff_2_kwh":null,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":null,"total_energy_production_tariff_2_kwh":null,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"} -|MyElectricity1;10101010;15694.050000;0.330000;7.480000;0.000000;236.000000;nan;nan;nan;nan;nan;nan;nan;nan;1111-11-11 11:11.11 +{"media":"electricity","meter":"amiplus","name":"MyElectricity1","id":"10101010","total_energy_consumption_kwh":15694.05,"current_power_consumption_kw":0.33,"total_energy_production_kwh":7.48,"current_power_production_kw":0,"voltage_at_phase_1_v":236,"voltage_at_phase_2_v":null,"voltage_at_phase_3_v":null,"device_date_time":"2019-03-20 12:57:00","total_energy_consumption_tariff_1_kwh":null,"total_energy_consumption_tariff_2_kwh":null,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":null,"total_energy_production_tariff_2_kwh":null,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"} +|MyElectricity1;10101010;15694.05;0.33;7.48;0;236;null;null;null;null;null;null;null;null;1111-11-11 11:11.11 # Test amiplus/apator electricity meter with three phase voltages telegram=|5E44B6105843250000027A2A005005_2F2F0C7835221400066D404708AC2A400E032022650900000E833C0000000000001B2B9647000B2B5510000BAB3C0000000AFDC9FC0135020AFDC9FC0245020AFDC9FC0339020BABC8FC100000002F2F| -{"media":"electricity","meter":"amiplus","name":"MyElectricity2","id":"00254358","total_energy_consumption_kwh":9652.22,"current_power_consumption_kw":1.055,"total_energy_production_kwh":0,"current_power_production_kw":0,"voltage_at_phase_1_v":235,"voltage_at_phase_2_v":245,"voltage_at_phase_3_v":239,"device_date_time":"2021-10-12 08:07","total_energy_consumption_tariff_1_kwh":null,"total_energy_consumption_tariff_2_kwh":null,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":null,"total_energy_production_tariff_2_kwh":null,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"} -|MyElectricity2;00254358;9652.220000;1.055000;0.000000;0.000000;235.000000;245.000000;239.000000;nan;nan;nan;nan;nan;nan;1111-11-11 11:11.11 +{"media":"electricity","meter":"amiplus","name":"MyElectricity2","id":"00254358","total_energy_consumption_kwh":9652.22,"current_power_consumption_kw":1.055,"total_energy_production_kwh":0,"current_power_production_kw":0,"voltage_at_phase_1_v":235,"voltage_at_phase_2_v":245,"voltage_at_phase_3_v":239,"device_date_time":"2021-10-12 08:07:00","total_energy_consumption_tariff_1_kwh":null,"total_energy_consumption_tariff_2_kwh":null,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":null,"total_energy_production_tariff_2_kwh":null,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"} +|MyElectricity2;00254358;9652.22;1.055;0;0;235;245;239;null;null;null;null;null;null;1111-11-11 11:11.11 # Test MKRadio3 T1 telegrams @@ -294,8 +294,8 @@ telegram=76442104710007612507727100076121042507B5006005E2E95A3C2A1279A5415E67326 # Test Unismart Gas Meter telegram=|6044B8059430040001037A1D005085E2B670BCF1A5C87E0C1A51DA18924EF984613DA2A9CD39D8F4C7208326C76D42DBEADF80D574192B71BD7C4F56A7F1513151768A9DB804883B28CB085CA2D0F7438C361CB9E2734712ED9BFBB2A14EF55208| -{"media":"gas","meter":"unismart","name":"GasMeter","id":"00043094","fabrication_no":"3162296","total_date_time":"2021-09-15 13:18","total_m3":917,"target_date_time":"2021-09-01 06:00","target_m3":911.32,"version":"UGG4","device_date_time":"2021-09-15 13:18","suppler_info":"00","status":"F00C","parameter_set":"02","other_counter":20,"timestamp":"1111-11-11T11:11:11Z"} -|GasMeter;00043094;917.000000;911.320000;1111-11-11 11:11.11 +{"media":"gas","meter":"unismart","name":"GasMeter","id":"00043094","fabrication_no":"03162296","status":"STATUS_FLAGS_CF0","other":"OTHER_FLAGS_14","total_date_time":"2021-09-15 13:18","total_m3":917,"target_date_time":"2021-09-01 06:00","target_m3":911.32,"version":" 4GGU","supplier_info":"00","parameter_set":"02","meter_timestamp":"2021-09-15 13:18:30","timestamp":"1111-11-11T11:11:11Z"} +|GasMeter;00043094;917;911.32;1111-11-11 11:11.11 # Test Hydrocal M3 heat/cooling meter telegram=|8E44B409747372710B0D7A798080052F2F_0C0E59600100046D1D36B9290C13679947000C0E000000000C13590000000C13000000000C13000000000A5A18020A5E11020F823D06003D06003D06003D0600140600620500480400E402001601000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002F2F| diff --git a/src/driver_amiplus.cc b/src/driver_amiplus.cc index 6be3325..77ba740 100644 --- a/src/driver_amiplus.cc +++ b/src/driver_amiplus.cc @@ -27,6 +27,7 @@ namespace static bool ok = registerDriver([](DriverInfo&di) { di.setName("amiplus"); + di.setDefaultFields("name,id,total_energy_consumption_kwh,current_power_consumption_kw,total_energy_production_kwh,current_power_production_kw,voltage_at_phase_1_v,voltage_at_phase_2_v,voltage_at_phase_3_v,total_energy_consumption_tariff_1_kwh,total_energy_consumption_tariff_2_kwh,total_energy_consumption_tariff_3_kwh,total_energy_production_tariff_1_kwh,total_energy_production_tariff_2_kwh,total_energy_production_tariff_3_kwh,timestamp"); di.setMeterType(MeterType::ElectricityMeter); di.addLinkMode(LinkMode::T1); di.addDetection(MANUFACTURER_APA, 0x02, 0x02); @@ -188,19 +189,19 @@ namespace // Test: MyElectricity1 amiplus 10101010 NOKEY // telegram=|4E4401061010101002027A00004005_2F2F0E035040691500000B2B300300066D00790C7423400C78371204860BABC8FC100000000E833C8074000000000BAB3C0000000AFDC9FC0136022F2F2F2F2F| -// {"media":"electricity","meter":"amiplus","name":"MyElectricity1","id":"10101010","total_energy_consumption_kwh":15694.05,"current_power_consumption_kw":0.33,"total_energy_production_kwh":7.48,"current_power_production_kw":0,"voltage_at_phase_1_v":236,"voltage_at_phase_2_v":null,"voltage_at_phase_3_v":null,"device_date_time":"2019-03-20 12:57","total_energy_consumption_tariff_1_kwh":null,"total_energy_consumption_tariff_2_kwh":null,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":null,"total_energy_production_tariff_2_kwh":null,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"} -// |MyElectricity1;10101010;15694.050000;0.330000;7.480000;0.000000;236.000000;nan;nan;nan;nan;nan;nan;nan;nan;1111-11-11 11:11.11 +// {"media":"electricity","meter":"amiplus","name":"MyElectricity1","id":"10101010","total_energy_consumption_kwh":15694.05,"current_power_consumption_kw":0.33,"total_energy_production_kwh":7.48,"current_power_production_kw":0,"voltage_at_phase_1_v":236,"voltage_at_phase_2_v":null,"voltage_at_phase_3_v":null,"device_date_time":"2019-03-20 12:57:00","total_energy_consumption_tariff_1_kwh":null,"total_energy_consumption_tariff_2_kwh":null,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":null,"total_energy_production_tariff_2_kwh":null,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"} +// |MyElectricity1;10101010;15694.05;0.33;7.48;0;236;null;null;null;null;null;null;null;null;1111-11-11 11:11.11 // Test: MyElectricity2 amiplus 00254358 NOKEY // amiplus/apator electricity meter with three phase voltages // telegram=|5E44B6105843250000027A2A005005_2F2F0C7835221400066D404708AC2A400E032022650900000E833C0000000000001B2B9647000B2B5510000BAB3C0000000AFDC9FC0135020AFDC9FC0245020AFDC9FC0339020BABC8FC100000002F2F| -// {"media":"electricity","meter":"amiplus","name":"MyElectricity2","id":"00254358","total_energy_consumption_kwh":9652.22,"current_power_consumption_kw":1.055,"total_energy_production_kwh":0,"current_power_production_kw":0,"voltage_at_phase_1_v":235,"voltage_at_phase_2_v":245,"voltage_at_phase_3_v":239,"device_date_time":"2021-10-12 08:07","total_energy_consumption_tariff_1_kwh":null,"total_energy_consumption_tariff_2_kwh":null,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":null,"total_energy_production_tariff_2_kwh":null,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"} -// |MyElectricity2;00254358;9652.220000;1.055000;0.000000;0.000000;235.000000;245.000000;239.000000;nan;nan;nan;nan;nan;nan;1111-11-11 11:11.11 +// {"media":"electricity","meter":"amiplus","name":"MyElectricity2","id":"00254358","total_energy_consumption_kwh":9652.22,"current_power_consumption_kw":1.055,"total_energy_production_kwh":0,"current_power_production_kw":0,"voltage_at_phase_1_v":235,"voltage_at_phase_2_v":245,"voltage_at_phase_3_v":239,"device_date_time":"2021-10-12 08:07:00","total_energy_consumption_tariff_1_kwh":null,"total_energy_consumption_tariff_2_kwh":null,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":null,"total_energy_production_tariff_2_kwh":null,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"} +// |MyElectricity2;00254358;9652.22;1.055;0;0;235;245;239;null;null;null;null;null;null;1111-11-11 11:11.11 // Test: MyElectricity3 amiplus 86064864 NOKEY // amiplus/apator electricity meter with three phase voltages and 2 tariffs // telegram=|804401066448068602027A000070052F2F_066D1E5C11DA21400C78644806868E10036110012500008E20038106531800008E10833C9949000000008E20833C8606000000001B2B5228020B2B3217000BAB3C0000000AFDC9FC0131020AFDC9FC0225020AFDC9FC0331020BABC8FC100000002F2F2F2F2F2F2F2F2F2F2F2F2FDE47| -// {"media":"electricity","meter":"amiplus","name":"MyElectricity3","id":"86064864","total_energy_consumption_kwh":null,"current_power_consumption_kw":1.732,"total_energy_production_kwh":null,"current_power_production_kw":0,"voltage_at_phase_1_v":231,"voltage_at_phase_2_v":225,"voltage_at_phase_3_v":231,"device_date_time":"2022-01-26 17:28","total_energy_consumption_tariff_1_kwh":25011.061,"total_energy_consumption_tariff_2_kwh":18530.681,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":4.999,"total_energy_production_tariff_2_kwh":0.686,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"} -// |MyElectricity3;86064864;nan;1.732000;nan;0.000000;231.000000;225.000000;231.000000;25011.061000;18530.681000;nan;4.999000;0.686000;nan;1111-11-11 11:11.11 +// {"media":"electricity","meter":"amiplus","name":"MyElectricity3","id":"86064864","total_energy_consumption_kwh":null,"current_power_consumption_kw":1.732,"total_energy_production_kwh":null,"current_power_production_kw":0,"voltage_at_phase_1_v":231,"voltage_at_phase_2_v":225,"voltage_at_phase_3_v":231,"device_date_time":"2022-01-26 17:28:30","total_energy_consumption_tariff_1_kwh":25011.061,"total_energy_consumption_tariff_2_kwh":18530.681,"total_energy_consumption_tariff_3_kwh":null,"total_energy_production_tariff_1_kwh":4.999,"total_energy_production_tariff_2_kwh":0.686,"total_energy_production_tariff_3_kwh":null,"timestamp":"1111-11-11T11:11:11Z"} +// |MyElectricity3;86064864;null;1.732;null;0;231;225;231;25011.061;18530.681;null;4.999;0.686;null;1111-11-11 11:11.11 diff --git a/src/driver_cma12w.cc b/src/driver_cma12w.cc index 42a2243..8f7a489 100644 --- a/src/driver_cma12w.cc +++ b/src/driver_cma12w.cc @@ -39,7 +39,7 @@ namespace Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di) { - addOptionalCommonFields(); + addOptionalCommonFields("software_version"); addStringField( "status", diff --git a/src/driver_ehzp.cc b/src/driver_ehzp.cc index 0e75fec..d417d21 100644 --- a/src/driver_ehzp.cc +++ b/src/driver_ehzp.cc @@ -41,7 +41,7 @@ namespace PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT | PrintProperty::STATUS | PrintProperty::JOIN_TPL_STATUS); - addOptionalCommonFields(); + addOptionalCommonFields("on_time_h"); addNumericFieldWithExtractor( "total_energy_consumption", diff --git a/src/driver_enercal.cc b/src/driver_enercal.cc index 22d4e32..7f603be 100644 --- a/src/driver_enercal.cc +++ b/src/driver_enercal.cc @@ -237,7 +237,7 @@ namespace .set(StorageNr(1)) ); - addOptionalCommonFields(); + addOptionalCommonFields("operating_time_h,on_time_h,meter_datetime"); } } diff --git a/src/driver_esyswm.cc b/src/driver_esyswm.cc index c13c119..9ccc166 100644 --- a/src/driver_esyswm.cc +++ b/src/driver_esyswm.cc @@ -37,7 +37,7 @@ namespace Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di) { - addOptionalCommonFields(); + addOptionalCommonFields("fabrication_no,enhanced_id,location"); addStringFieldWithExtractor( "location_hex", diff --git a/src/driver_itron.cc b/src/driver_itron.cc index f3f3501..07f0723 100644 --- a/src/driver_itron.cc +++ b/src/driver_itron.cc @@ -43,8 +43,8 @@ namespace addLinkMode(LinkMode::T1); - addOptionalCommonFields(); - addOptionalFlowRelatedFields(); + addOptionalCommonFields("enhanced_id,meter_datetime"); + addOptionalFlowRelatedFields("total_m3,total_backward_m3,volume_flow_m3h"); addStringFieldWithExtractorAndLookup( "status", @@ -136,10 +136,10 @@ namespace // Test: SomeWater itron 12345698 NOKEY // Comment: Test ITRON T1 telegram not encrypted, which has no 2f2f markers. // telegram=|384497269856341203077AD90000A0#0413FD110000066D2C1AA1D521004413300F0000426CBF2C047F0000060C027F862A0E79678372082100| -// {"media":"water","meter":"itron","name":"SomeWater","id":"12345698","enhanced_id":"002108728367","meter_datetime":"2022-01-21 01:26","total_m3":4.605,"status":"OK","target_m3":3.888,"target_date":"2021-12-31","unknown_a":"WOOTA_C060000","unknown_b":"WOOTB_2A86","timestamp":"1111-11-11T11:11:11Z"} +// {"media":"water","meter":"itron","name":"SomeWater","id":"12345698","enhanced_id":"002108728367","meter_datetime":"2022-01-21 01:26:44","total_m3":4.605,"status":"OK","target_m3":3.888,"target_date":"2021-12-31","unknown_a":"WOOTA_C060000","unknown_b":"WOOTB_2A86","timestamp":"1111-11-11T11:11:11Z"} // |SomeWater;12345698;4.605;3.888;1111-11-11 11:11.11 // Test: MoreWater itron 18000056 NOKEY // telegram=|46449726560000183307725600001897263307AF0030052F2F_066D0E1015C82A000C13771252000C933C000000000B3B0400004C1361045200426CC12A03FD971C0000002F2F2F| -// {"media":"water","meter":"itron","name":"MoreWater","id":"18000056","meter_datetime":"2022-10-08 21:16","total_m3":521.277,"total_backward_m3":0,"volume_flow_m3h":0.004,"status":"OK","target_m3":520.461,"target_date":"2022-10-01","timestamp":"1111-11-11T11:11:11Z"} +// {"media":"water","meter":"itron","name":"MoreWater","id":"18000056","meter_datetime":"2022-10-08 21:16:14","total_m3":521.277,"total_backward_m3":0,"volume_flow_m3h":0.004,"status":"OK","target_m3":520.461,"target_date":"2022-10-01","timestamp":"1111-11-11T11:11:11Z"} // |MoreWater;18000056;521.277;520.461;1111-11-11 11:11.11 diff --git a/src/driver_minomess.cc b/src/driver_minomess.cc index 1a8db51..337264f 100644 --- a/src/driver_minomess.cc +++ b/src/driver_minomess.cc @@ -37,8 +37,8 @@ namespace Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di) { - addOptionalCommonFields(); - addOptionalFlowRelatedFields(); + addOptionalCommonFields("meter_date,fabrication_no,operating_time_h,on_time_h,on_time_at_error_h,meter_datetime"); + addOptionalFlowRelatedFields("total_m3,total_backward_m3,volume_flow_m3h"); /* If the meter is recently commissioned, the target water consumption value is bogus. The bits store 0xffffffff. Should we deal with this? Now a very large value is printed in the json. diff --git a/src/driver_multical603.cc b/src/driver_multical603.cc index bb31e61..87fa60d 100644 --- a/src/driver_multical603.cc +++ b/src/driver_multical603.cc @@ -39,7 +39,7 @@ namespace Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di) { - addOptionalCommonFields(); + addOptionalCommonFields("on_time_h"); // Technical Description Multical 603 page 116 section 7.7.2 Information code types on serial communication. addStringFieldWithExtractorAndLookup( diff --git a/src/driver_munia.cc b/src/driver_munia.cc index 97dd7b1..571b04f 100644 --- a/src/driver_munia.cc +++ b/src/driver_munia.cc @@ -36,8 +36,6 @@ namespace Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di) { - addOptionalCommonFields(); - addStringFieldWithExtractorAndLookup( "status", "Meter status. Reports OK if neither tpl sts nor error flags have bits set.", @@ -59,11 +57,6 @@ namespace }, })); - /* - .set(MeasurementType::Instantaneous) - .set(VIFRange::ErrorFlags) - .add(VIFCombinable::StandardConformantDataContent) -*/ addNumericFieldWithExtractor( "current_temperature", "The current temperature.", diff --git a/src/driver_piigth.cc b/src/driver_piigth.cc index 04b742c..a31e31b 100644 --- a/src/driver_piigth.cc +++ b/src/driver_piigth.cc @@ -36,7 +36,7 @@ namespace Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di) { - addOptionalCommonFields(); + addOptionalCommonFields("fabrication_no,software_version"); addStringField( "status", diff --git a/src/driver_q400.cc b/src/driver_q400.cc index 14de1c9..c17250b 100644 --- a/src/driver_q400.cc +++ b/src/driver_q400.cc @@ -36,8 +36,8 @@ namespace Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di) { - addOptionalCommonFields(); - addOptionalFlowRelatedFields(); + addOptionalCommonFields("meter_datetime"); + addOptionalFlowRelatedFields("total_m3,total_forward_m3,total_backward_m3,flow_temperature_c,volume_flow_m3h"); addStringField( "status", diff --git a/src/driver_qualcosonic.cc b/src/driver_qualcosonic.cc index 558e20f..22a06b5 100644 --- a/src/driver_qualcosonic.cc +++ b/src/driver_qualcosonic.cc @@ -35,8 +35,8 @@ namespace Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di) { - addOptionalCommonFields(); - addOptionalFlowRelatedFields(); + addOptionalCommonFields("fabrication_no,operating_time_h,on_time_h,meter_datetime,meter_datetime_at_error"); + addOptionalFlowRelatedFields("total_m3,flow_temperature_c,return_temperature_c,flow_return_temperature_difference_c,volume_flow_m3h"); addStringFieldWithExtractorAndLookup( "status", diff --git a/src/driver_unismart.cc b/src/driver_unismart.cc new file mode 100644 index 0000000..3142e11 --- /dev/null +++ b/src/driver_unismart.cc @@ -0,0 +1,168 @@ +/* + Copyright (C) 2021-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("unismart"); + di.setDefaultFields("name,id,total_m3,target_m3,timestamp"); + di.setMeterType(MeterType::GasMeter); + di.addLinkMode(LinkMode::T1); + di.addDetection(MANUFACTURER_AMX, 0x03, 0x01); + + di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr(new Driver(mi, di)); }); + }); + + Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di) + { + addOptionalCommonFields("fabrication_no"); + + addStringFieldWithExtractorAndLookup( + "status", + "Status of meter?", + PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT | PrintProperty::STATUS, + FieldMatcher::build() + .set(DifVifKey("02FD74")), + { + { + { + "STATUS_FLAGS", + Translate::Type::BitToString, + 0xffff, + "OK", + { + } + }, + }, + }); + + addStringFieldWithExtractorAndLookup( + "other", + "Other status of meter?", + PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT | PrintProperty::STATUS, + FieldMatcher::build() + .set(DifVifKey("017F")), + { + { + { + "OTHER_FLAGS", + Translate::Type::BitToString, + 0xff, + "", + { + } + }, + }, + }); + + addStringFieldWithExtractor( + "total_date_time", + "Timestamp for this total measurement.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::DateTime) + .set(IndexNr(1)) + ); + + addNumericFieldWithExtractor( + "total", + "The total gas consumption recorded by this meter.", + PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT, + Quantity::Volume, + VifScaling::Auto, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::Volume) + .add(VIFCombinable::UncorrectedMeterUnit) + ); + + addStringFieldWithExtractor( + "target_date_time", + "Timestamp for gas consumption recorded at the beginning of this month.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::DateTime) + .set(StorageNr(1)) + ); + + addNumericFieldWithExtractor( + "target", + "The total gas consumption recorded by this meter at the beginning of this month.", + PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT, + Quantity::Volume, + VifScaling::Auto, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::Volume) + .set(StorageNr(1)) + .add(VIFCombinable::UncorrectedMeterUnit) + ); + + addStringFieldWithExtractor( + "version", + "Model version.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::ModelVersion) + ); + + addStringFieldWithExtractor( + "supplier_info", + "Supplier info?", + PrintProperty::JSON | PrintProperty::OPTIONAL, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::SpecialSupplierInformation) + ); + + addStringFieldWithExtractor( + "parameter_set", + "Meter configued with this parameter set?", + PrintProperty::JSON | PrintProperty::OPTIONAL, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::ParameterSet) + ); + + addStringFieldWithExtractor( + "meter_timestamp", + "Timestamp when this measurement was sent.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::DateTime) + .set(IndexNr(2)) + ); + + } +} + +// Test: GasMeter unismart 00043094 00000000000000000000000000000000 +// telegram=|6044B8059430040001037A1D005085E2B670BCF1A5C87E0C1A51DA18924EF984613DA2A9CD39D8F4C7208326C76D42DBEADF80D574192B71BD7C4F56A7F1513151768A9DB804883B28CB085CA2D0F7438C361CB9E2734712ED9BFBB2A14EF55208| +// {"media":"gas","meter":"unismart","name":"GasMeter","id":"00043094","fabrication_no":"03162296","status":"STATUS_FLAGS_CF0","other":"OTHER_FLAGS_14","total_date_time":"2021-09-15 13:18","total_m3":917,"target_date_time":"2021-09-01 06:00","target_m3":911.32,"version":" 4GGU","supplier_info":"00","parameter_set":"02","meter_timestamp":"2021-09-15 13:18:30","timestamp":"1111-11-11T11:11:11Z"} +// |GasMeter;00043094;917;911.32;1111-11-11 11:11.11 diff --git a/src/dvparser.cc b/src/dvparser.cc index 07db1d2..6c24fa4 100644 --- a/src/dvparser.cc +++ b/src/dvparser.cc @@ -1168,7 +1168,10 @@ bool DVEntry::extractDate(struct tm *out) // ..ss ssss int sec = (0x3f) & v[0]; out->tm_sec = sec; - // some daylight saving time decoding needed here.... + // There are also bits for day of week, week of year. + // A bit for if daylight saving is in use or not and its offset. + // A bit if it is a leap year. + // I am unsure how to deal with this here..... TODO } return ok; diff --git a/src/dvparser.h b/src/dvparser.h index 6a25078..57cb384 100644 --- a/src/dvparser.h +++ b/src/dvparser.h @@ -62,6 +62,7 @@ X(Current,0x7D50,0x7D5F, Quantity::Current, Unit::Ampere) \ X(ResetCounter,0x7D60,0x7D60, Quantity::Counter, Unit::COUNTER) \ X(CumulationCounter,0x7D61,0x7D61, Quantity::Counter, Unit::COUNTER) \ + X(SpecialSupplierInformation,0x7D67,0x7D67, Quantity::Text, Unit::TXT) \ X(RemainingBattery,0x7D74,0x7D74, Quantity::Time, Unit::Day) \ X(DurationSinceReadout,0x7DAC,0x7DAC, Quantity::Time, Unit::Hour) \ X(AnyVolumeVIF,0x00,0x00, Quantity::Volume, Unit::Unknown) \ @@ -280,6 +281,7 @@ struct IndexNr IndexNr(int n) : nr_(n) {} int intValue() { return nr_; } bool operator==(IndexNr s) { return nr_ == s.nr_; } + bool operator!=(IndexNr s) { return nr_ != s.nr_; } private: int nr_; @@ -381,8 +383,9 @@ struct FieldMatcher SubUnitNr subunit_nr_from { 0 }; SubUnitNr subunit_nr_to { 0 }; - // If the telegram has multiple identical difvif entries, use entry with this index nr. - // First entry has nr 1, which is the default value. + // If the telegram has multiple identical difvif entries matching this field + // and you want to catch the second matching entry, then set the index nr to 2. + // The default is 1. IndexNr index_nr { 1 }; FieldMatcher() : active(false) { } diff --git a/src/meter_detection.h b/src/meter_detection.h index 214cda2..7b5f4e0 100644 --- a/src/meter_detection.h +++ b/src/meter_detection.h @@ -75,7 +75,6 @@ X(SONTEX868, MANUFACTURER_SON, 0x08, 0x16) \ X(TOPASESKR, MANUFACTURER_AMT, 0x06, 0xf1) \ X(TOPASESKR, MANUFACTURER_AMT, 0x07, 0xf1) \ - X(UNISMART, MANUFACTURER_AMX, 0x03, 0x01) \ diff --git a/src/meter_unismart.cc b/src/meter_unismart.cc deleted file mode 100644 index ef0e2c4..0000000 --- a/src/meter_unismart.cc +++ /dev/null @@ -1,272 +0,0 @@ -/* - Copyright (C) 2021 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"dvparser.h" -#include"meters.h" -#include"meters_common_implementation.h" -#include"wmbus.h" -#include"wmbus_utils.h" -#include"util.h" - -#include - -using namespace std; - -struct MeterUnismart : public virtual MeterCommonImplementation { - MeterUnismart(MeterInfo &mi); - - // Total gas counted through the meter - double totalGasConsumption(Unit u); - bool hasTotalGasConsumption(); - // Consumption at the beginning of this month. - double targetGasConsumption(Unit u); - -private: - void processContent(Telegram *t); - - string fabrication_no_; - string total_date_time_; - double total_gas_consumption_m3_ {}; - string target_date_time_; - double target_gas_consumption_m3_ {}; - string version_; - string device_date_time_; - - string supplier_info_; - string status_; - string parameter_set_; - uint8_t other_; -}; - -shared_ptr createUnismart(MeterInfo &mi) -{ - return shared_ptr(new MeterUnismart(mi)); -} - -MeterUnismart::MeterUnismart(MeterInfo &mi) : - MeterCommonImplementation(mi, "unismart") -{ - setMeterType(MeterType::GasMeter); - - setExpectedTPLSecurityMode(TPLSecurityMode::AES_CBC_IV); - - addLinkMode(LinkMode::T1); - - addPrint("fabrication_no", Quantity::Text, - [&](){ return fabrication_no_; }, - "Static fabrication no information.", - PrintProperty::JSON); - - addPrint("total_date_time", Quantity::Text, - [&](){ return total_date_time_; }, - "Timestamp for this total measurement.", - PrintProperty::JSON); - - addPrint("total", Quantity::Volume, - [&](Unit u){ return totalGasConsumption(u); }, - "The total gas consumption recorded by this meter.", - PrintProperty::FIELD | PrintProperty::JSON); - - addPrint("target_date_time", Quantity::Text, - [&](){ return target_date_time_; }, - "Timestamp for gas consumption recorded at the beginning of this month.", - PrintProperty::JSON); - - addPrint("target", Quantity::Volume, - [&](Unit u){ return targetGasConsumption(u); }, - "The total gas consumption recorded by this meter at the beginning of this month.", - PrintProperty::FIELD | PrintProperty::JSON); - - addPrint("version", Quantity::Text, - [&](){ return version_; }, - "Model/version a reported by meter.", - PrintProperty::JSON); - - addPrint("device_date_time", Quantity::Text, - [&](){ return device_date_time_; }, - "Device date time? Seems to be the same as total date time.", - PrintProperty::JSON); - - addPrint("suppler_info", Quantity::Text, - [&](){ return supplier_info_; }, - "?", - PrintProperty::JSON); - - addPrint("status", Quantity::Text, - [&](){ return status_; }, - "?", - PrintProperty::JSON); - - addPrint("parameter_set", Quantity::Text, - [&](){ return parameter_set_; }, - "?", - PrintProperty::JSON); - - addPrint("other", Quantity::Counter, - [&](Unit u){ return other_; }, - "?", - PrintProperty::JSON); - -} - -void MeterUnismart::processContent(Telegram *t) -{ - /* - (unismart) 11: 0C dif (8 digit BCD Instantaneous value) -(unismart) 12: 78 vif (Fabrication no) -(unismart) 13: 96221603 -(unismart) 17: 04 dif (32 Bit Integer/Binary Instantaneous value) -(unismart) 18: 6D vif (Date and time type) -(unismart) 19: 122DAF29 -(unismart) 1d: 0C dif (8 digit BCD Instantaneous value) -(unismart) 1e: 94 vif (Volume 10⁻² m³) -(unismart) 1f: 3A vife (uncorrected meter unit) -(unismart) 20: * 00170900 total consumption (917.000000 m3) -(unismart) 24: 44 dif (32 Bit Integer/Binary Instantaneous value storagenr=1) -(unismart) 25: 6D vif (Date and time type) -(unismart) 26: 0026A129 -(unismart) 2a: 4C dif (8 digit BCD Instantaneous value storagenr=1) -(unismart) 2b: 94 vif (Volume 10⁻² m³) -(unismart) 2c: 3A vife (uncorrected meter unit) -(unismart) 2d: 32110900 -(unismart) 31: 01 dif (8 Bit Integer/Binary Instantaneous value) -(unismart) 32: FD vif (Second extension FD of VIF-codes) -(unismart) 33: 67 vife (Special supplier information) -(unismart) 34: 00 -(unismart) 35: 02 dif (16 Bit Integer/Binary Instantaneous value) -(unismart) 36: FD vif (Second extension FD of VIF-codes) -(unismart) 37: 74 vife (Reserved) -(unismart) 38: F00C -(unismart) 3a: 0D dif (variable length Instantaneous value) -(unismart) 3b: FD vif (Second extension FD of VIF-codes) -(unismart) 3c: 0C vife (Model/Version) -(unismart) 3d: 06 varlen=6 -(unismart) 3e: 554747342020 -(unismart) 44: 01 dif (8 Bit Integer/Binary Instantaneous value) -(unismart) 45: FD vif (Second extension FD of VIF-codes) -(unismart) 46: 0B vife (Parameter set identification) -(unismart) 47: 02 -(unismart) 48: 01 dif (8 Bit Integer/Binary Instantaneous value) -(unismart) 49: 7F vif (Manufacturer specific) -(unismart) 4a: 14 -(unismart) 4b: 06 dif (48 Bit Integer/Binary Instantaneous value) -(unismart) 4c: 6D vif (Date and time type) -(unismart) 4d: 1E120DAF296D -(unismart) 53: 2F skip -(unismart) 54: 2F skip -(unismart) 55: 2F skip -(unismart) 56: 2F skip -(unismart) 57: 2F skip -(unismart) 58: 2F skip -(unismart) 59: 2F skip -(unismart) 5a: 2F skip -(unismart) 5b: 2F skip -(unismart) 5c: 2F skip -(unismart) 5d: 2F skip -(unismart) 5e: 2F skip - -*/ - int offset; - string key; - - uint64_t v {}; - if (extractDVlong(&t->dv_entries, "0C78", &offset, &v)) - { - fabrication_no_ = to_string(v); - t->addMoreExplanation(offset, " fabrication no (%zu)", v); - } - - if (findKey(MeasurementType::Instantaneous, VIFRange::DateTime, 0, 0, &key, &t->dv_entries)) { - struct tm datetime; - extractDVdate(&t->dv_entries, key, &offset, &datetime); - total_date_time_ = strdatetime(&datetime); - t->addMoreExplanation(offset, " total datetime (%s)", total_date_time_.c_str()); - } - - if (findKey(MeasurementType::Instantaneous, VIFRange::Volume, 0, 0, &key, &t->dv_entries)) - { - extractDVdouble(&t->dv_entries, key, &offset, &total_gas_consumption_m3_); - t->addMoreExplanation(offset, " total consumption (%f m3)", total_gas_consumption_m3_); - } - - if (findKey(MeasurementType::Instantaneous, VIFRange::DateTime, 1, 0, &key, &t->dv_entries)) { - struct tm datetime; - extractDVdate(&t->dv_entries, key, &offset, &datetime); - target_date_time_ = strdatetime(&datetime); - t->addMoreExplanation(offset, " target datetime (%s)", target_date_time_.c_str()); - } - - if (findKey(MeasurementType::Instantaneous, VIFRange::Volume, 1, 0, &key, &t->dv_entries)) - { - extractDVdouble(&t->dv_entries, key, &offset, &target_gas_consumption_m3_); - t->addMoreExplanation(offset, " target consumption (%f m3)", target_gas_consumption_m3_); - } - - string tmp; - if (extractDVHexString(&t->dv_entries, "0DFD0C", &offset, &tmp)) - { - vector bin; - hex2bin(tmp, &bin); - version_ = safeString(bin); - trimWhitespace(&version_); - t->addMoreExplanation(offset, " version (%s)", version_.c_str()); - } - - struct tm datetime; - if (extractDVdate(&t->dv_entries, "066D", &offset, &datetime)) - { - device_date_time_ = strdatetime(&datetime); - t->addMoreExplanation(offset, " device datetime (%s)", device_date_time_.c_str()); - } - - if (extractDVHexString(&t->dv_entries, "01FD67", &offset, &supplier_info_)) - { - t->addMoreExplanation(offset, " suppler info (%s)", supplier_info_.c_str()); - } - - if (extractDVHexString(&t->dv_entries, "02FD74", &offset, &status_)) - { - t->addMoreExplanation(offset, " status (%s)", status_.c_str()); - } - - if (extractDVHexString(&t->dv_entries, "01FD0B", &offset, ¶meter_set_)) - { - t->addMoreExplanation(offset, " parameter set (%s)", parameter_set_.c_str()); - } - - if (extractDVuint8(&t->dv_entries, "017F", &offset, &other_)) - { - t->addMoreExplanation(offset, " status2 (%d)", other_); - } -} - -double MeterUnismart::totalGasConsumption(Unit u) -{ - assertQuantity(u, Quantity::Volume); - return convert(total_gas_consumption_m3_, Unit::M3, u); -} - -bool MeterUnismart::hasTotalGasConsumption() -{ - return true; -} - -double MeterUnismart::targetGasConsumption(Unit u) -{ - assertQuantity(u, Quantity::Volume); - return convert(target_gas_consumption_m3_, Unit::M3, u); -} diff --git a/src/meters.cc b/src/meters.cc index e67e293..20e206f 100644 --- a/src/meters.cc +++ b/src/meters.cc @@ -1873,6 +1873,8 @@ void MeterCommonImplementation::processFieldExtractors(Telegram *t) { map found; + // Sort the dv_entries based on their offset in the telegram. + // I.e. restore the ordering that was implicit in the telegram. vector sorted_entries; for (auto &p : t->dv_entries) @@ -1882,30 +1884,32 @@ void MeterCommonImplementation::processFieldExtractors(Telegram *t) sort(sorted_entries.begin(), sorted_entries.end(), [](const DVEntry* a, const DVEntry *b) -> bool { return a->offset < b->offset; }); - // Iterate through the data content (dv_entries) in the telegram. - for (DVEntry *dve : sorted_entries) + // Now go through each field_info defined by the driver. + for (FieldInfo &fi : field_infos_) { - // 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_) + int current_match_nr = 0; + + // This field_info has not been matched to a dv_entry before! + debug("(meters) trying field info %s(%s)[%d]...\n", + fi.vname().c_str(), + toString(fi.xuantity()), + fi.index()); + + // Iterate through dv_entries in the telegram in the same order the telegram presented them. + for (DVEntry *dve : sorted_entries) { if (fi.hasMatcher() && fi.matches(dve)) { - if (found.count(&fi) != 0) - { - DVEntry *old = found[&fi]; + current_match_nr++; - verbose("(meter) while processing field extractors ignoring dventry %s at offset %d matching since " - "field %s was already matched against dventry %s at offset %d !\n", - dve->dif_vif_key.str().c_str(), - dve->offset, - fi.vname().c_str(), - old->dif_vif_key.str().c_str(), - old->offset); - } - else + if (fi.matcher().index_nr != IndexNr(current_match_nr)) { - // We have field that wants to handle this entry! + // This field info did match, but requires another index nr! + // Increment the current index nr and look for the next match. + } + else if (found.count(&fi) == 0) + { + // This field_info has not been matched to a dv_entry before! debug("(meters) using field info %s(%s)[%d] to extract %s at offset %d\n", fi.vname().c_str(), toString(fi.xuantity()), @@ -1917,6 +1921,18 @@ void MeterCommonImplementation::processFieldExtractors(Telegram *t) fi.performExtraction(this, t, dve); found[&fi] = dve; } + else + { + DVEntry *old = found[&fi]; + + verbose("(meter) while processing field extractors ignoring dventry %s at offset %d matching since " + "field %s was already matched against dventry %s at offset %d !\n", + dve->dif_vif_key.str().c_str(), + dve->offset, + fi.vname().c_str(), + old->dif_vif_key.str().c_str(), + old->offset); + } } } } @@ -2912,7 +2928,17 @@ bool FieldInfo::extractString(Meter *m, Telegram *t, DVEntry *dve) { struct tm datetime; dve->extractDate(&datetime); - string extracted_device_date_time = strdatetime(&datetime); + string extracted_device_date_time; + + if (dve->value.size() == 12) + { + // A long date time sec + timezone field. TODO add timezone data. + extracted_device_date_time = strdatetimesec(&datetime); + } + else + { + extracted_device_date_time = strdatetime(&datetime); + } m->setStringValue(this, extracted_device_date_time); t->addMoreExplanation(dve->offset, renderJsonText(m)); found = true; @@ -2926,13 +2952,14 @@ bool FieldInfo::extractString(Meter *m, Telegram *t, DVEntry *dve) t->addMoreExplanation(dve->offset, renderJsonText(m)); found = true; } - else if (matcher_.vif_range == VIFRange::EnhancedIdentification || + else if (matcher_.vif_range == VIFRange::Any || + matcher_.vif_range == VIFRange::EnhancedIdentification || matcher_.vif_range == VIFRange::FabricationNo || matcher_.vif_range == VIFRange::ModelVersion || matcher_.vif_range == VIFRange::SoftwareVersion || matcher_.vif_range == VIFRange::Customer || matcher_.vif_range == VIFRange::Location || - matcher_.vif_range == VIFRange::Any || + matcher_.vif_range == VIFRange::SpecialSupplierInformation || matcher_.vif_range == VIFRange::ParameterSet) { string extracted_id; @@ -3030,204 +3057,302 @@ bool Address::parse(string &s) return true; } -void MeterCommonImplementation::addOptionalCommonFields() +bool checkIf(set &fields, const char *s) { - addStringFieldWithExtractor( - "fabrication_no", - "Fabrication number.", - PrintProperty::JSON | PrintProperty::OPTIONAL, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::FabricationNo) - ); - - addStringFieldWithExtractor( - "enhanced_id", - "Enhanced identification number.", - PrintProperty::JSON | PrintProperty::OPTIONAL, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::EnhancedIdentification) - ); - - addStringFieldWithExtractor( - "software_version", - "Software version.", - PrintProperty::JSON | PrintProperty::OPTIONAL, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::SoftwareVersion) - ); - - addStringFieldWithExtractor( - "customer", - "Customer name.", - PrintProperty::JSON | PrintProperty::OPTIONAL, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::Customer) - ); - - addStringFieldWithExtractor( - "location", - "Meter installed at this customer location.", - PrintProperty::JSON | PrintProperty::OPTIONAL, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::Location) - ); - - addNumericFieldWithExtractor( - "operating_time", - "How long the meter has been collecting data.", - PrintProperty::JSON | PrintProperty::OPTIONAL, - Quantity::Time, - VifScaling::Auto, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::OperatingTime) - ); - - addNumericFieldWithExtractor( - "on_time", - "How long the meter has been powered up.", - PrintProperty::JSON | PrintProperty::OPTIONAL, - Quantity::Time, - VifScaling::Auto, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::OnTime) - ); - - addNumericFieldWithExtractor( - "on_time_at_error", - "How long the meter has been in an error state while powered up.", - PrintProperty::JSON | PrintProperty::OPTIONAL, - Quantity::Time, - VifScaling::Auto, - FieldMatcher::build() - .set(MeasurementType::AtError) - .set(VIFRange::OnTime) - ); - - addStringFieldWithExtractor( - "meter_date", - "Date when the meter sent the telegram.", - PrintProperty::JSON | PrintProperty::OPTIONAL, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::Date) - ); - - addStringFieldWithExtractor( - "meter_date_at_error", - "Date when the meter was in error.", - PrintProperty::JSON | PrintProperty::OPTIONAL, - FieldMatcher::build() - .set(MeasurementType::AtError) - .set(VIFRange::Date) - ); - - addStringFieldWithExtractor( - "meter_datetime", - "Date and time when the meter sent the telegram.", - PrintProperty::JSON | PrintProperty::OPTIONAL, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::DateTime) - ); - - addStringFieldWithExtractor( - "meter_datetime_at_error", - "Date and time when the meter was in error.", - PrintProperty::JSON | PrintProperty::OPTIONAL, - FieldMatcher::build() - .set(MeasurementType::AtError) - .set(VIFRange::DateTime) - ); + if (fields.count(s) > 0) + { + fields.erase(s); + return true; + } + return false; } -void MeterCommonImplementation::addOptionalFlowRelatedFields() +void checkFieldsEmpty(set &fields, string name) { - addNumericFieldWithExtractor( - "total", - "The total media volume consumption recorded by this meter.", - PrintProperty::JSON | PrintProperty::OPTIONAL, - Quantity::Volume, - VifScaling::Auto, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::Volume) - ); + if (fields.size() > 0) + { + string info; + for (auto &s : fields) { info += s+" "; } - addNumericFieldWithExtractor( - "total_forward", - "The total media volume flowing forward.", - PrintProperty::JSON | PrintProperty::OPTIONAL, - Quantity::Volume, - VifScaling::Auto, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::Volume) - .add(VIFCombinable::ForwardFlow) - ); + warning("(meter) when adding common fields to driver %s, these fields were not found: %s\n", + name.c_str(), + info.c_str()); + } +} - addNumericFieldWithExtractor( - "total_backward", - "The total media volume flowing backward.", - PrintProperty::JSON | PrintProperty::OPTIONAL, - Quantity::Volume, - VifScaling::Auto, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::Volume) - .add(VIFCombinable::BackwardFlow) - ); +void MeterCommonImplementation::addOptionalCommonFields(string field_names) +{ + set fields = splitStringIntoSet(field_names, ','); - addNumericFieldWithExtractor( - "flow_temperature", - "Forward media temperature.", - PrintProperty::JSON | PrintProperty::OPTIONAL, - Quantity::Temperature, - VifScaling::Auto, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::FlowTemperature) - ); + if (checkIf(fields, "fabrication_no")) + { + addStringFieldWithExtractor( + "fabrication_no", + "Fabrication number.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::FabricationNo) + ); + } - addNumericFieldWithExtractor( - "return_temperature", - "Return media temperature.", - PrintProperty::JSON | PrintProperty::OPTIONAL, - Quantity::Temperature, - VifScaling::Auto, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::ReturnTemperature) - ); + if (checkIf(fields,"enhanced_id")) + { + addStringFieldWithExtractor( + "enhanced_id", + "Enhanced identification number.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::EnhancedIdentification) + ); + } - addNumericFieldWithExtractor( - "flow_return_temperature_difference", - "The difference between flow and return media temperatures.", - PrintProperty::JSON | PrintProperty::OPTIONAL, - Quantity::Temperature, - VifScaling::AutoSigned, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::TemperatureDifference) - ); + if (checkIf(fields,"software_version")) + { + addStringFieldWithExtractor( + "software_version", + "Software version.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::SoftwareVersion) + ); + } - addNumericFieldWithExtractor( - "volume_flow", - "Media volume flow.", - PrintProperty::JSON | PrintProperty::OPTIONAL, - Quantity::Flow, - VifScaling::Auto, - FieldMatcher::build() - .set(MeasurementType::Instantaneous) - .set(VIFRange::VolumeFlow) - ); + if (checkIf(fields,"model_version")) + { + addStringFieldWithExtractor( + "model_version", + "Meter model version.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::ModelVersion) + ); + } + + if (checkIf(fields,"customer")) + { + addStringFieldWithExtractor( + "customer", + "Customer name.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::Customer) + ); + } + + if (checkIf(fields,"location")) + { + addStringFieldWithExtractor( + "location", + "Meter installed at this customer location.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::Location) + ); + } + + if (checkIf(fields,"operating_time_h")) + { + addNumericFieldWithExtractor( + "operating_time", + "How long the meter has been collecting data.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + Quantity::Time, + VifScaling::Auto, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::OperatingTime) + ); + } + + if (checkIf(fields,"on_time_h")) + { + addNumericFieldWithExtractor( + "on_time", + "How long the meter has been powered up.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + Quantity::Time, + VifScaling::Auto, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::OnTime) + ); + } + + if (checkIf(fields,"on_time_at_error_h")) + { + addNumericFieldWithExtractor( + "on_time_at_error", + "How long the meter has been in an error state while powered up.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + Quantity::Time, + VifScaling::Auto, + FieldMatcher::build() + .set(MeasurementType::AtError) + .set(VIFRange::OnTime) + ); + } + + if (checkIf(fields,"meter_date")) + { + addStringFieldWithExtractor( + "meter_date", + "Date when the meter sent the telegram.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::Date) + ); + } + + if (checkIf(fields,"meter_date_at_error")) + { + addStringFieldWithExtractor( + "meter_date_at_error", + "Date when the meter was in error.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + FieldMatcher::build() + .set(MeasurementType::AtError) + .set(VIFRange::Date) + ); + } + + if (checkIf(fields,"meter_datetime")) + { + addStringFieldWithExtractor( + "meter_datetime", + "Date and time when the meter sent the telegram.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::DateTime) + ); + } + + if (checkIf(fields,"meter_datetime_at_error")) + { + addStringFieldWithExtractor( + "meter_datetime_at_error", + "Date and time when the meter was in error.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + FieldMatcher::build() + .set(MeasurementType::AtError) + .set(VIFRange::DateTime) + ); + } + + checkFieldsEmpty(fields, name()); +} + +void MeterCommonImplementation::addOptionalFlowRelatedFields(string field_names) +{ + set fields = splitStringIntoSet(field_names, ','); + + if (checkIf(fields,"total_m3")) + { + addNumericFieldWithExtractor( + "total", + "The total media volume consumption recorded by this meter.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + Quantity::Volume, + VifScaling::Auto, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::Volume) + ); + } + + if (checkIf(fields,"total_forward_m3")) + { + addNumericFieldWithExtractor( + "total_forward", + "The total media volume flowing forward.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + Quantity::Volume, + VifScaling::Auto, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::Volume) + .add(VIFCombinable::ForwardFlow) + ); + } + + if (checkIf(fields,"total_backward_m3")) + { + addNumericFieldWithExtractor( + "total_backward", + "The total media volume flowing backward.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + Quantity::Volume, + VifScaling::Auto, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::Volume) + .add(VIFCombinable::BackwardFlow) + ); + } + + if (checkIf(fields,"flow_temperature_c")) + { + addNumericFieldWithExtractor( + "flow_temperature", + "Forward media temperature.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + Quantity::Temperature, + VifScaling::Auto, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::FlowTemperature) + ); + } + + if (checkIf(fields,"return_temperature_c")) + { + addNumericFieldWithExtractor( + "return_temperature", + "Return media temperature.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + Quantity::Temperature, + VifScaling::Auto, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::ReturnTemperature) + ); + } + + if (checkIf(fields,"flow_return_temperature_difference_c")) + { + addNumericFieldWithExtractor( + "flow_return_temperature_difference", + "The difference between flow and return media temperatures.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + Quantity::Temperature, + VifScaling::AutoSigned, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::TemperatureDifference) + ); + } + + if (checkIf(fields,"volume_flow_m3h")) + { + addNumericFieldWithExtractor( + "volume_flow", + "Media volume flow.", + PrintProperty::JSON | PrintProperty::OPTIONAL, + Quantity::Flow, + VifScaling::Auto, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::VolumeFlow) + ); + } } diff --git a/src/meters.h b/src/meters.h index ca57221..83dadfa 100644 --- a/src/meters.h +++ b/src/meters.h @@ -83,7 +83,6 @@ LIST_OF_METER_TYPES X(sontex868, T1_bit, HeatCostAllocationMeter, SONTEX868, Sontex868) \ X(topaseskr, T1_bit, WaterMeter, TOPASESKR, TopasEsKr) \ X(lse_08, S1_bit|C1_bit, HeatCostAllocationMeter, LSE_08, LSE_08) \ - X(unismart, T1_bit, GasMeter, UNISMART, Unismart) \ enum class MeterDriver { @@ -292,7 +291,8 @@ enum PrintProperty DEPRECATED = 32, // This field is about to be removed or changed in a newer driver, which will have a new name. STATUS = 64, // This is >the< status field and it should read OK of not error flags are set. JOIN_TPL_STATUS = 128, // This text field also includes the tpl status decoding. multiple OK:s collapse to a single OK. - JOIN_INTO_STATUS = 256 // This text field is injected into the already defined status field. multiple OK:s collapse. + JOIN_INTO_STATUS = 256, // This text field is injected into the already defined status field. multiple OK:s collapse. + OFFICIAL = 512 // This field is listed as an official field for the driver. }; struct PrintProperties diff --git a/src/meters_common_implementation.h b/src/meters_common_implementation.h index c0e0308..2cbd94f 100644 --- a/src/meters_common_implementation.h +++ b/src/meters_common_implementation.h @@ -103,7 +103,6 @@ protected: void setMeterType(MeterType mt); void addLinkMode(LinkMode lm); void addMfctTPLStatusBits(Translate::Lookup lookup); - void setDefaultFields(string f); // Print with the default unit for this quantity. void addPrint(string vname, Quantity vquantity, @@ -257,8 +256,8 @@ protected: std::string decodeTPLStatusByte(uchar sts); - void addOptionalCommonFields(); - void addOptionalFlowRelatedFields(); + void addOptionalCommonFields(string fields); + void addOptionalFlowRelatedFields(string fields); vector &selectedFields() { return selected_fields_; } void setSelectedFields(vector &f) { selected_fields_ = f; } diff --git a/src/util.cc b/src/util.cc index faffa0f..3b8bf5c 100644 --- a/src/util.cc +++ b/src/util.cc @@ -1884,6 +1884,13 @@ vector splitString(const string &s, char c) return v; } +set splitStringIntoSet(const string &s, char c) +{ + vector v = splitString(s, c); + set words(v.begin(), v.end()); + return words; +} + vector splitDeviceString(const string& ds) { string s = ds; diff --git a/src/util.h b/src/util.h index e7595dc..cf70f10 100644 --- a/src/util.h +++ b/src/util.h @@ -23,6 +23,7 @@ #include #include #include +#include #include void onExit(std::function cb); @@ -152,6 +153,8 @@ bool isNumber(const std::string& fq); std::vector splitMatchExpressions(const std::string& mes); // Split s into strings separated by c. std::vector splitString(const std::string &s, char c); +// Split s into strings separated by c and store inte set. +std::set splitStringIntoSet(const std::string &s, char c); // Split device string cul:c1:CMD(bar 1:2) into cul c1 CMD(bar 1:2) // I.e. the : colon inside CMD is not used for splitting. std::vector splitDeviceString(const std::string &s); diff --git a/tests/test_broken.sh b/tests/test_broken.sh index ecf6c63..620d1e8 100755 --- a/tests/test_broken.sh +++ b/tests/test_broken.sh @@ -13,11 +13,7 @@ cat > $TEST/test_expected.txt <