No PIR ESP32 cam motion sensor in ESPHome?

Hi all,
I do have ESP32 cam with no PIR but still would like to have motion detection. My host running HA is pretty loaded and i found the below cool project with Arduino code where all processing is happening in the ESP32 cam, great!

Any chance you could get this working in ESPHope
Using Generic Custom Component — ESPHome ?
Any thought?

Cheers

2 Likes

Hi, did you find a way to activate some kind of motion detection in HA or esphome?

One word: motioneye :eye:

so you installed a new motion-daemon and feed it with the cams stream?

It’s as easy as installing the motioneye hassio addon and configuring it. You can detect motion on many streams your system can handle. For esphome you need to use the esp32 camera web server to work together with motioneye.

1 Like

Nope, would really like to have this in the ESP to unload my HA.

I’m looking at this project and I’m going to see if I can integrate mqtt into it.

Briefly tested this out. It publishes “motion detected” to MQTT and turns on an optional LED on pin 4

Then you would have to setup an MQTT sensor in HA. More to come… I like this idea for presence detection. I may add snapshots to it along with a temp sensor just for the fun of it…

I used the info from ESP32 MQTT Publish Subscribe with Arduino IDE | Random Nerd Tutorials and ESP32 Camera Motion Detection without PIR {The Ultimate Guide} | Eloquent Arduino.


/**
 * Camera motion detection demo
 */

#include "eloquent.h"
#include "eloquent/vision/motion/naive.h"


// uncomment based on your camera and resolution

//#include "eloquent/vision/camera/ov767x/gray/vga.h"
//#include "eloquent/vision/camera/ov767x/gray/qvga.h"
//#include "eloquent/vision/camera/ov767x/gray/qqvga.h"
//#include "eloquent/vision/camera/esp32/aithinker/gray/vga.h"
//#include "eloquent/vision/camera/esp32/aithinker/gray/qvga.h"
//#include "eloquent/vision/camera/esp32/aithinker/gray/qqvga.h"
//#include "eloquent/vision/camera/esp32/wrover/gray/vga.h"
//#include "eloquent/vision/camera/esp32/wrover/gray/qvga.h"
#include "eloquent/vision/camera/esp32/wrover/gray/qqvga.h"
//#include "eloquent/vision/camera/esp32/eye/gray/vga.h"
//#include "eloquent/vision/camera/esp32/eye/gray/qvga.h"
//#include "eloquent/vision/camera/esp32/eye/gray/qqvga.h"
//#include "eloquent/vision/camera/esp32/m5/gray/vga.h"
//#include "eloquent/vision/camera/esp32/m5/gray/qvga.h"
//#include "eloquent/vision/camera/esp32/m5/gray/qqvga.h"
//#include "eloquent/vision/camera/esp32/m5wide/gray/vga.h"
//#include "eloquent/vision/camera/esp32/m5wide/gray/qvga.h"
//#include "eloquent/vision/camera/esp32/m5wide/gray/qqvga.h"

// WiFi and MQTT PubSub
#include <WiFi.h>
#include <PubSubClient.h>

// Replace the next variable with the MQTT Topic for this device
const char* pubtopic = "motion/ENTER_DEVICE_NAME";

// Replace the next variables with your SSID/Password combination
const char* ssid = "ENTER_YOUR_SSID";
const char* password = "ENTER_YOUR_PASSWORD";

// Add your MQTT Broker IP address, example:
//const char* mqtt_server = "192.168.1.144";
const char* mqtt_server = "ENTER_YOUR_MQTT_BROKER_ADDRESS";

WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;

// LED Pin
const int ledPin = 4;

#define THUMB_WIDTH 32
#define THUMB_HEIGHT 24


Eloquent::Vision::Motion::Naive<THUMB_WIDTH, THUMB_HEIGHT> detector;


void setup() {
    delay(4000);
    Serial.begin(115200);

    // turn on high freq for fast streaming speed
    camera.setHighFreq();

    if (!camera.begin())
        eloquent::abort(Serial, "Camera init error");

    Serial.println("Camera init OK");

    // wait for at least 10 frames to be processed before starting to detect
    // motion (false triggers at start)
    // then, when motion is detected, don't trigger for the next 10 frames
    detector.startSinceFrameNumber(10);
    detector.debounceMotionTriggerEvery(10);

    // or, in one call
    detector.throttle(10);

    // trigger motion when at least 10% of pixels change intensity by
    // at least 15 out of 255
    detector.setPixelChangesThreshold(0.1);
    detector.setIntensityChangeThreshold(15);
    setup_wifi();
    client.setServer(mqtt_server, 1883);
    client.setCallback(callback);
}

