Get data from serial port

Hello everyone!

Firstly, what am I trying to achieve? I’d like to have the water level of a rain water reservoir as well as feedback if there is moisture in the pipes leading to the house.

What do I have currently? I have got a Nano in the tank that sends data of the ultrasonic distance sensor and a boolean that tells me if there is a lot of moisture/water in the pipe. The messages are structured like this: LM<moisture as a boolean (value: 0;1)>.
Currently, I display the data on a web server and store the water levels as CSVs on my NAS, but I just discovered HA and would like to implement it there.
I already installed the ESPHome plugin and installed ESPHome on the esp. I can configure and upload the YAML rn.

What I need HA/ESPHome to do. It needs to collect the data from the serial communication and display it. The water level is given as cm (float) and should be converted in the degree of filling. Therefore, the user (I) need to be able to enter a value of cm for max and min, so the software can calculate the percentage.

If you have any idea, please tell me. Also, what the code does and what exactly I have to do. As I said, I’m completely new, but I’d love to learn more. Thanks!

Here is what I got so far as code. The entities are there, but I can’t get the values. It shows N/A.

substitutions:
  name: esphome-web-******
  friendly_name: ESPHome Tank

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  min_version: 2024.6.0
  name_add_mac_suffix: false
  project:
    name: esphome.web
    version: dev

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:
  level: VERBOSE #makes uart stream available in esphome logstream
  baud_rate: 0 #disable logging over uart

# Enable Home Assistant API
api:

# Allow Over-The-Air updates
ota:
- platform: esphome

wifi:
  # Set up a wifi access point
  ap: {}

# In combination with the `ap` this allows the user
# to provision wifi credentials to the device via WiFi AP.
captive_portal:

dashboard_import:
  package_import_url: github://esphome/example-configs/esphome-web/esp32.yaml@main
  import_full_config: true

# Sets up Bluetooth LE (Only on ESP32) to allow the user
# to provision wifi credentials to the device.
esp32_improv:
  authorizer: none

# To have a "next url" for improv serial
web_server:

uart:
  baud_rate: 9600
  tx_pin: 1
  rx_pin: 3
  id: UART3
  debug:
    direction: RX
    dummy_receiver: true
    sequence:
      - lambda: |-
          auto str = std::string(bytes.begin(), bytes.end());
          float sensorL = 0;
          float sensorM1 = 0;
          
          // Parsing the sensor data "L<level>M<moisture>"
          if (sscanf(str.c_str(), "L%fM%f", &sensorL, &sensorM1) == 2){
            id(level).publish_state(sensorL);
            id(moisture).publish_state(sensorM1);
          }

sensor:
  - platform: template
    name: "Level"
    id: "level"
  - platform: template
    name: "Moisture"
    id: "moisture"

I am not sure what a nano is, but why not replace it with an esp and do the whole lot in esphome?

The nano is an Arduino Nano. I can’t replace it with an esp directly because the arduino is outside in the tank and there is no way it can connect to internet. In addition I only got 2 wires and power in the tank. So thats the only communication I can think of rn.

1 Like

Fair enough!

Can you do a full log please, particularly when the nano sends some data.

Use binary sensor for moisture.
Height conversion to level % you can do with calibrate linear filter.
Be aware that serial wiring should be short/shielded and that you need level shifter for Nano 5V to Esp 3.3.V, at least for rx line.

sensor:
  - platform: template
    name: "Level"
    id: "level"
    filters:
      - calibrate_linear:
          - 0.0 -> 0
          - 140.0 -> 100 #whatever your full/empty level readings are..
binary_sensor:
  - platform: template
    name: "Moisture"
    id: "moisture"
1 Like

I would use lots of little print/log statements to work through correct parsing of the values you want.
ESP_LOGD().

Also as Nick said, share logs as you go …

You might find some useful things here too:

1 Like

Hey everyone! Thanks for your help :slight_smile: I did get it running already (with some major help). However, if you have any improvements please tell me!

esphome:
  name: tank

esp32:
  board: esp32dev
  framework:
    type: arduino

logger:

api:

ota:
  platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  
uart:
  baud_rate: 115200
  rx_pin: 16
  id: UART3
  debug:
    direction: RX
    dummy_receiver: true
    sequence:
      - lambda: |-
          auto str = std::string(bytes.begin(), bytes.end());
          float sensorL = 0;
          float sensorM1 = 0;
          
          // Parsing the sensor data "L<level>M<moisture>"
          if (sscanf(str.c_str(), "L%fM%f", &sensorL, &sensorM1) == 2)
          {
            id(rawlevel).publish_state(sensorL);
            id(rawmoisture).publish_state(sensorM1);
          }

sensor:
  - platform: template
    name: "Raw Level"
    unit_of_measurement: "cm"
    device_class: "distance"
    state_class: "measurement"
    id: "rawlevel"
    filters:
      throttle_average: 60s

  - platform: template
    name: "Raw Moisture"
    device_class: "moisture"
    id: "rawmoisture"
    filters:
      throttle_average: 60s

  - platform: copy
    source_id: rawlevel
    name: "Percentage Level"
    unit_of_measurement: "%"
    icon: mdi:water-percent
    id: "precentagelevel"
    filters:
      lambda: return -(x-id(levelend).state)/(id(levelend).state-id(levelstart).state)*100;

  - platform: copy
    source_id: precentagelevel
    name: "Volume Level"
    unit_of_measurement: "L"
    device_class: "water"
    state_class: "measurement"
    filters:
      lambda: return x*id(max_volume).state/100;


binary_sensor:
  - platform: template
    name: "Moisture"
    lambda: |-
      float thresh = id(moisturethreshold).state;
      float value = id(rawmoisture).state;
      if (value >= thresh + 1)
        return true;
      if (value <= thresh - 1)
        return false;
      return {};

number:
  - platform: template
    name: "Level Start"
    optimistic: true
    min_value: 20
    max_value: 125
    initial_value: 20
    step: 1
    id: levelstart

  - platform: template
    name: "Level End"
    optimistic: true
    min_value: 20
    max_value: 125
    initial_value: 125
    step: 1
    id: levelend

  - platform: template
    name: "Max Volume"
    optimistic: true
    min_value: 0
    max_value: 4000
    initial_value: 4000
    step: 1
    id: max_volume

  - platform: template
    name: "Moisture Threshold"
    optimistic: true
    min_value: 0
    max_value: 1023
    initial_value: 511
    step: 1
    id: moisturethreshold

2 Likes