Skip to content

Commit 0f95a40

Browse files
authored
Add gas_delivered_gj field
* Fix #42 * Add gas_delivered_gj * Add `NumParser::parse_float_or_int` method to simplify existing code * `CMakeLists.txt`: freeze `cmake_template` version.
1 parent 4b31d5e commit 0f95a40

File tree

4 files changed

+50
-37
lines changed

4 files changed

+50
-37
lines changed

CMakeLists.txt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ include(FetchContent)
1111
file(DOWNLOAD
1212
https://github.com/doctest/doctest/releases/download/v2.4.12/doctest.h
1313
${CMAKE_BINARY_DIR}/doctest/doctest.h
14-
EXPECTED_MD5 0671bbca9fb00cb19a168cffa89ac184)
14+
EXPECTED_HASH SHA512=5a69cc6a5e874691877fbd1cea661ec736c2d78d38791cb22a12398c2731ea6d07e7edda4bd1efde2a65df9e4ddd4836f6571d9b04e0241d520790573634065a)
1515

1616
# mbedtls
1717
FetchContent_Populate(
1818
mded_tls
1919
URL https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-3.6.4/mbedtls-3.6.4.tar.bz2
20-
URL_HASH MD5=eb965a5bb8044bc43a49adb435fa72ee)
20+
URL_HASH SHA512=6671fb8fcaa832e5b115dfdce8f78baa6a4aea71f5c89a640583634cdee27aefe3bf4be075744da91f7c3ae5ea4e0c765c8fc3937b5cfd9ea73d87ef496524da)
2121
set(ENABLE_PROGRAMS OFF CACHE INTERNAL "")
2222
set(ENABLE_TESTING OFF CACHE INTERNAL "")
2323
add_subdirectory(${mded_tls_SOURCE_DIR})
@@ -26,16 +26,16 @@ add_subdirectory(${mded_tls_SOURCE_DIR})
2626
FetchContent_Populate(
2727
bearsll
2828
URL https://bearssl.org/bearssl-0.6.tar.gz
29-
URL_HASH MD5=1513f9828c5b174ea409ca581cb45c98)
29+
URL_HASH SHA512=f9ed25683cfc6c4abe7f1203a2b82ed101ee4c9e0f9ab60755b6a09c8d1e8e4f64d413624e7bb9c4b0033f909a2e4568a1d916cc6ce4736222900691e1f8359a)
3030
file(GLOB_RECURSE bearssl_sources CONFIGURE_DEPENDS "${bearsll_SOURCE_DIR}/src/*.c")
3131
add_library(bearssl STATIC ${bearssl_sources})
3232
target_include_directories(bearssl SYSTEM PUBLIC "${bearsll_SOURCE_DIR}/inc" PRIVATE "${bearsll_SOURCE_DIR}/src")
3333

3434
# cmake_template for warnings and sanitizers
3535
FetchContent_Populate(
3636
cmake_template
37-
URL https://github.com/cpp-best-practices/cmake_template/archive/refs/heads/main.zip
38-
URL_HASH MD5=c391b4c21eeabac1d5142bcf404c740c)
37+
URL https://github.com/cpp-best-practices/cmake_template/archive/1015c6b88410df411c0cc0413e3e64c33d7a8331.zip
38+
URL_HASH SHA512=b7ad5b05eb21e5cb8159f3bd8b05615ee55489e5ab6a543bfe50ba05c8c8935106a6870b438c97ca3894108650a4a542a810707954cbe6b3beb9c6de82f1bde7)
3939
include(${cmake_template_SOURCE_DIR}/cmake/CompilerWarnings.cmake)
4040
include(${cmake_template_SOURCE_DIR}/cmake/Sanitizers.cmake)
4141

src/dsmr_parser/fields.h

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -71,23 +71,13 @@ struct FixedValue {
7171
template <typename T, const char* _unit, const char* _int_unit>
7272
struct FixedField : ParsedField<T> {
7373
ParseResult<void> parse(const char* str, const char* end) {
74-
// Check if the value is a float value, plus its expected unit type.
75-
ParseResult<uint32_t> res_float = NumParser::parse(3, _unit, str, end);
76-
if (!res_float.err) {
77-
static_cast<T*>(this)->val()._value = res_float.result;
78-
return res_float;
79-
}
80-
// If not, then check for an int value, plus its expected unit type.
81-
// This accomodates for some smart meters that publish int values instead
82-
// of floats. E.g. most meters would publish "1-0:1.8.0(000441.879*kWh)",
74+
// Some smart meters publish int values instead of a float.
75+
// E.g. most meters would publish "1-0:1.8.0(000441.879*kWh)",
8376
// but some use "1-0:1.8.0(000441879*Wh)" instead.
84-
ParseResult<uint32_t> res_int = NumParser::parse(0, _int_unit, str, end);
85-
if (!res_int.err) {
86-
static_cast<T*>(this)->val()._value = res_int.result;
87-
return res_int;
88-
}
89-
// If not, then return the initial error result for the float parsing step.
90-
return res_float;
77+
auto res = NumParser::parse_float_or_int(3, _unit, _int_unit, str, end);
78+
if (!res.err)
79+
static_cast<T*>(this)->val()._value = res.result;
80+
return res;
9181
}
9282

9383
static const char* unit() noexcept { return _unit; }
@@ -199,12 +189,9 @@ struct AveragedFixedField : public FixedField<T, _unit, _int_unit> {
199189
return res;
200190

201191
// parse value (04.329*kW) or (04329*W)
202-
auto monthValue = NumParser::parse(3, _unit, res.next, end);
203-
if (monthValue.err) {
204-
monthValue = NumParser::parse(0, _int_unit, res.next, end);
205-
if (monthValue.err)
206-
return monthValue;
207-
}
192+
auto monthValue = NumParser::parse_float_or_int(3, _unit, _int_unit, res.next, end);
193+
if (monthValue.err)
194+
return monthValue;
208195

209196
average.next = monthValue.next;
210197
average.result += monthValue.result;
@@ -260,14 +247,14 @@ const uint8_t WATER_MBUS_ID = DSMR_WATER_MBUS_ID;
260247
const uint8_t THERMAL_MBUS_ID = DSMR_THERMAL_MBUS_ID;
261248
const uint8_t SUB_MBUS_ID = DSMR_SUB_MBUS_ID;
262249

263-
#define DEFINE_FIELD(fieldname, value_t, obis, field_t, ...) \
264-
struct fieldname : field_t<fieldname __VA_OPT__(, __VA_ARGS__)> { \
265-
value_t fieldname; \
266-
bool fieldname##_present = false; \
267-
static inline constexpr ObisId id = obis; \
268-
static inline constexpr char name[] = #fieldname; \
269-
value_t& val() { return fieldname; } \
270-
bool& present() { return fieldname##_present; } \
250+
#define DEFINE_FIELD(fieldname, value_t, obis, field_t, ...) \
251+
struct fieldname : field_t<fieldname __VA_OPT__(, __VA_ARGS__)> { \
252+
value_t fieldname; \
253+
bool fieldname##_present = false; \
254+
static inline constexpr ObisId id = obis; \
255+
static inline constexpr char name[] = #fieldname; \
256+
value_t& val() { return fieldname; } \
257+
bool& present() { return fieldname##_present; } \
271258
}
272259

273260
// Meter identification. This is not a normal field, but a specially-formatted first line of the message
@@ -514,6 +501,8 @@ DEFINE_FIELD(gas_valve_position, uint8_t, ObisId(0, GAS_MBUS_ID, 24, 4, 0), IntF
514501
// Last 5-minute value (temperature converted), gas delivered to client
515502
// in m3, including decimal values and capture time (Note: 4.x spec has "hourly value")
516503
DEFINE_FIELD(gas_delivered, TimestampedFixedValue, ObisId(0, GAS_MBUS_ID, 24, 2, 1), TimestampedFixedField, units::m3, units::dm3);
504+
// Eneco in the Netherlands has smart meters for their district heating network, which uses the gas_delivered in GJ rather than m3
505+
DEFINE_FIELD(gas_delivered_gj, TimestampedFixedValue, ObisId(0, GAS_MBUS_ID, 24, 2, 1), TimestampedFixedField, units::GJ, units::MJ);
517506
// _BE
518507
DEFINE_FIELD(gas_delivered_be, TimestampedFixedValue, ObisId(0, GAS_MBUS_ID, 24, 2, 3), TimestampedFixedField, units::m3, units::dm3);
519508
DEFINE_FIELD(gas_delivered_text, std::string, ObisId(0, GAS_MBUS_ID, 24, 3, 0), RawField);

src/dsmr_parser/parser.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,18 @@ static constexpr char INVALID_NUMBER[] = "Invalid number";
100100
static constexpr char INVALID_UNIT[] = "Invalid unit";
101101

102102
struct NumParser final {
103+
static ParseResult<uint32_t> parse_float_or_int(const size_t max_decimals, const char* float_unit, const char* int_unit, const char* str, const char* end) {
104+
auto float_res = NumParser::parse(max_decimals, float_unit, str, end);
105+
if (!float_res.err)
106+
return float_res;
107+
108+
auto int_res = NumParser::parse(0, int_unit, str, end);
109+
if (!int_res.err)
110+
return int_res;
111+
112+
return float_res;
113+
}
114+
103115
static ParseResult<uint32_t> parse(size_t max_decimals, const char* unit, const char* str, const char* end) {
104116
ParseResult<uint32_t> res;
105117
if (str >= end || *str != '(')

tests/parser_test.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ TEST_CASE("Whitespace after OBIS ID") {
614614
"0-1:24.2.1 (000101000000W)(00000000.0000)\r\n"
615615
"!";
616616
ParsedData<gas_delivered> data;
617-
const auto& res = P1Parser::parse(data, msg, std::size(msg), /*unknown_error=*/false, /*check_crc=*/false);
617+
const auto& res = P1Parser::parse(data, msg, std::size(msg), /*unknown_error=*/ true, /*check_crc=*/false);
618618
REQUIRE(std::string(res.err) == "Missing (");
619619
}
620620

@@ -624,7 +624,7 @@ TEST_CASE("Use integer fallback unit") {
624624
"1-0:14.7.0(50*Hz)\r\n"
625625
"!";
626626
ParsedData<gas_delivered, frequency> data;
627-
P1Parser::parse(data, msg, std::size(msg), /*unknown_error=*/false, /*check_crc=*/false);
627+
P1Parser::parse(data, msg, std::size(msg), /*unknown_error=*/ true, /*check_crc=*/false);
628628
REQUIRE(data.gas_delivered == 0.012f);
629629
REQUIRE(data.frequency == 0.05f);
630630
}
@@ -655,3 +655,15 @@ TEST_CASE("AveragedFixedField works properly for an empty array") {
655655
REQUIRE(data.active_energy_import_maximum_demand_last_13_months.val() == 0.0f);
656656
REQUIRE(data.energy_delivered_tariff1.val() == 1.0f);
657657
}
658+
659+
TEST_CASE("Should parse gas_delivered_gj field") {
660+
const auto& msg = "/identification\r\n"
661+
"0-1:24.2.1(251129203200W)(3.829*GJ)\r\n"
662+
"!";
663+
664+
ParsedData<gas_delivered_gj> data;
665+
666+
const auto& res = P1Parser::parse(data, msg, std::size(msg), /* unknown_error */ true, /* check_crc */ false);
667+
REQUIRE(res.err == nullptr);
668+
REQUIRE(data.gas_delivered_gj == 3.829f);
669+
}

0 commit comments

Comments
 (0)