Easy UART Parsing ESPHome Component

That did it, thanks!

Thx for this - it’s really going to help me.
I have managed to extract text strings into a text sensor OK
I have managed to extract ascii integer values into a sensor
I am struggling to extract float values into a sensor

For example string “V1123.45\r\n” I want to extract as value for state “V1” as 123.45

This is my sensor code (I can’t get it to disply formatted)

  • platform: uartex
    id: test_sensor_1
    name: “Test Sensor”
    unit_of_measurement: “units”
    state: “V1”
    state_number:
    offset: 2
    length: 6 #If value received is shorter than this, decoding seems OK
    precision: 2
    decode: “ascii”

But the value I get in my HA sensor is 1.00

Or if I receive “V1123456\r\n” I get 1235.00

It’s almost as if precision is being interpreted as rounding not floating point precision.

I guess I could do my own parsing in a lambda to find the position of received decimal point and then convert from there, or force all values to be sent as integers and then convert afterwards

Yes, that’s correct — the precision value only controls how the decimal places are displayed.
If you want to properly handle float values, you should use the sensor’s accuracy_decimals option instead.

- platform: uartex
  id: test_sensor_1
  name: “Test Sensor”
  unit_of_measurement: “units”
  accuracy_decimals: 2
  state: “V1”
  state_number:
    offset: 2
    length: 6 #If value received is shorter than this, decoding seems OK
    #precision: 0
    decode: “ascii”

OK that works but it assumes a fixed (virtual) decimal point position in the received string.
I could sort of work around that by multiplying my values to transmit by say 100 and then use accuracy_decimals: 2 to scale them back down. but not very elegant

