kopia lustrzana https://github.com/weetmuts/wmbusmeters
Initial support for electricity meters.
rodzic
e9cb0322f0
commit
6cc37271dc
1
Makefile
1
Makefile
|
@ -39,6 +39,7 @@ METERS_OBJS:=\
|
|||
$(BUILD)/meters.o \
|
||||
$(BUILD)/meter_multical21.o \
|
||||
$(BUILD)/meter_multical302.o \
|
||||
$(BUILD)/meter_omnipower.o \
|
||||
$(BUILD)/printer.o \
|
||||
$(BUILD)/serial.o \
|
||||
$(BUILD)/util.o \
|
||||
|
|
30
README.md
30
README.md
|
@ -26,14 +26,32 @@ Add --verbose for detailed debug information.
|
|||
Specifying auto as the device will automatically look for usb
|
||||
wmbus dongles on /dev/im871a and /dev/amb8465.
|
||||
|
||||
Two meter types are supported: multical21 and multical302 (multical302 is still work in progress).
|
||||
The meter type: multical21 (a water meter) is supported.
|
||||
The meter types: multical302 (heat) and omnipower (electricity)
|
||||
are work in progress.
|
||||
```
|
||||
|
||||
Currently the meters are hardcoded for the European default setting that specifies what extra data
|
||||
is sent in the telegrams. If someone has a non-default meter that sends other extra data, then this
|
||||
will show up as a warning when a long telegram is received (but not in the short telegrams, where wrong
|
||||
values might be printed instead!).
|
||||
If this happens to someone, then we need to implement a way to pass the meter configuration as a parameter.
|
||||
Currently the meters are hardcoded for the European default setting
|
||||
that specifies what extra data is sent in the telegrams. If someone
|
||||
has a non-default meter that sends other extra data, then this will
|
||||
show up as a warning when a long telegram is received (but not in the
|
||||
short telegrams, where wrong values might be printed instead!). If
|
||||
this happens to someone, then we need to implement a way to pass the
|
||||
meter configuration as a parameter.
|
||||
|
||||
Actually, the mbus (and consequently the wmbus) protocol is a standard
|
||||
that is self-describing. Thus in reality it should not be necessary
|
||||
to supply exactly which kind of meter we expect for a given id. This
|
||||
should be possible to figure out when we receive the first telegram.
|
||||
|
||||
Thus, strictly speaking, it should not be necessary to specify the
|
||||
exact meter type. A more generic meter type might be just "water",
|
||||
"heat" or electricity. But for the moment, the separation of meter
|
||||
types will remain in the code. Thus even though the meter type right
|
||||
now is named multical302, the other heat meters (multical-402 and
|
||||
multical-602) might be compatible as well. The same is true for the
|
||||
omnipower meter type, which might include the electricity meters
|
||||
Kamstrup-162 Kamstrup-382, Kamstrup-351 etc).
|
||||
|
||||
No meter quadruplets means listen for telegram traffic and print any id heard.
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
// SOFTWARE.
|
||||
|
||||
#include"cmdline.h"
|
||||
#include"meters.h"
|
||||
#include"util.h"
|
||||
|
||||
using namespace std;
|
||||
|
@ -142,7 +143,9 @@ CommandLine *parseCommandLine(int argc, char **argv) {
|
|||
char *id = argv[m*4+i+2];
|
||||
char *key = argv[m*4+i+3];
|
||||
|
||||
if (!isValidType(type)) error("Not a valid meter type \"%s\"\n", type);
|
||||
MeterType mt = toMeterType(type);
|
||||
|
||||
if (mt == UNKNOWN_METER) error("Not a valid meter type \"%s\"\n", type);
|
||||
if (!isValidId(id)) error("Not a valid meter id \"%s\"\n", id);
|
||||
if (!isValidKey(key)) error("Not a valid meter key \"%s\"\n", key);
|
||||
c->meters.push_back(MeterInfo(name,type,id,key));
|
||||
|
|
8
main.cc
8
main.cc
|
@ -51,7 +51,9 @@ int main(int argc, char **argv)
|
|||
" or 10m for ten minutes or 5s for five seconds.\n");
|
||||
printf("Specifying auto as the device will automatically look for usb\n");
|
||||
printf("wmbus dongles on /dev/im871a and /dev/amb8465\n\n");
|
||||
printf("Two meter types are supported: multical21 and multical302 (work in progress).\n\n");
|
||||
printf("The meter type: multical21 (a water meter) is supported.\n"
|
||||
"The meter types: multical302 (heat) and omnipower (electricity)\n"
|
||||
"are work in progress.\n\n");
|
||||
exit(0);
|
||||
}
|
||||
// We want the data visible in the log file asap!
|
||||
|
@ -116,6 +118,10 @@ int main(int argc, char **argv)
|
|||
m.meter = createMultical302(wmbus, m.name, m.id, m.key);
|
||||
verbose("(multical302) configured \"%s\" \"multical302\" \"%s\" \"%s\"\n", m.name, m.id, m.key);
|
||||
break;
|
||||
case OMNIPOWER_METER:
|
||||
m.meter = createOmnipower(wmbus, m.name, m.id, m.key);
|
||||
verbose("(omnipower) configured \"%s\" \"omnipower\" \"%s\" \"%s\"\n", m.name, m.id, m.key);
|
||||
break;
|
||||
case UNKNOWN_METER:
|
||||
error("No such meter type \"%s\"\n", m.type);
|
||||
break;
|
||||
|
|
|
@ -185,7 +185,8 @@ void MeterMultical21::processContent(Telegram *t) {
|
|||
|
||||
int crc0 = t->content[0];
|
||||
int crc1 = t->content[1];
|
||||
t->addExplanation(full_content, 2, "%02x%02x plcrc", crc0, crc1);
|
||||
t->addExplanation(full_content, 2, "%02x%02x payload crc %02x%02x", crc0, crc1);
|
||||
|
||||
int frame_type = t->content[2];
|
||||
t->addExplanation(full_content, 1, "%02x frame type (%s)", frame_type, frameTypeKamstrupC1(frame_type).c_str());
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
struct MeterMultical302 : public virtual HeatMeter, public virtual MeterCommonImplementation {
|
||||
MeterMultical302(WMBus *bus, const char *name, const char *id, const char *key);
|
||||
|
||||
float totalPowerConsumption();
|
||||
float totalEnergyConsumption();
|
||||
float currentPowerConsumption();
|
||||
float totalVolume();
|
||||
|
||||
|
@ -45,7 +45,7 @@ private:
|
|||
void handleTelegram(Telegram *t);
|
||||
void processContent(Telegram *t);
|
||||
|
||||
float total_power_ {};
|
||||
float total_energy_ {};
|
||||
float current_power_ {};
|
||||
float total_volume_ {};
|
||||
};
|
||||
|
@ -56,9 +56,9 @@ MeterMultical302::MeterMultical302(WMBus *bus, const char *name, const char *id,
|
|||
MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*));
|
||||
}
|
||||
|
||||
float MeterMultical302::totalPowerConsumption()
|
||||
float MeterMultical302::totalEnergyConsumption()
|
||||
{
|
||||
return total_power_;
|
||||
return total_energy_;
|
||||
}
|
||||
|
||||
float MeterMultical302::currentPowerConsumption()
|
||||
|
@ -117,7 +117,7 @@ void MeterMultical302::processContent(Telegram *t) {
|
|||
|
||||
int crc0 = t->content[0];
|
||||
int crc1 = t->content[1];
|
||||
t->addExplanation(full_content, 2, "%02x%02x plcrc", crc0, crc1);
|
||||
t->addExplanation(full_content, 2, "%02x%02x payload crc", crc0, crc1);
|
||||
int frame_type = t->content[2];
|
||||
t->addExplanation(full_content, 1, "%02x frame type (%s)", frame_type, frameTypeKamstrupC1(frame_type).c_str());
|
||||
|
||||
|
@ -136,10 +136,10 @@ void MeterMultical302::processContent(Telegram *t) {
|
|||
|
||||
t->addExplanation(full_content, 4, "%02x%02x%02x unknown", t->content[10], t->content[11], t->content[12]);
|
||||
|
||||
int total_power_raw = rec1val2*256*256 + rec1val1*256 + rec1val0;
|
||||
total_power_ = total_power_raw;
|
||||
int total_energy_raw = rec1val2*256*256 + rec1val1*256 + rec1val0;
|
||||
total_energy_ = total_energy_raw;
|
||||
t->addExplanation(full_content, 3, "%02x%02x%02x total power (%d)",
|
||||
rec1val0, rec1val1, rec1val2, total_power_raw);
|
||||
rec1val0, rec1val1, rec1val2, total_energy_raw);
|
||||
|
||||
int rec2val0 = t->content[13];
|
||||
int rec2val1 = t->content[14];
|
||||
|
@ -185,7 +185,7 @@ void MeterMultical302::printMeterHumanReadable(FILE *output)
|
|||
fprintf(output, "%s\t%s\t% 3.3f kwh\t% 3.3f m3\t% 3.3f kwh\t%s\n",
|
||||
name().c_str(),
|
||||
id().c_str(),
|
||||
totalPowerConsumption(),
|
||||
totalEnergyConsumption(),
|
||||
totalVolume(),
|
||||
currentPowerConsumption(),
|
||||
datetimeOfUpdateHumanReadable().c_str());
|
||||
|
@ -196,7 +196,7 @@ void MeterMultical302::printMeterFields(FILE *output, char separator)
|
|||
fprintf(output, "%s%c%s%c%3.3f%c%3.3f%c%3.3f%c%s\n",
|
||||
name().c_str(), separator,
|
||||
id().c_str(), separator,
|
||||
totalPowerConsumption(), separator,
|
||||
totalEnergyConsumption(), separator,
|
||||
totalVolume(), separator,
|
||||
currentPowerConsumption(), separator,
|
||||
datetimeOfUpdateRobot().c_str());
|
||||
|
@ -218,7 +218,7 @@ void MeterMultical302::printMeterJSON(FILE *output)
|
|||
"}\n",
|
||||
name().c_str(),
|
||||
id().c_str(),
|
||||
totalPowerConsumption(),
|
||||
totalEnergyConsumption(),
|
||||
totalVolume(),
|
||||
currentPowerConsumption(),
|
||||
datetimeOfUpdateRobot().c_str());
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
// Copyright (c) 2018 Fredrik Öhrström
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#include"meters.h"
|
||||
#include"meters_common_implementation.h"
|
||||
#include"wmbus.h"
|
||||
#include"wmbus_utils.h"
|
||||
#include"util.h"
|
||||
|
||||
#include<memory.h>
|
||||
#include<stdio.h>
|
||||
#include<string>
|
||||
#include<time.h>
|
||||
#include<vector>
|
||||
|
||||
struct MeterOmnipower : public virtual ElectricityMeter, public virtual MeterCommonImplementation {
|
||||
MeterOmnipower(WMBus *bus, const char *name, const char *id, const char *key);
|
||||
|
||||
float totalEnergyConsumption();
|
||||
float currentPowerConsumption();
|
||||
|
||||
void printMeterHumanReadable(FILE *output);
|
||||
void printMeterFields(FILE *output, char separator);
|
||||
void printMeterJSON(FILE *output);
|
||||
|
||||
private:
|
||||
void handleTelegram(Telegram *t);
|
||||
void processContent(Telegram *t);
|
||||
|
||||
float total_energy_ {};
|
||||
float current_power_ {};
|
||||
};
|
||||
|
||||
MeterOmnipower::MeterOmnipower(WMBus *bus, const char *name, const char *id, const char *key) :
|
||||
MeterCommonImplementation(bus, name, id, key, OMNIPOWER_METER, MANUFACTURER_KAM, 0x04)
|
||||
{
|
||||
MeterCommonImplementation::bus()->onTelegram(calll(this,handleTelegram,Telegram*));
|
||||
}
|
||||
|
||||
float MeterOmnipower::totalEnergyConsumption()
|
||||
{
|
||||
return total_energy_;
|
||||
}
|
||||
|
||||
float MeterOmnipower::currentPowerConsumption()
|
||||
{
|
||||
return current_power_;
|
||||
}
|
||||
|
||||
void MeterOmnipower::handleTelegram(Telegram *t) {
|
||||
|
||||
if (!isTelegramForMe(t)) {
|
||||
// This telegram is not intended for this meter.
|
||||
return;
|
||||
}
|
||||
|
||||
verbose("(omnipower) %s %02x%02x%02x%02x ",
|
||||
name().c_str(),
|
||||
t->a_field_address[0], t->a_field_address[1], t->a_field_address[2],
|
||||
t->a_field_address[3]);
|
||||
|
||||
if (t->a_field_device_type != 0x02) {
|
||||
warning("(omnipower) expected telegram for electricity media, but got \"%s\"!\n",
|
||||
mediaType(t->m_field, t->a_field_device_type).c_str());
|
||||
}
|
||||
|
||||
if (t->m_field != manufacturer() ||
|
||||
t->a_field_version != 0x01) {
|
||||
warning("(omnipower) expected telegram from KAM meter with version 0x01, but got \"%s\" version 0x2x !\n",
|
||||
manufacturerFlag(t->m_field).c_str(), t->a_field_version);
|
||||
}
|
||||
|
||||
if (useAes()) {
|
||||
vector<uchar> aeskey = key();
|
||||
// Proper decryption not yet implemented!
|
||||
decryptKamstrupC1(t, aeskey);
|
||||
} else {
|
||||
t->content = t->payload;
|
||||
}
|
||||
logTelegram("(omnipower) log", t->parsed, t->content);
|
||||
int content_start = t->parsed.size();
|
||||
processContent(t);
|
||||
if (isDebugEnabled()) {
|
||||
t->explainParse("(omnipower)", content_start);
|
||||
}
|
||||
triggerUpdate(t);
|
||||
}
|
||||
|
||||
void MeterOmnipower::processContent(Telegram *t) {
|
||||
vector<uchar> full_content;
|
||||
full_content.insert(full_content.end(), t->parsed.begin(), t->parsed.end());
|
||||
full_content.insert(full_content.end(), t->content.begin(), t->content.end());
|
||||
|
||||
int rec1dif = t->content[0];
|
||||
t->addExplanation(full_content, 1, "%02x dif (%s)", rec1dif, difType(rec1dif).c_str());
|
||||
int rec1vif = t->content[1];
|
||||
t->addExplanation(full_content, 1, "%02x vif (%s)", rec1vif, vifType(rec1vif).c_str());
|
||||
int rec1vife = t->content[2];
|
||||
t->addExplanation(full_content, 1, "%02x vife (%s)", rec1vife, vifeType(rec1vif, rec1vife).c_str());
|
||||
|
||||
int rec1val0 = t->content[3];
|
||||
int rec1val1 = t->content[4];
|
||||
int rec1val2 = t->content[5];
|
||||
int rec1val3 = t->content[6];
|
||||
|
||||
int total_energy_raw = rec1val3*256*256*256 + rec1val2*256*256 + rec1val1*256 + rec1val0;
|
||||
total_energy_ = ((float)total_energy_raw)/1000.0;
|
||||
t->addExplanation(full_content, 4, "%02x%02x%02x%02x total power (%d)",
|
||||
rec1val0, rec1val1, rec1val2, rec1val3, total_energy_raw);
|
||||
}
|
||||
|
||||
ElectricityMeter *createOmnipower(WMBus *bus, const char *name, const char *id, const char *key) {
|
||||
return new MeterOmnipower(bus,name,id,key);
|
||||
}
|
||||
|
||||
void MeterOmnipower::printMeterHumanReadable(FILE *output)
|
||||
{
|
||||
fprintf(output, "%s\t%s\t% 3.3f kwh\t% 3.3f kwh\t%s\n",
|
||||
name().c_str(),
|
||||
id().c_str(),
|
||||
totalEnergyConsumption(),
|
||||
currentPowerConsumption(),
|
||||
datetimeOfUpdateHumanReadable().c_str());
|
||||
}
|
||||
|
||||
void MeterOmnipower::printMeterFields(FILE *output, char separator)
|
||||
{
|
||||
fprintf(output, "%s%c%s%c%3.3f%c%3.3f%c%s\n",
|
||||
name().c_str(), separator,
|
||||
id().c_str(), separator,
|
||||
totalEnergyConsumption(), separator,
|
||||
currentPowerConsumption(), separator,
|
||||
datetimeOfUpdateRobot().c_str());
|
||||
}
|
||||
|
||||
#define Q(x,y) "\""#x"\":"#y","
|
||||
#define QS(x,y) "\""#x"\":\""#y"\","
|
||||
#define QSE(x,y) "\""#x"\":\""#y"\""
|
||||
|
||||
void MeterOmnipower::printMeterJSON(FILE *output)
|
||||
{
|
||||
fprintf(output, "{media:\"electricity\",meter:\"omnipower\","
|
||||
QS(name,%s)
|
||||
QS(id,%s)
|
||||
Q(total_kwh,%.3f)
|
||||
QS(current_kw,%.3f)
|
||||
QSE(timestamp,%s)
|
||||
"}\n",
|
||||
name().c_str(),
|
||||
id().c_str(),
|
||||
totalEnergyConsumption(),
|
||||
currentPowerConsumption(),
|
||||
datetimeOfUpdateRobot().c_str());
|
||||
}
|
|
@ -97,6 +97,10 @@ MeterType toMeterType(const char *type)
|
|||
{
|
||||
if (!strcmp(type, "multical21")) return MULTICAL21_METER;
|
||||
if (!strcmp(type, "multical302")) return MULTICAL302_METER;
|
||||
if (!strcmp(type, "omnipower")) return OMNIPOWER_METER;
|
||||
if (!strcmp(type, "water")) return MULTICAL21_METER;
|
||||
if (!strcmp(type, "heat")) return MULTICAL302_METER;
|
||||
if (!strcmp(type, "electricity")) return OMNIPOWER_METER;
|
||||
return UNKNOWN_METER;
|
||||
}
|
||||
|
||||
|
|
12
meters.h
12
meters.h
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2017 Fredrik Öhrström
|
||||
// Copyright (c) 2018 Fredrik Öhrström
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -27,7 +27,7 @@
|
|||
#include<string>
|
||||
#include<vector>
|
||||
|
||||
#define LIST_OF_METERS X(MULTICAL21_METER)X(MULTICAL302_METER)X(UNKNOWN_METER)
|
||||
#define LIST_OF_METERS X(MULTICAL21_METER)X(MULTICAL302_METER)X(OMNIPOWER_METER)X(UNKNOWN_METER)
|
||||
|
||||
enum MeterType {
|
||||
#define X(name) name,
|
||||
|
@ -79,14 +79,20 @@ struct WaterMeter : public virtual Meter {
|
|||
};
|
||||
|
||||
struct HeatMeter : public virtual Meter {
|
||||
virtual float totalPowerConsumption() = 0; // kwh
|
||||
virtual float totalEnergyConsumption() = 0; // kwh
|
||||
virtual float currentPowerConsumption() = 0; // kw
|
||||
virtual float totalVolume() = 0; // m3
|
||||
};
|
||||
|
||||
struct ElectricityMeter : public virtual Meter {
|
||||
virtual float totalEnergyConsumption() = 0; // kwh
|
||||
virtual float currentPowerConsumption() = 0; // kw
|
||||
};
|
||||
|
||||
|
||||
MeterType toMeterType(const char *type);
|
||||
WaterMeter *createMultical21(WMBus *bus, const char *name, const char *id, const char *key);
|
||||
HeatMeter *createMultical302(WMBus *bus, const char *name, const char *id, const char *key);
|
||||
ElectricityMeter *createOmnipower(WMBus *bus, const char *name, const char *id, const char *key);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -18,3 +18,8 @@ telegram=|25442D2C785634121b048D2093E13CBA20|0000790000000000000000000000000000|
|
|||
# full telegram
|
||||
|
||||
# Test Multical302 T1 telegrams
|
||||
|
||||
# Test Omnipower C1 telegrams
|
||||
|
||||
telegram=|1E442D2C0771941501027A|B300108504833B08340500|
|
||||
{media:"electricity",meter:"omnipower","name":"MyElectricity","id":"15947107","total_kwh":341.000,"current_kw":"0.000","timestamp":"1111-11-11T11:11:11Z"}
|
||||
|
|
5
test.sh
5
test.sh
|
@ -4,8 +4,9 @@ PROG="$1"
|
|||
|
||||
cat simulation.txt | grep '^{' > test_expected.txt
|
||||
$PROG --robot=json simulation.txt \
|
||||
MyTapWater multical21 76348799 "" \
|
||||
MyHeater multical302 12345678 "" \
|
||||
MyTapWater water 76348799 "" \
|
||||
MyHeater heat 12345678 "" \
|
||||
MyElectricity electricity 15947107 "" \
|
||||
> test_output.txt
|
||||
if [ "$?" == "0" ]
|
||||
then
|
||||
|
|
38
util.cc
38
util.cc
|
@ -185,13 +185,6 @@ void debug(const char* fmt, ...) {
|
|||
}
|
||||
}
|
||||
|
||||
bool isValidType(char *type)
|
||||
{
|
||||
if (!strcmp(type, "multical21")) return true;
|
||||
if (!strcmp(type, "multical302")) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isValidId(char *id)
|
||||
{
|
||||
if (strlen(id) == 0) return true;
|
||||
|
@ -359,3 +352,34 @@ int parseTime(string time) {
|
|||
int n = atoi(time.c_str());
|
||||
return n*mul;
|
||||
}
|
||||
|
||||
#define CRC16_EN_13757 0x3D65
|
||||
|
||||
uint16_t crc16_EN13757_per_byte(uint16_t crc, uchar b)
|
||||
{
|
||||
unsigned char i;
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
|
||||
if (((crc & 0x8000) >> 8) ^ (b & 0x80)){
|
||||
crc = (crc << 1) ^ CRC16_EN_13757;
|
||||
}else{
|
||||
crc = (crc << 1);
|
||||
}
|
||||
|
||||
b <<= 1;
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint16_t crc16_EN13757(uchar *data, size_t len)
|
||||
{
|
||||
uint16_t crc = 0x0000;
|
||||
|
||||
for (size_t i=0; i<len; ++i) {
|
||||
crc = crc16_EN13757_per_byte(crc, data[i]);
|
||||
}
|
||||
|
||||
return (~crc);
|
||||
}
|
||||
|
|
3
util.h
3
util.h
|
@ -55,7 +55,6 @@ bool isLogTelegramsEnabled();
|
|||
void debugPayload(std::string intro, std::vector<uchar> &payload);
|
||||
void logTelegram(std::string intro, std::vector<uchar> &header, std::vector<uchar> &content);
|
||||
|
||||
bool isValidType(char *type);
|
||||
bool isValidId(char *id);
|
||||
bool isValidKey(char *key);
|
||||
|
||||
|
@ -71,4 +70,6 @@ void padWithZeroesTo(std::vector<uchar> *content, size_t len, std::vector<uchar>
|
|||
|
||||
int parseTime(std::string time);
|
||||
|
||||
uint16_t crc_16_EN_13757(uchar *data, size_t len);
|
||||
|
||||
#endif
|
||||
|
|
53
wmbus.cc
53
wmbus.cc
|
@ -77,12 +77,29 @@ void Telegram::verboseFields() {
|
|||
ci_field,
|
||||
ciType(ci_field).c_str());
|
||||
|
||||
if (ci_field == 0x78) {
|
||||
// No data error and no encryption possible.
|
||||
}
|
||||
|
||||
if (ci_field == 0x7a) {
|
||||
// Short data header
|
||||
verbose(" CC-field=%02x (%s) ACC=%02x ",
|
||||
cc_field, ccType(cc_field).c_str(),
|
||||
acc,
|
||||
sn[3],sn[2],sn[1],sn[0]);
|
||||
}
|
||||
|
||||
if (ci_field == 0x8d) {
|
||||
verbose(" CC-field=%02x (%s) ACC=%02x SN=%02x%02x%02x%02x",
|
||||
cc_field, ccType(cc_field).c_str(),
|
||||
acc,
|
||||
sn[3],sn[2],sn[1],sn[0]);
|
||||
}
|
||||
if (ci_field == 0x8c) {
|
||||
verbose(" CC-field=%02x (%s) ACC=%02x",
|
||||
cc_field, ccType(cc_field).c_str(),
|
||||
acc);
|
||||
}
|
||||
verbose("\n");
|
||||
}
|
||||
|
||||
|
@ -297,20 +314,39 @@ void Telegram::parse(vector<uchar> &frame)
|
|||
ci_field=frame[10];
|
||||
addExplanation(frame, 1, "%02x ci-field (%s)", ci_field, ciType(ci_field).c_str());
|
||||
|
||||
if (ci_field == 0x8d) {
|
||||
int header_size = 0;
|
||||
if (ci_field == 0x78) {
|
||||
header_size = 0; // And no encryption possible.
|
||||
} else
|
||||
if (ci_field == 0x7a) {
|
||||
acc = frame[11];
|
||||
addExplanation(frame, 1, "%02x acc", acc);
|
||||
status = frame[12];
|
||||
addExplanation(frame, 1, "%02x status ()", status);
|
||||
configuration = frame[13]<<8 | frame[14];
|
||||
addExplanation(frame, 2, "%02x%02x configuration ()", frame[13], frame[14]);
|
||||
header_size = 4;
|
||||
} else
|
||||
if (ci_field == 0x8d || ci_field == 0x8c) {
|
||||
cc_field = frame[11];
|
||||
addExplanation(frame, 1, "%02x cc-field (%s)", cc_field, ccType(cc_field).c_str());
|
||||
acc = frame[12];
|
||||
addExplanation(frame, 1, "%02x acc", acc);
|
||||
sn[0] = frame[13];
|
||||
sn[1] = frame[14];
|
||||
sn[2] = frame[15];
|
||||
sn[3] = frame[16];
|
||||
addExplanation(frame, 4, "%02x%02x%02x%02x sn", sn[0], sn[1], sn[2], sn[3]);
|
||||
header_size = 2;
|
||||
if (ci_field == 0x8d) {
|
||||
sn[0] = frame[13];
|
||||
sn[1] = frame[14];
|
||||
sn[2] = frame[15];
|
||||
sn[3] = frame[16];
|
||||
addExplanation(frame, 4, "%02x%02x%02x%02x sn", sn[0], sn[1], sn[2], sn[3]);
|
||||
header_size = 6;
|
||||
}
|
||||
} else {
|
||||
warning("Unknown ci-field %02x\n", ci_field);
|
||||
}
|
||||
|
||||
payload.clear();
|
||||
payload.insert(payload.end(), frame.begin()+17, frame.end());
|
||||
payload.insert(payload.end(), frame.begin()+(11+header_size), frame.end());
|
||||
verbose("(wmbus) received telegram");
|
||||
verboseFields();
|
||||
debugPayload("(wmbus) frame", frame);
|
||||
|
@ -564,6 +600,9 @@ string vifeType(int vif, int vife)
|
|||
//int extension = vif & 0x80;
|
||||
//int t = vif & 0x7f;
|
||||
|
||||
if (vif == 0x83 && vife == 0x3b) {
|
||||
return "Forward flow contribution only";
|
||||
}
|
||||
if (vif == 0xff) {
|
||||
return "?";
|
||||
}
|
||||
|
|
31
wmbus.h
31
wmbus.h
|
@ -49,22 +49,28 @@ LIST_OF_LINK_MODES
|
|||
using namespace std;
|
||||
|
||||
struct Telegram {
|
||||
int len; // The length of the telegram, 1 byte.
|
||||
int c_field; // 1 byte (0x44=telegram, no response expected!)
|
||||
int m_field; // Manufacturer 2 bytes
|
||||
int len {}; // The length of the telegram, 1 byte.
|
||||
int c_field {}; // 1 byte (0x44=telegram, no response expected!)
|
||||
int m_field {}; // Manufacturer 2 bytes
|
||||
vector<uchar> a_field; // A field 6 bytes
|
||||
// The 6 a field bytes are composed of:
|
||||
vector<uchar> a_field_address; // Address in BCD = 8 decimal 00000000...99999999 digits.
|
||||
int a_field_version; // 1 byte
|
||||
int a_field_device_type; // 1 byte
|
||||
int a_field_version {}; // 1 byte
|
||||
int a_field_device_type {}; // 1 byte
|
||||
|
||||
int ci_field; // 1 byte
|
||||
// When ci_field==0x8d then there are 8 extra header bytes (ELL header?)
|
||||
int cc_field; // 1 byte
|
||||
int acc; // 1 byte
|
||||
uchar sn[4]; // 4 bytes
|
||||
// That is 6 bytes (not 8), perhaps the next two bytes (the plcrc?) are
|
||||
// part of this ELL header, even though they are inside the encrypted payload?
|
||||
int ci_field {}; // 1 byte
|
||||
|
||||
// When ci_field==0x7a then there are 4 extra header bytes, short data header
|
||||
int acc {}; // 1 byte
|
||||
int status {}; // 1 byte
|
||||
int configuration {}; // 2 bytes
|
||||
|
||||
// When ci_field==0x8d then there are 8 extra header bytes (ELL header)
|
||||
int cc_field {}; // 1 byte
|
||||
// acc; // 1 byte
|
||||
uchar sn[4] {}; // 4 bytes
|
||||
// That is 6 bytes (not 8), the next two bytes, the payload crc
|
||||
// part of this ELL header, even though they are inside the encrypted payload.
|
||||
|
||||
vector<uchar> parsed; // Parsed fields
|
||||
vector<uchar> payload; // To be parsed.
|
||||
|
@ -74,7 +80,6 @@ struct Telegram {
|
|||
|
||||
// The id as written on the physical meter device.
|
||||
string id() { return bin2hex(a_field_address); }
|
||||
|
||||
void parse(vector<uchar> &payload);
|
||||
void print();
|
||||
void verboseFields();
|
||||
|
|
Ładowanie…
Reference in New Issue