From 3fafb572289f09bfc7a4a2bf66ef21f380bb4a0b Mon Sep 17 00:00:00 2001 From: Sebastian Goodrick Date: Mon, 29 Jun 2026 21:36:24 +0200 Subject: [PATCH 1/4] Add Switzerland OBIS fields for channel 1-1 (e.g. EWL Luzern AM550) Adds 34 missing DEFINE_FIELD entries for the AM550 smart meter used by EWL Luzern: total energy (1-1:1.8.0 / 2.8.0), reactive energy totals (3.8.0 / 4.8.0), reactive energy by quadrant Q1-Q4 (5-8.8.0-2), per-phase active and reactive power (1-1:21-64.7.0), and maximum power demand with timestamp (1-1:1.6.0 / 2.6.0). --- src/dsmr_parser/fields.h | 44 ++++++++++ tests/parser_test.cpp | 170 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+) diff --git a/src/dsmr_parser/fields.h b/src/dsmr_parser/fields.h index 79f0b13..91356cf 100644 --- a/src/dsmr_parser/fields.h +++ b/src/dsmr_parser/fields.h @@ -330,14 +330,58 @@ DEFINE_FIELD(reactive_energy_returned_tariff3, FixedValue, ObisId(1, 0, 4, 8, 3) DEFINE_FIELD(reactive_energy_returned_tariff4, FixedValue, ObisId(1, 0, 4, 8, 4), FixedField, units::kvarh, units::varh); // Specific fields used for Switzerland +// Meter Reading electricity delivered to client (total, no tariff) in 0,001 kWh +DEFINE_FIELD(energy_delivered_ch, FixedValue, ObisId(1, 1, 1, 8, 0), FixedField, units::kWh, units::Wh); // Meter Reading electricity delivered to client (Tariff 1) in 0,001 kWh DEFINE_FIELD(energy_delivered_tariff1_ch, FixedValue, ObisId(1, 1, 1, 8, 1), FixedField, units::kWh, units::Wh); // Meter Reading electricity delivered to client (Tariff 2) in 0,001 kWh DEFINE_FIELD(energy_delivered_tariff2_ch, FixedValue, ObisId(1, 1, 1, 8, 2), FixedField, units::kWh, units::Wh); +// Meter Reading electricity delivered by client (total, no tariff) in 0,001 kWh +DEFINE_FIELD(energy_returned_ch, FixedValue, ObisId(1, 1, 2, 8, 0), FixedField, units::kWh, units::Wh); // Meter Reading electricity delivered by client (Tariff 1) in 0,001 kWh DEFINE_FIELD(energy_returned_tariff1_ch, FixedValue, ObisId(1, 1, 2, 8, 1), FixedField, units::kWh, units::Wh); // Meter Reading electricity delivered by client (Tariff 2) in 0,001 kWh DEFINE_FIELD(energy_returned_tariff2_ch, FixedValue, ObisId(1, 1, 2, 8, 2), FixedField, units::kWh, units::Wh); +// Reactive energy +R (total, no tariff) in 0,001 kvarh +DEFINE_FIELD(total_imported_energy_ch, FixedValue, ObisId(1, 1, 3, 8, 0), FixedField, units::kvarh, units::varh); +// Reactive energy -R (total, no tariff) in 0,001 kvarh +DEFINE_FIELD(total_exported_energy_ch, FixedValue, ObisId(1, 1, 4, 8, 0), FixedField, units::kvarh, units::varh); +// Reactive energy Q1 +Ri (total / Tariff 1 / Tariff 2) in 0,001 kvarh +DEFINE_FIELD(reactive_energy_q1_ch, FixedValue, ObisId(1, 1, 5, 8, 0), FixedField, units::kvarh, units::varh); +DEFINE_FIELD(reactive_energy_q1_tariff1_ch, FixedValue, ObisId(1, 1, 5, 8, 1), FixedField, units::kvarh, units::varh); +DEFINE_FIELD(reactive_energy_q1_tariff2_ch, FixedValue, ObisId(1, 1, 5, 8, 2), FixedField, units::kvarh, units::varh); +// Reactive energy Q2 +Rc (total / Tariff 1 / Tariff 2) in 0,001 kvarh +DEFINE_FIELD(reactive_energy_q2_ch, FixedValue, ObisId(1, 1, 6, 8, 0), FixedField, units::kvarh, units::varh); +DEFINE_FIELD(reactive_energy_q2_tariff1_ch, FixedValue, ObisId(1, 1, 6, 8, 1), FixedField, units::kvarh, units::varh); +DEFINE_FIELD(reactive_energy_q2_tariff2_ch, FixedValue, ObisId(1, 1, 6, 8, 2), FixedField, units::kvarh, units::varh); +// Reactive energy Q3 -Ri (total / Tariff 1 / Tariff 2) in 0,001 kvarh +DEFINE_FIELD(reactive_energy_q3_ch, FixedValue, ObisId(1, 1, 7, 8, 0), FixedField, units::kvarh, units::varh); +DEFINE_FIELD(reactive_energy_q3_tariff1_ch, FixedValue, ObisId(1, 1, 7, 8, 1), FixedField, units::kvarh, units::varh); +DEFINE_FIELD(reactive_energy_q3_tariff2_ch, FixedValue, ObisId(1, 1, 7, 8, 2), FixedField, units::kvarh, units::varh); +// Reactive energy Q4 -Rc (total / Tariff 1 / Tariff 2) in 0,001 kvarh +DEFINE_FIELD(reactive_energy_q4_ch, FixedValue, ObisId(1, 1, 8, 8, 0), FixedField, units::kvarh, units::varh); +DEFINE_FIELD(reactive_energy_q4_tariff1_ch, FixedValue, ObisId(1, 1, 8, 8, 1), FixedField, units::kvarh, units::varh); +DEFINE_FIELD(reactive_energy_q4_tariff2_ch, FixedValue, ObisId(1, 1, 8, 8, 2), FixedField, units::kvarh, units::varh); +// Instantaneous active power L1 (+P) / L2 / L3 in W resolution +DEFINE_FIELD(power_delivered_l1_ch, FixedValue, ObisId(1, 1, 21, 7, 0), FixedField, units::kW, units::W); +DEFINE_FIELD(power_delivered_l2_ch, FixedValue, ObisId(1, 1, 41, 7, 0), FixedField, units::kW, units::W); +DEFINE_FIELD(power_delivered_l3_ch, FixedValue, ObisId(1, 1, 61, 7, 0), FixedField, units::kW, units::W); +// Instantaneous active power L1 (-P) / L2 / L3 in W resolution +DEFINE_FIELD(power_returned_l1_ch, FixedValue, ObisId(1, 1, 22, 7, 0), FixedField, units::kW, units::W); +DEFINE_FIELD(power_returned_l2_ch, FixedValue, ObisId(1, 1, 42, 7, 0), FixedField, units::kW, units::W); +DEFINE_FIELD(power_returned_l3_ch, FixedValue, ObisId(1, 1, 62, 7, 0), FixedField, units::kW, units::W); +// Instantaneous reactive power L1 (+Q) / L2 / L3 in var resolution +DEFINE_FIELD(reactive_power_delivered_l1_ch, FixedValue, ObisId(1, 1, 23, 7, 0), FixedField, units::kvar, units::var); +DEFINE_FIELD(reactive_power_delivered_l2_ch, FixedValue, ObisId(1, 1, 43, 7, 0), FixedField, units::kvar, units::var); +DEFINE_FIELD(reactive_power_delivered_l3_ch, FixedValue, ObisId(1, 1, 63, 7, 0), FixedField, units::kvar, units::var); +// Instantaneous reactive power L1 (-Q) / L2 / L3 in var resolution +DEFINE_FIELD(reactive_power_returned_l1_ch, FixedValue, ObisId(1, 1, 24, 7, 0), FixedField, units::kvar, units::var); +DEFINE_FIELD(reactive_power_returned_l2_ch, FixedValue, ObisId(1, 1, 44, 7, 0), FixedField, units::kvar, units::var); +DEFINE_FIELD(reactive_power_returned_l3_ch, FixedValue, ObisId(1, 1, 64, 7, 0), FixedField, units::kvar, units::var); +// Maximum active power demand delivered (+P) with timestamp [kW] +DEFINE_FIELD(maximum_power_delivered_ch, TimestampedFixedValue, ObisId(1, 1, 1, 6, 0), TimestampedFixedField, units::kW, units::W); +// Maximum active power demand returned (-P) with timestamp [kW] +DEFINE_FIELD(maximum_power_returned_ch, TimestampedFixedValue, ObisId(1, 1, 2, 6, 0), TimestampedFixedField, units::kW, units::W); // Specific fields used for Israel // Meter Reading electricity delivered to client (Tariff 1) in 0,001 kWh diff --git a/tests/parser_test.cpp b/tests/parser_test.cpp index aa0c964..943ba99 100644 --- a/tests/parser_test.cpp +++ b/tests/parser_test.cpp @@ -800,3 +800,173 @@ TEST_CASE_FIXTURE(LogFixture, "ParsedData without any fields") { REQUIRE(res); REQUIRE(data.all_present()); } + +TEST_CASE_FIXTURE(LogFixture, "Should parse AM550 per-phase active power (1-1:2x/4x/6x.7.0)") { + const auto& msg = "/ISK5AM550-1234\r\n" + "\r\n" + "1-1:21.7.0(00.332*kW)\r\n" + "1-1:41.7.0(00.430*kW)\r\n" + "1-1:61.7.0(00.000*kW)\r\n" + "1-1:22.7.0(00.000*kW)\r\n" + "1-1:42.7.0(00.100*kW)\r\n" + "1-1:62.7.0(00.050*kW)\r\n" + "!"; + + ParsedData< + /* String */ identification, + /* FixedValue */ power_delivered_l1_ch, + /* FixedValue */ power_delivered_l2_ch, + /* FixedValue */ power_delivered_l3_ch, + /* FixedValue */ power_returned_l1_ch, + /* FixedValue */ power_returned_l2_ch, + /* FixedValue */ power_returned_l3_ch> + data; + + const auto& res = DsmrParser::parse(data, *DsmrUnencryptedTelegram::from_bytes(msg, false)); + REQUIRE(res); + REQUIRE(data.power_delivered_l1_ch == 0.332f); + REQUIRE(data.power_delivered_l2_ch == 0.430f); + REQUIRE(data.power_delivered_l3_ch == 0.0f); + REQUIRE(data.power_returned_l1_ch == 0.0f); + REQUIRE(data.power_returned_l2_ch == 0.100f); + REQUIRE(data.power_returned_l3_ch == 0.050f); +} + +TEST_CASE_FIXTURE(LogFixture, "Should parse AM550 per-phase reactive power (1-1:2x/4x/6x.7.0 reactive)") { + const auto& msg = "/ISK5AM550-1234\r\n" + "\r\n" + "1-1:23.7.0(00.120*kvar)\r\n" + "1-1:43.7.0(00.080*kvar)\r\n" + "1-1:63.7.0(00.000*kvar)\r\n" + "1-1:24.7.0(00.000*kvar)\r\n" + "1-1:44.7.0(00.010*kvar)\r\n" + "1-1:64.7.0(00.005*kvar)\r\n" + "!"; + + ParsedData< + /* String */ identification, + /* FixedValue */ reactive_power_delivered_l1_ch, + /* FixedValue */ reactive_power_delivered_l2_ch, + /* FixedValue */ reactive_power_delivered_l3_ch, + /* FixedValue */ reactive_power_returned_l1_ch, + /* FixedValue */ reactive_power_returned_l2_ch, + /* FixedValue */ reactive_power_returned_l3_ch> + data; + + const auto& res = DsmrParser::parse(data, *DsmrUnencryptedTelegram::from_bytes(msg, false)); + REQUIRE(res); + REQUIRE(data.reactive_power_delivered_l1_ch == 0.120f); + REQUIRE(data.reactive_power_delivered_l2_ch == 0.080f); + REQUIRE(data.reactive_power_delivered_l3_ch == 0.0f); + REQUIRE(data.reactive_power_returned_l1_ch == 0.0f); + REQUIRE(data.reactive_power_returned_l2_ch == 0.010f); + REQUIRE(data.reactive_power_returned_l3_ch == 0.005f); +} + +TEST_CASE_FIXTURE(LogFixture, "Should parse AM550 active and reactive energy totals (1-1:1-4.8.0)") { + const auto& msg = "/ISK5AM550-1234\r\n" + "\r\n" + "1-1:1.8.0(001234.567*kWh)\r\n" + "1-1:2.8.0(000012.345*kWh)\r\n" + "1-1:3.8.0(000456.789*kvarh)\r\n" + "1-1:4.8.0(000000.000*kvarh)\r\n" + "!"; + + ParsedData< + /* String */ identification, + /* FixedValue */ energy_delivered_ch, + /* FixedValue */ energy_returned_ch, + /* FixedValue */ total_imported_energy_ch, + /* FixedValue */ total_exported_energy_ch> + data; + + const auto& res = DsmrParser::parse(data, *DsmrUnencryptedTelegram::from_bytes(msg, false)); + REQUIRE(res); + REQUIRE(data.energy_delivered_ch == 1234.567f); + REQUIRE(data.energy_returned_ch == 12.345f); + REQUIRE(data.total_imported_energy_ch == 456.789f); + REQUIRE(data.total_exported_energy_ch == 0.0f); +} + +TEST_CASE_FIXTURE(LogFixture, "Should parse AM550 active energy total as integer Wh (1-1:1.8.0)") { + const auto& msg = "/ISK5AM550-1234\r\n" + "\r\n" + "1-1:1.8.0(001234567*Wh)\r\n" + "!"; + + ParsedData data; + + const auto& res = DsmrParser::parse(data, *DsmrUnencryptedTelegram::from_bytes(msg, false)); + REQUIRE(res); + REQUIRE(data.energy_delivered_ch == 1234.567f); // 1234567 Wh = 1234.567 kWh +} + +TEST_CASE_FIXTURE(LogFixture, "Should parse AM550 reactive energy by quadrant with tariffs (1-1:5-8.8.x)") { + const auto& msg = "/ISK5AM550-1234\r\n" + "\r\n" + "1-1:5.8.0(000100.000*kvarh)\r\n" + "1-1:5.8.1(000060.000*kvarh)\r\n" + "1-1:5.8.2(000040.000*kvarh)\r\n" + "1-1:6.8.0(000010.000*kvarh)\r\n" + "1-1:6.8.1(000005.000*kvarh)\r\n" + "1-1:6.8.2(000005.000*kvarh)\r\n" + "1-1:7.8.0(000000.000*kvarh)\r\n" + "1-1:7.8.1(000000.000*kvarh)\r\n" + "1-1:7.8.2(000000.000*kvarh)\r\n" + "1-1:8.8.0(000002.500*kvarh)\r\n" + "1-1:8.8.1(000001.000*kvarh)\r\n" + "1-1:8.8.2(000001.500*kvarh)\r\n" + "!"; + + ParsedData< + /* String */ identification, + /* FixedValue */ reactive_energy_q1_ch, + /* FixedValue */ reactive_energy_q1_tariff1_ch, + /* FixedValue */ reactive_energy_q1_tariff2_ch, + /* FixedValue */ reactive_energy_q2_ch, + /* FixedValue */ reactive_energy_q2_tariff1_ch, + /* FixedValue */ reactive_energy_q2_tariff2_ch, + /* FixedValue */ reactive_energy_q3_ch, + /* FixedValue */ reactive_energy_q3_tariff1_ch, + /* FixedValue */ reactive_energy_q3_tariff2_ch, + /* FixedValue */ reactive_energy_q4_ch, + /* FixedValue */ reactive_energy_q4_tariff1_ch, + /* FixedValue */ reactive_energy_q4_tariff2_ch> + data; + + const auto& res = DsmrParser::parse(data, *DsmrUnencryptedTelegram::from_bytes(msg, false)); + REQUIRE(res); + REQUIRE(data.reactive_energy_q1_ch == 100.0f); + REQUIRE(data.reactive_energy_q1_tariff1_ch == 60.0f); + REQUIRE(data.reactive_energy_q1_tariff2_ch == 40.0f); + REQUIRE(data.reactive_energy_q2_ch == 10.0f); + REQUIRE(data.reactive_energy_q2_tariff1_ch == 5.0f); + REQUIRE(data.reactive_energy_q2_tariff2_ch == 5.0f); + REQUIRE(data.reactive_energy_q3_ch == 0.0f); + REQUIRE(data.reactive_energy_q3_tariff1_ch == 0.0f); + REQUIRE(data.reactive_energy_q3_tariff2_ch == 0.0f); + REQUIRE(data.reactive_energy_q4_ch == 2.5f); + REQUIRE(data.reactive_energy_q4_tariff1_ch == 1.0f); + REQUIRE(data.reactive_energy_q4_tariff2_ch == 1.5f); +} + +TEST_CASE_FIXTURE(LogFixture, "Should parse AM550 maximum power demand with timestamp (1-1:1-2.6.0)") { + const auto& msg = "/ISK5AM550-1234\r\n" + "\r\n" + "1-1:1.6.0(250601000000W)(001.234*kW)\r\n" + "1-1:2.6.0(250601120000W)(000.000*kW)\r\n" + "!"; + + ParsedData< + /* String */ identification, + /* TimestampedFixedValue */ maximum_power_delivered_ch, + /* TimestampedFixedValue */ maximum_power_returned_ch> + data; + + const auto& res = DsmrParser::parse(data, *DsmrUnencryptedTelegram::from_bytes(msg, false)); + REQUIRE(res); + REQUIRE(data.maximum_power_delivered_ch == 1.234f); + REQUIRE(data.maximum_power_delivered_ch.timestamp == "250601000000W"); + REQUIRE(data.maximum_power_returned_ch == 0.0f); + REQUIRE(data.maximum_power_returned_ch.timestamp == "250601120000W"); +} From f968281b911c660b9fc7b8a28707f1e7de985168 Mon Sep 17 00:00:00 2001 From: PolarGoose <35307286+PolarGoose@users.noreply.github.com> Date: Sat, 4 Jul 2026 14:45:24 +0200 Subject: [PATCH 2/4] Add documentation --- ...wl-luzern.ch energy provider Switzerland.pdf | Bin 0 -> 35159 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 specs/ewl-luzern.ch energy provider Switzerland.pdf diff --git a/specs/ewl-luzern.ch energy provider Switzerland.pdf b/specs/ewl-luzern.ch energy provider Switzerland.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7a9d712dd2a2f9541e6f2b8ae6110a141ad8ae5d GIT binary patch literal 35159 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 yQ!&45f>U99$~HFO$r=y{XSnBp2M01cP Date: Sat, 4 Jul 2026 14:55:00 +0200 Subject: [PATCH 3/4] Move all added tests into the main test --- tests/parser_test.cpp | 262 +++++++++++++++--------------------------- 1 file changed, 92 insertions(+), 170 deletions(-) diff --git a/tests/parser_test.cpp b/tests/parser_test.cpp index 943ba99..86cb800 100644 --- a/tests/parser_test.cpp +++ b/tests/parser_test.cpp @@ -58,6 +58,36 @@ TEST_CASE_FIXTURE(LogFixture, "Should parse all fields in the DSMR message corre "1-0:0.2.8(1.0.smth smth-123)\r\n" "1-1:0.2.0((ER12)\r\n" "1-1:0.2.8(ER13))\r\n" + "1-1:1.8.0(001234567*Wh)\r\n" + "1-1:2.8.0(000012.345*kWh)\r\n" + "1-1:3.8.0(000456.789*kvarh)\r\n" + "1-1:4.8.0(000000.000*kvarh)\r\n" + "1-1:5.8.0(000100.000*kvarh)\r\n" + "1-1:5.8.1(000060.000*kvarh)\r\n" + "1-1:5.8.2(000040.000*kvarh)\r\n" + "1-1:6.8.0(000010.000*kvarh)\r\n" + "1-1:6.8.1(000005.000*kvarh)\r\n" + "1-1:6.8.2(000005.000*kvarh)\r\n" + "1-1:7.8.0(000000.000*kvarh)\r\n" + "1-1:7.8.1(000000.000*kvarh)\r\n" + "1-1:7.8.2(000000.000*kvarh)\r\n" + "1-1:8.8.0(000002.500*kvarh)\r\n" + "1-1:8.8.1(000001.000*kvarh)\r\n" + "1-1:8.8.2(000001.500*kvarh)\r\n" + "1-1:21.7.0(00.332*kW)\r\n" + "1-1:41.7.0(00.430*kW)\r\n" + "1-1:61.7.0(00.000*kW)\r\n" + "1-1:22.7.0(00.000*kW)\r\n" + "1-1:42.7.0(00.100*kW)\r\n" + "1-1:62.7.0(00.050*kW)\r\n" + "1-1:23.7.0(00.120*kvar)\r\n" + "1-1:43.7.0(00.080*kvar)\r\n" + "1-1:63.7.0(00.000*kvar)\r\n" + "1-1:24.7.0(00.000*kvar)\r\n" + "1-1:44.7.0(00.010*kvar)\r\n" + "1-1:64.7.0(00.005*kvar)\r\n" + "1-1:1.6.0(250601000000W)(001.234*kW)\r\n" + "1-1:2.6.0(250601120000W)(000.000*kW)\r\n" "0-1:24.4.0(1)\r\n" "1-0:16.24.0(-03.618*kW)\r\n" "1-0:13.7.0(0.998)\r\n" @@ -131,6 +161,36 @@ TEST_CASE_FIXTURE(LogFixture, "Should parse all fields in the DSMR message corre /* String */ fw_core_checksum, /* String */ fw_module_version, /* String */ fw_module_checksum, + /* FixedValue */ energy_delivered_ch, + /* FixedValue */ energy_returned_ch, + /* FixedValue */ total_imported_energy_ch, + /* FixedValue */ total_exported_energy_ch, + /* FixedValue */ reactive_energy_q1_ch, + /* FixedValue */ reactive_energy_q1_tariff1_ch, + /* FixedValue */ reactive_energy_q1_tariff2_ch, + /* FixedValue */ reactive_energy_q2_ch, + /* FixedValue */ reactive_energy_q2_tariff1_ch, + /* FixedValue */ reactive_energy_q2_tariff2_ch, + /* FixedValue */ reactive_energy_q3_ch, + /* FixedValue */ reactive_energy_q3_tariff1_ch, + /* FixedValue */ reactive_energy_q3_tariff2_ch, + /* FixedValue */ reactive_energy_q4_ch, + /* FixedValue */ reactive_energy_q4_tariff1_ch, + /* FixedValue */ reactive_energy_q4_tariff2_ch, + /* FixedValue */ power_delivered_l1_ch, + /* FixedValue */ power_delivered_l2_ch, + /* FixedValue */ power_delivered_l3_ch, + /* FixedValue */ power_returned_l1_ch, + /* FixedValue */ power_returned_l2_ch, + /* FixedValue */ power_returned_l3_ch, + /* FixedValue */ reactive_power_delivered_l1_ch, + /* FixedValue */ reactive_power_delivered_l2_ch, + /* FixedValue */ reactive_power_delivered_l3_ch, + /* FixedValue */ reactive_power_returned_l1_ch, + /* FixedValue */ reactive_power_returned_l2_ch, + /* FixedValue */ reactive_power_returned_l3_ch, + /* TimestampedFixedValue */ maximum_power_delivered_ch, + /* TimestampedFixedValue */ maximum_power_returned_ch, /* FixedValue */ active_demand_net, /* FixedValue */ power_factor, /* FixedValue */ power_factor_l1, @@ -194,6 +254,38 @@ TEST_CASE_FIXTURE(LogFixture, "Should parse all fields in the DSMR message corre REQUIRE(data.fw_core_checksum == "1.0.smth smth-123"); REQUIRE(data.fw_module_version == "(ER12"); REQUIRE(data.fw_module_checksum == "ER13)"); + REQUIRE(data.energy_delivered_ch == 1234.567f); // 1234567 Wh = 1234.567 kWh + REQUIRE(data.energy_returned_ch == 12.345f); + REQUIRE(data.total_imported_energy_ch == 456.789f); + REQUIRE(data.total_exported_energy_ch == 0.0f); + REQUIRE(data.reactive_energy_q1_ch == 100.0f); + REQUIRE(data.reactive_energy_q1_tariff1_ch == 60.0f); + REQUIRE(data.reactive_energy_q1_tariff2_ch == 40.0f); + REQUIRE(data.reactive_energy_q2_ch == 10.0f); + REQUIRE(data.reactive_energy_q2_tariff1_ch == 5.0f); + REQUIRE(data.reactive_energy_q2_tariff2_ch == 5.0f); + REQUIRE(data.reactive_energy_q3_ch == 0.0f); + REQUIRE(data.reactive_energy_q3_tariff1_ch == 0.0f); + REQUIRE(data.reactive_energy_q3_tariff2_ch == 0.0f); + REQUIRE(data.reactive_energy_q4_ch == 2.5f); + REQUIRE(data.reactive_energy_q4_tariff1_ch == 1.0f); + REQUIRE(data.reactive_energy_q4_tariff2_ch == 1.5f); + REQUIRE(data.power_delivered_l1_ch == 0.332f); + REQUIRE(data.power_delivered_l2_ch == 0.430f); + REQUIRE(data.power_delivered_l3_ch == 0.0f); + REQUIRE(data.power_returned_l1_ch == 0.0f); + REQUIRE(data.power_returned_l2_ch == 0.100f); + REQUIRE(data.power_returned_l3_ch == 0.050f); + REQUIRE(data.reactive_power_delivered_l1_ch == 0.120f); + REQUIRE(data.reactive_power_delivered_l2_ch == 0.080f); + REQUIRE(data.reactive_power_delivered_l3_ch == 0.0f); + REQUIRE(data.reactive_power_returned_l1_ch == 0.0f); + REQUIRE(data.reactive_power_returned_l2_ch == 0.010f); + REQUIRE(data.reactive_power_returned_l3_ch == 0.005f); + REQUIRE(data.maximum_power_delivered_ch == 1.234f); + REQUIRE(data.maximum_power_delivered_ch.timestamp == "250601000000W"); + REQUIRE(data.maximum_power_returned_ch == 0.0f); + REQUIRE(data.maximum_power_returned_ch.timestamp == "250601120000W"); REQUIRE(data.active_demand_net == -3.618f); REQUIRE(data.power_factor == 0.998f); REQUIRE(data.power_factor_l1 == 0.975f); @@ -800,173 +892,3 @@ TEST_CASE_FIXTURE(LogFixture, "ParsedData without any fields") { REQUIRE(res); REQUIRE(data.all_present()); } - -TEST_CASE_FIXTURE(LogFixture, "Should parse AM550 per-phase active power (1-1:2x/4x/6x.7.0)") { - const auto& msg = "/ISK5AM550-1234\r\n" - "\r\n" - "1-1:21.7.0(00.332*kW)\r\n" - "1-1:41.7.0(00.430*kW)\r\n" - "1-1:61.7.0(00.000*kW)\r\n" - "1-1:22.7.0(00.000*kW)\r\n" - "1-1:42.7.0(00.100*kW)\r\n" - "1-1:62.7.0(00.050*kW)\r\n" - "!"; - - ParsedData< - /* String */ identification, - /* FixedValue */ power_delivered_l1_ch, - /* FixedValue */ power_delivered_l2_ch, - /* FixedValue */ power_delivered_l3_ch, - /* FixedValue */ power_returned_l1_ch, - /* FixedValue */ power_returned_l2_ch, - /* FixedValue */ power_returned_l3_ch> - data; - - const auto& res = DsmrParser::parse(data, *DsmrUnencryptedTelegram::from_bytes(msg, false)); - REQUIRE(res); - REQUIRE(data.power_delivered_l1_ch == 0.332f); - REQUIRE(data.power_delivered_l2_ch == 0.430f); - REQUIRE(data.power_delivered_l3_ch == 0.0f); - REQUIRE(data.power_returned_l1_ch == 0.0f); - REQUIRE(data.power_returned_l2_ch == 0.100f); - REQUIRE(data.power_returned_l3_ch == 0.050f); -} - -TEST_CASE_FIXTURE(LogFixture, "Should parse AM550 per-phase reactive power (1-1:2x/4x/6x.7.0 reactive)") { - const auto& msg = "/ISK5AM550-1234\r\n" - "\r\n" - "1-1:23.7.0(00.120*kvar)\r\n" - "1-1:43.7.0(00.080*kvar)\r\n" - "1-1:63.7.0(00.000*kvar)\r\n" - "1-1:24.7.0(00.000*kvar)\r\n" - "1-1:44.7.0(00.010*kvar)\r\n" - "1-1:64.7.0(00.005*kvar)\r\n" - "!"; - - ParsedData< - /* String */ identification, - /* FixedValue */ reactive_power_delivered_l1_ch, - /* FixedValue */ reactive_power_delivered_l2_ch, - /* FixedValue */ reactive_power_delivered_l3_ch, - /* FixedValue */ reactive_power_returned_l1_ch, - /* FixedValue */ reactive_power_returned_l2_ch, - /* FixedValue */ reactive_power_returned_l3_ch> - data; - - const auto& res = DsmrParser::parse(data, *DsmrUnencryptedTelegram::from_bytes(msg, false)); - REQUIRE(res); - REQUIRE(data.reactive_power_delivered_l1_ch == 0.120f); - REQUIRE(data.reactive_power_delivered_l2_ch == 0.080f); - REQUIRE(data.reactive_power_delivered_l3_ch == 0.0f); - REQUIRE(data.reactive_power_returned_l1_ch == 0.0f); - REQUIRE(data.reactive_power_returned_l2_ch == 0.010f); - REQUIRE(data.reactive_power_returned_l3_ch == 0.005f); -} - -TEST_CASE_FIXTURE(LogFixture, "Should parse AM550 active and reactive energy totals (1-1:1-4.8.0)") { - const auto& msg = "/ISK5AM550-1234\r\n" - "\r\n" - "1-1:1.8.0(001234.567*kWh)\r\n" - "1-1:2.8.0(000012.345*kWh)\r\n" - "1-1:3.8.0(000456.789*kvarh)\r\n" - "1-1:4.8.0(000000.000*kvarh)\r\n" - "!"; - - ParsedData< - /* String */ identification, - /* FixedValue */ energy_delivered_ch, - /* FixedValue */ energy_returned_ch, - /* FixedValue */ total_imported_energy_ch, - /* FixedValue */ total_exported_energy_ch> - data; - - const auto& res = DsmrParser::parse(data, *DsmrUnencryptedTelegram::from_bytes(msg, false)); - REQUIRE(res); - REQUIRE(data.energy_delivered_ch == 1234.567f); - REQUIRE(data.energy_returned_ch == 12.345f); - REQUIRE(data.total_imported_energy_ch == 456.789f); - REQUIRE(data.total_exported_energy_ch == 0.0f); -} - -TEST_CASE_FIXTURE(LogFixture, "Should parse AM550 active energy total as integer Wh (1-1:1.8.0)") { - const auto& msg = "/ISK5AM550-1234\r\n" - "\r\n" - "1-1:1.8.0(001234567*Wh)\r\n" - "!"; - - ParsedData data; - - const auto& res = DsmrParser::parse(data, *DsmrUnencryptedTelegram::from_bytes(msg, false)); - REQUIRE(res); - REQUIRE(data.energy_delivered_ch == 1234.567f); // 1234567 Wh = 1234.567 kWh -} - -TEST_CASE_FIXTURE(LogFixture, "Should parse AM550 reactive energy by quadrant with tariffs (1-1:5-8.8.x)") { - const auto& msg = "/ISK5AM550-1234\r\n" - "\r\n" - "1-1:5.8.0(000100.000*kvarh)\r\n" - "1-1:5.8.1(000060.000*kvarh)\r\n" - "1-1:5.8.2(000040.000*kvarh)\r\n" - "1-1:6.8.0(000010.000*kvarh)\r\n" - "1-1:6.8.1(000005.000*kvarh)\r\n" - "1-1:6.8.2(000005.000*kvarh)\r\n" - "1-1:7.8.0(000000.000*kvarh)\r\n" - "1-1:7.8.1(000000.000*kvarh)\r\n" - "1-1:7.8.2(000000.000*kvarh)\r\n" - "1-1:8.8.0(000002.500*kvarh)\r\n" - "1-1:8.8.1(000001.000*kvarh)\r\n" - "1-1:8.8.2(000001.500*kvarh)\r\n" - "!"; - - ParsedData< - /* String */ identification, - /* FixedValue */ reactive_energy_q1_ch, - /* FixedValue */ reactive_energy_q1_tariff1_ch, - /* FixedValue */ reactive_energy_q1_tariff2_ch, - /* FixedValue */ reactive_energy_q2_ch, - /* FixedValue */ reactive_energy_q2_tariff1_ch, - /* FixedValue */ reactive_energy_q2_tariff2_ch, - /* FixedValue */ reactive_energy_q3_ch, - /* FixedValue */ reactive_energy_q3_tariff1_ch, - /* FixedValue */ reactive_energy_q3_tariff2_ch, - /* FixedValue */ reactive_energy_q4_ch, - /* FixedValue */ reactive_energy_q4_tariff1_ch, - /* FixedValue */ reactive_energy_q4_tariff2_ch> - data; - - const auto& res = DsmrParser::parse(data, *DsmrUnencryptedTelegram::from_bytes(msg, false)); - REQUIRE(res); - REQUIRE(data.reactive_energy_q1_ch == 100.0f); - REQUIRE(data.reactive_energy_q1_tariff1_ch == 60.0f); - REQUIRE(data.reactive_energy_q1_tariff2_ch == 40.0f); - REQUIRE(data.reactive_energy_q2_ch == 10.0f); - REQUIRE(data.reactive_energy_q2_tariff1_ch == 5.0f); - REQUIRE(data.reactive_energy_q2_tariff2_ch == 5.0f); - REQUIRE(data.reactive_energy_q3_ch == 0.0f); - REQUIRE(data.reactive_energy_q3_tariff1_ch == 0.0f); - REQUIRE(data.reactive_energy_q3_tariff2_ch == 0.0f); - REQUIRE(data.reactive_energy_q4_ch == 2.5f); - REQUIRE(data.reactive_energy_q4_tariff1_ch == 1.0f); - REQUIRE(data.reactive_energy_q4_tariff2_ch == 1.5f); -} - -TEST_CASE_FIXTURE(LogFixture, "Should parse AM550 maximum power demand with timestamp (1-1:1-2.6.0)") { - const auto& msg = "/ISK5AM550-1234\r\n" - "\r\n" - "1-1:1.6.0(250601000000W)(001.234*kW)\r\n" - "1-1:2.6.0(250601120000W)(000.000*kW)\r\n" - "!"; - - ParsedData< - /* String */ identification, - /* TimestampedFixedValue */ maximum_power_delivered_ch, - /* TimestampedFixedValue */ maximum_power_returned_ch> - data; - - const auto& res = DsmrParser::parse(data, *DsmrUnencryptedTelegram::from_bytes(msg, false)); - REQUIRE(res); - REQUIRE(data.maximum_power_delivered_ch == 1.234f); - REQUIRE(data.maximum_power_delivered_ch.timestamp == "250601000000W"); - REQUIRE(data.maximum_power_returned_ch == 0.0f); - REQUIRE(data.maximum_power_returned_ch.timestamp == "250601120000W"); -} From b6d7c6c417039f8d4b449464d2f5cb4886f45e8d Mon Sep 17 00:00:00 2001 From: PolarGoose <35307286+PolarGoose@users.noreply.github.com> Date: Sat, 4 Jul 2026 14:59:24 +0200 Subject: [PATCH 4/4] Increase version to `1.10` --- library.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.json b/library.json index bbcb2db..b83c709 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "dsmr_parser", - "version": "1.9.0", + "version": "1.10.0", "description": "A parser for Dutch Smart Meter Requirements (DSMR) telegrams. Fork of arduino-dsmr. Doesn't depend on the Arduino framework and has many bug fixes and code quality improvements. Supports encrypted DSMR packets.", "keywords": "dsmr", "repository": {