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

I think someone above said there was but in effort to not add an additional layer (already using several other esphome devices), wanting to learn esphome better, and in the spirit of just trying, I wanted to see if it is possible in esphome before giving up.

I think with glyndon’s help we have gotten pretty close. I’m sure the answer is somewhere in the spec sheet posted above but it is either worded oddly or it is over my head and I am missing something.

If there is a library then it is possible in esphome.

Disconnect the sensor, and manually pull the input line to ground and/or 3v3 and see if it trips the automation, just to be sure.

Just updating here for anyone who was hoping to see this work. I was not able to. It is measuring something but I guess its over my head what exactly it is measuring. The code I have when I gave up was:


globals:
  - id: send_millisecond
    type: int
    restore_value: yes
    initial_value: '0'
  - id: recv_millisecond
    type: int
    restore_value: yes
    initial_value: '0'
  - id: diff_millisecond
    type: int
    restore_value: yes
    initial_value: '0'

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

binary_sensor:
  - platform: gpio
    id: echo_receive_pin
    pin:
      number: D2
      mode:
        input: true
      inverted: true
    on_state:  
      if:
        condition:
          binary_sensor.is_on: echo_receive_pin
        then:
          - logger.log: D2 Received State Change ON
          - globals.set:
              id: recv_millisecond
              value: !lambda return millis();
          - globals.set:
              id: diff_millisecond
              value: !lambda "return int(id(recv_millisecond)) - int(id(send_millisecond));"
          - lambda: |-
              ESP_LOGD("main", "Recv millisecond is: %d", id(recv_millisecond));

interval:
  - interval: 20sec
    then:
      - logger.log: output is running
      - output.turn_on: trigger_output
      - delay: 200ms
      - output.turn_off: trigger_output
      - globals.set:
          id: send_millisecond
          value: !lambda return millis();
      - lambda: |-
          ESP_LOGD("main", "Send milisecond is: %d", id(send_millisecond));
      - logger.log: output is stopped

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

  - platform: template
    name: "Send Milli Time"
    id: send_milli_time 
    lambda: |-
      ESP_LOGD("main", "Send millisecond is set: %d", id(send_millisecond));
      return id(send_millisecond);

  - platform: template
    name: "Recv Milli Time"
    id: recv_milli_time 
    lambda: |-
      ESP_LOGD("main", "Recv millisecond is set: %d", id(recv_millisecond));
      return id(recv_millisecond);
      

Which outputs to the logs:

[13:23:37][D][main:292]: output is running
[13:23:37][D][binary_sensor:036]: 'echo_receive_pin': Sending state ON
[13:23:37][D][main:251]: D2 Received State Change ON
[13:23:37][D][main:077]: Recv millisecond is: 639695
[13:23:37][D][binary_sensor:036]: 'echo_receive_pin': Sending state OFF
[13:23:37][D][main:095]: Send milisecond is: 639887
[13:23:37][D][main:098]: output is stopped
[13:23:43][D][sensor:113]: 'Salt Tank WiFi Signal': Sending state -39.00000 dBm with 0 decimals of accuracy
[13:23:45][D][main:112]: Send millisecond is set: 639887
[13:23:45][D][sensor:113]: 'Send Milli Time': Sending state 639887.00000  with 1 decimals of accuracy
[13:23:56][D][main:105]: Diff millisecond is set: 19805
[13:23:56][D][sensor:113]: 'Last Echo Diff Time': Sending state 19805.00000  with 1 decimals of accuracy
[13:23:57][D][main:292]: output is running
[13:23:57][D][binary_sensor:036]: 'echo_receive_pin': Sending state ON
[13:23:57][D][main:251]: D2 Received State Change ON
[13:23:57][D][main:077]: Recv millisecond is: 659803
[13:23:57][D][binary_sensor:036]: 'echo_receive_pin': Sending state OFF
[13:23:57][D][main:095]: Send milisecond is: 659886
[13:23:57][D][main:098]: output is stopped
[13:23:59][D][main:119]: Recv millisecond is set: 659803
[13:23:59][D][sensor:113]: 'Recv Milli Time': Sending state 659803.00000  with 1 decimals of accuracy
[13:24:17][D][main:292]: output is running
[13:24:17][D][binary_sensor:036]: 'echo_receive_pin': Sending state ON
[13:24:17][D][main:251]: D2 Received State Change ON
[13:24:17][D][main:077]: Recv millisecond is: 679803
[13:24:17][D][binary_sensor:036]: 'echo_receive_pin': Sending state OFF
[13:24:17][D][main:095]: Send milisecond is: 679886
[13:24:17][D][main:098]: output is stopped

