How to transfer data from uart

You need a custom UART devices, which involves writing a short piece of C++ code.

There is an example in the docs, and if you search the forum for “custom UART” you will find plenty of posts.

https://esphome.io/custom/uart.html

Hi,
i see you maybe need the same as i do :slight_smile:
maybe you also get some infos from my topic.

The UART Bus component provides a write action but no read actions. Typically the UART read is implemented in a custom component that also does the processing. The UART debug sequence can be exploited to act like a call back function whenever there is received data to process. So basically instead of creating a small custom component a large lambda call can be used in the UART debug yaml. Here was an example that implements processing in the UART debug for the text formatting you provided. I tested it on an esp32 devboard with a jumper to loop back the UART tx pin output back into the rx pin. The buttons entities in HA can be used to test the logic.

esphome:
  name: uart-hack

esp32:
  board: esp32dev
  framework:
    type: arduino

api:

logger:
  baud_rate: 115200

ota:
  password: !secret esp_home_ota_pw

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  
uart:
  baud_rate: 115200
  tx_pin: 17
  rx_pin: 16
  id: UART3
  debug:
    direction: BOTH
    dummy_receiver: true
    after:
      delimiter: "\r\n"
    sequence:
      - lambda: |-
          UARTDebug::log_string(direction, bytes);
          int sensorID=0;
          float sensorTEMP=0; 
          float sensorRH=0; 
          std::string str(bytes.begin(), bytes.end());
          
          //there are 4 sensors
          //where<<< id:temperature:humidity
          //"3:37.86:10\r\n"
          if (sscanf(str.c_str(), "%d:%f:%f", &sensorID, &sensorTEMP, &sensorRH) == 3 ) {
            if(sensorID==1) {
              id(temp1).publish_state(sensorTEMP); 
              id(RH1).publish_state(sensorRH); 
            }
            if(sensorID==2) {
              id(temp2).publish_state(sensorTEMP); 
              id(RH2).publish_state(sensorRH); 
            }
            if(sensorID==3) {
              id(temp3).publish_state(sensorTEMP); 
              id(RH3).publish_state(sensorRH); 
            }
            if(sensorID==4) {
              id(temp4).publish_state(sensorTEMP); 
              id(RH4).publish_state(sensorRH); 
            }        
          }

sensor:
  - platform: template
    name: "Temp 1"
    id: "temp1"
  - platform: template
    name: "Temp 2"
    id: "temp2"
  - platform: template
    name: "Temp 3"
    id: "temp3"
  - platform: template
    name: "Temp 4"
    id: "temp4"
  - platform: template
    name: "RH 1"
    id: "RH1"
  - platform: template
    name: "RH 2"
    id: "RH2"
  - platform: template
    name: "RH 3"
    id: "RH3"
  - platform: template
    name: "RH 4"
    id: "RH4"

button:
  - platform: template
    name: "Test Button 1"
    on_press:
      - uart.write: "1:1.1:111\r\n"
  - platform: template
    name: "Test Button 2"
    on_press:
      - uart.write: "1:2.2:222\r\n"
  - platform: template
    name: "Test Button 3"
    on_press:
      - uart.write: "2:33.33:44.44\r\n"
  - platform: template
    name: "Test Button 4"
    on_press:
      - uart.write: "giberish\n"      
7 Likes

Can’t you skip the arduinos and deal with the sensors directly in esphome?

Thank you friend. :grinning: Your code is working. Data is sent to Home Assistant

1 Like

This is cool. I wasn’t aware of this approach. I think a lot of people get stuck with UART once you need a custom component.

I am not aware of this approach used anywhere else but that was my goal, find a way to help people read UART data without a custom component. It seems to be well suited for implementing simple UART devices where the code in the lambda would be 90% of the custom component code.

Key points are:

  • dummy_receiver: true to make sure the debug logging gets triggered
  • the rx data is returned in std::vector<uint8_t> bytes
  • delimiter and timeout tweaked so rx data is presented in consistent format
  • direction: RX if uart.write is used to tx data
3 Likes

Are you aware of any key differences in the two approaches such as performance or reliability?

The uart-logging-hack:

  • doesn’t have a setup() if you need to perform some initialization (esphome.on_boot could be work around)
  • is constrained to cases where the rx data packets can be handled by the uart debug after mapping
  • only gets called when data arrives

I think reliability and performance should be similar between both approaches. The uart-logging-hack is only called when there is data to process but the framework is still checking and buffering the data in the background.

1 Like

I have problems with a very similar set-up which is a modified version of your suggestion to capture a Rx into the uart

[22:38:04][D][uart_debug:158]: <<< "1:4.12\n"
[22:38:08][D][uart_debug:158]: <<< "1:4.12\n"
[22:38:10][D][uart_debug:158]: <<< "1:4.12\n"

For some reason I think it is not being ‘captured’ by the if statement and the ‘batVol’ sensors don’t update when a message is received (I have adjusted names of variables from your example)

esphome:
  name: "esp-now-hub-v1"

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:
  level: verbose

uart:
  id: uart_bus
  rx_pin: 16
  tx_pin: 17
  baud_rate: 115200
  debug:
    direction: BOTH
    dummy_receiver: true
    after:
      delimiter: "\n"
    sequence:
      - lambda: UARTDebug::log_string(direction, bytes);
          int sensorID=0;
          float sensorBatVol=0; 
          std::string str(bytes.begin(), bytes.end());
          //there are x sensors
          //where<<< id:batteryvoltage
          //"1:4.00\n"
          if (sscanf(str.c_str(), "%d:%f", &sensorID, &sensorBatVol) == 2 ) {
            if(sensorID==1) {
              id(batvol1).publish_state(sensorBatVol); 
            }
            if(sensorID==2) {
              id(batvol2).publish_state(sensorBatVol);  
            }
            if(sensorID==3) {
              id(batvol3).publish_state(sensorBatVol);  
            }
            if(sensorID==4) {
              id(batvol4).publish_state(sensorBatVol); 
            }        
          }



# Enable Home Assistant API
api:

ota:

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Esphome-Web-6Bdf8C"
    password: "hnLdPNIUXUZq"

captive_portal:

sensor:
  - platform: wifi_signal # Reports the WiFi signal strength/RSSI in dB
    name: "WiFi Signal dB"
    id: wifi_signal_db
    update_interval: 60s
    entity_category: "diagnostic"

  - platform: copy # Reports the WiFi signal strength in %
    source_id: wifi_signal_db
    name: "WiFi Signal Percent"
    filters:
      - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
    unit_of_measurement: "Signal %"
    entity_category: "diagnostic"
  - platform: template
    name: "Battery Voltage 1"
    id: "batvol1"
    update_interval: 5s
  - platform: template
    name: "Battery Voltage  2"
    id: "batvol2"
  - platform: template
    name: "Battery Voltage  3"
    id: "batvol3"
  - platform: template
    name: "Battery Voltage  4"
    id: "batvol4"
button:
  - platform: template
    name: "Test Button 1"
    on_press:
      - uart.write: "1:4.1\n"
  - platform: template
    name: "Test Button 2"
    on_press:
      - uart.write: "1:2.2\n"  

I also incorporated the test buttons and loop back wire. The test button creates the response at the Rx but again the sensorBatVol never updates.

Things to note:

There are only 2 arguments. the Sensor number and the Battery voltage
I only seen to have a /n in my message (even though I added a /r as well on the Tx of the message sending ESP32.

Also - have I amended this line correctly?

 if (sscanf(str.c_str(), "%d:%f", &sensorID, &sensorBatVol) == 2 ) {
1 Like

btw just to add a bit more information. This is the write statement to the sending ESP 32 Tx port

  Serial2.write("1:4.12", 6); // write data to serial port for onward transmission to Home Assistant
  Serial2.write('\r\n');

I should be able to work this out but it’s got me beat! :expressionless:

ah ha found the error. It was formatting

I changed this:

      - lambda: UARTDebug::log_string(direction, bytes);
          int sensorID=0;

to this:

      - lambda: |-
          UARTDebug::log_string(direction, bytes);
          int sensorID=0;

Clearly it makes a difference!

1 Like

Hi.
I’m trying to use your code to extract specific values from the string received by UART.
I receive this string:

57,67,251,0,1,0,31,249

I would like to get the first two values from this string and add them to the HA entity.
Will you help?

Take a look at this post for some more general examples. Assuming you want the first two values in HA as numeric sensors, modify the section with sscanf to extract the first two values. Example was changed to use comma as delimiter between the values. The ==2 make sure sscanf reads two values.

          //Sample uart text protocol 
          if (sscanf(str.c_str(), "%d,%d", &val1, &val2) == 2 ) {
              id(sensor1).publish_state(val1); 
              id(sensor2).publish_state(val2);
          }
3 Likes

The code probably works but not entirely correctly.
Code:

    sequence:
    - lambda: |-
       UARTDebug::log_int(direction, bytes,',');

         int val1=0;
         int val2=0;
         std::string str(bytes.begin(), bytes.end());

       if (sscanf(str.c_str(), "%d,%d", &val1, &val2) == 2 ) {
       id(sensor1).publish_state(val1); 
       id(sensor2).publish_state(val2);
          }

sensor:
  - platform: template
    name: "KOCIOŁ"
    unit_of_measurement: "°C"
    id: "sensor1"
  - platform: template
    name: "ZASOBNIK C.W.U."
    unit_of_measurement: "°C"
    id: "sensor2"

And these two entities in HA still have the “unknown” status

How do the ESPHOME logs look?

Is this from UARTDebug::log_string or UARTDebug::log_int? If it is log_int then you would just need this in the debug lambda:

       UARTDebug::log_int(direction, bytes,',');
       id(sensor1).publish_state( bytes[0] ); 
       id(sensor2).publish_state( bytes[1]);

The log_string debug output treats the received byte vales as the ASCII index for each character. For the log_int, it prints the byte values received formatted as integers.

1 Like

Amazing, it works perfectly.
Thank you very much.

1 Like

Hi Gents,
@mulcmu this is a very old post with a very smart solution, that’s awesome!
You saved my day!
Best,
Luca

1 Like

hi all
can someone tell me what is wrong with my code ?

esp dont put values in templates , rawstring is ok
my code:

uart:
  baud_rate: 9600
  tx_pin: GPIO17 
  rx_pin: GPIO16
  debug:
    direction: RX
    dummy_receiver: true
    after:
      delimiter: "\n\r"
    sequence:
      - lambda: |-
          UARTDebug::log_string(direction, bytes);
          std::string str(bytes.begin(), bytes.end());
          id(rawString).publish_state(str.c_str()); 
          float sensors_values0 =0;
          float sensors_values1 =0;
          float sensors_values2 =0;
          if (sscanf(str.c_str(), "%f;%f;%f", &sensors_values0, &sensors_values1, &sensors_values2) == 3 ) {
              id(tempA1).publish_state(sensors_values0);            
              id(tempA2).publish_state(sensors_values1);
              id(tempA3).publish_state(sensors_values2);
          }         

sensor:
  - platform: template
    name: "A1"
    id: "tempA1"
  - platform: template
    name: "A2"
    id: "tempA2"
  - platform: template
    name: "A3"
    id: "tempA3"

thanks