kopia lustrzana https://github.com/weetmuts/wmbusmeters
Added support for multiple comma separated ids.
rodzic
87c4cb9256
commit
7e72fe0f3f
|
@ -45,6 +45,9 @@ check with `tail -f /var/log/syslog` and `tail -f /var/log/wmbusmeters/wmbusmete
|
|||
|
||||
The latest reading of the meter can also be found here: /var/log/wmbusmeters/meter_readings/MyTapWater
|
||||
|
||||
You can use several ids using `id=1111111,2222222,3333333` or you can listen to all
|
||||
meters of a certain type `id=*`.
|
||||
|
||||
# Run using config files
|
||||
|
||||
If you cannot install as a daemon, then you can also start
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
telegram=|314493441234567835087a740000200b6e2701004b6e450100426c5f2ccb086e790000c2086c7f21326cffff046d200b7422|
|
||||
{"media":"heat_cost_allocation","meter":"qcaloric","name":"Element","id":"78563412","current_consumption_hca":127.000000,"set_date":"2018-12-31","consumption_at_set_date_hca":145.000000,"set_date_17":"2019-01-31","consumption_at_set_date_17_hca":79.000000,"error_date":"2127-15-31","device_date_time":"2019-02-20 11:32","timestamp":"1111-11-11T11:11:11Z"}
|
||||
telegram=|314493441334567835087a740000200b6e2701004b6e450100426c5f2ccb086e790000c2086c7f21326cffff046d200b7422|
|
||||
{"media":"heat_cost_allocation","meter":"qcaloric","name":"Element","id":"78563413","current_consumption_hca":127.000000,"set_date":"2018-12-31","consumption_at_set_date_hca":145.000000,"set_date_17":"2019-01-31","consumption_at_set_date_17_hca":79.000000,"error_date":"2127-15-31","device_date_time":"2019-02-20 11:32","timestamp":"1111-11-11T11:11:11Z"}
|
||||
telegram=|314493441434567835087a740000200b6e2701004b6e450100426c5f2ccb086e790000c2086c7f21326cffff046d200b7422|
|
||||
{"media":"heat_cost_allocation","meter":"qcaloric","name":"Element","id":"78563414","current_consumption_hca":127.000000,"set_date":"2018-12-31","consumption_at_set_date_hca":145.000000,"set_date_17":"2019-01-31","consumption_at_set_date_17_hca":79.000000,"error_date":"2127-15-31","device_date_time":"2019-02-20 11:32","timestamp":"1111-11-11T11:11:11Z"}
|
12
src/main.cc
12
src/main.cc
|
@ -37,7 +37,7 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
void oneshotCheck(Configuration *cmdline, SerialCommunicationManager *manager, Meter *meter, vector<unique_ptr<Meter>> &meters);
|
||||
void oneshotCheck(Configuration *cmdline, SerialCommunicationManager *manager, Telegram *t, Meter *meter, vector<unique_ptr<Meter>> &meters);
|
||||
void startUsingCommandline(Configuration *cmdline);
|
||||
void startUsingConfigFiles(string root, bool is_daemon);
|
||||
void startDaemon(string pid_file); // Will use config files.
|
||||
|
@ -277,7 +277,8 @@ void startUsingCommandline(Configuration *config)
|
|||
if (config->list_shell_envs) {
|
||||
string ignore1, ignore2, ignore3;
|
||||
vector<string> envs;
|
||||
meters.back()->printMeter("",
|
||||
Telegram t;
|
||||
meters.back()->printMeter(&t,
|
||||
&ignore1,
|
||||
&ignore2, config->separator,
|
||||
&ignore3,
|
||||
|
@ -290,8 +291,8 @@ void startUsingCommandline(Configuration *config)
|
|||
}
|
||||
exit(0);
|
||||
}
|
||||
meters.back()->onUpdate([&](string id, Meter* meter) { output->print(id,meter); });
|
||||
// meters.back()->onUpdate([&](string id, Meter* meter) { oneshotCheck(config, manager.get(), meter, meters); });
|
||||
meters.back()->onUpdate([&](Telegram*t,Meter* meter) { output->print(t,meter); });
|
||||
meters.back()->onUpdate([&](Telegram*t, Meter* meter) { oneshotCheck(config, manager.get(), t, meter, meters); });
|
||||
}
|
||||
} else {
|
||||
notice("No meters configured. Printing id:s of all telegrams heard!\n\n");
|
||||
|
@ -314,7 +315,7 @@ void startUsingCommandline(Configuration *config)
|
|||
}
|
||||
}
|
||||
|
||||
void oneshotCheck(Configuration *config, SerialCommunicationManager *manager, Meter *meter, vector<unique_ptr<Meter>> &meters)
|
||||
void oneshotCheck(Configuration *config, SerialCommunicationManager *manager, Telegram *t, Meter *meter, vector<unique_ptr<Meter>> &meters)
|
||||
{
|
||||
if (!config->oneshot) return;
|
||||
|
||||
|
@ -322,6 +323,7 @@ void oneshotCheck(Configuration *config, SerialCommunicationManager *manager, Me
|
|||
if (m->numUpdates() == 0) return;
|
||||
}
|
||||
// All meters have received at least one update! Stop!
|
||||
verbose("(main) all meters have received at least one update, stopping.\n");
|
||||
manager->stop();
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ struct MeterIperl : public virtual WaterMeter, public virtual MeterCommonImpleme
|
|||
string timeLeaking();
|
||||
string timeBursting();
|
||||
|
||||
void printMeter(string id,
|
||||
void printMeter(Telegram *t,
|
||||
string *human_readable,
|
||||
string *fields, char separator,
|
||||
string *json,
|
||||
|
@ -70,8 +70,10 @@ private:
|
|||
};
|
||||
|
||||
MeterIperl::MeterIperl(WMBus *bus, string& name, string& id, string& key) :
|
||||
MeterCommonImplementation(bus, name, id, key, IPERL_METER, MANUFACTURER_SEN, 0x16, LinkModeT1)
|
||||
MeterCommonImplementation(bus, name, id, key, IPERL_METER, MANUFACTURER_SEN, LinkModeT1)
|
||||
{
|
||||
addMedia(0x06);
|
||||
addMedia(0x07);
|
||||
MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*));
|
||||
}
|
||||
|
||||
|
@ -98,15 +100,7 @@ void MeterIperl::handleTelegram(Telegram *t)
|
|||
t->a_field_address[0], t->a_field_address[1], t->a_field_address[2],
|
||||
t->a_field_address[3]);
|
||||
|
||||
if (t->a_field_device_type != 0x07 && t->a_field_device_type != 0x06) {
|
||||
warning("(%s) expected telegram for cold or warm water media, but got \"%s\"!\n", "iperl",
|
||||
mediaType(t->m_field, t->a_field_device_type).c_str());
|
||||
}
|
||||
|
||||
updateMedia(t->a_field_device_type);
|
||||
|
||||
if (t->m_field != manufacturer() ||
|
||||
t->a_field_version != 0x68) {
|
||||
if (t->a_field_version != 0x68) {
|
||||
warning("(%s) expected telegram from SEN meter with version 0x%02x, "
|
||||
"but got \"%s\" meter with version 0x%02x !\n", "iperl",
|
||||
0x68,
|
||||
|
@ -149,7 +143,7 @@ void MeterIperl::processContent(Telegram *t)
|
|||
t->addMoreExplanation(offset, " total consumption (%f m3)", total_water_consumption_);
|
||||
}
|
||||
|
||||
void MeterIperl::printMeter(string id,
|
||||
void MeterIperl::printMeter(Telegram *t,
|
||||
string *human_readable,
|
||||
string *fields, char separator,
|
||||
string *json,
|
||||
|
@ -164,7 +158,7 @@ void MeterIperl::printMeter(string id,
|
|||
"% 3.3f m3\t"
|
||||
"%s",
|
||||
name().c_str(),
|
||||
id.c_str(),
|
||||
t->id.c_str(),
|
||||
totalWaterConsumption(),
|
||||
datetimeOfUpdateHumanReadable().c_str());
|
||||
|
||||
|
@ -176,7 +170,7 @@ void MeterIperl::printMeter(string id,
|
|||
"%f%c"
|
||||
"%s",
|
||||
name().c_str(), separator,
|
||||
id.c_str(), separator,
|
||||
t->id.c_str(), separator,
|
||||
totalWaterConsumption(), separator,
|
||||
datetimeOfUpdateRobot().c_str());
|
||||
|
||||
|
@ -194,9 +188,9 @@ void MeterIperl::printMeter(string id,
|
|||
Q(total_m3,%f)
|
||||
QSE(timestamp,%s)
|
||||
"}",
|
||||
mediaType(manufacturer(), media()).c_str(),
|
||||
mediaType(manufacturer(), t->a_field_device_type).c_str(),
|
||||
name().c_str(),
|
||||
id.c_str(),
|
||||
t->id.c_str(),
|
||||
totalWaterConsumption(),
|
||||
datetimeOfUpdateRobot().c_str());
|
||||
|
||||
|
@ -204,7 +198,7 @@ void MeterIperl::printMeter(string id,
|
|||
|
||||
envs->push_back(string("METER_JSON=")+*json);
|
||||
envs->push_back(string("METER_TYPE=iperl"));
|
||||
envs->push_back(string("METER_ID=")+id);
|
||||
envs->push_back(string("METER_ID=")+t->id);
|
||||
envs->push_back(string("METER_TOTAL_M3=")+to_string(totalWaterConsumption()));
|
||||
envs->push_back(string("METER_TIMESTAMP=")+datetimeOfUpdateRobot());
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ struct MeterMultical21 : public virtual WaterMeter, public virtual MeterCommonIm
|
|||
string timeLeaking();
|
||||
string timeBursting();
|
||||
|
||||
void printMeter(string id,
|
||||
void printMeter(Telegram *t,
|
||||
string *human_readable,
|
||||
string *fields, char separator,
|
||||
string *json,
|
||||
|
@ -105,8 +105,10 @@ private:
|
|||
};
|
||||
|
||||
MeterMultical21::MeterMultical21(WMBus *bus, string& name, string& id, string& key, MeterType mt) :
|
||||
MeterCommonImplementation(bus, name, id, key, mt, MANUFACTURER_KAM, 0x16, LinkModeC1)
|
||||
MeterCommonImplementation(bus, name, id, key, mt, MANUFACTURER_KAM, LinkModeC1)
|
||||
{
|
||||
addMedia(0x16); // Water media
|
||||
|
||||
if (type() == MULTICAL21_METER) {
|
||||
expected_version_ = 0x1b;
|
||||
meter_name_ = "multical21";
|
||||
|
@ -200,13 +202,7 @@ void MeterMultical21::handleTelegram(Telegram *t)
|
|||
t->a_field_address[0], t->a_field_address[1], t->a_field_address[2],
|
||||
t->a_field_address[3]);
|
||||
|
||||
if (t->a_field_device_type != 0x16) {
|
||||
warning("(%s) expected telegram for water media, but got \"%s\"!\n", meter_name_,
|
||||
mediaType(t->m_field, t->a_field_device_type).c_str());
|
||||
}
|
||||
|
||||
if (t->m_field != manufacturer() ||
|
||||
t->a_field_version != expected_version_) {
|
||||
if (t->a_field_version != expected_version_) {
|
||||
warning("(%s) expected telegram from KAM meter with version 0x%02x, "
|
||||
"but got \"%s\" meter with version 0x%02x !\n", meter_name_,
|
||||
expected_version_,
|
||||
|
@ -496,7 +492,7 @@ string MeterMultical21::decodeTime(int time)
|
|||
}
|
||||
}
|
||||
|
||||
void MeterMultical21::printMeter(string id,
|
||||
void MeterMultical21::printMeter(Telegram *t,
|
||||
string *human_readable,
|
||||
string *fields, char separator,
|
||||
string *json,
|
||||
|
@ -525,7 +521,7 @@ void MeterMultical21::printMeter(string id,
|
|||
|
||||
snprintf(buf, sizeof(buf)-1, "%s\t%s\t% 3.3f m3\t% 3.3f m3\t% 3.3f m3/h\t%s°C\t%s°C\t%s\t%s",
|
||||
name().c_str(),
|
||||
id.c_str(),
|
||||
t->id.c_str(),
|
||||
totalWaterConsumption(),
|
||||
targetWaterConsumption(),
|
||||
maxFlow(),
|
||||
|
@ -538,7 +534,7 @@ void MeterMultical21::printMeter(string id,
|
|||
|
||||
snprintf(buf, sizeof(buf)-1, "%s%c" "%s%c" "%f%c" "%f%c" "%f%c" "%.0f%c" "%.0f%c" "%s%c" "%s",
|
||||
name().c_str(), separator,
|
||||
id.c_str(), separator,
|
||||
t->id.c_str(), separator,
|
||||
totalWaterConsumption(), separator,
|
||||
targetWaterConsumption(), separator,
|
||||
maxFlow(), separator,
|
||||
|
@ -570,10 +566,10 @@ void MeterMultical21::printMeter(string id,
|
|||
QS(time_bursting,%s)
|
||||
QSE(timestamp,%s)
|
||||
"}",
|
||||
mediaType(manufacturer(), media()).c_str(),
|
||||
mediaType(manufacturer(), t->a_field_device_type).c_str(),
|
||||
meter_name_,
|
||||
name().c_str(),
|
||||
id.c_str(),
|
||||
t->id.c_str(),
|
||||
totalWaterConsumption(),
|
||||
targetWaterConsumption(),
|
||||
maxFlow(),
|
||||
|
@ -590,7 +586,7 @@ void MeterMultical21::printMeter(string id,
|
|||
|
||||
envs->push_back(string("METER_JSON=")+*json);
|
||||
envs->push_back(string("METER_TYPE=")+meter_name_);
|
||||
envs->push_back(string("METER_ID=")+id);
|
||||
envs->push_back(string("METER_ID=")+t->id);
|
||||
envs->push_back(string("METER_TOTAL_M3=")+to_string(totalWaterConsumption()));
|
||||
envs->push_back(string("METER_TARGET_M3=")+to_string(targetWaterConsumption()));
|
||||
envs->push_back(string("METER_MAX_FLOW_M3H=")+to_string(maxFlow()));
|
||||
|
|
|
@ -35,7 +35,7 @@ struct MeterMultical302 : public virtual HeatMeter, public virtual MeterCommonIm
|
|||
double totalVolume();
|
||||
|
||||
|
||||
void printMeter(string id,
|
||||
void printMeter(Telegram *t,
|
||||
string *human_readable,
|
||||
string *fields, char separator,
|
||||
string *json,
|
||||
|
@ -51,8 +51,9 @@ private:
|
|||
};
|
||||
|
||||
MeterMultical302::MeterMultical302(WMBus *bus, string& name, string& id, string& key) :
|
||||
MeterCommonImplementation(bus, name, id, key, MULTICAL302_METER, MANUFACTURER_KAM, 0x04, LinkModeC1)
|
||||
MeterCommonImplementation(bus, name, id, key, MULTICAL302_METER, MANUFACTURER_KAM, LinkModeC1)
|
||||
{
|
||||
addMedia(0x04); // Heat media
|
||||
MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*));
|
||||
}
|
||||
|
||||
|
@ -83,18 +84,6 @@ void MeterMultical302::handleTelegram(Telegram *t) {
|
|||
t->a_field_address[0], t->a_field_address[1], t->a_field_address[2],
|
||||
t->a_field_address[3]);
|
||||
|
||||
if (t->a_field_device_type != 0x04) {
|
||||
warning("(multical302) expected telegram for heat media, but got \"%s\"!\n",
|
||||
mediaType(t->m_field, t->a_field_device_type).c_str());
|
||||
}
|
||||
|
||||
/*
|
||||
if (t->m_field != manufacturer() ||
|
||||
t->a_field_version != 0x1b) {
|
||||
warning("(multical302) expected telegram from KAM meter with version 0x1b, but got \"%s\" version 0x2x !\n",
|
||||
manufacturerFlag(t->m_field).c_str(), t->a_field_version);
|
||||
}*/
|
||||
|
||||
if (useAes()) {
|
||||
vector<uchar> aeskey = key();
|
||||
decryptMode1_AES_CTR(t, aeskey);
|
||||
|
@ -183,7 +172,7 @@ unique_ptr<HeatMeter> createMultical302(WMBus *bus, string& name, string& id, st
|
|||
return unique_ptr<HeatMeter>(new MeterMultical302(bus,name,id,key));
|
||||
}
|
||||
|
||||
void MeterMultical302::printMeter(string id,
|
||||
void MeterMultical302::printMeter(Telegram *t,
|
||||
string *human_readable,
|
||||
string *fields, char separator,
|
||||
string *json,
|
||||
|
@ -194,7 +183,7 @@ void MeterMultical302::printMeter(string id,
|
|||
|
||||
snprintf(buf, sizeof(buf)-1, "%s\t%s\t% 3.3f kwh\t% 3.3f m3\t% 3.3f kwh\t%s",
|
||||
name().c_str(),
|
||||
id.c_str(),
|
||||
t->id.c_str(),
|
||||
totalEnergyConsumption(),
|
||||
totalVolume(),
|
||||
currentPowerConsumption(),
|
||||
|
@ -204,7 +193,7 @@ void MeterMultical302::printMeter(string id,
|
|||
|
||||
snprintf(buf, sizeof(buf)-1, "%s%c%s%c%f%c%f%c%f%c%s",
|
||||
name().c_str(), separator,
|
||||
id.c_str(), separator,
|
||||
t->id.c_str(), separator,
|
||||
totalEnergyConsumption(), separator,
|
||||
totalVolume(), separator,
|
||||
currentPowerConsumption(), separator,
|
||||
|
@ -227,7 +216,7 @@ void MeterMultical302::printMeter(string id,
|
|||
QSE(timestamp,%s)
|
||||
"}",
|
||||
name().c_str(),
|
||||
id.c_str(),
|
||||
t->id.c_str(),
|
||||
totalEnergyConsumption(),
|
||||
totalVolume(),
|
||||
currentPowerConsumption(),
|
||||
|
@ -237,7 +226,7 @@ void MeterMultical302::printMeter(string id,
|
|||
|
||||
envs->push_back(string("METER_JSON=")+*json);
|
||||
envs->push_back(string("METER_TYPE=multical302"));
|
||||
envs->push_back(string("METER_ID=")+id);
|
||||
envs->push_back(string("METER_ID=")+t->id);
|
||||
envs->push_back(string("METER_TOTAL_KWH=")+to_string(totalEnergyConsumption()));
|
||||
envs->push_back(string("METER_TOTAL_VOLUME_M3=")+to_string(totalVolume()));
|
||||
envs->push_back(string("METER_CURRENT_KW=")+to_string(currentPowerConsumption()));
|
||||
|
|
|
@ -35,7 +35,7 @@ struct MeterOmnipower : public virtual ElectricityMeter, public virtual MeterCom
|
|||
double totalEnergyConsumption();
|
||||
double currentPowerConsumption();
|
||||
|
||||
void printMeter(string id,
|
||||
void printMeter(Telegram *t,
|
||||
string *human_readable,
|
||||
string *fields, char separator,
|
||||
string *json,
|
||||
|
@ -50,8 +50,9 @@ private:
|
|||
};
|
||||
|
||||
MeterOmnipower::MeterOmnipower(WMBus *bus, string& name, string& id, string& key) :
|
||||
MeterCommonImplementation(bus, name, id, key, OMNIPOWER_METER, MANUFACTURER_KAM, 0x04, LinkModeC1)
|
||||
MeterCommonImplementation(bus, name, id, key, OMNIPOWER_METER, MANUFACTURER_KAM, LinkModeC1)
|
||||
{
|
||||
addMedia(0x02);
|
||||
MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*));
|
||||
}
|
||||
|
||||
|
@ -77,13 +78,7 @@ void MeterOmnipower::handleTelegram(Telegram *t) {
|
|||
t->a_field_address[0], t->a_field_address[1], t->a_field_address[2],
|
||||
t->a_field_address[3]);
|
||||
|
||||
if (t->a_field_device_type != 0x02) {
|
||||
warning("(omnipower) expected telegram for electricity media, but got \"%s\"!\n",
|
||||
mediaType(t->m_field, t->a_field_device_type).c_str());
|
||||
}
|
||||
|
||||
if (t->m_field != manufacturer() ||
|
||||
t->a_field_version != 0x01) {
|
||||
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);
|
||||
}
|
||||
|
@ -124,7 +119,7 @@ unique_ptr<ElectricityMeter> createOmnipower(WMBus *bus, string& name, string& i
|
|||
return unique_ptr<ElectricityMeter>(new MeterOmnipower(bus,name,id,key));
|
||||
}
|
||||
|
||||
void MeterOmnipower::printMeter(string id,
|
||||
void MeterOmnipower::printMeter(Telegram *t,
|
||||
string *human_readable,
|
||||
string *fields, char separator,
|
||||
string *json,
|
||||
|
@ -136,7 +131,7 @@ void MeterOmnipower::printMeter(string id,
|
|||
|
||||
snprintf(buf, sizeof(buf)-1, "%s\t%s\t% 3.3f kwh\t% 3.3f kwh\t%s",
|
||||
name().c_str(),
|
||||
id.c_str(),
|
||||
t->id.c_str(),
|
||||
totalEnergyConsumption(),
|
||||
currentPowerConsumption(),
|
||||
datetimeOfUpdateHumanReadable().c_str());
|
||||
|
@ -145,7 +140,7 @@ void MeterOmnipower::printMeter(string id,
|
|||
|
||||
snprintf(buf, sizeof(buf)-1, "%s%c%s%c%f%c%f%c%s",
|
||||
name().c_str(), separator,
|
||||
id.c_str(), separator,
|
||||
t->id.c_str(), separator,
|
||||
totalEnergyConsumption(), separator,
|
||||
currentPowerConsumption(), separator,
|
||||
datetimeOfUpdateRobot().c_str());
|
||||
|
@ -166,7 +161,7 @@ void MeterOmnipower::printMeter(string id,
|
|||
QSE(timestamp,%s)
|
||||
"}",
|
||||
name().c_str(),
|
||||
id.c_str(),
|
||||
t->id.c_str(),
|
||||
totalEnergyConsumption(),
|
||||
currentPowerConsumption(),
|
||||
datetimeOfUpdateRobot().c_str());
|
||||
|
@ -175,7 +170,7 @@ void MeterOmnipower::printMeter(string id,
|
|||
|
||||
envs->push_back(string("METER_JSON=")+*json);
|
||||
envs->push_back(string("METER_TYPE=omnipower"));
|
||||
envs->push_back(string("METER_ID=")+id);
|
||||
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());
|
||||
|
|
|
@ -36,7 +36,7 @@ struct MeterQCaloric : public virtual HeatCostMeter, public virtual MeterCommonI
|
|||
string setDate();
|
||||
double consumptionAtSetDate();
|
||||
|
||||
void printMeter(string id,
|
||||
void printMeter(Telegram *t,
|
||||
string *human_readable,
|
||||
string *fields, char separator,
|
||||
string *json,
|
||||
|
@ -60,8 +60,9 @@ private:
|
|||
};
|
||||
|
||||
MeterQCaloric::MeterQCaloric(WMBus *bus, string& name, string& id, string& key) :
|
||||
MeterCommonImplementation(bus, name, id, key, QCALORIC_METER, MANUFACTURER_QDS, 0x08, LinkModeC1)
|
||||
MeterCommonImplementation(bus, name, id, key, QCALORIC_METER, MANUFACTURER_QDS, LinkModeC1)
|
||||
{
|
||||
addMedia(0x08);
|
||||
MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*));
|
||||
}
|
||||
|
||||
|
@ -92,14 +93,8 @@ void MeterQCaloric::handleTelegram(Telegram *t) {
|
|||
t->a_field_address[0], t->a_field_address[1], t->a_field_address[2],
|
||||
t->a_field_address[3]);
|
||||
|
||||
if (t->a_field_device_type != 0x08) {
|
||||
warning("(qcaloric) expected telegram for heat cost allocator, but got \"%s\"!\n",
|
||||
mediaType(t->m_field, t->a_field_device_type).c_str());
|
||||
}
|
||||
|
||||
if (t->m_field != manufacturer() ||
|
||||
t->a_field_version != 0x35) {
|
||||
warning("(qcaloric) expected telegram from QDS meter with version 0x01, but got \"%s\" version 0x%02x !\n",
|
||||
if (t->a_field_version != 0x35) {
|
||||
warning("(qcaloric) expected telegram from QDS meter with version 0x35, but got \"%s\" version 0x%02x !\n",
|
||||
manufacturerFlag(t->m_field).c_str(), t->a_field_version);
|
||||
}
|
||||
|
||||
|
@ -227,7 +222,7 @@ unique_ptr<HeatCostMeter> createQCaloric(WMBus *bus, string& name, string& id, s
|
|||
return unique_ptr<HeatCostMeter>(new MeterQCaloric(bus,name,id,key));
|
||||
}
|
||||
|
||||
void MeterQCaloric::printMeter(string id,
|
||||
void MeterQCaloric::printMeter(Telegram *t,
|
||||
string *human_readable,
|
||||
string *fields, char separator,
|
||||
string *json,
|
||||
|
@ -239,7 +234,7 @@ void MeterQCaloric::printMeter(string id,
|
|||
|
||||
snprintf(buf, sizeof(buf)-1, "%s\t%s\t% 3.0f hca\t%s\t% 3.0f hca\t%s",
|
||||
name().c_str(),
|
||||
id.c_str(),
|
||||
t->id.c_str(),
|
||||
currentConsumption(),
|
||||
setDate().c_str(),
|
||||
consumptionAtSetDate(),
|
||||
|
@ -249,7 +244,7 @@ void MeterQCaloric::printMeter(string id,
|
|||
|
||||
snprintf(buf, sizeof(buf)-1, "%s%c%s%c%f%c%s%c%f%c%s",
|
||||
name().c_str(), separator,
|
||||
id.c_str(), separator,
|
||||
t->id.c_str(), separator,
|
||||
currentConsumption(), separator,
|
||||
setDate().c_str(), separator,
|
||||
consumptionAtSetDate(), separator,
|
||||
|
@ -276,7 +271,7 @@ void MeterQCaloric::printMeter(string id,
|
|||
QSE(timestamp,%s)
|
||||
"}",
|
||||
name().c_str(),
|
||||
id.c_str(),
|
||||
t->id.c_str(),
|
||||
currentConsumption(),
|
||||
setDate().c_str(),
|
||||
consumptionAtSetDate(),
|
||||
|
@ -290,7 +285,7 @@ void MeterQCaloric::printMeter(string id,
|
|||
|
||||
envs->push_back(string("METER_JSON=")+*json);
|
||||
envs->push_back(string("METER_TYPE=qcaloric"));
|
||||
envs->push_back(string("METER_ID=")+id);
|
||||
envs->push_back(string("METER_ID=")+t->id);
|
||||
envs->push_back(string("METER_CURRENT_CONSUMPTION_HCA=")+to_string(currentConsumption()));
|
||||
envs->push_back(string("METER_SET_DATE=")+setDate());
|
||||
envs->push_back(string("METER_CONSUMPTION_AT_SET_DATE_HCA=")+to_string(consumptionAtSetDate()));
|
||||
|
|
|
@ -54,7 +54,7 @@ struct MeterSupercom587 : public virtual WaterMeter, public virtual MeterCommonI
|
|||
string timeLeaking();
|
||||
string timeBursting();
|
||||
|
||||
void printMeter(string id,
|
||||
void printMeter(Telegram *t,
|
||||
string *human_readable,
|
||||
string *fields, char separator,
|
||||
string *json,
|
||||
|
@ -69,8 +69,10 @@ private:
|
|||
};
|
||||
|
||||
MeterSupercom587::MeterSupercom587(WMBus *bus, string& name, string& id, string& key) :
|
||||
MeterCommonImplementation(bus, name, id, key, SUPERCOM587_METER, MANUFACTURER_SON, 0x16, LinkModeT1)
|
||||
MeterCommonImplementation(bus, name, id, key, SUPERCOM587_METER, MANUFACTURER_SON, LinkModeT1)
|
||||
{
|
||||
addMedia(0x06);
|
||||
addMedia(0x07);
|
||||
MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*));
|
||||
}
|
||||
|
||||
|
@ -97,15 +99,7 @@ void MeterSupercom587::handleTelegram(Telegram *t)
|
|||
t->a_field_address[0], t->a_field_address[1], t->a_field_address[2],
|
||||
t->a_field_address[3]);
|
||||
|
||||
if (t->a_field_device_type != 0x07 && t->a_field_device_type != 0x06) {
|
||||
warning("(%s) expected telegram for cold or warm water media, but got \"%s\"!\n", "supercom587",
|
||||
mediaType(t->m_field, t->a_field_device_type).c_str());
|
||||
}
|
||||
|
||||
updateMedia(t->a_field_device_type);
|
||||
|
||||
if (t->m_field != manufacturer() ||
|
||||
t->a_field_version != 0x3c) {
|
||||
if (t->a_field_version != 0x3c) {
|
||||
warning("(%s) expected telegram from SON meter with version 0x%02x, "
|
||||
"but got \"%s\" meter with version 0x%02x !\n", "supercom587",
|
||||
0x3c,
|
||||
|
@ -144,7 +138,7 @@ void MeterSupercom587::processContent(Telegram *t)
|
|||
t->addMoreExplanation(offset, " total consumption (%f m3)", total_water_consumption_);
|
||||
}
|
||||
|
||||
void MeterSupercom587::printMeter(string id,
|
||||
void MeterSupercom587::printMeter(Telegram *t,
|
||||
string *human_readable,
|
||||
string *fields, char separator,
|
||||
string *json,
|
||||
|
@ -159,7 +153,7 @@ void MeterSupercom587::printMeter(string id,
|
|||
"% 3.3f m3\t"
|
||||
"%s",
|
||||
name().c_str(),
|
||||
id.c_str(),
|
||||
t->id.c_str(),
|
||||
totalWaterConsumption(),
|
||||
datetimeOfUpdateHumanReadable().c_str());
|
||||
|
||||
|
@ -171,7 +165,7 @@ void MeterSupercom587::printMeter(string id,
|
|||
"%f%c"
|
||||
"%s",
|
||||
name().c_str(), separator,
|
||||
id.c_str(), separator,
|
||||
t->id.c_str(), separator,
|
||||
totalWaterConsumption(), separator,
|
||||
datetimeOfUpdateRobot().c_str());
|
||||
|
||||
|
@ -189,9 +183,9 @@ void MeterSupercom587::printMeter(string id,
|
|||
Q(total_m3,%f)
|
||||
QSE(timestamp,%s)
|
||||
"}",
|
||||
mediaType(manufacturer(), media()).c_str(),
|
||||
mediaType(manufacturer(), t->a_field_device_type).c_str(),
|
||||
name().c_str(),
|
||||
id.c_str(),
|
||||
t->id.c_str(),
|
||||
totalWaterConsumption(),
|
||||
datetimeOfUpdateRobot().c_str());
|
||||
|
||||
|
@ -199,7 +193,7 @@ void MeterSupercom587::printMeter(string id,
|
|||
|
||||
envs->push_back(string("METER_JSON=")+*json);
|
||||
envs->push_back(string("METER_TYPE=supercom587"));
|
||||
envs->push_back(string("METER_ID=")+id);
|
||||
envs->push_back(string("METER_ID=")+t->id);
|
||||
envs->push_back(string("METER_TOTAL_M3=")+to_string(totalWaterConsumption()));
|
||||
envs->push_back(string("METER_TIMESTAMP=")+datetimeOfUpdateRobot());
|
||||
}
|
||||
|
|
|
@ -21,13 +21,13 @@
|
|||
#include<memory.h>
|
||||
|
||||
MeterCommonImplementation::MeterCommonImplementation(WMBus *bus, string& name, string& id, string& key,
|
||||
MeterType type, int manufacturer, int media,
|
||||
MeterType type, int manufacturer,
|
||||
LinkMode required_link_mode) :
|
||||
type_(type), manufacturer_(manufacturer), media_(media), name_(name), bus_(bus),
|
||||
type_(type), manufacturer_(manufacturer), name_(name), bus_(bus),
|
||||
required_link_mode_(required_link_mode)
|
||||
{
|
||||
use_aes_ = true;
|
||||
id_ = id;
|
||||
ids_ = splitIds(id);
|
||||
if (key.length() == 0) {
|
||||
use_aes_ = false;
|
||||
} else {
|
||||
|
@ -45,14 +45,19 @@ int MeterCommonImplementation::manufacturer()
|
|||
return manufacturer_;
|
||||
}
|
||||
|
||||
int MeterCommonImplementation::media()
|
||||
vector<int> MeterCommonImplementation::media()
|
||||
{
|
||||
return media_;
|
||||
}
|
||||
|
||||
string MeterCommonImplementation::id()
|
||||
void MeterCommonImplementation::addMedia(int m)
|
||||
{
|
||||
return id_;
|
||||
media_.push_back(m);
|
||||
}
|
||||
|
||||
vector<string> MeterCommonImplementation::ids()
|
||||
{
|
||||
return ids_;
|
||||
}
|
||||
|
||||
string MeterCommonImplementation::name()
|
||||
|
@ -70,7 +75,7 @@ LinkMode MeterCommonImplementation::requiredLinkMode()
|
|||
return required_link_mode_;
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::onUpdate(function<void(string,Meter*)> cb)
|
||||
void MeterCommonImplementation::onUpdate(function<void(Telegram*,Meter*)> cb)
|
||||
{
|
||||
on_update_.push_back(cb);
|
||||
}
|
||||
|
@ -125,16 +130,46 @@ LinkMode toMeterLinkMode(string& type)
|
|||
|
||||
bool MeterCommonImplementation::isTelegramForMe(Telegram *t)
|
||||
{
|
||||
if (manufacturer_ != 0 && t->m_field != manufacturer_) {
|
||||
debug("(meter) %s: for me? %s\n", name_.c_str(), t->id.c_str());
|
||||
|
||||
bool id_match = false;
|
||||
for (auto id : ids_)
|
||||
{
|
||||
if (id == t->id) {
|
||||
id_match = true;
|
||||
break;
|
||||
}
|
||||
if (id == "*") {
|
||||
id_match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!id_match) {
|
||||
debug("(meter) %s: not for me: not my id\n", name_.c_str());
|
||||
return false;
|
||||
}
|
||||
if (id_ == t->id) {
|
||||
return true;
|
||||
|
||||
if (manufacturer_ != 0 && t->m_field != manufacturer_) {
|
||||
debug("(meter) %s: not for me: manufacturer differs\n", name_.c_str());
|
||||
return false;
|
||||
}
|
||||
if (id_ == "*") {
|
||||
return true;
|
||||
|
||||
bool media_match = false;
|
||||
for (auto m : media_) {
|
||||
if (m == t->a_field_device_type) {
|
||||
media_match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
if (!media_match) {
|
||||
debug("(meter) %s: not for me: no media match\n", name_.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
debug("(meter) %s: yes for me\n", name_.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MeterCommonImplementation::useAes()
|
||||
|
@ -171,11 +206,6 @@ void MeterCommonImplementation::triggerUpdate(Telegram *t)
|
|||
{
|
||||
datetime_of_update_ = time(NULL);
|
||||
num_updates_++;
|
||||
for (auto &cb : on_update_) if (cb) cb(t->id, this);
|
||||
for (auto &cb : on_update_) if (cb) cb(t, this);
|
||||
t->handled = true;
|
||||
}
|
||||
|
||||
void MeterCommonImplementation::updateMedia(int media)
|
||||
{
|
||||
media_ = media;
|
||||
}
|
||||
|
|
|
@ -37,21 +37,21 @@ using namespace std;
|
|||
typedef unsigned char uchar;
|
||||
|
||||
struct Meter {
|
||||
virtual string id() = 0;
|
||||
virtual vector<string> ids() = 0;
|
||||
virtual string name() = 0;
|
||||
virtual MeterType type() = 0;
|
||||
virtual int manufacturer() = 0;
|
||||
virtual int media() = 0;
|
||||
virtual vector<int> media() = 0;
|
||||
virtual WMBus *bus() = 0;
|
||||
virtual LinkMode requiredLinkMode() = 0;
|
||||
|
||||
virtual string datetimeOfUpdateHumanReadable() = 0;
|
||||
virtual string datetimeOfUpdateRobot() = 0;
|
||||
|
||||
virtual void onUpdate(function<void(string id, Meter*)> cb) = 0;
|
||||
virtual void onUpdate(function<void(Telegram*t,Meter*)> cb) = 0;
|
||||
virtual int numUpdates() = 0;
|
||||
|
||||
virtual void printMeter(string id,
|
||||
virtual void printMeter(Telegram *t,
|
||||
string *human_readable,
|
||||
string *fields, char separator,
|
||||
string *json,
|
||||
|
|
|
@ -24,18 +24,18 @@
|
|||
|
||||
struct MeterCommonImplementation : public virtual Meter
|
||||
{
|
||||
string id();
|
||||
vector<string> ids();
|
||||
string name();
|
||||
MeterType type();
|
||||
int manufacturer();
|
||||
int media();
|
||||
vector<int> media();
|
||||
WMBus *bus();
|
||||
LinkMode requiredLinkMode();
|
||||
|
||||
string datetimeOfUpdateHumanReadable();
|
||||
string datetimeOfUpdateRobot();
|
||||
|
||||
void onUpdate(function<void(string id, Meter*)> cb);
|
||||
void onUpdate(function<void(Telegram*,Meter*)> cb);
|
||||
int numUpdates();
|
||||
|
||||
bool isTelegramForMe(Telegram *t);
|
||||
|
@ -47,7 +47,7 @@ struct MeterCommonImplementation : public virtual Meter
|
|||
uint16_t getRecordAsUInt16(std::string record);
|
||||
|
||||
MeterCommonImplementation(WMBus *bus, string& name, string& id, string& key,
|
||||
MeterType type, int manufacturer, int media,
|
||||
MeterType type, int manufacturer,
|
||||
LinkMode required_link_mode);
|
||||
|
||||
~MeterCommonImplementation() = default;
|
||||
|
@ -55,18 +55,18 @@ struct MeterCommonImplementation : public virtual Meter
|
|||
protected:
|
||||
|
||||
void triggerUpdate(Telegram *t);
|
||||
void updateMedia(int media);
|
||||
void addMedia(int media);
|
||||
|
||||
private:
|
||||
|
||||
MeterType type_ {};
|
||||
int manufacturer_ {};
|
||||
int media_ {};
|
||||
vector<int> media_ {};
|
||||
string name_;
|
||||
string id_;
|
||||
vector<string> ids_;
|
||||
vector<uchar> key_;
|
||||
WMBus *bus_ {};
|
||||
vector<function<void(string,Meter*)>> on_update_;
|
||||
vector<function<void(Telegram*,Meter*)>> on_update_;
|
||||
int num_updates_ {};
|
||||
bool use_aes_ {};
|
||||
time_t datetime_of_update_ {};
|
||||
|
|
|
@ -36,13 +36,13 @@ Printer::Printer(bool json, bool fields, char separator,
|
|||
overwrite_ = overwrite;
|
||||
}
|
||||
|
||||
void Printer::print(string id, Meter *meter)
|
||||
void Printer::print(Telegram *t, Meter *meter)
|
||||
{
|
||||
string human_readable, fields, json;
|
||||
vector<string> envs;
|
||||
bool printed = false;
|
||||
|
||||
meter->printMeter(id, &human_readable, &fields, separator_, &json, &envs);
|
||||
meter->printMeter(t, &human_readable, &fields, separator_, &json, &envs);
|
||||
|
||||
if (shell_cmdlines_.size() > 0) {
|
||||
printShells(meter, envs);
|
||||
|
|
|
@ -29,7 +29,7 @@ struct Printer {
|
|||
vector<string> shell_cmdlines,
|
||||
bool overwrite);
|
||||
|
||||
void print(string id, Meter *meter);
|
||||
void print(Telegram *t, Meter *meter);
|
||||
|
||||
private:
|
||||
|
||||
|
|
34
src/util.cc
34
src/util.cc
|
@ -303,12 +303,17 @@ void debug(const char* fmt, ...) {
|
|||
}
|
||||
}
|
||||
|
||||
bool isValidId(string& id)
|
||||
bool isValidId(string& ids)
|
||||
{
|
||||
if (id == "*") return true;
|
||||
if (id.length() != 8) return false;
|
||||
for (int i=0; i<8; ++i) {
|
||||
if (id[i]<'0' || id[i]>'9') return false;
|
||||
vector<string> v = splitIds(ids);
|
||||
|
||||
for (auto id : v)
|
||||
{
|
||||
if (id == "*") return true;
|
||||
if (id.length() != 8) return false;
|
||||
for (int i=0; i<8; ++i) {
|
||||
if (id[i]<'0' || id[i]>'9') return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -332,6 +337,23 @@ bool isFrequency(std::string& fq)
|
|||
return true;
|
||||
}
|
||||
|
||||
vector<string> splitIds(string& ids)
|
||||
{
|
||||
vector<string> r;
|
||||
bool eof, err;
|
||||
vector<uchar> v (ids.begin(), ids.end());
|
||||
auto i = v.begin();
|
||||
|
||||
for (;;) {
|
||||
auto id = eatTo(v, i, ',', 16, &eof, &err);
|
||||
if (err) break;
|
||||
trimWhitespace(&id);
|
||||
r.push_back(id);
|
||||
if (eof) break;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
void incrementIV(uchar *iv, size_t len) {
|
||||
uchar *p = iv+len-1;
|
||||
while (p >= iv) {
|
||||
|
@ -444,7 +466,7 @@ string eatTo(vector<uchar> &v, vector<uchar>::iterator &i, int c, size_t max, bo
|
|||
i++;
|
||||
max--;
|
||||
}
|
||||
if (c != -1 && *i != c)
|
||||
if (c != -1 && i != v.end() && *i != c)
|
||||
{
|
||||
*err = true;
|
||||
}
|
||||
|
|
|
@ -69,6 +69,8 @@ bool isValidId(std::string& id);
|
|||
bool isValidKey(std::string& key);
|
||||
bool isFrequency(std::string& fq);
|
||||
|
||||
std::vector<std::string> splitIds(std::string& id);
|
||||
|
||||
void incrementIV(uchar *iv, size_t len);
|
||||
|
||||
bool checkCharacterDeviceExists(const char *tty, bool fail_if_not);
|
||||
|
|
3
test.sh
3
test.sh
|
@ -14,5 +14,6 @@ tests/test_shell.sh $PROG
|
|||
tests/test_meterfiles.sh $PROG
|
||||
tests/test_config1.sh $PROG
|
||||
tests/test_logfile.sh $PROG
|
||||
|
||||
tests/test_listen_to_all.sh $PROG
|
||||
tests/test_multiple_ids.sh $PROG
|
||||
tests/test_oneshot.sh $PROG
|
||||
|
|
|
@ -25,4 +25,5 @@ then
|
|||
fi
|
||||
else
|
||||
echo Failure.
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
@ -17,5 +17,6 @@ then
|
|||
echo Config1 OK
|
||||
fi
|
||||
else
|
||||
Failure.
|
||||
echo Failure.
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
#!/bin/bash
|
||||
|
||||
PROG="$1"
|
||||
|
||||
mkdir -p testoutput
|
||||
|
||||
TEST=testoutput
|
||||
SIM=simulations/simulation_multiple_qcalorics.txt
|
||||
|
||||
cat $SIM | grep '^{' > $TEST/test_expected.txt
|
||||
$PROG --format=json $SIM \
|
||||
Element qcaloric '*' '' \
|
||||
> $TEST/test_output.txt
|
||||
|
||||
if [ "$?" == "0" ]
|
||||
then
|
||||
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
|
||||
diff $TEST/test_expected.txt $TEST/test_responses.txt
|
||||
if [ "$?" == "0" ]
|
||||
then
|
||||
echo Wildcard \'\*\' id OK
|
||||
fi
|
||||
else
|
||||
echo Failure.
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat $SIM | grep '^{' | grep -v 78563414 > $TEST/test_expected.txt
|
||||
|
||||
$PROG --format=json $SIM \
|
||||
Element qcaloric '78563412,78563413' '' \
|
||||
> $TEST/test_output.txt
|
||||
|
||||
if [ "$?" == "0" ]
|
||||
then
|
||||
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
|
||||
diff $TEST/test_expected.txt $TEST/test_responses.txt
|
||||
if [ "$?" == "0" ]
|
||||
then
|
||||
echo Multiple ids2 OK
|
||||
fi
|
||||
else
|
||||
echo Failure.
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat $SIM | grep '^{' > $TEST/test_expected.txt
|
||||
|
||||
$PROG --format=json $SIM \
|
||||
Element qcaloric '78563412,78563413,78563414' '' \
|
||||
> $TEST/test_output.txt
|
||||
|
||||
if [ "$?" == "0" ]
|
||||
then
|
||||
cat $TEST/test_output.txt | sed 's/"timestamp":"....-..-..T..:..:..Z"/"timestamp":"1111-11-11T11:11:11Z"/' > $TEST/test_responses.txt
|
||||
diff $TEST/test_expected.txt $TEST/test_responses.txt
|
||||
if [ "$?" == "0" ]
|
||||
then
|
||||
echo Multiple ids3 OK
|
||||
fi
|
||||
else
|
||||
echo Failure.
|
||||
exit 1
|
||||
fi
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash
|
||||
|
||||
PROG="$1"
|
||||
|
||||
mkdir -p testoutput
|
||||
|
||||
TEST=testoutput
|
||||
SIM=simulations/simulation_c1.txt
|
||||
|
||||
cat $SIM | grep '^{' > $TEST/test_expected.txt
|
||||
|
||||
$PROG --oneshot --verbose $SIM MyHeater multical302 '*' '' MyTapWater multical21 76348799 '' > $TEST/test_output.txt
|
||||
|
||||
RES=$(cat $TEST/test_output.txt | grep -o "all meters have received at least one update, stopping.")
|
||||
|
||||
if [ "$RES" = "all meters have received at least one update, stopping." ]
|
||||
then
|
||||
echo Oneshot OK
|
||||
else
|
||||
echo Fail oneshot check!
|
||||
exit 1
|
||||
fi
|
|
@ -17,5 +17,6 @@ then
|
|||
echo SHELL OK
|
||||
fi
|
||||
else
|
||||
Failure.
|
||||
echo Failure.
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
@ -21,5 +21,6 @@ then
|
|||
echo T1 OK
|
||||
fi
|
||||
else
|
||||
Failure.
|
||||
echo Failure.
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
@ -70,7 +70,7 @@ mqtt_publish) sent to a REST API (eg curl) or store it in a database
|
|||
.TP
|
||||
\fBmeter_type\fR multical21/flowiq3100/supercom587/iperl/multical302/omnipower/qcaloric
|
||||
.TP
|
||||
\fBmeter_id\fR an 8 digit number, usually printed on the meter
|
||||
\fBmeter_id\fR one or more 8 digit numbers separated with commas, or a single '*' wildcard.
|
||||
.TP
|
||||
\fBmeter_key\fR a unique key for the meter, if meter telegrams are not encrypted, you must supply an empty key: ""
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue