Reading/Sending to ESP32 serial: UART vs. MQTT (for HA workflows)

Hello there,

I’ve been researching alot to find a solution without luck. My last resort is here.

My current setup:
3xEZO-PMPs —> ESP32 —> H.A (on RPi4)

The EZO Pump receives & sends commands via UART: to dispense certain amounts, calibration, report issues, report dispensed amounts…etc

Using ESPHome UART i was able to:

  • Adding a switch that will successfully send UART command to the EZO (not optimal but good for testing)

But I wasn’t able to:

  • Read UART response from EZO
  • Make Node-red workflows by sending/receiving UART serial comm to esp32

My goal is:
1- Having workflows in node red to control the ezo via uart serial comm in esp32
2- Having workflows based on received data via uart (if error is x do y)
3- displaying error logs coming in RX UART in HA logs or dashboard or anywhere.

————

Now I’m reading people able to do similar workflows by pushing tx/rx to MQTT then handle them… but before setting up an MQTT service and changing everything to it, i’m wondering is the UART path not scalable and will be difficult to manage? Is it more efficient and better to do it all via mqtt?

Thank you guys

1 Like

This really cool. I have been looking at other Atlas stuff, specifically EZO sensors for pool monitoring. The step after that would be a perstaltic pump for pool chemicals like raspipool. The EZO sensors have recently been incorporated into esphome.

Back to your subject, the UART component in esphome has a write, but not a read. However you can use a custom component to read. I think there are some examples in here https://esphome.io/guides/diy.html

However I think this is probably better long term as an i2c component. The sensors are done over i2c, although the pumps are a good deal more complex.

Yeah really loved how they are providing flexible systems with good documentation, got me excited to do this project.

Speaking of UART, that’s unfortunate… I was planning to avoid i2c not to over complicate things and using only UART communications. Nonetheless, it seems having every communication done over MQTT would be a solid scalable option.

I believe I can get EZO-PMP <-> ESP32 UART output to be sent over MQTT, and ESP32 reading MQTT and sending corresponding UART commands to EZO.

Thanks!

I will watch with interest :slight_smile:

1 Like

So I got initial successful approach for reading UART in ESPHOME, and so far it works in showing the UART response in ESPhome log for that device:

This is using custom UART text sensor, as shown here: https://esphome.io/components/text_sensor/uart.html

Using that, i can both read & write UART commands (seeing ESPhome examples) and it seems ALOT MORE.

So from there, using the capabilities of:
1- Text Sensor: the Implementation of conditions & automations for text sensors is useful
2- Custom Text Sensor: Understanding the ability to define multiple ’ text_sensors’ in one lambda function is useful for cleaner implementation.
3- lambdas func.: amazing capability to push, receive and manipulate data & actions
4- HA API: Ability to get data from H.A. to process it and push it back as you see fit!!

I believe there is a good track to do full & flexible automation using NODE-RED & ESPhome via UART comm (no MQTT).

But the question still remains, is this a better approach than MQTT for scalability and stability in the automation system? or MQTT would be a better approach.

1 Like

IMHO node red is not a better approach than MQTT.

The uart text sensor was added in 1.15 and I had forgotten it even existed sorry.

Well done.

1 Like

So update in case anyone is interested,

I got it to work perfectly with EZO-PMP using ESP32 in Esphome and communication via UART.

  1. First: Defining UART comm for EZO:
# UART for EZO-Pumps: Defining all
uart:
  - id: pH_down
    tx_pin: 19
    rx_pin: 21
    baud_rate: 9600
  1. Custom Text Sensor to monitor logs for each UART comm, using Lambda to use UartReadLineSensor function that translate signals and return to text sensor
  2. homeassistant text sensor to read manual commands from HA and relay that to EZO.
text_sensor:
  #Display UART data in LOGGER
  - platform: custom
    text_sensors:
      id: "pH_down_uart"
      name: "UART READ"
    lambda: |-
      auto pH_down_sensor = new UartReadLineSensor(id(pH_down));
      App.register_component(pH_down_sensor);
      return {pH_down_sensor};
  #Read TextInput in Home Assistant
  - platform: homeassistant
    name: "EZO COMMANDS"
    id: ezo_command
    entity_id: input_text.ezo_command
  1. A button/switch that will read the written command in HA text box and push it to UART. Using uart.write method and Lambda to convert text strings to uart vector while hard-coding ‘carriage return’ signal at the end then “write signal to uart”.
