Custom component (A02YYUW) ESP Home

Thanks a lot!! You saved the day!!

Now, I will install the A02YYUW in the water tank, let’s see if it works without problems of condensation.

Regards
Jordi

How did that go for you @Despertaferro ?

Well, I had some troubles with the readings, highly random and with a lot of fluctuations. I still have to check thoroughly the cables and try again. I will chime in with news once I figure out what is going on. I guess that some of the soldering I did is defective, or maybe too long cables adding too much noise, go figure…

Two days and much frustration later:

Use Fileeditor
add file: /config/esphome/A02YYUW-Sensor.

#include "esphome.h"

class MyCustomSensor : public PollingComponent, public Sensor {
 public:
  MyCustomSensor() : PollingComponent(1000) {}

  void setup() override {
  
    // Configure the UART for communication with A02YYUW-Sensor
    Serial.begin(9600);
    pinMode(echo_pin_, INPUT);
    pinMode(trigger_pin_, OUTPUT);
  }

  void update() override {
  
    // Trigger the sensor to send a pulse
    digitalWrite(trigger_pin_, LOW);
    delayMicroseconds(2);
    digitalWrite(trigger_pin_, HIGH);
    delayMicroseconds(10);
    digitalWrite(trigger_pin_, LOW);

    // Read the pulse duration and calculate the distance
    long duration = pulseIn(echo_pin_, HIGH);
    float distance = (duration / 2) / 29.1;

    // Publish the distance as a sensor reading
    publish_state(distance);
  }

 private:
  const uint8_t echo_pin_   = 16;  // YOUR PIN HERE
  const uint8_t trigger_pin_ = 17;  // YOUR PIN HERE
};

And here the code snipped From EspHome-Device:

esphome:
  name: water-sensor
  friendly_name: Water-Sensor
  includes: 
    - A02YYUW-Sensor.h

esp32:
  board: node32s
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "YOUR KEY HERE"

ota:
  password: "YOUR PW HERE"

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

    # Static IP
  manual_ip:
    static_ip: 192.168.178.100
    gateway: 192.168.178.1
    subnet: 255.255.255.0

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Water-Sensor Fallback Hotspot"
    password: "YOUR PW HERE"


sensor:
- platform: custom
  lambda: |-
    auto my_sensor = new MyCustomSensor();
    App.register_component(my_sensor);
    return {my_sensor};
  sensors:
      name: "Tank Water Level"
      unit_of_measurement: "cm"
      accuracy_decimals: 0

It´s not the final version, but its works! :wink:
Hope it helps and gives you a good starting point.

p.s.

  • Unfortunately, uart did not work for me in any way.
  • ESPHome Current version: 2023.2.4

Best regards

Hi guys,
I am playing with that same sensor (A02YYUW) in ESPhome, too. I am currently using @taraant custom component. As it is nicer code then the one I glued together.
But, I find annoying to poll the sensor every 100 ms as I need the info, let’s say every minute. And you can not make the update interval bigger, as the UART apparently have a buffer. So, if I change the polling interval, I get to read the data from buffer, but only 4 bytes at a time. AKA, I get the data from now, much later in a future.

Do anybody know, the proper approach to this? I was thinking about, serial.begin() in .h file. That might help to solve that issue. Or is there another “proper way” to do this?

Bonjour j’ai aussi un capteur A02YYUW avec carte wemos d1 et je arrive pas à avoir de valeurs dans home assistant avec esp home

Pouvez vous m’expliquer quel code yaml avez vous modifier avec file editor ?

Bonjour j’ai le même projet avec le capteur ultrason je ne comprend pas les codes ci dessus ou faut il les installer ? Config/esp home/?

Hi Fantomaz,

  1. Install the Plugin: Esp-Home
  2. Install the Plugin: File editor
  3. Add a new file: /config/esphome/A02YYUW-Sensor.h (or what ever you want)
  4. Include this file in the ESP-Home Yaml (Your device script in ESP-Home):
esphome:
  name: water-sensor
  friendly_name: Water-Sensor
  includes: 
    - A02YYUW-Sensor.h

Thanks @Equilibrium for the custom component - however I am having some trouble with it, as it will only output a distance of 16cm. Is this still working for you?

