Refactor gransystems driver to new format.

pull/708/head
Fredrik Öhrström 2022-11-27 17:01:15 +01:00
rodzic 7db2fa9dee
commit 073aafd31c
49 zmienionych plików z 574 dodań i 500 usunięć

Wyświetl plik

@ -9,6 +9,10 @@ using the calculation feature.
E.g. --calculate_total_gj=total_kwh
ATTENTION! The gransystems driver has been refactored to the new driver format.
The field currrent_at_phase_1_a has been renamed to current_at_phase_1_a.
The info strings in the "status" field now have underscores instead of spaces within a single string.
ATTENTION! The rfmamb driver has been refactored to the new driver format.
A bug was found and fixed where min and max relative humidity over 1h and 24h were wrong.
The field device_date_time has been renamed to device_datetime.

Wyświetl plik

@ -239,13 +239,14 @@ telegram=|37446850336633663943a2_10672c866100181c01000480794435d5000000000000000
# Test Gran-System-S electricity meter 101
telegram=||7844731e78610418010278046d0f13bc21040394030000841003690300008420032b00000084300300000000848010030000000084016d0000bc2184010394030000841103690300008421032b00000084310300000000848110030000000004fd482e09000004fd5b0000000002fb2d861304fd1700000201|
{"media":"electricity","meter":"gransystems","name":"Gran101","id":"18046178","total_energy_consumption_kwh":0.916,"voltage_at_phase_1_v":235,"voltage_at_phase_2_v":0,"voltage_at_phase_3_v":0,"currrent_at_phase_1_a":0,"currrent_at_phase_2_a":0,"currrent_at_phase_3_a":0,"frequency_hz":49.98,"status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|Gran101;18046178;0.916000;235.000000;0.000000;0.000000;0.000000;0.000000;0.000000;49.980000;OK;1111-11-11 11:11.11
{"media":"electricity","meter":"gransystems","name":"Gran101","id":"18046178","status":"OK","info":"SINGLE_PHASE_METER","total_energy_consumption_kwh":0.916,"total_energy_consumption_tariff_4_kwh":0,"total_energy_consumption_tariff_2_kwh":0.043,"total_energy_consumption_tariff_3_kwh":0,"total_energy_consumption_tariff_1_kwh":0.873,"target_datetime":"2021-01-28 00:00","target_energy_consumption_kwh":0.916,"target_energy_consumption_tariff_2_kwh":0.043,"target_energy_consumption_tariff_4_kwh":0,"target_energy_consumption_tariff_3_kwh":0,"target_energy_consumption_tariff_1_kwh":0.873,"device_datetime":"2021-01-28 19:15","voltage_at_phase_1_v":235,"current_at_phase_1_a":0,"frequency_hz":49.98,"timestamp":"1111-11-11T11:11:11Z"}
|Gran101;18046178;0.916;1111-11-11 11:11.11
# Test Gran-System-S electricity meter 301
telegram=||9e44731e17011020010278046d0813bc21040300000000841003000000008420030000000084300300000000848010030000000084016d0000bc218401030000000084110300000000842103000000008431030000000084811003000000008440fd4825090000848040fd480000000084c040fd48000000008440fd5b00000000848040fd5b0000000084c040fd5b0000000002fb2d881304fd1700000101|
{"media":"electricity","meter":"gransystems","name":"Gran301","id":"20100117","total_energy_consumption_kwh":0,"voltage_at_phase_1_v":234.1,"voltage_at_phase_2_v":0,"voltage_at_phase_3_v":0,"currrent_at_phase_1_a":0,"currrent_at_phase_2_a":0,"currrent_at_phase_3_a":0,"frequency_hz":50,"status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|Gran301;20100117;0.000000;234.100000;0.000000;0.000000;0.000000;0.000000;0.000000;50.000000;OK;1111-11-11 11:11.11
{"media":"electricity","meter":"gransystems","name":"Gran301","id":"20100117","status":"OK","info":"THREE_PHASE_METER","total_energy_consumption_kwh":0,"total_energy_consumption_tariff_2_kwh":0,"total_energy_consumption_tariff_3_kwh":0,"total_energy_consumption_tariff_4_kwh":0,"total_energy_consumption_tariff_1_kwh":0,"target_datetime":"2021-01-28 00:00","target_energy_consumption_kwh":0,"target_energy_consumption_tariff_1_kwh":0,"target_energy_consumption_tariff_2_kwh":0,"target_energy_consumption_tariff_4_kwh":0,"target_energy_consumption_tariff_3_kwh":0,"device_datetime":"2021-01-28 19:08","voltage_at_phase_2_v":0,"voltage_at_phase_3_v":0,"voltage_at_phase_1_v":234.1,"current_at_phase_1_a":0,"current_at_phase_2_a":0,"current_at_phase_3_a":0,"frequency_hz":50,"timestamp":"1111-11-11T11:11:11Z"}
|Gran301;20100117;0;1111-11-11 11:11.11
# Test Hydrometer/Diehl Metering Sharky 774 heat meter
telegram=|5E44A5112751617241047A8B0050052F2F0C0E000000000C13010000000B3B0000000C2B000000000A5A26020A5E18020B260321000AA6180000C2026CBE2BCC020E00000000CC021301000000DB023B000000DC022B000000002F2F2F2F2F|

Wyświetl plik

@ -109,7 +109,7 @@ MeterAventiesHCA::MeterAventiesHCA(MeterInfo &mi, DriverInfo &di) : MeterCommonI
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"",
{
{ 0x01, "MEASUREMENT", TestBit::Set },

Wyświetl plik

@ -96,7 +96,7 @@ MeterAventiesWM::MeterAventiesWM(MeterInfo &mi, DriverInfo &di) :
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"",
{
{ 0x01, "MEASUREMENT" },
@ -119,5 +119,5 @@ MeterAventiesWM::MeterAventiesWM(MeterInfo &mi, DriverInfo &di) :
// Test: Vatten aventieswm 61070072 NOKEY
// telegram=76442104720007612507727200076121042507B50060052F2F0413281E0700431404B60083011440B300C30114A5AF00830214CBAC00C3021463A8008303149EA500C3031433A200830414C79F00C304148F9C00830514989900C30514CF9700830614269400C30614069100830714C88B0002FD171111
// {"media":"water","meter":"aventieswm","name":"Vatten","id":"61070072","total_m3":466.472,"consumption_at_set_date_1_m3":465.96,"consumption_at_set_date_2_m3":458.88,"consumption_at_set_date_3_m3":449.65,"consumption_at_set_date_4_m3":442.35,"consumption_at_set_date_5_m3":431.07,"consumption_at_set_date_6_m3":423.98,"consumption_at_set_date_7_m3":415.23,"consumption_at_set_date_8_m3":409.03,"consumption_at_set_date_9_m3":400.79,"consumption_at_set_date_10_m3":393.2,"consumption_at_set_date_11_m3":388.63,"consumption_at_set_date_12_m3":379.26,"consumption_at_set_date_13_m3":371.26,"consumption_at_set_date_14_m3":357.84,"error_flags":"MEASUREMENT HF ERROR_FLAGS_1100","timestamp":"1111-11-11T11:11:11Z"}
// |Vatten;61070072;466.472000;MEASUREMENT HF ERROR_FLAGS_1100;1111-11-11 11:11.11
// {"media":"water","meter":"aventieswm","name":"Vatten","id":"61070072","total_m3":466.472,"consumption_at_set_date_1_m3":465.96,"consumption_at_set_date_2_m3":458.88,"consumption_at_set_date_3_m3":449.65,"consumption_at_set_date_4_m3":442.35,"consumption_at_set_date_5_m3":431.07,"consumption_at_set_date_6_m3":423.98,"consumption_at_set_date_7_m3":415.23,"consumption_at_set_date_8_m3":409.03,"consumption_at_set_date_9_m3":400.79,"consumption_at_set_date_10_m3":393.2,"consumption_at_set_date_11_m3":388.63,"consumption_at_set_date_12_m3":379.26,"consumption_at_set_date_13_m3":371.26,"consumption_at_set_date_14_m3":357.84,"error_flags":"ERROR_FLAGS_1100 HF MEASUREMENT","timestamp":"1111-11-11T11:11:11Z"}
// |Vatten;61070072;466.472000;ERROR_FLAGS_1100 HF MEASUREMENT;1111-11-11 11:11.11

Wyświetl plik

@ -101,7 +101,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::DecimalsToString,
9999,
AlwaysTrigger, MaskBits(9999),
"OK",
{
{ 2000, "VERIFICATION_EXPIRED" }, // Status initial verification expired

Wyświetl plik

@ -77,21 +77,10 @@ namespace
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::DigitalInput),
Translate::Lookup(
{
{
{
"BATTERY",
Translate::Type::BitToString,
0xffff,
"",
{
}
},
},
}));
Translate::Lookup()
.add(Translate::Rule("BATTERY", Translate::Type::BitToString)
.set(MaskBits(0xffff)))
);
}
}

