From 1faabb752642c33ddaaad4ae3f6b4e95668bc356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20=C3=96hrstr=C3=B6m?= Date: Tue, 22 Aug 2023 22:28:04 +0200 Subject: [PATCH] Adding more fields to abbb23 driver. --- src/driver_abbb23.cc | 335 ++++++++++++++++++++++++++++- src/driver_apator08.cc | 2 +- src/driver_apator162.cc | 2 +- src/driver_apator172.cc | 2 +- src/driver_compact5.cc | 4 +- src/driver_vario451.cc | 4 +- src/dvparser.cc | 48 ++++- src/dvparser.h | 12 ++ src/manufacturer_specificities.cc | 1 + src/meters.cc | 22 +- src/meters.h | 3 + src/meters_common_implementation.h | 3 +- src/testinternals.cc | 2 + 13 files changed, 414 insertions(+), 26 deletions(-) diff --git a/src/driver_abbb23.cc b/src/driver_abbb23.cc index 63d7185..5a5554b 100644 --- a/src/driver_abbb23.cc +++ b/src/driver_abbb23.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2019-2022 Fredrik Öhrström (gpl-3.0-or-later) + Copyright (C) 2019-2023 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 @@ -36,16 +36,170 @@ namespace Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di) { + addStringField( + "status", + "Status, error, warning and alarm flags.", + DEFAULT_PRINT_PROPERTIES | PrintProperty::INCLUDE_TPL_STATUS | PrintProperty::STATUS); + addNumericFieldWithExtractor( "total_energy_consumption", - "The total energy consumption recorded by this meter.", + "Total cumulative active imported energy.", DEFAULT_PRINT_PROPERTIES, Quantity::Energy, VifScaling::Auto, FieldMatcher::build() - .set(DifVifKey("0E8400")) + .set(MeasurementType::Instantaneous) + .set(VIFRange::AnyEnergyVIF) + .add(VIFCombinableRaw(0)) // Stupid 00 combinable.... ); + addNumericFieldWithExtractor( + "total_energy_consumption_tariff_{tariff_counter}", + "Total cumulative active imported energy per tariff.", + DEFAULT_PRINT_PROPERTIES, + Quantity::Energy, + VifScaling::Auto, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::AnyEnergyVIF) + .set(TariffNr(1),TariffNr(4)) + .add(VIFCombinableRaw(0)) // Stupid 00 combinable.... + ); + + addNumericFieldWithExtractor( + "total_energy_production", + "Total cumulative active exported energy.", + DEFAULT_PRINT_PROPERTIES, + Quantity::Energy, + VifScaling::Auto, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::AnyEnergyVIF) + .set(TariffNr(0)) + .set(SubUnitNr(1)) + .add(VIFCombinableRaw(0)) // Stupid 00 combinable.... + ); + + addNumericFieldWithExtractor( + "total_energy_production_tariff_{tariff_counter}", + "Total cumulative active exported energy per tariff.", + DEFAULT_PRINT_PROPERTIES, + Quantity::Energy, + VifScaling::Auto, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::AnyEnergyVIF) + .set(TariffNr(1),TariffNr(4)) + .set(SubUnitNr(1)) + .add(VIFCombinableRaw(0)) // Stupid 00 combinable.... + ); + + addNumericFieldWithExtractor( + "active_tariff", + "Active tariff.", + DEFAULT_PRINT_PROPERTIES, + Quantity::Dimensionless, + VifScaling::None, + FieldMatcher::build() + .set(DifVifKey("01FF9300")) + ); + + addNumericFieldWithExtractor( + "ct_numerator", + "Current transformer ratio (numerator).", + DEFAULT_PRINT_PROPERTIES, + Quantity::Dimensionless, + VifScaling::None, + FieldMatcher::build() + .set(DifVifKey("04FFA015")), + Unit::FACTOR + ); + + addNumericFieldWithExtractor( + "vt_numerator", + "Voltage transformer ratio (numerator).", + DEFAULT_PRINT_PROPERTIES, + Quantity::Dimensionless, + VifScaling::None, + FieldMatcher::build() + .set(DifVifKey("04FFA115")), + Unit::FACTOR + ); + + addNumericFieldWithExtractor( + "ct_denominator", + "Current transformer ratio (denominator).", + DEFAULT_PRINT_PROPERTIES, + Quantity::Dimensionless, + VifScaling::None, + FieldMatcher::build() + .set(DifVifKey("04FFA215")), + Unit::FACTOR + ); + + addNumericFieldWithExtractor( + "vt_denominator", + "Voltage transformer ratio (denominator).", + DEFAULT_PRINT_PROPERTIES, + Quantity::Dimensionless, + VifScaling::None, + FieldMatcher::build() + .set(DifVifKey("04FFA315")), + Unit::FACTOR + ); + + addStringFieldWithExtractorAndLookup( + "error_flags", + "Error flags.", + DEFAULT_PRINT_PROPERTIES | PrintProperty::INJECT_INTO_STATUS, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(DifVifKey("07FFA600")), + Translate::Lookup() + .add(Translate::Rule("ERROR_FLAGS", Translate::Type::BitToString) + .set(MaskBits(0xffffffffffffffff)) + .set(DefaultMessage("OK")) + )); + + addStringFieldWithExtractorAndLookup( + "warning_flags", + "Warning flags.", + DEFAULT_PRINT_PROPERTIES | PrintProperty::INJECT_INTO_STATUS, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(DifVifKey("07FFA700")), + Translate::Lookup() + .add(Translate::Rule("WARNING_FLAGS", Translate::Type::BitToString) + .set(MaskBits(0xffffffffffffffff)) + .set(DefaultMessage("OK")) + )); + + addStringFieldWithExtractorAndLookup( + "information_flags", + "Information flags.", + DEFAULT_PRINT_PROPERTIES, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(DifVifKey("07FFA800")), + Translate::Lookup() + .add(Translate::Rule("INFORMATION_FLAGS", Translate::Type::BitToString) + .set(MaskBits(0xffffffffffffffff)) + .set(DefaultMessage("")) + )); + + addStringFieldWithExtractorAndLookup( + "alarm_flags", + "alarm flags.", + DEFAULT_PRINT_PROPERTIES | PrintProperty::INJECT_INTO_STATUS, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(DifVifKey("07FFA900")), + Translate::Lookup() + .add(Translate::Rule("ALARM_FLAGS", Translate::Type::BitToString) + .set(MaskBits(0xffffffffffffffff)) + .set(DefaultMessage("OK")) + )); + addStringFieldWithExtractor( "firmware_version", "Firmware version.", @@ -53,7 +207,7 @@ namespace FieldMatcher::build() .set(MeasurementType::Instantaneous) .set(VIFRange::FirmwareVersion) - .add(VIFCombinable::Any) // Stupid 00 combinable.... + .add(VIFCombinableRaw(0)) // Stupid 00 combinable.... ); addStringFieldWithExtractor( @@ -62,10 +216,175 @@ namespace DEFAULT_PRINT_PROPERTIES, FieldMatcher::build(). set(DifVifKey("0DFFAA00"))); + + addNumericFieldWithExtractor( + "power_fail", + "Power fail counter.", + DEFAULT_PRINT_PROPERTIES, + Quantity::Dimensionless, + VifScaling::None, + FieldMatcher::build() + .set(DifVifKey("04FF9800")) + ); + + addNumericFieldWithExtractor( + "active_consumption", + "Instantaneous total active imported power.", + DEFAULT_PRINT_PROPERTIES, + Quantity::Power, + VifScaling::Auto, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::AnyPowerVIF) + .add(VIFCombinableRaw(0)) // Stupid 00 combinable.... + ); + + addNumericFieldWithExtractor( + "reactive_consumption", + "Instantaneous total reactive imported power.", + DEFAULT_PRINT_PROPERTIES, + Quantity::Power, + VifScaling::AutoSigned, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::AnyPowerVIF) + .set(SubUnitNr(2)) + .add(VIFCombinableRaw(0)) // Stupid 00 combinable.... + ); + + addNumericFieldWithExtractor( + "apparent_consumption", + "Instantaneous total apparent imported power.", + DEFAULT_PRINT_PROPERTIES, + Quantity::Power, + VifScaling::AutoSigned, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::AnyPowerVIF) + .set(SubUnitNr(4)) + .add(VIFCombinableRaw(0)) // Stupid 00 combinable.... + ); + + addNumericFieldWithExtractor( + "voltage_l1_n", + "Instantaneous voltage between L1 and neutral.", + DEFAULT_PRINT_PROPERTIES, + Quantity::Voltage, + VifScaling::AutoSigned, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::Voltage) + .add(VIFCombinableRaw(0x7f01)) + .add(VIFCombinableRaw(0)) // Stupid 00 combinable.... + ); + + addNumericFieldWithExtractor( + "voltage_l2_n", + "Instantaneous voltage between L2 and neutral.", + DEFAULT_PRINT_PROPERTIES, + Quantity::Voltage, + VifScaling::AutoSigned, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::Voltage) + .add(VIFCombinableRaw(0x7f02)) + .add(VIFCombinableRaw(0)) // Stupid 00 combinable.... + ); + + addNumericFieldWithExtractor( + "voltage_l3_n", + "Instantaneous voltage between L3 and neutral.", + DEFAULT_PRINT_PROPERTIES, + Quantity::Voltage, + VifScaling::AutoSigned, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::Voltage) + .add(VIFCombinableRaw(0x7f03)) + .add(VIFCombinableRaw(0)) // Stupid 00 combinable.... + ); + + addNumericFieldWithExtractor( + "current_l1", + "Instantaneous current in the L1 phase.", + DEFAULT_PRINT_PROPERTIES, + Quantity::Amperage, + VifScaling::AutoSigned, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::Amperage) + .add(VIFCombinableRaw(0x7f01)) + .add(VIFCombinableRaw(0)) // Stupid 00 combinable.... + ); + + addNumericFieldWithExtractor( + "current_l2", + "Instantaneous current in the L2 phase.", + DEFAULT_PRINT_PROPERTIES, + Quantity::Amperage, + VifScaling::AutoSigned, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::Amperage) + .add(VIFCombinableRaw(0x7f02)) + .add(VIFCombinableRaw(0)) // Stupid 00 combinable.... + ); + + addNumericFieldWithExtractor( + "current_l3", + "Instantaneous current in the L3 phase.", + DEFAULT_PRINT_PROPERTIES, + Quantity::Amperage, + VifScaling::AutoSigned, + FieldMatcher::build() + .set(MeasurementType::Instantaneous) + .set(VIFRange::Amperage) + .add(VIFCombinableRaw(0x7f03)) + .add(VIFCombinableRaw(0)) // Stupid 00 combinable.... + ); + + addNumericFieldWithExtractor( + "frequency", + "Frequency of AC", + DEFAULT_PRINT_PROPERTIES, + Quantity::Frequency, + VifScaling::None, + FieldMatcher::build() + .set(DifVifKey("0AFFD900")), + Unit::HZ, + 0.01 + ); + + addNumericFieldWithExtractor( + "power", + "Power factor.", + DEFAULT_PRINT_PROPERTIES, + Quantity::Dimensionless, + VifScaling::None, + FieldMatcher::build() + .set(DifVifKey("02FFE000")), + Unit::FACTOR, + 0.001 + ); + + addNumericFieldWithExtractor( + "energy_co2", + "Energy in co2.", + DEFAULT_PRINT_PROPERTIES, + Quantity::Mass, + VifScaling::None, + FieldMatcher::build() + .set(DifVifKey("0EFFF9C400")), + Unit::KG, + 0.001 + ); + + } } -// Test: ABBmeter abbb23 33221100 NOKEY -// telegram=|844442040011223320027A3E000020_0E840017495200000004FFA0150000000004FFA1150000000004FFA2150000000004FFA3150000000007FFA600000000000000000007FFA700000000000000000007FFA800000000000000000007FFA90000000000000000000DFD8E0007302E38322E31420DFFAA000B3030312D313131203332421F| -// {"media":"electricity","meter":"abbb23","name":"ABBmeter","id":"33221100","total_energy_consumption_kwh":5249.17,"firmware_version": "B1.28.0","product_no": "B23 111-100","timestamp":"1111-11-11T11:11:11Z"} -// |ABBmeter;33221100;5249.17;1111-11-11 11:11.11 +/* / Test: ABBmeter abbb23 33221100 NOKEY + / telegram=|844442040011223320027A3E000020_0E840017495200000004FFA0150000000004FFA1150000000004FFA2150000000004FFA3150000000007FFA600000000000000000007FFA700000000000000000007FFA800000000000000000007FFA90000000000000000000DFD8E0007302E38322E31420DFFAA000B3030312D313131203332421F| + / {"media":"electricity","meter":"abbb23","name":"ABBmeter","id":"33221100","total_energy_consumption_kwh":5249.17,"firmware_version": "B1.28.0","product_no": "B23 111-100","timestamp":"1111-11-11T11:11:11Z"} + / |ABBmeter;33221100;5249.17;1111-11-11 11:11.11 +*/ diff --git a/src/driver_apator08.cc b/src/driver_apator08.cc index 6b06473..58cd309 100644 --- a/src/driver_apator08.cc +++ b/src/driver_apator08.cc @@ -61,7 +61,7 @@ namespace string total; strprintf(&total, "%02x%02x%02x%02x", content[0], content[1], content[2], content[3]); - vendor_values["0413"] = { 25, DVEntry(25, DifVifKey("0413"), MeasurementType::Instantaneous, 0x13, {}, 0, 0, 0, total) }; + vendor_values["0413"] = { 25, DVEntry(25, DifVifKey("0413"), MeasurementType::Instantaneous, 0x13, {}, {}, 0, 0, 0, total) }; int offset; string key; if(findKey(MeasurementType::Instantaneous, VIFRange::Volume, 0, 0, &key, &vendor_values)) diff --git a/src/driver_apator162.cc b/src/driver_apator162.cc index 416196a..fc66a5d 100644 --- a/src/driver_apator162.cc +++ b/src/driver_apator162.cc @@ -119,7 +119,7 @@ namespace string total; strprintf(&total, "%02x%02x%02x%02x", content[i+0], content[i+1], content[i+2], content[i+3]); int offset = i-1+t->header_size; - vendor_values["0413"] = {offset, DVEntry(offset, DifVifKey("0413"), MeasurementType::Instantaneous, 0x13, {}, 0, 0, 0, total) }; + vendor_values["0413"] = {offset, DVEntry(offset, DifVifKey("0413"), MeasurementType::Instantaneous, 0x13, {}, {}, 0, 0, 0, total) }; double total_water_consumption_m3 {}; extractDVdouble(&vendor_values, "0413", &offset, &total_water_consumption_m3); total = "*** 10-"+total+" total consumption (%f m3)"; diff --git a/src/driver_apator172.cc b/src/driver_apator172.cc index 8ef232f..9b04b50 100644 --- a/src/driver_apator172.cc +++ b/src/driver_apator172.cc @@ -64,7 +64,7 @@ namespace string total; strprintf(&total, "%02x%02x%02x%02x", content[i+0], content[i+1], content[i+2], content[i+3]); int offset = i-1+t->header_size; - vendor_values["0413"] = {offset, DVEntry(offset, DifVifKey("0413"), MeasurementType::Instantaneous, 0x13, {}, 0, 0, 0, total) }; + vendor_values["0413"] = {offset, DVEntry(offset, DifVifKey("0413"), MeasurementType::Instantaneous, 0x13, {}, {}, 0, 0, 0, total) }; double tmp = 0; extractDVdouble(&vendor_values, "0413", &offset, &tmp); // Single tick seems to be 1/3 of a m3. Divide by 3 and keep a single decimal. diff --git a/src/driver_compact5.cc b/src/driver_compact5.cc index 9c1b553..5872c20 100644 --- a/src/driver_compact5.cc +++ b/src/driver_compact5.cc @@ -80,7 +80,7 @@ namespace string prevs; strprintf(&prevs, "%02x%02x", prev_lo, prev_hi); int offset = t->parsed.size()+3; - vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, prevs) }; + vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, {}, 0, 0, 0, prevs) }; Explanation pe(offset, 2, prevs, KindOfData::CONTENT, Understanding::FULL); t->explanations.push_back(pe); t->addMoreExplanation(offset, " energy used in previous billing period (%f KWH)", prev); @@ -92,7 +92,7 @@ namespace string currs; strprintf(&currs, "%02x%02x", curr_lo, curr_hi); offset = t->parsed.size()+7; - vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, currs) }; + vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, {}, 0, 0, 0, currs) }; Explanation ce(offset, 2, currs, KindOfData::CONTENT, Understanding::FULL); t->explanations.push_back(ce); t->addMoreExplanation(offset, " energy used in current billing period (%f KWH)", curr); diff --git a/src/driver_vario451.cc b/src/driver_vario451.cc index 2e7b601..536a6d2 100644 --- a/src/driver_vario451.cc +++ b/src/driver_vario451.cc @@ -76,7 +76,7 @@ namespace string prevs; strprintf(&prevs, "%02x%02x", prev_lo, prev_hi); int offset = t->parsed.size()+3; - vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, prevs) }; + vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, {}, 0, 0, 0, prevs) }; t->explanations.push_back(Explanation(offset, 2, prevs, KindOfData::CONTENT, Understanding::FULL)); t->addMoreExplanation(offset, " energy used in previous billing period (%f GJ)", prev_gj); @@ -87,7 +87,7 @@ namespace string currs; strprintf(&currs, "%02x%02x", curr_lo, curr_hi); offset = t->parsed.size()+7; - vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, 0, 0, 0, currs) }; + vendor_values["0215"] = { offset, DVEntry(offset, DifVifKey("0215"), MeasurementType::Instantaneous, 0x15, {}, {}, 0, 0, 0, currs) }; t->explanations.push_back(Explanation(offset, 2, currs, KindOfData::CONTENT, Understanding::FULL)); t->addMoreExplanation(offset, " energy used in current billing period (%f GJ)", curr_gj); diff --git a/src/dvparser.cc b/src/dvparser.cc index 2c7d7be..3f72716 100644 --- a/src/dvparser.cc +++ b/src/dvparser.cc @@ -319,6 +319,7 @@ bool parseDV(Telegram *t, int combinable_full_vif = 0; bool combinable_extension_vif = false; set found_combinable_vifs; + set found_combinable_vifs_raw; DEBUG_PARSER("(dvparser debug) vif=%04x \"%s\"\n", vif, vifType(vif).c_str()); @@ -407,6 +408,8 @@ bool parseDV(Telegram *t, combinable_extension_vif = false; VIFCombinable vc = toVIFCombinable(combinable_full_vif); found_combinable_vifs.insert(vc); + found_combinable_vifs_raw.insert(combinable_full_vif); + if (data_has_difvifs) { t->addExplanationAndIncrementPos(*format, 1, KindOfData::PROTOCOL, Understanding::FULL, @@ -432,6 +435,8 @@ bool parseDV(Telegram *t, { VIFCombinable vc = toVIFCombinable(combinable_full_vif); found_combinable_vifs.insert(vc); + found_combinable_vifs_raw.insert(combinable_full_vif); + if (data_has_difvifs) { t->addExplanationAndIncrementPos(*format, 1, KindOfData::PROTOCOL, Understanding::FULL, @@ -487,6 +492,7 @@ bool parseDV(Telegram *t, mt, Vif(full_vif), found_combinable_vifs, + found_combinable_vifs_raw, StorageNr(storage_nr), TariffNr(tariff), SubUnitNr(subunit), @@ -1127,12 +1133,13 @@ double DVEntry::getCounter(DVEntryCounterType ct) string DVEntry::str() { string s = - tostrprintf("%d: %s %s vif=%x %s st=%d ta=%d su=%d", + tostrprintf("%d: %s %s vif=%x %s%s st=%d ta=%d su=%d", offset, dif_vif_key.str().c_str(), toString(measurement_type), vif.intValue(), combinable_vifs.size() > 0 ? "HASCOMB ":"", + combinable_vifs_raw.size() > 0 ? "HASCOMBRAW ":"", storage_nr.intValue(), tariff_nr.intValue(), subunit_nr.intValue() @@ -1248,15 +1255,28 @@ bool FieldMatcher::matches(DVEntry &dv_entry) // So far so good, now test the combinables. // If field matcher has no combinables, then do NOT match any dventry with a combinable! - if (vif_combinables.size()== 0) + if (vif_combinables.size()== 0 && vif_combinables_raw.size() == 0) { - if (dv_entry.combinable_vifs.size() == 0) return true; + // If there is a combinable vif, then there is a raw combinable vif. So comparing both not strictly necessary. + if (dv_entry.combinable_vifs.size() == 0 && dv_entry.combinable_vifs_raw.size() == 0) return true; // Oups, field matcher does not expect any combinables, but the dv_entry has combinables. // This means no match for us since combinables must be handled explicitly. return false; } + // Lets check that the dv_entry vif combinables raw contains the field matcher requested combinable raws. + // The raws are used for meters using reserved and manufacturer specific vif combinables. + for (uint16_t vcr : vif_combinables_raw) + { + if (dv_entry.combinable_vifs_raw.count(vcr) == 0) + { + // Ouch, one of the requested vif combinables raw did not exist in the dv_entry. No match! + return false; + } + } + // Lets check that the dv_entry combinables contains the field matcher requested combinables. + // The named vif combinables are used by well behaved meters. for (VIFCombinable vc : vif_combinables) { if (vc != VIFCombinable::Any && dv_entry.combinable_vifs.count(vc) == 0) @@ -1270,12 +1290,26 @@ bool FieldMatcher::matches(DVEntry &dv_entry) // then we need to check if there are unmatched combinables in the telegram, if so fail the match. if (vif_combinables.count(VIFCombinable::Any) == 0) { - for (VIFCombinable vc : dv_entry.combinable_vifs) + if (vif_combinables.size() > 0) { - if (vif_combinables.count(vc) == 0) + for (VIFCombinable vc : dv_entry.combinable_vifs) { - // Oups, the telegram entry had a combinable that we had no matcher for. - return false; + if (vif_combinables.count(vc) == 0) + { + // Oups, the telegram entry had a vif combinable that we had no matcher for. + return false; + } + } + } + else + { + for (uint16_t vcr : dv_entry.combinable_vifs_raw) + { + if (vif_combinables_raw.count(vcr) == 0) + { + // Oups, the telegram entry had a vif combinable raw that we had no matcher for. + return false; + } } } } diff --git a/src/dvparser.h b/src/dvparser.h index 9846598..9c584e9 100644 --- a/src/dvparser.h +++ b/src/dvparser.h @@ -193,6 +193,11 @@ LIST_OF_VIF_COMBINABLES #undef X }; +struct VIFCombinableRaw { + VIFCombinableRaw(uint16_t v) : value(v) {} + uint16_t value; +}; + VIFCombinable toVIFCombinable(int i); const char *toString(VIFCombinable v); @@ -322,6 +327,7 @@ struct DVEntry MeasurementType measurement_type; Vif vif; std::set combinable_vifs; + std::set combinable_vifs_raw; StorageNr storage_nr; TariffNr tariff_nr; SubUnitNr subunit_nr; @@ -332,6 +338,7 @@ struct DVEntry MeasurementType mt, Vif vi, std::set vc, + std::set vc_raw, StorageNr st, TariffNr ta, SubUnitNr su, @@ -341,6 +348,7 @@ struct DVEntry measurement_type(mt), vif(vi), combinable_vifs(vc), + combinable_vifs_raw(vc_raw), storage_nr(st), tariff_nr(ta), subunit_nr(su), @@ -393,6 +401,7 @@ struct FieldMatcher // Match any vif combinables. std::set vif_combinables; + std::set vif_combinables_raw; // Match the storage nr. If no storage is specified, default to match only 0. bool match_storage_nr = true; @@ -432,6 +441,9 @@ struct FieldMatcher FieldMatcher &add(VIFCombinable v) { vif_combinables.insert(v); return *this; } + FieldMatcher &add(VIFCombinableRaw v) { + vif_combinables_raw.insert(v.value); + return *this; } FieldMatcher &set(StorageNr s) { storage_nr_from = storage_nr_to = s; match_storage_nr = (s != AnyStorageNr); diff --git a/src/manufacturer_specificities.cc b/src/manufacturer_specificities.cc index d786907..116ad45 100644 --- a/src/manufacturer_specificities.cc +++ b/src/manufacturer_specificities.cc @@ -358,6 +358,7 @@ void qdsExtractWalkByField(Telegram *t, Meter *driver, DVEntry &mfctEntry, int p MeasurementType::Instantaneous, key.vif(), set(), + set(), AnyStorageNr, AnyTariffNr, SubUnitNr(0), diff --git a/src/meters.cc b/src/meters.cc index 6d948fd..def1437 100644 --- a/src/meters.cc +++ b/src/meters.cc @@ -311,7 +311,8 @@ void MeterCommonImplementation::addNumericFieldWithExtractor(string vname, Quantity vquantity, VifScaling vif_scaling, FieldMatcher matcher, - Unit display_unit) + Unit display_unit, + double scale) { field_infos_.emplace_back( FieldInfo(field_infos_.size(), @@ -319,6 +320,7 @@ void MeterCommonImplementation::addNumericFieldWithExtractor(string vname, vquantity, display_unit == Unit::Unknown ? defaultUnitForQuantity(vquantity) : display_unit, vif_scaling, + scale, matcher, help, print_properties, @@ -357,6 +359,7 @@ void MeterCommonImplementation::addNumericFieldWithCalculator(string vname, vquantity, display_unit == Unit::Unknown ? defaultUnitForQuantity(vquantity) : display_unit, VifScaling::Auto, + 1.0, FieldMatcher::noMatcher(), help, print_properties, @@ -396,6 +399,7 @@ void MeterCommonImplementation::addNumericFieldWithCalculatorAndMatcher(string v vquantity, display_unit == Unit::Unknown ? defaultUnitForQuantity(vquantity) : display_unit, VifScaling::Auto, + 1.0, matcher, help, print_properties, @@ -422,6 +426,7 @@ void MeterCommonImplementation::addNumericField( vquantity, display_unit == Unit::Unknown ? defaultUnitForQuantity(vquantity) : display_unit, VifScaling::None, + 1.0, FieldMatcher::noMatcher(), help, print_properties, @@ -445,6 +450,7 @@ void MeterCommonImplementation::addStringFieldWithExtractor(string vname, Quantity::Text, defaultUnitForQuantity(Quantity::Text), VifScaling::None, + 1.0, matcher, help, print_properties, @@ -469,6 +475,7 @@ void MeterCommonImplementation::addStringFieldWithExtractorAndLookup(string vnam Quantity::Text, defaultUnitForQuantity(Quantity::Text), VifScaling::None, + 1.0, matcher, help, print_properties, @@ -491,6 +498,7 @@ void MeterCommonImplementation::addStringField(string vname, Quantity::Text, defaultUnitForQuantity(Quantity::Text), VifScaling::None, + 1.0, FieldMatcher(), help, print_properties, @@ -1459,6 +1467,7 @@ FieldInfo::FieldInfo(int index, Quantity xuantity, Unit display_unit, VifScaling vif_scaling, + double scale, FieldMatcher matcher, string help, PrintProperties print_properties, @@ -1474,6 +1483,7 @@ FieldInfo::FieldInfo(int index, xuantity_(xuantity), display_unit_(display_unit), vif_scaling_(vif_scaling), + scale_(scale), matcher_(matcher), help_(help), print_properties_(print_properties), @@ -2173,13 +2183,19 @@ bool FieldInfo::extractNumeric(Meter *m, Telegram *t, DVEntry *dve) decoded_unit = toDefaultUnit(matcher_.vif_range); } - debug("(meter) %s %s decoded %s default %s value %g\n", + debug("(meter) %s %s decoded %s default %s value %g (scale %g)\n", toString(matcher_.vif_range), field_name.c_str(), unitToStringLowerCase(decoded_unit).c_str(), unitToStringLowerCase(display_unit_).c_str(), - extracted_double_value); + extracted_double_value, + scale()); + if (scale() != 1.0) + { + // Hardcoded scale factor for this field used for manufacturer specific values without vif units. + extracted_double_value *= scale(); + } m->setNumericValue(this, dve, display_unit_, convert(extracted_double_value, decoded_unit, display_unit_)); t->addMoreExplanation(dve->offset, renderJson(m, dve)); found = true; diff --git a/src/meters.h b/src/meters.h index ff81717..139cffa 100644 --- a/src/meters.h +++ b/src/meters.h @@ -274,6 +274,7 @@ struct FieldInfo Quantity quantity, Unit display_unit, VifScaling vif_scaling, + double scale, FieldMatcher matcher, string help, PrintProperties print_properties, @@ -290,6 +291,7 @@ struct FieldInfo Quantity xuantity() { return xuantity_; } Unit displayUnit() { return display_unit_; } VifScaling vifScaling() { return vif_scaling_; } + double scale() { return scale_; } FieldMatcher& matcher() { return matcher_; } string help() { return help_; } PrintProperties printProperties() { return print_properties_; } @@ -336,6 +338,7 @@ private: Quantity xuantity_; // Quantity: Energy, Volume Unit display_unit_; // Selected display unit for above quantity: KWH, M3 VifScaling vif_scaling_; + double scale_; // A hardcoded scale factor. Used only for manufacturer specific values with unknown units for the vifs. FieldMatcher matcher_; string help_; // Helpful information on this meters use of this value. PrintProperties print_properties_; diff --git a/src/meters_common_implementation.h b/src/meters_common_implementation.h index 77221f3..9164e42 100644 --- a/src/meters_common_implementation.h +++ b/src/meters_common_implementation.h @@ -109,7 +109,8 @@ protected: Quantity vquantity, // Value belongs to this quantity, this quantity determines the default unit. VifScaling vif_scaling, // How should any Vif value be scaled. FieldMatcher matcher, - Unit display_unit = Unit::Unknown); // If specified use this unit for the json field instead instead of the default unit. + Unit display_unit = Unit::Unknown, // If specified use this unit for the json field instead instead of the default unit. + double scale = 1.0); // A hard coded extra scale factor. Useful for manufacturer specific values. void addNumericFieldWithCalculator( string vname, // Name of value without unit, eg "total" "total_month{storagenr}" diff --git a/src/testinternals.cc b/src/testinternals.cc index f58bd4b..b4d2dc0 100644 --- a/src/testinternals.cc +++ b/src/testinternals.cc @@ -1493,6 +1493,7 @@ void test_field_matcher() MeasurementType::Instantaneous, Vif(0x13), { }, + { }, StorageNr(0), TariffNr(0), SubUnitNr(0), @@ -1522,6 +1523,7 @@ void test_field_matcher() MeasurementType::Instantaneous, Vif(0x10), { VIFCombinable::DeltaBetweenImportAndExport }, + { }, StorageNr(2), TariffNr(0), SubUnitNr(0),