[14:13:41][D][sensor:110]: 'Tank Water Level': Sending state 16.08247 cm with 0 decimals of accuracy
[14:13:42][D][sensor:110]: 'Tank Water Level': Sending state 16.04811 cm with 0 decimals of accuracy
[14:13:43][D][sensor:110]: 'Tank Water Level': Sending state 16.04811 cm with 0 decimals of accuracy
[14:13:44][D][sensor:110]: 'Tank Water Level': Sending state 16.08247 cm with 0 decimals of accuracy
[14:13:45][D][sensor:110]: 'Tank Water Level': Sending state 16.08247 cm with 0 decimals of accuracy

So i was finally able to get the A02YYUW sensor working with UART , here is the below code. Following above code i was also getting the same readings.

I am using the sensor with ESP32 so using the hardware serial at GPIO 16/17

below is the code for the A02YYUW.h file

#pragma once

#include "esphome.h"

class A02YYUWSensor : public esphome::Component, public esphome::sensor::Sensor {
public:
  void setup() override;
  void loop() override;

private:
  HardwareSerial serial_ = HardwareSerial(2); // Use the second hardware serial port (GPIO16=RX, GPIO17=TX)
  uint8_t buffer_[4] = {};
  float distance_ = 0.0f;
};

void A02YYUWSensor::setup() {
  // Set up the serial port for the UART communication
  serial_.begin(9600, SERIAL_8N1, 16, 17); // baud rate, parity, RX pin, TX pin
}

void A02YYUWSensor::loop() {
  // Read the UART data from the sensor
  while (serial_.available()) {
    if (buffer_[0] == 0xFF && serial_.readBytes(buffer_ + 1, 3) == 3) {
      uint8_t sum = (buffer_[0] + buffer_[1] + buffer_[2]) & 0xFF;
      if (sum == buffer_[3]) {
        uint16_t distance_raw = (buffer_[1] << 8) | buffer_[2];
        if (distance_raw > 30) {
          distance_ = static_cast<float>(distance_raw) / 10.0f; // Convert to cm
        } else {
          distance_ = 0.0f; // Below the lower limit
        }
        break; // Successfully parsed a valid UART frame, exit the loop
      }
    } else {
      buffer_[0] = serial_.read(); // Invalid header, discard and try again
    }
  }

  // Update the sensor with the current distance value
  publish_state(distance_);

  // Your code for processing sensor data goes here
  // ...
}

below is the code for the yaml file

esphome:
  name: tank-level
  platform: ESP32
  board: esp32dev

  includes:
    - A02YYUW-Sensor.h


# Enable logging
logger:
  level: DEBUG

# Enable Home Assistant API
api:
  encryption:
    key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

ota:
  password: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

wifi:
  ssid: "xxxxxxxxxxj"
  password: "xxxxxxxxxxxx"

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "xxxxxxxxxxxx"
    password: "xxxxxxxxxxxxxxxxxx"
uart:
  rx_pin: 16
  tx_pin: 17
  baud_rate: 9600

sensor:
  - platform: custom
    lambda: |-
      auto a02yyuw_sensor = new A02YYUWSensor();
      App.register_component(a02yyuw_sensor);
      return {a02yyuw_sensor};
    sensors:
      - name: "Tank Level"
        unit_of_measurement: cm
        accuracy_decimals: 1
        icon: mdi:gauge

image

just try this one and update if this holds good for you too.

4 Likes

Thank you for this, it works perfectly :slight_smile:

Is there a way to reduce the interval it stores in ESPHOME please?
Would it be a case of header file to only publish every xx loop, instead of constantly?

I played with the sensor with some arduino code (not in ESPHOME) and it has a default sensing delay of 100mS. When i increased that , it didnt read properly so I am assuming, it must run/sample at that frequency to function correclty?

I am just consious about how often its battering ESPHOME with readings.

Thanks

Code from @vishwamittrabhardwaj worked but with no update interval it updated every 100ms so I’ve added PollingComponent into the code

#pragma once

#include "esphome.h"

class A02YYUWSensor : public PollingComponent, public Sensor {
public:
  A02YYUWSensor() : PollingComponent(4000) {}
  void setup() override;
  void loop() override;
  void update() override;

private:
  HardwareSerial serial_ = HardwareSerial(2); // Use the second hardware serial port (GPIO16=RX, GPIO17=TX)
  uint8_t buffer_[4] = {};
  float distance_ = 0.0f;
};
void A02YYUWSensor::setup() {
  // Set up the serial port for the UART communication
  serial_.begin(9600, SERIAL_8N1, 44, 43); // baud rate, parity, RX pin, TX pin
}