Wyświetl plik

@ -48,7 +48,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"OK",
{
}

Wyświetl plik

@ -36,7 +36,7 @@ namespace
{
"TPL_STS",
Translate::Type::BitToString,
0xe0, // Always use 0xe0 for tpl mfct status bits.
AlwaysTrigger, MaskBits(0xe0), // Always use 0xe0 for tpl mfct status bits.
"OK",
{
{ 0x40, "RTC_INVALID" }
@ -62,7 +62,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"OK",
{
/* When used for m-bus?
@ -216,7 +216,7 @@ namespace
{
"DUST",
Translate::Type::IndexToString,
0x1f,
AlwaysTrigger, MaskBits(0x1f),
"",
{
}
@ -236,7 +236,7 @@ namespace
{
"BATTERY_VOLTAGE",
Translate::Type::IndexToString,
0x0f00,
AlwaysTrigger, MaskBits(0x0f00),
"",
{
{ 0x0000, "2.25V" },
@ -275,7 +275,7 @@ namespace
{
"OBSTACLE_DISTANCE",
Translate::Type::IndexToString,
0x700000,
AlwaysTrigger, MaskBits(0x700000),
"",
{
{ 0x000000, "SEODS_NOT_COMPLETED" },
@ -305,7 +305,7 @@ namespace
{
"HEAD_STATUS",
Translate::Type::BitToString,
0xff8ff0e0,
AlwaysTrigger, MaskBits(0xff8ff0e0),
"OK",
{
/* 0x00000000-0x000000f dust level*/

Wyświetl plik

@ -36,7 +36,7 @@ namespace
{
"TPL_STS",
Translate::Type::BitToString,
0xe0, // Always use 0xe0 for tpl mfct status bits.
AlwaysTrigger, MaskBits(0xe0), // Always use 0xe0 for tpl mfct status bits.
"OK",
{
}
@ -60,7 +60,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffffffff,
AlwaysTrigger, MaskBits(0xffffffff),
"OK",
{
}

Wyświetl plik

@ -53,7 +53,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xff,
AlwaysTrigger, MaskBits(0xff),
"OK",
{
{ 0x01, "V_1_OVERFLOW" },
@ -80,7 +80,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xff,
AlwaysTrigger, MaskBits(0xff),
"",
{
{ 0x01, "V_1_OVERFLOW" },
@ -165,5 +165,5 @@ namespace
// Test: Elen em24 66666666 NOKEY
// telegram=|35442D2C6666666633028D2070806A0520B4D378_0405F208000004FB82753F00000004853C0000000004FB82F53CCA01000001FD1722|
// {"media":"electricity","meter":"em24","name":"Elen","id":"66666666","status":"I_3_OVERFLOW V_2_OVERFLOW","error":"V_2_OVERFLOW I_3_OVERFLOW","total_energy_consumption_kwh":229,"total_energy_production_kwh":0,"total_reactive_energy_consumption_kvarh":63,"total_reactive_energy_production_kvarh":458,"total_apparent_energy_consumption_kvah":237.507895,"total_apparent_energy_production_kvah":458,"timestamp":"1111-11-11T11:11:11Z"}
// {"media":"electricity","meter":"em24","name":"Elen","id":"66666666","status":"I_3_OVERFLOW V_2_OVERFLOW","error":"I_3_OVERFLOW V_2_OVERFLOW","total_energy_consumption_kwh":229,"total_energy_production_kwh":0,"total_reactive_energy_consumption_kvarh":63,"total_reactive_energy_production_kvarh":458,"total_apparent_energy_consumption_kvah":237.507895,"total_apparent_energy_production_kvah":458,"timestamp":"1111-11-11T11:11:11Z"}
// |Elen;66666666;229;0;63;458;237.507895;458;1111-11-11 11:11.11

Wyświetl plik

@ -47,7 +47,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xff,
AlwaysTrigger, MaskBits(0xff),
"OK",
{
{ 0x01, "VOLUME_DETECTION_COILS_DEFECT" },

Wyświetl plik

@ -50,7 +50,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"OK",
{
{ 0x0001, "MEASUREMENT" },
@ -78,7 +78,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"OK",
{
{ 0x0001, "MEASUREMENT" },

Wyświetl plik

@ -50,7 +50,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"OK",
{
// { 0x01 , "WOOT" },

Wyświetl plik

@ -52,7 +52,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffffffff,
AlwaysTrigger, MaskBits(0xffffffff),
"OK",
{
/* Maybe these are the same as the multical21 but we do not know!
@ -217,7 +217,7 @@ namespace
{
"DRY",
Translate::Type::IndexToString,
0x0070,
AlwaysTrigger, MaskBits(0x0070),
"",
{
{ 0x0000, "" },
@ -244,7 +244,7 @@ namespace
{
"REVERSED",
Translate::Type::IndexToString,
0x0380,
AlwaysTrigger, MaskBits(0x0380),
"",
{
{ 0x0000, "" },
@ -271,7 +271,7 @@ namespace
{
"LEAKING",
Translate::Type::IndexToString,
0x1c00,
AlwaysTrigger, MaskBits(0x1c00),
"",
{
{ 0x0000, "" },
@ -298,7 +298,7 @@ namespace
{
"BURSTING",
Translate::Type::IndexToString,
0xe000,
AlwaysTrigger, MaskBits(0xe000),
"",
{
{ 0x0000, "" },

Wyświetl plik

@ -0,0 +1,287 @@
/*
Copyright (C) 2018-2022 Fredrik Öhrström (gpl-3.0-or-later)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
Implemented January 2021 Xael South:
Implements GSS, CC101 and CC301 energy meter.
This T1 WM-Bus meter broadcasts:
- accumulated energy consumption
- accumulated energy consumption till yesterday
- current date
- actually measured voltage
- actually measured current
- actually measured frequency
- meter status and errors
The single-phase and three-phase send apparently the same datagram:
three-phase meter sends voltage and current values for every phase
L1 .. L3.
Meter version. Implementation tested against meters:
- CC101 one-phase with firmware version 0x01.
- CC301 three-phase with firmware version 0x01.
Encryption: None.
*/
#include"meters_common_implementation.h"
namespace
{
struct Driver : public virtual MeterCommonImplementation
{
Driver(MeterInfo &mi, DriverInfo &di);
};
static bool ok = registerDriver([](DriverInfo&di)
{
di.setName("gransystems");
di.setDefaultFields("name,id,total_energy_consumption_kwh,timestamp");
di.setMeterType(MeterType::ElectricityMeter);
di.addLinkMode(LinkMode::T1);
di.addDetection(MANUFACTURER_GSS, 0x02, 0x01);
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
});
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
{
addStringFieldWithExtractorAndLookup(
"status",
"Status of meter.",
PrintProperty::JSON | PrintProperty::STATUS | PrintProperty::JOIN_TPL_STATUS,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::ErrorFlags),
{
{
{
"ERROR_FLAGS",
Translate::Type::BitToString,
AlwaysTrigger,
AutoMask,
"",
{
{ 0x0001, "METER_HARDWARE_ERROR" },
{ 0x0002, "RTC_ERROR" },
{ 0x0100, "DSP_COMMUNICATION_ERROR" },
{ 0x0200, "DSP_HARDWARE_ERROR" },
{ 0x4000, "RAM_ERROR" },
{ 0x8000, "ROM_ERROR" }
}
},
{
"ERROR_FLAGS_SINGLE_PHASE",
Translate::Type::BitToString,
TriggerBits(0x01020000),
AutoMask,
"",
{
{ 0x0008, "DEVICE_NOT_CONFIGURED" },
{ 0x0010, "INTERNAL_ERROR" },
{ 0x0020, "BATTERY_LOW" },
{ 0x0040, "MAGNETIC_FRAUD_PRESENT" },
{ 0x0080, "MAGNETIC_FRAUD_PAST" },
}
},
{
"ERROR_FLAGS_THREE_PHASE",
Translate::Type::BitToString,
TriggerBits(0x01010000),
AutoMask,
"",
{
{ 0x0008, "CALIBRATION_EEPROM_ERROR" },
{ 0x0010, "NETWORK_INTERFERENCE" },
{ 0x0800, "CALIBRATION_EEPROM_ERROR" },
{ 0x1000, "EEPROM1_ERROR" }
}
},
},
});
addStringFieldWithExtractorAndLookup(
"info",
"Is it a three phase or single phase meter.",
PrintProperty::JSON,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::ErrorFlags),
{
{
{
"INFO_FLAGS",
Translate::Type::IndexToString,
AlwaysTrigger,
AutoMask,
"",
{
{ 0x01020000, "SINGLE_PHASE_METER" },
{ 0x01010000, "THREE_PHASE_METER" }
}
}
},
});
addNumericFieldWithExtractor(
"total_energy_consumption",
"The total energy consumption recorded by this meter.",
PrintProperty::JSON,
Quantity::Energy,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::EnergyWh)
);
addNumericFieldWithExtractor(
"total_energy_consumption_tariff_{tariff_counter}",
"The total energy consumption recorded by this meter.",
PrintProperty::JSON,
Quantity::Energy,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::EnergyWh)
.set(TariffNr(1), TariffNr(4))
);
addNumericFieldWithExtractor(
"target",
"Last day?",
PrintProperty::JSON,
Quantity::PointInTime,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::DateTime)
.set(StorageNr(2))
);
addNumericFieldWithExtractor(
"target_energy_consumption",
"Last day energy consumption?",
PrintProperty::JSON,
Quantity::Energy,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::EnergyWh)
.set(StorageNr(2))
);
addNumericFieldWithExtractor(
"target_energy_consumption_tariff_{tariff_counter}",
"Last day energy consumption for tariff?",
PrintProperty::JSON,
Quantity::Energy,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::EnergyWh)
.set(TariffNr(1), TariffNr(4))
.set(StorageNr(2))
);
addNumericFieldWithExtractor(
"device",
"Device date time when telegram was sent.",
PrintProperty::JSON,
Quantity::PointInTime,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::DateTime)
);
addNumericFieldWithExtractor(
"voltage_at_phase_1",
"Voltage for single phase meter.",
PrintProperty::JSON | PrintProperty::OPTIONAL,
Quantity::Voltage,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::Voltage)
.set(SubUnitNr(0))
);
addNumericFieldWithExtractor(
"voltage_at_phase_{subunit_counter}",
"Voltage at phase L#.",
PrintProperty::JSON | PrintProperty::OPTIONAL,
Quantity::Voltage,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::Voltage)
.set(SubUnitNr(1), SubUnitNr(3))
);
addNumericFieldWithExtractor(
"current_at_phase_1",
"Amperage for single phase meter.",
PrintProperty::JSON | PrintProperty::OPTIONAL,
Quantity::Amperage,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::Amperage)
.set(SubUnitNr(0))
);
addNumericFieldWithExtractor(
"current_at_phase_{subunit_counter}",
"Amperage at phase L#.",
PrintProperty::JSON | PrintProperty::OPTIONAL,
Quantity::Amperage,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::Amperage)
.set(SubUnitNr(1), SubUnitNr(3))
);
addNumericFieldWithExtractor(
"raw_frequency",
"Raw input to frequency.",
PrintProperty::JSON | PrintProperty::HIDE,
Quantity::Frequency,
VifScaling::None,
FieldMatcher::build()
.set(DifVifKey("02FB2D"))
);
addNumericFieldWithCalculator(
"frequency",
"Frequency of AC.",
PrintProperty::JSON,
Quantity::Frequency,
"raw_frequency_hz / 100 counter");
}
}
// Test: Gran101 gransystems 18046178 NOKEY
// Comment: Gran-System-S electricity meter 101
// telegram=|7844731e78610418010278046d0f13bc21040394030000841003690300008420032b00000084300300000000848010030000000084016d0000bc2184010394030000841103690300008421032b00000084310300000000848110030000000004fd482e09000004fd5b0000000002fb2d861304fd1700000201|
// {"media":"electricity","meter":"gransystems","name":"Gran101","id":"18046178","status":"OK","info":"SINGLE_PHASE_METER","total_energy_consumption_kwh":0.916,"total_energy_consumption_tariff_4_kwh":0,"total_energy_consumption_tariff_2_kwh":0.043,"total_energy_consumption_tariff_3_kwh":0,"total_energy_consumption_tariff_1_kwh":0.873,"target_datetime":"2021-01-28 00:00","target_energy_consumption_kwh":0.916,"target_energy_consumption_tariff_2_kwh":0.043,"target_energy_consumption_tariff_4_kwh":0,"target_energy_consumption_tariff_3_kwh":0,"target_energy_consumption_tariff_1_kwh":0.873,"device_datetime":"2021-01-28 19:15","voltage_at_phase_1_v":235,"current_at_phase_1_a":0,"frequency_hz":49.98,"timestamp":"1111-11-11T11:11:11Z"}
// |Gran101;18046178;0.916;1111-11-11 11:11.11
// Test: Gran301 gransystems 20100117 NOKEY
// Comment: Gran-System-S electricity meter 301
// telegram=|9e44731e17011020010278046d0813bc21040300000000841003000000008420030000000084300300000000848010030000000084016d0000bc218401030000000084110300000000842103000000008431030000000084811003000000008440fd4825090000848040fd480000000084c040fd48000000008440fd5b00000000848040fd5b0000000084c040fd5b0000000002fb2d881304fd1702000101|
// {"media":"electricity","meter":"gransystems","name":"Gran301","id":"20100117","status":"RTC_ERROR","info":"THREE_PHASE_METER","total_energy_consumption_kwh":0,"total_energy_consumption_tariff_2_kwh":0,"total_energy_consumption_tariff_3_kwh":0,"total_energy_consumption_tariff_4_kwh":0,"total_energy_consumption_tariff_1_kwh":0,"target_datetime":"2021-01-28 00:00","target_energy_consumption_kwh":0,"target_energy_consumption_tariff_1_kwh":0,"target_energy_consumption_tariff_2_kwh":0,"target_energy_consumption_tariff_4_kwh":0,"target_energy_consumption_tariff_3_kwh":0,"device_datetime":"2021-01-28 19:08","voltage_at_phase_2_v":0,"voltage_at_phase_3_v":0,"voltage_at_phase_1_v":234.1,"current_at_phase_1_a":0,"current_at_phase_2_a":0,"current_at_phase_3_a":0,"frequency_hz":50,"timestamp":"1111-11-11T11:11:11Z"}
// |Gran301;20100117;0;1111-11-11 11:11.11

