diff --git a/Makefile b/Makefile index 4e27dd7..c76a38a 100644 --- a/Makefile +++ b/Makefile @@ -67,6 +67,7 @@ METER_OBJS:=\ $(BUILD)/meter_iperl.o \ $(BUILD)/meter_qcaloric.o \ $(BUILD)/meter_apator162.o \ + $(BUILD)/meter_amiplus.o \ $(BUILD)/printer.o \ $(BUILD)/serial.o \ $(BUILD)/shell.o \ diff --git a/README.md b/README.md index 114cd89..cf602da 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ Qundis Q caloric (qcaloric) Work in progress: Water meter Apator at-wmbus-16-2 (apator162) Heat meter Kamstrup Multical 302 (multical302) -Electricity meter Kamstrup Omnipower (omnipower) +Electricity meter Kamstrup Omnipower (omnipower) and Tauron Amiplus (amiplus) ``` The wmbus dongles imst871a and amb8465 can only listen on one type of wmbus telegrams at a time. diff --git a/src/main.cc b/src/main.cc index 89c5c9e..25c17a9 100644 --- a/src/main.cc +++ b/src/main.cc @@ -102,7 +102,8 @@ Qundis Q caloric (qcaloric) Work in progress: Water meter Apator at-wmbus-16-2 (apator162) Heat meter Kamstrup Multical 302 (multical302) -Electricity meter Kamstrup Omnipower (omnipower) +Electricity meter Kamstrup Omnipower (omnipower) and Tauron Amiplus (amiplus) + )MANUAL"; puts(msg); } @@ -259,6 +260,10 @@ void startUsingCommandline(Configuration *config) meters.push_back(createOmnipower(wmbus.get(), m.name, m.id, m.key)); verbose("(omnipower) configured \"%s\" \"omnipower\" \"%s\" %s\n", m.name.c_str(), m.id.c_str(), keymsg); break; + case AMIPLUS_METER: + meters.push_back(createAmiplus(wmbus.get(), m.name, m.id, m.key)); + verbose("(amiplus) configured \"%s\" \"amiplus\" \"%s\" %s\n", m.name.c_str(), m.id.c_str(), keymsg); + break; case SUPERCOM587_METER: meters.push_back(createSupercom587(wmbus.get(), m.name, m.id, m.key)); verbose("(supercom587) configured \"%s\" \"supercom587\" \"%s\" %s\n", m.name.c_str(), m.id.c_str(), keymsg); diff --git a/src/meter_amiplus.cc b/src/meter_amiplus.cc new file mode 100644 index 0000000..36c1214 --- /dev/null +++ b/src/meter_amiplus.cc @@ -0,0 +1,174 @@ +/* + Copyright (C) 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 . +*/ + +#include"dvparser.h" +#include"meters.h" +#include"meters_common_implementation.h" +#include"wmbus.h" +#include"wmbus_utils.h" +#include"util.h" + +#include +#include +#include +#include +#include +#include + +struct MeterAmiplus : public virtual ElectricityMeter, public virtual MeterCommonImplementation { + MeterAmiplus(WMBus *bus, string& name, string& id, string& key); + + double totalEnergyConsumption(); + double currentPowerConsumption(); + + void printMeter(Telegram *t, + string *human_readable, + string *fields, char separator, + string *json, + vector *envs); + +private: + void handleTelegram(Telegram *t); + void processContent(Telegram *t); + + double total_energy_ {}; + double current_power_ {}; +}; + +MeterAmiplus::MeterAmiplus(WMBus *bus, string& name, string& id, string& key) : + MeterCommonImplementation(bus, name, id, key, AMIPLUS_METER, MANUFACTURER_APA, LinkMode::T1) +{ + addMedia(0x02); + MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*)); +} + +double MeterAmiplus::totalEnergyConsumption() +{ + return total_energy_; +} + +double MeterAmiplus::currentPowerConsumption() +{ + return current_power_; +} + +void MeterAmiplus::handleTelegram(Telegram *t) { + + if (!isTelegramForMe(t)) { + // This telegram is not intended for this meter. + return; + } + + verbose("(omnipower) %s %02x%02x%02x%02x ", + name().c_str(), + t->a_field_address[0], t->a_field_address[1], t->a_field_address[2], + t->a_field_address[3]); + + if (t->a_field_version != 0x01) { + warning("(omnipower) expected telegram from KAM meter with version 0x01, but got \"%s\" version 0x2x !\n", + manufacturerFlag(t->m_field).c_str(), t->a_field_version); + } + + if (t->isEncrypted() && !useAes() && !t->isSimulated()) { + warning("(omnipower) warning: telegram is encrypted but no key supplied!\n"); + } + if (useAes()) { + vector aeskey = key(); + decryptMode5_AES_CBC(t, aeskey); + } else { + t->content = t->payload; + } + logTelegram("(omnipower) log", t->parsed, t->content); + int content_start = t->parsed.size(); + processContent(t); + if (isDebugEnabled()) { + t->explainParse("(omnipower)", content_start); + } + triggerUpdate(t); +} + +void MeterAmiplus::processContent(Telegram *t) +{ + map> values; + parseDV(t, t->content, t->content.begin(), t->content.size(), &values); + + int offset; + extractDVdouble(&values, "04833B", &offset, &total_energy_); + t->addMoreExplanation(offset, " total power (%f kwh)", total_energy_); +} + +unique_ptr createAmiplus(WMBus *bus, string& name, string& id, string& key) +{ + return unique_ptr(new MeterAmiplus(bus,name,id,key)); +} + +void MeterAmiplus::printMeter(Telegram *t, + string *human_readable, + string *fields, char separator, + string *json, + vector *envs) +{ + + char buf[65536]; + buf[65535] = 0; + + snprintf(buf, sizeof(buf)-1, "%s\t%s\t% 3.3f kwh\t% 3.3f kwh\t%s", + name().c_str(), + t->id.c_str(), + totalEnergyConsumption(), + currentPowerConsumption(), + datetimeOfUpdateHumanReadable().c_str()); + + *human_readable = buf; + + snprintf(buf, sizeof(buf)-1, "%s%c%s%c%f%c%f%c%s", + name().c_str(), separator, + t->id.c_str(), separator, + totalEnergyConsumption(), separator, + currentPowerConsumption(), separator, + datetimeOfUpdateRobot().c_str()); + + *fields = buf; + +#define Q(x,y) "\""#x"\":"#y"," +#define QS(x,y) "\""#x"\":\""#y"\"," +#define QSE(x,y) "\""#x"\":\""#y"\"" + + snprintf(buf, sizeof(buf)-1, "{" + QS(media,electricity) + QS(meter,omnipower) + QS(name,%s) + QS(id,%s) + Q(total_kwh,%f) + QS(current_kw,%f) + QSE(timestamp,%s) + "}", + name().c_str(), + t->id.c_str(), + totalEnergyConsumption(), + currentPowerConsumption(), + datetimeOfUpdateRobot().c_str()); + + *json = buf; + + envs->push_back(string("METER_JSON=")+*json); + envs->push_back(string("METER_TYPE=omnipower")); + envs->push_back(string("METER_ID=")+t->id); + envs->push_back(string("METER_TOTAL_KWH=")+to_string(totalEnergyConsumption())); + envs->push_back(string("METER_CURRENT_KW=")+to_string(currentPowerConsumption())); + envs->push_back(string("METER_TIMESTAMP=")+datetimeOfUpdateRobot()); +} diff --git a/src/meters.cc b/src/meters.cc index f96a07c..4960dce 100644 --- a/src/meters.cc +++ b/src/meters.cc @@ -108,6 +108,7 @@ MeterType toMeterType(string& type) if (type == "flowiq3100") return FLOWIQ3100_METER; if (type == "multical302") return MULTICAL302_METER; if (type == "omnipower") return OMNIPOWER_METER; + if (type == "amiplus") return AMIPLUS_METER; if (type == "supercom587") return SUPERCOM587_METER; if (type == "iperl") return IPERL_METER; if (type == "qcaloric") return QCALORIC_METER; @@ -121,6 +122,7 @@ LinkMode toMeterLinkMode(string& type) if (type == "flowiq3100") return LinkMode::C1; if (type == "multical302") return LinkMode::C1; if (type == "omnipower") return LinkMode::C1; + if (type == "amiplus") return LinkMode::T1; if (type == "supercom587") return LinkMode::T1; if (type == "iperl") return LinkMode::T1; if (type == "qcaloric") return LinkMode::C1; diff --git a/src/meters.h b/src/meters.h index a359e7a..938f3e2 100644 --- a/src/meters.h +++ b/src/meters.h @@ -24,7 +24,7 @@ #include #include -#define LIST_OF_METERS X(MULTICAL21_METER)X(FLOWIQ3100_METER)X(MULTICAL302_METER)X(OMNIPOWER_METER)X(SUPERCOM587_METER)X(IPERL_METER)X(QCALORIC_METER)X(APATOR162_METER)X(UNKNOWN_METER) +#define LIST_OF_METERS X(MULTICAL21_METER)X(FLOWIQ3100_METER)X(MULTICAL302_METER)X(OMNIPOWER_METER)X(SUPERCOM587_METER)X(IPERL_METER)X(QCALORIC_METER)X(APATOR162_METER)X(AMIPLUS_METER)X(UNKNOWN_METER) enum MeterType { #define X(name) name, @@ -115,6 +115,7 @@ unique_ptr createMultical21(WMBus *bus, string& name, string& id, st unique_ptr createFlowIQ3100(WMBus *bus, string& name, string& id, string& key); unique_ptr createMultical302(WMBus *bus, string& name, string& id, string& key); unique_ptr createOmnipower(WMBus *bus, string& name, string& id, string& key); +unique_ptr createAmiplus(WMBus *bus, string& name, string& id, string& key); unique_ptr createSupercom587(WMBus *bus, string& name, string& id, string& key); unique_ptr createApator162(WMBus *bus, string& name, string& id, string& key); unique_ptr createIperl(WMBus *bus, string& name, string& id, string& key); diff --git a/wmbusmeters.1 b/wmbusmeters.1 index 85bd696..d81f3aa 100644 --- a/wmbusmeters.1 +++ b/wmbusmeters.1 @@ -68,7 +68,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 +\fBmeter_type\fR multical21/flowiq3100/supercom587/iperl/multical302/omnipower/qcaloric/apator162/amiplus .TP \fBmeter_id\fR one or more 8 digit numbers separated with commas, or a single '*' wildcard. .TP