Skip to content

Commit 411d081

Browse files
committed
Add a HexStringField parser which understands some DSMRs encode the
equipement ids as hex strings.
1 parent 391031b commit 411d081

File tree

2 files changed

+52
-7
lines changed

2 files changed

+52
-7
lines changed

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,8 @@ need, to make the parsing and printing code smaller and faster.
160160

161161
Some values are parsed to an Arduino `String` value or C++ integer type,
162162
those should be fairly straightforward. There are three special types
163-
that need some explanation: `FixedValue` and `TimestampedFixedValue`.
163+
that need some explanation: `FixedValue`, `TimestampedFixedValue` and
164+
`HexStringField`.
164165

165166
When looking at the DSMR P1 format, it defines a floating point format.
166167
It is described as `Fn(x,y)`, where `n` is the total number of (decimal)
@@ -201,6 +202,15 @@ Parsing these into something like a UNIX timestamp is tricky (think
201202
leap years and seconds) and of limited use, so this just keeps the
202203
original format.
203204

205+
The final one `HexStringField` is a smart version of the StringField
206+
parser and is only used for equipment id's as in practise these are
207+
often hex coded strings instead of a normal strings. If the given
208+
string is indeed a hex coding, the decoded string is returned else
209+
the original string is returned.
210+
211+
0-0:96.1.1(4530303639303030373138323035333231)
212+
E0069000718205321
213+
204214
## Connecting the P1 port
205215

206216
The P1 port essentially consists of three parts:

src/dsmr/fields.h

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,41 @@ namespace dsmr
7373
}
7474
};
7575

76+
// A hexstring field is essencially a string filled with hex digits. If the
77+
// string does not consist of an even number of hex digits, the original
78+
// string is returned, otherwise the decoded hex string is returned.
79+
template <typename T, size_t minlen, size_t maxlen>
80+
struct HexStringField : ParsedField<T>
81+
{
82+
ParseResult<void> parse(const char *str, const char *end)
83+
{
84+
ParseResult<String> res = StringParser::parse_string(minlen, maxlen, str, end);
85+
if (!res.err) {
86+
static_cast<T *>(this)->val() = res.result;
87+
88+
if (res.result.length() & 1) {
89+
// Odd number of chars, can't be a hex coded string.
90+
return res;
91+
}
92+
if (!std::all_of(res.result.begin(), res.result.end(), [](const char ch){ return isxdigit(ch); })) {
93+
// Not all chars are hex digits.
94+
return res;
95+
}
96+
String hexStr;
97+
hexStr.reserve(res.result.length()/2);
98+
for (auto it = res.result.begin(); it != res.result.end(); ++it) {
99+
const unsigned char ch1 = static_cast<unsigned const char>(*it++);
100+
const unsigned char ch2 = static_cast<unsigned const char>(*it);
101+
uint8_t val = (isdigit(ch1) ? ch1 - '0' : toupper(ch1) - 'A' + 10) * 16 +
102+
(isdigit(ch2) ? ch2 - '0' : toupper(ch2) - 'A' + 10);
103+
hexStr.concat(static_cast<char>(val));
104+
}
105+
static_cast<T *>(this)->val() = hexStr;
106+
}
107+
return res;
108+
}
109+
};
110+
76111
// A timestamp is essentially a string using YYMMDDhhmmssX format (where
77112
// X is W or S for wintertime or summertime). Parsing this into a proper
78113
// (UNIX) timestamp is hard to do generically. Parsing it into a
@@ -268,7 +303,7 @@ namespace dsmr
268303
DEFINE_FIELD(timestamp, String, ObisId(0, 0, 1, 0, 0), TimestampField);
269304

270305
/* Equipment identifier */
271-
DEFINE_FIELD(equipment_id, String, ObisId(0, 0, 96, 1, 1), StringField, 0, 96);
306+
DEFINE_FIELD(equipment_id, String, ObisId(0, 0, 96, 1, 1), HexStringField, 0, 96);
272307

