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_multical21.o \
$(BUILD)/meter_multical302.o \
$(BUILD)/meter_multical403.o \
$(BUILD)/meter_omnipower.o \
$(BUILD)/meter_q400.o \
$(BUILD)/meter_qcaloric.o \

Wyświetl plik

@ -226,6 +226,7 @@ Techem FHKV data II/III (fhkvdataiii)
Supported heat meter:
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 403 (multical403) (in C1 mode)
Supported room sensors:
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 sn = v.second.second.storagenr;
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(),
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
&& (mit == MeasurementType::Unknown || mit == ty)

Wyświetl plik

@ -31,10 +31,12 @@
X(Volume,0x10,0x17) \
X(VolumeFlow,0x38,0x3F) \
X(FlowTemperature,0x58,0x5B) \
X(ReturnTemperature,0x5D,0x5D) \
X(ExternalTemperature,0x64,0x67) \
X(HeatCostAllocation,0x6E,0x6E) \
X(Date,0x6C,0x6C) \
X(DateTime,0x6D,0x6D) \
X(EnergyMJ,0x0e,0x0e) \
X(EnergyWh,0x00,0x07) \
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(multical21, C1_bit, Water, MULTICAL21, Multical21) \
X(multical302,C1_bit, Heat, MULTICAL302, Multical302) \
X(multical403,C1_bit, Heat, MULTICAL403, Multical403) \
X(omnipower, C1_bit, Electricity, OMNIPOWER, Omnipower) \
X(rfmamb, T1_bit, TempHygro, RFMAMB, RfmAmb) \
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> createFlowIQ3100(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<WaterMeter> createWaterstarM(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(Year, Hour, {vto=vfrom*24.0*365;}) \
X(KWH, GJ, {vto=vfrom*0.0036;}) \
X(KWH, MJ, {vto=vfrom*0.0036*1000.0;}) \
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(L, M3, {vto=vfrom/1000.0;}) \
X(C, F, {vto=(vfrom*9.0/5.0)+32.0;}) \

Wyświetl plik

@ -35,6 +35,7 @@
#define LIST_OF_UNITS \
X(KWH,kwh,"kWh",Energy,"kilo Watt hour") \
X(MJ,mj,"MJ",Energy,"Mega Joule") \
X(GJ,gj,"GJ",Energy,"Giga Joule") \
X(M3,m3,"m3",Volume,"cubic meter") \
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
\fBmeter_name\fR a mnemonic for your utility meter
.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.
This is necessary since an apator162 can be configured to send either c1 or t1 telegrams.)
.TP