Wyświetl plik

@ -85,7 +85,7 @@ namespace
{
"TPL_FLAGS",
Translate::Type::IndexToString,
0xe0,
AlwaysTrigger, MaskBits(0xe0),
"OK",
{
{ 0x20, "AIR_IN_PIPE" },

Wyświetl plik

@ -59,7 +59,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffffff,
AlwaysTrigger, MaskBits(0xffffff),
"OK",
{
// No known layout for field
@ -101,7 +101,7 @@ namespace
{
"WOOTA",
Translate::Type::BitToString,
0xffffffff,
AlwaysTrigger, MaskBits(0xffffffff),
"",
{
// No known layout for field
@ -121,7 +121,7 @@ namespace
{
"WOOTB",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"",
{
// No known layout for field

Wyświetl plik

@ -46,7 +46,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"OK",
{
{ 0x01, "DROP" }, // Unexpected drop in pressure in relation to average pressure.

Wyświetl plik

@ -38,7 +38,7 @@ namespace
{
"TPL_STS",
Translate::Type::BitToString,
0xe0, // Always use 0xe0 for tpl mfct status bits.
AlwaysTrigger, MaskBits(0xe0), // Always use 0xe0 for tpl mfct status bits.
"OK",
{
{ 0x40, "SABOTAGE_ENCLOSURE" }

Wyświetl plik

@ -57,7 +57,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"OK",
{
{ 0x0004, "SMOKE" },

Wyświetl plik

@ -36,7 +36,7 @@ namespace
{
"TPL_STS",
Translate::Type::BitToString,
0xe0, // Always use 0xe0 for tpl mfct status bits.
AlwaysTrigger, MaskBits(0xe0), // Always use 0xe0 for tpl mfct status bits.
"OK",
{
{ 0x40, "SABOTAGE_ENCLOSURE" }

Wyświetl plik

@ -105,7 +105,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"OK",
{
// { 0x01, "?" },

Wyświetl plik

@ -52,7 +52,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xff,
AlwaysTrigger, MaskBits(0xff),
"OK",
{
// Bits unknown

Wyświetl plik

@ -199,7 +199,7 @@ MeterMicroClima::MeterMicroClima(MeterInfo &mi, DriverInfo &di) : MeterCommonImp
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"OK",
{
{ 0x01, "?" },

Wyświetl plik

@ -129,7 +129,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"OK",
{
{ 0x8000, "WAS_REMOVED" },

Wyświetl plik

@ -45,22 +45,15 @@ namespace
PrintProperty::JSON | PrintProperty::FIELD | PrintProperty::IMPORTANT | PrintProperty::STATUS,
FieldMatcher::build()
.set(DifVifKey("02FF20")),
{
{
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0x000f,
"OK",
{
{ 0x01 , "DRY" },
{ 0x02 , "REVERSE" },
{ 0x04 , "LEAK" },
{ 0x08 , "BURST" },
}
},
},
});
Translate::Lookup()
.add(Translate::Rule("ERROR_FLAGS", Translate::Type::BitToString)
.set(MaskBits(0x000f))
.set(DefaultMessage("OK"))
.add(Translate::Map(0x01 ,"DRY", TestBit::Set))
.add(Translate::Map(0x02 ,"REVERSE", TestBit::Set))
.add(Translate::Map(0x04 ,"LEAK", TestBit::Set))
.add(Translate::Map(0x08 ,"BURST", TestBit::Set))
));
addNumericFieldWithExtractor(
"total",
@ -144,7 +137,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0x000f,
AlwaysTrigger, MaskBits(0x000f),
"",
{
{ 0x01 , "DRY" },
@ -167,7 +160,7 @@ namespace
{
"DRY",
Translate::Type::IndexToString,
0x0070,
AlwaysTrigger, MaskBits(0x0070),
"",
{
{ 0x0000, "" },
@ -194,7 +187,7 @@ namespace
{
"REVERSED",
Translate::Type::IndexToString,
0x0380,
AlwaysTrigger, MaskBits(0x0380),
"",
{
{ 0x0000, "" },
@ -221,7 +214,7 @@ namespace
{
"LEAKING",
Translate::Type::IndexToString,
0x1c00,
AlwaysTrigger, MaskBits(0x1c00),
"",
{
{ 0x0000, "" },
@ -248,7 +241,7 @@ namespace
{
"BURSTING",
Translate::Type::IndexToString,
0xe000,
AlwaysTrigger, MaskBits(0xe000),
"",
{
{ 0x0000, "" },

Wyświetl plik

@ -125,7 +125,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"OK",
{
{ 0x0000, "OK" },

Wyświetl plik

@ -55,7 +55,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffffffff,
AlwaysTrigger, MaskBits(0xffffffff),
"OK",
{
{ 0x00000001, "VOLTAGE_INTERRUPTED" },
@ -197,7 +197,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffffffff,
AlwaysTrigger, MaskBits(0xffffffff),
"",
{
{ 0x00000001, "VOLTAGE_INTERRUPTED" },

Wyświetl plik

@ -48,7 +48,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"OK",
{
// What does these bits mean?

Wyświetl plik

@ -58,7 +58,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xff,
AlwaysTrigger, MaskBits(0xff),
"OK",
{
// Bits unknown

Wyświetl plik

@ -50,7 +50,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"OK",
{
// !!! Uncertain !!!

Wyświetl plik

@ -50,7 +50,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"OK",
{
}

Wyświetl plik

@ -51,7 +51,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffffffff,
AlwaysTrigger, MaskBits(0xffffffff),
"OK",
{
/* What do these bits mean? There are a lot of them...

Wyświetl plik

@ -145,7 +145,7 @@ MeterQWater::MeterQWater(MeterInfo &mi, DriverInfo &di) :
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"OK",
{
{ 0x01, "?" },

Wyświetl plik

@ -155,7 +155,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xff,
AlwaysTrigger, MaskBits(0xff),
"OK",
{
// based on information published here: https://www.engelmann.de/wp-content/uploads/2022/10/1080621004_2022-10-12_BA_S3_ES_Comm_en.pdf

Wyświetl plik

@ -68,7 +68,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffffff,
AlwaysTrigger, MaskBits(0xffffff),
"OK",
{
/* What are the bits?

Wyświetl plik

@ -50,7 +50,7 @@ namespace
{
"STATUS_FLAGS",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"OK",
{
}
@ -69,7 +69,7 @@ namespace
{
"OTHER_FLAGS",
Translate::Type::BitToString,
0xff,
AlwaysTrigger, MaskBits(0xff),
"",
{
}

Wyświetl plik

@ -83,7 +83,7 @@ namespace
{
"ERROR_FLAGS",
Translate::Type::BitToString,
0xffff,
AlwaysTrigger, MaskBits(0xffff),
"OK",
{
{ 0x01, "SW_ERROR" },

Wyświetl plik

@ -62,7 +62,7 @@
X(DurationOfTariff,0x7D31,0x7D33, Quantity::Time, Unit::Hour) \
X(Dimensionless,0x7D3A,0x7D3A, Quantity::Counter, Unit::COUNTER) \
X(Voltage,0x7D40,0x7D4F, Quantity::Voltage, Unit::Volt) \
X(Current,0x7D50,0x7D5F, Quantity::Current, Unit::Ampere) \
X(Amperage,0x7D50,0x7D5F, Quantity::Amperage, 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) \

Wyświetl plik

@ -272,6 +272,8 @@ void list_fields(Configuration *config, string meter_driver)
int width = 13; // Width of timestamp_utc
for (FieldInfo &fi : meter->fieldInfos())
{
if (fi.printProperties().hasHIDE()) continue;
string name = fi.vname();
if (fi.xuantity() != Quantity::Text)
{
@ -303,6 +305,8 @@ void list_fields(Configuration *config, string meter_driver)
printf("%s The rssi for the received telegram as reported by the device.\n", rssi.c_str());
for (auto &fi : meter->fieldInfos())
{
if (fi.printProperties().hasHIDE()) continue;
if (fi.vname() == "") continue;
string name = fi.vname();
if (fi.xuantity() != Quantity::Text)

Wyświetl plik

@ -27,7 +27,6 @@
// meter driver, manufacturer, media, version
//
#define METER_DETECTION \
X(CCx01, MANUFACTURER_GSS, 0x02, 0x01) \
X(MULTICAL302,MANUFACTURER_KAM, 0x04, 0x30) \
X(MULTICAL302,MANUFACTURER_KAM, 0x0d, 0x30) \
X(MULTICAL302,MANUFACTURER_KAM, 0x0c, 0x30) \

Wyświetl plik

@ -1,348 +0,0 @@
/*
Copyright (C) 2018-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 <http://www.gnu.org/licenses/>.
*/
/*
Implemented January 2021 Xael South:
Implements GSS, CC101 and CC301 energy meter.
This T1 WM-Bus meter broadcasts:
- accumulated energy consumption
- accumulated energy consumption till yesterday
- current date
- actually measured voltage
- actually measured current
- actually measured frequency
- meter status and errors
The single-phase and three-phase send apparently the same datagram:
three-phase meter sends voltage and current values for every phase
L1 .. L3.
Meter version. Implementation tested against meters:
- CC101 one-phase with firmware version 0x01.
- CC301 three-phase with firmware version 0x01.
Encryption: None.
*/
#include"dvparser.h"
#include"meters.h"
#include"meters_common_implementation.h"
#include"wmbus.h"
#include"wmbus_utils.h"
#include"util.h"
struct MeterGransystemsCCx01: public virtual MeterCommonImplementation {
MeterGransystemsCCx01(MeterInfo &mi);
double totalEnergyConsumption(Unit u);
string status();
private:
void processContent(Telegram *t);
static const std::size_t MAX_TARIFFS = std::size_t(4);
double current_total_energy_kwh_{};
std::size_t current_tariff_energy_kwh_idx_{};
double current_tariff_energy_kwh_[MAX_TARIFFS] {};
double last_day_total_energy_kwh_{};
std::size_t last_day_tariff_energy_kwh_idx_{};
double last_day_tariff_energy_kwh_[MAX_TARIFFS] {};
double voltage_L_[3]{0, 0, 0};
double current_L_[3]{0, 0, 0};
double frequency_{0};
bool single_phase_{};
bool three_phase_{};
uint32_t status_{};
};
shared_ptr<Meter> createCCx01(MeterInfo &mi)
{
return shared_ptr<Meter>(new MeterGransystemsCCx01(mi));
}
MeterGransystemsCCx01::MeterGransystemsCCx01(MeterInfo &mi) :
MeterCommonImplementation(mi, "gransystems")
{
setMeterType(MeterType::ElectricityMeter);
addLinkMode(LinkMode::T1);
addPrint("total_energy_consumption", Quantity::Energy,
[&](Unit u){ return totalEnergyConsumption(u); },
"The total energy consumption recorded by this meter.",
PrintProperty::FIELD | PrintProperty::JSON);
addPrint("voltage_at_phase_1", Quantity::Voltage,
[&](Unit u){ return convert(voltage_L_[0], Unit::Volt, u); },
"Voltage at phase L1.",
PrintProperty::FIELD | PrintProperty::JSON);
addPrint("voltage_at_phase_2", Quantity::Voltage,
[&](Unit u){ return convert(voltage_L_[1], Unit::Volt, u); },
"Voltage at phase L2.",
PrintProperty::FIELD | PrintProperty::JSON);
addPrint("voltage_at_phase_3", Quantity::Voltage,
[&](Unit u){ return convert(voltage_L_[2], Unit::Volt, u); },
"Voltage at phase L3.",
PrintProperty::FIELD | PrintProperty::JSON);
addPrint("currrent_at_phase_1", Quantity::Amperage,
[&](Unit u){ return convert(current_L_[0], Unit::Ampere, u); },
"Current at phase L1.",
PrintProperty::FIELD | PrintProperty::JSON);
addPrint("currrent_at_phase_2", Quantity::Amperage,
[&](Unit u){ return convert(current_L_[1], Unit::Ampere, u); },
"Current at phase L2.",
PrintProperty::FIELD | PrintProperty::JSON);
addPrint("currrent_at_phase_3", Quantity::Amperage,
[&](Unit u){ return convert(current_L_[2], Unit::Ampere, u); },
"Current at phase L3.",
PrintProperty::FIELD | PrintProperty::JSON);
addPrint("frequency", Quantity::Frequency,
[&](Unit u){ return convert(frequency_, Unit::HZ, u); },
"Frequency.",
PrintProperty::FIELD | PrintProperty::JSON);
addPrint("status", Quantity::Text,
[&](){ return status(); },
"The meter status.",
PrintProperty::FIELD | PrintProperty::JSON);
}
double MeterGransystemsCCx01::totalEnergyConsumption(Unit u)
{
assertQuantity(u, Quantity::Energy);
return convert(current_total_energy_kwh_, Unit::KWH, u);
}
void MeterGransystemsCCx01::processContent(Telegram *t)
{
int offset;
string key;
// Single-phase or three-phase meter?
key = "04FD17";
if (hasKey(&t->dv_entries, key))
{
if (extractDVuint32(&t->dv_entries, key, &offset, &status_))
{
if ((status_ & 0xFFFF0000u) == 0x01020000u)
{
single_phase_ = true;
}
else if ((status_ & 0xFFFF0000u) == 0x01010000u)
{
three_phase_ = true;
}
else
{
error("Internal error! Can't determine phase number.\n");
return;
}
}
else
{
error("Internal error! Can't detect meter type.\n");
return;
}
}
if (extractDVdouble(&t->dv_entries, "0403", &offset, &current_total_energy_kwh_))
{
t->addMoreExplanation(offset, " total energy (%f kwh)", current_total_energy_kwh_);
}
for ( current_tariff_energy_kwh_idx_ = 0;
current_tariff_energy_kwh_idx_ < MAX_TARIFFS;
current_tariff_energy_kwh_idx_++ )
{
static const std::string pattern[MAX_TARIFFS] = {"841003", "842003", "843003", "84801003"};
if (extractDVdouble(&t->dv_entries, pattern[current_tariff_energy_kwh_idx_],
&offset, &current_tariff_energy_kwh_[current_tariff_energy_kwh_idx_]))
{
t->addMoreExplanation(offset, " tariff %zu energy (%f kwh)",
current_tariff_energy_kwh_idx_ + 1, current_tariff_energy_kwh_[current_tariff_energy_kwh_idx_]);
}
}
if (extractDVdouble(&t->dv_entries, "840103", &offset, &last_day_total_energy_kwh_))
{
t->addMoreExplanation(offset, " last day total energy (%f kwh)", last_day_total_energy_kwh_);
}
for ( last_day_tariff_energy_kwh_idx_ = 0;
last_day_tariff_energy_kwh_idx_ < MAX_TARIFFS;
last_day_tariff_energy_kwh_idx_++ )
{
static const std::string pattern[MAX_TARIFFS] = {"841103", "842103", "843103", "84811003"};
if (extractDVdouble(&t->dv_entries, pattern[last_day_tariff_energy_kwh_idx_],
&offset, &last_day_tariff_energy_kwh_[last_day_tariff_energy_kwh_idx_]))
{
t->addMoreExplanation(offset, " tariff %zu last day energy (%f kwh)",
last_day_tariff_energy_kwh_idx_ + 1, last_day_tariff_energy_kwh_[last_day_tariff_energy_kwh_idx_]);
}
}
voltage_L_[0] = voltage_L_[1] = voltage_L_[2] = 0;
current_L_[0] = current_L_[1] = current_L_[2] = 0;
if (single_phase_)
{
if (extractDVdouble(&t->dv_entries, "04FD48", &offset, &voltage_L_[0], false))
{
voltage_L_[0] /= 10.;
t->addMoreExplanation(offset, " voltage (%f volts)", voltage_L_[0]);
}
if (extractDVdouble(&t->dv_entries, "04FD5B", &offset, &current_L_[0], false))
{
current_L_[0] /= 10.;
t->addMoreExplanation(offset, " current (%f ampere)", current_L_[0]);
}
}
else if (three_phase_)
{
for (std::size_t i = 0; i < 3; i++)
{
static const std::string pattern[] = {"8440FD48", "848040FD48", "84C040FD48"};
if (extractDVdouble(&t->dv_entries, pattern[i], &offset, &voltage_L_[i], false))
{
voltage_L_[i] /= 10.;
t->addMoreExplanation(offset, " voltage L%d (%f volts)", i + 1, voltage_L_[i]);
}
}
for (std::size_t i = 0; i < 3; i++)
{
static const std::string pattern[] = {"8440FD5B", "848040FD5B", "84C040FD5B"};
if (extractDVdouble(&t->dv_entries, pattern[i], &offset, &current_L_[i], false))
{
current_L_[i] /= 10.;
t->addMoreExplanation(offset, " current L%d (%f ampere)", i + 1, current_L_[i]);
}
}
}
if (extractDVdouble(&t->dv_entries, "02FB2D", &offset, &frequency_, false))
{
frequency_ /= 100.;
t->addMoreExplanation(offset, " frequency (%f hz)", frequency_);
}
}
string MeterGransystemsCCx01::status()
{
const uint16_t error_codes = status_ & 0xFFFFu;
string s;
if (error_codes == 0)
{
s.append("OK");
}
if (error_codes & 0x0001u)
{
s.append("METER HARDWARE ERROR");
}
if (error_codes & 0x0002u)
{
s.append("RTC ERROR");
}
if (error_codes & 0x0100u)
{
s.append("DSP COMMUNICATION ERROR");
}
if (error_codes & 0x0200u)
{
s.append("DSP HARDWARE ERROR");
}
if (error_codes & 0x4000u)
{
s.append("RAM ERROR");
}
if (error_codes & 0x8000u)
{
s.append("ROM ERROR");
}
if (single_phase_)
{
if (error_codes & 0x0008u)
{
s.append("DEVICE NOT CONFIGURED");
}
if (error_codes & 0x0010u)
{
s.append("INTERNAL ERROR");
}
if (error_codes & 0x0020u)
{
s.append("BATTERIE LOW");
}
if (error_codes & 0x0040u)
{
s.append("MAGNETIC FRAUD PRESENT");
}
if (error_codes & 0x0080u)
{
s.append("MAGNETIC FRAUD PAST");
}
if (error_codes & 0x0400u)
{
s.append("CALIBRATION EEPROM ERROR");
}
if (error_codes & 0x0800u)
{
s.append("EEPROM1 ERROR");
}
}
else if (three_phase_)
{
if (error_codes & 0x0008u)
{
s.append("CALIBRATION EEPROM ERROR");
}
if (error_codes & 0x0010u)
{
s.append("NETWORK INTERFERENCE");
}
if (error_codes & 0x0800u)
{
s.append("CALIBRATION EEPROM ERROR");
}
if (error_codes & 0x1000u)
{
s.append("EEPROM1 ERROR");
}
}
return s;
}

Wyświetl plik

@ -1631,13 +1631,15 @@ string MeterCommonImplementation::getStringValue(FieldInfo *fi)
if (f.printProperties().hasJOININTOSTATUS())
{
string more = getStringValue(&f);
string joined = joinStatusStrings(value, more);
string joined = joinStatusOKStrings(value, more);
value = joined;
}
}
// Sort all found flags and remove any duplicates. A well designed meter decoder
// should not be able to generate duplicates.
value = sortStatusString(value);
// If it is empty, then translate to OK!
if (value == "") value = "OK";
}
return value;
@ -1870,7 +1872,7 @@ void MeterCommonImplementation::printMeter(Telegram *t,
for (FieldInfo& fi : field_infos_)
{
if (fi.printProperties().hasJSON())
if (fi.printProperties().hasJSON() && !fi.printProperties().hasHIDE())
{
// The field should be printed in the json. (Most usually should.)
for (auto& i : t->dv_entries)
@ -1892,7 +1894,7 @@ void MeterCommonImplementation::printMeter(Telegram *t,
for (FieldInfo& fi : field_infos_)
{
if (fi.printProperties().hasJSON())
if (fi.printProperties().hasJSON() && !fi.printProperties().hasHIDE())
{
if (founds.count(&fi) != 0)
{
@ -1978,7 +1980,7 @@ void MeterCommonImplementation::printMeter(Telegram *t,
for (FieldInfo& fi : field_infos_)
{
if (fi.printProperties().hasJSON())
if (fi.printProperties().hasJSON() && !fi.printProperties().hasHIDE())
{
string default_unit = unitToStringUpperCase(fi.defaultUnit());
string var = fi.vname();

Wyświetl plik

@ -61,7 +61,6 @@ LIST_OF_METER_TYPES
#define LIST_OF_METERS \
X(auto, 0, AutoMeter, AUTO, Auto) \
X(unknown, 0, UnknownMeter, UNKNOWN, Unknown) \
X(gransystems,T1_bit, ElectricityMeter, CCx01, CCx01) \
X(multical302,C1_bit|T1_bit, HeatMeter, MULTICAL302, Multical302) \
X(multical403,C1_bit, HeatMeter, MULTICAL403, Multical403) \
X(multical602,C1_bit, HeatMeter, MULTICAL602, Multical602) \
@ -275,7 +274,8 @@ enum PrintProperty
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.
OFFICIAL = 512 // This field is listed as an official field for the driver.
OFFICIAL = 512, // This field is listed as an official field for the driver.
HIDE = 1024 // This field is only used in calculations, do not print it!
};
struct PrintProperties
@ -283,6 +283,7 @@ struct PrintProperties
PrintProperties(int x) : props_(x) {}
bool hasJSON() { return props_ & PrintProperty::JSON; }
bool hasHIDE() { return props_ & PrintProperty::HIDE; }
bool hasFIELD() { return props_ & PrintProperty::FIELD; }
bool hasIMPORTANT() { return props_ & PrintProperty::IMPORTANT; }
bool hasOPTIONAL() { return props_ & PrintProperty::OPTIONAL; }

Wyświetl plik

@ -1227,31 +1227,19 @@ void test_hex()
void test_translate()
{
Translate::Lookup lookup1 =
{
{
{
"ACCESS_BITS",
Translate::Type::BitToString,
0xf0,
"",
{
{ 0x10, "NO_ACCESS" },
{ 0x20, "ALL_ACCESS" },
{ 0x40, "TEMP_ACCESS" },
}
},
{
"ACCESSOR_TYPE",
Translate::Type::IndexToString,
0x0f,
"",
{
{ 0x00, "ACCESSOR_RED" },
{ 0x07, "ACCESSOR_GREEN" },
},
},
},
};
Translate::Lookup()
.add(Translate::Rule("ACCESS_BITS", Translate::Type::BitToString)
.set(MaskBits(0xf0))
.add(Translate::Map(0x10, "NO_ACCESS", TestBit::Set))
.add(Translate::Map(0x20, "ALL_ACCESS", TestBit::Set))
.add(Translate::Map(0x40, "TEMP_ACCESS", TestBit::Set))
)
.add(Translate::Rule("ACCESSOR_TYPE", Translate::Type::IndexToString)
.set(MaskBits(0x0f))
.add(Translate::Map(0x00, "ACCESSOR_RED", TestBit::Set))
.add(Translate::Map(0x07, "ACCESSOR_GREEN", TestBit::Set))
)
;
Translate::Lookup lookup2 =
{
@ -1259,7 +1247,8 @@ void test_translate()
{
"FLOW_FLAGS",
Translate::Type::BitToString,
0x3f,
AlwaysTrigger,
MaskBits(0x3f),
"OOOK",
{
{ 0x01, "BACKWARD_FLOW" },
@ -1277,7 +1266,8 @@ void test_translate()
{
"NO_FLAGS",
Translate::Type::BitToString,
0x03,
AlwaysTrigger,
MaskBits(0x03),
"OK",
{
// Test that 0x01 is set, means OK (ie installed)
@ -1293,16 +1283,16 @@ void test_translate()
uint8_t bits;
bits = 0xa0;
s = lookup1.translate(bits);
e = "ALL_ACCESS ACCESS_BITS_80 ACCESSOR_RED";
s = sortStatusString(lookup1.translate(bits));
e = sortStatusString("ALL_ACCESS ACCESS_BITS_80 ACCESSOR_RED");
if (s != e)
{
printf("ERROR lookup1 0x%02x expected \"%s\" but got \"%s\"\n", bits, e.c_str(), s.c_str());
}
bits = 0x35;
s = lookup1.translate(bits);
e = "NO_ACCESS ALL_ACCESS ACCESSOR_TYPE_5";
s = sortStatusString(lookup1.translate(bits));
e = sortStatusString("NO_ACCESS ALL_ACCESS ACCESSOR_TYPE_5");
if (s != e)
{
printf("ERROR lookup1 0x%02x expected \"%s\" but got \"%s\"\n", bits, e.c_str(), s.c_str());
@ -1327,8 +1317,8 @@ void test_translate()
// Verify that the not set 0x01 bit translates to NOT_INSTALLED
// The set bit 0x02 translates to FOO.
bits = 0x02;
s = lookup3.translate(0x02);
e = "NOT_INSTALLED FOO";
s = sortStatusString(lookup3.translate(0x02));
e = sortStatusString("NOT_INSTALLED FOO");
if (s != e)
{
printf("ERROR lookup3 0x%02x expected \"%s\" but got \"%s\"\n", bits, e.c_str(), s.c_str());
@ -1444,7 +1434,7 @@ void test_ascii_detection()
void test_join(string a, string b, string s)
{
string t = joinStatusStrings(a, b);
string t = joinStatusOKStrings(a, b);
if (t != s)
{
printf("Expected joinStatusString(\"%s\",\"%s\") to be \"%s\" but got \"%s\"\n",

Wyświetl plik

@ -23,14 +23,35 @@
using namespace Translate;
using namespace std;
TriggerBits AlwaysTrigger(~(uint64_t)0);
MaskBits AutoMask(0);
void handleBitToString(Rule& rule, string &out_s, uint64_t bits)
{
string s;
bits = bits & rule.mask;
if (rule.trigger != AlwaysTrigger && (bits & rule.trigger.intValue()) == 0 )
{
// The trigger bits are needed and there are no trigger bits. Ignore this rule.
return;
}
uint64_t mask = rule.mask.intValue();
if (rule.mask == AutoMask)
{
mask = 0;
for (Map& m : rule.map)
{
// Collect all listed bits as the mask.
mask |= m.from;
}
}
bits = bits & mask;
for (Map& m : rule.map)
{
if ((~rule.mask & m.from) != 0)
if ((~mask & m.from) != 0)
{
// Check that the match rule does not extend outside of the mask!
// If mask is 0xff then a match for 0x100 will trigger this bad warning!
@ -38,7 +59,7 @@ void handleBitToString(Rule& rule, string &out_s, uint64_t bits)
s += tmp+" ";
}
uint64_t from = m.from & rule.mask; // Better safe than sorry.
uint64_t from = m.from & mask; // Better safe than sorry.
if (m.test == TestBit::Set)
{
@ -71,7 +92,7 @@ void handleBitToString(Rule& rule, string &out_s, uint64_t bits)
if (s == "")
{
s = rule.no_bits_msg+" ";
s = rule.default_message.stringValue()+" ";
}
out_s += s;
@ -81,19 +102,37 @@ void handleIndexToString(Rule& rule, string &out_s, uint64_t bits)
{
string s;
bits = bits & rule.mask;
if (rule.trigger != AlwaysTrigger && (bits & rule.trigger.intValue()) == 0 )
{
// The trigger bits are needed and there are no trigger bits. Ignore this rule.
return;
}
uint64_t mask = rule.mask.intValue();
if (rule.mask == AutoMask)
{
mask = 0;
for (Map& m : rule.map)
{
// Collect all listed bits as the mask.
mask |= m.from;
}
}
bits = bits & mask;
bool found = false;
for (Map& m : rule.map)
{
assert(m.test == TestBit::Set);
if ((~rule.mask & m.from) != 0)
if ((~mask & m.from) != 0)
{
string tmp;
strprintf(&tmp, "BAD_RULE_%s(from=0x%x mask=0x%x)", rule.name.c_str(), m.from, rule.mask);
s += tmp+" ";
}
uint64_t from = m.from & rule.mask; // Better safe than sorry.
uint64_t from = m.from & mask; // Better safe than sorry.
if (bits == from)
{
s += m.to+" ";
@ -115,23 +154,41 @@ void handleDecimalsToString(Rule& rule, string &out_s, uint64_t bits)
{
string s;
if (rule.trigger != AlwaysTrigger && (bits & rule.trigger.intValue()) == 0 )
{
// The trigger bits are needed and there are no trigger bits. Ignore this rule.
return;
}
uint64_t mask = rule.mask.intValue();
if (rule.mask == AutoMask)
{
mask = 0;
for (Map& m : rule.map)
{
// Collect all listed bits as the mask.
mask |= m.from;
}
}
// Switch to signed number here.
int number = bits % rule.mask;
int number = bits % mask;
if (number == 0)
{
s += rule.no_bits_msg+" ";
s += rule.default_message.stringValue()+" ";
}
for (Map& m : rule.map)
{
assert(m.test == TestBit::Set);
if ((m.from - (m.from % rule.mask)) != 0)
if ((m.from - (m.from % mask)) != 0)
{
string tmp;
strprintf(&tmp, "BAD_RULE_%s(from=%d modulomask=%d)", rule.name.c_str(), m.from, rule.mask);
s += tmp+" ";
}
int num = m.from % rule.mask; // Better safe than sorry.
int num = m.from % mask; // Better safe than sorry.
if ((number - num) >= 0)
{
s += m.to+" ";
@ -172,15 +229,25 @@ void handleRule(Rule& rule, string &s, uint64_t bits)
string Lookup::translate(uint64_t bits)
{
string s = "";
string total = "";
for (Rule& r : rules)
{
string s;
handleRule(r, s, bits);
total = joinStatusEmptyStrings(total, s);
}
while (s.size() > 0 && s.back() == ' ') s.pop_back();
return s;
while (total.size() > 0 && total.back() == ' ') total.pop_back();
return sortStatusString(total);
}
Lookup NoLookup = {};
Map m = { 123, "howdy" };
vector<Map> vm = { { 123, "howdy" } };
Rule r = { "name", Translate::Type::IndexToString,
AlwaysTrigger, MaskBits(0xe000), "", { } };

Wyświetl plik

@ -24,6 +24,46 @@
#include"util.h"
struct TriggerBits
{
TriggerBits() : bits_(0) {}
TriggerBits(uint64_t b) : bits_(b) {}
int intValue() const { return bits_; }
bool operator==(const TriggerBits &tb) const { return bits_ == tb.bits_; }
bool operator!=(const TriggerBits &tb) const { return bits_ != tb.bits_; }
private:
uint64_t bits_;
};
extern TriggerBits AlwaysTrigger;
struct MaskBits
{
MaskBits() : bits_(0) {}
MaskBits(uint64_t b) : bits_(b) {}
int intValue() { return bits_; }
bool operator==(const MaskBits &tb) const { return bits_ == tb.bits_; }
bool operator!=(const MaskBits &tb) const { return bits_ != tb.bits_; }
private:
uint64_t bits_;
};
extern MaskBits AutoMask;
struct DefaultMessage
{
DefaultMessage() : message_("") {}
DefaultMessage(std::string m) : message_(m) {}
const std::string &stringValue() { return message_; }
bool operator==(const DefaultMessage &dm) const { return message_ == dm.message_; }
bool operator!=(const DefaultMessage &dm) const { return message_ != dm.message_; }
private:
std::string message_;
};
namespace Translate
{
enum class Type
@ -38,15 +78,29 @@ namespace Translate
uint64_t from;
std::string to;
TestBit test;
Map(uint64_t f, std::string t, TestBit b) : from(f), to(t), test(b) {};
Map(uint64_t f, std::string t) : from(f), to(t), test(TestBit::Set) {};
};
struct Rule
{
std::string name;
Type type;
uint64_t mask; // Bits to be used are set as 1.
std::string no_bits_msg; // If no bits are set print this, typically "OK".
TriggerBits trigger; // Bits that must be set.
MaskBits mask; // Bits to be used are set as 1.
DefaultMessage default_message; // If no bits are set print this, typically "OK" or "".
std::vector<Map> map;
Rule() {};
Rule(std::string n, Type t, TriggerBits tr, MaskBits mb, std::string dm, std::vector<Map> m)
: name(n), type(t), trigger(tr), mask(mb), default_message(dm), map(m) {}
Rule(std::string n, Type t) :
name(n), type(t), trigger(AlwaysTrigger), mask(AutoMask), default_message(DefaultMessage("")) {}
Rule &set(TriggerBits t) { trigger = t; return *this; }
Rule &set(MaskBits m) { mask = m; return *this; }
Rule &set(DefaultMessage m) { default_message = m; return *this; }
Rule &add(Map m) { map.push_back(m); return *this; }
};
struct Lookup
@ -55,6 +109,8 @@ namespace Translate
std::string translate(uint64_t bits);
bool hasLookups() { return rules.size() > 0; }
Lookup &add(Rule r) { rules.push_back(r); return *this; }
};
};

Wyświetl plik

@ -2301,8 +2301,13 @@ bool isLikelyAscii(const string& v)
return true;
}
string joinStatusStrings(const string &a, const string &b)
string joinStatusOKStrings(const string &aa, const string &bb)
{
string a = aa;
while (a.length() > 0 && a.back() == ' ') a.pop_back();
string b = bb;
while (b.length() > 0 && b.back() == ' ') b.pop_back();
if (a == "" || a == "OK" || a == "null")
{
if (b == "" || b == "null") return "OK";
@ -2317,6 +2322,27 @@ string joinStatusStrings(const string &a, const string &b)
return a+" "+b;
}
string joinStatusEmptyStrings(const string &aa, const string &bb)
{
string a = aa;
while (a.length() > 0 && a.back() == ' ') a.pop_back();
string b = bb;
while (b.length() > 0 && b.back() == ' ') b.pop_back();
if (a == "" || a == "null")
{
if (b == "" || b == "null") return "";
return b;
}
if (b == "" || b == "null")
{
if (a == "" || a == "null") return "";
return a;
}
return a+" "+b;
}
string sortStatusString(const string &a)
{

Wyświetl plik

@ -276,7 +276,10 @@ enum class OutputFormat
// It also translates empty strings into OK.
// "" + "OK" --> "OK"
// "" + "" --> "OK"
std::string joinStatusStrings(const std::string &a, const std::string &b);
std::string joinStatusOKStrings(const std::string &a, const std::string &b);
// Same but do not introduce OK, keep empty strings empty.
std::string joinStatusEmptyStrings(const std::string &a, const std::string &b);
// Sort the words in a status string: "GAMMA BETA ALFA" --> "ALFA BETA GAMMA"
// Also identical flags are merged: "BETA ALFA ALFA" --> "ALFA BETA"