Does anyone know how to get this sensor to work with Esphome?

I tried your latest code and it installed without any changes. The output to the logs look like:

[20:57:16][D][binary_sensor:036]: 'echo_receive_pin': Sending state OFF
[20:57:16][D][main:067]: Diff millisecond is: 39954
[20:57:16][D][main:085]: Diff millisecond is set: 39954
[20:57:16][D][sensor:113]: 'Last Echo Diff Time': Sending state 39954.00000  with 1 decimals of accuracy
[20:57:16][D][main:092]: Recv millisecond is set: 841419
[20:57:16][D][sensor:113]: 'Recv Milli Time': Sending state 841419.00000  with 1 decimals of accuracy
[20:57:16][D][binary_sensor:036]: 'echo_receive_pin': Sending state ON
[20:57:17][D][main:085]: Diff millisecond is set: 39954
[20:57:17][D][sensor:113]: 'Last Echo Diff Time': Sending state 39954.00000  with 1 decimals of accuracy
[20:57:27][D][sensor:113]: 'Salt Tank WiFi Signal': Sending state -21.00000 dBm with 0 decimals of accuracy
[20:57:39][D][main:092]: Recv millisecond is set: 881419
[20:57:39][D][sensor:113]: 'Recv Milli Time': Sending state 881419.00000  with 1 decimals of accuracy
[20:57:56][D][binary_sensor:036]: 'echo_receive_pin': Sending state OFF
[20:57:56][D][main:067]: Diff millisecond is: 39949
[20:57:56][D][main:085]: Diff millisecond is set: 39949
[20:57:56][D][sensor:113]: 'Last Echo Diff Time': Sending state 39949.00000  with 1 decimals of accuracy
[20:57:56][D][main:092]: Recv millisecond is set: 881419
[20:57:56][D][sensor:113]: 'Recv Milli Time': Sending state 881419.00000  with 1 decimals of accuracy
[20:57:56][D][binary_sensor:036]: 'echo_receive_pin': Sending state ON
[20:58:16][D][binary_sensor:036]: 'echo_receive_pin': Sending state OFF
[20:58:16][D][main:067]: Diff millisecond is: 19959
[20:58:16][D][main:085]: Diff millisecond is set: 19959
[20:58:16][D][sensor:113]: 'Last Echo Diff Time': Sending state 19959.00000  with 1 decimals of accuracy
[20:58:16][D][main:092]: Recv millisecond is set: 921414
[20:58:16][D][sensor:113]: 'Recv Milli Time': Sending state 921414.00000  with 1 decimals of accuracy
[20:58:16][D][binary_sensor:036]: 'echo_receive_pin': Sending state ON
[20:58:17][D][main:085]: Diff millisecond is set: 19959
[20:58:17][D][sensor:113]: 'Last Echo Diff Time': Sending state 19959.00000  with 1 decimals of accuracy
[20:58:27][D][sensor:113]: 'Salt Tank WiFi Signal': Sending state -21.00000 dBm with 0 decimals of accuracy
[20:58:36][D][binary_sensor:036]: 'echo_receive_pin': Sending state OFF
[20:58:36][D][main:067]: Diff millisecond is: 19954
[20:58:36][D][main:085]: Diff millisecond is set: 19954
[20:58:36][D][sensor:113]: 'Last Echo Diff Time': Sending state 19954.00000  with 1 decimals of accuracy
[20:58:36][D][main:092]: Recv millisecond is set: 941419
[20:58:36][D][sensor:113]: 'Recv Milli Time': Sending state 941419.00000  with 1 decimals of accuracy
[20:58:36][D][binary_sensor:036]: 'echo_receive_pin': Sending state ON
[20:58:39][D][main:092]: Recv millisecond is set: 961419
[20:58:39][D][sensor:113]: 'Recv Milli Time': Sending state 961419.00000  with 1 decimals of accuracy
[20:58:56][D][binary_sensor:036]: 'echo_receive_pin': Sending state OFF


[20:58:56][D][main:067]: Diff millisecond is: 19955
[20:58:56][D][main:085]: Diff millisecond is set: 19955
[20:58:56][D][sensor:113]: 'Last Echo Diff Time': Sending state 19955.00000  with 1 decimals of accuracy
[20:58:56][D][main:092]: Recv millisecond is set: 961419
[20:58:56][D][sensor:113]: 'Recv Milli Time': Sending state 961419.00000  with 1 decimals of accuracy
[20:58:56][D][binary_sensor:036]: 'echo_receive_pin': Sending state ON
[20:59:16][D][binary_sensor:036]: 'echo_receive_pin': Sending state OFF
[20:59:16][D][main:067]: Diff millisecond is: 19951
[20:59:16][D][main:085]: Diff millisecond is set: 19951
[20:59:16][D][sensor:113]: 'Last Echo Diff Time': Sending state 19951.00000  with 1 decimals of accuracy
[20:59:16][D][main:092]: Recv millisecond is set: 981420
[20:59:16][D][sensor:113]: 'Recv Milli Time': Sending state 981420.00000  with 1 decimals of accuracy
[20:59:16][D][binary_sensor:036]: 'echo_receive_pin': Sending state ON
[20:59:17][D][main:085]: Diff millisecond is set: 19951
[20:59:17][D][sensor:113]: 'Last Echo Diff Time': Sending state 19951.00000  with 1 decimals of accuracy
[20:59:27][D][sensor:113]: 'Salt Tank WiFi Signal': Sending state -19.00000 dBm with 0 decimals of accuracy
[20:59:36][D][binary_sensor:036]: 'echo_receive_pin': Sending state OFF
[20:59:36][D][main:067]: Diff millisecond is: 19950
[20:59:36][D][main:085]: Diff millisecond is set: 19950
[20:59:36][D][sensor:113]: 'Last Echo Diff Time': Sending state 19950.00000  with 1 decimals of accuracy
[20:59:36][D][main:092]: Recv millisecond is set: 1001416
[20:59:36][D][sensor:113]: 'Recv Milli Time': Sending state 1001416.00000  with 1 decimals of accuracy
[20:59:36][D][binary_sensor:036]: 'echo_receive_pin': Sending state ON
[20:59:39][D][main:092]: Recv millisecond is set: 1021413
[20:59:39][D][sensor:113]: 'Recv Milli Time': Sending state 1021413.00000  with 1 decimals of accuracy

Everything before 20:58:56 the sensor is pointing into the tank (abt 30 cm to the surface) everything after is pointing at the ceiling (abt 130 cm away). So still not there I don’t think.

glyndon I appreciate that you are continuing to try even after I had given up. I believe you are far more likely for figure it out than me so if you would be willing I would happily have one of these sensors sent to you (or put in an Amazon locker near you for your privacy) so you can play with it and post the code if you get it working properly.

It is a pity that you do not write the exact millis to the log at output.turn_on and at on_press. It would allow further analysis if you add a logging lambda line at both points and repost your log.

What seems obvious so far:
You receive an on_release when you actually expect on_press and vice versa. As a result the pulse width time you are measuring is basically your total cycle time of 20 seconds. If you would add the said logging lines it would become clear when exactly the trigger happens and when the response interval starts.

I bet the trigger and the response overlap because the 100ms trigger time is much to long. (Which should not be a reason for the wrong results.)

You’re probably correct, @jpsy, but I don’t have a sensor or the scope trace to verify if the logic level is inverted or not - so I wrote it as though it was a rise-fall pulse as depicted, but it’s hard to know without the actual hardware and a scope on it, so it was a shot in the dark. Perhaps just inverting the pin in the binary_sensor definition, and the on_push/release events would map to the proper sequence.
The 100ms duration I also pulled out of thin air because it was difficult to figure what the author of the ‘datasheet’ was saying about RX needing to be >70ms. Another shot in the dark, thanks to the ambiguity of the datasheet.
@keassol, I’d be happy to find a way to make one of these work with one in-hand, as I have LOTS of free time (retired) How is best to message you my location?
But before going to the trouble of sending one, I’d suggest just loading a copy of ping.ino (e.g. this one) on the board and see if it would “just work” as the reviewer indicates.
Alternatively, we could try replacing this lambda code with the actual Arduino function calls, but that leaves in question how much time is added by the ESPhome code, if any, that gets introduced by the framework.
Since this thing looks like it wants us to measure pulse time in microseconds, that we’re counting millis may also be a factor keeping it from seeing the true pulse width.
There are also a few places here for race conditions to occur unless we’re very careful at managing the logic.

Hi @glyndon,
absolutely no insult meant! All just speculations from my side.

I saw the 70ms too in the datasheet, but I think this is the total cycle time between two trigger events (“the trigger cycle of the module must be >70ms”).
I concluded that the trigger pulse length should be much shorter from the fact that the T1 in the diagram is much shorter than the PWM pulse time T2, and T2 in turn is < 35ms.

But as you said: This datasheet is a jungle of ambiguities.

That makes sense!
Originally I had pulled 10ms out of the Arduino example for the trigger pulse, then the line about 70 made me doubt that choice.

I’m about 80% convinced that:

  • T1 represents the ‘dead zone’ (too close to the sensor). Echoes received during this period may be ignored.
    TX then goes high as it begins to listen for an echo (after the T1 dead zone time has elapsed), and then falls whenever the echo arrives (or 35ms passes and having heard no echo it abandons the cycle)
    That seems to jibe with the Arduino code, too.
    This is probably very simple to code, if only we could be sure what the datasheet meant. It’s just tough to be sure without hooking it to the scope & signal generator, and seeing it for oneself, or lots of trial-and-error.
1 Like

Without actually having tried I suspect that ping.ino is more or less what esphome sensor component is doing under the hood. Even the name of the code is “HC-SR04_UltrasonicSensor” and I’m 99% sure this sensor doesn’t work with that esphome component.

glyndon, I’ll send you a private message through your profile sowe can figure out how to get you one of these sensors.

1 Like

You are spot-on. I looked around in that component’s code this morning, and it is almost exactly what the example .ino sketch does. But the datasheet’s (of the sensor named in ESPHome’s code) explanation of timing made me wonder if they’re not just different enough to not work with each other’s timings.