################## CONTROLS ##################
switch:
# EZO-PUMP push Commands
  - platform: template
    name: "Send Command"
    id: send_cmd
    icon: "mdi:send"
    turn_on_action:
      - uart.write:
         id: pH_down
         data: !lambda |-
          std::string str = id(ezo_command).state;
          std::vector<uint8_t> vec(str.begin(), str.end());
          vec.push_back('\r');
          return vec;
      - delay: 2000ms
      - switch.turn_off: send_cmd

Now, this is enough to display this dashboard to control and monitor responses:


For Automation using Node-Red, Or any other HA automations, we can easily configure HA API like this:

# Enable Home Assistant API
api:
  password: "********"
  services:
    - service: uart_command
      variables:
        pump: string
        command: string
      then:
        - if:
            condition:
              lambda: 'return pump == "1";'
            then:
              - uart.write:
                 id: pH_down
                 data: !lambda |-
                  std::string str = command;
                  std::vector<uint8_t> vec(str.begin(), str.end());
                  vec.push_back('\r');
                  return vec;

Noting:

  1. I added extra pump variable, to select which UART comm to send signal to.
  2. Using same Lambda that converts txt to UART vector signals in uart.write method

This will show an entity in HA that you can call, automate or anything from anywhere. :smiley:

Thanks also to the esphome Discord channel, they really helped in understanding many of the functions.

4 Likes

hi, trying to follow your lead but this doesn’t seem to work for me (yet).
can you tell me which part goes into the esphome file and which part goes to the configuration YAML file

All the published code here is esphome code.

/config/esphome/ph-down.yaml: In lambda function:
/config/esphome/ph-down.yaml:38:33: error: expected type-specifier before 'UartReadLineSensor'
       auto pH_down_sensor = new UartReadLineSensor(id(pH_down));
                                 ^
/config/esphome/ph-down.yaml:40:29: error: could not convert '{pH_down_sensor}' from '<brace-enclosed initializer list>' to 'std::vector<esphome::text_sensor::TextSensor*>'
       return {pH_down_sensor};
                             ^
*** [/data/ph-down/.pioenvs/ph-down/src/main.cpp.o] Error 1

this is the error message i get after i insert the code as is into esphome and try to upload it.

hello, I also get the same error, did you manage to solve it? thanks greetings.
Kaesar

good evening, I realized that I needed to create the file create file uart_read_line_sensor.h within ESPHOME.

#include "esphome.h"

class UartReadLineSensor : public Component, public UARTDevice, public TextSensor {
 public:
  UartReadLineSensor(UARTComponent *parent) : UARTDevice(parent) {}

  void setup() override {
    // nothing to do here
  }

  int readline(int readch, char *buffer, int len)
  {
    static int pos = 0;
    int rpos;

    if (readch > 0) {
      switch (readch) {
        case '\n': // Ignore new-lines
          break;
        case '\r': // Return on CR
          rpos = pos;
          pos = 0;  // Reset position index ready for next time
          return rpos;
        default:
          if (pos < len-1) {
            buffer[pos++] = readch;
            buffer[pos] = 0;
          }
      }
    }
    // No end of line has been found, so return -1.
    return -1;
  }

  void loop() override {
    const int max_line_length = 80;
    static char buffer[max_line_length];
    while (available()) {
      if(readline(read(), buffer, max_line_length) > 0) {
        publish_state(buffer);
      }
    }
  }
};

and add the line

includes:
    - uart_read_line_sensor.h

in the esphome

and walks out
greetings and thanks

I created the file uart_read_line_sensor.h and put it in the esphome directory. But if i try to install it on the esp32 i get the following error==>Expected class name before ’ { ’
The code is exactly the same as youcan find on the ESPhome page:

Compiling /data/serial-gateway/.pioenvs/serial-gateway/src/main.cpp.o
In file included from src/main.cpp:26:0:
src/uart_read_line_sensor.h:3:83: error: expected class-name before '{' token
 class UartReadLineSensor : public Component, public UARTDevice, public TextSensor {
                                                                                   ^
src/uart_read_line_sensor.h: In member function 'virtual void UartReadLineSensor::loop()':
src/uart_read_line_sensor.h:40:29: error: 'publish_state' was not declared in this scope
         publish_state(buffer);
                             ^
*** [/data/serial-gateway/.pioenvs/serial-gateway/src/main.cpp.o] Error 1
========================== [FAILED] Took 8.59 seconds ========================== 

Does anybody have an idea??

how to transforme the send command to HEX?
std::string str = id(ezo_command).state;
std::vector<uint8_t> vec(str.begin(), str.end());
vec.push_back(’\r’);
where can I modefiy?