Add mbus support.

pull/522/head
Fredrik Öhrström 2022-04-25 19:38:36 +02:00
rodzic 1146fae2c2
commit 3a6bb0a03d
7 zmienionych plików z 256 dodań i 252 usunięć

Wyświetl plik

@ -2,7 +2,7 @@
# wmbusmeters
The program receives and decodes C1,T1 or S1 telegrams
(using the wireless mbus protocol) to acquire
(using the wireless mbus protocol or the wired mbus protocol) to acquire
utility meter readings. The readings can then be published using
MQTT, curled to a REST api, inserted into a database or stored in a log file.
@ -45,6 +45,8 @@ from your computer. Then do:
`./configure; make; sudo make install` will install wmbusmeters as a daemon.
# Usage
Check the contents of your `/etc/wmbusmeters.conf` file, assuming it
has `device=auto:t1` and you are using a im871a,amb8465,rc1180,cul or rtlsdr device,
then you can now start the daemon with `sudo systemctl start wmbusmeters`
@ -102,6 +104,25 @@ device=/dev/ttyUSB0:im871a[00102759]:c1,t1
device=/dev/ttyUSB1:rc1800:t1
```
# Bus aliases and polling
To poll an C2/T2/S2 wireless meter or an wired m-bus meter you need to give the (w)mbus device a bus-alias, for example
here we pick the bus alias MAIN:
```
MAIN=/dev/ttyUSB0:mbus
```
and here we pick the bus alias WIRELESS2 for an im871a dongle:
```
WIRELESS2=/dev/ttyUSB1:im871a:c2
```
The bus alias is then used in the meter driver specification to specify which
bus the mbus poll request should be sent to.
```
wmbusmeters MAIN=/dev/ttyUSB0:mbus MyTempMeter piigth:MAIN:2400:mbus 12001932 NOKEY
```
# Example wmbusmeter.conf file
```ini
loglevel=normal
@ -262,6 +283,7 @@ As {options} you can use:
--normal for normal logging
--oneshot wait for an update from each meter, then quit
--ppjson pretty print the json
--pollevery=<time> time between polling of meters, default is 10m
--resetafter=<time> reset the wmbus dongle regularly, default is 23h
--selectfields=id,timestamp,total_m3 select only these fields to be printed (--listfields=<meter> to list available fields)
--separator=<c> change field separator to c
@ -297,6 +319,7 @@ rtlsdr dongle like this `rtlwmbus[1234]`.
`/dev/ttyUSB0:38400`, to have wmbusmeters set the baud rate to 38400 and listen for raw wmbus telegrams.
These telegrams are expected to have the data link layer crc bytes removed already!
`MAIN=/dev/ttyUSB0`
`rtlwmbus`, to spawn the background process: `rtl_sdr -f 868.625M -s 1600000 - 2>/dev/null | rtl_wmbus -s`
for each attached rtlsdr dongle. This will listen to S1,T1 and C1 meters in parallel.
@ -363,6 +386,9 @@ Radiocraft (RC1180)
rtl_wmbus (rtlwmbus)
rtl_433 (rtl433)
Supported mbus dongles:
Any bus controller dongle/board behaving like a plain serial port.
Supported water meters:
Aventies (aventieswm)
Apator at-wmbus-08 (apator08) (non-standard protocol)
@ -416,6 +442,7 @@ Bmeters RFM-AMB Thermometer/Hygrometer (rfmamb)
Elvaco CMa12w Thermometer (cma12w)
Lansen Thermometer/Hygrometer (lansenth)
Weptech Munia Thermometer/Hygrometer (munia)
PiiGAB Thermometer/Hygrometer (piigth) wired
Supported smoke detectors:
Lansen Smoke Detector (lansensm)

Wyświetl plik

