Serial.Read() из ESP32 пропускает 0 значений при чтении HEX-данных

Я пытаюсь считать данные с моего SmartMeter (Siemens IM350), поэтому я хотел бы использовать ESP32. SmartMeter отправляет данные каждую секунду при вызове. Сигнал SmartMeter инвертируется с помощью транзистора и считывается с помощью последовательного порта 2 (поскольку UART0 по умолчанию используется для связи USB) ESP32.

SmartMeter отправляет пакет данных размером 114 байт. При отключении ESP32 (нажатии кнопки сброса) и использовании его в качестве USB-интерфейса для ПК HTERM получает такое сообщение: 7EA070CF0002002313E0C7E6E700DB08534D53677001764B5620000009FF5472DF3F6384A0115A6F55B5E01D050C9CC534CA128D8EE69EDDFA6E1907F2165EF32F55 86150572B800F172BB27F04558944F9A77C67CAE0F06B11ECD8494F583519A514E58180948CAA21CE69DB523AE017B7E

Это сообщение имеет размер 114 байт и может быть легко расшифровано. Однако при использовании ESP32 отдельно получается более короткое сообщение: 7EA070CF0202313E0C7E6E70DB8534D5367701764B562000A728764BBF7916DA350DBBFE1915F4B9B93AD694A3D11A55B118440932C5A2D738A98D3B66F5B0C93 AECA829CAA1EC17EF71E841F95ADE89C9E587105B2DA89ECFAB53A7CF3442D19854863C70ADBD1848277E

Это сообщение имеет длину всего 107 байт (но оно варьируется: некоторые длиннее, некоторые короче, но ни одно из них не имеет длины 114 байт). Кажется, что Serial.Read() по какой-то причине пропускает 0 значений. Это можно увидеть в первых 20–30 байтах, которые должны быть идентичными, однако нули отсутствуют. Как можно решить эту проблему?

Вот мой код, он скопирован и изменен из esphome_im350 (проводку также можно увидеть на этой странице):

Основной:

#include <Arduino.h>
#include <ArduinoOTA.h>
#include <TelnetStream.h>
#include <Crypto.h>
#include <AES.h>
#include <GCM.h>
#include <WiFi.h>
#include "time.h"

#include "secrets.h"
#include "settings.h"

// NTP Settings
struct tm ntpTime;
uint8_t current_time_day;
uint8_t current_time_month;
uint16_t current_time_year;

// message date variables
uint16_t message_year;
uint8_t message_month;
uint8_t message_day;
uint8_t message_hour;
uint8_t message_minute;
uint8_t message_second;

// reading variables
uint32_t counter_reading_p_in;
uint32_t counter_reading_p_out;
uint32_t counter_reading_q_in;
uint32_t counter_reading_q_out;
uint32_t current_power_usage_in;
uint32_t current_power_usage_out;

struct Vector_GCM {
    const char *name;
    uint8_t key[16];
    uint8_t ciphertext[90];
    uint8_t authdata[16];
    uint8_t iv[12];
    uint8_t tag[12];
    size_t authsize;
    size_t datasize;
    size_t tagsize;
    size_t ivsize;
};

Vector_GCM Vector_SM;
GCM<AES128> *gcmaes128 = 0;

boolean getLocalTime()
{
  if (!getLocalTime(&ntpTime)){
    Serial.println("Failed to obtain time");
    return false;
  }
  else {
    current_time_day = ntpTime.tm_mday;
    current_time_month = ntpTime.tm_mon + 1; // Month is 0 - 11, add 1 to get a jan-dec 1-12 concept
    current_time_year = ntpTime.tm_year + 1900; // Year is # years since 1900
    return true;
  }
}

void printLocalTime() {
  if (getLocalTime()) {
    Serial.println(&ntpTime, "%A, %B %d %Y %H:%M:%S");  
  }
}

