Help creating a people counter passing through a door

Hello everyone!
Newbie here…
I’m trying to count how many people are in a room by knowing the direction that the person passes through a door. I’m using two VL53L0X ToF sensors setting them up to provide a binary_sensor state instead of the distance by giving a true value when the distance is < than 70cm and false value when the distance is > 70cm. this part is working great. I created a flow on Node-Red to count the people and it is doing its job well. But I would like to make the ESP32 give the count value and not using Node-red to make the calculation.

Basically what I’m trying to do is store values on the “seq” variables according to the sensors states and then check if the sequence correspond to a person going IN or OUT of the room.

Here drawing for reference (credits to OnlineGuz, his post here):

This is my code on ESPHome:

esphome:
  name: lab_sensors
  platform: ESP32
  board: esp-wrover-kit

wifi:
  ssid: <deleted>
  password: <deleted>

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Lab Sensors Fallback Hotspot"
    password: "YcSmQRKGWols"

captive_portal:

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:

i2c:
  sda: GPIO21
  scl: GPIO22
  scan: True
  id: bus_a

globals:
  - id: seq1
    type: int
    restore_value: no
    initial_value: '0'
  - id: seq2
    type: int
    restore_value: no
    initial_value: '0'
  - id: seq3
    type: int
    restore_value: no
    initial_value: '0'
  - id: seq4
    type: int
    restore_value: no
    initial_value: '0'
    
  - id: count
    type: int
    restore_value: no
    initial_value: '0'


binary_sensor:
  - platform: template
    id: lab_inside_door_laser
    name: "Lab Inside Door Laser"
    filters:
      - delayed_off: 100ms
    lambda: !lambda |-
        return id(sens_1).state < 0.7f;

  - platform: template
    id: lab_outside_door_laser
    name: "Lab Outside Door Laser"
    filters:
      - delayed_off: 100ms
    lambda: !lambda |-
        return id(sens_2).state < 0.7f;
        
  - platform: gpio
    pin: GPIO2
    name: "Lab PIR Sensor"
    device_class: motion


sensor:
  - platform: template
    name: "People in Lab"
    lambda: !lambda |-
        if(id(lab_inside_door_laser).state == false && id(lab_outside_door_laser).state == false){
          seq1 = seq2;
          seq2 = seq3;
          seq3 = seq4;
          id(seq4) = 0;
        }else if(id(lab_inside_door_laser).state == false && id(lab_outside_door_laser).state == true){
          seq1 = seq2;
          seq2 = seq3;
          seq3 = seq4;
          id(seq4) = 1;
        }else if(id(lab_inside_door_laser).state == true && id(lab_outside_door_laser).state == false){
          seq1 = seq2;
          seq2 = seq3;
          seq3 = seq4;
          id(seq4) = 2;
        }else if(id(lab_inside_door_laser).state == true && id(lab_outside_door_laser).state == true){
          seq1 = seq2;
          seq2 = seq3;
          seq3 = seq4;
          id(seq4) = 3;
        }
        if(id(seq1) == 1 && id(seq2) == 3 && id(seq3) == 2 && id(seq4) == 0){
          count++;
        }else if(id(seq1) == 2 && id(seq2) == 3 && id(seq3) == 1 && id(seq4) == 0){
          count--;
        }return id(count);

  - platform: vl53l0x
    name: "Sensor 1"
    id: sens_1
    i2c_id: bus_a
    address: 0x41
    update_interval: 50ms
    enable_pin: GPIO25
    timeout: 500us
    internal: true
    
  - platform: vl53l0x
    name: "Sensor 2"
    id: sens_2
    i2c_id: bus_a
    address: 0x42
    update_interval: 50ms
    enable_pin: GPIO32
    timeout: 500us
    internal: true
    
  - platform: adc
    pin: A3
    name: "Lab Illuminance"
    unit_of_measurement: lx
    filters:
      - median:
      - lambda: |-
          return (x / 10000.0) * 2000000.0;
    attenuation: 11db
    update_interval: 5s
    
  - platform: dht
    pin: GPIO17
    temperature:
      filters:
      - offset: -6.0
      - median:
      name: "Lab Temperature"
    humidity:
      name: "Lab Humidity"
      filters:
      - median:
    update_interval: 5s
    model: AM2302

and this is my flow on Node-Red:

[{"id":"7cba67a5.05eb88","type":"change","z":"67487c43.97ecf4","name":"inside","rules":[{"t":"set","p":"topic","pt":"msg","to":"inside","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":510,"y":80,"wires":[["cfa760ae.b9ac6"]]},{"id":"c6315233.fe28","type":"change","z":"67487c43.97ecf4","name":"outside","rules":[{"t":"set","p":"topic","pt":"msg","to":"outside","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":520,"y":160,"wires":[["cfa760ae.b9ac6"]]},{"id":"51704eaa.0141e","type":"api-call-service","z":"67487c43.97ecf4","name":"","server":"13f57a0.20e1386","version":1,"debugenabled":false,"service_domain":"input_number","service":"increment","entityId":"input_number.people_in_lab","data":"","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":1080,"y":80,"wires":[[]]},{"id":"42f045a6.dc79bc","type":"api-call-service","z":"67487c43.97ecf4","name":"","server":"13f57a0.20e1386","version":1,"debugenabled":false,"service_domain":"input_number","service":"decrement","entityId":"input_number.people_in_lab","data":"","dataType":"jsonata","mergecontext":"","output_location":"","output_location_type":"none","mustacheAltTags":false,"x":1090,"y":160,"wires":[[]]},{"id":"cfa760ae.b9ac6","type":"function","z":"67487c43.97ecf4","name":"","func":"//get stored values if present\ncontext.set(msg.topic,msg.payload);\n\nlet inside = context.get(\"inside\");\nlet outside = context.get(\"outside\");\n\n\n\nif(inside == false && outside == false){\n    msg.payload = 0;\n}\nelse if (inside == false && outside == true){\n    msg.payload = 1;\n}\nelse if (inside == true && outside == false){\n    msg.payload = 2;\n}\nelse {\n    msg.payload = 3;\n}\n\nreturn msg;\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":620,"y":120,"wires":[["e983029b.3566d","6cf27517.220dac"]]},{"id":"e983029b.3566d","type":"sequence-detector","z":"67487c43.97ecf4","name":"Sequence Detector","watch":"payload","sequence":"1\n3\n2\n0","timeout":"3000","matchMessage":"match","resetMessage":"reset","timeoutMessage":"timeout","x":810,"y":80,"wires":[["51704eaa.0141e"],[]]},{"id":"6cf27517.220dac","type":"sequence-detector","z":"67487c43.97ecf4","name":"Sequence Detector","watch":"payload","sequence":"2\n3\n1\n0","timeout":"30000","matchMessage":"match","resetMessage":"reset","timeoutMessage":"timeout","x":810,"y":160,"wires":[["42f045a6.dc79bc"],[]]},{"id":"b770922b.e3e7b","type":"server-state-changed","z":"67487c43.97ecf4","name":"","server":"13f57a0.20e1386","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"binary_sensor.lab_inside_door_laser","entityidfiltertype":"exact","outputinitially":true,"state_type":"habool","haltifstate":"","halt_if_type":"num","halt_if_compare":"lt","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":true,"ignoreCurrentStateUnavailable":false,"x":230,"y":80,"wires":[["7cba67a5.05eb88"]]},{"id":"683f32b5.f73d6c","type":"server-state-changed","z":"67487c43.97ecf4","name":"","server":"13f57a0.20e1386","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"binary_sensor.lab_outside_door_laser","entityidfiltertype":"exact","outputinitially":true,"state_type":"habool","haltifstate":"","halt_if_type":"num","halt_if_compare":"lt","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":true,"ignoreCurrentStateUnavailable":false,"x":240,"y":160,"wires":[["c6315233.fe28"]]},{"id":"812514c7.b41b18","type":"inject","z":"67487c43.97ecf4","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":740,"y":260,"wires":[["42f045a6.dc79bc"]]},{"id":"3141f0be.35b6e","type":"inject","z":"67487c43.97ecf4","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":740,"y":220,"wires":[["51704eaa.0141e"]]},{"id":"cf203f67.dc451","type":"server-state-changed","z":"67487c43.97ecf4","name":"","server":"13f57a0.20e1386","version":1,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_number.people_in_lab","entityidfiltertype":"exact","outputinitially":true,"state_type":"num","haltifstate":"","halt_if_type":"num","halt_if_compare":"gt","outputs":1,"output_only_on_state_change":true,"for":0,"forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"x":210,"y":260,"wires":[[]]},{"id":"13f57a0.20e1386","type":"server","name":"Home Assistant","legacy":false,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true}]

Obviously my lambda is not counting, it gives the value ‘0’ all the time.

Any piece of advice from the gurus here?

After learning a little bit more about how to program in ESPHome, I solved my issue.

Now the ESP32 have a sensor that returns the count of people inside the room. this approach is more reliable that sending the binary sensors states and letting node-red do the count.

Here the code for the curious ones (maybe some one can optimize it a little bit):

esphome:
  name: lab_sensors
  platform: ESP32
  board: esp-wrover-kit

wifi:
  ssid: <deleted>
  password: <deleted>

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Lab Sensors Fallback Hotspot"
    password: "YcSmQRKGWols"

captive_portal:

# Enable logging
logger:
  level: INFO

# Enable Home Assistant API
api:

ota:

i2c:
  sda: GPIO21
  scl: GPIO22
  scan: True
  id: bus_a

globals:
  - id: seq1
    type: int
    restore_value: no
    initial_value: '0'
  - id: seq2
    type: int
    restore_value: no
    initial_value: '0'
  - id: seq3
    type: int
    restore_value: no
    initial_value: '0'
  - id: seq4
    type: int
    restore_value: no
    initial_value: '0'
    
  - id: count
    type: int
    restore_value: no
    initial_value: '0'
    
  - id: inside_previous_value
    type: bool
    restore_value: no
    initial_value: 'false'
    
  - id: outside_previous_value
    type: bool
    restore_value: no
    initial_value: "false"


binary_sensor:
  - platform: template
    id: lab_inside_door_laser
    name: "Lab Inside Door Laser"
    lambda: !lambda |-
        if((id(sens_1).state < 0.7f) != id(inside_previous_value)){
          id(inside_previous_value) = (id(sens_1).state < 0.7f);
          if(((id(sens_1).state < 0.7f) == false) && ((id(sens_2).state < 0.7f) == false)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 0;
          }else if(((id(sens_1).state < 0.7f) == false) && ((id(sens_2).state < 0.7f) == true)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 1;
          }else if(((id(sens_1).state < 0.7f) == true) && ((id(sens_2).state < 0.7f) == false)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 2;
          }else if(((id(sens_1).state < 0.7f) == true) && ((id(sens_2).state < 0.7f) == true)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 3;
          }
          if((id(seq1) == 1) && (id(seq2) == 3) && (id(seq3) == 2) && (id(seq4) == 0)){
            id(count) ++;
            id(people_in_lab).publish_state(id(count));
          }else if((id(seq1) == 2) && (id(seq2) == 3) && (id(seq3) == 1) && (id(seq4) == 0)){
            id(count) --;
            if(id(count) < 0){
              id(count) = 0;
            }
            id(people_in_lab).publish_state(id(count));
          }
        }
        return id(sens_1).state < 0.7f;
        
        

  - platform: template
    id: lab_outside_door_laser
    name: "Lab Outside Door Laser"
    lambda: !lambda |-
        if((id(sens_2).state < 0.7f) != id(outside_previous_value)){
          id(outside_previous_value) = (id(sens_2).state < 0.7f);
          if(((id(sens_1).state < 0.7f) == false) && ((id(sens_2).state < 0.7f) == false)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 0;
          }else if(((id(sens_1).state < 0.7f) == false) && ((id(sens_2).state < 0.7f) == true)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 1;
          }else if(((id(sens_1).state < 0.7f) == true) && ((id(sens_2).state < 0.7f) == false)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 2;
          }else if(((id(sens_1).state < 0.7f) == true) && ((id(sens_2).state < 0.7f) == true)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 3;
          }
          if((id(seq1) == 1) && (id(seq2) == 3) && (id(seq3) == 2) && (id(seq4) == 0)){
            id(count)++;
            id(people_in_lab).publish_state(id(count));
          }else if((id(seq1) == 2) && (id(seq2) == 3) && (id(seq3) == 1) && (id(seq4) == 0)){
            id(count)--;
            if(id(count) < 0){
              id(count) = 0;
            }
            id(people_in_lab).publish_state(id(count));
          }
        }
        return id(sens_2).state < 0.7f;
        
        
  - platform: gpio
    pin: GPIO2
    name: "Lab PIR Sensor"
    device_class: motion



sensor:
  - platform: template
    id: people_in_lab
    name: "People in Lab"
    lambda: !lambda |-
      return id(count);
        

  - platform: vl53l0x
    name: "Sensor 1"
    id: sens_1
    i2c_id: bus_a
    address: 0x41
    update_interval: 50ms
    enable_pin: GPIO25
    timeout: 500us
    internal: true
    
    
  - platform: vl53l0x
    name: "Sensor 2"
    id: sens_2
    i2c_id: bus_a
    address: 0x42
    update_interval: 50ms
    enable_pin: GPIO32
    timeout: 500us
    internal: true
3 Likes

Where does the “f” come from/is for?

There’s also this product although continually oos due to component supply issues,

the “f” is to make sure it is comparing the values as a float variable.

Looks like a nice device, also pricey.

And the 0.7?

That is the 70cm.
That line of code is comparing the measurement from the sensor and comparing it against 0.7m, if is less than that it will return a true value meaning that something is on the path of the laser.

Ah I see… I am trying to rebuild/reuse your code to make my sensors (just 2 binairy ones) work. It works, but partially. It misses some motions…

This is what I did and have. Any help/clue appreciated to fit to my sensors greatly appreciated.

globals:
  - id: seq1
    type: int
    restore_value: no
    initial_value: '0'
  - id: seq2
    type: int
    restore_value: no
    initial_value: '0'
  - id: seq3
    type: int
    restore_value: no
    initial_value: '0'
  - id: seq4
    type: int
    restore_value: no
    initial_value: '0'
    
  - id: count
    type: int
    restore_value: no
    initial_value: '0'
    
  - id: inside_previous_value
    type: bool
    restore_value: no
    initial_value: 'false'
    
  - id: outside_previous_value
    type: bool
    restore_value: no
    initial_value: 'false'

binary_sensor:
  - platform: gpio
    pin: 
      number: GPIO21
      mode: INPUT_PULLUP
    id: sens_1
    name: "Sensor 1"
    device_class: motion
#    internal: true
    
  - platform: gpio
    pin:
      number: GPIO22
      mode: INPUT_PULLUP
    name: "Sensor 2"
    id: sens_2
    device_class: motion
#    internal: true

  - platform: template
    id: inside_door_sensor
    name: "Inside Door Sensor"
    lambda: !lambda |-
        if((id(sens_1).state < 0.7f) != id(inside_previous_value)){
          id(inside_previous_value) = (id(sens_1).state < 0.7f);
          if(((id(sens_1).state < 0.7f) == false) && ((id(sens_2).state < 0.7f) == false)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 0;
          }else if(((id(sens_1).state < 0.7f) == false) && ((id(sens_2).state < 0.7f) == true)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 1;
          }else if(((id(sens_1).state < 0.7f) == true) && ((id(sens_2).state < 0.7f) == false)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 2;
          }else if(((id(sens_1).state < 0.7f) == true) && ((id(sens_2).state < 0.7f) == true)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 3;
          }
          if((id(seq1) == 1) && (id(seq2) == 3) && (id(seq3) == 2) && (id(seq4) == 0)){
            id(count) ++;
            id(people_in).publish_state(id(count));
          }else if((id(seq1) == 2) && (id(seq2) == 3) && (id(seq3) == 1) && (id(seq4) == 0)){
            id(count) --;
            if(id(count) < 0){
              id(count) = 0;
            }
            id(people_in).publish_state(id(count));
          }
        }
        return id(sens_1).state < 0.7f;

  - platform: template
    id: outside_door_sensor
    name: "Outside Door Sensor"
    lambda: !lambda |-
        if((id(sens_2).state < 0.7f) != id(outside_previous_value)){
          id(outside_previous_value) = (id(sens_2).state < 0.7f);
          if(((id(sens_1).state < 0.7f) == false) && ((id(sens_2).state < 0.7f) == false)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 0;
          }else if(((id(sens_1).state < 0.7f) == false) && ((id(sens_2).state < 0.7f) == true)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 1;
          }else if(((id(sens_1).state < 0.7f) == true) && ((id(sens_2).state < 0.7f) == false)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 2;
          }else if(((id(sens_1).state < 0.7f) == true) && ((id(sens_2).state < 0.7f) == true)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 3;
          }
          if((id(seq1) == 1) && (id(seq2) == 3) && (id(seq3) == 2) && (id(seq4) == 0)){
            id(count)++;
            id(people_in).publish_state(id(count));
          }else if((id(seq1) == 2) && (id(seq2) == 3) && (id(seq3) == 1) && (id(seq4) == 0)){
            id(count)--;
            if(id(count) < 0){
              id(count) = 0;
            }
            id(people_in).publish_state(id(count));
          }
        }
        return id(sens_2).state < 0.7f;
        
sensor:
  - platform: template
    id: people_in
    name: "People Inside"
    lambda: !lambda |-
      return id(count);

EDIT: Disclaimer: I am no coder at all, just a copy-cat with understanding of logics (some sort of :slight_smile: )

As per your picture, you are using binary sensors, this code is made for the VL53L0X sensors that measure distance.

it should not dificult to acomodate it to binary sensors. I’ll take a look tomorrow, is too late now and my brain is not working.

1 Like

I amended like below. It now works better. But I have 1 strange behavior. When I test the setup it counts well one way and also the other way. But when I count let’s say forward it counts and when I do backward it misses the last one and vice versa. So lets say someone walks in it counts it, but walking out not and the wen walking in and out it does not count it anymore unless a second one passes. Get me :)?

globals:
  - id: seq1
    type: int
    restore_value: no
    initial_value: '0'
  - id: seq2
    type: int
    restore_value: no
    initial_value: '0'
  - id: seq3
    type: int
    restore_value: no
    initial_value: '0'
  - id: seq4
    type: int
    restore_value: no
    initial_value: '0'
    
  - id: count
    type: int
    restore_value: no
    initial_value: '0'
    
  - id: inside_previous_value
    type: bool
    restore_value: no
    initial_value: 'false'
    
  - id: outside_previous_value
    type: bool
    restore_value: no
    initial_value: 'false'

binary_sensor:
  - platform: gpio
    pin: 
      number: GPIO21
      mode: INPUT_PULLUP
    id: sens_1
    name: "Sensor 1"
    device_class: motion
#    internal: true
    
  - platform: gpio
    pin:
      number: GPIO22
      mode: INPUT_PULLUP
    name: "Sensor 2"
    id: sens_2
    device_class: motion
#    internal: true

  - platform: template
    id: inside_door_sensor
    name: "Inside Door Sensor"
    lambda: !lambda |-
        if((id(sens_1).state < 1) != id(inside_previous_value)){
          id(inside_previous_value) = (id(sens_1).state < 1);
          if(((id(sens_1).state < 1) == false) && ((id(sens_2).state < 1) == false)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 0;
          }else if(((id(sens_1).state < 1) == false) && ((id(sens_2).state < 1) == true)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 1;
          }else if(((id(sens_1).state < 1) == true) && ((id(sens_2).state < 1) == false)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 2;
          }else if(((id(sens_1).state < 1) == true) && ((id(sens_2).state < 1) == true)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 3;
          }
          if((id(seq1) == 1) && (id(seq2) == 3) && (id(seq3) == 2) && (id(seq4) == 0)){
            id(count) ++;
            id(people_in).publish_state(id(count));
          }else if((id(seq1) == 2) && (id(seq2) == 3) && (id(seq3) == 1) && (id(seq4) == 0)){
            id(count) --;
            if(id(count) < 0){
              id(count) = 0;
            }
            id(people_in).publish_state(id(count));
          }
        }
        return id(sens_1).state < 1;

  - platform: template
    id: outside_door_sensor
    name: "Outside Door Sensor"
    lambda: !lambda |-
        if((id(sens_2).state < 1) != id(outside_previous_value)){
          id(outside_previous_value) = (id(sens_2).state < 1);
          if(((id(sens_1).state < 1) == false) && ((id(sens_2).state < 1) == false)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 0;
          }else if(((id(sens_1).state < 1) == false) && ((id(sens_2).state < 1) == true)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 1;
          }else if(((id(sens_1).state < 1) == true) && ((id(sens_2).state < 1) == false)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 2;
          }else if(((id(sens_1).state < 1) == true) && ((id(sens_2).state < 1) == true)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 3;
          }
          if((id(seq1) == 1) && (id(seq2) == 3) && (id(seq3) == 2) && (id(seq4) == 0)){
            id(count)++;
            id(people_in).publish_state(id(count));
          }else if((id(seq1) == 2) && (id(seq2) == 3) && (id(seq3) == 1) && (id(seq4) == 0)){
            id(count)--;
            if(id(count) < 0){
              id(count) = 0;
            }
            id(people_in).publish_state(id(count));
          }
        }
        return id(sens_2).state < 1;
        
sensor:
  - platform: template
    id: people_in
    name: "People Inside"
    lambda: !lambda |-
      return id(count);

This should do the work

I added a 10ms de-bounce for your binary sensors and treated as a boolean on the “if” statements (on my original code I treated as a number)

globals:
  - id: seq1
    type: int
    restore_value: no
    initial_value: '0'
  - id: seq2
    type: int
    restore_value: no
    initial_value: '0'
  - id: seq3
    type: int
    restore_value: no
    initial_value: '0'
  - id: seq4
    type: int
    restore_value: no
    initial_value: '0'
    
  - id: count
    type: int
    restore_value: no
    initial_value: '0'
    
  - id: inside_previous_value
    type: bool
    restore_value: no
    initial_value: 'false'
  
  - id: outside_previous_value
    type: bool
    restore_value: no
    initial_value: 'false'
  
binary_sensor:
  - platform: gpio
    pin: 
      number: GPIO21
      mode: INPUT_PULLUP
    id: sens_1
    name: "Sensor 1"
    device_class: motion
    filters:
      - delayed_on: 10ms
#    internal: true
    
  - platform: gpio
    pin:
      number: GPIO22
      mode: INPUT_PULLUP
    name: "Sensor 2"
    id: sens_2
    device_class: motion
    filters:
      - delayed_on: 10ms
#    internal: true

  - platform: template
    id: inside_door_sensor
    name: "Inside Door Sensor"
    lambda: !lambda |-
        if(id(sens_1).state != id(inside_previous_value)){
          id(inside_previous_value) = id(sens_1).state;
          if((id(sens_1).state == false) && (id(sens_2).state == false)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 0;
          }else if((id(sens_1).state == false) && (id(sens_2).state == true)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 1;
          }else if((id(sens_1).state == true) && (id(sens_2).state == false)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 2;
          }else if((id(sens_1).state == true) && (id(sens_2).state == true)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 3;
          }
          if((id(seq1) == 1) && (id(seq2) == 3) && (id(seq3) == 2) && (id(seq4) == 0)){
            id(count) ++;
            id(people_in).publish_state(id(count));
          }else if((id(seq1) == 2) && (id(seq2) == 3) && (id(seq3) == 1) && (id(seq4) == 0)){
            id(count) --;
            if(id(count) < 0){
              id(count) = 0;
            }
            id(people_in).publish_state(id(count));
          }
        }
        return id(sens_1).state;

  - platform: template
    id: outside_door_sensor
    name: "Outside Door Sensor"
    lambda: !lambda |-
        if(id(sens_2).state != id(outside_previous_value)){
          id(outside_previous_value) = id(sens_2).state;
          if((id(sens_1).state == false) && (id(sens_2).state == false)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 0;
          }else if((id(sens_1).state == false) && (id(sens_2).state == true)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 1;
          }else if((id(sens_1).state == true) && (id(sens_2).state == false)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 2;
          }else if((id(sens_1).state == true) && (id(sens_2).state == true)){
            id(seq1) = id(seq2);
            id(seq2) = id(seq3);
            id(seq3) = id(seq4);
            id(seq4) = 3;
          }
          if((id(seq1) == 1) && (id(seq2) == 3) && (id(seq3) == 2) && (id(seq4) == 0)){
            id(count)++;
            id(people_in).publish_state(id(count));
          }else if((id(seq1) == 2) && (id(seq2) == 3) && (id(seq3) == 1) && (id(seq4) == 0)){
            id(count)--;
            if(id(count) < 0){
              id(count) = 0;
            }
            id(people_in).publish_state(id(count));
          }
        }
        return id(sens_2).state;
        
sensor:
  - platform: template
    id: people_in
    name: "People Inside"
    lambda: !lambda |-
      return id(count);
1 Like

Thank you for publishing your code. I would like to use M5Stack distance sensors M5StickC ToF HAT (VL53L0X). I guess it should work.
VL53L0X

Yeah, it should work, it uses the same chip.

Thank you!

Do you have a photo or wire diagram of the hardware? I’m very interested trying to do this at my house.

You could just implement the two beams as the A and B lines of a Rotary Encoder.
The built-in state machine would then tell your code when a person moved inward, or outward, through the beams, just as it can tell when the rotary encoder is turned one way or the other.
And it’d all be handled in one simple entity reference.

I’m testing your code and it works very well.
I have the sensors next to each other at a distance of 15 cm.
Question: you have been tested at the smallest distance it is possible to place sensors next to each other so that they do not affect each other.
Thank you

Hey @Shannondalebreaux, no, I don’t have any diagrams, but it is easy to figure it out from the code.

I’m going to omit the power part of it because it just connecting the correct voltage to the Vin pin for and the ground to the GND pin for each device.

The sensors have 3 pins for data and control, the Enable pin is setup as follow:

  - platform: vl53l0x
    name: "Sensor 1"
    id: sens_1
    i2c_id: bus_a
    address: 0x41
    update_interval: 50ms
    enable_pin: GPIO25
    timeout: 500us
    internal: true
    
    
  - platform: vl53l0x
    name: "Sensor 2"
    id: sens_2
    i2c_id: bus_a
    address: 0x42
    update_interval: 50ms
    enable_pin: GPIO32
    timeout: 500us
    internal: true

So you connect the enable pin of the sensor 1 to the GPIO25 of the ESP32 and the enable pin of the sensor 2 to the GPIO32.

Then the data pins for both sensors goes like this:

i2c:
  sda: GPIO21
  scl: GPIO22
  scan: True
  id: bus_a

Where the SDA pin of both sensors goes to the GPIO21 and the SCL to the GPIO22.

and that should be it if you are using an ESP32 and the vl53l0x sensor. You can modify the code according to your setup.

@glyndon Yeah, that was my first approach, but it didn’t work consistently. Maybe I didn’t coded it right.