/* Copyright (C) 2019-2023 Fredrik Öhrström (gpl-3.0-or-later) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include"meters_common_implementation.h" namespace { struct Driver : public virtual MeterCommonImplementation { Driver(MeterInfo &mi, DriverInfo &di); }; static bool ok = registerDriver([](DriverInfo&di) { di.setName("qcaloric"); di.addNameAlias("whe5x"); di.addNameAlias("whe46x"); di.setDefaultFields("name,id,current_consumption_hca,set_date,consumption_at_set_date_hca,timestamp"); di.setMeterType(MeterType::HeatCostAllocationMeter); di.addLinkMode(LinkMode::C1); di.addLinkMode(LinkMode::T1); di.addLinkMode(LinkMode::S1); di.addDetection(MANUFACTURER_LSE, 0x08, 0x34); di.addDetection(MANUFACTURER_LSE, 0x08, 0x35); di.addDetection(MANUFACTURER_QDS, 0x08, 0x35); di.addDetection(MANUFACTURER_QDS, 0x08, 0x34); di.addDetection(MANUFACTURER_QDS, 0x08, 0x36); di.addDetection(MANUFACTURER_LSE, 0x08, 0x18); // whe4 di.addDetection(MANUFACTURER_ZRI, 0x08, 0xfd); di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr(new Driver(mi, di)); }); }); Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di) { addStringFieldWithExtractorAndLookup( "status", "Meter status from tpl status field.", DEFAULT_PRINT_PROPERTIES | PrintProperty::STATUS | PrintProperty::INCLUDE_TPL_STATUS, FieldMatcher::build() .set(DifVifKey("01FD73")), Translate::Lookup( { { { "ERROR_FLAGS", Translate::MapType::BitToString, AlwaysTrigger, MaskBits(0xff), "OK", { // Bits unknown } }, }, })); addNumericFieldWithExtractor( "current_consumption", "The current heat cost allocation.", DEFAULT_PRINT_PROPERTIES, Quantity::HCA, VifScaling::Auto, DifSignedness::Signed, FieldMatcher::build() .set(MeasurementType::Instantaneous) .set(VIFRange::HeatCostAllocation) ); addStringFieldWithExtractor( "set_date", "The most recent billing period date.", DEFAULT_PRINT_PROPERTIES, FieldMatcher::build() .set(MeasurementType::Instantaneous) .set(VIFRange::Date) .set(StorageNr(1)) ); addNumericFieldWithExtractor( "consumption_at_set_date", "Heat cost allocation at the most recent billing period date.", DEFAULT_PRINT_PROPERTIES, Quantity::HCA, VifScaling::Auto, DifSignedness::Signed, FieldMatcher::build() .set(MeasurementType::Instantaneous) .set(VIFRange::HeatCostAllocation) .set(StorageNr(1)) ); addStringFieldWithExtractor( "set_date_1", "The most recent billing period date.", DEFAULT_PRINT_PROPERTIES, FieldMatcher::build() .set(MeasurementType::Instantaneous) .set(VIFRange::Date) .set(StorageNr(1)) ); addNumericFieldWithExtractor( "consumption_at_set_date_1", "Heat cost allocation at the most recent billing period date.", DEFAULT_PRINT_PROPERTIES, Quantity::HCA, VifScaling::Auto, DifSignedness::Signed, FieldMatcher::build() .set(MeasurementType::Instantaneous) .set(VIFRange::HeatCostAllocation) .set(StorageNr(1)) ); addStringFieldWithExtractor( "set_date_8", "The 8 billing period date.", DEFAULT_PRINT_PROPERTIES, FieldMatcher::build() .set(MeasurementType::Instantaneous) .set(VIFRange::Date) .set(StorageNr(8)) ); addNumericFieldWithExtractor( "consumption_at_set_date_8", "Heat cost allocation at the 8 billing period date.", DEFAULT_PRINT_PROPERTIES, Quantity::HCA, VifScaling::Auto, DifSignedness::Signed, FieldMatcher::build() .set(MeasurementType::Instantaneous) .set(VIFRange::HeatCostAllocation) .set(StorageNr(8)) ); addStringFieldWithExtractor( "set_date_17", "The 17 billing period date.", DEFAULT_PRINT_PROPERTIES, FieldMatcher::build() .set(MeasurementType::Instantaneous) .set(VIFRange::Date) .set(StorageNr(17)) ); addNumericFieldWithExtractor( "consumption_at_set_date_17", "Heat cost allocation at the 17 billing period date.", DEFAULT_PRINT_PROPERTIES, Quantity::HCA, VifScaling::Auto, DifSignedness::Signed, FieldMatcher::build() .set(MeasurementType::Instantaneous) .set(VIFRange::HeatCostAllocation) .set(StorageNr(17)) ); addStringFieldWithExtractor( "error_date", "Date when the meter entered an error state.", DEFAULT_PRINT_PROPERTIES, FieldMatcher::build() .set(MeasurementType::AtError) .set(VIFRange::Date) ); addStringFieldWithExtractor( "device_date_time", "Date and time when the meter sent the telegram.", DEFAULT_PRINT_PROPERTIES, FieldMatcher::build() .set(MeasurementType::Instantaneous) .set(VIFRange::DateTime) ); addStringFieldWithExtractor( "model_version", "model version.", DEFAULT_PRINT_PROPERTIES, FieldMatcher::build() .set(MeasurementType::Instantaneous) .set(VIFRange::ModelVersion) ); addNumericFieldWithExtractor( "flow_temperature", "Forward media temperature.", DEFAULT_PRINT_PROPERTIES, Quantity::Temperature, VifScaling::Auto, DifSignedness::Signed, FieldMatcher::build() .set(MeasurementType::Instantaneous) .set(VIFRange::FlowTemperature) ); } } // Test: MyElement qcaloric 78563412 NOKEY // telegram=|314493441234567835087a740000200b6e2701004b6e450100426c5f2ccb086e790000c2086c7f21326cffff046d200b7422| // {"media":"heat cost allocation","meter":"qcaloric","name":"MyElement","id":"78563412","status":"OK","current_consumption_hca":127,"set_date":"2018-12-31","consumption_at_set_date_hca":145,"set_date_1":"2018-12-31","consumption_at_set_date_1_hca":145,"set_date_17":"2019-01-31","consumption_at_set_date_17_hca":79,"error_date":"2127-15-31","device_date_time":"2019-02-20 11:32","timestamp":"1111-11-11T11:11:11Z"} // |MyElement;78563412;127;2018-12-31;145;1111-11-11 11:11.11 // Test: MyElement2 qcaloric 90919293 NOKEY // Comment: Test mostly proprietary telegram without values // telegram=|49449344939291903408780DFF5F350082180000800007B06EFFFF970000009F2C70020000BE26970000000000010018002E001F002E0023FF210008000500020000002F046D220FA227| // {"media":"heat cost allocation","meter":"qcaloric","name":"MyElement2","id":"90919293","status":"OK","device_date_time":"2021-07-02 15:34","timestamp":"1111-11-11T11:11:11Z"} // |MyElement2;90919293;null;null;null;1111-11-11 11:11.11 // Test: zenner_heat qcaloric 25932395 NOKEY // telegram=|5E44496A95239325FD087A2CC050052F2F_0B6E030100426CDF2C4B6EFFFFFF82046CE1228B046E6200008D04EE132C3BFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2F2F2F2F| // {"media":"heat cost allocation","meter":"qcaloric","name":"zenner_heat","id":"25932395","status":"UNKNOWN_C0","current_consumption_hca":103,"set_date":"2022-12-31","set_date_1":"2022-12-31","set_date_8":"2023-02-01","consumption_at_set_date_8_hca":62,"timestamp":"1111-11-11T11:11:11Z"} // |zenner_heat;25932395;103;2022-12-31;null;1111-11-11 11:11.11 // Comment: Normal telegram that fills in values. // telegram=|314493449392919034087a520000200b6e9700004b6e700200426c9f2ccb086e970000c2086cbe26326cffff046d2d16a227| // {"media":"heat cost allocation","meter":"qcaloric","name":"MyElement2","id":"90919293","status":"OK","current_consumption_hca":97,"set_date":"2020-12-31","consumption_at_set_date_hca":270,"set_date_1":"2020-12-31","consumption_at_set_date_1_hca":270,"set_date_17":"2021-06-30","consumption_at_set_date_17_hca":97,"error_date":"2127-15-31","device_date_time":"2021-07-02 22:45","timestamp":"1111-11-11T11:11:11Z"} // |MyElement2;90919293;97;2020-12-31;270;1111-11-11 11:11.11 // Comment: Another mostly empty telegram, but values are now valid. // telegram=|49449344939291903408780DFF5F350082180000800007B06EFFFF970000009F2C70020000BE26970000000000010018002E001F002E0023FF210008000500020000002F046D220FA228| // {"media":"heat cost allocation","meter":"qcaloric","name":"MyElement2","id":"90919293","status":"OK","current_consumption_hca":97,"set_date":"2020-12-31","consumption_at_set_date_hca":270,"set_date_1":"2020-12-31","consumption_at_set_date_1_hca":270,"set_date_17":"2021-06-30","consumption_at_set_date_17_hca":97,"error_date":"2127-15-31","device_date_time":"2021-08-02 15:34","timestamp":"1111-11-11T11:11:11Z"} // |MyElement2;90919293;97;2020-12-31;270;1111-11-11 11:11.11 // Comment: Another version of the heat cost allocator. Was known as whe5x, so a name alias exist that maps to qcaloric. // Test: HCA whe5x 91835132 NOKEY // telegram=|244465323251839134087a4f0000000b6e0403004b6e660300426c9e29326cffff046d1416b921dd2f| // {"media":"heat cost allocation","meter":"qcaloric","name":"HCA","id":"91835132","status":"OK","current_consumption_hca":304,"set_date":"2020-09-30","consumption_at_set_date_hca":366,"set_date_1":"2020-09-30","consumption_at_set_date_1_hca":366,"error_date":"2127-15-31","device_date_time":"2021-01-25 22:20","timestamp":"1111-11-11T11:11:11Z"} // |HCA;91835132;304;2020-09-30;366;1111-11-11 11:11.11 // Comment: Another version of the heat cost allocator. Was known as whe46x, which now is a name alias mapped to qcaloric. // Test: HCA2 whe46x 60366655 NOKEY // telegram=|344465325566366018087A90040000046D1311962C01FD0C03326CFFFF01FD7300025AC2000DFF5F0C0008003030810613080BFFFC| // {"media":"heat cost allocation","meter":"qcaloric","name":"HCA2","id":"60366655","status":"POWER_LOW","error_date":"2127-15-31","device_date_time":"2020-12-22 17:19","model_version":"03","flow_temperature_c":19.4,"timestamp":"1111-11-11T11:11:11Z"} // |HCA2;60366655;null;null;null;1111-11-11 11:11.11 // telegram=|2a4465325566366018087ac3040000046d1617Ba210B6e890000426c9f2c4B6e520600326cffff01fd7300| // {"media":"heat cost allocation","meter":"qcaloric","name":"HCA2","id":"60366655","status":"POWER_LOW","current_consumption_hca":89,"set_date":"2020-12-31","consumption_at_set_date_hca":652,"set_date_1":"2020-12-31","consumption_at_set_date_1_hca":652,"error_date":"2127-15-31","device_date_time":"2021-01-26 23:22","model_version":"03","flow_temperature_c":19.4,"timestamp":"1111-11-11T11:11:11Z"} // |HCA2;60366655;89;2020-12-31;652;1111-11-11 11:11.11 // Test: HCA55 qcaloric 30535282 NOKEY // Comment: QCaloric 5.5 encrypted but with some not-encrypted bytes at the end. We should print these, but we do not right now.... // Since it is encrypted and we do not have the key, wmbusmeters currently ignores it and every following telegram. // The readable bytes are: 326cffff046d230dda2c which decodes to // 32 dif (16 Bit Integer/Binary Value during error state) // 6C vif (Date type G) // FFFF // 04 dif (32 Bit Integer/Binary Instantaneous value) // 6D vif (Date and time type) // 230DDA2C // 32 dif (16 Bit Integer/Binary Value during error state) // 6C vif (Date type G) // FFFF ("error_date":"2127-15-31") // 04 dif (32 Bit Integer/Binary Instantaneous value) // 6D vif (Date and time type) // 230DDA2C ("device_date_time":"2022-12-26 13:35") // NOTYET telegram=|384493448252533036087A430020253F59515BD90F76E8576AF36C988EEA9B398EC5C205E5DBBE3F2698408947CB8E326CFFFF046D230DDA2C| // Comment: Mostly mfct specific data. Not yet decoded. // telegram=|49449344825253303608780DFF5F350082430035E3DFC4EAC97A58B8610713D93549E2601258D617D267E7515C764B002A88CD341A9F9DF3C6034DE5B6D1FAB3619CBA9F046D250DDA2C| // {"device_date_time": "2022-12-26 13:37","id": "30535282","media": "heat cost allocation","meter": "qcaloric","name": "HCA55","status": "OK","timestamp": "1111-11-11T11:11:11Z"} // |HCA55;30535282;null;null;null;1111-11-11 11:11.11