So something is happening, just not what I expected to happen. Pointing the sensor at something farther away does not change the difference in send and receive times. Also don’t know why it always sets Last Echo Diff Time to 19805, I must not be using int() correctly.

Some corrections from earlier in the thread: The output pin is not apparently PWM. The sensor itself does not have an arduino library that I could find. It doesn’t look like the NewPing library would work with this sensor rather only for the HC-SR04 I was trying to replace.

Thanks glyndon and nickrout for trying to help. If anyone ever does figure out how to get this sensor working please update this post so that I can copy from you.

@keassol, I noticed that the difference always looks like about 20 seconds, which coincides with the interval timer for sending pulses, suggests that it’s not actually reacting to a received echo, but to the pulse-send itself.
Or, the pulse-receive is arriving so quickly after send that it’s not even seeing it until the next send-cycle. i.e. the incoming pulse is arriving during the 200ms delay, which is before you record the millis() from that pulse. So the difference always ends up being about 20 seconds.
Do you have an oscilloscope with which you could watch the receive line to see if it ever does pulse, and if it’s a dual-trace scope, compare its timing to the send pulse?

I suggest a few changes:

  • Do most of the work in lambdas, in order to avoid the overhead (i.e. possible delay) of the code created by the yaml config.
  • Act on the received pulse as quickly as possible, don’t spend time writing to the log first.
  • Store the pulse-send time before triggering the pulse, and do them as closely in code as possible.
  • Don’t use restore_value on the globals. Their past values are meaningless if the ESP reboots, and storing them in flash takes extra time and will wear the flash memory unnecessarily.
  • use on_press (or on_release, depending on logic level of the receiving signal’s leading edge) rather than on_state. On_state may be only triggering when the sensor publishes itself, which may be later after some other internal housekeeping occurs, and you need to be reacting to the state transition as quickly as your code can - so don’t wait for it to publish its change.

Here’s an alternative config that attempts to tighten the things up that I described above.

globals:
  - id: send_millisecond
    type: int
    initial_value: '0'
  - id: recv_millisecond
    type: int
    initial_value: '0'
  - id: diff_millisecond
    type: int
    initial_value: '0'

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

binary_sensor:
  - platform: gpio
    id: echo_receive_pin
    pin:
      number: D2
      mode:
        input: true
      inverted: false
    on_press:  # this signal is active when positive, so not 'inverted'
        then:
          - lambda: |-
              id(recv_millisecond) = millis();
              id(diff_millisecond) = id(recv_millisecond) - id(send_millisecond);
              ESP_LOGD("main", "Diff millisecond is: %d", id(diff_millisecond));
              // not entirely sure if these next three calls are needed.
              // The idea is to nudge the sensors into noticing that the globa values have changed.
              id(last_echo_diff_time).update();
              id(send_milli_time).update();
              id(recv_milli_time).update();

interval:
  - interval: 20sec
    then:
      - lambda: |-
          id(send_millisecond) = millis();
       # this signal is inverted, and the leading-edge (the edge that triggers it) should be falling
       # since the incoming pulse is in a range of 0.16-35ms for 'false' and longer for 'true',
       # I shortened the sending pulse to just 10ms - the doc doesn't say how long it should be,
       # but judging by the relative sizes in the state diagram, it's not very long.
      - output.turn_on: trigger_output
      - delay: 10ms
      - output.turn_off: trigger_output

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

  - platform: template
    name: "Send Milli Time"
    id: send_milli_time 
    lambda: |-
      ESP_LOGD("main", "Send millisecond is set: %d", id(send_millisecond));
      return id(send_millisecond);

  - platform: template
    name: "Recv Milli Time"
    id: recv_milli_time 
    lambda: |-
      ESP_LOGD("main", "Recv millisecond is set: %d", id(recv_millisecond));
      return id(recv_millisecond);

