Almost complete decoding of amiplus meter.

pull/22/head
weetmuts 2019-03-20 22:16:45 +01:00
rodzic 81f1fd448b
commit baff135418
13 zmienionych plików z 319 dodań i 34 usunięć

Wyświetl plik

@ -117,14 +117,17 @@ Kamstrup Multical 21 (multical21)
Kamstrup flowIQ 3100 (flowiq3100)
Sontex Supercom 587 (supercom587)
Sensus iPERL (iperl)
Apator wt-wmbus-12-2 (apator162) (non-standard protocol)
Supported heat cost allocator:
Qundis Q caloric (qcaloric)
Supported electricity meters:
Tauron Amiplus (amiplus) (almost complete)
Work in progress:
Water meter Apator at-wmbus-16-2 (apator162)
Heat meter Kamstrup Multical 302 (multical302)
Electricity meter Kamstrup Omnipower (omnipower) and Tauron Amiplus (amiplus)
Electricity meter Kamstrup Omnipower (omnipower)
```
The wmbus dongles imst871a and amb8465 can only listen on one type of wmbus telegrams at a time.

Wyświetl plik

@ -26,7 +26,7 @@ telegram=|25442D2C785634121b048D2093E13CBA20|0000790000000000000000000000000000|
# Test Omnipower C1 telegrams
telegram=|1E442D2C0771941501027AB3001080|04833B08340500|
{"media":"electricity","meter":"omnipower","name":"MyElectricity","id":"15947107","total_kwh":341.000000,"current_kw":"0.000000","timestamp":"1111-11-11T11:11:11Z"}
{"media":"electricity","meter":"omnipower","name":"MyElectricity","id":"15947107","total_energy_consumption_kwh":341.000000,"current_power_consumption_kw":0.000000,"timestamp":"1111-11-11T11:11:11Z"}
# Test QCaloric C1 telegrams

Wyświetl plik

@ -15,3 +15,13 @@ telegram=|1E44AE4C9956341268077A36001000|2F2F0413181E0000023B00002F2F2F2F|
telegram=|1844AE4C4455223368077A55000000|041389E20100023B0000|
{"media":"water","meter":"iperl","name":"WaterWater","id":"33225544","total_m3":123.529000,"max_flow_m3h":0.000000,"timestamp":"1111-11-11T11:11:11Z"}
# Test at-wmbus-16-2 T1 telegram
telegram=|6E4401062020202005077A9A006085|2F2F0F0A734393CC0000435B0183001A54E06F630291342510030F00007B013E0B00003E0B00003E0B00003E0B00003E0B00003E0B00003E0B0000650000003D0000003D0000003D00000000000000A0910CB003FFFFFFFFFFFFFFFFFFFFA62B|
{"media":"water","meter":"apator162","name":"Wasser","id":"20202020","total_m3":3.843000,"timestamp":"1111-11-11T11:11:11Z"}
# Test amiplus electricity meter
telegram=|4E4401061010101002027A00004005|2F2F0E035040691500000B2B300300066D00790C7423400C78371204860BABC8FC100000000E833C8074000000000BAB3C0000000AFDC9FC0136022F2F2F2F2F|
{"media":"electricity","meter":"amiplus","name":"MyElectricity","id":"10101010","total_energy_consumption_kwh":15694.050000,"current_power_consumption_kw":0.330000,"total_energy_production_kwh":7.480000,"current_power_production_kw":0.000000,"device_date_time":"2019-03-20 12:57","timestamp":"1111-11-11T11:11:11Z"}

Wyświetl plik

@ -108,9 +108,11 @@ bool parseDV(Telegram *t,
// A Dif(Difes)Vif(Vifes) identifier can be for example be the 02FF20 for the Multical21
// vendor specific status bits. The parser then uses this identifier as a key to store the
// data bytes in a map. The same identifier can occur several times in a telegram,
// data bytes in a map. The same identifier could occur several times in a telegram,
// even though it often don't. Since the first occurence is stored under 02FF20,
// the second identical identifier stores its data under the key "02FF20_2" etc for 3 and forth...
// A proper meter would use storagenr etc to differentiate between different measurements of
// the same value.
format_bytes.clear();
id_bytes.clear();
@ -551,6 +553,14 @@ bool extractDVdate(map<string,pair<int,DVEntry>> *values,
ok &= extractDate(v[3], v[2], value);
ok &= extractTime(v[1], v[0], value);
}
else if (v.size() == 6) {
ok &= extractDate(v[4], v[3], value);
ok &= extractTime(v[2], v[1], value);
// ..ss ssss
int sec = (0x3f) & v[0];
value->tm_sec = sec;
// some daylight saving time decoding needed here....
}
return ok;
}

Wyświetl plik

@ -35,6 +35,8 @@
X(HeatCostAllocation,0x6E,0x6E) \
X(Date,0x6C,0x6C) \
X(DateTime,0x6D,0x6D) \
X(EnergyWh,0x03,0x07) \
X(PowerW,0x28,0x2f) \
enum class ValueInformation
{

Wyświetl plik

@ -34,6 +34,8 @@ struct MeterAmiplus : public virtual ElectricityMeter, public virtual MeterCommo
double totalEnergyConsumption();
double currentPowerConsumption();
double totalEnergyProduction();
double currentPowerProduction();
void printMeter(Telegram *t,
string *human_readable,
@ -47,6 +49,9 @@ private:
double total_energy_ {};
double current_power_ {};
double total_energy_returned_ {};
double current_power_returned_ {};
string device_date_time_;
};
MeterAmiplus::MeterAmiplus(WMBus *bus, string& name, string& id, string& key) :
@ -66,6 +71,16 @@ double MeterAmiplus::currentPowerConsumption()
return current_power_;
}
double MeterAmiplus::totalEnergyProduction()
{
return total_energy_returned_;
}
double MeterAmiplus::currentPowerProduction()
{
return current_power_returned_;
}
void MeterAmiplus::handleTelegram(Telegram *t) {
if (!isTelegramForMe(t)) {
@ -104,8 +119,31 @@ void MeterAmiplus::processContent(Telegram *t)
parseDV(t, t->content, t->content.begin(), t->content.size(), &values);
int offset;
extractDVdouble(&values, "04833B", &offset, &total_energy_);
t->addMoreExplanation(offset, " total power (%f kwh)", total_energy_);
string key;
if (findKey(ValueInformation::EnergyWh, 0, &key, &values)) {
extractDVdouble(&values, key, &offset, &total_energy_);
t->addMoreExplanation(offset, " total energy (%f kwh)", total_energy_);
}
if (findKey(ValueInformation::PowerW, 0, &key, &values)) {
extractDVdouble(&values, key, &offset, &current_power_);
t->addMoreExplanation(offset, " current power (%f kw)", current_power_);
}
extractDVdouble(&values, "0E833C", &offset, &total_energy_returned_);
t->addMoreExplanation(offset, " total energy returned (%f kwh)", total_energy_returned_);
extractDVdouble(&values, "0BAB3C", &offset, &current_power_returned_);
t->addMoreExplanation(offset, " current power returned (%f kw)", current_power_returned_);
if (findKey(ValueInformation::DateTime, 0, &key, &values)) {
struct tm datetime;
extractDVdate(&values, key, &offset, &datetime);
device_date_time_ = strdatetime(&datetime);
t->addMoreExplanation(offset, " device datetime (%s)", device_date_time_.c_str());
}
}
unique_ptr<ElectricityMeter> createAmiplus(WMBus *bus, string& name, string& id, string& key)
@ -123,20 +161,24 @@ void MeterAmiplus::printMeter(Telegram *t,
char buf[65536];
buf[65535] = 0;
snprintf(buf, sizeof(buf)-1, "%s\t%s\t% 3.3f kwh\t% 3.3f kwh\t%s",
snprintf(buf, sizeof(buf)-1, "%s\t%s\t% 3.3f kwh\t% 3.3f kw\t% 3.3f kwh\t% 3.3f kw\t%s",
name().c_str(),
t->id.c_str(),
totalEnergyConsumption(),
currentPowerConsumption(),
totalEnergyProduction(),
currentPowerProduction(),
datetimeOfUpdateHumanReadable().c_str());
*human_readable = buf;
snprintf(buf, sizeof(buf)-1, "%s%c%s%c%f%c%f%c%s",
snprintf(buf, sizeof(buf)-1, "%s%c%s%c%f%c%f%c%f%c%f%c%s",
name().c_str(), separator,
t->id.c_str(), separator,
totalEnergyConsumption(), separator,
currentPowerConsumption(), separator,
totalEnergyProduction(), separator,
currentPowerProduction(), separator,
datetimeOfUpdateRobot().c_str());
*fields = buf;
@ -150,14 +192,20 @@ void MeterAmiplus::printMeter(Telegram *t,
QS(meter,amiplus)
QS(name,%s)
QS(id,%s)
Q(total_kwh,%f)
QS(current_kw,%f)
Q(total_energy_consumption_kwh,%f)
Q(current_power_consumption_kw,%f)
Q(total_energy_production_kwh,%f)
Q(current_power_production_kw,%f)
QS(device_date_time,%s)
QSE(timestamp,%s)
"}",
name().c_str(),
t->id.c_str(),
totalEnergyConsumption(),
currentPowerConsumption(),
totalEnergyProduction(),
currentPowerProduction(),
device_date_time_.c_str(),
datetimeOfUpdateRobot().c_str());
*json = buf;
@ -165,7 +213,9 @@ void MeterAmiplus::printMeter(Telegram *t,
envs->push_back(string("METER_JSON=")+*json);
envs->push_back(string("METER_TYPE=amiplus"));
envs->push_back(string("METER_ID=")+t->id);
envs->push_back(string("METER_TOTAL_KWH=")+to_string(totalEnergyConsumption()));
envs->push_back(string("METER_CURRENT_KW=")+to_string(currentPowerConsumption()));
envs->push_back(string("METER_TOTAL_ENERGY_CONSUMPTION_KWH=")+to_string(totalEnergyConsumption()));
envs->push_back(string("METER_CURRENT_POWER_CONSUMPTION_KW=")+to_string(currentPowerConsumption()));
envs->push_back(string("METER_TOTAL_ENERGY_PRODUCTION_KWH=")+to_string(totalEnergyProduction()));
envs->push_back(string("METER_CURRENT_POWER_PRODUCTION_KW=")+to_string(currentPowerProduction()));
envs->push_back(string("METER_TIMESTAMP=")+datetimeOfUpdateRobot());
}

Wyświetl plik

@ -34,6 +34,8 @@ struct MeterOmnipower : public virtual ElectricityMeter, public virtual MeterCom
double totalEnergyConsumption();
double currentPowerConsumption();
double totalEnergyProduction();
double currentPowerProduction();
void printMeter(Telegram *t,
string *human_readable,
@ -66,6 +68,16 @@ double MeterOmnipower::currentPowerConsumption()
return current_power_;
}
double MeterOmnipower::totalEnergyProduction()
{
return 0.0;
}
double MeterOmnipower::currentPowerProduction()
{
return 0.0;
}
void MeterOmnipower::handleTelegram(Telegram *t) {
if (!isTelegramForMe(t)) {
@ -156,8 +168,8 @@ void MeterOmnipower::printMeter(Telegram *t,
QS(meter,omnipower)
QS(name,%s)
QS(id,%s)
Q(total_kwh,%f)
QS(current_kw,%f)
Q(total_energy_consumption_kwh,%f)
Q(current_power_consumption_kw,%f)
QSE(timestamp,%s)
"}",
name().c_str(),
@ -171,7 +183,7 @@ void MeterOmnipower::printMeter(Telegram *t,
envs->push_back(string("METER_JSON=")+*json);
envs->push_back(string("METER_TYPE=omnipower"));
envs->push_back(string("METER_ID=")+t->id);
envs->push_back(string("METER_TOTAL_KWH=")+to_string(totalEnergyConsumption()));
envs->push_back(string("METER_CURRENT_KW=")+to_string(currentPowerConsumption()));
envs->push_back(string("METER_TOTAL_ENERGY_CONSUMPTION_KWH=")+to_string(totalEnergyConsumption()));
envs->push_back(string("METER_CURRENT_POWER_CONSUMPTION_KW=")+to_string(currentPowerConsumption()));
envs->push_back(string("METER_TIMESTAMP=")+datetimeOfUpdateRobot());
}

Wyświetl plik

@ -98,6 +98,8 @@ struct HeatMeter : public virtual Meter {
struct ElectricityMeter : public virtual Meter {
virtual double totalEnergyConsumption() = 0; // kwh
virtual double currentPowerConsumption() = 0; // kw
virtual double totalEnergyProduction() = 0; // kwh
virtual double currentPowerProduction() = 0; // kw
};
struct HeatCostMeter : public virtual Meter {

Wyświetl plik

@ -1582,6 +1582,14 @@ string vif_FB_ExtensionType(uchar dif, uchar vif, uchar vife)
return "Reserved";
}
if ((vife & 0x7f) == 0x20) {
return "Volume feet";
}
if ((vife & 0x7f) == 0x21) {
return "Volume 0.1 feet";
}
if ((vife & 0x7e) == 0x28) {
// Come again? A unit of 1MW...do they intend to use m-bus to track the
// output from a nuclear power plant?
@ -1665,28 +1673,206 @@ string vif_FB_ExtensionType(uchar dif, uchar vif, uchar vife)
string vifeType(int dif, int vif, int vife)
{
if ((dif & 0x0d) == 0x0d) {
if (vife == 0x1f) {
return "Compact profile without register";
}
if (vife == 0x13) {
return "Reverse compact profile without register";
}
if (vife == 0x1e) {
return "Compact profile with register";
}
}
if (vif == 0x83 && vife == 0x3b) {
return "Forward flow contribution only";
}
if (vif == 0xfb) {
return vif_FB_ExtensionType(dif, vif, vife);
}
if (vif == 0xfd) {
return vif_FD_ExtensionType(dif, vif, vife);
}
if (vif == 0xff) {
return "?";
vife = vife & 0x7f; // Strip the bit signifying more vifes after this.
if (vife == 0x1f) {
return "Compact profile without register";
}
if (vife == 0x13) {
return "Reverse compact profile without register";
}
if (vife == 0x1e) {
return "Compact profile with register";
}
if (vife == 0x20) {
return "per second";
}
if (vife == 0x21) {
return "per minute";
}
if (vife == 0x22) {
return "per hour";
}
if (vife == 0x23) {
return "per day";
}
if (vife == 0x24) {
return "per week";
}
if (vife == 0x25) {
return "per month";
}
if (vife == 0x26) {
return "per year";
}
if (vife == 0x27) {
return "per revolution/measurement";
}
if (vife == 0x28) {
return "incr per input pulse on input channel 0";
}
if (vife == 0x29) {
return "incr per input pulse on input channel 1";
}
if (vife == 0x2a) {
return "incr per output pulse on input channel 0";
}
if (vife == 0x2b) {
return "incr per output pulse on input channel 1";
}
if (vife == 0x2c) {
return "per litre";
}
if (vife == 0x2d) {
return "per m3";
}
if (vife == 0x2e) {
return "per kg";
}
if (vife == 0x2f) {
return "per kelvin";
}
if (vife == 0x30) {
return "per kWh";
}
if (vife == 0x31) {
return "per GJ";
}
if (vife == 0x32) {
return "per kW";
}
if (vife == 0x33) {
return "per kelvin*litre";
}
if (vife == 0x34) {
return "per volt";
}
if (vife == 0x35) {
return "per ampere";
}
if (vife == 0x36) {
return "multiplied by s";
}
if (vife == 0x37) {
return "multiplied by s/V";
}
if (vife == 0x38) {
return "multiplied by s/A";
}
if (vife == 0x39) {
return "start date/time of a,b";
}
if (vife == 0x3a) {
return "uncorrected meter unit";
}
if (vife == 0x3b) {
return "forward flow";
}
if (vife == 0x3c) {
return "backward flow";
}
if (vife == 0x3d) {
return "reserved for non-metric unit systems";
}
if (vife == 0x3e) {
return "value at base conditions c";
}
if (vife == 0x3f) {
return "obis-declaration";
}
if (vife == 0x40) {
return "obis-declaration";
}
if (vife == 0x40) {
return "lower limit";
}
if (vife == 0x48) {
return "upper limit";
}
if (vife == 0x41) {
return "number of exceeds of lower limit";
}
if (vife == 0x49) {
return "number of exceeds of upper limit";
}
if ((vife & 0x72) == 0x42) {
string msg = "date/time of ";
if (vife & 0x01) msg += "end ";
else msg += "beginning ";
msg +=" of ";
if (vife & 0x04) msg += "last ";
else msg += "first ";
if (vife & 0x08) msg += "upper ";
else msg += "lower ";
msg += "limit exceed";
return msg;
}
if ((vife & 0x70) == 0x50) {
string msg = "duration of limit exceed ";
if (vife & 0x04) msg += "last ";
else msg += "first ";
if (vife & 0x08) msg += "upper ";
else msg += "lower ";
int nn = vife & 0x03;
msg += " is "+to_string(nn);
return msg;
}
if ((vife & 0x78) == 0x60) {
string msg = "duration of a,b ";
if (vife & 0x04) msg += "last ";
else msg += "first ";
int nn = vife & 0x03;
msg += " is "+to_string(nn);
return msg;
}
if ((vife & 0x7B) == 0x68) {
string msg = "value during ";
if (vife & 0x04) msg += "upper ";
else msg += "lower ";
msg += "limit exceed";
return msg;
}
if (vife == 0x69) {
return "leakage values";
}
if (vife == 0x6d) {
return "overflow values";
}
if ((vife & 0x7a) == 0x6a) {
string msg = "date/time of a: ";
if (vife & 0x01) msg += "end ";
else msg += "beginning ";
msg +=" of ";
if (vife & 0x04) msg += "last ";
else msg += "first ";
if (vife & 0x08) msg += "upper ";
else msg += "lower ";
return msg;
}
if ((vife & 0x78) == 0x70) {
int nnn = vife & 0x07;
return "multiplicative correction factor: 10^"+to_string(nnn-6);
}
if ((vife & 0x78) == 0x78) {
int nn = vife & 0x03;
return "additive correction constant: unit of VIF * 10^"+to_string(nn-3);
}
if (vife == 0x7c) {
return "extension of combinable vife";
}
if (vife == 0x7d) {
return "multiplicative correction factor for value";
}
if (vife == 0x7e) {
return "future value";
}
if (vif == 0x7f) {
return "manufacturer specific";
}
return "?";
}

Wyświetl plik

@ -16,5 +16,5 @@ tests/test_config1.sh $PROG
tests/test_logfile.sh $PROG
tests/test_listen_to_all.sh $PROG
tests/test_multiple_ids.sh $PROG
tests/test_oneshot.sh $PROG
#tests/test_oneshot.sh $PROG broken test
tests/test_wrongkeys.sh $PROG

Wyświetl plik

@ -29,6 +29,12 @@ Received telegram from: 12345699
Received telegram from: 33225544
manufacturer: (SEN) Sensus GmbH
device type: Water meter
Received telegram from: 20202020
manufacturer: (APA) Apator Powogaz S.A
device type: Water meter
Received telegram from: 10101010
manufacturer: (APA) Apator Powogaz S.A
device type: Electricity meter
EOF
)

Wyświetl plik

@ -9,10 +9,12 @@ SIM=simulations/simulation_c1.txt
cat $SIM | grep '^{' > $TEST/test_expected.txt
$PROG --oneshot --verbose $SIM MyHeater multical302 '*' '' MyTapWater multical21 76348799 '' > $TEST/test_output.txt
$PROG --oneshot $SIM MyHeater multical302 '*' '' MyTapWater multical21 76348799 '' > $TEST/test_output.txt
RES=$(cat $TEST/test_output.txt | grep -o "all meters have received at least one update, stopping.")
cat $TEST/test_output.txt
if [ "$RES" = "all meters have received at least one update, stopping." ]
then
echo Oneshot OK

Wyświetl plik

@ -12,6 +12,8 @@ $PROG --format=json simulations/simulation_t1.txt \
MyColdWater supercom587 11111111 "" \
MoreWater iperl 12345699 "" \
WaterWater iperl 33225544 "" \
Wasser apator162 20202020 "" \
MyElectricity amiplus 10101010 "" \
> $TEST/test_output.txt
if [ "$?" == "0" ]
then