UART String Split

Hi Guys,

I need a bit help in case of a UART string return.
To readout data from a RS485 I have to send a command like

~22014A42E00201FD28\r

and get back an answer like

~22014A00E0C601221B14B8100CF60CF00CF50CF40CF40CF30CF30CF20CF20CF40CF50CF20CF50CF30CF20CF201180118010E040118011801180118FDD500000064002710221B0016000000020000000000230000000000000000000000000000000000000000000000D48A\r

Out of this answer I’d like to extract for example: Position 10 to 12
and convert this HEX data to decimal data
Which means, if I convert:

hex: 0CF3 to decimal: 3315

and collect it in a sensor

I’m not good in custom_uart components, so I tried to solve it like this in my YAML

substitutions:
tx_pin: GPIO18
rx_pin: GPIO19
uart:
- id: uart_0
baud_rate: 9600
tx_pin: ${tx_pin}
rx_pin: ${rx_pin}
stop_bits: 1
debug:
direction: BOTH
dummy_receiver: true
after:
delimiter: “\r\n”
sequence:
- lambda: |-
UARTDebug::log_string(direction, bytes);
std::string str(bytes.begin(), bytes.end());
interval:
- interval: 30s
then:
- uart.write: “~22014A42E00201FD28\r”

I receive the string back … but I don’t know how to extract the exact position of the HEX and than convert it to decimal
Thought there should be a function like

split(“string”, “position1”,“position2”)

and than with

format_hex_pretty()

to format it to decimal numbers?

can somebody give me support ?
thanks to all

You can use std::stoul(value.substr(startPos,len), nullptr,16);

The value is the variable that holds the string value, startPos the position to extract and len the number of chars to copy. In your case resp 9 and 2.

If you don’t know how to incorporate in your yaml, post the content.

Thank You very much,

But I received this error while compiling the YAML

/config/esphome/esphome-web-dd8a60.yaml: In lambda function:
/config/esphome/esphome-web-dd8a60.yaml:54:40: error: conversion from ‘long unsigned int’ to non-scalar type ‘std::__cxx11::string’ {aka ‘std::__cxx11::basic_string’} requested
std::string socdata1 = std::stoul(socdata, nullptr, 16);
^~~~~~~~~~~~
*** [/data/esphome-web-dd8a60/.pioenvs/esphome-web-dd8a60/src/main.cpp.o] Error 1

this is partly the lambda function

std::string rawdata(bytes.begin(), bytes.end());
std::string socdata = rawdata.substr(15,4);
std::string socdata1 = std::stoul(socdata, nullptr, 16);
ESP_LOGD(“Battery SOC”, “%s”, socdata.c_str());
ESP_LOGD(“Battery SOC”, “%s”, socdata1.c_str());

The HEX code is perfectly extracting (thanks for this)
but the std::stoul function is not working, maybe I have to convert the string first correct for the std:stoul function ?

What stoul produces is an unsigned long which is a numerical value. You can’t assign it to a string variable. Instead, change it to the following:

unsigned long socdataDecimal = std::stoul(socdata, nullptr, 16);
ESP_LOGD(“Battery SOC”, “%s”, socdata.c_str());
ESP_LOGD(“Battery SOC”, “%lu”, socdataDecimal);

Thanks again ckxsmart
you’re the best.

Now it works, but I have one more Problem.
The ESP32 get a second answer directly after the first.
And for both, he will do this operation, which overwrites me the right value.

[13:41:44][D][uart_debug:158]: <<< “~22014A00E0C601271015C1100DE00DD90D680D630D850D810D”
[13:41:44][D][Battery SOC:054]: 10000
[13:41:45][D][uart_debug:158]: <<< “00000000000002300000000000000009515D4C7\r”
[13:41:45][D][Battery SOC:054]: 0

which means, the first decimal value I’ve extracted with your help is 10000
This I can publish as an sensor which is no problem !
but directly after the ESP dedects the second message and overwrites the sensor with 0

I tried to do stuff like

- lambda: |-
UARTDebug::log_string(direction, bytes);
std::string rawdata(bytes.begin(), bytes.end());
unsigned long databatterysoc = std::stoul(rawdata.substr(15,4), nullptr, 16);
id(batterysoc).publish_state(databatterysoc);
ESP_LOGD(“Battery SOC”, “%lu”, databatterysoc);

float testdatasplit=0;
sscanf(rawdata, “22014%d”, &testdatasplit)
ESP_LOGD(“Testausgabe”, “%s”, testdatasplit);

which means, I’d like to search for 22014 (which is only included in the first answer, and always the same) and put it in the variable testdatasplit
With this variable I could search again for the right position and convert it to decimal

But I get again a compiling error

/config/esphome/esphome-web-dd8a60.yaml: In lambda function:
/config/esphome/esphome-web-dd8a60.yaml:56:14: error: cannot convert ‘std::__cxx11::string’ {aka ‘std::__cxx11::basic_string’} to ‘const char*’
sscanf(rawdata, “22014%d”, &testdatasplit)

maybe you can help again!,

