Using Ultrasonic sensor HC-SR04 in 3-wire mode

Is it ok to use this sensor with esphome in 3-wire mode?

I’m thinking it might be ok, having read this paragraph in an online article:

In 3-Wire mode the single I/O pin is used as both an input and as an output. This is possible because there is never a time when both the input and output are being used. By eliminating one I/O pin requirement we can save a connection to our Arduino and use it for something else. It also is useful when using a chip like the ATtiny85 which has a limited number of I/O pins.

My intended example config for using it this way:

sensor:
  - platform: ultrasonic
    name: "Ultrasonic Sensor"
    # use the same pin for the trigger and echo
    trigger_pin: 26
    echo_pin: 26

I’m not sure what the esphome code looks like and haven’t experimented with this.

Then, if I may also ask a second question: What is the unit provided by the sensor? It’s not quite clear to me from the docs whether it’s m or cm.

Hey Pieter

Look here:
https://wiki.seeedstudio.com/Grove-Ultrasonic_Ranger/

Thats a 3-wire Ultra Sonic Sensor. After abit of struggle I finally got a reading into HA using ESPHome and custom sensor. Pin 5 is used.

This is the code used, removed wifi and more:
yaml

esphome:
  name: name
  platform: ESP32
  board: esp-wrover-kit
  includes:
    - Custom_US_sensor.h
  libraries:
    - "Grove Ultrasonic Ranger"

sensor:
- platform: custom
  lambda: |-
    auto my_sensor = new MyCustomSensor();
    App.register_component(my_sensor);
    return {my_sensor};

  sensors:
    name: "My Custom Sensor"
    unit_of_measurement: cm

Custom_US_sensor.h

#include "esphome.h"
#include "Ultrasonic.h"

Ultrasonic ultrasonic(5);

class MyCustomSensor : public PollingComponent, public Sensor {
 public:


  MyCustomSensor() : PollingComponent(15000) { }

  void setup() override {
    
  }

  void update() override {
    int distance = ultrasonic.MeasureInCentimeters();
    publish_state(distance);
  }
};

My plan is to make this a binary sensor. But that is another hurdle to get over.

1 Like

Thanks, this is helpful. I didn’t know you could customise it with custom C code (but I’m also not surprised given the quality of this project).

I’ve since decided just to stick to the 4-wire solution because I already had the sensors.

You can create your binary sensor in the YAML from the sensor you now have. This is what I’ve done:

  - platform: template
    name: "Garage Occupied"
    lambda: |-
      if (id(garage_ultrasonic_sensor).state < 1) {
        return true;
      } else {
        return false;
      }

Note: The 1 is one meter.

Thank you for this code! Worked for me! :slight_smile:

1 Like

Thanks a lot for this solution!
Does anyone know why I don’t see correct numbers after the decimal point? For example I get a result 8.00000 cm.

Hi all,
Since with ESPHome 2025.02 update removed the custom platform that would support the 3-pin ultrasonic configured discussed in this topic, have any of you found a solution to change the code to support new ESPHome requirements?

Thank you

Hi all, I have the same issue. How to convert the code to custom platfom ? Can we have at least some starting tracks ? :slightly_smiling_face:

Just use the normal 4-wire solution, if you can. I never in the end tried the 3-wire solution. My question was really a matter of curiosity and not necessity.

I can’t use the 4 wire solution because I have a 3 wire composant … :cold_sweat:

Do you have a link to the component’s product page?

Yes I have.

Thanks, it definitely supports only a 3-wire configuration.

Have you tried to set the trigger and echo pins the same?

I had a quick look at the code (I didn’t before). One can see it simply writes to the trigger pin and then waits on the echo pin.

I tried to trace the setup() to see whether the pin directions are configured beforehand, but I just end up at this virtual function (doesn’t look like it’s overridden by the ultrasonic component).

Note the comment:

  /** Where the component's initialization should happen.
   *
   * Analogous to Arduino's setup(). This method is guaranteed to only be called once.
   * Defaults to doing nothing.
   */

In Arduino world, pins default to input pins. It’s not clear to me what ESPHome does.

In the product page you linked, one can see how they change the mode of the pin – ESPHome would need to do the same and I don’t see it in the ultrasonic component’s code.

def _get_distance(self):
        self.dio.dir(GPIO.OUT)  # <------------
        ...

        self.dio.dir(GPIO.IN)  # <------------

So, all I can really say is: Try it. If it works, great. If it doesn’t, it’s probably because the pin directions are fixed, meaning that the config for a single pin will be conflicting.

UPDATE:

The trick might be to get the pin setup right. In the ESPHome config, you can specify more pin options according to the pin schema. Setting it to INPUT_OUTPUT_OPEN_DRAIN might be the right option here. Also see this post. If all else fails, you might be able to implement this using custom lambdas. Also note some caveats here and an example here, as well as this one.

Thank you for your reply and your time.

With the “allow_other_uses”, I can compile the yaml (before I had “Pin 7 is used in multiple places”).

An extract of my yaml code

 - platform : ultrasonic
    trigger_pin : 
      number : GPIO7
      allow_other_uses : true
      mode :
        input : true
    echo_pin :
      number : GPIO7
      allow_other_uses : true
      mode :
        output : true
        open_drain : true
    name : “DistanceFuel
    update_interval : 10s

run log

[19:48:02][D][ultrasonic.sensor:036]: 'DistanceFuel' - Distance measurement timed out!
[19:48:02][D][sensor:094]: 'DistanceFuel': Sending state nan m with 2 decimals of accuracy

I’ll try other combinations of options, but it’s a bit random. :slightly_smiling_face:

I’ll read all the related articles you give.

Thanks !

1 Like

If I compare that

https://esphome.io/api/ultrasonic__sensor_8cpp_source

void UltrasonicSensorComponent::update() {
   this->trigger_pin_->digital_write(true);
   delayMicroseconds(this->pulse_time_us_);
   this->trigger_pin_->digital_write(false);

and the code of this Grove - Ultrasonic Ranger | Seeed Studio Wiki

I think , I just need to add two lines

void UltrasonicSensorComponent::update() {
this->trigger_pin_->pin_mode(FLAG_OUTPUT);    <---THAT
      this->trigger_pin_->digital_write(true);
     delayMicroseconds(this->pulse_time_us_);
    this->trigger_pin_->digital_write(false);
   
this->echo_pin_->pin_mode(FLAG_INPUT);  <--- THAT

   const uint32_t start = micros();

Can I change this cpp file only on my home assistant to make a test ? And where is this file on my instance ?

Basically, yes.

I don’t know. It might depend on how you use ESPHome: In a Python env separate to HA, or on your HA installation (via an add-on, I think). It will be tricky though, because I don’t think that code gets recompiled when you build a config for a device. It gets done when you install ESPHome.

Creating an external component would be a more natural and maintainable way. In that case, copy the Ultrasonic code and modify it as needed. (I haven’t used this myself; I only know about this.)