@ -526,6 +526,14 @@ static shared_ptr<Configuration> parseNormalCommandLine(Configuration *c, int ar
i++;
continue;
}
if (!strncmp(argv[i], "--pollevery=", 12) && strlen(argv[i]) > 12) {
c->pollevery = parseTime(argv[i]+12);
if (c->pollevery <= 0) {
error("Not a valid time to regularly poll meters. \"%s\"\n", argv[i]+12);
}
i++;
continue;
}
if (!strncmp(argv[i], "--resetafter=", 13) && strlen(argv[i]) > 13) {
c->resetafter = parseTime(argv[i]+13);
if (c->resetafter <= 0) {

Wyświetl plik

@ -96,6 +96,7 @@ struct Configuration
std::string logfile;
bool json {};
bool pretty_print_json {};
int pollevery {}; // Time between polling.
bool fields {};
char separator { ';' };
std::vector<std::string> telegram_shells;

Wyświetl plik

@ -16,250 +16,76 @@
*/
#include"meters_common_implementation.h"
#include"bus.h"
struct MeterPIIGTH : public virtual MeterCommonImplementation
namespace
{
MeterPIIGTH(MeterInfo &mi, DriverInfo &di);
void poll(shared_ptr<BusManager> bus_manager);
double temperature_c_ {};
double average_temperature_1h_c_ {};
double average_temperature_24h_c_ {};
double relative_humidity_rh_ {};
double average_relative_humidity_1h_rh_ {};
double average_relative_humidity_24h_rh_ {};
string fabrication_no_txt_ {};
};
static bool ok = registerDriver([](DriverInfo&di)
{
di.setName("piigth");
di.setMeterType(MeterType::TempHygroMeter);
di.addLinkMode(LinkMode::MBUS);
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new MeterPIIGTH(mi, di)); });
di.addDetection(MANUFACTURER_PII, 0x1b, 0x01);
});
MeterPIIGTH::MeterPIIGTH(MeterInfo &mi, DriverInfo &di) :
MeterCommonImplementation(mi, di)
{
addNumericFieldWithExtractor(
"temperature",
Quantity::Temperature,
NoDifVifKey,
VifScaling::Auto,
MeasurementType::Instantaneous,
VIFRange::ExternalTemperature,
StorageNr(0),
TariffNr(0),
IndexNr(1),
PrintProperty::JSON | PrintProperty::FIELD,
"The current temperature.",
SET_FUNC(temperature_c_, Unit::C),
GET_FUNC(temperature_c_, Unit::C));
addNumericFieldWithExtractor(
"average_temperature_1h",
Quantity::Temperature,
NoDifVifKey,
VifScaling::Auto,
MeasurementType::Instantaneous,
VIFRange::ExternalTemperature,
StorageNr(1),
TariffNr(0),
IndexNr(1),
PrintProperty::JSON | PrintProperty::FIELD,
"The average temperature over the last hour.",
SET_FUNC(average_temperature_1h_c_, Unit::C),
GET_FUNC(average_temperature_1h_c_, Unit::C));
addNumericFieldWithExtractor(
"average_temperature_24h",
Quantity::Temperature,
NoDifVifKey,
VifScaling::Auto,
MeasurementType::Instantaneous,
VIFRange::ExternalTemperature,
StorageNr(2),
TariffNr(0),
IndexNr(1),
PrintProperty::JSON | PrintProperty::FIELD,
"The average temperature over the last 24 hours.",
SET_FUNC(average_temperature_24h_c_, Unit::C),
GET_FUNC(average_temperature_24h_c_, Unit::C));
/*
addNumericFieldWithExtractor(
"relative_humidity",
Quantity::Temperature,
DifVifKey("02FB1A"),
VifScaling::Auto,
MeasurementType::Instantaneous,
VIFRange::RelativeHumidity,
StorageNr(0),
TariffNr(0),
IndexNr(1),
PrintProperty::JSON | PrintProperty::FIELD,
"The current relative humidity.",
SET_FUNC(relative_humidity_rh_, Unit::RH),
GET_FUNC(relative_humidity_rh_, Unit::RH));
*/
addStringFieldWithExtractor(
"fabrication_no",
Quantity::Text,
NoDifVifKey,
MeasurementType::Instantaneous,
VIFRange::FabricationNo,
StorageNr(0),
TariffNr(0),
IndexNr(1),
PrintProperty::JSON,
"Fabrication number.",
SET_STRING_FUNC(fabrication_no_txt_),
GET_STRING_FUNC(fabrication_no_txt_));
}
/*
addPrint("current_relative_humidity", Quantity::RelativeHumidity,
[&](Unit u){ return currentRelativeHumidity(); },
"The current relative humidity.",
true, true);
addPrint("average_temperature_1h", Quantity::Temperature,
[&](Unit u){ return convert(average_temperature_1h_c_, Unit::C, u); },
"The average temperature over the last hour.",
false, true);
addPrint("average_relative_humidity_1h", Quantity::RelativeHumidity,
[&](Unit u){ return average_relative_humidity_1h_rh_; },
"The average relative humidity over the last hour.",
false, true);
addPrint("average_temperature_24h", Quantity::Temperature,
[&](Unit u){ return convert(average_temperature_24h_c_, Unit::C, u); },
"The average temperature over the last 24 hours.",
false, true);
addPrint("average_relative_humidity_24h", Quantity::RelativeHumidity,
[&](Unit u){ return average_relative_humidity_24h_rh_; },
"The average relative humidity over the last 24 hours.",
false, true);
}
*/
void MeterPIIGTH::poll(shared_ptr<BusManager> bus_manager)
{
WMBus *dev = bus_manager->findBus(bus());
if (!dev)
struct Driver : public virtual MeterCommonImplementation
{
fprintf(stderr, "Could not find bus from name %s\n", bus().c_str());
return;
}
Driver(MeterInfo &mi, DriverInfo &di);
};
vector<uchar> buf;
/*
buf[0] = 0x10; // Start
buf[1] = 0x40; // SND_NKE
buf[2] = 0x00; // address 0
uchar cs = 0;
for (int i=1; i<3; ++i) cs += buf[i];
buf[3] = cs; // checksum
buf[4] = 0x16; // Stop
dev->serial()->send(buf);
sleep(2);
*/
// 10000284
/*
10000284 2941 01 1B
*/
buf.resize(17);
buf[0] = 0x68;
buf[1] = 0x0b;
buf[2] = 0x0b;
buf[3] = 0x68;
buf[4] = 0x73; // SND_UD
buf[5] = 0xfd; // address 253
buf[6] = 0x52; // ci 52
buf[7] = 0x84; // id 78
buf[8] = 0x02; // id 56
buf[9] = 0x00; // id 34
buf[10] = 0x10; // id 12
buf[11] = 0x29; // mfct 29
buf[12] = 0x41; // mfct 41 2941 == PII
buf[13] = 0x01; // version/generation
buf[14] = 0x1b; // type/media/device
uchar cs = 0;
for (int i=4; i<15; ++i) cs += buf[i];
buf[15] = cs; // checksum
buf[16] = 0x16; // Stop
dev->serial()->send(buf);
sleep(1);
// 10 5B FD 58 16
buf.resize(5);
buf[0] = 0x10; // Start
buf[1] = 0x5b; // REQ_UD2
buf[2] = 0xfd; // 00 or address 253 previously setup
cs = 0;
for (int i=1; i<3; ++i) cs += buf[i];
buf[3] = cs; // checksum
buf[4] = 0x16; // Stop
dev->serial()->send(buf);
}
/*
void MeterPIIGTH::processContent(Telegram *t)
{
int offset;
string key;
if (findKey(MeasurementType::Instantaneous, VIFRange::ExternalTemperature, 1, 0, &key, &t->dv_entries))
static bool ok = registerDriver([](DriverInfo&di)
{
extractDVdouble(&t->dv_entries, key, &offset, &average_temperature_1h_c_);
t->addMoreExplanation(offset, " average temperature 1h (%f C))", average_temperature_1h_c_);
}
di.setName("piigth");
di.setMeterType(MeterType::TempHygroMeter);
di.addLinkMode(LinkMode::MBUS);
di.setConstructor([](MeterInfo& mi, DriverInfo& di){ return shared_ptr<Meter>(new Driver(mi, di)); });
di.addDetection(MANUFACTURER_PII, 0x1b, 0x01);
});
if (findKey(MeasurementType::Instantaneous, VIFRange::ExternalTemperature, 2, 0, &key, &t->dv_entries))
Driver::Driver(MeterInfo &mi, DriverInfo &di) : MeterCommonImplementation(mi, di)
{
extractDVdouble(&t->dv_entries, key, &offset, &average_temperature_24h_c_);
t->addMoreExplanation(offset, " average temperature 24h (%f C))", average_temperature_24h_c_);
}
addNumericFieldWithExtractor(
"temperature",
"The current temperature.",
PrintProperty::JSON | PrintProperty::FIELD,
Quantity::Temperature,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::ExternalTemperature)
);
// Temporarily silly solution until the dvparser is upgraded with support for extension
addNumericFieldWithExtractor(
"average_temperature_1h",
"The average temperature over the last hour.",
PrintProperty::JSON | PrintProperty::FIELD,
Quantity::Temperature,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::ExternalTemperature)
);
key = "02FB1A"; // 1A = 0001 1010 = First extension vif code Relative Humidity 10^-1
if (hasKey(&t->dv_entries, key))
{
double tmp;
extractDVdouble(&t->dv_entries, key, &offset, &tmp, false);
current_relative_humidity_rh_ = tmp / (double)10.0;
t->addMoreExplanation(offset, " current relative humidity (%f RH)", current_relative_humidity_rh_);
}
key = "42FB1A"; // 1A = 0001 1010 = First extension vif code Relative Humidity 10^-1
if (hasKey(&t->dv_entries, key))
{
double tmp;
extractDVdouble(&t->dv_entries, key, &offset, &tmp, false);
average_relative_humidity_1h_rh_ = tmp / (double)10.0;
t->addMoreExplanation(offset, " average relative humidity 1h (%f RH)", average_relative_humidity_1h_rh_);
}
key = "8201FB1A"; // 1A = 0001 1010 = First extension vif code Relative Humidity 10^-1
if (hasKey(&t->dv_entries, key))
{
double tmp;
extractDVdouble(&t->dv_entries, key, &offset, &tmp, false);
average_relative_humidity_24h_rh_ = tmp / (double)10.0;
t->addMoreExplanation(offset, " average relative humidity 24h (%f RH)", average_relative_humidity_24h_rh_);
addNumericFieldWithExtractor(
"average_temperature_24h",
"The average temperature over the last 24 hours.",
PrintProperty::JSON | PrintProperty::FIELD,
Quantity::Temperature,
VifScaling::Auto,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::ExternalTemperature)
.set(StorageNr(2))
);
addNumericFieldWithExtractor(
"relative_humidity",
"The current relative humidity.",
PrintProperty::JSON | PrintProperty::FIELD,
Quantity::RelativeHumidity,
VifScaling::Auto,
FieldMatcher::build().
set(DifVifKey("02FB1A"))
);
addStringFieldWithExtractor(
"fabrication_no",
"Fabrication number.",
PrintProperty::JSON,
FieldMatcher::build()
.set(MeasurementType::Instantaneous)
.set(VIFRange::FabricationNo)
);
}
}
*/

