Added meter code for Multical 403

pull/147/head
eborned 2020-08-18 18:20:30 +02:00
rodzic aa24eac099
commit b40c2cdf5b
9 zmienionych plików z 226 dodań i 3 usunięć

Wyświetl plik

@ -131,6 +131,7 @@ METER_OBJS:=\
$(BUILD)/meter_mkradio3.o \ $(BUILD)/meter_mkradio3.o \
$(BUILD)/meter_multical21.o \ $(BUILD)/meter_multical21.o \
$(BUILD)/meter_multical302.o \ $(BUILD)/meter_multical302.o \
$(BUILD)/meter_multical403.o \
$(BUILD)/meter_omnipower.o \ $(BUILD)/meter_omnipower.o \
$(BUILD)/meter_q400.o \ $(BUILD)/meter_q400.o \
$(BUILD)/meter_qcaloric.o \ $(BUILD)/meter_qcaloric.o \

Wyświetl plik

@ -226,6 +226,7 @@ Techem FHKV data II/III (fhkvdataiii)
Supported heat meter: Supported heat meter:
Heat meter Techem Vario 4 (vario451) (non-standard protocol) Heat meter Techem Vario 4 (vario451) (non-standard protocol)
Heat meter Kamstrup Multical 302 (multical302) (in C1 mode, please open issue for T1 mode) Heat meter Kamstrup Multical 302 (multical302) (in C1 mode, please open issue for T1 mode)
Heat meter Kamstrup Multical 403 (multical403) (in C1 mode)
Supported room sensors: Supported room sensors:
Lansen Thermometer/Hygrometer (lansenth) Lansen Thermometer/Hygrometer (lansenth)

Wyświetl plik