And I just attempted again to make sense of the documentation for this device, and now I believe this approach we’ve got in the code above won’t work. I’d been thinking like SONAR, where you measure from ‘ping’ to ‘echo’, but this device delivers the output data as a PWM on the TX line, and NOT as the distance in time from ‘sending’ on RX and seeing the pulse’s leading edge on TX.
That is, the time ‘T1’ is not what we need to measure. But it does look like that T1 should be >70ms (I’d go for about 100ms). That pulse just makes the device take a sample.

What they call T3 in the text seems to be a typo for T2 shown in the state diagram, and it is the length T2 of the TX pulse that is the sensor’s report of the distance it measured. If T2=35ms, no echo was heard. Shorter T2 pulses (their example shows 10ms) indicate success and distance measured will be in proportion to T2 pulse width.

I’ll take another stab at making the code do it this way.

globals:
  - id: recv_millisecond
    type: int
    initial_value: '0'
  - id: diff_millisecond
    type: int
    initial_value: '0'

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

binary_sensor:
  - platform: gpio
    id: echo_receive_pin
    pin:
      number: D2
      mode:
        input: true
      inverted: false
    on_press:
        then:
          - lambda: |-
              // remember the moment when the TX line went high, indicating an echo has been measured
              id(recv_millisecond) = millis();
    on_release:
        then:
          - lambda: |-
              // TX line just fell, measure the time it had been high, which is its report to us
              id(diff_millisecond) = millis() - id(recv_millisecond);
              // now do some reporting to the user
              ESP_LOGD("main", "Diff millisecond is: %d", id(diff_millisecond));
              // not entirely sure if these next two calls are needed.
              // The idea is to nudge the sensors into noticing that the global values have changed.
              id(last_echo_diff_time).update();
              id(recv_milli_time).update();

interval:
  - interval: 20sec
    then:
      - output.turn_on: trigger_output
      - delay: 100ms
      - output.turn_off: trigger_output

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

  - platform: template
    name: "Recv Milli Time"
    id: recv_milli_time 
    lambda: |-
      ESP_LOGD("main", "Recv millisecond is set: %d", id(recv_millisecond));
      return id(recv_millisecond);

There’s a copy of the datasheet on that page. I tried posting a PDF made from it, but this site doesn’t accept PDF.

There is said to be an arduino library, but no one seems to have found it. Anyone?

All I could find was a review of it on Amazon that said it worked with the Ping sketch.
But the original post above said it’s not compatible with the sensor(s) that sketch seems to have been written for.
But yeah, it couldn’t hurt to just throw ping.ino on an ESP and see what happens.
And the sketch uses the pulseIn function, which does what I’m emulating in ESPhome code above.
So perhaps this whole thing could be reduced to a couple lambda’s calling the same things as the ino sketch does, and skip the whole self-created timing state-machine I’d cobbled up for it.
Didn’t know that the arduino lib already had a pulseIn function. Handy!

What and where is the ping sketch? You guys are not being very transparent. Coupled with the fact that the first poster has posted a barely readable photo of a screwed up piece of paper. Jeeeeezzz.

Google’s your friend, in this case. It took me about 3 seconds to find the ping.ino the reviewer was referring to.
And if you click the OP’s link (it’s in the OP), or mine a couple comments above, the full datasheet is viewable on Amazon’s product page.
Transparent enough?

The reviewer referred (in 2021) to the

new ping library.

The first one google finds is dated 2015 and reviewed in 2018. Hardly “new”.

And if the datasheet is there, why post a screwed up dark photo?

The rules here are titled “help us to help you”. People who want to help shouldn’t need to google to find stuff the poster is referring to.

Here is a better scan of the datasheet.

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