Merge pull request #247 from don-vip/add-sharky

Add Sharky 775 + fix Diehl address swapping
pull/249/head
Fredrik Öhrström 2021-02-13 08:14:36 +01:00 zatwierdzone przez GitHub
commit 4f5d51b891
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
13 zmienionych plików z 533 dodań i 122 usunięć

Wyświetl plik

@ -156,6 +156,7 @@ METER_OBJS:=\
$(BUILD)/meter_qcaloric.o \
$(BUILD)/meter_rfmamb.o \
$(BUILD)/meter_rfmtx1.o \
$(BUILD)/meter_sharky.o \
$(BUILD)/meter_supercom587.o \
$(BUILD)/meter_sontex868.o \
$(BUILD)/meter_topaseskr.o \

Wyświetl plik

@ -6,12 +6,12 @@ telegram=|1944304C72242421D401A2|013D4013DD8B46A4999C1293E582CC|
# Test new version of IZAR
telegram=|2944A511780729662366A20118001378D3B3DB8CEDD77731F25832AAF3DA8CADF9774EA673172E8C61F2|
{"media":"water","meter":"izar","name":"IzarWater2","id":"23662907","total_m3":16.76,"last_month_total_m3":11.84,"last_month_measure_date":"2019-11-30","remaining_battery_life_y":12,"current_alarms":"no_alarm","previous_alarms":"no_alarm","transmit_period_s":8,"timestamp":"1111-11-11T11:11:11Z"}
{"media":"water","meter":"izar","name":"IzarWater2","id":"66236629","total_m3":16.76,"last_month_total_m3":11.84,"last_month_measure_date":"2019-11-30","remaining_battery_life_y":12,"current_alarms":"no_alarm","previous_alarms":"no_alarm","transmit_period_s":8,"timestamp":"1111-11-11T11:11:11Z"}
# Yet another version of IZAR
telegram=|1944A511780779194820A1|21170013355F8EDB2D03C6912B1E37
{"media":"water","meter":"izar","name":"IzarWater3","id":"48197907","total_m3":4.366,"last_month_total_m3":0,"last_month_measure_date":"2020-12-31","remaining_battery_life_y":11.5,"current_alarms":"no_alarm","previous_alarms":"no_alarm","transmit_period_s":8,"timestamp":"1111-11-11T11:11:11Z"}
{"media":"water","meter":"izar","name":"IzarWater3","id":"20481979","total_m3":4.366,"last_month_total_m3":0,"last_month_measure_date":"2020-12-31","remaining_battery_life_y":11.5,"current_alarms":"no_alarm","previous_alarms":"no_alarm","transmit_period_s":8,"timestamp":"1111-11-11T11:11:11Z"}
# And another izar, with a mfct specific tpl ci field a3.

Wyświetl plik

@ -220,3 +220,9 @@ telegram=||7844731e78610418010278046d0f13bc21040394030000841003690300008420032b0
telegram=||9e44731e17011020010278046d0813bc21040300000000841003000000008420030000000084300300000000848010030000000084016d0000bc218401030000000084110300000000842103000000008431030000000084811003000000008440fd4825090000848040fd480000000084c040fd48000000008440fd5b00000000848040fd5b0000000084c040fd5b0000000002fb2d881304fd1700000101|
{"media":"electricity","meter":"gransystems","name":"Gran301","id":"20100117","total_energy_consumption_kwh":0,"voltage_at_phase_1_v":234.1,"voltage_at_phase_2_v":0,"voltage_at_phase_3_v":0,"currrent_at_phase_1_a":0,"currrent_at_phase_2_a":0,"currrent_at_phase_3_a":0,"frequency_hz":50,"status":"OK","timestamp":"1111-11-11T11:11:11Z"}
|Gran301;20100117;0.000000;234.100000;0.000000;0.000000;0.000000;0.000000;0.000000;50.000000;OK;1111-11-11 11:11.11
# Test Hydrometer/Diehl Metering Sharky 775 heat meter
telegram=|534424232004256092687A370045752235854DEEEA5939FAD81C25FEEF5A23C38FB9168493C563F08DB10BAF87F660FBA91296BA2397E8F4220B86D3A192FB51E0BFCF24DCE72118E0C75A9E89F43BDFE370824B|
{"media":"heat","meter":"sharky","name":"Sharky775","id":"68926025","total_energy_consumption_kwh":2651,"total_energy_consumption_tariff1_kwh":0,"total_volume_m3":150.347,"total_volume_m3":0.018,"volume_flow_m3h":0,"power_kw":0,"flow_temperature_c":42.3,"return_temperature_c":28.1,"temperature_difference_c":14.1,"timestamp":"1111-11-11T11:11:11Z"}
|Sharky775;68926025;2651.000000;0.000000;150.347000;0.018000;0.000000;0.000000;42.300000;28.100000;14.100000;1111-11-11 11:11.11

Wyświetl plik

@ -31,7 +31,8 @@
X(Volume,0x10,0x17) \
X(VolumeFlow,0x38,0x3F) \
X(FlowTemperature,0x58,0x5B) \
X(ReturnTemperature,0x5D,0x5E) \
X(ReturnTemperature,0x5C,0x5F) \
X(TemperatureDifference,0x60,0x63) \
X(ExternalTemperature,0x64,0x67) \
X(HeatCostAllocation,0x6E,0x6E) \
X(Date,0x6C,0x6C) \

Wyświetl plik

@ -1,5 +1,6 @@
/*
Copyright (C) 2021 Vincent Privat
Copyright (C) 2019 Jacek Tomasiak
2021 Vincent Privat
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
@ -27,8 +28,35 @@ std::set<int> diehl_manufacturers = {
MANUFACTURER_SPL
};
// Diehl: is "A field" coded as version/type/serialnumber instead of standard serialnumber/version/type?
DiehlAddressTransformMethod mustTransformDiehlAddress(uchar c_field, int m_field, uchar ci_field, int tpl_cfg)
// Default keys for Izar/PRIOS and Sharky meters
#define PRIOS_DEFAULT_KEY1 "39BC8A10E66D83F8"
#define PRIOS_DEFAULT_KEY2 "51728910E66D83F8"
// Diehl: Is "A field" coded differently from standard?
DiehlAddressTransformMethod mustTransformDiehlAddress(DiehlFrameInterpretation interpretation)
{
switch (interpretation) {
case DiehlFrameInterpretation::PRIOS:
case DiehlFrameInterpretation::PRIOS_SCR:
case DiehlFrameInterpretation::REAL_DATA:
return DiehlAddressTransformMethod::SWAPPING;
case DiehlFrameInterpretation::SAP_PRIOS:
return DiehlAddressTransformMethod::SAP_PRIOS;
case DiehlFrameInterpretation::SAP_PRIOS_STD:
return DiehlAddressTransformMethod::SAP_PRIOS_STANDARD;
case DiehlFrameInterpretation::RESERVED:
case DiehlFrameInterpretation::OMS:
case DiehlFrameInterpretation::NA:
default:
return DiehlAddressTransformMethod::NONE;
}
}
// Diehl: Determines how to interpret frame
DiehlFrameInterpretation detectDiehlFrameInterpretation(uchar c_field, int m_field, uchar ci_field, int tpl_cfg)
{
if (diehl_manufacturers.find(m_field) != diehl_manufacturers.end())
{
@ -36,13 +64,13 @@ DiehlAddressTransformMethod mustTransformDiehlAddress(uchar c_field, int m_field
{
switch (ci_field) {
case 0x71: // Alarm
return DiehlAddressTransformMethod::SWAPPING; // Real Data
return DiehlFrameInterpretation::REAL_DATA;
case 0x7A: // EN 13757-3 Application Layer (short tplh)
if (((tpl_cfg >> 8) & 0x10) == 0x10)
if (((tpl_cfg >> 8) & 0x10) == 0x10) // Bit 12 from MMMMM bits of CFG field
{
return DiehlAddressTransformMethod::SWAPPING; // Real Data
return DiehlFrameInterpretation::REAL_DATA;
}
break; // OMS
return DiehlFrameInterpretation::OMS;
case 0xA0: // Manufacturer specific
case 0xA1: // Manufacturer specific
case 0xA2: // Manufacturer specific
@ -53,15 +81,15 @@ DiehlAddressTransformMethod mustTransformDiehlAddress(uchar c_field, int m_field
case 0xA7: // Manufacturer specific
if (m_field == MANUFACTURER_SAP)
{
return DiehlAddressTransformMethod::SAP_PRIOS; // SAP PRIOS
return DiehlFrameInterpretation::SAP_PRIOS;
}
return DiehlAddressTransformMethod::SWAPPING; // PRIOS
return DiehlFrameInterpretation::PRIOS;
case 0xB0: // Manufacturer specific
if (m_field == MANUFACTURER_SAP)
{
return DiehlAddressTransformMethod::SAP_PRIOS_STANDARD; // SAP PRIOS STD
return DiehlFrameInterpretation::SAP_PRIOS_STD;
}
break; // Reserved
return DiehlFrameInterpretation::RESERVED;
case 0xA8: // Manufacturer specific
case 0xA9: // Manufacturer specific
case 0xAA: // Manufacturer specific
@ -74,21 +102,40 @@ DiehlAddressTransformMethod mustTransformDiehlAddress(uchar c_field, int m_field
case 0xB5: // Manufacturer specific
case 0xB6: // Manufacturer specific
case 0xB7: // Manufacturer specific
break; // Reserved
return DiehlFrameInterpretation::RESERVED;
case 0xB1: // Manufacturer specific
case 0xB2: // Manufacturer specific
case 0xB3: // Manufacturer specific
return DiehlAddressTransformMethod::SWAPPING; // PRIOS SCR
return DiehlFrameInterpretation::PRIOS_SCR;
default:
break; // OMS
return DiehlFrameInterpretation::OMS;
}
}
}
return DiehlAddressTransformMethod::NONE;
return DiehlFrameInterpretation::NA;
}
// Diehl: Determines how to interpret frame
DiehlFrameInterpretation detectDiehlFrameInterpretation(const vector<uchar>& frame)
{
if (frame.size() < 15)
return DiehlFrameInterpretation::NA;
uchar c_field = frame[1];
int m_field = frame[3] <<8 | frame[2];
uchar ci_field = frame[10];
int tpl_cfg = frame[14] <<8 | frame[13];
return detectDiehlFrameInterpretation(c_field, m_field, ci_field, tpl_cfg);
}
// Diehl: Is "A field" coded differently from standard?
DiehlAddressTransformMethod mustTransformDiehlAddress(const vector<uchar>& frame)
{
return mustTransformDiehlAddress(detectDiehlFrameInterpretation(frame));
}
// Diehl: transform "A field" to make it compliant to standard
void transformDiehlAddress(std::vector<uchar>& frame, DiehlAddressTransformMethod transform_method)
void transformDiehlAddress(vector<uchar>& frame, DiehlAddressTransformMethod transform_method)
{
if (transform_method == DiehlAddressTransformMethod::SWAPPING)
{
@ -97,7 +144,7 @@ void transformDiehlAddress(std::vector<uchar>& frame, DiehlAddressTransformMetho
uchar type = frame[5];
for (int i = 4; i < 8; i++)
{
frame[i] = frame[i+1];
frame[i] = frame[i+2];
}
frame[8] = version;
frame[9] = type;
@ -114,3 +161,127 @@ void transformDiehlAddress(std::vector<uchar>& frame, DiehlAddressTransformMetho
}
}
// Diehl: decode LFSR encrypted data used in Izar/PRIOS and Sharky meters
vector<uchar> decodeDiehlLfsr(const vector<uchar> &origin, const vector<uchar> &frame, uint32_t key, DiehlLfsrCheckMethod check_method, uint32_t check_value)
{
// modify seed key with header values
key ^= uint32FromBytes(origin, 2); // manufacturer + address[0-1]
key ^= uint32FromBytes(origin, 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);
if (check_method == DiehlLfsrCheckMethod::HEADER_1_BYTE)
{
// check-byte doesn't match?
if (decoded[0] != 0x4B) {
decoded.clear();
return decoded;
}
}
else if (check_method == DiehlLfsrCheckMethod::CHECKSUM_AND_0XEF)
{
if (i == size - 1) {
uint32_t checksum = 0;
for (int index = 0; index < size; index++) {
checksum += (decoded[index] & 0xFF);
}
if ((checksum & 0xEF) != check_value) {
decoded.clear();
return decoded;
}
}
}
}
return decoded;
}
uint32_t 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 convertKey(const vector<uchar> &bytes)
{
uint32_t key1 = uint32FromBytes(bytes, 0);
uint32_t key2 = uint32FromBytes(bytes, 4);
uint32_t key = key1 ^ key2;
return key;
}
uint32_t convertKey(const char *hex)
{
vector<uchar> bytes;
hex2bin(hex, &bytes);
return convertKey(bytes);
}
// Diehl: initialize support of default keys in a meter
void initializeDiehlDefaultKeySupport(const vector<uchar> &confidentiality_key, vector<uint32_t>& keys)
{
if (!confidentiality_key.empty())
keys.push_back(convertKey(confidentiality_key));
// fallback to default keys if no custom key provided
if (keys.empty())
{
keys.push_back(convertKey(PRIOS_DEFAULT_KEY1));
keys.push_back(convertKey(PRIOS_DEFAULT_KEY2));
}
}
// Diehl: Is payload real data crypted (LFSR)?
bool mustDecryptDiehlRealData(const vector<uchar>& frame)
{
return detectDiehlFrameInterpretation(frame) == DiehlFrameInterpretation::REAL_DATA;
}
// Diehl: decrypt real data payload (LFSR)
bool decryptDielhRealData(Telegram *t, vector<uchar> &frame, vector<uchar>::iterator &pos, const vector<uchar> &confidentiality_key)
{
vector<uint32_t> keys;
initializeDiehlDefaultKeySupport(confidentiality_key, keys);
vector<uchar> decoded_content;
for (auto& key : keys) {
decoded_content = decodeDiehlLfsr(t->original.empty() ? frame : t->original, frame, key, DiehlLfsrCheckMethod::CHECKSUM_AND_0XEF, frame[14] & 0xEF);
if (!decoded_content.empty())
break;
}
if (decoded_content.empty())
{
warning("(diehl) Decoding LFSR real data failed.\n");
return false;
}
debug("(diehl) Decoded LFSR real data: %s\n", bin2hex(decoded_content).c_str());
frame.erase(pos, frame.end());
frame.insert(frame.end(), decoded_content.begin(), decoded_content.end());
return true;
}

Wyświetl plik

@ -1,5 +1,6 @@
/*
Copyright (C) 2021 Vincent Privat
Copyright (C) 2019 Jacek Tomasiak
2021 Vincent Privat
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
@ -19,18 +20,54 @@
#define MANUFACTURER_SPECIFICITIES_H
#include"util.h"
#include"wmbus.h"
enum class DiehlAddressTransformMethod {
NONE,
SWAPPING,
SAP_PRIOS,
SAP_PRIOS_STANDARD
using namespace std;
uint32_t uint32FromBytes(const vector<uchar> &data, int offset, bool reverse = false);
// Diehl: initialize support of default keys in a meter
void initializeDiehlDefaultKeySupport(const vector<uchar> &confidentiality_key, vector<uint32_t>& keys);
// Diehl: check method of LFSR decryption algorithm
enum class DiehlLfsrCheckMethod {
CHECKSUM_AND_0XEF,
HEADER_1_BYTE
};
// Diehl: Is "A field" coded as version/type/serialnumber instead of standard serialnumber/version/type?
DiehlAddressTransformMethod mustTransformDiehlAddress(uchar c_field, int m_field, uchar ci_field, int tpl_cfg);
// Diehl: decode LFSR encrypted data used in Izar/PRIOS and Sharky meters
vector<uchar> decodeDiehlLfsr(const vector<uchar> &origin, const vector<uchar> &frame, uint32_t key, DiehlLfsrCheckMethod check_method, uint32_t check_value);
// Diehl: swap "A field" to make it compliant to standard
void transformDiehlAddress(std::vector<uchar>& frame, DiehlAddressTransformMethod method);
// Diehl: frame interpretation
enum class DiehlFrameInterpretation {
NA, // N/A: not a Diehl frame
REAL_DATA,
OMS,
PRIOS,
SAP_PRIOS,
SAP_PRIOS_STD,
PRIOS_SCR,
RESERVED
};
// Diehl: address transformation method
enum class DiehlAddressTransformMethod {
NONE, // "A field" coded as per standard
SWAPPING, // "A field" coded as version/type/serialnumber instead of standard serialnumber/version/type
SAP_PRIOS, // Version and type not included in telegram. Must be hardcoded to 0 and 7
SAP_PRIOS_STANDARD // ?
};
// Diehl: Is "A field" coded differently from standard?
DiehlAddressTransformMethod mustTransformDiehlAddress(const vector<uchar>& frame);
// Diehl: transform "A field" to make it compliant to standard
void transformDiehlAddress(vector<uchar>& frame, DiehlAddressTransformMethod method);
// Diehl: Is payload real data crypted (LFSR)?
bool mustDecryptDiehlRealData(const vector<uchar>& frame);
// Diehl: decrypt real data payload (LFSR)
bool decryptDielhRealData(Telegram *t, vector<uchar> &frame, vector<uchar>::iterator &pos, const vector<uchar> &meterkey);
#endif

Wyświetl plik

@ -1,6 +1,7 @@
/*
Copyright (C) 2019 Jacek Tomasiak
2020 Fredrik Öhrström
2021 Vincent Privat
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
@ -20,15 +21,11 @@
#include"meters_common_implementation.h"
#include"wmbus.h"
#include"wmbus_utils.h"
#include"manufacturer_specificities.h"
#include<algorithm>
#include<stdbool.h>
using namespace std;
#define PRIOS_DEFAULT_KEY1 "39BC8A10E66D83F8"
#define PRIOS_DEFAULT_KEY2 "51728910E66D83F8"
/** Contains all the booleans required to store the alarms of a PRIOS device. */
typedef struct _izar_alarms {
bool general_alarm;
@ -60,9 +57,6 @@ struct MeterIzar : public virtual WaterMeter, public virtual MeterCommonImplemen
private:
void processContent(Telegram *t);
uint32_t convertKey(const char *hex);
uint32_t convertKey(const vector<uchar> &bytes);
uint32_t uint32FromBytes(const vector<uchar> &data, int offset, bool reverse = false);
vector<uchar> decodePrios(const vector<uchar> &origin, const vector<uchar> &payload, uint32_t key);
double remaining_battery_life;
@ -85,17 +79,7 @@ shared_ptr<WaterMeter> createIzar(MeterInfo &mi)
MeterIzar::MeterIzar(MeterInfo &mi) :
MeterCommonImplementation(mi, MeterType::IZAR)
{
MeterKeys *mk = meterKeys();
if (!mk->confidentiality_key.empty())
keys.push_back(convertKey(mk->confidentiality_key));
// fallback to default keys if no custom key provided
if (keys.empty())
{
keys.push_back(convertKey(PRIOS_DEFAULT_KEY1));
keys.push_back(convertKey(PRIOS_DEFAULT_KEY2));
}
initializeDiehlDefaultKeySupport(meterKeys()->confidentiality_key, keys);
addLinkMode(LinkMode::T1);
addPrint("total", Quantity::Volume,
@ -214,35 +198,6 @@ string MeterIzar::previousAlarmsText()
return "no_alarm";
}
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);
return convertKey(bytes);
}
uint32_t MeterIzar::convertKey(const vector<uchar> &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)
{
vector<uchar> frame;
@ -297,31 +252,5 @@ void MeterIzar::processContent(Telegram *t)
vector<uchar> MeterIzar::decodePrios(const vector<uchar> &origin, const vector<uchar> &frame, uint32_t key)
{
// modify seed key with header values
key ^= uint32FromBytes(origin, 2); // manufacturer + address[0-1]
key ^= uint32FromBytes(origin, 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;
return decodeDiehlLfsr(origin, frame, key, DiehlLfsrCheckMethod::HEADER_1_BYTE, 0x4B);
}

255
src/meter_sharky.cc 100644
Wyświetl plik

@ -0,0 +1,255 @@
/*
Copyright (C) 2021 Vincent Privat
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"dvparser.h"
#include"wmbus.h"
#include"wmbus_utils.h"
struct MeterSharky : public virtual HeatMeter, public virtual MeterCommonImplementation {
MeterSharky(MeterInfo &mi);
double totalEnergyConsumption(Unit u);
double totalEnergyConsumptionTariff1(Unit u);
double totalVolume(Unit u);
double totalVolumeTariff2(Unit u);
double volumeFlow(Unit u);
double power(Unit u);
double flowTemperature(Unit u);
double returnTemperature(Unit u);
double temperatureDifference(Unit u);
private:
void processContent(Telegram *t);
double total_energy_kwh_ {};
double total_energy_tariff1_kwh_ {};
double total_volume_m3_ {};
double total_volume_tariff2_m3_ {};
double volume_flow_m3h_ {};
double power_w_ {};
double flow_temperature_c_ {};
double return_temperature_c_ {};
double temperature_difference_c_ {};
};
MeterSharky::MeterSharky(MeterInfo &mi) :
MeterCommonImplementation(mi, MeterType::SHARKY)
{
addLinkMode(LinkMode::T1);
addPrint("total_energy_consumption", Quantity::Energy,
[&](Unit u){ return totalEnergyConsumption(u); },
"The total energy consumption recorded by this meter.",
true, true);
addPrint("total_energy_consumption_tariff1", Quantity::Energy,
[&](Unit u){ return totalEnergyConsumptionTariff1(u); },
"The total energy consumption recorded by this meter on tariff 1.",
true, true);
addPrint("total_volume", Quantity::Volume,
[&](Unit u){ return totalVolume(u); },
"The total volume recorded by this meter.",
true, true);
addPrint("total_volume", Quantity::Volume,
[&](Unit u){ return totalVolumeTariff2(u); },
"The total volume recorded by this meter on tariff 2.",
true, true);
addPrint("volume_flow", Quantity::Flow,
[&](Unit u){ return volumeFlow(u); },
"The current flow.",
true, true);
addPrint("power", Quantity::Power,
[&](Unit u){ return power(u); },
"The power.",
true, true);
addPrint("flow_temperature", Quantity::Temperature,
[&](Unit u){ return flowTemperature(u); },
"The flow temperature.",
true, true);
addPrint("return_temperature", Quantity::Temperature,
[&](Unit u){ return returnTemperature(u); },
"The return temperature.",
true, true);
addPrint("temperature_difference", Quantity::Temperature,
[&](Unit u){ return temperatureDifference(u); },
"The temperature difference.",
true, true);
}
shared_ptr<HeatMeter> createSharky(MeterInfo &mi) {
return shared_ptr<HeatMeter>(new MeterSharky(mi));
}
double MeterSharky::totalEnergyConsumption(Unit u)
{
assertQuantity(u, Quantity::Energy);
return convert(total_energy_kwh_, Unit::KWH, u);
}
double MeterSharky::totalEnergyConsumptionTariff1(Unit u)
{
assertQuantity(u, Quantity::Energy);
return convert(total_energy_tariff1_kwh_, Unit::KWH, u);
}
double MeterSharky::totalVolume(Unit u)
{
assertQuantity(u, Quantity::Volume);
return convert(total_volume_m3_, Unit::M3, u);
}
double MeterSharky::totalVolumeTariff2(Unit u)
{
assertQuantity(u, Quantity::Volume);
return convert(total_volume_tariff2_m3_, Unit::M3, u);
}
double MeterSharky::volumeFlow(Unit u)
{
assertQuantity(u, Quantity::Flow);
return convert(volume_flow_m3h_, Unit::M3H, u);
}
double MeterSharky::power(Unit u)
{
assertQuantity(u, Quantity::Power);
return convert(power_w_, Unit::KW, u);
}
double MeterSharky::flowTemperature(Unit u)
{
assertQuantity(u, Quantity::Temperature);
return convert(flow_temperature_c_, Unit::C, u);
}
double MeterSharky::returnTemperature(Unit u)
{
assertQuantity(u, Quantity::Temperature);
return convert(return_temperature_c_, Unit::C, u);
}
double MeterSharky::temperatureDifference(Unit u)
{
assertQuantity(u, Quantity::Temperature);
return convert(temperature_difference_c_, Unit::C, u);
}
void MeterSharky::processContent(Telegram *t)
{
/*
(wmbus) 0f: 0C dif (8 digit BCD Instantaneous value)
(wmbus) 10: 06 vif (Energy kWh)
(wmbus) 11: 51260000
(wmbus) 15: 8C dif (8 digit BCD Instantaneous value)
(wmbus) 16: 10 dife (subunit=0 tariff=1 storagenr=0)
(wmbus) 17: 06 vif (Energy kWh)
(wmbus) 18: 00000000
(wmbus) 1c: 0C dif (8 digit BCD Instantaneous value)
(wmbus) 1d: 13 vif (Volume l)
(wmbus) 1e: 47031500
(wmbus) 22: 8C dif (8 digit BCD Instantaneous value)
(wmbus) 23: 20 dife (subunit=0 tariff=2 storagenr=0)
(wmbus) 24: 13 vif (Volume l)
(wmbus) 25: 18000000
(wmbus) 29: 8C dif (8 digit BCD Instantaneous value)
(wmbus) 2a: 40 dife (subunit=1 tariff=0 storagenr=0)
(wmbus) 2b: 13 vif (Volume l)
(wmbus) 2c: 00000000
(wmbus) 30: 8C dif (8 digit BCD Instantaneous value)
(wmbus) 31: 80 dife (subunit=0 tariff=0 storagenr=0)
(wmbus) 32: 40 dife (subunit=2 tariff=0 storagenr=0)
(wmbus) 33: 13 vif (Volume l)
(wmbus) 34: 00000000
(wmbus) 38: 02 dif (16 Bit Integer/Binary Instantaneous value)
(wmbus) 39: FD vif (Second extension FD of VIF-codes)
(wmbus) 3a: 17 vife (Error flags (binary))
(wmbus) 3b: 0000
(wmbus) 3d: 0B dif (6 digit BCD Instantaneous value)
(wmbus) 3e: 3B vif (Volume flow l/h)
(wmbus) 3f: 000000
(wmbus) 42: 0C dif (8 digit BCD Instantaneous value)
(wmbus) 43: 2B vif (Power W)
(wmbus) 44: 00000000
(wmbus) 48: 0A dif (4 digit BCD Instantaneous value)
(wmbus) 49: 5A vif (Flow temperature 10¹ °C)
(wmbus) 4a: 2304
(wmbus) 4c: 0A dif (4 digit BCD Instantaneous value)
(wmbus) 4d: 5E vif (Return temperature 10¹ °C)
(wmbus) 4e: 8102
(wmbus) 50: 0A dif (4 digit BCD Instantaneous value)
(wmbus) 51: 62 vif (Temperature difference 10¹ K)
(wmbus) 52: 4101
*/
int offset;
string key;
if (findKey(MeasurementType::Instantaneous, ValueInformation::EnergyWh, 0, 0, &key, &t->values)) {
extractDVdouble(&t->values, key, &offset, &total_energy_kwh_);
t->addMoreExplanation(offset, " total energy consumption (%f kWh)", total_energy_kwh_);
}
if (findKey(MeasurementType::Instantaneous, ValueInformation::EnergyWh, 0, 1, &key, &t->values)) {
extractDVdouble(&t->values, key, &offset, &total_energy_tariff1_kwh_);
t->addMoreExplanation(offset, " total energy tariff 1 (%f kwh)", total_energy_tariff1_kwh_);
}
if (findKey(MeasurementType::Instantaneous, ValueInformation::Volume, 0, 0, &key, &t->values)) {
extractDVdouble(&t->values, key, &offset, &total_volume_m3_);
t->addMoreExplanation(offset, " total volume (%f ㎥)", total_volume_m3_);
}
if (findKey(MeasurementType::Instantaneous, ValueInformation::Volume, 0, 2, &key, &t->values)) {
extractDVdouble(&t->values, key, &offset, &total_volume_tariff2_m3_);
t->addMoreExplanation(offset, " total volume tariff 2 (%f ㎥)", total_volume_tariff2_m3_);
}
if (findKey(MeasurementType::Instantaneous, ValueInformation::VolumeFlow, 0, 0, &key, &t->values)) {
extractDVdouble(&t->values, key, &offset, &volume_flow_m3h_);
t->addMoreExplanation(offset, " volume flow (%f ㎥/h)", volume_flow_m3h_);
}
if (findKey(MeasurementType::Instantaneous, ValueInformation::PowerW, 0, 0, &key, &t->values)) {
extractDVdouble(&t->values, key, &offset, &power_w_);
t->addMoreExplanation(offset, " power (%f W)", power_w_);
}
if (findKey(MeasurementType::Instantaneous, ValueInformation::FlowTemperature, 0, 0, &key, &t->values)) {
extractDVdouble(&t->values, key, &offset, &flow_temperature_c_);
t->addMoreExplanation(offset, " flow temperature (%f °C)", flow_temperature_c_);
}
if (findKey(MeasurementType::Instantaneous, ValueInformation::ReturnTemperature, 0, 0, &key, &t->values)) {
extractDVdouble(&t->values, key, &offset, &return_temperature_c_);
t->addMoreExplanation(offset, " return temperature (%f °C)", return_temperature_c_);
}
if (findKey(MeasurementType::Instantaneous, ValueInformation::TemperatureDifference, 0, 0, &key, &t->values)) {
extractDVdouble(&t->values, key, &offset, &temperature_difference_c_);
t->addMoreExplanation(offset, " temperature difference (%f °C)", temperature_difference_c_);
}
}

Wyświetl plik

@ -67,6 +67,7 @@
X(tsd2, T1_bit, SmokeDetector, TSD2, TSD2) \
X(q400, T1_bit, WaterMeter, Q400, Q400) \
X(qcaloric, C1_bit, HeatCostAllocationMeter, QCALORIC, QCaloric) \
X(sharky, T1_bit, HeatMeter, SHARKY, Sharky) \
X(sontex868, T1_bit, HeatCostAllocationMeter, SONTEX868, Sontex868) \
X(supercom587,T1_bit, WaterMeter, SUPERCOM587, Supercom587) \
X(ultrimis, T1_bit, WaterMeter, ULTRIMIS, Ultrimis) \
@ -150,6 +151,7 @@
X(TSD2, MANUFACTURER_TCH, 0xf0, 0x76) \
X(Q400, MANUFACTURER_AXI, 0x07, 0x10) \
X(QCALORIC, MANUFACTURER_QDS, 0x08, 0x35) \
X(SHARKY, MANUFACTURER_HYD, 0x04, 0x20) \
X(SUPERCOM587,MANUFACTURER_SON, 0x06, 0x3c) \
X(SUPERCOM587,MANUFACTURER_SON, 0x07, 0x3c) \
X(SONTEX868, MANUFACTURER_SON, 0x08, 0x16) \

Wyświetl plik

@ -1430,6 +1430,16 @@ bool Telegram::potentiallyDecrypt(vector<uchar>::iterator &pos)
}
addExplanationAndIncrementPos(pos, 2, "%02x%02x decrypt check bytes", *(pos+0), *(pos+1));
}
else if (tpl_sec_mode == TPLSecurityMode::SPECIFIC_16_31)
{
if (mustDecryptDiehlRealData(frame))
{
if (!meter_keys) return false;
bool ok = decryptDielhRealData(this, frame, pos, meter_keys->confidentiality_key);
if (!ok) return false;
// Now the frame from pos and onwards has been decrypted.
}
}
return true;
}
@ -1582,18 +1592,11 @@ bool Telegram::parseTPL(vector<uchar>::iterator &pos)
void Telegram::preProcess()
{
if (frame.size() >= 15)
DiehlAddressTransformMethod diehl_method = mustTransformDiehlAddress(frame);
if (diehl_method != DiehlAddressTransformMethod::NONE)
{
uchar c_field = frame[1];
int m_field = frame[3] <<8 | frame[2];
uchar ci_field = frame[10];
int tpl_cfg = frame[14] <<8 | frame[13];
DiehlAddressTransformMethod diehl_method = mustTransformDiehlAddress(c_field, m_field, ci_field, tpl_cfg);
if (diehl_method != DiehlAddressTransformMethod::NONE)
{
original = vector<uchar>(frame.begin(), frame.begin() + 10);
transformDiehlAddress(frame, diehl_method);
}
original = vector<uchar>(frame.begin(), frame.begin() + 10);
transformDiehlAddress(frame, diehl_method);
}
}