void print_array(byte array[], unsigned int len)
{
  char text_buffer[len];

  for (unsigned int i = 0; i < len; i++)
  {
      byte nib1 = (array[i] >> 4) & 0x0F;
      byte nib2 = (array[i] >> 0) & 0x0F;
      text_buffer[i*2+0] = nib1  < 0xA ? '0' + nib1  : 'A' + nib1  - 0xA;
      text_buffer[i*2+1] = nib2  < 0xA ? '0' + nib2  : 'A' + nib2  - 0xA;
  }
  text_buffer[len*2] = '\0';
  for (unsigned int i = 0; i < len; i++) {
    Serial.print(text_buffer[i]);
  }
}

uint32_t byteToUInt32(byte array[], unsigned int startByte)
{
    // https://stackoverflow.com/questions/12240299/convert-bytes-to-int-uint-in-c
    // convert 4 bytes to uint32
    uint32_t result;
    result = (uint32_t) array[startByte] << 24;
    result |=  (uint32_t) array[startByte+1] << 16;
    result |= (uint32_t) array[startByte+2] << 8;
    result |= (uint32_t) array[startByte+3];

    return result;
}

void parse_timestamp(byte array[]) {
  message_year = (array[6] << 8) + (array[7]);
  message_month = array[8];
  message_day = array[9];
  message_hour = array[11];
  message_minute = array[12];
  message_second = array[13];
}

// used to see if encrypted data are correct...
bool validate_message_date() {
  Serial.println();
  // compare date from ntp and message should be the same day!
  Serial.println();
  TelnetStream.println();
  Serial.println("======DEBUG====== = ");
  TelnetStream.println("======DEBUG====== = ");
  Serial.printf("DATE FROM NTP: %02d-%02d-%02d", current_time_year, current_time_month, current_time_day);
  TelnetStream.printf("DATE FROM NTP: %02d-%02d-%02d", current_time_year, current_time_month, current_time_day);
  Serial.println();
  TelnetStream.println();
  Serial.printf("DATE FROM MESSAGE: %02d-%02d-%02d", message_year, message_month, message_day);
  TelnetStream.printf("DATE FROM MESSAGE: %02d-%02d-%02d", message_year, message_month, message_day);
  Serial.println();
  TelnetStream.println();

  if (current_time_year == message_year and current_time_month == message_month and current_time_day == message_day){
    Serial.printf("Message Date is VALID!, ntp_date: %02d-%02d-%02d == message_date: %02d-%02d-%02d\n", current_time_year, current_time_month, current_time_day, message_year, message_month, message_day);
    TelnetStream.printf("Message Date is VALID!, ntp_date: %02d-%02d-%02d == message_date: %02d-%02d-%02d\n", current_time_year, current_time_month, current_time_day, message_year, message_month, message_day);
    Serial.println("======DEBUG====== = ");
    TelnetStream.println("======DEBUG====== = ");
    Serial.println();
    TelnetStream.println();
    return true;
  }
  else {
    Serial.printf("Message Date is INVALID!, ntp_date: %02d-%02d-%02d !=  message_date: %02d-%02d-%02d\n", current_time_year, current_time_month, current_time_day, message_year, message_month, message_day);
    TelnetStream.printf("Message Date is INVALID!, ntp_date: %02d-%02d-%02d !=  message_date: %02d-%02d-%02d\n", current_time_year, current_time_month, current_time_day, message_year, message_month, message_day);
    Serial.println("======DEBUG====== = ");
    TelnetStream.println("======DEBUG====== = ");
    Serial.println();
    TelnetStream.println();
    return false;
  }
}

int readMessage() {
    unsigned short serial_cnt = 0;
    if (use_test_data == true) {
        Serial.println("USE TEST DATA IS ACTIVE!");
        TelnetStream.println("USE TEST DATA IS ACTIVE!");
        serial_cnt = 123;
        for (unsigned int i = 0; i < 123; i++) {
            message[i] = testData[i];
        }
    }
    else {
        Serial.println("Try to read data from serial port.");
        TelnetStream.println("Try to read data from serial port.");
        
        memset(message, 0, message_length);

        int cnt = 0;
        int readBuffer = 250;

        pinMode(led_builtin, OUTPUT);
        pinMode(data_request_gpio, OUTPUT);
        digitalWrite(led_builtin, HIGH);
        digitalWrite(data_request_gpio,HIGH);
        unsigned long requestMillis = millis();
        delay(delay_before_reading_data);
        while ((Serial2.available()) && (cnt < readBuffer) && (millis()-requestMillis <= max_wait_time_for_reading_data)) {
          message[cnt] = Serial2.read();
          if (message[0] != start_byte && cnt == 0) {
            continue;
          }
          else {
            cnt++;
          }
        }

        digitalWrite(led_builtin, LOW);
        digitalWrite(data_request_gpio,LOW);
    }
  Serial.println("Done with reading from from serial port.");
  TelnetStream.println("Done with reading from from serial port.");
  return (serial_cnt);
}

void init_vector(Vector_GCM *vect, const char *Vect_name, byte *key_SM) {
  vect->name = Vect_name;  // vector name
  for (unsigned int i = 0; i < 16; i++) {
    vect->key[i] = key_SM[i];
  }


  //changed (from 90 to 81)
  for (unsigned int i = 0; i < 81; i++) {
    vect->ciphertext[i] = message[i+30];
  }
  byte AuthData[] = {0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf}; // fixed value, i got it from the gurus director software

  for (int i = 0; i < 16; i++) {
     vect->authdata[i] = AuthData[i];
  }

  for (int i = 0; i < 8; i++) {
     vect->iv[i] = message[16+i]; // manufacturer + serialnumber 8 bytes
  }
  for (int i = 8; i < 12; i++) {
    vect->iv[i] = message[18+i]; // frame counter
  }

  byte tag[12]; // 12x zero
  for (int i = 0; i < 12; i++) {
    vect->tag[i] = tag[i];
  }

  vect->authsize = 16;
  vect->datasize = 81;
  vect->tagsize = 12;
  vect->ivsize  = 12;
}

void decrypt_text(Vector_GCM *vect) {
  gcmaes128 = new GCM<AES128>();
  gcmaes128->setKey(vect->key, gcmaes128->keySize());
  gcmaes128->setIV(vect->iv, vect->ivsize);
  gcmaes128->decrypt((byte*)buffer, vect->ciphertext, vect->datasize);

  // this does not work...
  // bool decryption_failed = false;
  // if (!gcmaes128->checkTag(vect->tag, vect->tagsize)) {
  //   decryption_failed = true;
  //   Serial.println("Decryption Failed!");
  // }
  // else {
  //   Serial.println("Decryption OK!");
  // }
  delete gcmaes128;
}

void parse_message(byte array[]) {

      //Changed (Number)
      counter_reading_p_in = byteToUInt32(array, 52);
      counter_reading_p_out = byteToUInt32(array, 57);
      counter_reading_q_in = byteToUInt32(array, 62);
      counter_reading_q_out = byteToUInt32(array, 67);
      current_power_usage_in = byteToUInt32(array, 72);
      current_power_usage_out = byteToUInt32(array, 77);

      // Serial.println(result, DEC);
      Serial.printf("counter_reading_p_in: %d\n", counter_reading_p_in);
      TelnetStream.printf("counter_reading_p_in: %d\n", counter_reading_p_in);
      Serial.printf("counter_reading_p_out: %d\n", counter_reading_p_out);
      TelnetStream.printf("counter_reading_p_out: %d\n", counter_reading_p_out);
      Serial.printf("counter_reading_q_in: %d\n", counter_reading_q_in);
      TelnetStream.printf("counter_reading_q_in: %d\n", counter_reading_q_in);
      Serial.printf("counter_reading_q_out: %d\n", counter_reading_q_out);
      TelnetStream.printf("counter_reading_q_out: %d\n", counter_reading_q_out);
      Serial.printf("current_power_usage_in: %d\n", current_power_usage_in);
      TelnetStream.printf("current_power_usage_in: %d\n", current_power_usage_in);
      Serial.printf("current_power_usage_out: %d\n", current_power_usage_out);
      TelnetStream.printf("current_power_usage_out: %d\n", current_power_usage_out);
}

void printBytesToHex(byte array[], unsigned int len) {
  
  for (unsigned int i = 0; i < len; i++) {
    Serial.print(message[i], HEX);
    TelnetStream.print(message[i], HEX);
  }
  Serial.print("\n");
  TelnetStream.print("\n");
}

void SerialTelnetPrint(char msg[]) {
  Serial.println(msg);
  Serial.println(msg);
}

void setup() {
    btStop(); // disable bluetooth
    Serial.begin(115200);
    Serial2.begin(115200, SERIAL_8N1, uart2_rx_gpio, uart2_tx_gpio);
    
    //connect to WiFi
    Serial.printf("Connecting to %s ", wifi_ssid);
    WiFi.mode(WIFI_STA);
    WiFi.begin(wifi_ssid, wifi_password);
    while (WiFi.waitForConnectResult() != WL_CONNECTED) {
      Serial.println("Connection Failed! Rebooting...");
      delay(5000);
      ESP.restart();
    }
    

  // Port defaults to 3232
  ArduinoOTA.setPort(3232);

  // Hostname defaults to esp3232-[MAC]
  ArduinoOTA.setHostname("sm_reader");

  // No authentication by default
  // ArduinoOTA.setPassword("admin");

  // Password can be set with it's md5 value as well
  // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
  // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");

  ArduinoOTA
    .onStart([]() {
      String type;
      if (ArduinoOTA.getCommand() == U_FLASH)
        type = "sketch";
      else // U_SPIFFS
        type = "filesystem";

      // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
      Serial.println("Start updating " + type);
    })
    .onEnd([]() {
      Serial.println("\nEnd");
    })
    .onProgress([](unsigned int progress, unsigned int total) {
      Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
    })
    .onError([](ota_error_t error) {
      Serial.printf("Error[%u]: ", error);
      if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
      else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
      else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
      else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
      else if (error == OTA_END_ERROR) Serial.println("End Failed");
    });


    ArduinoOTA.begin();
    Serial.println("Ready");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

    //init and get the time
    configTime(gmtOffset_sec, daylightOffset_sec, ntp_server);
    getLocalTime();
    printLocalTime();

    TelnetStream.begin();
}