void setup_wifi() {
  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

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

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

void callback(char* topic, byte* message, unsigned int length) {
  Serial.print("Message arrived on topic: ");
  Serial.print(topic);
  Serial.print(". Message: ");
  String messageTemp;
  
  for (int i = 0; i < length; i++) {
    Serial.print((char)message[i]);
    messageTemp += (char)message[i];
  }
  Serial.println();

  // Feel free to add more if statements to control more GPIOs with MQTT

  // If a message is received on the topic esp32/output, you check if the message is either "on" or "off". 
  // Changes the output state according to the message
  if (String(topic) == "esp32/output") {
    Serial.print("Changing output to ");
    if(messageTemp == "on"){
      Serial.println("on");
      digitalWrite(ledPin, HIGH);
    }
    else if(messageTemp == "off"){
      Serial.println("off");
      digitalWrite(ledPin, LOW);
    }
  }
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESP8266Client")) {
      Serial.println("connected");
      // Subscribe
      client.subscribe("esp32/output");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void loop() {
    if (!client.connected()) {
      reconnect();
    }
    client.loop();


  
    if (!camera.capture()) {
        Serial.println(camera.getErrorMessage());
        delay(1000);
        return;
    }

    // perform motion detection on resized image for fast detection
    camera.image.resize<THUMB_WIDTH, THUMB_HEIGHT>();
    camera.image.printAsJsonTo(Serial);
    detector.update(camera.image);

    // if motion is detected, print coordinates to serial in JSON format
    if (detector.isMotionDetected()) {
        client.publish(pubtopic, "Motion Detected");
        detector.printAsJsonTo(Serial);
    }

    // release memory
    camera.free();
}
1 Like

Sounds great, exciting :slight_smile:

A bit more robust and a few more features:

  • Enable/Disable Motion Detection
    • configured by publishing “enable” or “disable” to motion/office/settings/enable
  • Configurable “Heartbeat”
    • MQTT Topic: motion/office/online
    • reports onliine every 60 seconds
    • configured by publishing a new interval in ms to motion/office/settings/heartbeat
      • By default between 60000 - 300000 (1 minute - 5 minutes)
  • Configurable Motion “Lockout”
    • Stops motion detection for X miliseconds after motion detected
    • configured by publishing a new interval in ms to motion/office/settings/lockout
      • By default between 1000 - 60000 (1-60 seconds)
  • LED Indication
    • By default runs on pin 12 (works on FreeNove wrover dev board)
/**
 * ESP32 Camera as Motion Detector
 * Based on https://eloquentarduino.com/projects/esp32-arduino-motion-detection
 * Tested using ESP32 wrover dev board w/ qqvga camera only
 * 
 * In Arduino IDE you need the following extra libraries installed:
 *   - EloquantArduino
 *   - PubSubClient
 */

// REQUIRED SETTINGS:

// Include WiFi and MQTT PubSub
#include <WiFi.h>
#include <PubSubClient.h>

// WiFi Setup *REQUIRED*
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

// MQTT Broker Setup *REQUIRED*
const char* mqtt_server = "YOUR_MQTT_BROKER";           // MQTT Broker IP/URL
const int mqtt_port = 1883;                         // MQTT Broker Port


// MQTT Pub Topics **REQUIRED**
const char* onlinetopic = "motion/office/online";                 // Tells us it's online. (heartbeat)
unsigned long heartbeatInterval = 60000;                    // Heartbeat interval in ms (60000 = 60 seconds)
const char* motiontopic = "motion/office/status";                 // Tells us if there's motion or not
const char* lockoutTimePub = "motion/office/newLockout";          // Tells us new Motion Lockout time when set
const char* heartbeatIntervalPub = "motion/office/newHeartbeat";  // Tells us new Heartbeat Interval when set

//MQTT Topics to change settings
const char* MQTTheartbeat = "motion/office/settings/heartbeat";   // set heartbeat interval
const int MQTTheartbeatMIN = 59999;                               // 1 minute
const int MQTTheartbeatMAX = 300001;                              // 5 minutes
const char* MQTTmotionenable = "motion/office/settings/enable";   // "enable" or "disable"
const char* MQTTmotionlockout = "motion/office/settings/lockout"; // time in ms to keep motion triggered
const int MQTTmotionlockoutMIN = 999;
const int MQTTmotionlockoutMAX = 60001;
// MQTT Sub Topics
const char* subtopic = "motion/office/settings/+";

// LED Pin
#define LEDPIN 12



// NO NEED TO EDIT BELOW THIS LINE UNLESS YOU WANT TO CHANGE THINGS


#include "eloquent.h"
#include "eloquent/vision/motion/naive.h"


// uncomment based on your camera and resolution

//#include "eloquent/vision/camera/ov767x/gray/vga.h"
//#include "eloquent/vision/camera/ov767x/gray/qvga.h"
//#include "eloquent/vision/camera/ov767x/gray/qqvga.h"
//#include "eloquent/vision/camera/esp32/aithinker/gray/vga.h"
//#include "eloquent/vision/camera/esp32/aithinker/gray/qvga.h"
//#include "eloquent/vision/camera/esp32/aithinker/gray/qqvga.h"
//#include "eloquent/vision/camera/esp32/wrover/gray/vga.h"
//#include "eloquent/vision/camera/esp32/wrover/gray/qvga.h"
#include "eloquent/vision/camera/esp32/wrover/gray/qqvga.h"
//#include "eloquent/vision/camera/esp32/eye/gray/vga.h"
//#include "eloquent/vision/camera/esp32/eye/gray/qvga.h"
//#include "eloquent/vision/camera/esp32/eye/gray/qqvga.h"
//#include "eloquent/vision/camera/esp32/m5/gray/vga.h"
//#include "eloquent/vision/camera/esp32/m5/gray/qvga.h"
//#include "eloquent/vision/camera/esp32/m5/gray/qqvga.h"
//#include "eloquent/vision/camera/esp32/m5wide/gray/vga.h"
//#include "eloquent/vision/camera/esp32/m5wide/gray/qvga.h"
//#include "eloquent/vision/camera/esp32/m5wide/gray/qqvga.h"



// Setup Detection Variables
bool motionEnabled = true;
bool detection = false;
bool motionEvent = false;
unsigned long motionLockoutTime = 5000;
unsigned long detectionStartTime = millis();
unsigned long lastHeartbeat = millis();
bool initialHeartbeat = false;


WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;

#define THUMB_WIDTH 32
#define THUMB_HEIGHT 24


Eloquent::Vision::Motion::Naive<THUMB_WIDTH, THUMB_HEIGHT> detector;


void setup() {
  
    pinMode(LEDPIN, OUTPUT);
    digitalWrite(LEDPIN, LOW);
    delay(4000);
    Serial.begin(115200);

    // turn on high freq for fast streaming speed
    camera.setHighFreq();

    if (!camera.begin())
        eloquent::abort(Serial, "Camera init error");

    Serial.println("Camera init OK");

    // wait for at least 10 frames to be processed before starting to detect
    // motion (false triggers at start)
    // then, when motion is detected, don't trigger for the next 10 frames
    detector.startSinceFrameNumber(10);
    detector.debounceMotionTriggerEvery(10);

    // or, in one call
    detector.throttle(10);

    // trigger motion when at least 10% of pixels change intensity by
    // at least 15 out of 255
    detector.setPixelChangesThreshold(0.1);
    detector.setIntensityChangeThreshold(15);
    setup_wifi();
    client.setServer(mqtt_server, 1883);
    client.setCallback(callback);
}

void setup_wifi() {
  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

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

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

void callback(char* topic, byte* message, unsigned int length) {
  Serial.print("Message arrived on topic: ");
  Serial.print(topic);
  Serial.print(". Message: ");
  String messageTemp;
  
  for (int i = 0; i < length; i++) {
    Serial.print((char)message[i]);
    messageTemp += (char)message[i];
  }
  Serial.println();

  // MQTT Actions
  
  // Heartbeat Interval
  if (String(topic) == MQTTheartbeat){
    unsigned long newHeartbeatInterval = strtoul(messageTemp.c_str(),NULL,0);
    Serial.print("Received new Heartbeat Interval: ");
    Serial.println(newHeartbeatInterval);
    if ((newHeartbeatInterval-MQTTheartbeatMIN)*(newHeartbeatInterval-MQTTheartbeatMAX)){
      heartbeatInterval = newHeartbeatInterval;
      Serial.print("Heartbeat Interval Set To :");
      Serial.print(newHeartbeatInterval);
      Serial.println(" via MQTT");
      client.publish(heartbeatIntervalPub,messageTemp.c_str());
    }
    else{
      Serial.print("New Heartbeat Interval Must be > ");
      Serial.println(MQTTheartbeatMIN);
      Serial.print("Heartbeat Interval Remains: ");
      Serial.println(heartbeatInterval);
    }    
  }

  
  // Motion Lockout Time
  if (String(topic) == MQTTmotionlockout){
    unsigned long newLockoutTime = strtoul(messageTemp.c_str(),NULL,0);
    Serial.print("Received new lockout: ");
    Serial.println(newLockoutTime);
    if ((newLockoutTime-MQTTmotionlockoutMIN)*(newLockoutTime-MQTTmotionlockoutMAX)){
      motionLockoutTime = newLockoutTime;
      Serial.print("Motion Lockout Time Set To :");
      Serial.print(newLockoutTime);
      Serial.println(" via MQTT");
      client.publish(lockoutTimePub,messageTemp.c_str());
    }
    else{
      Serial.print("New Lockout Time Must be > ");
      Serial.println(MQTTmotionlockoutMIN);
      Serial.print("Lockout Time Remains: ");
      Serial.println(motionLockoutTime);
    }    
  }

  // Motion Detection Enable/Disable
  if (String(topic) == MQTTmotionenable){
    if (messageTemp == "enable"){
      if (motionEnabled == false){
        motionEnabled = true;
        Serial.println("Motion Sensor Enabled");
        client.publish(MQTTmotionenable, "Enabled");
      }
    }
    if (messageTemp == "disable"){
      if (motionEnabled == true){
        motionEnabled = false;
        Serial.println("MotionSensor Disabled");
        client.publish(MQTTmotionenable, "Disabled");
      }
    }
  }

  
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESP8266Client")) {
      Serial.println("connected");
      // Subscribe
      client.subscribe(subtopic);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void detect_motion(){
    if (camera.capture()){
        // perform motion detection on resized image for fast detection
        camera.image.resize<THUMB_WIDTH, THUMB_HEIGHT>();
        //camera.image.printAsJsonTo(Serial);
        detector.update(camera.image);
    
        // if motion is detected, print coordinates to serial in JSON format
        if (detector.isMotionDetected()) {
            detection = true;
        }
        else{
            detection = false;
        }
        // release memory
        camera.free();
    }
    else {
      delay(1000);
      detect_motion();
    }
}
void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();                                // Listen to MQTT Broker

  // Get current execution time
  unsigned long currentTime = millis();         // Used for HEARTBEAT and MOTION DETECTION

  // BEGIN HEARTBEAT
  if (initialHeartbeat){
    unsigned long timeoutHeartbeat = lastHeartbeat + heartbeatInterval;
    if (currentTime > timeoutHeartbeat){
      Serial.print("Heartbeat: ");
      Serial.println(currentTime);
      client.publish(onlinetopic,"online");
      lastHeartbeat = millis();
    }
  }
  if (!initialHeartbeat){
    Serial.print("Heartbeat: ");
    Serial.println(currentTime);
    client.publish(onlinetopic,"online");
    lastHeartbeat = millis();
    initialHeartbeat = true;
  }
  // END HEARTBEAT
  
  
  // BEGIN MOTION DETECTION
  if (motionEnabled){
    if (motionEvent){
      unsigned long timeout = detectionStartTime + motionLockoutTime;
      if (currentTime > timeout){
        detect_motion();
        if (!detection){
          Serial.println("Motion Detection Event End");
          client.publish(motiontopic, "off");   // Publish motiontopic "off"
          digitalWrite(LEDPIN, LOW);            // Turn off LED
          motionEvent = false;
        }
      }
    }
    else{
      detect_motion();
      if (detection){
        Serial.println("Motion Detection Event Begin");
        client.publish(motiontopic, "on");   // Publish motiontopic "on"
        digitalWrite(LEDPIN, HIGH);               // Turn on LED
        detectionStartTime = millis();
        motionEvent = true;
      }
    }
  }
  // END MOTION DETECTION
}
3 Likes

How cool!! Do not have the time to test this right now for reasons…but soon :slight_smile:
Many thanks!!

I’m digging this so I added more…

Full MQTT Discovery Setup, It’s now available on my GitHub:

2 Likes

I have a ESP32 Cam with a PIR and LED attached.
If your not bothered about soldering etc I can share my setup?

Please share?

Since i find PIRs super unreliable and slow, would it be possible to use the camera feed to motion eye to set up zones to detect motion and set it up so on motion detection it triggers event in HA?

You can use third party stuff like Doods.

However, I’ve never had issues with PIRs, usually very quick…

So what about kitchen countertop lights is?

In my experience PIR will:

  • not detect motion correctly
  • detect motion not above countertop
  • false positive
  • slow

zigbee: all of the above + becomes stuck
bt: slow, but so far best of the battery powered, can do sensing in the shape that it does nto react to the motion
wifi - i did found some diy, finished shelly maybe? but rather expensive to just try if it works by a chance
diy wifi - i did not found any i could adjust sensitivity and detect only certain angle etc. using eshome, all had mechanical adjustements and some mechancialy set timeout - not really sensor.

That is why i’m now thinking about camera with set detection zone or mmwave