Merge pull request #1371 from wmbusmeters/AddiU891A

Add iu891a wmbus dongle.
pull/1366/head
Fredrik Öhrström 2024-09-21 19:54:12 +02:00 zatwierdzone przez GitHub
commit c5fc8e4336
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
10 zmienionych plików z 1051 dodań i 34 usunięć

Wyświetl plik

@ -177,6 +177,7 @@ PROG_OBJS:=\
$(BUILD)/wmbus.o \
$(BUILD)/wmbus_amb8465.o \
$(BUILD)/wmbus_im871a.o \
$(BUILD)/wmbus_iu891a.o \
$(BUILD)/wmbus_cul.o \
$(BUILD)/wmbus_rtlwmbus.o \
$(BUILD)/wmbus_rtl433.o \

Wyświetl plik

@ -90,7 +90,7 @@ Read the wiki for more info on how to use the snap: https://wmbusmeters.github.i
Building and installing from source is easy and recommended since the
development progresses quickly. First remove the wmbus dongle
(im871a,amb8465(metis),amb3665,cul,rc1180) or the generic rtlsdr dongle (RTL2832U)
(im871a,iu891a,amb8465(metis),amb3665,cul,rc1180) or the generic rtlsdr dongle (RTL2832U)
from your computer. Then do:
`./configure; make; sudo make install` will install wmbusmeters as a daemon.
@ -98,7 +98,7 @@ from your computer. Then do:
# Usage
Check the contents of your `/etc/wmbusmeters.conf` file, assuming it
has `device=auto:t1` and you are using a im871a,amb8465(metis),amb3665,rc1180,cul or rtlsdr device,
has `device=auto:t1` and you are using a im871a,iu891a,amb8465(metis),amb3665,rc1180,cul or rtlsdr device,
then you can now start the daemon with `sudo systemctl start wmbusmeters`
or you can try it from the command line `wmbusmeters auto:t1`
@ -108,11 +108,11 @@ several dongle types, the scan can take some time!
Use `auto` for testing and to find your dongle. For production it is very much
recommended that you change `auto:t1` to the device name with the full device path
(eg `/dev/ttyUSB0:im871a:c1,t1`). This will skip the slow probing for all possible
(eg `/dev/ttyAMA0:iu891:c1,t1`). This will skip the slow probing for all possible
wmbus dongles when wmbusmeters startup.
If the serial device (ttyUSB0) might change you can also use `device=im871a:c1,t1`
which will probe all serial devices but only scans for im871a which also speeds it up.
If the serial device (ttyUSB0) might change you can also use `device=iu891:c1,t1`
which will probe all serial devices but only scans for im891a which also speeds it up.
Note that the rtl-sdr devices are not found under the tty devices (e.g. `/dev/tty...`).
Instead the rtl-sdr devices are accessed through character device special files named `/dev/swradio0` to `/dev/swradio255`[^kernel_docs_sdr]. Wmbusmeters uses librtsldr to probe these devices.
@ -139,26 +139,26 @@ When using useconfig, the files/dir should be:
`/home/me/.config/wmbusmeters/wmbusmeters.d`
Check the config file /etc/wmbusmeters.conf and edit the device. For example:
`/dev/ttyUSB1:amb8465:c1,t1` or `im871a:c1,t1` or `im871a[457200101056]:t1`.
`/dev/ttyUSB1:amb8465:c1,t1` or `iu891:c1,t1` or `iu891a[457200101056]:t1`.
Adding a device like auto or im871a will trigger an automatic probe of all serial ttys
to auto find or to find on which tty the im871a resides.
Adding a device like auto or iu891a will trigger an automatic probe of all serial ttys
to auto find or to find on which tty the iu891a resides.
If you specify a full device path like `/dev/ttyUSB0:im871a:c1` or `rtlwmbus` or `rtl433`
If you specify a full device path like `/dev/ttyUSB0:iu891a:c1` or `rtlwmbus` or `rtl433`
then it will not probe the serial devices. If you must be really sure that it will not probe something
you can add `donotprobe=/dev/ttyUSB0` or `donotprobe=all`.
You can specify combinations like: `device=rc1180:t1` `device=auto:c1`
to set the rc1180 dongle to t1 but any other auto-detected dongle to c1.
Some dongles have identifiers (im871a,amb8465(metis),amb3665 and rtlsdrs) (for example: rtlsdr can be set with `rtl_eeprom -s myname`)
Some dongles have identifiers (im871a,iu891a,amb8465(metis),amb3665 and rtlsdrs) (for example: rtlsdr can be set with `rtl_eeprom -s myname`)
You might have two rtlsdr dongles, one attached to an antenna tuned to 433MHz and the other
attached to an antenna tuned for 868.95MHz, then a more complicated setup could look like this:
```
device=rtlwmbus[555555]:433M
device=rtlwmbus[112233]
device=/dev/ttyUSB0:im871a[00102759]:c1,t1
device=/dev/ttyUSB0:iu891a[00102759]:c1,t1
device=/dev/ttyUSB1:rc1180:t1
```
@ -169,9 +169,9 @@ here we pick the bus alias MAIN for the mbus using 2400 bps for all meters on th
```
MAIN=/dev/ttyUSB0:mbus:2400
```
and here we pick the bus alias RADIOMAIN for an im871a dongle:
and here we pick the bus alias RADIOMAIN for an iu891a dongle:
```
RADIOMAIN=/dev/ttyUSB1:im871a:c2
RADIOMAIN=/dev/ttyUSB1:iu891a:c2
```
The bus alias is then used in the meter driver specification to specify which
@ -192,7 +192,7 @@ wmbusmeters --pollinterval=60s MAIN=/dev/ttyUSB0:mbus:2400 MyTempMeter piigth:MA
loglevel=normal
# You can use auto:t1 to find the device you have connected to your system.
# But do not use auto here since it will cause unnecessary and slow probing of the serial ports.
device=/dev/ttyUSB0:im871a:c1,t1
device=/dev/ttyUSB0:iu891a:c1,t1
# And mbus
device=MAIN=/dev/ttyUSB1:mbus:2400
# But do not probe this serial tty.
@ -234,7 +234,7 @@ pollinterval=60s
You can use `driver=auto` to have wmbusmeters automatically detect
and use the best driver for your meter, but you should >not< use auto in production.
You can find out which driver is recommended by running `wmbusmeters im871a:t1`.
You can find out which driver is recommended by running `wmbusmeters iu891a:t1`.
This will print information like:
```
Received telegram from: 71727374
@ -518,20 +518,20 @@ As {options} you can use:
As device you can use:
`auto:c1`, to have wmbusmeters probe for devices: im871a, amb8465(metis), amb3665, cul, rc1180 or rtlsdr (spawns rtlwmbus).
`auto:c1`, to have wmbusmeters probe for devices: im871a, iu891a, amb8465(metis), amb3665, cul, rc1180 or rtlsdr (spawns rtlwmbus).
`im871a:c1` to start all connected *im871a* devices in *c1* mode, ignore all other devices.
`iu891a:c1` to start all connected *iu891a* devices in *c1* mode, ignore all other devices.
`/dev/ttyUSB1:amb8465:c1` to start only this device on this tty. Do not probe for other devices.
If you have two im871a you can supply both of them with their unique id:s and set different listening modes:
`im871a[12345678]:c1` `im871a[11223344]:t1`
If you have two iu891a you can supply both of them with their unique id:s and set different listening modes:
`iu891a[12345678]:c1` `iu891a[11223344]:t1`
You can also specify rtlwmbus and if you set the serial in the rtlsdr
dongle using `rtl_eeprom -s 1234` you can also refer to a specific
rtlsdr dongle like this `rtlwmbus[1234]`.
`/dev/ttyUSB0:amb8465`, if you have an amb8465(metis) dongle assigned to ttyUSB0. Other suffixes are im871a,cul.
`/dev/ttyUSB0:amb8465`, if you have an amb8465(metis) dongle assigned to ttyUSB0. Other suffixes are iu891a,cul.
(Note that a plain `/dev/ttyUSB0` no longer works, you have to specify the device expected on the device.)
@ -615,6 +615,7 @@ As meter quadruples you specify:
```
Supported wmbus dongles:
IMST 871a (im871a)
IMST 891a (iu891a)
Amber 8465-M/8665-M/8626-M/Metis-II (amb8465) 868MHz
Amber 3665-M (amb3665) 169MHz
CUL family (cul)
@ -724,6 +725,8 @@ also listen to c1 and t1 telegrams at the same time.
If you have the older firmware you can download the upgrader here:
https://wireless-solutions.de/downloadfile/wireless-m-bus-software/
The wmbus dongle iu891a can listen to either s1, c1 or t1 or c1,t1 at the same time.
The amb8465 dongle (new model name is Metis-II) can listen to either
s1, c1 or t1. It can also listen to c1 and t1 at the same time.