This works (but crashes my MQTT connection! (must be a string error)

platform: uartex
id: test_sensor_3
name: “Test Sensor 3”
unit_of_measurement: “units”
accuracy_decimals: 2
state: “V3”
lambda: |-
auto n = parse_number(std::string(reinterpret_cast<const char*>(data+2), len-2).c_str());
return n.has_value() ? n.value() : NAN;

Try setting the length: value in state_number: to the maximum possible length of the incoming data.
The length parameter cannot exceed the actual length of the parsed data.

Get invalid yaml errors if I try and have a lambda: and a state_number:

My only relaible method is to read the uart input into a text sensor, then use a template sensor to parse that into a floating point sensor value.

The parse_number helper function works well with all sorts of varying text numbers, varying sign, varying decimal point precision etc.

I didn’t know about the parse_number function, but now I do.
That seems like a good method as well.

Yes it just means I have to duplicate all my sensors as both text and a template (I will have around 100 sensors in the full implementation)

text_sensor:
  - platform: uartex
    id: test_text_sensor
    name: "Test Text Sensor"
    state: "T1"
    lambda: |-
      // return std::string(reinterpret_cast<const char*>(data), len); // Simply return the received string
      return std::string(reinterpret_cast<const char*>(data+2), len-2); // Simply return the received string excluding first 2 chars
    on_value:
      then:
        - sensor.template.publish:
            id: num_from_text
            state: !lambda |-
              auto n = parse_number<float>(x);
              return n.has_value() ? n.value() : NAN;

sensor:
  - platform: template
    id: num_from_text
    name: "Number from text"
    unit_of_measurement: "units"
    accuracy_decimals: 2

It seems like it might be possible to integrate it as shown below. Have you tested this approach?

sensor:
  - platform: uartex
    name: "Test Sensor"
    state: "T1"
    unit_of_measurement: "units"
    accuracy_decimals: 2
    lambda: |-
      std::string str =  std::string(reinterpret_cast<const char*>(data+2), len-2); // Simply 

      auto n = parse_number<float>(str);
      return n.has_value() ? n.value() : NAN;

It seems any of these three syntax versions work.
My MQTT crashes seem to have been unrelated (I had moved the aerial) although I am still getting occasional disconnects just after I parse soem serial input

  - platform: uartex
    id: test_sensor_3
    name: "Test Sensor 3"
    unit_of_measurement: "units"
    accuracy_decimals: 2
    state: "V3"
    lambda: |-
      auto n = parse_number<float>(std::string(reinterpret_cast<const char*>(data+2), len-2).c_str());
      return n.has_value() ? n.value() : NAN;


  - platform: uartex
    id: test_sensor_4
    name: "Test Sensor 4"
    unit_of_measurement: "units"
    accuracy_decimals: 3
    state: "V4"
    lambda: |-
      auto n = parse_number<float>(std::string(reinterpret_cast<const char*>(data+2), len-2));
      return n.has_value() ? n.value() : NAN;

  - platform: uartex
    id: test_sensor_5
    name: "Test Sensor 5"
    unit_of_measurement: "units"
    accuracy_decimals: 3
    state: "V5"
    lambda: |-
      std::string str =  std::string(reinterpret_cast<const char*>(data+2), len-2); // Simply 

      auto n = parse_number<float>(str);
      return n.has_value() ? n.value() : NAN;

This option prioritizes loop execution over serial data processing. By default, the system blocks the loop and processes incoming data first. Could you test whether this option is actually effective?

I suspect that the loop might be getting blocked by prioritizing data processing, which could be causing the MQTT connection to drop.

uartex:
  ...
  rx_priority: loop

I’m not sure it’s that - even without that change I now cannot replicate the issue. I suspect something was off with my MQTT server as I just restarted everything.

I will try building a file of 100 or so sensor values to transmit in one go to stress test it. If necessary I could then try your rx_priority: loop setting

So stress testing with 100 or so sensor values to process just sent in one go as a text file worked without issues, all the values got processed correctly.

Thx for all your help

1 Like

Now I have a new issue if I try and add uartex sensors.
I first started to get issues when I added my 10th sensor, I have cut out everything else in this example and now the issue occurs when I add the 15th sensor. Commenting out any of these uartex sensors the code runs fine.

I have tested on different model ESP8266 boards - same issue

After connecting to WiFi and then MQTT, the board crashes with a OOM crash dump.

[14:28:38]User exception (panic/abort/assert)
[14:28:38]--------------- CUT HERE FOR EXCEPTION DECODER ---------------
[14:28:38]
[14:28:38]Unhandled C++ exception: OOM
[14:28:38]
[14:28:38]>>>stack>>>

The program is really small (compresses down to 303018 bytes) devices have 4MB of flash

substitutions:
  devicename: boat
  # led_pin: GPIO14 #ATMega with ESP8266
  # button_pin: GPIO13
  # tx_pin: GPIO1
  # rx_pin: GPIO3
  led_pin: GPIO2 #D1 Mini Pro ESP8266 note LED is inverted logic
  button_pin: GPIO0
  tx_pin: GPIO15
  rx_pin: GPIO13
  sensor_update: 60s
  led_strbuf_size: "16"

esphome:
  name: $devicename

esp8266:
  # board: esp07s #d1_mini_pro
  # board: d1_mini_pro
  board: nodemcuv2 #Actualy Node MCUV3

logger:
  # baud_rate: 0 # disable uart logging (MQTT logging still happens) as we need to retain the serial port free for comms with AT Mega
  level: DEBUG

uart:
  baud_rate: 9600
  data_bits: 8
  parity: NONE
  stop_bits: 1
  tx_pin: $tx_pin
  rx_pin: $rx_pin
  rx_buffer_size: 512

wifi:
  networks:
    - ssid: !secret wifi_home_ssid
      password: !secret wifi_home_password
    
mqtt:
  broker: !secret mqtt_broker
  port: 1883
  username: !secret mqtt_username
  password: !secret mqtt_password
  #no clientid reqd
  discovery: true # disable entity discovery
  discover_ip: true # enable device discovery
  discovery_retain: true #retain discovery messages
  discovery_unique_id_generator: mac #Use Mac address to generate unique entity Ids

ota:
  platform: esphome

light:
  - platform: status_led #Built in LED to show status for SP8266
    name: "ESP8266 LED"
    id: devicename_led
    pin: 
      number: $led_pin
      # inverted: False #AT Mega with ESP8266
      inverted: True #D1 Mini Pro ESP8266
    restore_mode: RESTORE_DEFAULT_ON

binary_sensor:
  - platform: gpio
    pin: 
      number: $button_pin
      inverted: True
    name: "ESP8266 Button"


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

uartex:
  rx_timeout: 10ms
  tx_delay: 50ms
  tx_timeout: 500ms
  tx_retry_cnt: 3
  rx_priority: loop #Lower priority for Rx than main loop tasks
  # rx_header: [0x02, 0x01]
  rx_footer: [0x0D, 0x0A]
  # tx_header: [0x02, 0x01]
  tx_footer: [0x0D, 0x0A]

  version:
    name: "Uartex Version"
    disabled: False
  error:
    name: "Uartex Error"
    disabled: False
  log:
    name: "Uartex Log"
    disabled: True #False

sensor: 
  - platform: uartex
    name: "Sensor 1"
    state: "XX01"
    lambda: |-
      auto n = parse_number<float>(std::string(reinterpret_cast<const char*>(data+4), len-4).c_str());
      return n.has_value() ? n.value() : NAN;

  - platform: uartex
    name: "Sensor 2"
    state: "XX02"
    lambda: |-
      auto n = parse_number<float>(std::string(reinterpret_cast<const char*>(data+4), len-4).c_str());
      return n.has_value() ? n.value() : NAN;

  - platform: uartex
    name: "Sensor 3"
    state: "XX03"
    lambda: |-
      auto n = parse_number<float>(std::string(reinterpret_cast<const char*>(data+4), len-4).c_str());
      return n.has_value() ? n.value() : NAN;

  - platform: uartex
    name: "Sensor 4"
    state: "XX04"
    lambda: |-
      auto n = parse_number<float>(std::string(reinterpret_cast<const char*>(data+4), len-4).c_str());
      return n.has_value() ? n.value() : NAN;

  - platform: uartex
    name: "Sensor 5"
    state: "XX05"
    lambda: |-
      auto n = parse_number<float>(std::string(reinterpret_cast<const char*>(data+4), len-4).c_str());
      return n.has_value() ? n.value() : NAN;

  - platform: uartex
    name: "Sensor 6"
    state: "XX06"
    lambda: |-
      auto n = parse_number<float>(std::string(reinterpret_cast<const char*>(data+4), len-4).c_str());
      return n.has_value() ? n.value() : NAN;

  - platform: uartex
    name: "Sensor 7"
    state: "XX07"
    lambda: |-
      auto n = parse_number<float>(std::string(reinterpret_cast<const char*>(data+4), len-4).c_str());
      return n.has_value() ? n.value() : NAN;

  - platform: uartex
    name: "Sensor 8"
    state: "XX08"
    lambda: |-
      auto n = parse_number<float>(std::string(reinterpret_cast<const char*>(data+4), len-4).c_str());
      return n.has_value() ? n.value() : NAN;

  - platform: uartex
    name: "Sensor 9"
    state: "XX09"
    lambda: |-
      auto n = parse_number<float>(std::string(reinterpret_cast<const char*>(data+4), len-4).c_str());
      return n.has_value() ? n.value() : NAN;

  - platform: uartex
    name: "Sensor 10"
    state: "XX10"
    lambda: |-
      auto n = parse_number<float>(std::string(reinterpret_cast<const char*>(data+4), len-4).c_str());
      return n.has_value() ? n.value() : NAN;

  - platform: uartex
    name: "Sensor 11"
    state: "XX11"
    lambda: |-
      auto n = parse_number<float>(std::string(reinterpret_cast<const char*>(data+4), len-4).c_str());
      return n.has_value() ? n.value() : NAN;

  - platform: uartex
    name: "Sensor 12"
    state: "XX12"
    lambda: |-
      auto n = parse_number<float>(std::string(reinterpret_cast<const char*>(data+4), len-4).c_str());
      return n.has_value() ? n.value() : NAN;

  - platform: uartex
    name: "Sensor 13"
    state: "XX13"
    lambda: |-
      auto n = parse_number<float>(std::string(reinterpret_cast<const char*>(data+4), len-4).c_str());
      return n.has_value() ? n.value() : NAN;

  - platform: uartex
    name: "Sensor 14"
    state: "XX14"
    lambda: |-
      auto n = parse_number<float>(std::string(reinterpret_cast<const char*>(data+4), len-4).c_str());
      return n.has_value() ? n.value() : NAN;

  - platform: uartex
    name: "Sensor 15"
    state: "XX15"
    lambda: |-
      auto n = parse_number<float>(std::string(reinterpret_cast<const char*>(data+4), len-4).c_str());
      return n.has_value() ? n.value() : NAN;

Uartex data uses a vector, so it does allocate dynamic memory, but I don’t think it should be using that much. MQTT also seems to use some dynamic memory as well. If possible, could we take a look at the stack trace? You can download the ELF file from ESPHome and then add the stack trace information to this link.

Also, could you add a debug sensor here to monitor the heap status?