Wyświetl plik

@ -15,6 +15,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include"bus.h"
#include"config.h"
#include"meters.h"
#include"meter_detection.h"
@ -1009,8 +1010,118 @@ void MeterCommonImplementation::addStringFieldWithExtractorAndLookup(string vnam
));
}
void MeterCommonImplementation::poll(shared_ptr<BusManager> bus)
void MeterCommonImplementation::poll(shared_ptr<BusManager> bus_manager)
{
if (link_modes_.has(LinkMode::MBUS))
{
WMBus *bus_device = bus_manager->findBus(bus());
if (!bus_device)
{
debug("(meter) Could not find bus from name \"%s\"\n", bus().c_str());
return;
}
/*
Reset mbus...
vector<uchar> buf;
buf[0] = 0x10; // Start
buf[1] = 0x40; // SND_NKE
buf[2] = 0x00; // address 0
uchar cs = 0;
for (int i=1; i<3; ++i) cs += buf[i];
buf[3] = cs; // checksum
buf[4] = 0x16; // Stop
dev->serial()->send(buf);
sleep(2);
*/
string id = ids().back();
if (id.length() != 2 && id.length() != 3 && id.length() != 8)
{
debug("(meter) not polling from bad id \"%s\" with wrong length\n", id.c_str());
return;
}
if (id.length() == 2 || id.length() == 3)
{
vector<uchar> idhex;
int idnum = atoi(id.c_str());
if (idnum < 0 || idnum > 250 || idhex.size() != 1)
{
debug("(meter) not polling from bad id \"%s\"\n", id.c_str());
return;
}
vector<uchar> buf;
buf.resize(5);
buf[0] = 0x10; // Start
buf[1] = 0x5b; // REQ_UD2
buf[2] = idhex[0];
uchar cs = 0;
for (int i=1; i<3; ++i) cs += buf[i];
buf[3] = cs; // checksum
buf[4] = 0x16; // Stop
verbose("(meter) req ud2 bus %s address %s\n", bus_device->busAlias().c_str(),id.c_str());
bus_device->serial()->send(buf);
}
if (id.length() == 8)
{
// A full secondary address 12345678 was specified.
vector<uchar> idhex;
bool ok = hex2bin(id, &idhex);
if (!ok || idhex.size() != 4)
{
debug("(meter) not polling from bad id \"%s\"\n", id.c_str());
return;
}
vector<uchar> buf;
buf.resize(17);
buf[0] = 0x68;
buf[1] = 0x0b;
buf[2] = 0x0b;
buf[3] = 0x68;
buf[4] = 0x73; // SND_UD
buf[5] = 0xfd; // address 253
buf[6] = 0x52; // ci 52
buf[7] = idhex[3]; // id 78
buf[8] = idhex[2]; // id 56
buf[9] = idhex[1]; // id 34
buf[10] = idhex[0]; // id 12
buf[11] = 0x29; // mfct 29
buf[12] = 0x41; // mfct 41 2941 == PII
buf[13] = 0x01; // version/generation
buf[14] = 0x1b; // type/media/device
uchar cs = 0;
for (int i=4; i<15; ++i) cs += buf[i];
buf[15] = cs; // checksum
buf[16] = 0x16; // Stop
debug("(meter) secondary addressing bus %s to address %s\n", bus_device->busAlias().c_str(), id.c_str());
bus_device->serial()->send(buf);
usleep(1000*500);
buf.resize(5);
buf[0] = 0x10; // Start
buf[1] = 0x5b; // REQ_UD2
buf[2] = 0xfd; // 00 or address 253 previously setup
cs = 0;
for (int i=1; i<3; ++i) cs += buf[i];
buf[3] = cs; // checksum
buf[4] = 0x16; // Stop
verbose("(meter) req ud2 bus %s address %s\n", bus_device->busAlias().c_str(),id.c_str());
bus_device->serial()->send(buf);
}
}
}
vector<string>& MeterCommonImplementation::ids()
@ -1991,7 +2102,7 @@ LIST_OF_METERS
return newm;
}
bool is_driver_extras(string t, MeterDriver *out_driver, DriverName *out_driver_name, string *out_extras)
bool is_driver_and_extras(string t, MeterDriver *out_driver, DriverName *out_driver_name, string *out_extras)
{
// piigth(jump=foo)
// multical21
@ -2084,7 +2195,7 @@ bool MeterInfo::parse(string n, string d, string i, string k)
for (auto& p : parts)
{
if (!driverextras_checked && is_driver_extras(p, &driver, &driver_name, &extras))
if (!driverextras_checked && is_driver_and_extras(p, &driver, &driver_name, &extras))
{
driverextras_checked = true;
}

Wyświetl plik

@ -801,13 +801,21 @@ void testm(string arg, bool xok,
}
if (ok == false) return;
if (toString(mi.driver) != xdriver,
mi.extras != xextras ||
mi.bus != xbus ||
to_string(mi.bps) != xbps ||
mi.link_modes.hr() != xlm)
bool driver_ok = toString(mi.driver) == xdriver || mi.driverName().str() == xdriver;
bool extras_ok = mi.extras == xextras;
bool bus_ok = mi.bus == xbus;
bool bps_ok = to_string(mi.bps) == xbps;
bool link_modes_ok = mi.link_modes.hr() == xlm;
if (!driver_ok || !extras_ok || !bus_ok || !bps_ok || !link_modes_ok)
{
printf("ERROR in meter parsing parts \"%s\" - got (driver: %s, extras: %s, bus: %s, bbps: %s, linkmodes: %s)\n", arg.c_str(), toString(mi.driver).c_str(), mi.extras.c_str(), mi.bus.c_str(), to_string(mi.bps).c_str(), mi.link_modes.hr().c_str());
printf("ERROR in meterm parsing parts \"%s\" - got (driver: \"%s/%s\" (%d), extras: \"%s\" (%d), bus: \"%s\" (%d), bbps: \"%s\" (%d), linkmodes: \"%s\" (%d))\n",
arg.c_str(),
toString(mi.driver).c_str(), mi.driverName().str().c_str(), driver_ok,
mi.extras.c_str(), extras_ok,
mi.bus.c_str(), bus_ok,
to_string(mi.bps).c_str(), bps_ok,
mi.link_modes.hr().c_str(), link_modes_ok);
}
}
@ -823,13 +831,16 @@ void testc(string file, string file_content,
parseMeterConfig(&c, meter_conf, file);
mi = c.meters.back();
if (toString(mi.driver) != xdriver,
if ((toString(mi.driver) != xdriver && mi.driverName().str() != xdriver) ||
mi.extras != xextras ||
mi.bus != xbus ||
to_string(mi.bps) != xbps ||
mi.link_modes.hr() != xlm)
{
printf("ERROR in meter parsing parts \"%s\" - got (driver: %s, extras: %s, bus: %s, bbps: %s, linkmodes: %s)\n", file.c_str(), toString(mi.driver).c_str(), mi.extras.c_str(), mi.bus.c_str(), to_string(mi.bps).c_str(), mi.link_modes.hr().c_str());
printf("ERROR in meterc parsing parts \"%s\" - got (driver: %s/%s, extras: %s, bus: %s, bbps: %s, linkmodes: %s)\n",
file.c_str(),
toString(mi.driver).c_str(), mi.driverName().str().c_str(),
mi.extras.c_str(), mi.bus.c_str(), to_string(mi.bps).c_str(), mi.link_modes.hr().c_str());
}
}
@ -837,18 +848,38 @@ void test_meters()
{
string config_content;
/*
testm("piigth:BUS1:2400", true,
"piigth", // driver
"", // extras
"BUS1", // bus
"2400", // bps
"none"); // linkmodes
testm("c5isf:MAINO:9600:mbus", true,
"c5isf", // driver
"", // extras
"MAINO", // bus
"9600", // bps
"mbus"); // linkmodes
*/
testm("c5isf:DONGLE:t1", true,
"c5isf", // driver
"", // extras
"DONGLE", // bus
"0", // bps
"t1"); // linkmodes
testm("c5isf:t1,c1,mbus", true,
"c5isf", // driver
"", // extras
"", // bus
"0", // bps
"c1,t1,mbus"); // linkmodes
/*
config_content =
"name=test\n"
"driver=piigth:BUS1:2400\n"
"driver=piigth:BUS1:2400:mbus\n"
"id=01234567\n";
testc("meter/piigth:BUS1:2400", config_content,

Wyświetl plik

@ -4760,7 +4760,7 @@ FrameStatus checkMBusFrame(vector<uchar> &data,
return ErrorInFrame;
}
*payload_len_out = *frame_length-2; // Drop checksum byte and stop byte.
*payload_len_out = *frame_length; // Drop checksum byte and stop byte.
*payload_offset = 0; // Drop 0x68 len len 0x68.
if (!only_test)
{