void A02YYUWSensor::loop() {
  // Read the UART data from the sensor
  while (serial_.available()) {
    if (buffer_[0] == 0xFF && serial_.readBytes(buffer_ + 1, 3) == 3) {
      uint8_t sum = (buffer_[0] + buffer_[1] + buffer_[2]) & 0xFF;
      if (sum == buffer_[3]) {
        uint16_t distance_raw = (buffer_[1] << 8) | buffer_[2];
        if (distance_raw > 30) {
          distance_ = static_cast<float>(distance_raw) / 10.0f; // Convert to cm
        } else {
          distance_ = 0.0f; // Below the lower limit
        }
        break; // Successfully parsed a valid UART frame, exit the loop
      }
    } else {
      buffer_[0] = serial_.read(); // Invalid header, discard and try again
    }
  }
}
void A02YYUWSensor::update() {
  // Update the sensor with the current distance value
  publish_state(distance_);
}

Sorry if there mistakes, first time understanding C++ code
I used Update and Loop at the same time.
Every 4000ms publish_state is sent with the latest distance from loop.
If you just use Update then it’s the older distance that is in a queue

I also added Quantity and Percentage of water to use in a tank into the yaml file:

uart:
  rx_pin: GPIO44
  tx_pin: GPIO43
  baud_rate: 9600

sensor:
  - platform: custom
    lambda: |-
      auto a02yyuw_sensor = new A02YYUWSensor();
      App.register_component(a02yyuw_sensor);
      return {a02yyuw_sensor};
    sensors:
      - name: "Tank Level"
        id: level_cm
        unit_of_measurement: cm
        accuracy_decimals: 1
        icon: mdi:gauge

  - platform: template
    name: "Tank Quantity"
    id: quantity_l
    icon: "mdi:water"
    unit_of_measurement: "l"
    accuracy_decimals: 0
    lambda: return ((((id(level_cm).state)-7)-89)/89)*-826;
    # 7: Distance from sensor and full water
    # 826: Numbers of liters of the tank
    # 89: Max distance from top to bottom water
    update_interval: 1s
    filters:
      - clamp:
          min_value: 0
          max_value: 826

  - platform: template
    name: "Tank Percentage"
    id: percentage
    icon: "mdi:water"
    unit_of_measurement: "%"
    accuracy_decimals: 0
    lambda: return ((((id(level_cm).state)-7)-89)/89)*-100;
    update_interval: 1s
    filters:
      - clamp:
          min_value: 0
          max_value: 100

text_sensor:
  - platform: template
    name: "Tank"
    icon: "mdi:gauge-full"
    lambda: |-
      if (id(percentage).state >= 100) {
        return {"Full"};
      }
      else if (id(level_cm).state >= 89) {
        return {"Empty"};
      }
      else {
        return {"Filling"};
      }
    update_interval: 1s

You need to change the RX and TX pin, mine are from an ESP32-S3

1 Like

Official integration was added in 2023.12.0 by @TH-Braemer

I’m trying to get the A02YYUW sensor working on a Wemos D1 mini but I’m not successful. Tried every suggestion in this topic, nothing works for me.
The docs are not very helpful either. Any help would be greatly appreciated.

never mind, I got it to work.

I dont get it to work either? How did you do it?

I’m not using it any more, but this is the code I used in my yaml file:
(I had it connected to a D1 mini)

uart:
    rx_pin: D4
    baud_rate: 9600

sensor:
  - platform: a02yyuw
    id: a02yyuw_sensor
    name: "Water level"
    unit_of_measurement: 'm'
    accuracy_decimals: 1
    filters:
      - multiply: 0.001
      - throttle: 5s
      - heartbeat: 5s
      - quantile:
         window_size: 7
         send_every: 4
         send_first_at: 3
         quantile: .9
      - debounce: 0.1s

Hello!
i get this?
Failed config

sensor.a02yyuw: [source /config/esphome/avstand.yaml:41]

Platform not found: ‘sensor.a02yyuw’.
platform: a02yyuw
id: a02yyuw_sensor
name: Water level
unit_of_measurement: m
accuracy_decimals: 1
filters:
- multiply: 0.001
- throttle: 5s
- heartbeat: 5s
- quantile:
window_size: 7
send_every: 4
send_first_at: 3
quantile: 0.9
- debounce: 0.1s

What do i have to add?

i got it working!! I hade the RX TX shifted…

Thank you! Fore your answer…

/Micke

1 Like