273308
/* Meter Reading electricity delivered to client (Special for Lux) in 0,001 kWh */
274309
DEFINE_FIELD(energy_delivered_lux, FixedValue, ObisId(1, 0, 1, 8, 0), FixedField, units::kWh, units::Wh);
@@ -398,9 +433,9 @@ namespace dsmr
398433
DEFINE_FIELD(gas_device_type, uint16_t, ObisId(0, GAS_MBUS_ID, 24, 1, 0), IntField, units::none);
399434

400435
/* Equipment identifier (Gas) */
401-
DEFINE_FIELD(gas_equipment_id, String, ObisId(0, GAS_MBUS_ID, 96, 1, 0), StringField, 0, 96);
436+
DEFINE_FIELD(gas_equipment_id, String, ObisId(0, GAS_MBUS_ID, 96, 1, 0), HexStringField, 0, 96);
402437
/* Equipment identifier (Gas) BE */
403-
DEFINE_FIELD(gas_equipment_id_be, String, ObisId(0, GAS_MBUS_ID, 96, 1, 1), StringField, 0, 96);
438+
DEFINE_FIELD(gas_equipment_id_be, String, ObisId(0, GAS_MBUS_ID, 96, 1, 1), HexStringField, 0, 96);
404439

405440
/* Valve position Gas (on/off/released) (Note: Removed in 4.0.7 / 4.2.2 / 5.0). */
406441
DEFINE_FIELD(gas_valve_position, uint8_t, ObisId(0, GAS_MBUS_ID, 24, 4, 0), IntField, units::none);
@@ -419,7 +454,7 @@ namespace dsmr
419454
DEFINE_FIELD(thermal_device_type, uint16_t, ObisId(0, THERMAL_MBUS_ID, 24, 1, 0), IntField, units::none);
420455

421456
/* Equipment identifier (Thermal: heat or cold) */
422-
DEFINE_FIELD(thermal_equipment_id, String, ObisId(0, THERMAL_MBUS_ID, 96, 1, 0), StringField, 0, 96);
457+
DEFINE_FIELD(thermal_equipment_id, String, ObisId(0, THERMAL_MBUS_ID, 96, 1, 0), HexStringField, 0, 96);
423458

424459
/* Valve position (on/off/released) (Note: Removed in 4.0.7 / 4.2.2 / 5.0). */
425460
DEFINE_FIELD(thermal_valve_position, uint8_t, ObisId(0, THERMAL_MBUS_ID, 24, 4, 0), IntField, units::none);
@@ -433,7 +468,7 @@ namespace dsmr
433468
DEFINE_FIELD(water_device_type, uint16_t, ObisId(0, WATER_MBUS_ID, 24, 1, 0), IntField, units::none);
434469

435470
/* Equipment identifier (Thermal: heat or cold) */
436-
DEFINE_FIELD(water_equipment_id, String, ObisId(0, WATER_MBUS_ID, 96, 1, 0), StringField, 0, 96);
471+
DEFINE_FIELD(water_equipment_id, String, ObisId(0, WATER_MBUS_ID, 96, 1, 0), HexStringField, 0, 96);
437472

438473
/* Valve position (on/off/released) (Note: Removed in 4.0.7 / 4.2.2 / 5.0). */
439474
DEFINE_FIELD(water_valve_position, uint8_t, ObisId(0, WATER_MBUS_ID, 24, 4, 0), IntField, units::none);
@@ -447,7 +482,7 @@ namespace dsmr
447482
DEFINE_FIELD(sub_device_type, uint16_t, ObisId(0, SUB_MBUS_ID, 24, 1, 0), IntField, units::none);
448483

449484
/* Equipment identifier (Thermal: heat or cold) */
450-
DEFINE_FIELD(sub_equipment_id, String, ObisId(0, SUB_MBUS_ID, 96, 1, 0), StringField, 0, 96);
485+
DEFINE_FIELD(sub_equipment_id, String, ObisId(0, SUB_MBUS_ID, 96, 1, 0), HexStringField, 0, 96);
451486

452487
/* Valve position (on/off/released) (Note: Removed in 4.0.7 / 4.2.2 / 5.0). */
453488
DEFINE_FIELD(sub_valve_position, uint8_t, ObisId(0, SUB_MBUS_ID, 24, 4, 0), IntField, units::none);

0 commit comments

Comments
 (0)