Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a HexStringField parser. #12

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ need, to make the parsing and printing code smaller and faster.

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

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

The final one `HexStringField` is a smart version of the StringField
parser and is only used for equipment id's as in practise these are
often hex coded strings instead of a normal strings. If the given
string is indeed a hex coding, the decoded string is returned else
the original string is returned.

0-0:96.1.1(4530303639303030373138323035333231)
E0069000718205321

## Connecting the P1 port

The P1 port essentially consists of three parts:
Expand Down
47 changes: 41 additions & 6 deletions src/dsmr/fields.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,41 @@ namespace dsmr
}
};

// A hexstring field is essencially a string filled with hex digits. If the
// string does not consist of an even number of hex digits, the original
// string is returned, otherwise the decoded hex string is returned.
template <typename T, size_t minlen, size_t maxlen>
struct HexStringField : ParsedField<T>
{
ParseResult<void> parse(const char *str, const char *end)
{
ParseResult<String> res = StringParser::parse_string(minlen, maxlen, str, end);
if (!res.err) {
static_cast<T *>(this)->val() = res.result;

if (res.result.length() & 1) {
// Odd number of chars, can't be a hex coded string.
return res;
}
if (!std::all_of(res.result.begin(), res.result.end(), [](const char ch){ return isxdigit(ch); })) {
// Not all chars are hex digits.
return res;
}
String hexStr;
hexStr.reserve(res.result.length()/2);
for (auto it = res.result.begin(); it != res.result.end(); ++it) {
const unsigned char ch1 = static_cast<unsigned const char>(*it++);
const unsigned char ch2 = static_cast<unsigned const char>(*it);
uint8_t val = (isdigit(ch1) ? ch1 - '0' : toupper(ch1) - 'A' + 10) * 16 +
(isdigit(ch2) ? ch2 - '0' : toupper(ch2) - 'A' + 10);
hexStr.concat(static_cast<char>(val));
}
static_cast<T *>(this)->val() = hexStr;
}
return res;
}
};

// A timestamp is essentially a string using YYMMDDhhmmssX format (where
// X is W or S for wintertime or summertime). Parsing this into a proper
// (UNIX) timestamp is hard to do generically. Parsing it into a
Expand Down Expand Up @@ -275,7 +310,7 @@ namespace dsmr
DEFINE_FIELD(timestamp, String, ObisId(0, 0, 1, 0, 0), TimestampField);

/* Equipment identifier */
DEFINE_FIELD(equipment_id, String, ObisId(0, 0, 96, 1, 1), StringField, 0, 96);
DEFINE_FIELD(equipment_id, String, ObisId(0, 0, 96, 1, 1), HexStringField, 0, 96);

/* Meter Reading electricity delivered to client (Special for Lux) in 0,001 kWh */
/* TODO: by OBIS 1-0:1.8.0.255 IEC 62056 it should be Positive active energy (A+) total [kWh], should we rename it? */
Expand Down Expand Up @@ -495,9 +530,9 @@ namespace dsmr
DEFINE_FIELD(gas_device_type, uint16_t, ObisId(0, GAS_MBUS_ID, 24, 1, 0), IntField, units::none);

/* Equipment identifier (Gas) */
DEFINE_FIELD(gas_equipment_id, String, ObisId(0, GAS_MBUS_ID, 96, 1, 0), StringField, 0, 96);
DEFINE_FIELD(gas_equipment_id, String, ObisId(0, GAS_MBUS_ID, 96, 1, 0), HexStringField, 0, 96);
/* Equipment identifier (Gas) BE */
DEFINE_FIELD(gas_equipment_id_be, String, ObisId(0, GAS_MBUS_ID, 96, 1, 1), StringField, 0, 96);
DEFINE_FIELD(gas_equipment_id_be, String, ObisId(0, GAS_MBUS_ID, 96, 1, 1), HexStringField, 0, 96);

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

/* Equipment identifier (Thermal: heat or cold) */
DEFINE_FIELD(thermal_equipment_id, String, ObisId(0, THERMAL_MBUS_ID, 96, 1, 0), StringField, 0, 96);
DEFINE_FIELD(thermal_equipment_id, String, ObisId(0, THERMAL_MBUS_ID, 96, 1, 0), HexStringField, 0, 96);

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

/* Equipment identifier (Thermal: heat or cold) */
DEFINE_FIELD(water_equipment_id, String, ObisId(0, WATER_MBUS_ID, 96, 1, 0), StringField, 0, 96);
DEFINE_FIELD(water_equipment_id, String, ObisId(0, WATER_MBUS_ID, 96, 1, 0), HexStringField, 0, 96);

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

/* Equipment identifier (Thermal: heat or cold) */
DEFINE_FIELD(sub_equipment_id, String, ObisId(0, SUB_MBUS_ID, 96, 1, 0), StringField, 0, 96);
DEFINE_FIELD(sub_equipment_id, String, ObisId(0, SUB_MBUS_ID, 96, 1, 0), HexStringField, 0, 96);

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