/* Copyright (C) 2018-2020 Fredrik Öhrström 2020 Eric Bus 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.h" #include"meters_common_implementation.h" #include"dvparser.h" #include"wmbus.h" #include"wmbus_utils.h" #include"util.h" #define INFO_CODE_VOLTAGE_INTERRUPTED 1 #define INFO_CODE_LOW_BATTERY_LEVEL 2 #define INFO_CODE_EXTERNAL_ALARM 4 #define INFO_CODE_SENSOR_T1_ABOVE_MEASURING_RANGE 8 #define INFO_CODE_SENSOR_T2_ABOVE_MEASURING_RANGE 16 #define INFO_CODE_SENSOR_T1_BELOW_MEASURING_RANGE 32 #define INFO_CODE_SENSOR_T2_BELOW_MEASURING_RANGE 64 #define INFO_CODE_TEMP_DIFF_WRONG_POLARITY 128 struct MeterMultical403 : public virtual HeatMeter, public virtual MeterCommonImplementation { MeterMultical403(MeterInfo &mi); double totalEnergyConsumption(Unit u); string status(); double totalVolume(Unit u); double volumeFlow(Unit u); // Water temperatures double t1Temperature(Unit u); bool hasT1Temperature(); double t2Temperature(Unit u); bool hasT2Temperature(); private: void processContent(Telegram *t); uchar info_codes_ {}; double total_energy_mj_ {}; double total_volume_m3_ {}; double volume_flow_m3h_ {}; double t1_temperature_c_ { 127 }; bool has_t1_temperature_ {}; double t2_temperature_c_ { 127 }; bool has_t2_temperature_ {}; string target_date_ {}; }; MeterMultical403::MeterMultical403(MeterInfo &mi) : MeterCommonImplementation(mi, MeterType::MULTICAL403) { setExpectedELLSecurityMode(ELLSecurityMode::AES_CTR); addLinkMode(LinkMode::C1); addPrint("total_energy_consumption", Quantity::Energy, [&](Unit u){ return totalEnergyConsumption(u); }, "The total energy consumption recorded by this meter.", true, true); addPrint("total_volume", Quantity::Volume, [&](Unit u){ return totalVolume(u); }, "Total volume of media.", true, true); addPrint("volume_flow", Quantity::Flow, [&](Unit u){ return volumeFlow(u); }, "The current flow.", true, true); addPrint("t1_temperature", Quantity::Temperature, [&](Unit u){ return t1Temperature(u); }, "The T1 temperature.", true, true); addPrint("t2_temperature", Quantity::Temperature, [&](Unit u){ return t2Temperature(u); }, "The T2 temperature.", true, true); addPrint("at_date", Quantity::Text, [&](){ return target_date_; }, "Date when total energy consumption was recorded.", false, true); addPrint("current_status", Quantity::Text, [&](){ return status(); }, "Status of meter.", true, true); } shared_ptr createMultical403(MeterInfo &mi) { return shared_ptr(new MeterMultical403(mi)); } double MeterMultical403::totalEnergyConsumption(Unit u) { assertQuantity(u, Quantity::Energy); return convert(total_energy_mj_, Unit::MJ, u); } double MeterMultical403::totalVolume(Unit u) { assertQuantity(u, Quantity::Volume); return convert(total_volume_m3_, Unit::M3, u); } double MeterMultical403::t1Temperature(Unit u) { assertQuantity(u, Quantity::Temperature); return convert(t1_temperature_c_, Unit::C, u); } bool MeterMultical403::hasT1Temperature() { return has_t1_temperature_; } double MeterMultical403::t2Temperature(Unit u) { assertQuantity(u, Quantity::Temperature); return convert(t2_temperature_c_, Unit::C, u); } bool MeterMultical403::hasT2Temperature() { return has_t2_temperature_; } double MeterMultical403::volumeFlow(Unit u) { assertQuantity(u, Quantity::Flow); return convert(volume_flow_m3h_, Unit::M3H, u); } void MeterMultical403::processContent(Telegram *t) { int offset; string key; extractDVuint8(&t->values, "04FF22", &offset, &info_codes_); t->addMoreExplanation(offset, " info codes (%s)", status().c_str()); if(findKey(MeasurementType::Instantaneous, ValueInformation::EnergyMJ, 0, 0, &key, &t->values)) { extractDVdouble(&t->values, key, &offset, &total_energy_mj_); t->addMoreExplanation(offset, " total energy consumption (%f MJ)", total_energy_mj_); } if(findKey(MeasurementType::Instantaneous, ValueInformation::Volume, 0, 0, &key, &t->values)) { extractDVdouble(&t->values, key, &offset, &total_volume_m3_); t->addMoreExplanation(offset, " total volume (%f m3)", total_volume_m3_); } if(findKey(MeasurementType::Unknown, ValueInformation::VolumeFlow, 0, 0, &key, &t->values)) { extractDVdouble(&t->values, key, &offset, &volume_flow_m3h_); t->addMoreExplanation(offset, " volume flow (%f m3/h)", volume_flow_m3h_); } if(findKey(MeasurementType::Instantaneous, ValueInformation::FlowTemperature, 0, 0, &key, &t->values)) { has_t1_temperature_ = extractDVdouble(&t->values, key, &offset, &t1_temperature_c_); t->addMoreExplanation(offset, " T1 flow temperature (%f °C)", t1_temperature_c_); } if(findKey(MeasurementType::Instantaneous, ValueInformation::ReturnTemperature, 0, 0, &key, &t->values)) { has_t2_temperature_ = extractDVdouble(&t->values, key, &offset, &t2_temperature_c_); t->addMoreExplanation(offset, " T2 flow temperature (%f °C)", t2_temperature_c_); } if (findKey(MeasurementType::Unknown, ValueInformation::Date, 0, 0, &key, &t->values)) { struct tm datetime; extractDVdate(&t->values, key, &offset, &datetime); target_date_ = strdatetime(&datetime); t->addMoreExplanation(offset, " target date (%s)", target_date_.c_str()); } } string MeterMultical403::status() { string s; if (info_codes_ & INFO_CODE_VOLTAGE_INTERRUPTED) s.append("VOLTAGE_INTERRUPTED "); if (info_codes_ & INFO_CODE_LOW_BATTERY_LEVEL) s.append("LOW_BATTERY_LEVEL "); if (info_codes_ & INFO_CODE_EXTERNAL_ALARM) s.append("EXTERNAL_ALARM "); if (info_codes_ & INFO_CODE_SENSOR_T1_ABOVE_MEASURING_RANGE) s.append("SENSOR_T1_ABOVE_MEASURING_RANGE "); if (info_codes_ & INFO_CODE_SENSOR_T2_ABOVE_MEASURING_RANGE) s.append("SENSOR_T2_ABOVE_MEASURING_RANGE "); if (info_codes_ & INFO_CODE_SENSOR_T1_BELOW_MEASURING_RANGE) s.append("SENSOR_T1_BELOW_MEASURING_RANGE "); if (info_codes_ & INFO_CODE_SENSOR_T2_BELOW_MEASURING_RANGE) s.append("SENSOR_T2_BELOW_MEASURING_RANGE "); if (info_codes_ & INFO_CODE_TEMP_DIFF_WRONG_POLARITY) s.append("TEMP_DIFF_WRONG_POLARITY "); if (s.length() > 0) { s.pop_back(); // Remove final space return s; } return s; }