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.