Wyświetl plik

@ -5,7 +5,7 @@ PROG="$1"
mkdir -p testoutput
TEST=testoutput
TESTNAME="Test Izas meters"
TESTNAME="Test Izars meters"
TESTRESULT="ERROR"
LOGFILE=$TEST/logfile
LOGFILE_EXPECTED=$TEST/logfile_expected
@ -14,8 +14,8 @@ mkdir -p $TEST
rm -f $LOGFILE
METERS="IzarWater izar 21242472 NOKEY
IzarWater2 izar 23662907 NOKEY
IzarWater3 izar 48197907 NOKEY
IzarWater2 izar 66236629 NOKEY
IzarWater3 izar 20481979 NOKEY
IzarWater4 izar 2124589c NOKEY"
cat simulations/simulation_izars.txt | grep '^{' > $TEST/test_expected.txt
@ -43,7 +43,7 @@ then
exit 1
fi
TESTNAME="Test listen to all izas"
TESTNAME="Test listen to all izars"
TESTRESULT="ERROR"
cat > $LOGFILE_EXPECTED <<EOF
@ -53,12 +53,12 @@ Received telegram from: 21242472
type: Water meter (0x07)
ver: 0x00
driver: izar
Received telegram from: 23662907
Received telegram from: 66236629
manufacturer: (DME) DIEHL Metering, Germany (0x11a5)
type: Water meter (0x07)
ver: 0x78
driver: izar
Received telegram from: 48197907
Received telegram from: 20481979
manufacturer: (DME) DIEHL Metering, Germany (0x11a5)
type: Water meter (0x07)
ver: 0x78

Wyświetl plik

@ -241,6 +241,11 @@ Received telegram from: 20100117
type: Electricity meter (0x02)
ver: 0x01
driver: gransystems
Received telegram from: 68926025
manufacturer: (HYD) Hydrometer (0x2324)
type: Heat meter (0x04)
ver: 0x20
driver: sharky
EOF
RES=$($PROG --logfile=$LOGFILE --t1 simulations/simulation_t1.txt 2>&1)

Wyświetl plik

@ -48,6 +48,7 @@ METERS="MyWarmWater supercom587 12345678 NOKEY
Voda ev200 99993030 NOKEY
Vodda emerlin868 95949392 NOKEY
Smokey tsd2 91633569 NOKEY
Sharky775 sharky 68926025 NOKEY
Heating compact5 62626262 NOKEY"