Wyświetl plik

@ -8,18 +8,13 @@ driver {
}
use = actuality_duration_s
field {
name = total
quantity = Volume
name = total
quantity = Volume
info = 'The total water consumption.'
match {
measurement_type = Instantaneous
vif_range = Volume
}
about {
de = 'Der Gesamtwasserverbrauch.'
en = 'The total water consumption.'
fr = '''La consommation totale d'eau.'''
sv = 'Den totala vattenförbrukningen.'
}
}
test {
args = 'Gas elster 05105025 NOKEY'

Wyświetl plik

@ -200,6 +200,10 @@ shared_ptr<BusDevice> BusManager::createWmbusObject(Detected *detected, Configur
verbose("(im170a) on %s\n", detected->found_file.c_str());
wmbus = openIM170A(*detected, serial_manager_, serial_override);
break;
case DEVICE_IU891A:
verbose("(iu891a) on %s\n", detected->found_file.c_str());
wmbus = openIU891A(*detected, serial_manager_, serial_override);
break;
case DEVICE_AMB8465:
verbose("(amb8465) on %s\n", detected->found_file.c_str());
wmbus = openAMB8465(*detected, serial_manager_, serial_override);

Wyświetl plik

@ -5547,6 +5547,16 @@ Detected detectBusDeviceOnTTY(string tty,
}
}
// Talk iu891a with it...
// assumes this device is configured for 115200 bps, which seems to be the default.
if (has_auto || probe_for.count(BusDeviceType::DEVICE_IU891A))
{
if (detectIU891A(&detected, handler) == AccessCheck::AccessOK)
{
return detected;
}
}
// Talk iu880b with it...
// assumes this device is configured for 115200 bps, which seems to be the default.
if (has_auto || probe_for.count(BusDeviceType::DEVICE_IU880B))

Wyświetl plik

@ -44,6 +44,7 @@ bool trimCRCsFrameFormatB(std::vector<uchar> &payload);
X(CUL,cul,true,false,detectCUL) \
X(IM871A,im871a,true,false,detectIM871AIM170A) \
X(IM170A,im170a,true,false,detectSKIP) \
X(IU891A,iu891a,true,false,detectIU891A) \
X(RAWTTY,rawtty,true,false,detectRAWTTY) \
X(HEXTTY,hextty,true,false,detectSKIP) \
X(RC1180,rc1180,true,false,detectRC1180) \
@ -704,6 +705,9 @@ shared_ptr<BusDevice> openIM871A(Detected detected,
shared_ptr<BusDevice> openIM170A(Detected detected,
shared_ptr<SerialCommunicationManager> manager,
shared_ptr<SerialDevice> serial_override);
shared_ptr<BusDevice> openIU891A(Detected detected,
shared_ptr<SerialCommunicationManager> manager,
shared_ptr<SerialDevice> serial_override);
shared_ptr<BusDevice> openIU880B(Detected detected,
shared_ptr<SerialCommunicationManager> manager,
shared_ptr<SerialDevice> serial_override);
@ -800,6 +804,7 @@ AccessCheck detectAMB8465AMB3665(Detected *detected, shared_ptr<SerialCommunicat
AccessCheck detectCUL(Detected *detected, shared_ptr<SerialCommunicationManager> handler);
AccessCheck detectD1TC(Detected *detected, shared_ptr<SerialCommunicationManager> manager);
AccessCheck detectIM871AIM170A(Detected *detected, shared_ptr<SerialCommunicationManager> handler);
AccessCheck detectIU891A(Detected *detected, shared_ptr<SerialCommunicationManager> handler);
AccessCheck detectIU880B(Detected *detected, shared_ptr<SerialCommunicationManager> handler);
AccessCheck detectRAWTTY(Detected *detected, shared_ptr<SerialCommunicationManager> handler);
AccessCheck detectMBUS(Detected *detected, shared_ptr<SerialCommunicationManager> handler);

Wyświetl plik

@ -731,9 +731,9 @@ bool WMBusIM871aIM170A::deviceSetLinkModes(LinkModeSet lms)
}
FrameStatus WMBusIM871aIM170A::checkIM871AFrame(vector<uchar> &data,
size_t *frame_length, int *endpoint_out, int *msgid_out,
int *payload_len_out, int *payload_offset,
int *rssi_dbm)
size_t *frame_length, int *endpoint_out, int *msgid_out,
int *payload_len_out, int *payload_offset,
int *rssi_dbm)
{
if (data.size() == 0) return PartialFrame;

Wyświetl plik

@ -1,6 +1,6 @@
// Copyright (C) 2017 Fredrik Öhrström (CC0-1.0)
// Definitions from WMBus_HCI_Spec_V1_6.pdf
// Found here: https://wireless-solutions.de/products/gateways/wirelessadapter.html
// Copyright (C) 2017 Fredrik Öhrström (CC0-1.0) Definitions from
// WMBus_HCI_Spec_V1_6.pdf Found here:
// https://wireless-solutions.de/products/gateways/wirelessadapter.html
#define IM871A_SERIAL_SOF 0xA5

847
src/wmbus_iu891a.cc 100644
Wyświetl plik

@ -0,0 +1,847 @@
/*
Copyright (C) 2017-2024 Fredrik Öhrström (gpl-3.0-or-later)
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"wmbus.h"
#include"wmbus_common_implementation.h"
#include"wmbus_utils.h"
#include"wmbus_iu891a.h"
#include"serial.h"
#include"threads.h"
#include<assert.h>
#include<pthread.h>
#include<errno.h>
#include<memory.h>
#include<semaphore.h>
#include<unistd.h>
using namespace std;
struct DeviceInfo_IU891A
{
uchar module_type; // 109=0x6d=iM891A-XL 110=0x6e=iU891A-XL 163=0xa3=iM881A-XL
uint32_t uid;
string uids;
string product_type;
string product_id;
string str()
{
string s;
/* s += "status=";
string st = tostrprintf( "%02x", status);*/
//s += st;
s += " type=";
string mt = tostrprintf("%02x", module_type);
if (module_type == 0x6d) s+="im891a ";
else if (module_type == 0x6e) s+="iu891a ";
else if (module_type == 0xa3) s+="im881 ";
else s+="unknown_type("+mt+") ";
string ss;
strprintf(&ss, "uid=%08x", uid);
return s+ss;
}
bool decode(vector<uchar> &bytes)
{
if (bytes.size() < 8) return false;
int i = 0;
module_type = bytes[i++];
uid = bytes[i+3]<<24|bytes[i+2]<<16|bytes[i+1]<<8|bytes[i]; i+=4;
strprintf(&uids, "%08x", uid);
return true;
}
};
struct WMBusAddressInfo_IU891A
{
uint16_t mfct {};
uint32_t id {};
uint8_t version {};
uint8_t type {};
string dongleId()
{
string s;
strprintf(&s, "%08x", id);
return s;
}
bool decode(vector<uchar> &bytes)
{
if (bytes.size() < 8) return false;
int i = 0;
mfct = bytes[i] << 8 | bytes[i+1];
i += 2;
id = bytes[i]<<24|bytes[i+1]<<16|bytes[i+1]<<8|bytes[i+3];
i+=4;
version = bytes[i++];
type = bytes[i++];
return true;
}
};
struct Config_IU891A
{
LinkModeSet link_modes;
uint16_t option_bits {};
uint16_t ui_option_bits {};
uint16_t led_flash_timing {};
uint32_t recalibrate_in_ms {};
string str()
{
string s;
if (option_bits & 0x01) s += "RCV_FILTER ";
else s += "RCV_ALL ";
if (option_bits & 0x02) s += "RCV_IND ";
if (option_bits & 0x04) s += "SND_IND ";
if (option_bits & 0x08) s += "RECALIB ";
if (ui_option_bits & 0x01) s += "ASSERT_PIN24_ON_TELEGRAM_ARRIVAL ";
if (ui_option_bits & 0x02) s += "PIN24_POLARITY_REVERSED ";
if (ui_option_bits & 0x04) s += "ASSERT_PIN25_ON_TELEGRAM_SENT ";
if (ui_option_bits & 0x08) s += "PIN25_POLARITY_REVERSED ";
s += tostrprintf("led flash: %d ms ", led_flash_timing);
s += tostrprintf("recalibrate: %d ms ", recalibrate_in_ms);
s.pop_back();
return s;
}
void encode(vector<uchar> &bytes, uchar lm)
{
bytes.clear();
bytes.resize(11);
size_t i = 0;
bytes[i++] = lm;
bytes[i++] = option_bits & 0xff;
bytes[i++] = (option_bits >> 8) & 0xff;
bytes[i++] = ui_option_bits & 0xff;
bytes[i++] = (ui_option_bits >> 8) & 0xff;
bytes[i++] = led_flash_timing & 0xff;
bytes[i++] = (led_flash_timing >> 8) & 0xff;
bytes[i++] = recalibrate_in_ms & 0xff;
bytes[i++] = (recalibrate_in_ms >> 8) & 0xff;
bytes[i++] = (recalibrate_in_ms >> 16) & 0xff;
bytes[i++] = (recalibrate_in_ms >> 24) & 0xff;
}
bool decode(vector<uchar> &bytes)
{
if (bytes.size() < 11) return false;
size_t i = 0;
uchar c = bytes[i++];
link_modes.clear();
if (c == LINK_MODE_OFF)
{
}
else if (c == LINK_MODE_S)
{
link_modes.addLinkMode(LinkMode::S1);
}
else if (c == LINK_MODE_T)
{
link_modes.addLinkMode(LinkMode::T1);
}
else if (c == LINK_MODE_CT)
{
link_modes.addLinkMode(LinkMode::C1).addLinkMode(LinkMode::T1);
}
else if (c == LINK_MODE_C)
{
link_modes.addLinkMode(LinkMode::C1);
}
option_bits = bytes[i+1]<<8 | bytes[i+0];
i += 2;
ui_option_bits = bytes[i+1]<<8 | bytes[i+0];
i += 2;
led_flash_timing = bytes[i+1]<<8 | bytes[i+0];
i += 2;
recalibrate_in_ms =
bytes[i+3]<<24 | bytes[i+2] <<16 |
bytes[i+1]<<8 | bytes[i+0];
return true;
}
};
const char *toString(ErrorCodeIU891ADevMgmt ec)
{
switch (ec)
{
#define X(num,text,info) case ErrorCodeIU891ADevMgmt::text: return #info;
LIST_OF_IU891A_DEVMGMT_ERROR_CODES
#undef X
default: return "Unknown";
}
}
ErrorCodeIU891ADevMgmt toErrorCodeIU891ADevMgmt(uchar c)
{
switch (c)
{
#define X(num,text,info) case num: return ErrorCodeIU891ADevMgmt::text;
LIST_OF_IU891A_DEVMGMT_ERROR_CODES
#undef X
default: return ErrorCodeIU891ADevMgmt::UNKNOWN;
}
}
const char *toString(ErrorCodeIU891AWMBUSGW ec)
{
switch (ec)
{
#define X(num,text,info) case ErrorCodeIU891AWMBUSGW::text: return #info;
LIST_OF_IU891A_WMBUSGW_ERROR_CODES
#undef X
default: return "Unknown";
}
}
ErrorCodeIU891AWMBUSGW toErrorCodeIU891AWMBUSGW(uchar c)
{
switch (c)
{
#define X(num,text,info) case num: return ErrorCodeIU891AWMBUSGW::text;
LIST_OF_IU891A_WMBUSGW_ERROR_CODES
#undef X
default: return ErrorCodeIU891AWMBUSGW::UNKNOWN;
}
}
struct WMBusIU891A : public virtual BusDeviceCommonImplementation
{
bool ping();
string getDeviceId();
string getDeviceUniqueId();
uchar getFirmwareVersion();
LinkModeSet getLinkModes();
void deviceReset();
bool deviceSetLinkModes(LinkModeSet lms);
LinkModeSet supportedLinkModes()
{
return
C1_bit |
C2_bit |
S1_bit |
S1m_bit |
T1_bit |
T2_bit;
}
int numConcurrentLinkModes()
{
return 2;
}
bool canSetLinkModes(LinkModeSet lms)
{
if (lms.empty()) return false;
if (!supportedLinkModes().supports(lms)) return false;
// Ok, the supplied link modes are compatible.
// For iu891a
if (2 == countSetBits(lms.asBits()) &&
lms.has(LinkMode::C1) &&
lms.has(LinkMode::T1))
{
return true;
}
// Otherwise its a single link mode.
return 1 == countSetBits(lms.asBits());
}
bool sendTelegram(LinkMode lm, TelegramFormat format, vector<uchar> &content);
void processSerialData();
void simulate() { }
WMBusIU891A(BusDeviceType type, string alias, shared_ptr<SerialDevice> serial, shared_ptr<SerialCommunicationManager> manager);
~WMBusIU891A() {
}
static FrameStatus checkIU891AFrame(vector<uchar> &data,
vector<uchar> &out,
size_t *frame_length,
int *endpoint_id_out,
int *msg_id_out,
int *status_out,
int *rssi_dbm);
static void extractFrame(vector<uchar> &payload, int *rssi_dbm, vector<uchar> *frame);
private:
DeviceInfo_IU891A device_info_ {};
WMBusAddressInfo_IU891A device_wmbus_address_ {};
Config_IU891A device_config_ {};
uchar last_set_link_mode_ { 0x00 };
vector<uchar> read_buffer_;
vector<uchar> request_;
vector<uchar> response_;
bool getDeviceInfo();
bool loaded_device_info_ {};
bool getConfig();
friend AccessCheck detectIU891A(Detected *detected, shared_ptr<SerialCommunicationManager> manager);
void handleDevMgmt(int msgid, vector<uchar> &payload);
void handleWMbusGateway(int msgid, vector<uchar> &payload);
};
shared_ptr<BusDevice> openIU891A(BusDeviceType type, Detected detected, shared_ptr<SerialCommunicationManager> manager, shared_ptr<SerialDevice> serial_override)
{
string bus_alias = detected.specified_device.bus_alias;
string device_file = detected.found_file;
assert(device_file != "");
if (serial_override)
{
WMBusIU891A *imp = new WMBusIU891A(type, bus_alias, serial_override, manager);
imp->markAsNoLongerSerial();
return shared_ptr<BusDevice>(imp);
}
auto serial = manager->createSerialDeviceTTY(device_file.c_str(), 115200, PARITY::NONE, "iu891a");
WMBusIU891A *imp = new WMBusIU891A(type, bus_alias, serial, manager);
return shared_ptr<BusDevice>(imp);
}
shared_ptr<BusDevice> openIU891A(Detected detected, shared_ptr<SerialCommunicationManager> manager, shared_ptr<SerialDevice> serial_override)
{
return openIU891A(BusDeviceType::DEVICE_IU891A, detected, manager, serial_override);
}
shared_ptr<BusDevice> open(Detected detected, shared_ptr<SerialCommunicationManager> manager, shared_ptr<SerialDevice> serial_override)
{
return openIU891A(BusDeviceType::DEVICE_IU891A, detected, manager, serial_override);
}
WMBusIU891A::WMBusIU891A(BusDeviceType type, string alias, shared_ptr<SerialDevice> serial, shared_ptr<SerialCommunicationManager> manager) :
BusDeviceCommonImplementation(alias, type, manager, serial, true)
{
reset();
}
static void buildRequest(int endpoint_id, int msg_id, vector<uchar>& body, vector<uchar>& out)
{
vector<uchar> request;
request.push_back(endpoint_id);
request.push_back(msg_id);
request.insert(request.end(), body.begin(), body.end());
uint16_t crc = ~crc16_CCITT(&request[0], request.size()); // Safe to use &[0] since request size > 0
request.push_back(crc & 0xff);
request.push_back(crc >> 8);
addSlipFraming(request, out);
}
bool WMBusIU891A::ping()
{
if (serial()->readonly()) return true; // Feeding from stdin or file.
return true;
}
string WMBusIU891A::getDeviceId()
{
if (serial()->readonly()) return "?"; // Feeding from stdin or file.
if (cached_device_id_ != "") return cached_device_id_;
bool ok = getDeviceInfo();
if (!ok) return "ERR";
ok = getConfig();
if (!ok) return "ERR";
cached_device_id_ = device_wmbus_address_.dongleId();
verbose("(iu891a) got device id %s\n", cached_device_id_.c_str());
return cached_device_id_;
}
string WMBusIU891A::getDeviceUniqueId()
{
if (serial()->readonly()) return "?"; // Feeding from stdin or file.
if (cached_device_unique_id_ != "") return cached_device_unique_id_;
bool ok = getDeviceInfo();
if (!ok) return "ERR";
cached_device_unique_id_ = tostrprintf("%08x", device_info_.uid);
verbose("(im871a) got device unique id %s\n", cached_device_unique_id_.c_str());
return cached_device_unique_id_;
}
uchar WMBusIU891A::getFirmwareVersion()
{
if (serial()->readonly()) return 0x15; // Feeding from stdin or file.
// bool ok = getDeviceInfo();
// if (!ok) return 255;
return 0; // device_info_.firmware_version;
}
LinkModeSet WMBusIU891A::getLinkModes()
{
if (serial()->readonly()) { return Any_bit; } // Feeding from stdin or file.
bool ok = getConfig();
if (!ok) return LinkModeSet();
return device_config_.link_modes;
}
void WMBusIU891A::deviceReset()
{
// No device specific settings needed right now.
// The common code in wmbus.cc reset()
// will open the serial device and potentially
// set the link modes properly.
}
uchar setupIMSTBusDeviceToReceiveTelegrams(LinkModeSet lms)
{
if (lms.has(LinkMode::C1) && lms.has(LinkMode::T1))
{
return LINK_MODE_CT;
}
else if (lms.has(LinkMode::C1))
{
return LINK_MODE_C;
}
else if (lms.has(LinkMode::C2))
{
return LINK_MODE_C;
}
else if (lms.has(LinkMode::S1))
{
return LINK_MODE_S;
}
else if (lms.has(LinkMode::S1m))
{
return LINK_MODE_S;
}
else if (lms.has(LinkMode::T1))
{
return LINK_MODE_T;
}
else if (lms.has(LinkMode::T2))
{
return LINK_MODE_T;
}
else
{
return LINK_MODE_C; // Defaults to C
}
assert(false);
// Error
return 0xff;
}
bool WMBusIU891A::deviceSetLinkModes(LinkModeSet lms)
{
if (serial()->readonly()) return true; // Feeding from stdin or file.
if (!canSetLinkModes(lms))
{
string modes = lms.hr();
error("(iu871a) setting link mode(s) %s is not supported for iu891a\n", modes.c_str());
}
LOCK_WMBUS_EXECUTING_COMMAND(set_link_modes);
vector<uchar> body;
vector<uchar> request;
device_config_.option_bits &= 0xfffe; // forward all received telegrams to wmbusmeters.
device_config_.option_bits |= 0x0006; // get notified when received and sent.
device_config_.encode(body, setupIMSTBusDeviceToReceiveTelegrams(lms));
buildRequest(SAP_WMBUSGW_ID, WMBUSGW_SET_ACTIVE_CONFIGURATION_REQ, body, request);
verbose("(iu891a) set config\n");
bool sent = serial()->send(request);
if (!sent) return false;
bool ok = waitForResponse(WMBUSGW_SET_ACTIVE_CONFIGURATION_RSP);
if (!ok) return false; // timeout
verbose("(iu871a) set config to set link mode %02x\n", body[0]);
return true;
}
void WMBusIU891A::extractFrame(vector<uchar> &payload, int *rssi_dbm, vector<uchar> *frame)
{
if (payload.size() < 10) return;
*rssi_dbm = (int8_t)payload[7];
frame->clear();
frame->insert(frame->begin(), payload.begin()+8, payload.end());
}
FrameStatus WMBusIU891A::checkIU891AFrame(vector<uchar> &data,
vector<uchar> &out,
size_t *frame_length_out,
int *endpoint_id_out,
int *msg_id_out,
int *status_byte_out,
int *rssi_dbm)
{
vector<uchar> msg;
removeSlipFraming(data, frame_length_out, msg);
if (msg.size() < 5) return PartialFrame;
*endpoint_id_out = msg[0];
*msg_id_out = msg[1];
*status_byte_out = msg[2];
uint16_t crc = ~crc16_CCITT(&msg[0], msg.size()-2);
uchar crc_lo = crc & 0xff;
uchar crc_hi = crc >> 8;
if (msg[msg.size()-2] != crc_lo || msg[msg.size()-1] != crc_hi)
{
debug("(iu880b) bad crc got %02x%02x expected %02x%02x\n",
msg[msg.size()-1], msg[msg.size()-2], crc_hi, crc_lo);
return ErrorInFrame;
}
int payload_offset = 3;
int payload_len = msg.size()-2;
out.clear();
out.insert(out.end(), msg.begin()+payload_offset, msg.begin()+payload_len);
return FullFrame;
}
void WMBusIU891A::processSerialData()
{
vector<uchar> data;
// Receive and accumulated serial data until a full frame has been received.
serial()->receive(&data);
read_buffer_.insert(read_buffer_.end(), data.begin(), data.end());
size_t frame_length;
int endpoint_id;
int msg_id;
int status_byte;
int rssi_dbm = 0;
vector<uchar> payload;
for (;;)
{
FrameStatus status = checkIU891AFrame(read_buffer_,
payload,
&frame_length,
&endpoint_id,
&msg_id,
&status_byte,
&rssi_dbm);
if (status == PartialFrame)
{
if (read_buffer_.size() > 0)
{
debugPayload("(iu891a) partial frame, expecting more.", read_buffer_);
}
break;
}
if (status == ErrorInFrame)
{
debugPayload("(iu891a) bad frame, clearing.", read_buffer_);
read_buffer_.clear();
break;
}
if (status == FullFrame)
{
read_buffer_.erase(read_buffer_.begin(), read_buffer_.begin()+frame_length);
// We now have a proper message in payload. Let us trigger actions based on it.
// It can be wmbus receiver-dongle messages or wmbus remote meter messages received over the radio.
switch (endpoint_id) {
case SAP_DEVMGMT_ID: handleDevMgmt(msg_id, payload); break;
case SAP_WMBUSGW_ID: handleWMbusGateway(msg_id, payload); break;
}
}
}
}
bool WMBusIU891A::getDeviceInfo()
{
if (loaded_device_info_) return true;
LOCK_WMBUS_EXECUTING_COMMAND(get_device_info);
vector<uchar> body;
vector<uchar> request;
buildRequest(SAP_DEVMGMT_ID, DEVMGMT_MSG_GET_DEVICE_INFO_REQ, body, request);
verbose("(iu891a) get device info\n");
bool sent = serial()->send(request);
if (!sent) return false; // tty overridden with stdin/file
bool ok = waitForResponse(DEVMGMT_MSG_GET_DEVICE_INFO_RSP);
if (!ok) return false; // timeout
// Now device info response is in response_ vector.
device_info_.decode(response_);
verbose("(iu891a) device info: %s\n", device_info_.str().c_str());
body.clear();
request.clear();
buildRequest(SAP_WMBUSGW_ID, WMBUSGW_GET_WMBUS_ADDRESS_REQ, body, request);
verbose("(iu891a) get wmbus address\n");
sent = serial()->send(request);
if (!sent) return false; // tty overridden with stdin/file
ok = waitForResponse(WMBUSGW_GET_WMBUS_ADDRESS_RSP);
if (!ok) return false; // timeout
// Now device info response is in response_ vector.
device_wmbus_address_.decode(response_);
loaded_device_info_ = true;
verbose("(iu891a) device info: %s %s\n", device_wmbus_address_.dongleId().c_str(), device_info_.str().c_str());
return true;
}
bool WMBusIU891A::getConfig()
{
if (serial()->readonly()) return true;
LOCK_WMBUS_EXECUTING_COMMAND(get_config);
vector<uchar> body;
vector<uchar> request;
buildRequest(SAP_WMBUSGW_ID, WMBUSGW_GET_ACTIVE_CONFIGURATION_REQ, body, request);
verbose("(iu891a) get config\n");
bool sent = serial()->send(request);
if (!sent) return false; // tty overridden with stdin/file
bool ok = waitForResponse(WMBUSGW_GET_ACTIVE_CONFIGURATION_RSP);
if (!ok) return false; // timeout
// Now device info response is in response_ vector.
device_config_.decode(response_);
verbose("(iu891a) config: %s link modes: %s\n",
device_config_.str().c_str(),
device_config_.link_modes.hr().c_str());
return true;
}
bool WMBusIU891A::sendTelegram(LinkMode lm, TelegramFormat format, vector<uchar> &content)
{
if (serial()->readonly()) return true;
if (content.size() > 250) return false;
return false;
}
AccessCheck detectIU891A(Detected *detected, shared_ptr<SerialCommunicationManager> manager)
{
assert(detected->found_file != "");
// Talk to the device and expect a very specific answer.
auto serial = manager->createSerialDeviceTTY(detected->found_file.c_str(), 115200, PARITY::NONE, "detect iu891a");
serial->disableCallbacks();
bool ok = serial->open(false);
if (!ok)
{
verbose("(iu891a) could not open tty %s for detection\n", detected->found_file.c_str());
return AccessCheck::NoSuchDevice;
}
vector<uchar> response;
// First clear out any data in the queue.
serial->receive(&response);
response.clear();
vector<uchar> init;
init.resize(30);
for (int i=0; i<30; ++i)
{
init[i] = 0xc0;
}
// Wake-up the dongle.
serial->send(init);
vector<uchar> body;
vector<uchar> request;
buildRequest(SAP_DEVMGMT_ID, DEVMGMT_MSG_GET_DEVICE_INFO_REQ, body, request);
serial->send(request);
// Wait for 100ms so that the USB stick have time to prepare a response.
usleep(100*1000);
serial->receive(&response);
int endpoint_id = 0;
int msg_id = 0;
int status_byte = 0;
size_t frame_length = 0;
int rssi_dbm = 0;
vector<uchar> payload;
FrameStatus status = WMBusIU891A::checkIU891AFrame(response,
payload,
&frame_length,
&endpoint_id,
&msg_id,
&status_byte,
&rssi_dbm);
if (status != FullFrame ||
endpoint_id != SAP_DEVMGMT_ID ||
msg_id != DEVMGMT_MSG_GET_DEVICE_INFO_RSP)
{
verbose("(iu891a) are you there? no.\n");
serial->close();
return AccessCheck::NoProperResponse;
}
debugPayload("(iu891a) device info response", payload);
debug("(iu891a) endpoint %02x msg %02x status %02x\n", endpoint_id, msg_id, status_byte);
DeviceInfo_IU891A di;
di.decode(payload);
debug("(iu891a) info: %s\n", di.str().c_str());
body.clear();
request.clear();
buildRequest(SAP_WMBUSGW_ID, WMBUSGW_GET_WMBUS_ADDRESS_REQ, body, request);
serial->send(request);
// Wait for 100ms so that the USB stick have time to prepare a response.
usleep(100*1000);
serial->receive(&response);
status = WMBusIU891A::checkIU891AFrame(response,
payload,
&frame_length,
&endpoint_id,
&msg_id,
&status_byte,
&rssi_dbm);
if (status != FullFrame ||
endpoint_id != SAP_WMBUSGW_ID ||
msg_id != WMBUSGW_GET_WMBUS_ADDRESS_RSP)
{
verbose("(iu891a) are you there? I thought so, but no.\n");
serial->close();
return AccessCheck::NoProperResponse;
}
debugPayload("(iu891a) wmbus address response", payload);
WMBusAddressInfo_IU891A wa;
wa.decode(payload);
serial->close();
detected->setAsFound(wa.dongleId(), BusDeviceType::DEVICE_IU891A, 115200, false, detected->specified_device.linkmodes);
verbose("(iu891a) are you there? yes %s\n", wa.dongleId().c_str());
return AccessCheck::AccessOK;
}
void WMBusIU891A::handleDevMgmt(int msgid, vector<uchar> &payload)
{
switch (msgid) {
case DEVMGMT_MSG_PING_RSP:
debug("(iu891a) rsp pong\n");
break;
case DEVMGMT_MSG_GET_DEVICE_INFO_RSP:
debug("(iu891a) rsp got device info\n");
break;
case DEVMGMT_MSG_GET_FW_INFO_RSP:
debug("(iu891a) rsp got firmware\n");
break;
default:
warning("(iu891a) Unhandled device management message %d\n", msgid);
return;
}
response_.clear();
response_.insert(response_.end(), payload.begin(), payload.end());
notifyResponseIsHere(msgid);
}
void WMBusIU891A::handleWMbusGateway(int msgid, vector<uchar> &payload)
{
switch (msgid) {
case WMBUSGW_GET_WMBUS_ADDRESS_RSP:
debug("(iu891a) rsp got wmbus address\n");
break;
case WMBUSGW_GET_ACTIVE_CONFIGURATION_RSP:
debug("(iu891a) rsp got active config\n");
break;
case WMBUSGW_SET_ACTIVE_CONFIGURATION_RSP:
debug("(iu891a) rsp set active config\n");
break;
case WMBUSGW_RX_MESSAGE_IND:
{
// Invoke common telegram reception code in BusDeviceCommonImplementation.
vector<uchar> frame;
int rssi_dbm;
extractFrame(payload, &rssi_dbm, &frame);
AboutTelegram about("iu891a["+cached_device_id_+"]", rssi_dbm, FrameType::WMBUS);
handleTelegram(about, frame);
return;
}
break;
default:
warning("(iu891a) Unhandled wmbus gateway message %d\n", msgid);
return;
}
response_.clear();
response_.insert(response_.end(), payload.begin(), payload.end());
notifyResponseIsHere(msgid);
}

