├── README.md └── esp32-cam-motion-detector.ino /README.md: -------------------------------------------------------------------------------- 1 | # esp32-cam-motion-detector 2 | Arduino Sketch to turn an ESP32 dev board into a configurable MQTT motion detector 3 | ![20220509_010204](https://user-images.githubusercontent.com/93575915/167377149-ae3e2918-35f0-4af7-ad13-2af7858bee13.jpg) 4 | 5 | A bit more robust and a few more features: 6 | - Enable/Disable Motion Detection 7 | - configured by publishing "enable" or "disable" to motion/office/settings/enable 8 | - Configurable "Heartbeat" 9 | - MQTT Topic: motion/office/online 10 | - reports onliine every 60 seconds 11 | - configured by publishing a new interval in ms to motion/office/settings/heartbeat 12 | - By default between 60000 - 300000 (1 minute - 5 minutes) 13 | - Configurable Motion "Lockout" 14 | - Stops motion detection for X miliseconds after motion detected 15 | - configured by publishing a new interval in ms to motion/office/settings/lockout 16 | - By default between 1000 - 60000 (1-60 seconds) 17 | - LED Indication 18 | - By default runs on pin 12 (works on FreeNove wrover dev board) 19 | 20 | ``` 21 | /** 22 | * ESP32 Camera as Motion Detector 23 | * Based on https://eloquentarduino.com/projects/esp32-arduino-motion-detection 24 | * Tested using ESP32 wrover dev board w/ qqvga camera only 25 | * 26 | * In Arduino IDE you need the following extra libraries installed: 27 | * - EloquantArduino 28 | * - PubSubClient 29 | */ 30 | 31 | // REQUIRED SETTINGS: 32 | 33 | // Include WiFi and MQTT PubSub 34 | #include 35 | #include 36 | 37 | // WiFi Setup *REQUIRED* 38 | const char* ssid = "YOUR_SSID"; 39 | const char* password = "YOUR_WIFI_PASSWORD"; 40 | 41 | // MQTT Broker Setup *REQUIRED* 42 | const char* mqtt_server = "YOUR_MQTT_BROKER"; // MQTT Broker IP/URL 43 | const int mqtt_port = 1883; // MQTT Broker Port 44 | 45 | 46 | // MQTT Pub Topics **REQUIRED** 47 | const char* onlinetopic = "motion/office/online"; // Tells us it's online. (heartbeat) 48 | unsigned long heartbeatInterval = 60000; // Heartbeat interval in ms (60000 = 60 seconds) 49 | const char* motiontopic = "motion/office/status"; // Tells us if there's motion or not 50 | const char* lockoutTimePub = "motion/office/newLockout"; // Tells us new Motion Lockout time when set 51 | const char* heartbeatIntervalPub = "motion/office/newHeartbeat"; // Tells us new Heartbeat Interval when set 52 | 53 | //MQTT Topics to change settings 54 | const char* MQTTheartbeat = "motion/office/settings/heartbeat"; // set heartbeat interval 55 | const int MQTTheartbeatMIN = 59999; // 1 minute 56 | const int MQTTheartbeatMAX = 300001; // 5 minutes 57 | const char* MQTTmotionenable = "motion/office/settings/enable"; // "enable" or "disable" 58 | const char* MQTTmotionlockout = "motion/office/settings/lockout"; // time in ms to keep motion triggered 59 | const int MQTTmotionlockoutMIN = 999; 60 | const int MQTTmotionlockoutMAX = 60001; 61 | // MQTT Sub Topics 62 | const char* subtopic = "motion/office/settings/+"; 63 | 64 | // LED Pin 65 | #define LEDPIN 12 66 | 67 | 68 | 69 | // NO NEED TO EDIT BELOW THIS LINE UNLESS YOU WANT TO CHANGE THINGS 70 | 71 | 72 | #include "eloquent.h" 73 | #include "eloquent/vision/motion/naive.h" 74 | 75 | 76 | // uncomment based on your camera and resolution 77 | 78 | //#include "eloquent/vision/camera/ov767x/gray/vga.h" 79 | //#include "eloquent/vision/camera/ov767x/gray/qvga.h" 80 | //#include "eloquent/vision/camera/ov767x/gray/qqvga.h" 81 | //#include "eloquent/vision/camera/esp32/aithinker/gray/vga.h" 82 | //#include "eloquent/vision/camera/esp32/aithinker/gray/qvga.h" 83 | //#include "eloquent/vision/camera/esp32/aithinker/gray/qqvga.h" 84 | //#include "eloquent/vision/camera/esp32/wrover/gray/vga.h" 85 | //#include "eloquent/vision/camera/esp32/wrover/gray/qvga.h" 86 | #include "eloquent/vision/camera/esp32/wrover/gray/qqvga.h" 87 | //#include "eloquent/vision/camera/esp32/eye/gray/vga.h" 88 | //#include "eloquent/vision/camera/esp32/eye/gray/qvga.h" 89 | //#include "eloquent/vision/camera/esp32/eye/gray/qqvga.h" 90 | //#include "eloquent/vision/camera/esp32/m5/gray/vga.h" 91 | //#include "eloquent/vision/camera/esp32/m5/gray/qvga.h" 92 | //#include "eloquent/vision/camera/esp32/m5/gray/qqvga.h" 93 | //#include "eloquent/vision/camera/esp32/m5wide/gray/vga.h" 94 | //#include "eloquent/vision/camera/esp32/m5wide/gray/qvga.h" 95 | //#include "eloquent/vision/camera/esp32/m5wide/gray/qqvga.h" 96 | 97 | 98 | 99 | // Setup Detection Variables 100 | bool motionEnabled = true; 101 | bool detection = false; 102 | bool motionEvent = false; 103 | unsigned long motionLockoutTime = 5000; 104 | unsigned long detectionStartTime = millis(); 105 | unsigned long lastHeartbeat = millis(); 106 | bool initialHeartbeat = false; 107 | 108 | 109 | WiFiClient espClient; 110 | PubSubClient client(espClient); 111 | long lastMsg = 0; 112 | char msg[50]; 113 | int value = 0; 114 | 115 | #define THUMB_WIDTH 32 116 | #define THUMB_HEIGHT 24 117 | 118 | 119 | Eloquent::Vision::Motion::Naive detector; 120 | 121 | 122 | void setup() { 123 | 124 | pinMode(LEDPIN, OUTPUT); 125 | digitalWrite(LEDPIN, LOW); 126 | delay(4000); 127 | Serial.begin(115200); 128 | 129 | // turn on high freq for fast streaming speed 130 | camera.setHighFreq(); 131 | 132 | if (!camera.begin()) 133 | eloquent::abort(Serial, "Camera init error"); 134 | 135 | Serial.println("Camera init OK"); 136 | 137 | // wait for at least 10 frames to be processed before starting to detect 138 | // motion (false triggers at start) 139 | // then, when motion is detected, don't trigger for the next 10 frames 140 | detector.startSinceFrameNumber(10); 141 | detector.debounceMotionTriggerEvery(10); 142 | 143 | // or, in one call 144 | detector.throttle(10); 145 | 146 | // trigger motion when at least 10% of pixels change intensity by 147 | // at least 15 out of 255 148 | detector.setPixelChangesThreshold(0.1); 149 | detector.setIntensityChangeThreshold(15); 150 | setup_wifi(); 151 | client.setServer(mqtt_server, 1883); 152 | client.setCallback(callback); 153 | } 154 | 155 | void setup_wifi() { 156 | delay(10); 157 | // We start by connecting to a WiFi network 158 | Serial.println(); 159 | Serial.print("Connecting to "); 160 | Serial.println(ssid); 161 | 162 | WiFi.begin(ssid, password); 163 | 164 | while (WiFi.status() != WL_CONNECTED) { 165 | delay(500); 166 | Serial.print("."); 167 | } 168 | 169 | Serial.println(""); 170 | Serial.println("WiFi connected"); 171 | Serial.println("IP address: "); 172 | Serial.println(WiFi.localIP()); 173 | } 174 | 175 | void callback(char* topic, byte* message, unsigned int length) { 176 | Serial.print("Message arrived on topic: "); 177 | Serial.print(topic); 178 | Serial.print(". Message: "); 179 | String messageTemp; 180 | 181 | for (int i = 0; i < length; i++) { 182 | Serial.print((char)message[i]); 183 | messageTemp += (char)message[i]; 184 | } 185 | Serial.println(); 186 | 187 | // MQTT Actions 188 | 189 | // Heartbeat Interval 190 | if (String(topic) == MQTTheartbeat){ 191 | unsigned long newHeartbeatInterval = strtoul(messageTemp.c_str(),NULL,0); 192 | Serial.print("Received new Heartbeat Interval: "); 193 | Serial.println(newHeartbeatInterval); 194 | if ((newHeartbeatInterval-MQTTheartbeatMIN)*(newHeartbeatInterval-MQTTheartbeatMAX)){ 195 | heartbeatInterval = newHeartbeatInterval; 196 | Serial.print("Heartbeat Interval Set To :"); 197 | Serial.print(newHeartbeatInterval); 198 | Serial.println(" via MQTT"); 199 | client.publish(heartbeatIntervalPub,messageTemp.c_str()); 200 | } 201 | else{ 202 | Serial.print("New Heartbeat Interval Must be > "); 203 | Serial.println(MQTTheartbeatMIN); 204 | Serial.print("Heartbeat Interval Remains: "); 205 | Serial.println(heartbeatInterval); 206 | } 207 | } 208 | 209 | 210 | // Motion Lockout Time 211 | if (String(topic) == MQTTmotionlockout){ 212 | unsigned long newLockoutTime = strtoul(messageTemp.c_str(),NULL,0); 213 | Serial.print("Received new lockout: "); 214 | Serial.println(newLockoutTime); 215 | if ((newLockoutTime-MQTTmotionlockoutMIN)*(newLockoutTime-MQTTmotionlockoutMAX)){ 216 | motionLockoutTime = newLockoutTime; 217 | Serial.print("Motion Lockout Time Set To :"); 218 | Serial.print(newLockoutTime); 219 | Serial.println(" via MQTT"); 220 | client.publish(lockoutTimePub,messageTemp.c_str()); 221 | } 222 | else{ 223 | Serial.print("New Lockout Time Must be > "); 224 | Serial.println(MQTTmotionlockoutMIN); 225 | Serial.print("Lockout Time Remains: "); 226 | Serial.println(motionLockoutTime); 227 | } 228 | } 229 | 230 | // Motion Detection Enable/Disable 231 | if (String(topic) == MQTTmotionenable){ 232 | if (messageTemp == "enable"){ 233 | if (motionEnabled == false){ 234 | motionEnabled = true; 235 | Serial.println("Motion Sensor Enabled"); 236 | client.publish(MQTTmotionenable, "Enabled"); 237 | } 238 | } 239 | if (messageTemp == "disable"){ 240 | if (motionEnabled == true){ 241 | motionEnabled = false; 242 | Serial.println("MotionSensor Disabled"); 243 | client.publish(MQTTmotionenable, "Disabled"); 244 | } 245 | } 246 | } 247 | 248 | 249 | } 250 | 251 | void reconnect() { 252 | // Loop until we're reconnected 253 | while (!client.connected()) { 254 | Serial.print("Attempting MQTT connection..."); 255 | // Attempt to connect 256 | if (client.connect("ESP8266Client")) { 257 | Serial.println("connected"); 258 | // Subscribe 259 | client.subscribe(subtopic); 260 | } else { 261 | Serial.print("failed, rc="); 262 | Serial.print(client.state()); 263 | Serial.println(" try again in 5 seconds"); 264 | // Wait 5 seconds before retrying 265 | delay(5000); 266 | } 267 | } 268 | } 269 | 270 | void detect_motion(){ 271 | if (camera.capture()){ 272 | // perform motion detection on resized image for fast detection 273 | camera.image.resize(); 274 | //camera.image.printAsJsonTo(Serial); 275 | detector.update(camera.image); 276 | 277 | // if motion is detected, print coordinates to serial in JSON format 278 | if (detector.isMotionDetected()) { 279 | detection = true; 280 | } 281 | else{ 282 | detection = false; 283 | } 284 | // release memory 285 | camera.free(); 286 | } 287 | else { 288 | delay(1000); 289 | detect_motion(); 290 | } 291 | } 292 | void loop() { 293 | if (!client.connected()) { 294 | reconnect(); 295 | } 296 | client.loop(); // Listen to MQTT Broker 297 | 298 | // Get current execution time 299 | unsigned long currentTime = millis(); // Used for HEARTBEAT and MOTION DETECTION 300 | 301 | // BEGIN HEARTBEAT 302 | if (initialHeartbeat){ 303 | unsigned long timeoutHeartbeat = lastHeartbeat + heartbeatInterval; 304 | if (currentTime > timeoutHeartbeat){ 305 | Serial.print("Heartbeat: "); 306 | Serial.println(currentTime); 307 | client.publish(onlinetopic,"online"); 308 | lastHeartbeat = millis(); 309 | } 310 | } 311 | if (!initialHeartbeat){ 312 | Serial.print("Heartbeat: "); 313 | Serial.println(currentTime); 314 | client.publish(onlinetopic,"online"); 315 | lastHeartbeat = millis(); 316 | initialHeartbeat = true; 317 | } 318 | // END HEARTBEAT 319 | 320 | 321 | // BEGIN MOTION DETECTION 322 | if (motionEnabled){ 323 | if (motionEvent){ 324 | unsigned long timeout = detectionStartTime + motionLockoutTime; 325 | if (currentTime > timeout){ 326 | detect_motion(); 327 | if (!detection){ 328 | Serial.println("Motion Detection Event End"); 329 | client.publish(motiontopic, "off"); // Publish motiontopic "off" 330 | digitalWrite(LEDPIN, LOW); // Turn off LED 331 | motionEvent = false; 332 | } 333 | } 334 | } 335 | else{ 336 | detect_motion(); 337 | if (detection){ 338 | Serial.println("Motion Detection Event Begin"); 339 | client.publish(motiontopic, "on"); // Publish motiontopic "on" 340 | digitalWrite(LEDPIN, HIGH); // Turn on LED 341 | detectionStartTime = millis(); 342 | motionEvent = true; 343 | } 344 | } 345 | } 346 | // END MOTION DETECTION 347 | } 348 | ``` 349 | 350 | -------------------------------------------------------------------------------- /esp32-cam-motion-detector.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | /** 8 | * ESP32 Camera as Motion Detector 9 | * Based on https://eloquentarduino.com/projects/esp32-arduino-motion-detection 10 | * Tested using ESP32 wrover dev board w/ qqvga camera only 11 | * 12 | * In Arduino IDE you need the following extra libraries installed: 13 | * - EloquantArduino 14 | * - PubSubClient 15 | */ 16 | 17 | // REQUIRED SETTINGS: 18 | 19 | // Include WiFi and MQTT PubSub 20 | #include 21 | #include 22 | 23 | // WiFi Setup *REQUIRED* 24 | const char* ssid = "YOUR_SSID"; 25 | const char* password = "YOUR_PASSWORD"; 26 | 27 | // MQTT Broker Setup *REQUIRED* 28 | const char* mqtt_server = "YOUR_MQTT_BROKER"; // MQTT Broker IP/URL 29 | const int mqtt_port = 1883; // MQTT Broker Port 30 | const char* uniqueID = "OfficeMultiSensor"; // Unique Client Name 31 | 32 | // Home Assistant Setup 33 | // State/Attrib Topics 34 | const char* haStateTopic = "homeassistant/sensor/office_multi_sensor/state"; 35 | const char* haAttribTopic = "homeassistant/sensor/office_multi_sensor/state"; 36 | //Motion Sensor 37 | const char* haMotionConfigTopic = "homeassistant/binary_sensor/office_motion_sensor/config"; 38 | const char* haMotionName = "Office Motion Sensor"; 39 | // Temp Sensor 40 | const char* haTempStateTopic = "homeassistant/sensor/office_multi_sensor/state"; 41 | const char* haTempConfigTopic = "homeassistant/sensor/office_temp_sensor/config"; 42 | const char* haTempName = "Office Temperature Sensor"; 43 | // RH Sensor 44 | const char* haRHStateTopic = "homeassistant/sensor/office_multi_sensor/state"; 45 | const char* haRHConfigTopic = "homeassistant/sensor/office_humidity_sensor/config"; 46 | const char* haRHName = "Office Humidity Sensor"; 47 | 48 | //MQTT Topics to trigger action 49 | const char* MQTTledEnable = "motion/office/settings/ledEnable"; // enable/disable LED 50 | const char* MQTTheartbeat = "motion/office/settings/heartbeat"; // set heartbeat interval 51 | const char* MQTTmotionenable = "motion/office/settings/enable"; // "enable" or "disable" motion detection 52 | const char* MQTTmotionlockout = "motion/office/settings/lockout"; // time in ms to keep motion triggered 53 | const char* MQTTstatus = "motion/office/settings/status"; // JSON status message 54 | 55 | // Support Variables 56 | const int MQTTheartbeatMIN = 59999; // 1 minute 57 | const int MQTTheartbeatMAX = 300001; // 5 minutes 58 | const int MQTTmotionlockoutMIN = 999; 59 | const int MQTTmotionlockoutMAX = 60001; 60 | 61 | // MQTT Sub Topics 62 | const char* subtopic = "motion/office/settings/+"; 63 | 64 | // LED Setup 65 | #define LEDPIN 12 66 | 67 | //DHT Setup 68 | #define DHT_PIN 13 69 | #define DHT_TYPE DHT11 70 | bool tempEnabled = true; 71 | bool humidityEnabled = true; 72 | bool reportTempF = true; 73 | unsigned long dhtWait = 60000; // in miliseconds 74 | 75 | 76 | // NO NEED TO EDIT BELOW THIS LINE UNLESS YOU WANT TO CHANGE THINGS 77 | 78 | 79 | #include "eloquent.h" 80 | #include "eloquent/vision/motion/naive.h" 81 | #include "DHT.h" 82 | 83 | 84 | // uncomment based on your camera and resolution 85 | 86 | //#include "eloquent/vision/camera/ov767x/gray/vga.h" 87 | //#include "eloquent/vision/camera/ov767x/gray/qvga.h" 88 | //#include "eloquent/vision/camera/ov767x/gray/qqvga.h" 89 | //#include "eloquent/vision/camera/esp32/aithinker/gray/vga.h" 90 | //#include "eloquent/vision/camera/esp32/aithinker/gray/qvga.h" 91 | //#include "eloquent/vision/camera/esp32/aithinker/gray/qqvga.h" 92 | //#include "eloquent/vision/camera/esp32/wrover/gray/vga.h" 93 | //#include "eloquent/vision/camera/esp32/wrover/gray/qvga.h" 94 | #include "eloquent/vision/camera/esp32/wrover/gray/qqvga.h" 95 | //#include "eloquent/vision/camera/esp32/eye/gray/vga.h" 96 | //#include "eloquent/vision/camera/esp32/eye/gray/qvga.h" 97 | //#include "eloquent/vision/camera/esp32/eye/gray/qqvga.h" 98 | //#include "eloquent/vision/camera/esp32/m5/gray/vga.h" 99 | //#include "eloquent/vision/camera/esp32/m5/gray/qvga.h" 100 | //#include "eloquent/vision/camera/esp32/m5/gray/qqvga.h" 101 | //#include "eloquent/vision/camera/esp32/m5wide/gray/vga.h" 102 | //#include "eloquent/vision/camera/esp32/m5wide/gray/qvga.h" 103 | //#include "eloquent/vision/camera/esp32/m5wide/gray/qqvga.h" 104 | 105 | 106 | 107 | // Setup Detection Variables 108 | bool ledEnable = true; 109 | bool motionEnabled = true; 110 | bool detection = false; 111 | bool motionEvent = false; 112 | unsigned long motionLockoutTime = 5000; 113 | unsigned long detectionStartTime = millis(); 114 | unsigned long lastHeartbeat = millis(); 115 | bool initialHeartbeat = false; 116 | unsigned long heartbeatInterval = 60000; // Heartbeat interval in ms (60000 = 60 seconds) 117 | 118 | // Setup DHT 119 | DHT dht(DHT_PIN, DHT_TYPE); 120 | unsigned long dhtLastRead = millis(); 121 | float dhtF = 0; 122 | float dhtT = 0; 123 | float dhtH = 0; 124 | float dhtHIF = 0; 125 | float dhtHIC = 0; 126 | 127 | // Setup Debugging 128 | bool debug = false; 129 | 130 | WiFiClient espClient; 131 | PubSubClient client(espClient); 132 | long lastMsg = 0; 133 | char msg[50]; 134 | int value = 0; 135 | 136 | #define THUMB_WIDTH 32 137 | #define THUMB_HEIGHT 24 138 | 139 | 140 | Eloquent::Vision::Motion::Naive detector; 141 | 142 | void setup() { 143 | dht.begin(); 144 | pinMode(LEDPIN, OUTPUT); 145 | digitalWrite(LEDPIN, LOW); 146 | delay(4000); 147 | Serial.begin(115200); 148 | 149 | // turn on high freq for fast streaming speed 150 | camera.setHighFreq(); 151 | 152 | if (!camera.begin()) 153 | eloquent::abort(Serial, "Camera init error"); 154 | if (debug){ 155 | Serial.println("Camera init OK"); 156 | } 157 | 158 | // wait for at least 10 frames to be processed before starting to detect 159 | // motion (false triggers at start) 160 | // then, when motion is detected, don't trigger for the next 10 frames 161 | detector.startSinceFrameNumber(10); 162 | detector.debounceMotionTriggerEvery(10); 163 | 164 | // or, in one call 165 | detector.throttle(10); 166 | 167 | // trigger motion when at least 10% of pixels change intensity by 168 | // at least 15 out of 255 169 | detector.setPixelChangesThreshold(0.1); 170 | detector.setIntensityChangeThreshold(15); 171 | setup_wifi(); 172 | client.setServer(mqtt_server, 1883); 173 | client.setCallback(callback); 174 | } 175 | 176 | void setup_wifi() { 177 | delay(10); 178 | // We start by connecting to a WiFi network 179 | Serial.println(); 180 | Serial.print("Connecting to "); 181 | Serial.println(ssid); 182 | 183 | WiFi.begin(ssid, password); 184 | 185 | while (WiFi.status() != WL_CONNECTED) { 186 | delay(500); 187 | Serial.print("."); 188 | } 189 | 190 | Serial.println(""); 191 | Serial.println("WiFi connected"); 192 | Serial.println("IP address: "); 193 | Serial.println(WiFi.localIP()); 194 | } 195 | const char* True = "true"; 196 | const char* False = "false"; 197 | 198 | void publishHATempConfig(){ 199 | /* 200 | * Publish sensor status to MQTT 201 | */ 202 | // Create Buffer 203 | char buffer[256]; // Used for all publish commands 204 | 205 | // Temp Sensor 206 | // Build JSON 207 | StaticJsonDocument<1000> haTemp; 208 | 209 | // Add Values 210 | haTemp["device_class"] = "temperature"; 211 | haTemp["name"] = haTempName; 212 | haTemp["state_topic"] = haStateTopic; 213 | haTemp["value_template"] = "{{value_json.temperature}}"; 214 | haTemp["json_attributes_topic"] = haAttribTopic; 215 | 216 | // Publish haTemp Config 217 | serializeJsonPretty(haTemp, buffer); 218 | client.publish(haTempConfigTopic,buffer,true); 219 | } 220 | void publishHARHConfig(){ 221 | /* 222 | * Publish sensor status to MQTT 223 | */ 224 | // Create Buffer 225 | char buffer[1000]; // Used for all publish commands 226 | 227 | // Humidity Sensor 228 | // Build JSON 229 | StaticJsonDocument<1000> haRH; 230 | 231 | // Add Values 232 | haRH["device_class"] = "humidity"; 233 | haRH["name"] = haRHName; 234 | haRH["state_topic"] = haStateTopic; 235 | haRH["value_template"] = "{{value_json.humidity}}"; 236 | haRH["json_attributes_topic"] = haAttribTopic; 237 | 238 | // Publish haRH Config 239 | serializeJsonPretty(haRH, buffer); 240 | client.publish(haRHConfigTopic,buffer,true); 241 | } 242 | 243 | void publishHAMotionConfig(){ 244 | /* 245 | * Publish sensor status to MQTT 246 | */ 247 | // Create Buffer 248 | char buffer[1000]; // Used for all publish commands 249 | 250 | 251 | // Motion Sensor 252 | // Build JSON 253 | StaticJsonDocument<1000> haMotion; 254 | 255 | // Add Values 256 | haMotion["device_class"] = "motion"; 257 | haMotion["name"] = haMotionName; 258 | haMotion["state_topic"] = haStateTopic; 259 | haMotion["value_template"] = "{{value_json.motion}}"; 260 | haMotion["json_attributes_topic"] = haAttribTopic; 261 | 262 | //Publish haMotion Config 263 | serializeJsonPretty(haMotion, buffer); 264 | client.publish(haMotionConfigTopic,buffer,true); 265 | } 266 | 267 | 268 | void publishState(){ 269 | /* 270 | * Publish sensor status to MQTT 271 | */ 272 | Serial.println("Executing publishState()"); 273 | // Build JSON 274 | // Allocate memory for the document 275 | StaticJsonDocument<1000> haState; 276 | 277 | // Add some values 278 | 279 | haState["heartbeat"] = lastHeartbeat; 280 | haState["heartbeatInterval"] = heartbeatInterval; 281 | haState["motionLockoutTime"] = motionLockoutTime; 282 | haState["ledEnable"] = ledEnable?"Enabled":"Disabled"; 283 | haState["motionEnabled"]= motionEnabled?"Enabled":"Disabled"; 284 | 285 | if (reportTempF){ 286 | haState["temperature"] = dhtF; 287 | haState["HeatIndex"] = dhtHIF; 288 | haState["units"] = "°F"; 289 | } 290 | else{ 291 | haState["temperatureCelsius"] = dhtT; 292 | haState["HeatIndexCelsius"] = dhtHIC; 293 | haState["units"] = "°C"; 294 | } 295 | haState["humidity"] = dhtH; 296 | haState["motion"] = detection?"ON":"OFF"; 297 | 298 | // Create Buffer 299 | Serial.println("creating buffer"); 300 | char buffer[1000]; 301 | Serial.println("buffer created"); 302 | serializeJsonPretty(haState, buffer); 303 | Serial.print("Publish State: "); 304 | Serial.println(client.publish(haStateTopic,buffer)?"OK":"Error"); 305 | Serial.println(buffer); 306 | } 307 | 308 | void callback(char* topic, byte* message, unsigned int length) { 309 | if (debug){ 310 | Serial.print("Message arrived on topic: "); 311 | Serial.print(topic); 312 | Serial.print(". Message: "); 313 | } 314 | String messageTemp; 315 | 316 | for (int i = 0; i < length; i++) { 317 | if (debug){ 318 | Serial.print((char)message[i]); 319 | } 320 | messageTemp += (char)message[i]; 321 | } 322 | if (debug){ 323 | Serial.println(); 324 | } 325 | 326 | // MQTT Actions 327 | 328 | // LED Enable/Disable 329 | if (String(topic) == MQTTledEnable){ 330 | if (messageTemp == "true"){ 331 | if (ledEnable == false){ 332 | if (debug){ 333 | Serial.println("LED disabled"); 334 | } 335 | ledEnable = true; 336 | } 337 | if (messageTemp == "false"){ 338 | if (ledEnable == true){ 339 | ledEnable = false; 340 | } 341 | } 342 | } 343 | } 344 | 345 | 346 | // Heartbeat Interval 347 | if (String(topic) == MQTTheartbeat){ 348 | unsigned long newHeartbeatInterval = strtoul(messageTemp.c_str(),NULL,0); 349 | if (debug){ 350 | Serial.print("Received new Heartbeat Interval: "); 351 | Serial.println(newHeartbeatInterval); 352 | } 353 | if ((newHeartbeatInterval-MQTTheartbeatMIN)*(newHeartbeatInterval-MQTTheartbeatMAX)){ 354 | heartbeatInterval = newHeartbeatInterval; 355 | if (debug){ 356 | Serial.print("Heartbeat Interval Set To :"); 357 | Serial.print(newHeartbeatInterval); 358 | Serial.println(" via MQTT"); 359 | client.publish(heartbeatIntervalPub,messageTemp.c_str()); 360 | } 361 | } 362 | else{ 363 | if (debug){ 364 | Serial.print("New Heartbeat Interval Must be > "); 365 | Serial.println(MQTTheartbeatMIN); 366 | Serial.print("Heartbeat Interval Remains: "); 367 | Serial.println(heartbeatInterval); 368 | } 369 | } 370 | } 371 | 372 | 373 | // Motion Lockout Time 374 | if (String(topic) == MQTTmotionlockout){ 375 | unsigned long newLockoutTime = strtoul(messageTemp.c_str(),NULL,0); 376 | if (debug){ 377 | Serial.print("Received new lockout: "); 378 | Serial.println(newLockoutTime); 379 | } 380 | if ((newLockoutTime-MQTTmotionlockoutMIN)*(newLockoutTime-MQTTmotionlockoutMAX)){ 381 | motionLockoutTime = newLockoutTime; 382 | if (debug){ 383 | Serial.print("Motion Lockout Time Set To :"); 384 | Serial.print(newLockoutTime); 385 | Serial.println(" via MQTT"); 386 | client.publish(lockoutTimePub,messageTemp.c_str()); 387 | } 388 | } 389 | else{ 390 | if (debug){ 391 | Serial.print("New Lockout Time Must be > "); 392 | Serial.println(MQTTmotionlockoutMIN); 393 | Serial.print("Lockout Time Remains: "); 394 | Serial.println(motionLockoutTime); 395 | } 396 | } 397 | } 398 | 399 | // Motion Detection Enable/Disable 400 | if (String(topic) == MQTTmotionenable){ 401 | if (messageTemp == "enable"){ 402 | if (motionEnabled == false){ 403 | motionEnabled = true; 404 | if (debug){ 405 | Serial.println("Motion Sensor Enabled"); 406 | client.publish(MQTTmotionenable, "Enabled"); 407 | } 408 | } 409 | } 410 | if (messageTemp == "disable"){ 411 | if (motionEnabled == true){ 412 | motionEnabled = false; 413 | if (debug){ 414 | Serial.println("MotionSensor Disabled"); 415 | client.publish(MQTTmotionenable, "Disabled"); 416 | } 417 | } 418 | } 419 | } 420 | } 421 | 422 | void reconnect() { 423 | // Loop until we're reconnected 424 | while (!client.connected()) { 425 | Serial.print("Attempting MQTT connection..."); 426 | // Attempt to connect 427 | if (client.connect(uniqueID)) { 428 | client.setBufferSize(1024); 429 | Serial.print ("connected as"); 430 | Serial.println(uniqueID); 431 | // Subscribe 432 | client.subscribe(subtopic); 433 | Serial.print("Subscribed to "); 434 | Serial.println(subtopic); 435 | } else { 436 | Serial.print("failed, rc="); 437 | Serial.print(client.state()); 438 | Serial.println(" try again in 5 seconds"); 439 | // Wait 5 seconds before retrying 440 | delay(5000); 441 | } 442 | } 443 | } 444 | 445 | void detect_motion(){ 446 | if (camera.capture()){ 447 | // perform motion detection on resized image for fast detection 448 | camera.image.resize(); 449 | //camera.image.printAsJsonTo(Serial); 450 | detector.update(camera.image); 451 | 452 | // if motion is detected, print coordinates to serial in JSON format 453 | if (detector.isMotionDetected()) { 454 | detection = true; 455 | } 456 | else{ 457 | detection = false; 458 | } 459 | // release memory 460 | camera.free(); 461 | } 462 | else { 463 | delay(1000); 464 | detect_motion(); 465 | } 466 | } 467 | 468 | int readDHT(){ 469 | if (debug){ 470 | Serial.println(); 471 | Serial.print(millis()); 472 | Serial.println(": Reading DHT11"); 473 | } 474 | dhtH = dht.readHumidity(); 475 | dhtT = dht.readTemperature(); 476 | dhtF = dht.readTemperature(true); 477 | if (isnan(dhtH) || isnan(dhtT) || isnan(dhtF)){ 478 | Serial.print("ERROR: failed to read DHT11 on Pin #"); 479 | Serial.println(DHT_PIN); 480 | } 481 | else{ 482 | dhtLastRead = millis(); 483 | dhtHIF = dht.computeHeatIndex(dhtF,dhtH); 484 | dhtHIC = dht.computeHeatIndex(dhtT,dhtH); 485 | if (debug){ 486 | Serial.println("DHT Sensor Readings:"); 487 | Serial.print("Humidity: "); 488 | Serial.print(dhtH); 489 | Serial.println("%RH"); 490 | Serial.print("Temp: "); 491 | Serial.print(dhtF); 492 | Serial.print("°F "); 493 | Serial.print(dhtT); 494 | Serial.println("°C"); 495 | Serial.print("Heat Index: "); 496 | Serial.print(dhtHIF); 497 | Serial.print("°F "); 498 | Serial.print(dhtHIC); 499 | Serial.print("°C"); 500 | } 501 | return 0; 502 | } 503 | } 504 | 505 | 506 | void loop() { 507 | if (!client.connected()) { 508 | reconnect(); 509 | } 510 | client.loop(); // Listen to MQTT Broker 511 | 512 | // Get current execution time 513 | unsigned long currentTime = millis(); // Used for HEARTBEAT and MOTION DETECTION 514 | 515 | // BEGIN HEARTBEAT 516 | if (!initialHeartbeat){ 517 | // This allows us to do initial setup of the sensor upon the first heartbeat... 518 | // Publish Home Assistant MQTT Config 519 | } 520 | else{ 521 | // Send First Heartbeat 522 | unsigned long timeoutHeartbeat = lastHeartbeat + heartbeatInterval; 523 | 524 | if (currentTime > timeoutHeartbeat){ 525 | if (debug){ 526 | Serial.print("Heartbeat: "); 527 | Serial.println(currentTime); 528 | } 529 | publishState(); 530 | lastHeartbeat = millis(); 531 | } 532 | } 533 | if (!initialHeartbeat){ 534 | if (debug){ 535 | Serial.print("Heartbeat: "); 536 | Serial.println(currentTime); 537 | } 538 | 539 | // Publish Home Assistant Configs 540 | publishHATempConfig(); 541 | publishHARHConfig(); 542 | publishHAMotionConfig(); 543 | 544 | // Publish Initial State 545 | publishState(); 546 | 547 | 548 | lastHeartbeat = millis(); 549 | initialHeartbeat = true; 550 | } 551 | // END HEARTBEAT 552 | 553 | // BEGIN MOTION DETECTION 554 | if (motionEnabled){ 555 | if (motionEvent){ 556 | unsigned long timeout = detectionStartTime + motionLockoutTime; 557 | if (currentTime > timeout){ 558 | detect_motion(); 559 | if (!detection){ 560 | publishState(); 561 | digitalWrite(LEDPIN, LOW); // Turn off LED 562 | motionEvent = false; 563 | } 564 | } 565 | } 566 | else{ 567 | detect_motion(); 568 | if (detection){ 569 | publishState(); 570 | digitalWrite(LEDPIN, HIGH); // Turn on LED 571 | detectionStartTime = millis(); 572 | motionEvent = true; 573 | } 574 | } 575 | } 576 | // END MOTION DETECTION 577 | 578 | // BEGIN DHT 579 | if (tempEnabled || humidityEnabled){ 580 | unsigned long dhtTimeout = dhtLastRead + dhtWait; 581 | if (currentTime >= dhtTimeout || dhtLastRead == 0){ 582 | readDHT(); 583 | } 584 | } 585 | // END DHT 586 | } 587 | --------------------------------------------------------------------------------