kopia lustrzana https://github.com/weetmuts/wmbusmeters
Refactor gransystems driver to new format.
rodzic
7db2fa9dee
commit
073aafd31c
4
CHANGES
4
CHANGES
|
@ -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.
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -109,7 +109,7 @@ MeterAventiesHCA::MeterAventiesHCA(MeterInfo &mi, DriverInfo &di) : MeterCommonI
|
|||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xffff,
|
||||
AlwaysTrigger, MaskBits(0xffff),
|
||||
"",
|
||||
{
|
||||
{ 0x01, "MEASUREMENT", TestBit::Set },
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -101,7 +101,7 @@ namespace
|
|||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::DecimalsToString,
|
||||
9999,
|
||||
AlwaysTrigger, MaskBits(9999),
|
||||
"OK",
|
||||
{
|
||||
{ 2000, "VERIFICATION_EXPIRED" }, // Status initial verification expired
|
||||
|
|
|
@ -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)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ namespace
|
|||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xffff,
|
||||
AlwaysTrigger, MaskBits(0xffff),
|
||||
"OK",
|
||||
{
|
||||
}
|
||||
|
|
|
@ -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*/
|
||||
|
|
|
@ -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",
|
||||
{
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -47,7 +47,7 @@ namespace
|
|||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xff,
|
||||
AlwaysTrigger, MaskBits(0xff),
|
||||
"OK",
|
||||
{
|
||||
{ 0x01, "VOLUME_DETECTION_COILS_DEFECT" },
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -50,7 +50,7 @@ namespace
|
|||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xffff,
|
||||
AlwaysTrigger, MaskBits(0xffff),
|
||||
"OK",
|
||||
{
|
||||
// { 0x01 , "WOOT" },
|
||||
|
|
|
@ -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, "" },
|
||||
|
|
|
@ -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
|
|
@ -85,7 +85,7 @@ namespace
|
|||
{
|
||||
"TPL_FLAGS",
|
||||
Translate::Type::IndexToString,
|
||||
0xe0,
|
||||
AlwaysTrigger, MaskBits(0xe0),
|
||||
"OK",
|
||||
{
|
||||
{ 0x20, "AIR_IN_PIPE" },
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -57,7 +57,7 @@ namespace
|
|||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xffff,
|
||||
AlwaysTrigger, MaskBits(0xffff),
|
||||
"OK",
|
||||
{
|
||||
{ 0x0004, "SMOKE" },
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -105,7 +105,7 @@ namespace
|
|||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xffff,
|
||||
AlwaysTrigger, MaskBits(0xffff),
|
||||
"OK",
|
||||
{
|
||||
// { 0x01, "?" },
|
||||
|
|
|
@ -52,7 +52,7 @@ namespace
|
|||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xff,
|
||||
AlwaysTrigger, MaskBits(0xff),
|
||||
"OK",
|
||||
{
|
||||
// Bits unknown
|
||||
|
|
|
@ -199,7 +199,7 @@ MeterMicroClima::MeterMicroClima(MeterInfo &mi, DriverInfo &di) : MeterCommonImp
|
|||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xffff,
|
||||
AlwaysTrigger, MaskBits(0xffff),
|
||||
"OK",
|
||||
{
|
||||
{ 0x01, "?" },
|
||||
|
|
|
@ -129,7 +129,7 @@ namespace
|
|||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xffff,
|
||||
AlwaysTrigger, MaskBits(0xffff),
|
||||
"OK",
|
||||
{
|
||||
{ 0x8000, "WAS_REMOVED" },
|
||||
|
|
|
@ -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, "" },
|
||||
|
|
|
@ -125,7 +125,7 @@ namespace
|
|||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xffff,
|
||||
AlwaysTrigger, MaskBits(0xffff),
|
||||
"OK",
|
||||
{
|
||||
{ 0x0000, "OK" },
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -48,7 +48,7 @@ namespace
|
|||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xffff,
|
||||
AlwaysTrigger, MaskBits(0xffff),
|
||||
"OK",
|
||||
{
|
||||
// What does these bits mean?
|
||||
|
|
|
@ -58,7 +58,7 @@ namespace
|
|||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xff,
|
||||
AlwaysTrigger, MaskBits(0xff),
|
||||
"OK",
|
||||
{
|
||||
// Bits unknown
|
||||
|
|
|
@ -50,7 +50,7 @@ namespace
|
|||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xffff,
|
||||
AlwaysTrigger, MaskBits(0xffff),
|
||||
"OK",
|
||||
{
|
||||
// !!! Uncertain !!!
|
||||
|
|
|
@ -50,7 +50,7 @@ namespace
|
|||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xffff,
|
||||
AlwaysTrigger, MaskBits(0xffff),
|
||||
"OK",
|
||||
{
|
||||
}
|
||||
|
|
|
@ -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...
|
||||
|
|
|
@ -145,7 +145,7 @@ MeterQWater::MeterQWater(MeterInfo &mi, DriverInfo &di) :
|
|||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xffff,
|
||||
AlwaysTrigger, MaskBits(0xffff),
|
||||
"OK",
|
||||
{
|
||||
{ 0x01, "?" },
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -68,7 +68,7 @@ namespace
|
|||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xffffff,
|
||||
AlwaysTrigger, MaskBits(0xffffff),
|
||||
"OK",
|
||||
{
|
||||
/* What are the bits?
|
||||
|
|
|
@ -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),
|
||||
"",
|
||||
{
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ namespace
|
|||
{
|
||||
"ERROR_FLAGS",
|
||||
Translate::Type::BitToString,
|
||||
0xffff,
|
||||
AlwaysTrigger, MaskBits(0xffff),
|
||||
"OK",
|
||||
{
|
||||
{ 0x01, "SW_ERROR" },
|
||||
|
|
|
@ -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) \
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) \
|
||||
|
|
|
@ -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, ¤t_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, ¤t_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, ¤t_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, ¤t_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;
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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), "", { } };
|
||||
|
|
|
@ -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; }
|
||||
};
|
||||
};
|
||||
|
||||
|
|
28
src/util.cc
28
src/util.cc
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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"
|
||||
|
|
Ładowanie…
Reference in New Issue