Skip to content

Commit fccbd17

Browse files
authored
Improve API. Various refactoring (#5)
1 parent b50d174 commit fccbd17

19 files changed

Lines changed: 676 additions & 871 deletions

README.md

Lines changed: 4 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ It is designed for embedded and integration-heavy environments such as ESPHome,
1515

1616
## How to use
1717

18-
Complete example with the explanation: [test_example.cpp](https://github.com/esphome-libs/dlms_parser/blob/main/tests/test_example.cpp)
18+
* Complete example with the explanation: [test_example.cpp](https://github.com/esphome-libs/dlms_parser/blob/main/tests/test_example.cpp)
19+
* Usage in ESPHome: [dlms_meter component](https://github.com/esphome/esphome/tree/dev/esphome/components/dlms_meter)
1920

2021
### Creating custom patterns to match your meter's telegram structure
2122

@@ -43,14 +44,10 @@ parser.load_default_patterns();
4344

4445
If your meter emits a layout not covered by the built-ins, you can register custom patterns. Lower priority numbers are evaluated first.
4546
```c++
46-
// Simple — priority 0 (tried before built-ins)
47-
parser.register_pattern("TC, TO, TDTM");
48-
49-
// Named with explicit priority
50-
parser.register_pattern("MyPattern", "TO, TV, S(TS, TU)", 5);
47+
parser.register_pattern("MyPattern", "TO, TV, S(TS, TU)", 5, {});
5148

5249
// With default OBIS — used when the pattern captures no OBIS code
53-
const uint8_t meter_obis[] = {0, 0, 96, 1, 0, 255}; // 0.0.96.1.0.255
50+
dlms_parser::ObisId meter_obis(0, 0, 96, 1, 0, 255); // 0.0.96.1.0.255
5451
parser.register_pattern("MeterID", "L, TSTR", 0, meter_obis);
5552
```
5653
@@ -94,90 +91,6 @@ parser.register_pattern("TOW, TV, TSU"); // Landis+Gyr swapped OBIS
9491
| `DN` | descend into nested structure | control token |
9592
| `UP` | return from nested structure | control token |
9693

97-
## API Reference
98-
99-
### `DlmsParser` Core Methods
100-
101-
> **⚠️ Warning:** If you intend to use encryption, you **must** provide a concrete `Aes128GcmDecryptor` backend to the constructor before calling `set_decryption_key` or `set_authentication_key`. Calling these methods on a parser initialized with the default `nullptr` decryptor will cause a null pointer dereference.
102-
103-
| Method | Description |
104-
|----------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------|
105-
| `DlmsParser(Aes128GcmDecryptor* = nullptr)` | Constructor accepting an optional pointer to an AES-128-GCM decryptor backend. |
106-
| `set_skip_crc_check(bool)` | Skip CRC/checksum validation for HDLC and M-Bus. |
107-
| `set_decryption_key(const Aes128GcmDecryptionKey&)` | Set AES-128-GCM decryption key (GUEK). **Requires a non-null decryptor.** |
108-
| `set_authentication_key(const Aes128GcmAuthenticationKey&)` | Set AES-128-GCM authentication key (GAK) for GCM tag verification. **Requires a non-null decryptor.** |
109-
| `load_default_patterns()` | Register all built-in patterns (T1, T2, T3, DateTime, etc.). |
110-
| `ParseResult parse(std::span<uint8_t> buf, const DlmsDataCallback&)` | Parse a complete frame; modifies the buffer in-place and triggers the callback. |
111-
### Supported APDU Tags
112-
113-
Common APDU tags accepted by the parser:
114-
115-
| Byte | Meaning |
116-
|--------|-------------------------------------------------------------------------------------|
117-
| `0x0F` | `DATA-NOTIFICATION` |
118-
| `0xE0` | `General-Block-Transfer` — reassembles numbered blocks, then re-enters APDU parsing |
119-
| `0xDB` | `General-GLO-Ciphering` — encrypted, needs decryption key |
120-
| `0xDF` | `General-DED-Ciphering` — encrypted, needs decryption key |
121-
| `0x01` | raw AXDR array |
122-
| `0x02` | raw AXDR structure |
123-
124-
### Basic Example
125-
```c++
126-
#include <vector>
127-
#include "dlms_parser.h"
128-
#include "decryption/aes_128_gcm_decryptor_mbedtls.h"
129-
130-
using namespace dlms_parser;
131-
132-
int main() {
133-
// 1. Initialize a crypto backend (e.g., mbedTLS)
134-
Aes128GcmDecryptorMbedTls decryptor;
135-
136-
// 2. Initialize the parser with a pointer to the decryptor
137-
DlmsParser parser(&decryptor);
138-
139-
// 3. Set keys using the robust hex loader
140-
auto dec_key = Aes128GcmDecryptionKey::from_hex("00112233445566778899AABBCCDDEEFF");
141-
auto auth_key = Aes128GcmAuthenticationKey::from_hex("FFEEDDCCBBAA99887766554433221100");
142-
143-
if (dec_key) parser.set_decryption_key(*dec_key);
144-
if (auth_key) parser.set_authentication_key(*auth_key);
145-
146-
// 4. Load common built-in meter layout patterns
147-
parser.load_default_patterns();
148-
149-
// 5. Provide your data (will be modified in-place during parsing)
150-
std::vector<uint8_t> my_telegram = { /* ... byte data ... */ };
151-
152-
// 6. Define your callback
153-
auto callback = [](const char* obis, float f_val, const char* s_val, bool is_numeric) {
154-
printf("Matched OBIS: %s | String: %s | Float: %f\n", obis, s_val, f_val);
155-
};
156-
157-
// 7. Parse the telegram by explicitly constructing a std::span<uint8_t>
158-
ParseResult result = parser.parse(std::span<uint8_t>(my_telegram.data(), my_telegram.size()), callback);
159-
160-
printf("Successfully parsed %zu COSEM objects!\n", result.count);
161-
return 0;
162-
}
163-
```
164-
165-
## Logging
166-
167-
`dlms_parser` includes a built-in logging system that is useful for debugging frame parsing and pattern matching. You can hook into it by providing a custom log function:
168-
```c++
169-
#include "log.h"
170-
#include <cstdarg>
171-
#include <cstdio>
172-
173-
// ... inside your setup code ...
174-
dlms_parser::Logger::set_log_function([](dlms_parser::LogLevel level, const char* fmt, va_list args) {
175-
// Implement your platform-specific print here
176-
vprintf(fmt, args);
177-
printf("\n");
178-
});
179-
```
180-
18194
## How to add the library to your project
18295

18396
### PlatformIO package

library.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dlms_parser",
3-
"version": "1.3.0",
3+
"version": "2.0.0",
44
"description": "A C++20 library for parsing DLMS/COSEM push telegrams from electricity meters. It is designed for embedded and integration-heavy environments such as ESPHome",
55
"keywords": [
66
"dlms",

src/dlms_parser/apdu_handler.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#include "apdu_handler.h"
2-
#include "log.h"
32
#include "utils.h"
3+
#include "log.h"
44
#include <algorithm>
55
#include <array>
66

@@ -28,8 +28,8 @@ static bool is_known_tag(const uint8_t b) {
2828
case DLMS_APDU_DATA_NOTIFICATION:
2929
case DLMS_APDU_GENERAL_GLO_CIPHERING:
3030
case DLMS_APDU_GENERAL_DED_CIPHERING:
31-
case DLMS_DATA_TYPE_ARRAY:
32-
case DLMS_DATA_TYPE_STRUCTURE:
31+
case static_cast<uint8_t>(DlmsDataType::ARRAY):
32+
case static_cast<uint8_t>(DlmsDataType::STRUCTURE):
3333
return true;
3434
default:
3535
return false;
@@ -51,9 +51,9 @@ std::span<uint8_t> parse_apdu_in_place(std::span<uint8_t> buf, Aes128GcmDecrypto
5151
const uint8_t tag = buf[0];
5252

5353
// --- Raw AXDR (0x01/0x02): done
54-
if (tag == DLMS_DATA_TYPE_ARRAY || tag == DLMS_DATA_TYPE_STRUCTURE) {
54+
if (tag == static_cast<uint8_t>(DlmsDataType::ARRAY) || tag == static_cast<uint8_t>(DlmsDataType::STRUCTURE)) {
5555
Logger::log(LogLevel::VERBOSE, "Found raw AXDR %s (0x%02X) - no APDU wrapper",
56-
tag == DLMS_DATA_TYPE_ARRAY ? "ARRAY" : "STRUCTURE", tag);
56+
tag == static_cast<uint8_t>(DlmsDataType::ARRAY) ? "ARRAY" : "STRUCTURE", tag);
5757
return buf;
5858
}
5959

0 commit comments

Comments
 (0)