void loop() {
    ArduinoOTA.handle();
    
    //init and get the time
    configTime(gmtOffset_sec, daylightOffset_sec, ntp_server);
    getLocalTime();
    printLocalTime();
    
    Serial.printf("RSSI: %d dBm\n", WiFi.RSSI());
    TelnetStream.printf("RSSI: %d dBm\n", WiFi.RSSI());
    Serial.println(WiFi.BSSIDstr());
    TelnetStream.println(WiFi.BSSIDstr());

    readMessage();




    if (message[0] == start_byte and message[sizeof(message)-1] == stop_byte) {
      Serial.println("Got message from meter, try to decrypt.");
      TelnetStream.println("Got message from meter, try to decrypt.");
      Serial.print("ReceivedMessage: ");
      TelnetStream.print("ReceivedMessage: ");
      printBytesToHex(message, (sizeof(message)/sizeof(message[0])));

      init_vector(&Vector_SM,"Vector_SM",sm_decryption_key); 

      // print decryption details
      Serial.print("IV: ");
      TelnetStream.print("IV: ");
      printBytesToHex(Vector_SM.iv, (sizeof(Vector_SM.iv)/sizeof(Vector_SM.iv[0])));
      Serial.print("Key: ");
      TelnetStream.print("Key: ");
      printBytesToHex(Vector_SM.key, (sizeof(Vector_SM.key)/sizeof(Vector_SM.key[0])));
      Serial.print("Authdata: ");
      TelnetStream.print("Authdata: ");
      printBytesToHex(Vector_SM.authdata, (sizeof(Vector_SM.authdata)/sizeof(Vector_SM.authdata[0])));
      Serial.print("Tag: ");
      TelnetStream.print("Tag: ");
      printBytesToHex(Vector_SM.tag, (sizeof(Vector_SM.tag)/sizeof(Vector_SM.tag[0])));
      Serial.print("Encrypted Data (Ciphertext): ");
      TelnetStream.print("Encrypted Data (Ciphertext): ");
      printBytesToHex(Vector_SM.ciphertext, (sizeof(Vector_SM.ciphertext)/sizeof(Vector_SM.ciphertext[0])));

      decrypt_text(&Vector_SM);
      Serial.print("Decrypted Data: ");
      TelnetStream.print("Decrypted Data: ");
      printBytesToHex(buffer, (sizeof(buffer)/sizeof(buffer[0])));

      Serial.print("======Decrypted Parsed Data======\n");
      TelnetStream.print("======Decrypted Parsed Data======\n");
      parse_message(buffer);
      Serial.print("======Decrypted Parsed Data======\n");
      TelnetStream.print("======Decrypted Parsed Data======\n");

      parse_timestamp(buffer);

      if (validate_message_date()) {
        Serial.println("Do something.");
        TelnetStream.println("Do something.");
      }
      else {
        Serial.println("Do nothing.");
        TelnetStream.println("Do nothing.");
      }

    }
    else {
      Serial.println("Message not starting/ending with 0xE7, skip this message!");
      TelnetStream.println("Message not starting/ending with 0xE7, skip this message!");
      Serial.print("Received Message: ");
      TelnetStream.print("Received Message: ");
      printBytesToHex(message, (sizeof(message)/sizeof(message[0])));
    }
    
    delay(1000);
    Serial.println("waiting 1 second...");
    TelnetStream.println("waiting 1 second...");
    Serial.println("reset");
    TelnetStream.println("reset");
}

Настройки.ч:

// Using UART2 for reading the data from the smart meter, if you use the UART0 you can not upload new code to the board cause its blocked while reading from the meter!
int uart2_rx_gpio = 16;
int uart2_tx_gpio = 17;
int data_request_gpio = 26;
int led_builtin = 2;
// NTP Settings
char ntp_server[] = "pool.ntp.org";
const long gmtOffset_sec = 3600;
const int daylightOffset_sec = 3600;

const byte start_byte = 0x7E; 
const byte stop_byte = 0x7E;

const int max_wait_time_for_reading_data = 1100;
int delay_before_reading_data = 1000;

const int message_length = 114;
byte message[message_length];
byte buffer[90];
bool use_test_data = false;
byte testData[123] = {0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                      0x00, 0x00, 0x7e};

Я уже использовал разные ESP32 и пытался прочитать сигнал без процедуры расшифровки, однако, похоже, ничего не работает. Как это можно исправить?

Вы искали парсера HAN? ваш пакет данных выглядит как данные порта HAN

Haresh Sondagar 19.07.2024 12:13

вы можете использовать TeePrint из моей StreamLib для печати на двух выходах одновременно

Juraj 19.07.2024 12:16

SmartMeter использует протокол DLMS.

Dr_Didi 19.07.2024 12:26
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
60
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В основном цикле вы вызываете readMessage() и заполняете сообщение последовательными данными. После этого вы передаете «сообщение» в «printBytesToHex», и эта функция не обрабатывает ведущие нули.

Функция print_array, похоже, имеет дело с ведущими нулями. Вы можете попробовать использовать его или изменить printBytesToHex на:

void printBytesToHex(byte array[], unsigned int len) 
{

    for (unsigned int i = 0; i < len; i++) 
    {
        Serial.print(message[i]>>4, HEX);
        Serial.print(message[i]&0x0F, HEX);
        
        TelnetStream.print(message[i]>>4, HEX);
        TelnetStreamrial.print(message[i]&0x0F, HEX);
    }
    Serial.print("\n");
    TelnetStream.print("\n");
}

Другие вопросы по теме