This shows you the differences between two versions of the page.
iothings:proiecte:2025sric:security-system-to_detect-movement-and-capture-image [2025/05/29 09:43] felicia.saghin [Software] |
iothings:proiecte:2025sric:security-system-to_detect-movement-and-capture-image [2025/05/29 11:19] (current) felicia.saghin [Introduction] |
||
---|---|---|---|
Line 7: | Line 7: | ||
====== Introduction ====== | ====== Introduction ====== | ||
- | In today’s connected world, real-time security monitoring at home or in small offices has become both practical and affordable. This project delivers a lightweight IoT door-security solution by combining an ESP32-CAM module with a Hall-effect sensor. Whenever the door opens, the sensor immediately signals the ESP32-CAM to capture a high-resolution image. That image is then uploaded to Firebase Storage, and its publicly accessible URL is written to Firebase Realtime Database. Simultaneously, Twilio’s API is invoked to send an SMS to your smartphone. Finally, a web gallery hosted on Firebase displays the latest snapshot instantly, giving you secure and remote visibility of every entry event. | + | In today’s connected world, real-time security monitoring at home or in small offices is both practical and affordable. This project delivers a lightweight IoT door-security solution by combining an ESP32-CAM module with a Hall-effect sensor. Whenever the door opens, the sensor immediately signals the ESP32-CAM to capture a high-resolution JPEG, which is then Base64-encoded and stored directly in Firebase Realtime Database. Simultaneously, Twilio’s API sends an SMS alert with a link to your live web gallery. A responsive dashboard hosted on Firebase Hosting listens for new entries in the database and displays each snapshot instantly, giving you secure, remote visibility of every entry event. |
====== Context ====== | ====== Context ====== | ||
__**Diagram**__ | __**Diagram**__ | ||
Line 62: | Line 62: | ||
====== Software Design ====== | ====== Software Design ====== | ||
- | <note tip>I used **VS Code** with the **PlatformIO IDE** extension. | + | I used **VS Code** with the **PlatformIO IDE** extension. |
- | I’ve built the core logic so that each door-open triggers exactly one clean sequence: | + | |
- | * A debounce routine on the Hall sensor waits for the input to remain stable (no rapid bouncing) for ~50 ms before recognizing a genuine open event. | + | ====== Key Project Features ====== |
- | * I track the previous sensor state to detect a rising edge (closed→open) and ignore all other transitions. | + | - **Firebase Realtime Database** |
+ | Instant data synchronization between the ESP32-CAM device and the web dashboard. | ||
- | * On that single edge, the ESP32-CAM captures a JPEG, then immediately: | + | - **Image Processing** |
+ | Automated JPEG capture, Base64 encoding, and seamless cloud upload. | ||
- | * * Uploads it to Firebase Storage over HTTPS. | + | - **SMS Notifications (Twilio)** |
+ | Instant SMS alerts via Twilio API with direct gallery links. | ||
- | * * Parses the JSON response to extract the download URL. | + | - **Responsive Web Dashboard** |
+ | Mobile-friendly interface for real-time image monitoring and history review. | ||
- | * * Writes that into Firebase Realtime Database. | + | - **Hall Sensor Integration** |
+ | Magnetic door-open detection with software debouncing to ensure one capture per event. | ||
- | * * Calls Twilio’s REST API (over secure TLS) to send an SMS with the gallery link. | + | - **PSRAM Optimization** |
+ | Advanced PSRAM configuration on ESP32-CAM for reliable image buffering and processing. | ||
- | After each step I check for errors and retry once if something fails (network hiccup, JSON parse error, etc.). | + | - **Modern Web Technologies** |
+ | Built with HTML5, CSS3, JavaScript ES6+, Firebase Hosting, and fetch API for a smooth user experience. | ||
- | Finally, I release the camera buffer and reset state, so the loop stays non-blocking (using millis() for timing) and immediately resumes listening for the next door-open. | ||
- | All of this makes the system rock-solid: one photo, one upload, one DB update, one SMS—every single time. | ||
+ | === Code Snippets === | ||
+ | <code> | ||
+ | bool uploadImageToFirebase(camera_fb_t* fb, const char* source) { | ||
+ | if (!fb || !fb->buf || fb->len == 0) { | ||
+ | return false; | ||
+ | } | ||
+ | | ||
+ | // Convert to base64 encoding | ||
+ | size_t required_len = ((fb->len + 2) / 3) * 4 + 1; | ||
+ | char* base64_buffer = (char*)malloc(required_len); | ||
+ | | ||
+ | size_t olen = 0; | ||
+ | int ret = mbedtls_base64_encode((unsigned char*)base64_buffer, | ||
+ | required_len, &olen, fb->buf, fb->len); | ||
+ | | ||
+ | // Create JSON payload and upload to Firebase | ||
+ | FirebaseJson imageData; | ||
+ | imageData.set("timestamp", millis()); | ||
+ | imageData.set("source", source); | ||
+ | imageData.set("size", (int)fb->len); | ||
+ | imageData.set("base64", String(base64_buffer)); | ||
+ | | ||
+ | String path = String("/images/") + String(millis()); | ||
+ | | ||
+ | if (Firebase.RTDB.setJSON(&fbdo, path.c_str(), &imageData)) { | ||
+ | // Send SMS notification via Twilio | ||
+ | String smsMessage = "📸 Intruder detected! Check: https://proiect-iot-feli.web.app/"; | ||
+ | sendTwilioSMS(smsMessage.c_str()); | ||
+ | return true; | ||
+ | } | ||
+ | | ||
+ | free(base64_buffer); | ||
+ | return false; | ||
+ | } | ||
+ | </code> | ||
+ | <code> | ||
+ | #define HALL_SENSOR_PIN 12 | ||
+ | const unsigned long DEBOUNCE_DELAY = 500; // 500ms debounce | ||
+ | void checkHallSensor() { | ||
+ | static int lastState = HIGH; | ||
+ | static unsigned long lastTriggerTime = 0; | ||
+ | static bool initialized = false; | ||
+ | | ||
+ | // Initialize sensor state on first run | ||
+ | if (!initialized) { | ||
+ | lastState = digitalRead(HALL_SENSOR_PIN); | ||
+ | initialized = true; | ||
+ | return; | ||
+ | } | ||
+ | | ||
+ | int currentState = digitalRead(HALL_SENSOR_PIN); | ||
+ | unsigned long currentTime = millis(); | ||
+ | | ||
+ | // Check for state change with debouncing | ||
+ | if (currentState != lastState && | ||
+ | (currentTime - lastTriggerTime) > DEBOUNCE_DELAY) { | ||
+ | | ||
+ | if (currentState == LOW) { | ||
+ | Serial.println("🧲 Magnet detected - capturing photo"); | ||
+ | } else { | ||
+ | Serial.println("🧲 Magnet removed - capturing photo"); | ||
+ | } | ||
+ | | ||
+ | capturePhotoFromSensor(); | ||
+ | lastTriggerTime = currentTime; | ||
+ | lastState = currentState; | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | <code> | ||
+ | const firebaseConfig = { | ||
+ | apiKey: "AIzaSyB_GqKEayiItvWUcP9b0PLF8xKqtqJBjXM", | ||
+ | authDomain: "proiect-iot-feli.firebaseapp.com", | ||
+ | databaseURL: "https://proiect-iot-feli-default-rtdb.europe-west1.firebasedatabase.app", | ||
+ | projectId: "proiect-iot-feli" | ||
+ | }; | ||
+ | function initializeFirebase() { | ||
+ | firebase.initializeApp(firebaseConfig); | ||
+ | database = firebase.database(); | ||
+ | imagesRef = database.ref('images'); | ||
+ | | ||
+ | // Set up real-time listener for new images | ||
+ | setupRealtimeListener(); | ||
+ | loadImages(); | ||
+ | } | ||
+ | function setupRealtimeListener() { | ||
+ | imagesRef.on('child_added', function(snapshot) { | ||
+ | const imageData = snapshot.val(); | ||
+ | displayImage(snapshot.key, imageData); | ||
+ | updateStatus(`📸 New image detected at ${new Date().toLocaleTimeString()}`, 'new-image'); | ||
+ | }); | ||
+ | } | ||
+ | </code> | ||
+ | <code> | ||
+ | bool sendTwilioSMS(const char* message) { | ||
+ | if (!twilio) { | ||
+ | Serial.println("❌ Twilio client not initialized"); | ||
+ | return false; | ||
+ | } | ||
+ | | ||
+ | Serial.println("📱 Sending SMS notification..."); | ||
+ | String response; | ||
+ | bool result = twilio->send_message(TO_PHONE_NUMBER, TWILIO_PHONE_NUMBER, message, response); | ||
+ | | ||
+ | if (result) { | ||
+ | Serial.println("✅ SMS sent successfully"); | ||
+ | Serial.println("SMS Response:"); | ||
+ | Serial.println(response); | ||
+ | } else { | ||
+ | Serial.println("❌ SMS failed to send"); | ||
+ | Serial.printf("Error: %s\n", response.c_str()); | ||
+ | } | ||
+ | | ||
+ | return result; | ||
+ | } | ||
+ | </code> | ||
+ | ==== Firebase ==== | ||
- | === Code Snippets === | + | {{ :iothings:proiecte:2025sric:firebase_feli.jpeg?600 }} |
+ | |||
+ | The entire application is deployed on **Firebase Hosting** and uses **Firebase Realtime Database** to coordinate data between the ESP32-CAM and the web dashboard. The database contains: | ||
+ | |||
+ | * **images/** | ||
+ | - A collection of timestamped entries, each holding the Base64-encoded JPEG and metadata (size, source). | ||
+ | * **latestImage** | ||
+ | - Stores the key of the most recent images/{timestamp} entry for quick access (optional). | ||
+ | * **notificationsEnabled** | ||
+ | - A Boolean flag to enable or disable SMS alerts on the fly. | ||
+ | |||
+ | All communication flows through the Realtime Database: | ||
+ | |||
+ | * **ESP32-CAM** writes a new child under /images on every door-open event. | ||
+ | * **Web dashboard** listens for child_added and updates the UI in real time. | ||
+ | * **Toggles** to /notificationsEnabled can be flipped by the user to mute alerts without redeploy. | ||
+ | |||
+ | {{ :iothings:proiecte:2025sric:firebase2_feli.jpg?600 }} | ||
====== Challenges ====== | ====== Challenges ====== | ||
+ | |||
+ | One of the first hurdles I encountered was the ESP32-CAM’s limited onboard RAM. Capturing high-resolution JPEGs and then encoding them into Base64 demanded more memory than the 520 KB of SRAM could comfortably provide. To overcome this, I enabled the module’s external PSRAM and carefully managed my buffer allocations so that large image data would reside in PSRAM rather than exhausting the internal memory. | ||
+ | |||
+ | Another issue came up when trying to upload these large images all at once: memory would sometimes fragment, or the HTTP requests would time out, leading to failed uploads. I solved this by breaking the Base64 encoding into smaller, manageable chunks, freeing each buffer immediately after use, and adding a simple retry mechanism for any failed network calls. This approach dramatically reduced memory spikes and improved upload reliability. | ||
+ | |||
+ | Finally, my Hall-effect sensor occasionally produced multiple false “door open” triggers due to electrical noise or rapid swings near the magnet. Instead of acting on every glitch, I implemented a 500 ms software debounce in my sensor-reading routine. That way, once the sensor changes state, I wait half a second to ensure it’s stable before capturing a photo—guaranteeing exactly one image per genuine door-open event. | ||
====== References ====== | ====== References ====== | ||
+ | |||
+ | https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-wrover-kit.html | ||
+ | |||
+ | https://github.com/mobizt/Firebase-ESP-Client | ||
+ | |||
+ | https://github.com/ademuri/twilio-esp32-client | ||
+ | |||
+ | https://firebase.google.com/docs/database | ||
+ | |||
+ | https://docs.platformio.org/en/latest/ | ||
+ | |||
+ | https://github.com/espressif/esp32-camera | ||
+ | |||
+ | https://www.twilio.com/docs/sms/api | ||
+ | |||
+ | https://firebase.google.com/docs/web/setup | ||
+ | |||
+ | https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/external-ram.html | ||
+ | |||
+ | https://randomnerdtutorials.com/esp32-cam-pinout-gpios/ | ||