152
src/wmbus_iu891a.h 100644
Wyświetl plik

@ -0,0 +1,152 @@
// Copyright (C) 2024 Fredrik Öhrström (gpl-3.0-or-later)
// Service access endpoints inside the iu891a dongle that can receive send messages.
#define SAP_DEVMGMT_ID 0x01
#define SAP_WMBUSGW_ID 0x09
#define LINK_MODE_OFF 0
#define LINK_MODE_S 1
#define LINK_MODE_T 2
#define LINK_MODE_CT 3
#define LINK_MODE_C 5
#define LINK_MODE_ENHANCED_T 6
#define DEVMGMT_MSG_PING_REQ 0x01
#define DEVMGMT_MSG_PING_RSP 0x02
#define DEVMGMT_MSG_GET_DEVICE_INFO_REQ 0x03
#define DEVMGMT_MSG_GET_DEVICE_INFO_RSP 0x04
#define DEVMGMT_MSG_GET_FW_INFO_REQ 0x05
#define DEVMGMT_MSG_GET_FW_INFO_RSP 0x06
#define DEVMGMT_MSG_RESTART_REQ 0x07
#define DEVMGMT_MSG_RESTART_RSP 0x08
#define DEVMGMT_MSG_SET_SYS_OP_MODE_REQ 0x09
#define DEVMGMT_MSG_SET_SYS_OP_MODE_RSP 0x0a
#define DEVMGMT_MSG_GET_SYS_OP_MODE_REQ 0x0b
#define DEVMGMT_MSG_GET_SYS_OP_MODE_RSP 0x0c
#define DEVMGMT_MSG_SET_SYS_DATE_TIME_REQ 0x0d
#define DEVMGMT_MSG_SET_SYS_DATE_TIME_RSP 0x0e
#define DEVMGMT_MSG_GET_SYS_DATE_TIME_REQ 0x0f
#define DEVMGMT_MSG_GET_SYS_DATE_TIME_RSP 0x10
#define DEVMGMT_MSG_SET_SYS_OPTIONS_REQ 0xf7
#define DEVMGMT_MSG_SET_SYS_OPTIONS_RSP 0xf8
#define DEVMGMT_MSG_GET_SYS_OPTIONS_REQ 0xf9
#define DEVMGMT_MSG_GET_SYS_OPTIONS_RSP 0xfa
#define LIST_OF_IU891A_DEVMGMT_ERROR_CODES \
X(0, OK, "ok") \
X(1, ERROR, "error") \
X(2, COMMAND_NOT_SUPPORTED, "command not supported") \
X(3, WRONG_PARAMETER, "wrong parameter") \
X(4, WRONG_APPLICATION, "wrong application / device mode") \
X(5, RESERVED, "reserved") \
X(6, BUSY, "application / device busy, try later") \
X(7, WRONG_MSG_LENGTH, "wrong message length") \
X(8, NVM_WRITE_ERROR, "NVM write error") \
X(9, NVM_READ_ERROR, "NVM read error ( NVM content is invalid )") \
X(10, COMMAND_REJECTED, "command rejected") \
X(11, RESERVED2, "reserved2") \
X(12, UNEXPECTED_MSG_FORMAT, "unexpected message format")
enum class ErrorCodeIU891ADevMgmt {
#define X(num,text,info) text,
LIST_OF_IU891A_DEVMGMT_ERROR_CODES
#undef X
UNKNOWN
};
const char *toString(ErrorCodeIU891ADevMgmt ec);
ErrorCodeIU891ADevMgmt toErrorCodeIU891ADevMgmt(uchar c);
#define WMBUSGW_GET_ACTIVE_CONFIGURATION_REQ 0x01
#define WMBUSGW_GET_ACTIVE_CONFIGURATION_RSP 0x02
#define WMBUSGW_SET_ACTIVE_CONFIGURATION_REQ 0x03
#define WMBUSGW_SET_ACTIVE_CONFIGURATION_RSP 0x04
#define WMBUSGW_GET_DEFAULT_CONFIGURATION_REQ 0x05
#define WMBUSGW_GET_DEFAULT_CONFIGURATION_RSP 0x06
#define WMBUSGW_SET_DEFAULT_CONFIGURATION_REQ 0x07
#define WMBUSGW_SET_DEFAULT_CONFIGURATION_RSP 0x08
#define WMBUSGW_RESET_DEFAULT_CONFIGURATION_REQ 0x09
#define WMBUSGW_RESET_DEFAULT_CONFIGURATION_RSP 0x0a
#define WMBUSGW_CLEAR_WMBUS_DEVICE_LIST_REQ 0x11
#define WMBUSGW_CLEAR_WMBUS_DEVICE_LIST_RSP 0x12
#define WMBUSGW_APPEND_WMBUS_DEVICE_LIST_REQ 0x13
#define WMBUSGW_APPEND_WMBUS_DEVICE_LIST_RSP 0x14
#define WMBUSGW_READ_WMBUS_DEVICE_LIST_REQ 0x15
#define WMBUSGW_READ_WMBUS_DEVICE_LIST_RSP 0x16
#define WMBUSGW_SAVE_WMBUS_DEVICE_LIST_REQ 0x17
#define WMBUSGW_SAVE_WMBUS_DEVICE_LIST_RSP 0x18
#define WMBUSGW_LOAD_WMBUS_DEVICE_LIST_REQ 0x19
#define WMBUSGW_LOAD_WMBUS_DEVICE_LIST_RSP 0x1a
#define WMBUSGW_RX_MESSAGE_IND 0x20
#define WMBUSGW_ENABLE_DISABLE_SCAN_MODE_REQ 0x21
#define WMBUSGW_ENABLE_DISABLE_SCAN_MODE_RSP 0x22
#define WMBUSGW_SCAN_MODE_PACKET_IND 0x24
// Only iM881-XL and iM891A-XL
#define WMBUSGW_SEND_MESSAGE_REQ 0x31
#define WMBUSGW_SEND_MESSAGE_RSP 0x32
#define WMBUSGW_MESSAGE_PACKET_TRANSMITTED_IND 0x34
// Only iM881-XL and iM891A-XL
#define WMBUSGW_ENCRYPT_SEND_MESSAGE_REQ 0x35
#define WMBUSGW_ENCRYPT_SEND_MESSAGE_RSP 0x36
#define WMBUSGW_ENCRYPTED_MESSAGE_PACKET_TRANSMITTED_IND 0x38
// OnlyiU891A-XL
#define WMBUSGW_SEND_PACKET_REQ 0x39
#define WMBUSGW_SEND_PACKET_RSP 0x3A
// Only iU891A-XL
#define WMBUSGW_ENCRYPT_SEND_PACKET_REQ 0x3b
#define WMBUSGW_ENCRYPT_SEND_PACKET_RSP 0x3c
#define WMBUSGW_GET_APPLICATION_STATUS_REQ 0x41
#define WMBUSGW_GET_APPLICATION_STATUS_RSP 0x42
#define WMBUSGW_RESET_APPLICATION_STATUS_REQ 0x43
#define WMBUSGW_RESET_APPLICATION_STATUS_RSP 0x44
// Only iM881A-XL, iM891A-XL
#define WMBUSGW_GET_RADIO_CONTROL_CONFIG_REQ 0x51
#define WMBUSGW_GET_RADIO_CONTROL_CONFIG_RSP 0x52
#define WMBUSGW_SET_RADIO_CONTROL_CONFIG_REQ 0x53
#define WMBUSGW_SET_RADIO_CONTROL_CONFIG_RSP 0x54
// Only iU891A-XL
#define WMBUSGW_GET_WMBUS_ADDRESS_REQ 0x81
#define WMBUSGW_GET_WMBUS_ADDRESS_RSP 0x82
#define LIST_OF_IU891A_WMBUSGW_ERROR_CODES \
X(0, OK, "ok") \
X(1, ERROR, "error") \
X(2, COMMAND_NOT_SUPPORTED, "command not supported") \
X(3, WRONG_PARAMETER, "wrong parameter") \
X(4, WRONG_APPLICATION_MODE, "wrong application mode") \
X(5, NO_MORE_DATA, "no more data") \
X(6, APPLICATION_BUSY, "application busy, try later") \
X(7, WRONG_MSG_LENGTH, "wrong message length") \
X(8, NVM_WRITE_ERROR, "NVM write error") \
X(9, NVM_READ_ERROR, "NVM read error ( NVM content is invalid )") \
X(10, COMMAND_REJECTED, "command rejected, execution not possible in current application state") \
X(11, ACCESS_DENIED, "access denied, operation may need another system operation mode") \
X(12, DATA_TRUNCATED, "data truncated") \
X(13, UNSUPPORTED_ENCRYPTION_MODE, "unsupported encryption mode") \
X(14, NO_ENCRYPTION_KEY_FOUND, "no encryption key found for given meter address") \
X(15, ENCRYPTION_PARAMETER_MISSING, "encryption info / parameter missing") \
X(16, ENCRYPTION_ERROR, "encryption error")
enum class ErrorCodeIU891AWMBUSGW {
#define X(num,text,info) text,
LIST_OF_IU891A_WMBUSGW_ERROR_CODES
#undef X
UNKNOWN
};
const char *toString(ErrorCodeIU891AWMBUSGW ec);
ErrorCodeIU891AWMBUSGW toErrorCodeIU891AWMBUSGW(uchar c);