It looks like the two responses are different lengths so you could use the size of the response to distinguish the data. You can treat bytes like an array to compare just one character at time. Here was example for checking size and the first character is ‘~’ before processing the data:

          if (bytes.size()==51) {
            if(bytes[0]==0x7E)  //~ is 0x7E
              id(binData).publish_state( bytes[4] );

it worked, big thanks to all =)

1 Like

Hi Again, Guys,

I have one more Problem.
I’d like to convert from HEX to Binary format,
Which means, if I use “23” as HEX I will get “00100011”
I didn’t found any convertation which seems to be functional here

Hello Guys
I’ve got the similar issue as you Malcom111
If someone can help me

uart:
  id: U1
  baud_rate: 9600
  tx_pin: 17
  rx_pin: 16
  
  debug:
    direction: RX
    dummy_receiver: True
    sequence:

    - lambda: |-
       UARTDebug::log_string(direction, bytes);
       std::string rawdata(bytes.begin(), bytes.end());
      

sensor:

  - platform: template
    id: Voltage
    name: "Tension"
    
  - platform: template
    id: Power1
    name: "Power1"
    
  - platform: template
    id: Power2
    name: "Power2"
    
  - platform: template
    id: Power3
    name: "Power3"
    

following by the log file

[18:12:15][V][sensor:043]: 'temp6D': Received new state 19.250000
[18:12:15][D][sensor:094]: 'temp6D': Sending state 19.25000 °C with 1 decimals of accuracy
[18:12:16][D][uart_debug:158]: <<< ",234.42,643.79,642.42,639.10, \r\n"
[18:12:18][D][uart_debug:158]: <<< ",233.49,637.61,642.93,635.18, \r\n"
[18:12:19][D][uart_debug:158]: <<< ",233.89,642.31,640.67,637.04, \r\n"
[18:12:21][D][uart_debug:158]: <<< ",233.56,636.61,648.36,633.00, \r\n"
[18:12:22][D][dallas.temp.sensor:054]: 'tempA4': Got Temperature=16.4°C
[18:12:22][V][sensor:043]: 'tempA4': Received new state 16.437500
[18:12:22][D][sensor:094]: 'tempA4': Sending state 16.43750 °C with 1 decimals of accuracy
[18:12:23][D][uart_debug:158]: <<< ",233.47,632.78,639.18,636.68, \r\n"
[18:12:24][D][uart_debug:158]: <<< ",233.52,636.18,640.20,636.84, \r\n"
[18:12:26][D][uart_debug:158]: <<< ",234.47,641.33,637.44,635.28, \r\n"

What i would like to do is to split the UARTdebug on 4 sensors ?
hope it’s clear.

Don’t hesitate to ask me any more information.
Thanks in advance.

Would you like to try this out?

external_components:
  - source: github://eigger/espcomponents/relreases/latest
    components: [ uartex ]
    refresh: always

uart:
  baud_rate: 9600
  tx_pin: 17
  rx_pin: 16

uartex:
  rx_timeout: 10ms
  tx_delay: 10ms
  tx_timeout: 500ms
  tx_retry_cnt: 3
  tx_footer: "\r\n"
  rx_footer: "\r\n"
  
  version:
    disabled: False
  error:
    disabled: False
  log:
    disabled: False

sensor:
  - platform: uartex
    id: Voltage
    name: "Tension"
    accuracy_decimals: 2
    state:
      data: "\0"
      mask: "\0"
    state_number: !lambda |-
      int idx = 1;
      std::string str;
      for (int i = 0, count = 0; i < len; i++)
      {
        if (data[i] == ',') 
        {
          if (count++ >= idx) break;
          str.clear();
        }
        else str += data[i];
      }
      return std::stof(str);

  - platform: uartex
    id: Power1
    name: "Power1"
    accuracy_decimals: 2
    state:
      data: "\0"
      mask: "\0"
    state_number: !lambda |-
      int idx = 2;
      std::string str;
      for (int i = 0, count = 0; i < len; i++)
      {
        if (data[i] == ',') 
        {
          if (count++ >= idx) break;
          str.clear();
        }
        else str += data[i];
      }
      return std::stof(str);

  - platform: uartex
    id: Power2
    name: "Power2"
    accuracy_decimals: 2
    state:
      data: "\0"
      mask: "\0"
    state_number: !lambda |-
      int idx = 3;
      std::string str;
      for (int i = 0, count = 0; i < len; i++)
      {
        if (data[i] == ',') 
        {
          if (count++ >= idx) break;
          str.clear();
        }
        else str += data[i];
      }
      return std::stof(str);

  - platform: uartex
    id: Power3
    name: "Power3"
    accuracy_decimals: 2
    state:
      data: "\0"
      mask: "\0"
    state_number: !lambda |-
      int idx = 4;
      std::string str;
      for (int i = 0, count = 0; i < len; i++)
      {
        if (data[i] == ',') 
        {
          if (count++ >= idx) break;
          str.clear();
        }
        else str += data[i];
      }
      return std::stof(str);
3 Likes

Hello eigger

a great thanks, i’ve just done “cut and paste” and it Works fine

it’s magic :+1:

Thanks again

I’m glad to hear that it’s working well. :grinning: