Occupancy Detection and People Counting sensor

I have been experimenting with People Counting using an IR Break Beam sensor inspired by MySmarthome Blog
Basic idea is to count people going in and out of rooms and avoid using motion detectors to automate lights.
I have a setup running which has an Arduino Uno creating the ir beams on one side of the door and a NodeMCU monitoring the beams on the other. If there is a breakage of the beams NodeMCU publishes the times on to a MQTT topic.
I do some processing of the times in python and publishes the people count to another MQTT topic which is monitored by HA
The whole setup is working and is more or less accurate. It sometimes do not count or thinks that I am going out instead of actually going in.
But I think this can be corrected or compensated by creating the whole picture of the house in software like how many people are in the house and with some logic.
Eventually I am planning to add these sensors to all my external and internal doors. I think I can power the beams using one or two Uno’s throughout the house. One NodeMCU for each door

Part list - Emitter
Uno
IR Emitter (https://www.aliexpress.com/item/Hot-New-Black-Clear-5pcs-Launch-Emitter-5pcs-Receive-Receiver-5mm-940nm-IR-Infrared-Diode-LED/32759910988.html?spm=2114.13010608.0.0.rEfHUI )


Sketch

#include <IRremote.h>

#define PIN_IR 3
IRsend irsend;
void setup()
{
  //Serial.begin(9600);
  irsend.enableIROut(38);
  irsend.mark(0);
}


void loop() {

}

Part list Receiver

NodeMCU
IR Receiver TSSP58038 (http://au.rs-online.com/web/p/ir-receivers/7730382/)

Sketch

/*Occupancy Detection and People Counting sensor
 * based on http://www.mysmarthomeblog.com/page--16.html and
 * sketch heavily copied from Bruh Multisensor https://github.com/bruhautomation/ESP-MQTT-JSON-Multisensor
 * This implements the logic behind the people counting
 * 
 * Issues: 
 * Needs to cover the IR Emitter while the sensor reboots to get it working
 * OTA not working
 * Not tested with multiple doors
 */

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <ArduinoJson.h>

/**************************** PIN DEFINITIONS ********************************************/
// ir receiver pin def
#define IR_RECEIVER_PIN2 14 // 14 = D5 on nodemcu #define only usefull for ESP8266
#define IR_RECEIVER_PIN 2 // put 2 = D4 on nodemcu, 2 = D2 on arduino

#define BUZZER_PIN  D2 //used for testing
#define MQTT_MAX_PACKET_SIZE 512 //no idea why it is here

/************ WIFI and MQTT INFORMATION (CHANGE THESE FOR YOUR SETUP) ******************/
#define wifi_ssid "xxx" //type your WIFI information inside the quotes
#define wifi_password "xxx"
#define mqtt_server "192.168.1.12"
#define mqtt_user "xxx" 
#define mqtt_password "xxx"
#define mqtt_port 1883

/************* MQTT TOPICS (change these topics as you wish)  **************************/
//Set topic as home/rooms/<room_name>/<door_No>
#define room_occupancy_topic "home/rooms/masterbed/door1" //topic to post break beam sensor times
#define room_set_topic "home/rooms/masterbed/set" //to be used for setting initial values

/**************************** FOR OTA **************************************************/
#define SENSORNAME "masterbed_door1" //sensor name
#define OTApassword "xxx" 
int OTAport = 8266;



unsigned long start1, newStart, finished, elapsed; //values for beam 1
unsigned long start2, newStart2, finished2, elapsed2; //values for beam 2
bool timerStarted = false;
bool timerStarted2 = false;
bool timer1Calculated = false;
bool timer2Calculated = false;

bool stateOn = false; 
char message_buff[100];
int calibrationTime = 0;
const int BUFFER_SIZE = 300;

WiFiClient espClient;
PubSubClient client(espClient);

void setup()
{
  Serial.begin(9600);
  
  pinMode(IR_RECEIVER_PIN, INPUT);
  pinMode(IR_RECEIVER_PIN2, INPUT);
  pinMode(BUZZER_PIN, OUTPUT); //for testing
  delay(10);

  ArduinoOTA.setPort(OTAport);
  ArduinoOTA.setHostname(SENSORNAME);
  ArduinoOTA.setPassword((const char *)OTApassword);
  Serial.print("Setting up door sensor ");
  for (int i = 0; i < calibrationTime; i++) {
    Serial.print(".");
    delay(1000);
  }
  Serial.println("Starting door Node named " + String(SENSORNAME));
  
  setup_wifi();
  client.setServer(mqtt_server, mqtt_port);
 // client.setCallback(callback);
  ArduinoOTA.onStart([]() {
    Serial.println("Starting");
  });
  
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });
  
  ArduinoOTA.begin();
  Serial.println("Ready");
  Serial.print("IPess: ");
  Serial.println(WiFi.localIP());
}
/********************************** START SETUP WIFI*****************************************/
void setup_wifi() {

  delay(10);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(wifi_ssid);

  WiFi.mode(WIFI_STA);
  WiFi.begin(wifi_ssid, wifi_password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}



// /********************************** START CALLBACK*****************************************/
// void callback(char* topic, byte* payload, unsigned int length) {
//   Serial.print("Message arrived [");
//   Serial.print(topic);
//   Serial.print("] ");
// 
//   char message[length + 1];
//   for (int i = 0; i < length; i++) {
//     message[i] = (char)payload[i];
//   }
//   message[length] = '\0';
//   Serial.println(message);
// 
//   if (!processJson(message)) {
//     return;
//   }
  
/********************************** START SEND STATE*****************************************/
void sendState() {
  StaticJsonBuffer<BUFFER_SIZE> jsonBuffer;

  JsonObject& root = jsonBuffer.createObject();
//set the state to "set" and start1 to whatever peoplecount you want to set the initial value 
// or to correct the value
  root["state"] = (stateOn) ? "Motion Detected" : "Idle"; 
  root["start1"] = (String)start1;
  root["finished1"] = (String)finished;
  root["start2"] = (String)start2;
  root["finished2"] = (String)finished2;
  char buffer[root.measureLength() + 1];
  root.printTo(buffer, sizeof(buffer));

  Serial.println(buffer);
  client.publish(room_occupancy_topic, buffer, true);
}

/********************************** START RECONNECT*****************************************/
void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect(SENSORNAME, mqtt_user, mqtt_password)) {
      Serial.println("connected");
      //client.subscribe(light_set_topic);
      //setColor(0, 0, 0);
      sendState();
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}


void displayResult()
{  elapsed=finished-start1;
//testing
//  Serial.print("Start1: ");
//  Serial.print(start1);
//  Serial.print(", finished time: ");
//  Serial.print(finished);
//  Serial.print(", elapsed time: ");
//  Serial.println(elapsed);
//  Serial.println();
//  Serial.println();
//  Serial.println();
  timerStarted = false;
//  if (elapsed > 40)
   timer1Calculated = true;
}

void displayResult2()
{
  elapsed2=finished2-start2;
  //testing
//  Serial.print("Start2: ");
//  Serial.print(start2);
//  Serial.print(", finished2 time: ");
//  Serial.print(finished2);
//  Serial.print(", elapsed time: ");
//  Serial.println(elapsed2);
//  Serial.println();
//  Serial.println();
//  Serial.println();
  timerStarted2 = false;
 // if (elapsed > 40)
   timer2Calculated = true;
}
//testing
void beep(unsigned char delayms){
  digitalWrite(BUZZER_PIN, HIGH);      // Almost any value can be used except 0 and 255
  delay(delayms);          // wait for a delayms ms
  digitalWrite(BUZZER_PIN, LOW);       // 0 turns it off
  delay(delayms);          // wait for a delayms ms   
}  

void loop() {
    
  ArduinoOTA.handle();

  if (!client.connected()) {
    reconnect();
  }
  client.loop();
//checking led1
//  Serial.println(timerStarted2);
  if (digitalRead(IR_RECEIVER_PIN)==HIGH and timerStarted == false)
  {
    newStart = millis();
    Serial.print("New Start diff is: ");
    Serial.println(newStart - finished);
    if ((newStart - finished) > 100) {
       start1=newStart;
       timerStarted = true;
       stateOn = true;
       delay(20); // for debounce
       
    }
  }
  if (digitalRead(IR_RECEIVER_PIN)==LOW and timerStarted == true)
  {
    //Serial.println((int)digitalRead(IR_RECEIVER_PIN));
    finished=millis();
    displayResult();
    delay(20); // for debounce
    
  }
  
//checking led2

   if (digitalRead(IR_RECEIVER_PIN2)==HIGH and timerStarted2 == false)
   {
     newStart2 = millis();
     Serial.print("New Start2 diff is: ");
     Serial.println(newStart2 - finished2);
    if ((newStart2 - finished2) > 100) {
       start2=newStart2;
     start2=millis();
     timerStarted2 = true;
     delay(20); // for debounce
    }
   }
   if (digitalRead(IR_RECEIVER_PIN2)==LOW and timerStarted2 == true)
   {
     finished2=millis();
     displayResult2();
     delay(20); // for debounce
   }
   
   if (timer1Calculated == true and timer2Calculated == true)
       
   { 
    sendState();
    timer1Calculated = false;
     timer2Calculated = false;
     stateOn = false;
   }
 //checking direction is implemented in python
//     //Serial.println("Checking direction");
//     if (start < start2 and finished < finished2)
//     {
//       Serial.println("Going in");
//       //digitalWrite(PIN_SPKR, 128);
//       beep(50);
//       beep(50);
//     }
//     if (start > start2 and finished < finished2)
//     {
//       Serial.println("Going out");
//       //analogWrite(PIN_SPKR, 300);
//       beep(100);
//     }
    
//   }
  
}

Python logic

#Occupancy Detection and People Counting sensor
#Script to monitor MQTT Topic and workout if the people are going in or out of room 
# and to adjust people count accordingly
# I have no idea what I am doing, I am a copy paste programmer, help and tips appreciated.

import paho.mqtt.client as mqtt
import json
from room import Room
#from db import save_to_db
totalPeopleInTheHouse = 0
isEntryPoint = False

roomPeopleCount = 0
doorno = 1
state = None
elapsed1 = 0
elapsed2 = 0
masterbed = Room("masterbed", 1, 0)


def on_connect(mqttc, userdata, rc):
    print('connected...rc=' + str(rc))
    mqttc.subscribe(topic='home/rooms/masterbed/door1', qos=0)
    

def on_disconnect(mqttc, userdata, rc):
    print('disconnected...rc=' + str(rc))

def on_message(mqttc, userdata, msg):
    """This is executed everytime a message is received in the topic """
    #print('message received...')
   # print('topic: ' + msg.topic + ', qos: ' + 
  #        str(msg.qos) + ', message: ' + str(msg.payload))
#    //save_to_db(msg)
    json_string = msg.payload
    parsed_json = json.loads(json_string)
    print(parsed_json['start1'], parsed_json['finished1'], parsed_json['start2'], parsed_json['finished2'] ) 
    if (parsed_json['state'] == "Motion Detected"):
       checkState(parsed_json['start1'], parsed_json['finished1'], parsed_json['start2'], parsed_json['finished2'] )
    elif (parsed_json['state'] == "set"): #to set the peoplecount to desired value
       setPeopleCount(int(parsed_json['start1']))
    else:
       return
          
          
def setPeopleCount(count):
    """Set room's occupants count"""
    masterbed.set_peopleCount(count)
    mqttc.publish("home/rooms/masterbed/peopleCount",count)
    
def checkState(start1, end1, start2, end2):
    """Logic to see if the person entered or exited the room"""
    start1 = int(start1)
    end1 = int(end1)
    start2 = int(start2)
    end2 = int(end2)
    elapsed1 = end1 - start1
   # print "Elapsed1 is %s" % elapsed1
    elapsed2 = end2 - start2
  #  print "Elapsed2 is %s" % elapsed2
    #check direction
   # print "People Count  outside is  %s " % masterbed.peopleCount
    if start1 < start2 and end1 < end2:
        print "Going out"
        if not masterbed.peopleCount == 0:
            newcount = masterbed.peopleCount - 1
            masterbed.set_peopleCount(newcount)
            mqttc.publish("home/rooms/masterbed/peopleCount",newcount)
           # print "People Count after reducing is %s " % masterbed.peopleCount
    elif start1 > start2 and end1 > end2:
        print "Going in"
        newcount = masterbed.peopleCount + 1
        masterbed.set_peopleCount(newcount)
        mqttc.publish("home/rooms/masterbed/peopleCount",newcount) 
       # print "People Count increasing is %s " % masterbed.peopleCount
    
def on_subscribe(mqttc, userdata, mid, granted_qos):
    print('subscribed (qos=' + str(granted_qos) + ')')

def on_unsubscribe(mqttc, userdata, mid, granted_qos):
    print('unsubscribed (qos=' + str(granted_qos) + ')')

mqttc = mqtt.Client()
mqttc.on_connect = on_connect
mqttc.on_disconnect = on_disconnect
#mqttc.message_callback_add('home/rooms/masterbed/set', on_message_set)
mqttc.on_message = on_message
mqttc.on_subscribe = on_subscribe
mqttc.on_unsubscribe = on_unsubscribe
mqttc.connect(host='192.168.1.12', port=1883)
mqttc.loop_forever()
#Occupancy Detection and People Counting sensor
#A class to keep track of room properties
# and to adjust people count accordingly
# I have no idea what I am doing, I am a copy paste programmer, help and tips appreciated.
class Room(object):
    """ Class to track rooms"
    
Attributes:
        name: A string representing the rooms's name.
        doors: An integer representing number of doors
        peopleCount: An integer representing number of occupants.
    """

    def __init__(self, name, doors, peopleCount=0):
        """Return a Room object whose name is *name*.""" 
        self.name = name
        self.doors = doors
        self.peopleCount = peopleCount

    def set_peopleCount(self, count):
        """Set room's occupants count"""
        self.peopleCount = count

HA Sensor

- platform: mqtt  
  state_topic: "home/rooms/masterbed/peopleCount"
  name: "MasterBed People Count"```
<img src="//community-assets.home-assistant.io/original/2X/b/bd63078ee86aa0e17ef025bd8d457a7275fa6361.jpg" width="281" height="500"><img src="//community-assets.home-assistant.io/original/2X/2/20428b45207dbae28df92ba4555b442bfcf7c25a.jpg" width="600" height="338">
<img src="//community-assets.home-assistant.io/original/2X/e/e6a3132afb0e060feda807e850141f6ff79aaca6.jpg" width="281" height="500">

This is more of a proof of concept stage,while I wait for our house being built. Sorry for the mess and I can not modify the rented house anyway. 
Suggestions for improvements and comments appreciated.
I am not a programmer, more of a hobbyist/copy paste programmer. 
NB:
I tried to run the emitter from NodeMCU but could not get it to emit the 38khz continuously.
`irsend.mark(100);`  would emit for 100 microseconds but in Uno `irsend.mark(0);`  will continuously emit the signal.
If anyone knows how to emit ir signals continuously from a NodeMCU please comment.
5 Likes

Hello, friend, please give me a complete program, I compile error, thank you

This looks cool, @anilet !

Did you contact Jon at mysmarthomeblog to ask him about the logic he’d managed to build into his sensors using the PIC16?

I’m really interested in attempting something like this and am wondering if what you’ve done is an improvement or a replication of his work?

:slight_smile: :slight_smile: Thanks!

I tried contacting him without any success. I tried to do a prototype of his work using arduino
This works around 90% of time to accurately count people.
The problem I encountered was to properly locate the sensor on the door frame to account for my kids as well as dogs.
If I locate it too high it will not count kids, too low counts dogs.
Fine tuning the delay between line breaks to account for large or small body type is also problematic.
At the end I found it is very hard to fix the last 5~10% inaccuracy.
I could not use this for any light automation or occupancy detection if it is less than 99% accurate.

1 Like

Thanks. Such a shame he’s gone to ground. Hope he’s OK. Looks like another online username for his home automation posts was jon_nc17 and a previous website havelockonline. No sign of ANY recent posts. But someone must be paying the domain registration bill?! It renews yearly. :frowning: