kopia lustrzana https://github.com/weetmuts/wmbusmeters
Add Izar water meter support
rodzic
04d8662f87
commit
302b08a478
1
Makefile
1
Makefile
|
@ -104,6 +104,7 @@ METER_OBJS:=\
|
|||
$(BUILD)/meter_vario451.o \
|
||||
$(BUILD)/meter_lansenth.o \
|
||||
$(BUILD)/meter_rfmamb.o \
|
||||
$(BUILD)/meter_izar.o \
|
||||
$(BUILD)/printer.o \
|
||||
$(BUILD)/serial.o \
|
||||
$(BUILD)/shell.o \
|
||||
|
|
|
@ -174,6 +174,7 @@ Tauron Amiplus (amiplus) (includes vendor apator and echelon)
|
|||
Work in progress:
|
||||
Heat meter Kamstrup Multical 302 (multical302)
|
||||
Electricity meter Kamstrup Omnipower (omnipower)
|
||||
Water meter Diehl/Sappel IZAR RC 868 I R4 PL (izar)
|
||||
```
|
||||
|
||||
The wmbus dongles imst871a can listen to one type of wmbus telegrams
|
||||
|
|
|
@ -58,3 +58,7 @@ telegram=|2e44333003020100071b7a634820252f2f0265840842658308820165950802fb1aae01
|
|||
|
||||
telegram=|5744b40988227711101b7ab20800000265a00842658f088201659f08226589081265a0086265510852652b0902fb1aba0142fb1ab0018201fb1abd0122fb1aa90112fb1aba0162fb1aa60152fb1af501066d3b3bb36b2a00|
|
||||
{"media":"room sensor","meter":"rfmamb","name":"Rummet","id":"11772288","current_temperature_c":22.08,"average_temperature_1h_c":21.91,"average_temperature_24h_c":22.07,"maximum_temperature_1h_c":22.08,"minimum_temperature_1h_c":21.85,"maximum_temperature_24h_c":23.47,"minimum_temperature_24h_c":21.29,"current_relative_humidity_rh":44.2,"average_relative_humidity_1h_rh":43.2,"average_relative_humidity_24h_rh":44.5,"minimum_relative_humidity_1h_rh":42.2,"maximum_relative_humidity_1h_rh":50.1,"maximum_relative_humidity_24h_rh":0,"minimum_relative_humidity_24h_rh":0,"device_date_time":"2019-10-11 19:59","timestamp":"1111-11-11T11:11:11Z"}
|
||||
|
||||
# Test IZAR RC 868 I R4 PL water meter telegram
|
||||
telegram=|1944304C72242421D401A2|013D4013DD8B46A4999C1293E582CC|
|
||||
{"media":"oil","meter":"izar","name":"IzarWater","id":"21242472","total_m3":3.488,"timestamp":"1111-11-11T11:11:11Z"}
|
||||
|
|
|
@ -359,7 +359,7 @@ unique_ptr<Configuration> parseCommandLine(int argc, char **argv) {
|
|||
mt = toMeterType(type);
|
||||
|
||||
if (mt == MeterType::UNKNOWN) error("Not a valid meter type \"%s\"\n", type.c_str());
|
||||
if (!isValidMatchExpressions(id, false)) error("Not a valid id nor a valid meter match expression \"%s\"\n", id.c_str());
|
||||
if (!isValidMatchExpressions(id, true)) error("Not a valid id nor a valid meter match expression \"%s\"\n", id.c_str());
|
||||
if (!isValidKey(key)) error("Not a valid meter key \"%s\"\n", key.c_str());
|
||||
vector<string> no_meter_shells, no_meter_jsons;
|
||||
c->meters.push_back(MeterInfo(name, type, id, key, modes, no_meter_shells, no_meter_jsons));
|
||||
|
|
|
@ -124,7 +124,7 @@ void parseMeterConfig(Configuration *c, vector<char> &buf, string file)
|
|||
warning("Not a valid meter type \"%s\"\n", type.c_str());
|
||||
use = false;
|
||||
}
|
||||
if (!isValidMatchExpressions(id, false)) {
|
||||
if (!isValidMatchExpressions(id, true)) {
|
||||
warning("Not a valid meter id nor a valid meter match expression \"%s\"\n", id.c_str());
|
||||
use = false;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
Copyright (C) 2019 Jacek Tomasiak
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include"meters.h"
|
||||
#include"meters_common_implementation.h"
|
||||
#include"wmbus.h"
|
||||
#include"wmbus_utils.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define PRIOS_DEFAULT_KEY1 "39BC8A10E66D83F8"
|
||||
#define PRIOS_DEFAULT_KEY2 "51728910E66D83F8"
|
||||
|
||||
struct MeterIzar : public virtual WaterMeter, public virtual MeterCommonImplementation {
|
||||
MeterIzar(WMBus *bus, MeterInfo &mi);
|
||||
|
||||
// Total water counted through the meter
|
||||
double totalWaterConsumption(Unit u);
|
||||
bool hasTotalWaterConsumption();
|
||||
|
||||
private:
|
||||
|
||||
void processContent(Telegram *t);
|
||||
uint32_t convertKey(const char *hex);
|
||||
uint32_t uint32FromBytes(const vector<uchar> &data, int offset, bool reverse = false);
|
||||
vector<uchar> decodePrios(const vector<uchar> &payload, uint32_t key);
|
||||
|
||||
double total_water_consumption_l_ {};
|
||||
};
|
||||
|
||||
unique_ptr<WaterMeter> createIzar(WMBus *bus, MeterInfo &mi)
|
||||
{
|
||||
return unique_ptr<WaterMeter>(new MeterIzar(bus, mi));
|
||||
}
|
||||
|
||||
MeterIzar::MeterIzar(WMBus *bus, MeterInfo &mi) :
|
||||
MeterCommonImplementation(bus, mi, MeterType::IZAR, MANUFACTURER_SAP)
|
||||
{
|
||||
addMedia(0x01); // Oil meter? why?
|
||||
|
||||
addLinkMode(LinkMode::T1);
|
||||
|
||||
// meters with different versions exist, don't set any to avoid warnings
|
||||
// setExpectedVersion(0xd4); // or 0xcc
|
||||
|
||||
addPrint("total", Quantity::Volume,
|
||||
[&](Unit u){ return totalWaterConsumption(u); },
|
||||
"The total water consumption recorded by this meter.",
|
||||
true, true);
|
||||
|
||||
MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*));
|
||||
}
|
||||
|
||||
double MeterIzar::totalWaterConsumption(Unit u)
|
||||
{
|
||||
assertQuantity(u, Quantity::Volume);
|
||||
return convert(total_water_consumption_l_, Unit::L, u);
|
||||
}
|
||||
|
||||
bool MeterIzar::hasTotalWaterConsumption()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t MeterIzar::uint32FromBytes(const vector<uchar> &data, int offset, bool reverse)
|
||||
{
|
||||
if (reverse)
|
||||
return ((uint32_t)data[offset + 3] << 24) |
|
||||
((uint32_t)data[offset + 2] << 16) |
|
||||
((uint32_t)data[offset + 1] << 8) |
|
||||
(uint32_t)data[offset];
|
||||
else
|
||||
return ((uint32_t)data[offset] << 24) |
|
||||
((uint32_t)data[offset + 1] << 16) |
|
||||
((uint32_t)data[offset + 2] << 8) |
|
||||
(uint32_t)data[offset + 3];
|
||||
}
|
||||
|
||||
uint32_t MeterIzar::convertKey(const char *hex)
|
||||
{
|
||||
vector<uchar> bytes;
|
||||
hex2bin(hex, &bytes);
|
||||
uint32_t key1 = uint32FromBytes(bytes, 0);
|
||||
uint32_t key2 = uint32FromBytes(bytes, 4);
|
||||
uint32_t key = key1 ^ key2;
|
||||
return key;
|
||||
}
|
||||
|
||||
void MeterIzar::processContent(Telegram *t)
|
||||
{
|
||||
// recover full frame content
|
||||
vector<uchar> frame;
|
||||
frame.insert(frame.end(), t->parsed.begin(), t->parsed.end());
|
||||
frame.insert(frame.end(), t->payload.begin(), t->payload.end());
|
||||
|
||||
vector<uint32_t> keys;
|
||||
keys.push_back(convertKey(PRIOS_DEFAULT_KEY1));
|
||||
keys.push_back(convertKey(PRIOS_DEFAULT_KEY2));
|
||||
|
||||
vector<uchar> decoded_content;
|
||||
for (auto& key : keys) {
|
||||
decoded_content = decodePrios(frame, key);
|
||||
if (!decoded_content.empty())
|
||||
break;
|
||||
}
|
||||
|
||||
debug("(izar) Decoded PRIOS data: %s\n", bin2hex(decoded_content).c_str());
|
||||
|
||||
if (decoded_content.empty())
|
||||
{
|
||||
warning("(izar) Decoding PRIOS data failed. Ignoring telegram.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
total_water_consumption_l_ = uint32FromBytes(decoded_content, 1, true);
|
||||
}
|
||||
|
||||
vector<uchar> MeterIzar::decodePrios(const vector<uchar> &frame, uint32_t key)
|
||||
{
|
||||
// modify seed key with header values
|
||||
key ^= uint32FromBytes(frame, 2); // manufacturer + address[0-1]
|
||||
key ^= uint32FromBytes(frame, 6); // address[2-3] + version + type
|
||||
key ^= uint32FromBytes(frame, 10); // ci + some more bytes from the telegram...
|
||||
|
||||
int size = frame.size() - 15;
|
||||
vector<uchar> decoded(size);
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
// calculate new key (LFSR)
|
||||
// https://en.wikipedia.org/wiki/Linear-feedback_shift_register
|
||||
for (int j = 0; j < 8; ++j) {
|
||||
// calculate new bit value (xor of selected bits from previous key)
|
||||
uchar bit = ((key & 0x2) != 0) ^ ((key & 0x4) != 0) ^ ((key & 0x800) != 0) ^ ((key & 0x80000000) != 0);
|
||||
// shift key bits and add new one at the end
|
||||
key = (key << 1) | bit;
|
||||
}
|
||||
// decode i-th content byte with fresh/last 8-bits of key
|
||||
decoded[i] = frame[i + 15] ^ (key & 0xFF);
|
||||
// check-byte doesn't match?
|
||||
if (decoded[0] != 0x4B) {
|
||||
decoded.clear();
|
||||
return decoded;
|
||||
}
|
||||
}
|
||||
|
||||
return decoded;
|
||||
}
|
|
@ -31,6 +31,7 @@
|
|||
X(eurisii, T1_bit, HeatCostAllocation, EURISII, EurisII) \
|
||||
X(flowiq3100, C1_bit, Water, FLOWIQ3100, Multical21) \
|
||||
X(iperl, T1_bit, Water, IPERL, Iperl) \
|
||||
X(izar, T1_bit, Water, IZAR, Izar) \
|
||||
X(lansenth, T1_bit, TempHygro, LANSENTH, LansenTH) \
|
||||
X(mkradio3, T1_bit, Water, MKRADIO3, MKRadio3) \
|
||||
X(multical21, C1_bit, Water, MULTICAL21, Multical21) \
|
||||
|
@ -187,6 +188,7 @@ unique_ptr<WaterMeter> createSupercom587(WMBus *bus, MeterInfo &m);
|
|||
unique_ptr<WaterMeter> createMKRadio3(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<WaterMeter> createApator162(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<WaterMeter> createIperl(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<WaterMeter> createIzar(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<HeatCostMeter> createQCaloric(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<HeatCostMeter> createEurisII(WMBus *bus, MeterInfo &m);
|
||||
unique_ptr<TempHygroMeter> createLansenTH(WMBus *bus, MeterInfo &m);
|
||||
|
|
|
@ -21,6 +21,7 @@ $PROG --format=json simulations/simulation_t1.txt \
|
|||
HeatMeter eurisii 88018801 "" \
|
||||
Tempoo lansenth 00010203 "" \
|
||||
Rummet rfmamb 11772288 "" \
|
||||
IzarWater izar 21242472 "" \
|
||||
> $TEST/test_output.txt
|
||||
if [ "$?" == "0" ]
|
||||
then
|
||||
|
|
Ładowanie…
Reference in New Issue