38 size_t remaining = MBUS_MAX_FRAME_LENGTH * 2 - this->
receive_buffer_.size();
40 ESP_LOGW(TAG,
"Receive buffer full, dropping remaining bytes");
44 if (avail > remaining) {
49 size_t to_read = std::min(avail,
sizeof(buf));
65 uint16_t message_length;
66 uint8_t systitle_length;
67 uint16_t header_offset;
71 if (message_length < DECODER_START_OFFSET || message_length > MAX_MESSAGE_LENGTH) {
72 ESP_LOGE(TAG,
"DLMS: Message length invalid: %u", message_length);
85 ESP_LOGV(TAG,
"Parsing M-Bus frames");
86 uint16_t frame_offset = 0;
90 if (this->
receive_buffer_.size() - frame_offset < MBUS_HEADER_INTRO_LENGTH) {
91 ESP_LOGE(TAG,
"MBUS: Not enough data for frame header (need %d, have %d)", MBUS_HEADER_INTRO_LENGTH,
98 if (this->
receive_buffer_[frame_offset + MBUS_START1_OFFSET] != START_BYTE_LONG_FRAME ||
99 this->
receive_buffer_[frame_offset + MBUS_START2_OFFSET] != START_BYTE_LONG_FRAME) {
100 ESP_LOGE(TAG,
"MBUS: Start bytes do not match");
108 ESP_LOGE(TAG,
"MBUS: Length bytes do not match");
113 uint8_t frame_length = this->
receive_buffer_[frame_offset + MBUS_LENGTH1_OFFSET];
118 ESP_LOGE(TAG,
"MBUS: Frame too big for received data");
124 size_t required_total =
125 frame_length + MBUS_HEADER_INTRO_LENGTH + MBUS_FOOTER_LENGTH;
127 ESP_LOGE(TAG,
"MBUS: Incomplete frame (need %d, have %d)", (
unsigned int) required_total,
133 if (this->
receive_buffer_[frame_offset + frame_length + MBUS_HEADER_INTRO_LENGTH + MBUS_FOOTER_LENGTH - 1] !=
135 ESP_LOGE(TAG,
"MBUS: Invalid stop byte");
142 for (uint16_t i = 0; i < frame_length; i++) {
146 ESP_LOGE(TAG,
"MBUS: Invalid checksum: %x != %x",
checksum,
147 this->
receive_buffer_[frame_offset + frame_length + MBUS_HEADER_INTRO_LENGTH]);
152 mbus_payload.insert(mbus_payload.end(), &this->receive_buffer_[frame_offset + MBUS_FULL_HEADER_LENGTH],
153 &this->receive_buffer_[frame_offset + MBUS_HEADER_INTRO_LENGTH + frame_length]);
155 frame_offset += MBUS_HEADER_INTRO_LENGTH + frame_length + MBUS_FOOTER_LENGTH;
161 uint8_t &systitle_length, uint16_t &header_offset) {
162 ESP_LOGV(TAG,
"Parsing DLMS header");
163 if (mbus_payload.size() < DLMS_HEADER_LENGTH + DLMS_HEADER_EXT_OFFSET) {
164 ESP_LOGE(TAG,
"DLMS: Payload too short");
169 if (mbus_payload[DLMS_CIPHER_OFFSET] != GLO_CIPHERING) {
170 ESP_LOGE(TAG,
"DLMS: Unsupported cipher");
175 systitle_length = mbus_payload[DLMS_SYST_OFFSET];
177 if (systitle_length != 0x08) {
178 ESP_LOGE(TAG,
"DLMS: Unsupported system title length");
183 message_length = mbus_payload[DLMS_LENGTH_OFFSET];
189 if (message_length == NETZ_NOE_MAGIC_BYTE &&
190 mbus_payload[DLMS_LENGTH_OFFSET + 1] == NETZ_NOE_EXPECTED_MESSAGE_LENGTH &&
191 mbus_payload[DLMS_LENGTH_OFFSET + 2] == NETZ_NOE_EXPECTED_SECURITY_CONTROL_BYTE) {
192 message_length = mbus_payload[DLMS_LENGTH_OFFSET + 1];
195 ESP_LOGE(TAG,
"Wrong Length - Security Control Byte sequence detected for provider EVN");
198 if (message_length == TWO_BYTE_LENGTH) {
199 message_length =
encode_uint16(mbus_payload[DLMS_LENGTH_OFFSET + 1], mbus_payload[DLMS_LENGTH_OFFSET + 2]);
200 header_offset = DLMS_HEADER_EXT_OFFSET;
203 if (message_length < DLMS_LENGTH_CORRECTION) {
204 ESP_LOGE(TAG,
"DLMS: Message length too short: %u", message_length);
208 message_length -= DLMS_LENGTH_CORRECTION;
210 if (mbus_payload.size() - DLMS_HEADER_LENGTH - header_offset != message_length) {
211 ESP_LOGV(TAG,
"DLMS: Length mismatch - payload=%d, header=%d, offset=%d, message=%d", mbus_payload.size(),
212 DLMS_HEADER_LENGTH, header_offset, message_length);
213 ESP_LOGE(TAG,
"DLMS: Message has invalid length");
218 if (mbus_payload[header_offset + DLMS_SECBYTE_OFFSET] != 0x21 &&
219 mbus_payload[header_offset + DLMS_SECBYTE_OFFSET] !=
221 ESP_LOGE(TAG,
"DLMS: Unsupported security control byte");
230 uint16_t header_offset) {
231 ESP_LOGV(TAG,
"Decrypting payload");
235 memcpy(&iv[0], &mbus_payload[DLMS_SYST_OFFSET + 1], systitle_length);
236 memcpy(&iv[8], &mbus_payload[header_offset + DLMS_FRAMECOUNTER_OFFSET],
237 DLMS_FRAMECOUNTER_LENGTH);
239 uint8_t *payload_ptr = &mbus_payload[header_offset + DLMS_PAYLOAD_OFFSET];
241#if defined(USE_ESP8266_FRAMEWORK_ARDUINO)
242 br_gcm_context gcm_ctx;
243 br_aes_ct_ctr_keys bc;
244 br_aes_ct_ctr_init(&bc, this->
decryption_key_.data(), this->decryption_key_.size());
245 br_gcm_init(&gcm_ctx, &bc.vtable, br_ghash_ctmul32);
246 br_gcm_reset(&gcm_ctx, iv,
sizeof(iv));
247 br_gcm_flip(&gcm_ctx);
248 br_gcm_run(&gcm_ctx, 0, payload_ptr, message_length);
249#elif defined(USE_ESP32)
250#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
252 psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
253 psa_set_key_type(&attributes, PSA_KEY_TYPE_AES);
255 psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT);
256 psa_set_key_algorithm(&attributes, PSA_ALG_GCM);
258 mbedtls_svc_key_id_t key_id;
259 bool decrypt_failed =
true;
260 if (psa_import_key(&attributes, this->
decryption_key_.data(), this->decryption_key_.size(), &key_id) == PSA_SUCCESS) {
261 psa_aead_operation_t op = PSA_AEAD_OPERATION_INIT;
262 if (psa_aead_decrypt_setup(&op, key_id, PSA_ALG_GCM) == PSA_SUCCESS &&
263 psa_aead_set_nonce(&op, iv,
sizeof(iv)) == PSA_SUCCESS) {
265 if (psa_aead_update(&op, payload_ptr, message_length, payload_ptr, message_length, &outlen) == PSA_SUCCESS &&
266 outlen == message_length) {
267 decrypt_failed =
false;
271 psa_destroy_key(key_id);
273 if (decrypt_failed) {
274 ESP_LOGE(TAG,
"Decryption failed");
280 mbedtls_gcm_context gcm_ctx;
281 mbedtls_gcm_init(&gcm_ctx);
282 mbedtls_gcm_setkey(&gcm_ctx, MBEDTLS_CIPHER_ID_AES, this->
decryption_key_.data(), this->decryption_key_.size() * 8);
283 mbedtls_gcm_starts(&gcm_ctx, MBEDTLS_GCM_DECRYPT, iv,
sizeof(iv));
284 auto ret = mbedtls_gcm_update(&gcm_ctx, payload_ptr, message_length, payload_ptr, message_length, &outlen);
285 mbedtls_gcm_free(&gcm_ctx);
287 ESP_LOGE(TAG,
"Decryption failed with error: %d", ret);
293#error "Invalid Platform"
296 if (payload_ptr[0] != DATA_NOTIFICATION || payload_ptr[5] != TIMESTAMP_DATETIME) {
297 ESP_LOGE(TAG,
"OBIS: Packet was decrypted but data is invalid");
301 ESP_LOGV(TAG,
"Decrypted payload: %d bytes", message_length);
306 ESP_LOGV(TAG,
"Decoding payload");
308 uint16_t current_position = DECODER_START_OFFSET;
309 bool power_factor_found =
false;
311 while (current_position + OBIS_CODE_OFFSET <= message_length) {
313 ESP_LOGE(TAG,
"OBIS: Unsupported OBIS header type: %x", plaintext[current_position + OBIS_TYPE_OFFSET]);
318 uint8_t obis_code_length = plaintext[current_position + OBIS_LENGTH_OFFSET];
319 if (obis_code_length != OBIS_CODE_LENGTH_STANDARD && obis_code_length != OBIS_CODE_LENGTH_EXTENDED) {
320 ESP_LOGE(TAG,
"OBIS: Unsupported OBIS header length: %x", obis_code_length);
324 if (current_position + OBIS_CODE_OFFSET + obis_code_length > message_length) {
325 ESP_LOGE(TAG,
"OBIS: Buffer too short for OBIS code");
330 uint8_t *obis_code = &plaintext[current_position + OBIS_CODE_OFFSET];
331 uint8_t obis_medium = obis_code[OBIS_A];
332 uint16_t obis_cd =
encode_uint16(obis_code[OBIS_C], obis_code[OBIS_D]);
334 bool timestamp_found =
false;
335 bool meter_number_found =
false;
338 if ((obis_code_length == OBIS_CODE_LENGTH_EXTENDED) && (current_position == DECODER_START_OFFSET)) {
339 timestamp_found =
true;
340 }
else if (power_factor_found) {
341 meter_number_found =
true;
342 power_factor_found =
false;
344 current_position += obis_code_length + OBIS_CODE_OFFSET;
347 current_position += obis_code_length + OBIS_CODE_OFFSET;
351 ESP_LOGE(TAG,
"OBIS: Unsupported OBIS medium: %x", obis_medium);
356 if (current_position >= message_length) {
357 ESP_LOGE(TAG,
"OBIS: Buffer too short for data type");
363 uint8_t value_size = 0;
364 uint8_t data_type = plaintext[current_position];
370 if (current_position + value_size > message_length) {
371 ESP_LOGE(TAG,
"OBIS: Buffer too short for DOUBLE_LONG_UNSIGNED");
375 value =
encode_uint32(plaintext[current_position + 0], plaintext[current_position + 1],
376 plaintext[current_position + 2], plaintext[current_position + 3]);
377 current_position += value_size;
382 if (current_position + value_size > message_length) {
383 ESP_LOGE(TAG,
"OBIS: Buffer too short for LONG_UNSIGNED");
387 value =
encode_uint16(plaintext[current_position + 0], plaintext[current_position + 1]);
388 current_position += value_size;
392 uint8_t data_length = plaintext[current_position];
394 if (current_position + data_length > message_length) {
395 ESP_LOGE(TAG,
"OBIS: Buffer too short for OCTET_STRING");
400 if (obis_cd == OBIS_TIMESTAMP || timestamp_found) {
401 if (data_length < 8) {
402 ESP_LOGE(TAG,
"OBIS: Timestamp data too short: %u", data_length);
406 uint16_t
year =
encode_uint16(plaintext[current_position + 0], plaintext[current_position + 1]);
407 uint8_t
month = plaintext[current_position + 2];
408 uint8_t
day = plaintext[current_position + 3];
409 uint8_t
hour = plaintext[current_position + 5];
410 uint8_t
minute = plaintext[current_position + 6];
411 uint8_t
second = plaintext[current_position + 7];
418 snprintf(data.timestamp,
sizeof(data.timestamp),
"%04u-%02u-%02uT%02u:%02u:%02uZ",
year,
month,
day,
hour,
420 }
else if (meter_number_found) {
421 snprintf(data.meternumber,
sizeof(data.meternumber),
"%.*s", data_length, &plaintext[current_position]);
423 current_position += data_length;
427 ESP_LOGE(TAG,
"OBIS: Unsupported OBIS data type: %x", data_type);
435 if (!timestamp_found) {
436 current_position += 2;
439 current_position += 2;
443 if (current_position < message_length && plaintext[current_position] ==
DataType::INTEGER) {
445 if (current_position + 1 < message_length) {
446 int8_t scaler =
static_cast<int8_t
>(plaintext[current_position + 1]);
454 current_position += 4;
456 current_position += 6;
461 if (value_size > 0) {
463 case OBIS_VOLTAGE_L1:
464 data.voltage_l1 = value;
466 case OBIS_VOLTAGE_L2:
467 data.voltage_l2 = value;
469 case OBIS_VOLTAGE_L3:
470 data.voltage_l3 = value;
472 case OBIS_CURRENT_L1:
473 data.current_l1 = value;
475 case OBIS_CURRENT_L2:
476 data.current_l2 = value;
478 case OBIS_CURRENT_L3:
479 data.current_l3 = value;
481 case OBIS_ACTIVE_POWER_PLUS:
482 data.active_power_plus = value;
484 case OBIS_ACTIVE_POWER_MINUS:
485 data.active_power_minus = value;
487 case OBIS_ACTIVE_ENERGY_PLUS:
488 data.active_energy_plus = value;
490 case OBIS_ACTIVE_ENERGY_MINUS:
491 data.active_energy_minus = value;
493 case OBIS_REACTIVE_ENERGY_PLUS:
494 data.reactive_energy_plus = value;
496 case OBIS_REACTIVE_ENERGY_MINUS:
497 data.reactive_energy_minus = value;
499 case OBIS_POWER_FACTOR:
500 data.power_factor = value;
501 power_factor_found =
true;
504 ESP_LOGW(TAG,
"Unsupported OBIS code 0x%04X", obis_cd);
511 ESP_LOGI(TAG,
"Received valid data");