@ -328,9 +328,9 @@ bool findKey(MeasurementType mit, ValueInformation vif, int storagenr, int tarif
int vi = v.second.second.value_information; int vi = v.second.second.value_information;
int sn = v.second.second.storagenr; int sn = v.second.second.storagenr;
int tn = v.second.second.tariff; int tn = v.second.second.tariff;
/*debug("(dvparser) match? %s type=%s vif=%02x (%s) and storagenr=%d\n", debug("(dvparser) match? %s type=%s vif=%02x (%s) and storagenr=%d\n",
v.first.c_str(), v.first.c_str(),
measurementTypeName(ty).c_str(), vi, toString(toValueInformation(vi)), storagenr, sn);*/ measurementTypeName(ty).c_str(), vi, toString(toValueInformation(vi)), storagenr, sn);
if (vi >= low && vi <= hi if (vi >= low && vi <= hi
&& (mit == MeasurementType::Unknown || mit == ty) && (mit == MeasurementType::Unknown || mit == ty)

Wyświetl plik

@ -31,10 +31,12 @@
X(Volume,0x10,0x17) \ X(Volume,0x10,0x17) \
X(VolumeFlow,0x38,0x3F) \ X(VolumeFlow,0x38,0x3F) \
X(FlowTemperature,0x58,0x5B) \ X(FlowTemperature,0x58,0x5B) \
X(ReturnTemperature,0x5D,0x5D) \
X(ExternalTemperature,0x64,0x67) \ X(ExternalTemperature,0x64,0x67) \
X(HeatCostAllocation,0x6E,0x6E) \ X(HeatCostAllocation,0x6E,0x6E) \
X(Date,0x6C,0x6C) \ X(Date,0x6C,0x6C) \
X(DateTime,0x6D,0x6D) \ X(DateTime,0x6D,0x6D) \
X(EnergyMJ,0x0e,0x0e) \
X(EnergyWh,0x00,0x07) \ X(EnergyWh,0x00,0x07) \
X(PowerW,0x28,0x2f) \ X(PowerW,0x28,0x2f) \

Wyświetl plik

@ -0,0 +1,212 @@
/*
Copyright (C) 2018-2019 Fredrik Öhrström
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/>.
*/
#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(WMBus *bus, 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_kwh_ {};
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(WMBus *bus, MeterInfo &mi) :
MeterCommonImplementation(bus, mi, MeterType::MULTICAL403, MANUFACTURER_KAM)
{
setExpectedELLSecurityMode(ELLSecurityMode::AES_CTR);
addMedia(0x0a); // Heat/Cooling load
addMedia(0x0b); // Heat/Cooling load
addMedia(0x0c); // Heat/Cooling load
addMedia(0x0d); // Heat/Cooling load
addExpectedVersion(0x34);
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);
}
unique_ptr<HeatMeter> createMultical403(WMBus *bus, MeterInfo &mi) {
return unique_ptr<HeatMeter>(new MeterMultical403(bus, 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;
}

Wyświetl plik

@ -47,6 +47,7 @@
X(mkradio3, T1_bit, Water, MKRADIO3, MKRadio3) \ X(mkradio3, T1_bit, Water, MKRADIO3, MKRadio3) \
X(multical21, C1_bit, Water, MULTICAL21, Multical21) \ X(multical21, C1_bit, Water, MULTICAL21, Multical21) \
X(multical302,C1_bit, Heat, MULTICAL302, Multical302) \ X(multical302,C1_bit, Heat, MULTICAL302, Multical302) \
X(multical403,C1_bit, Heat, MULTICAL403, Multical403) \
X(omnipower, C1_bit, Electricity, OMNIPOWER, Omnipower) \ X(omnipower, C1_bit, Electricity, OMNIPOWER, Omnipower) \
X(rfmamb, T1_bit, TempHygro, RFMAMB, RfmAmb) \ X(rfmamb, T1_bit, TempHygro, RFMAMB, RfmAmb) \
X(rfmtx1, T1_bit, Water, RFMTX1, RfmTX1) \ X(rfmtx1, T1_bit, Water, RFMTX1, RfmTX1) \
@ -224,6 +225,7 @@ LinkModeSet toMeterLinkModeSet(string& type);
unique_ptr<WaterMeter> createMultical21(WMBus *bus, MeterInfo &m); unique_ptr<WaterMeter> createMultical21(WMBus *bus, MeterInfo &m);
unique_ptr<WaterMeter> createFlowIQ3100(WMBus *bus, MeterInfo &m); unique_ptr<WaterMeter> createFlowIQ3100(WMBus *bus, MeterInfo &m);
unique_ptr<HeatMeter> createMultical302(WMBus *bus, MeterInfo &m); unique_ptr<HeatMeter> createMultical302(WMBus *bus, MeterInfo &m);
unique_ptr<HeatMeter> createMultical403(WMBus *bus, MeterInfo &m);
unique_ptr<HeatMeter> createVario451(WMBus *bus, MeterInfo &m); unique_ptr<HeatMeter> createVario451(WMBus *bus, MeterInfo &m);
unique_ptr<WaterMeter> createWaterstarM(WMBus *bus, MeterInfo &m); unique_ptr<WaterMeter> createWaterstarM(WMBus *bus, MeterInfo &m);
unique_ptr<ElectricityMeter> createOmnipower(WMBus *bus, MeterInfo &m); unique_ptr<ElectricityMeter> createOmnipower(WMBus *bus, MeterInfo &m);

Wyświetl plik

@ -28,7 +28,11 @@ using namespace std;
X(Hour, Year, {vto=vfrom/24.0/365;}) \ X(Hour, Year, {vto=vfrom/24.0/365;}) \
X(Year, Hour, {vto=vfrom*24.0*365;}) \ X(Year, Hour, {vto=vfrom*24.0*365;}) \
X(KWH, GJ, {vto=vfrom*0.0036;}) \ X(KWH, GJ, {vto=vfrom*0.0036;}) \
X(KWH, MJ, {vto=vfrom*0.0036*1000.0;}) \
X(GJ, KWH,{vto=vfrom/0.0036;}) \ X(GJ, KWH,{vto=vfrom/0.0036;}) \
X(MJ, GJ, {vto=vfrom/1000.0;}) \
X(MJ, KWH,{vto=vfrom/1000.0/0.0036;}) \
X(GJ, MJ, {vto=vfrom*1000.0;}) \
X(M3, L, {vto=vfrom*1000.0;}) \ X(M3, L, {vto=vfrom*1000.0;}) \
X(L, M3, {vto=vfrom/1000.0;}) \ X(L, M3, {vto=vfrom/1000.0;}) \
X(C, F, {vto=(vfrom*9.0/5.0)+32.0;}) \ X(C, F, {vto=(vfrom*9.0/5.0)+32.0;}) \

Wyświetl plik

@ -35,6 +35,7 @@
#define LIST_OF_UNITS \ #define LIST_OF_UNITS \
X(KWH,kwh,"kWh",Energy,"kilo Watt hour") \ X(KWH,kwh,"kWh",Energy,"kilo Watt hour") \
X(MJ,mj,"MJ",Energy,"Mega Joule") \
X(GJ,gj,"GJ",Energy,"Giga Joule") \ X(GJ,gj,"GJ",Energy,"Giga Joule") \
X(M3,m3,"m3",Volume,"cubic meter") \ X(M3,m3,"m3",Volume,"cubic meter") \
X(L,l,"l",Volume,"litre") \ X(L,l,"l",Volume,"litre") \

Wyświetl plik

@ -83,7 +83,7 @@ mqtt_publish) sent to a REST API (eg curl) or store it in a database
.TP .TP
\fBmeter_name\fR a mnemonic for your utility meter \fBmeter_name\fR a mnemonic for your utility meter
.TP .TP
\fBmeter_type\fR multical21/flowiq3100/supercom587/iperl/multical302/omnipower/qcaloric/apator162/amiplus \fBmeter_type\fR multical21/flowiq3100/supercom587/iperl/multical302/multical403/omnipower/qcaloric/apator162/amiplus
(Can be suffix with :<mode>, eg apator162:t1 to tell wmbusmeters that you expect only t1 telegrams. (Can be suffix with :<mode>, eg apator162:t1 to tell wmbusmeters that you expect only t1 telegrams.
This is necessary since an apator162 can be configured to send either c1 or t1 telegrams.) This is necessary since an apator162 can be configured to send either c1 or t1 telegrams.)
.TP .TP