/* 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 . */ #ifndef DVPARSER_H #define DVPARSER_H #include"util.h" #include"units.h" #include #include #include #include #include #include #define LIST_OF_VIF_RANGES \ X(Volume,0x10,0x17,Quantity::Volume,Unit::M3) \ X(OnTime,0x20,0x23, Quantity::Time, Unit::Hour) \ X(OperatingTime,0x24,0x27, Quantity::Time, Unit::Hour) \ X(VolumeFlow,0x38,0x3F, Quantity::Flow, Unit::M3H) \ X(FlowTemperature,0x58,0x5B, Quantity::Temperature, Unit::C) \ X(ReturnTemperature,0x5C,0x5F, Quantity::Temperature, Unit::C) \ X(TemperatureDifference,0x60,0x63, Quantity::Temperature, Unit::C) \ X(ExternalTemperature,0x64,0x67, Quantity::Temperature, Unit::C) \ X(Pressure,0x68,0x6B, Quantity::Pressure, Unit::BAR) \ X(HeatCostAllocation,0x6E,0x6E, Quantity::HCA, Unit::HCA) \ X(Date,0x6C,0x6C, Quantity::PointInTime, Unit::DateTimeLT) \ X(DateTime,0x6D,0x6D, Quantity::PointInTime, Unit::DateTimeLT) \ X(EnergyMJ,0x08,0x0F, Quantity::Energy, Unit::MJ) \ X(EnergyWh,0x00,0x07, Quantity::Energy, Unit::KWH) \ X(PowerW,0x28,0x2f, Quantity::Power, Unit::KW) \ X(ActualityDuration,0x74,0x77, Quantity::Time, Unit::Hour) \ X(FabricationNo,0x78,0x78, Quantity::Text, Unit::TXT) \ X(EnhancedIdentification,0x79,0x79, Quantity::Text, Unit::TXT) \ X(RelativeHumidity,0x7B1A,0x7B1B, Quantity::RH, Unit::RH) \ X(AccessNumber,0x7D08,0x7D08, Quantity::Counter, Unit::COUNTER) \ X(ParameterSet,0x7D0B,0x7D0B, Quantity::Text, Unit::TXT) \ X(ModelVersion,0x7D0C,0x7D0C, Quantity::Text, Unit::TXT) \ X(HardwareVersion,0x7D0D,0x7D0D, Quantity::Text, Unit::TXT) \ X(FirmwareVersion,0x7D0E,0x7D0E, Quantity::Text, Unit::TXT) \ X(SoftwareVersion,0x7D0F,0x7D0F, Quantity::Text, Unit::TXT) \ X(Location,0x7D10,0x7D10, Quantity::Text, Unit::TXT) \ X(Customer,0x7D11,0x7D11, Quantity::Text, Unit::TXT) \ X(ErrorFlags,0x7D17,0x7D17, Quantity::Text, Unit::TXT) \ X(DigitalInput,0x7D1B,0x7D1B, Quantity::Text, Unit::TXT) \ X(DurationSinceReadout,0x7D2c,0x7D2f, Quantity::Time, Unit::Hour) \ 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(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) \ X(RemainingBattery,0x7D74,0x7D74, Quantity::Time, Unit::Day) \ X(AnyVolumeVIF,0x00,0x00, Quantity::Volume, Unit::Unknown) \ X(AnyEnergyVIF,0x00,0x00, Quantity::Energy, Unit::Unknown) \ X(AnyPowerVIF,0x00,0x00, Quantity::Power, Unit::Unknown) enum class VIFRange { None, Any, #define X(name,from,to,quantity,unit) name, LIST_OF_VIF_RANGES #undef X }; const char *toString(VIFRange v); Unit toDefaultUnit(VIFRange v); VIFRange toVIFRange(int i); bool isInsideVIFRange(int i, VIFRange range); #define LIST_OF_VIF_COMBINABLES \ X(Reserved,0x00,0x11) \ X(Average,0x12,0x12) \ X(InverseCompactProfile,0x13,0x13) \ X(RelativeDeviation,0x14,0x14) \ X(RecordErrorCodeMeterToController,0x15,0x1c) \ X(StandardConformantDataContent,0x1d,0x1d) \ X(CompactProfileWithRegister,0x1e,0x1e) \ X(CompactProfile,0x1f,0x1f) \ X(PerSecond,0x20,0x20) \ X(PerMinute,0x21,0x21) \ X(PerHour,0x22,0x22) \ X(PerDay,0x23,0x23) \ X(PerWeek,0x24,0x24) \ X(PerMonth,0x25,0x25) \ X(PerYear,0x26,0x26) \ X(PerRevolutionMeasurement,0x27,0x27) \ X(IncrPerInputPulseChannel0,0x28,0x28) \ X(IncrPerInputPulseChannel1,0x29,0x29) \ X(IncrPerOutputPulseChannel0,0x2a,0x2a) \ X(IncrPerOutputPulseChannel1,0x2b,0x2b) \ X(PerLitre,0x2c,0x2c) \ X(PerM3,0x2d,0x2d) \ X(PerKg,0x2e,0x2e) \ X(PerKelvin,0x2f,0x2f) \ X(PerKWh,0x30,0x30) \ X(PerGJ,0x31,0x31) \ X(PerKW,0x32,0x32) \ X(PerKelvinLitreW,0x33,0x33) \ X(PerVolt,0x34,0x34) \ X(PerAmpere,0x35,0x35) \ X(MultipliedByS,0x36,0x36) \ X(MultipliedBySDivV,0x37,0x37) \ X(MultipliedBySDivA,0x38,0x38) \ X(StartDateTimeOfAB,0x39,0x39) \ X(UncorrectedMeterUnit,0x3a,0x3a) \ X(ForwardFlow,0x3b,0x3b) \ X(BackwardFlow,0x3c,0x3c) \ X(ReservedNonMetric,0x3d,0x3d) \ X(ValueAtBaseCondC,0x3e,0x3e) \ X(ObisDeclaration,0x3f,0x3f) \ X(LowerLimit,0x40,0x40) \ X(ExceedsLowerLimit,0x41,0x41) \ X(DateTimeExceedsLowerFirstBegin, 0x42,0x42) \ X(DateTimeExceedsLowerFirstEnd, 0x43,0x43) \ X(DateTimeExceedsLowerLastBegin, 0x46,0x46) \ X(DateTimeExceedsLowerLastEnd, 0x47,0x47) \ X(UpperLimit,0x48,0x48) \ X(ExceedsUpperLimit,0x49,0x49) \ X(DateTimeExceedsUpperFirstBegin, 0x4a,0x4a) \ X(DateTimeExceedsUpperFirstEnd, 0x4b,0x4b) \ X(DateTimeExceedsUpperLastBegin, 0x4d,0x4d) \ X(DateTimeExceedsUpperLastEnd, 0x4e,0x4e) \ X(DurationExceedsLowerFirst,0x50,0x53) \ X(DurationExceedsLowerLast,0x54,0x57) \ X(DurationExceedsUpperFirst,0x58,0x5b) \ X(DurationExceedsUpperLast,0x5c,0x5f) \ X(DurationOfDFirst,0x60,0x63) \ X(DurationOfDLast,0x64,0x67) \ X(ValueDuringLowerLimitExceeded,0x68,0x68) \ X(LeakageValues,0x69,0x69) \ X(OverflowValues,0x6a,0x6a) \ X(ValueDuringUpperLimitExceeded,0x6c,0x6c) \ X(DateTimeOfDEFirstBegin,0x6a,0x6a) \ X(DateTimeOfDEFirstEnd,0x6b,0x6b) \ X(DateTimeOfDELastBegin,0x6e,0x6e) \ X(DateTimeOfDELastEnd,0x6f,0x6f) \ X(MultiplicativeCorrectionFactorForValue,0x70,0x77) \ X(AdditiveCorrectionConstant,0x78,0x7b) \ X(CombinableVIFExtension,0x7c,0x7c) \ X(MultiplicativeCorrectionFactorForValue103,0x7d,0x7d) \ X(FutureValue,0x7e,0x7e) \ X(MfctSpecific,0x7f,0x7f) \ X(AtPhase1,0x7c01,0x7c01) \ X(AtPhase2,0x7c02,0x7c02) \ X(AtPhase3,0x7c03,0x7c03) \ X(AtNeutral,0x7c04,0x7c04) \ X(BetweenPhaseL1AndL2,0x7c05,0x7c05) \ X(BetweenPhaseL2AndL3,0x7c06,0x7c06) \ X(BetweenPhaseL3AndL1,0x7c07,0x7c07) \ X(AtQuadrantQ1,0x7c08,0x7c08) \ X(AtQuadrantQ2,0x7c09,0x7c09) \ X(AtQuadrantQ3,0x7c0a,0x7c0a) \ X(AtQuadrantQ4,0x7c0b,0x7c0b) \ X(DeltaBetweenImportAndExport,0x7c0c,0x7c0c) \ X(AccumulationOfAbsoluteValue,0x7c10,0x7c10) \ X(DataPresentedWithTypeC,0x7c11,0x7c11) \ X(DataPresentedWithTypeD,0x7c12,0x7c12) \ X(Mfct00,0x7f00,0x7f00) \ X(Mfct01,0x7f01,0x7f01) \ X(Mfct02,0x7f02,0x7f02) \ X(Mfct03,0x7f03,0x7f03) \ X(Mfct04,0x7f04,0x7f04) \ X(Mfct05,0x7f05,0x7f05) \ X(Mfct06,0x7f06,0x7f06) \ X(Mfct07,0x7f07,0x7f07) \ X(Mfct21,0x7f21,0x7f21) \ enum class VIFCombinable { None, Any, #define X(name,from,to) name, LIST_OF_VIF_COMBINABLES #undef X }; VIFCombinable toVIFCombinable(int i); const char *toString(VIFCombinable v); enum class MeasurementType { Any, Instantaneous, Minimum, Maximum, AtError }; const char *toString(MeasurementType mt); void extractDV(std::string &s, uchar *dif, int *vif, bool *has_difes, bool *has_vifes); struct DifVifKey { DifVifKey(std::string key) : key_(key) { extractDV(key, &dif_, &vif_, &has_difes_, &has_vifes_); } std::string str() { return key_; } bool operator==(DifVifKey &dvk) { return key_ == dvk.key_; } uchar dif() { return dif_; } int vif() { return vif_; } bool hasDifes() { return has_difes_; } bool hasVifes() { return has_vifes_; } private: std::string key_; uchar dif_; int vif_; bool has_difes_; bool has_vifes_; }; void extractDV(DifVifKey &s, uchar *dif, int *vif, bool *has_difes, bool *has_vifes); static DifVifKey NoDifVifKey = DifVifKey(""); struct Vif { Vif(int n) : nr_(n) {} int intValue() { return nr_; } bool operator==(Vif s) { return nr_ == s.nr_; } private: int nr_; }; Unit toDefaultUnit(Vif v); enum class DVEntryCounterType { UNKNOWN, STORAGE_COUNTER, TARIFF_COUNTER, SUBUNIT_COUNTER }; DVEntryCounterType toDVEntryCounterType(const std::string &s); const char *toString(DVEntryCounterType ct); struct StorageNr { StorageNr(int n) : nr_(n) {} int intValue() { return nr_; } bool operator==(StorageNr s) { return nr_ == s.nr_; } bool operator!=(StorageNr s) { return nr_ != s.nr_; } bool operator>=(StorageNr s) { return nr_ >= s.nr_; } bool operator<=(StorageNr s) { return nr_ <= s.nr_; } private: int nr_; }; static StorageNr AnyStorageNr = StorageNr(-1); struct TariffNr { TariffNr(int n) : nr_(n) {} int intValue() { return nr_; } bool operator==(TariffNr s) { return nr_ == s.nr_; } bool operator!=(TariffNr s) { return nr_ != s.nr_; } bool operator>=(TariffNr s) { return nr_ >= s.nr_; } bool operator<=(TariffNr s) { return nr_ <= s.nr_; } private: int nr_; }; static TariffNr AnyTariffNr = TariffNr(-1); struct SubUnitNr { SubUnitNr(int n) : nr_(n) {} int intValue() { return nr_; } bool operator==(SubUnitNr s) { return nr_ == s.nr_; } bool operator!=(SubUnitNr s) { return nr_ != s.nr_; } bool operator>=(SubUnitNr s) { return nr_ >= s.nr_; } bool operator<=(SubUnitNr s) { return nr_ <= s.nr_; } private: int nr_; }; struct IndexNr { IndexNr(int n) : nr_(n) {} int intValue() { return nr_; } bool operator==(IndexNr s) { return nr_ == s.nr_; } bool operator!=(IndexNr s) { return nr_ != s.nr_; } private: int nr_; }; static IndexNr AnyIndexNr = IndexNr(-1); struct FieldInfo; struct DVEntry { int offset {}; // Where in the telegram this dventry was found. DifVifKey dif_vif_key; MeasurementType measurement_type; Vif vif; std::set combinable_vifs; StorageNr storage_nr; TariffNr tariff_nr; SubUnitNr subunit_nr; std::string value; DVEntry(int off, DifVifKey dvk, MeasurementType mt, Vif vi, std::set vc, StorageNr st, TariffNr ta, SubUnitNr su, std::string &val) : offset(off), dif_vif_key(dvk), measurement_type(mt), vif(vi), combinable_vifs(vc), storage_nr(st), tariff_nr(ta), subunit_nr(su), value(val) { } DVEntry() : offset(999999), dif_vif_key("????"), measurement_type(MeasurementType::Instantaneous), vif(0), storage_nr(0), tariff_nr(0), subunit_nr(0), value("x") { } bool extractDouble(double *out, bool auto_scale, bool assume_signed); bool extractLong(uint64_t *out); bool extractDate(struct tm *out); bool extractReadableString(std::string *out); void addFieldInfo(FieldInfo *fi) { field_infos_.insert(fi); } bool hasFieldInfo(FieldInfo *fi) { return field_infos_.count(fi) > 0; } std::string str(); double getCounter(DVEntryCounterType ct); private: std::set field_infos_; // The field infos selected to decode this entry. }; struct FieldMatcher { // If not actually used, this remains false. bool active = false; // Exact difvif hex string matching all other checks are ignored. bool match_dif_vif_key = false; DifVifKey dif_vif_key { "" }; // Match the measurement_type. bool match_measurement_type = false; MeasurementType measurement_type { MeasurementType::Instantaneous }; // Match the value information range. See dvparser.h bool match_vif_range = false; VIFRange vif_range { VIFRange::Any }; // Match any vif combinables. std::set vif_combinables; // Match the storage nr. If no storage is specified, default to match only 0. bool match_storage_nr = true; StorageNr storage_nr_from { 0 }; StorageNr storage_nr_to { 0 }; // Match the tariff nr. If no tariff is specified, default to match only 0. bool match_tariff_nr = true; TariffNr tariff_nr_from { 0 }; TariffNr tariff_nr_to { 0 }; // Match the subunit. If no subunit is specified, default to match only 0. bool match_subunit_nr = true; SubUnitNr subunit_nr_from { 0 }; SubUnitNr subunit_nr_to { 0 }; // If the telegram has multiple identical difvif entries matching this field // and you want to catch the second matching entry, then set the index nr to 2. // The default is 1. IndexNr index_nr { 1 }; FieldMatcher() : active(false) { } FieldMatcher(bool act) : active(act) { } static FieldMatcher build() { return FieldMatcher(true); } static FieldMatcher noMatcher() { return FieldMatcher(false); } FieldMatcher &set(DifVifKey k) { dif_vif_key = k; match_dif_vif_key = (k.str() != ""); return *this; } FieldMatcher &set(MeasurementType mt) { measurement_type = mt; match_measurement_type = (mt != MeasurementType::Any); return *this; } FieldMatcher &set(VIFRange v) { vif_range = v; match_vif_range = (v != VIFRange::Any); return *this; } FieldMatcher &add(VIFCombinable v) { vif_combinables.insert(v); return *this; } FieldMatcher &set(StorageNr s) { storage_nr_from = storage_nr_to = s; match_storage_nr = (s != AnyStorageNr); return *this; } FieldMatcher &set(StorageNr from, StorageNr to) { storage_nr_from = from; storage_nr_to = to; match_storage_nr = true; return *this; } FieldMatcher &set(TariffNr s) { tariff_nr_from = tariff_nr_to = s; match_tariff_nr = (s != AnyTariffNr); return *this; } FieldMatcher &set(TariffNr from, TariffNr to) { tariff_nr_from = from; tariff_nr_to = to; match_tariff_nr = true; return *this; } FieldMatcher &set(SubUnitNr s) { subunit_nr_from = subunit_nr_to = s; match_subunit_nr = true; return *this; } FieldMatcher &set(SubUnitNr from, SubUnitNr to) { subunit_nr_from = from; subunit_nr_to = to; match_subunit_nr = true; return *this; } FieldMatcher &set(IndexNr i) { index_nr = i; return *this; } bool matches(DVEntry &dv_entry); // Returns true of there is any range for storage, tariff, subunit nrs. // I.e. this matcher is expected to match against multiple dv entries! bool expectedToMatchAgainstMultipleEntries() { return (match_storage_nr && storage_nr_from != storage_nr_to) || (match_tariff_nr && tariff_nr_from != tariff_nr_to) || (match_subunit_nr && subunit_nr_from != subunit_nr_to); } std::string str(); }; bool loadFormatBytesFromSignature(uint16_t format_signature, std::vector *format_bytes); struct Telegram; bool parseDV(Telegram *t, std::vector &databytes, std::vector::iterator data, size_t data_len, std::map> *dv_entries, std::vector::iterator *format = NULL, size_t format_len = 0, uint16_t *format_hash = NULL); // Instead of using a hardcoded difvif as key in the extractDV... below, // find an existing difvif entry in the values based on the desired value information type. // Like: Volume, VolumeFlow, FlowTemperature, ExternalTemperature etc // in combination with the storagenr. (Later I will add tariff/subunit) bool findKey(MeasurementType mt, VIFRange vi, StorageNr storagenr, TariffNr tariffnr, std::string *key, std::map> *values); // Some meters have multiple identical DIF/VIF values! Meh, they are not using storage nrs or tariff nrs. // So here we can pick for example nr 2 of an identical set if DIF/VIF values. // Nr 1 means the first found value. bool findKeyWithNr(MeasurementType mt, VIFRange vi, StorageNr storagenr, TariffNr tariffnr, int indexnr, std::string *key, std::map> *values); bool hasKey(std::map> *values, std::string key); bool extractDVuint8(std::map> *values, std::string key, int *offset, uchar *value); bool extractDVuint16(std::map> *values, std::string key, int *offset, uint16_t *value); bool extractDVuint24(std::map> *values, std::string key, int *offset, uint32_t *value); bool extractDVuint32(std::map> *values, std::string key, int *offset, uint32_t *value); // All values are scaled according to the vif and wmbusmeters scaling defaults. bool extractDVdouble(std::map> *values, std::string key, int *offset, double *value, bool auto_scale = true, bool assume_signed = false); // Extract a value without scaling. Works for 8bits to 64 bits, binary and bcd. bool extractDVlong(std::map> *values, std::string key, int *offset, uint64_t *value); // Just copy the raw hex data into the string, not reversed or anything. bool extractDVHexString(std::map> *values, std::string key, int *offset, std::string *value); // Read the content and attempt to reverse and transform it into a readble string // based on the dif information. bool extractDVReadableString(std::map> *values, std::string key, int *offset, std::string *value); bool extractDVdate(std::map> *values, std::string key, int *offset, struct tm *value); #endif