My experimental device is due here sometime tomorrow, so in a few days I should have something to report.
First thing I’m trying is a config with just the existing ultrasonic sensor setup, and watch the signals on the scope to see if it’s doing anything.
If that doesn’t work, then I’ll try a config with an ‘interval’ firing, and in its lambda will be an adapted version of the code from the ping.ino.
That failing, I may take the ultrasonic sensor, copy it and change the name and then try fiddling with its logic, because it seems to be using interrupt-driven receive logic, and that may be important for accuracy (millisecond timing is too coarse, and micros() would do, as long as the rest of the overhead doesn’t get in the way (e.g. yields back to the loop so WiFi gets the attention it needs).
But my guess is that the first approach will be easiest, and it would all live in one block of lambda code. That would eliminate all those on_press/release hooks.

1 Like

That sounds like a good approach. Let us know how it goes. Thank you.

Just a thought, if you have to replace it every 2 months because of the salt corroding the sensor would it not be better to try a different approach like, weight sensor, pressure sensor, contact sensor.

1 Like

You are right, those probably would be a better approach and I will go that route if we can’t get this sensor to work. If we can however get this sensor to work, that would be better since I already have the sensor, it is already installed, and others have expressed interest in getting it to work also.

Well, the sensor has been here for about 2 hours, and I’ve had it working two different ways.
Not satisfied with either of the methods I’ve used, but they (and the oscilloscope traces) show that the sensor is working, and measuring distance as shown by the varying numeric results of the Arduino PulseIn() function.
Nothing useful yet, but it’s a start, and proves that it can be done.
I have several other approaches I want to try, and will then put some polish on it to make it more useful.

Here’s one of the two approaches I just used to verify function.
Nothing much defined in the YAML config except the output, and the binary_sensor, and this ‘interval’ which runs some Arduino code. It’s just a start, so don’t take this example too seriously.
You may notice I’m mixing milliseconds and microseconds, both in terminology and variable names. It’s just scaffolding at this point, and I’m not sure whether this needs to be built to run at microsecond timing, or if milliseconds will be precise enough for it. But all the useful numbers coming out of the pulseIn function are in microseconds and begin to be relevant at about 1000uS (about 6" distance) then go up to a ceiling of ~9000uS or so.
Which is why I’m going to now try it using my own code instead of pulseIn, in case I’m hitting a ceiling in that function.

globals:
  - id: tx_pwm_millis
    type: int
    initial_value: '0'

output:
  - platform: gpio
    id: trigger_output
    pin:
      number: GPIO4
      inverted: true
      mode:
        output: true

binary_sensor:
  - platform: gpio
    id: echo_receive_pin
    pin:
      number: GPIO5
      inverted: false
      mode:
        input: true

sensor:
  - platform: template
    name: "Last Echo Diff Time"
    id: last_echo_diff_time 
    lambda: |-
      ESP_LOGD("main", "Diff milliseconds is set: %d", id(tx_pwm_millis));
      return id(tx_pwm_millis);

interval:
  - interval: 2sec
    then:
      - lambda: |-
          id(trigger_output).turn_on();
          delayMicroseconds(10);
          id(trigger_output).turn_off();

          id(tx_pwm_millis) = pulseIn(5,HIGH);
      - component.update: last_echo_diff_time
1 Like

The attempt using on_press/on_release and lambda’s failed miserably because they’re just too slow.
Embedding Arduino code in the lambda worked somewhat but was IMO a kludge.

Determined to make use of the existing component if at all possible, I started experimenting with various combinations of inverted GPIO’s, etc.
But the error was always “timeout exceeded”…
so… :wink: I wonder, what if I increased the timeout? (gee)
It measures now. This is working:

sensor:
  - platform: ultrasonic
    trigger_pin:
      number: GPIO4
      inverted: true
    echo_pin:
      number: GPIO5
    name: "Ultrasonic Sensor"
    update_interval: 3s
    timeout: 10.0m

(modify GPIO pin assignments and update_interval to your liking.)
The trigger pulse is supposed to be a falling-edge one, so I inverted that pin.
I’m not certain of why this works, because the default timeout was for 2 metres so it should have at least measured things closer than that, but it wouldn’t.
My theory is that the code is anticipating the echo pulse to begin much sooner than it does on this device, so by increasing the timeout it waits long enough to see the receive pulse, then measures its length in microseconds, and voila!

While looking in the code of the ultrasonic component, it looked correct, like it should be working.
But the author of that component made an assumption related to the span of time designated T1 on this device’s datasheet.
They used a similar bit of distance-driven timeout logic to wait across T1 as they later use to measure the pulse length (T2) itself. But as I observed it on the scope, T1 is a very consistent 14ms no matter how far the sensor was from the target. It’s only the length of T2 that varies.
It may be that the device for which that component was written does vary T1 by distance, but this one doesn’t - it may be waiting until it’s finished receiving the echo and doing its internal math before ever raising TX to start the T2 reply pulse.
Whoever wrote the component may have been thinking of it the way I originally was: That the receive pulse is the actual sonar echo, arriving sooner if the target is nearer. But in fact it’s just a PWM datum, and its leading-edge arrival time is meaningless, only its duration matters.

1 Like

It’s satisfying if I analyse and solve a problem and get some electronics working. But it is likewise satisfying to read about you doing it. Love it. Thanks. Good job.

1 Like

I only wish I could provide a suggested improvement to the component’s code in a way that would fit the processes and customs of today’s software team environment but I can barely use git.
(I’m old-school. Very old-school)
Basically I’d just add a different timeout for waiting across T1, because it’s not the same thing as the distance. It’s just a characteristic of this sensor’s protocol, and is not itself a report datum.

So all that’s needed is to change the ‘timeout’ value used on lines 24 and 26 of ultrasonic_sensor.cpp.
I’d make it something like 30ms, just to give whatever device is used plenty of time to start its report signal. Both those lines are just spinning and waiting for the report signal to begin. It doesn’t get measured until the while on line 29 concludes.

Oh, but I did do the math and ran it through their existing logic. If I had set ‘timeout’ to about 2.4m[etres], that would have made the timeout long enough to span the 14ms T1 period, and it would have worked. But I just brute-forced it with 10.0. Turns out the max reportable distance for this device is about 5.98metres, which corresponds to the 35ms pulse width it sends for “didn’t get an echo.”

I have no doubt that this is likely the solution but I am getting different/interesting results.

First off, when I set the trigger pin to be inverted like your example the output is always NaN but when I set it not inverted it do get a value, but not the value I expected.

I used:

sensor:
  - platform: ultrasonic
    trigger_pin:
      number: D3
      inverted: false
    echo_pin:
      number: D2
    name: "Salt Tank Gauge"
    update_interval: 5s
    timeout: 10.0m

I get in the logs - pointing into the tank (abt 30 cm to the surface):

[20:00:40][D][ultrasonic.sensor:040]: 'Salt Tank Gauge' - Got distance: 0.01 m
[20:00:40][D][sensor:113]: 'Salt Tank Gauge': Sending state 0.00635 m with 2 decimals of accuracy
[20:00:45][D][ultrasonic.sensor:040]: 'Salt Tank Gauge' - Got distance: 0.00 m
[20:00:45][D][sensor:113]: 'Salt Tank Gauge': Sending state 0.00120 m with 2 decimals of accuracy
[20:00:50][D][ultrasonic.sensor:040]: 'Salt Tank Gauge' - Got distance: 0.00 m
[20:00:50][D][sensor:113]: 'Salt Tank Gauge': Sending state 0.00103 m with 2 decimals of accuracy
[20:00:55][D][ultrasonic.sensor:036]: 'Salt Tank Gauge' - Distance measurement timed out!
[20:00:55][D][sensor:113]: 'Salt Tank Gauge': Sending state nan m with 2 decimals of accuracy
[20:01:00][D][ultrasonic.sensor:040]: 'Salt Tank Gauge' - Got distance: 0.00 m
[20:01:00][D][sensor:113]: 'Salt Tank Gauge': Sending state 0.00103 m with 2 decimals of accuracy
[20:01:05][D][ultrasonic.sensor:040]: 'Salt Tank Gauge' - Got distance: 0.00 m
[20:01:05][D][sensor:113]: 'Salt Tank Gauge': Sending state 0.00103 m with 2 decimals of accuracy
[20:01:10][D][ultrasonic.sensor:040]: 'Salt Tank Gauge' - Got distance: 0.01 m
[20:01:10][D][sensor:113]: 'Salt Tank Gauge': Sending state 0.00652 m with 2 decimals of accuracy
[20:01:15][D][ultrasonic.sensor:040]: 'Salt Tank Gauge' - Got distance: 0.05 m
[20:01:15][D][sensor:113]: 'Salt Tank Gauge': Sending state 0.05316 m with 2 decimals of accuracy
[20:01:20][D][ultrasonic.sensor:040]: 'Salt Tank Gauge' - Got distance: 0.03 m
[20:01:20][D][sensor:113]: 'Salt Tank Gauge': Sending state 0.02675 m with 2 decimals of accuracy
[20:01:25][D][ultrasonic.sensor:040]: 'Salt Tank Gauge' - Got distance: 0.00 m
[20:01:25][D][sensor:113]: 'Salt Tank Gauge': Sending state 0.00223 m with 2 decimals of accuracy

and pointing at the ceiling (abt 130 cm away):

[20:03:25][D][sensor:113]: 'Salt Tank Gauge': Sending state 0.00120 m with 2 decimals of accuracy
[20:03:30][D][ultrasonic.sensor:040]: 'Salt Tank Gauge' - Got distance: 0.00 m
[20:03:30][D][sensor:113]: 'Salt Tank Gauge': Sending state 0.00120 m with 2 decimals of accuracy
[20:03:35][D][sensor:113]: 'Salt Tank WiFi Signal': Sending state -30.00000 dBm with 0 decimals of accuracy
[20:03:35][D][ultrasonic.sensor:040]: 'Salt Tank Gauge' - Got distance: 0.00 m
[20:03:35][D][sensor:113]: 'Salt Tank Gauge': Sending state 0.00103 m with 2 decimals of accuracy
[20:03:40][D][ultrasonic.sensor:040]: 'Salt Tank Gauge' - Got distance: 0.00 m
[20:03:40][D][sensor:113]: 'Salt Tank Gauge': Sending state 0.00120 m with 2 decimals of accuracy
[20:03:45][D][ultrasonic.sensor:040]: 'Salt Tank Gauge' - Got distance: 0.02 m
[20:03:45][D][sensor:113]: 'Salt Tank Gauge': Sending state 0.02281 m with 2 decimals of accuracy
[20:03:50][D][ultrasonic.sensor:040]: 'Salt Tank Gauge' - Got distance: 0.00 m
[20:03:50][D][sensor:113]: 'Salt Tank Gauge': Sending state 0.00497 m with 2 decimals of accuracy
[20:03:55][D][ultrasonic.sensor:040]: 'Salt Tank Gauge' - Got distance: 0.00 m
[20:03:55][D][sensor:113]: 'Salt Tank Gauge': Sending state 0.00120 m with 2 decimals of accuracy
[20:04:00][D][ultrasonic.sensor:040]: 'Salt Tank Gauge' - Got distance: 0.00 m
[20:04:00][D][sensor:113]: 'Salt Tank Gauge': Sending state 0.00120 m with 2 decimals of accuracy

I don’t know, maybe I damaged the sensor while trying some test or something.

I can’t recall what board you’re using. One of the least-ambiguous ways to specify pins is to call them by their GPIOn names. I’m not a fan of using the board labels like ‘D3’, etc, because IMO using GPIOn forms can reduce likelihood of problems.

That said, on a d1_mini D3 maps to GPIO0, which may be pulled up by an LED on some boards.
I suggest trying different GPIO pins, if possible, and D2/D1 (GPIO’s 4&5) would be my preference for this, as those pins are less likely to be burdened by pullups/pulldowns from the board itself.
The GPIO12-14 (D5-7) range are also faves, but GPIOs 4&5 work well too, as long as you don’t have to use them for an I2C bus.
So, don’t write the sensor off until you’ve verified that it’s not your board causing it to misreport.

Oh, and not inverting the trigger pin also ‘worked’ in my testing, but I believe that was only because it only cares about the falling edge, which just meant it was triggering at the end of the trigger pulse instead of the beginning. The pulse is far less than the T1 time, so it shouldn’t matter, unless there’s a pullup/down on that pin. But in my case it gave results either way.
So I’d vacate D3 as the trigger, and maybe try using D5/D6 instead, for example.

1 Like

glyndon, cannot even begin to thank you enough for your patients and persistence on this issue.

I moved from D2 and D3 and used GPIOx and it appears to be working perfectly! Code working for me (same as yours except pins reversed and trigger pin not inverted):

sensor:
  - platform: ultrasonic
    trigger_pin:
      number: GPIO5
      inverted: false
    echo_pin:
      number: GPIO4
    name: "Salt Tank Gauge"
    update_interval: 30s
    timeout: 10.0m

Lesson learned: Know the dang board I’m working with (D3!=D2 feature-wise)

Thanks again for all your help, talk to you in about 5 months when this one also corrodes and I need to figure out a pressure sensor. :slight_smile:

1 Like

Just made 4u : :rofl:

Glad it works now!
You might still want to go ahead and invert the trigger pin so the waveform is what the sensor expects (normally-high, leading-edge is falling). You shouldn’t be able to tell the diff, but it would be a precaution in case they change design to one that insists on it being normally-high and someone copies the config from here someday …

This is great! Thank you for post it.