Rakenna Älykäs Valvontakamera ESP32-CAM:lla, RTC:llä ja PIR-anturilla: Täydellinen Opas
Yhdistä Freenove ESP32-CAM, DS3231 ja HC-SR501 automaattiseen kuvien lähetykseen Telegramiin liikkeestä tai ajastetusti.
Tämä opas näyttää, kuinka rakennat tehokkaan ja älykkään valvontajärjestelmän käyttämällä Freenove ESP32-CAM -kehitysalustaa, DS3231-reaaliaikakelloa (RTC) tarkan ajanottoon ja HC-SR501 passiivista infrapuna (PIR) -liiketunnistinta. Järjestelmä hyödyntää ESP32:n syväunitoimintoa (deep sleep) virransäästöön ja herää ottamaan kuvia joko havaitessaan liikettä tai päivittäisen RTC-hälytyksen perusteella. Kuvat lähetetään automaattisesti Telegram-tilillesi.
Projektin kohokohdat
- Energiatehokas toiminta: Hyödyntää ESP32:n syväunitoimintoa minimoidakseen virrankulutuksen lepotilassa.
- Kaksi herätystapaa: Laite herää joko HC-SR501 PIR-anturin havaitsemasta liikkeestä tai DS3231 RTC:n asettamasta päivittäisestä hälytyksestä (klo 12:00).
- Automaattinen kuvien lähetys: Ottaa ja lähettää kuvia Telegramiin herätystavan mukaan, optimoiden lähetettävän datan määrän.
Vaadittavat komponentit ja kytkentä
Komponenttilista
- Freenove ESP32-CAM (tai vastaava ESP32-CAM -levy Wrover-moduulilla)
- DS3231 RTC -moduuli (varmista, että siinä on paristo aikaa varten)
- HC-SR501 PIR -liiketunnistin
- USB-kaapeli (ohjelmointiin ja virransyöttöön testauksen aikana)
- Hyppyjohtoja (Dupont-kaapeleita)
- Vakaa 5V virtalähde jatkuvaan käyttöön
Kytkentäohjeet
Huolellinen kytkentä on projektin onnistumisen kannalta välttämätöntä. Tarkista aina käyttämäsi ESP32-CAM -levyn pinnijärjestys dokumentaatiosta, sillä se voi hieman vaihdella malleittain. Alla on yleinen kytkentäohje:
Esimerkki ESP32-CAM:n ja PIR-anturin kytkennästä. Varmista pinnit omasta levystäsi.
DS3231 RTC -moduulin kytkentä (I2C)
- DS3231 VCC: Yhdistä ESP32-CAM:n 3.3V tai 5V -pinniin. DS3231 toimii 3.3V:lla, mutta jotkut moduulit vaativat 5V sisäisen regulaattorin ja pariston latauspiirin vuoksi. 3.3V on yleensä turvallisempi vaihtoehto, jos moduuli tukee sitä.
- DS3231 GND: Yhdistä ESP32-CAM:n GND -pinniin.
- DS3231 SDA: Yhdistä ESP32-CAM:n GPIO 21 (oletus I2C SDA) -pinniin.
- DS3231 SCL: Yhdistä ESP32-CAM:n GPIO 22 (oletus I2C SCL) -pinniin.
- DS3231 SQW: Yhdistä ESP32-CAM:n vapaaseen GPIO-pinniin, joka tukee ulkoista herätystä (EXT1). Esimerkiksi GPIO 2 tai GPIO 4 ovat mahdollisia. Tämä pinni laukaisee herätyksen, kun RTC:n hälytys aktivoituu (tyypillisesti aktiivinen matala).
HC-SR501 PIR -anturin kytkentä
- HC-SR501 VCC: Yhdistä ESP32-CAM:n 5V -pinniin. HC-SR501 toimii tyypillisesti 4.5V - 12V jännitteellä.
- HC-SR501 GND: Yhdistä ESP32-CAM:n GND -pinniin.
- HC-SR501 OUT: Yhdistä ESP32-CAM:n vapaaseen GPIO-pinniin, joka tukee ulkoista herätystä (EXT0). GPIO 13 on yleinen ja usein sopiva valinta tähän tarkoitukseen.
Kytkentätaulukko (Yhteenveto)
Tämä taulukko tiivistää suositellut kytkennät. Varmista pinnien yhteensopivuus oman ESP32-CAM-levysi kanssa.
| Komponentti | Pinni | ESP32-CAM Pinni | Huomioita |
|---|---|---|---|
| DS3231 RTC | VCC | 3.3V (tai 5V moduulista riippuen) | Virransyöttö RTC:lle |
| DS3231 RTC | GND | GND | Maadoitus |
| DS3231 RTC | SDA | GPIO 21 | I2C Data |
| DS3231 RTC | SCL | GPIO 22 | I2C Kello |
| DS3231 RTC | SQW | GPIO 2 (tai muu EXT1-yhteensopiva) | RTC Hälytyslähtö -> ESP32 Herätys |
| HC-SR501 PIR | VCC | 5V | Virransyöttö PIR:lle |
| HC-SR501 PIR | GND | GND | Maadoitus |
| HC-SR501 PIR | OUT | GPIO 13 (tai muu EXT0-yhteensopiva) | PIR Liiketunnistussignaali -> ESP32 Herätys |
Huomioitavaa kytkennässä:
- GPIO-konfliktit: Varmista, etteivät valitut GPIO-pinnit (erityisesti herätyspinnit 2, 4, 13) ole ristiriidassa kameran tai SD-kortin käyttämien pinnien kanssa. Freenove ESP32-CAM -dokumentaatio auttaa tässä.
- WiFi-häiriöt: ESP32:n WiFi voi aiheuttaa häiriöitä PIR-anturin toimintaan. Jos saat vääriä liikehälytyksiä, yritä sijoittaa PIR-anturi fyysisesti kauemmas ESP32-CAM:n antennista tai käytä ferriittihelmeä PIR-anturin johdoissa.
- RTC SQW -pinnin toiminta: Varmista, että DS3231:n SQW-pinni on konfiguroitu antamaan hälytyssignaali (ei oletus 1Hz neliöaaltoa).
RTClib.h-kirjastossa on funktioita tähän (writeSqwPinMode(DS3231_OFF)ja hälytyksen aktivointi).
Projektin logiikka ja toimintakulku
Alla oleva kaavio visualisoi järjestelmän toimintalogiikkaa alkaen ensimmäisestä käynnistyksestä aina syväuneen ja heräämisiin asti.
Tämä logiikka varmistaa, että laite kuluttaa mahdollisimman vähän virtaa odottaessaan herätystapahtumaa ja suorittaa määritellyt toiminnot herättyään.
C++ Koodi Arduino IDE:lle
Alla on C++ -koodi, joka toteuttaa kuvatun toiminnallisuuden. Muista asentaa tarvittavat kirjastot Arduino IDE:n kirjastonhallinnan kautta: RTClib by Adafruit, ja varmista että ESP32-levyjen tuki on asennettu ja oikea ESP32-CAM -malli (esim. "AI Thinker ESP32-CAM" tai vastaava Wrover-moduulilla) on valittu.
#include "esp_camera.h" #include "esp_timer.h" #include "esp_wifi.h" #include "esp_sleep.h" #include "driver/rtc_io.h" #include <WiFi.h> #include <Wire.h> #include <RTClib.h> // Käytetään pyydettyä RTClib.h -kirjastoa #include <HTTPClient.h> // Kuvien lähettämiseen Telegramiin #include <time.h> // NTP-aikasynkronointiin // <b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b>* // Määritä omat asetukset tähän // <b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b>* const char* ssid = "OMA_WIFI_SSID"; // Korvaa omalla WiFi-verkon nimellä const char* password = "OMA_WIFI_SALASANA"; // Korvaa omalla WiFi-salasanalla // Määritä GPIO-pinnit kytkennän mukaan const int PIR_PIN = 13; // PIR OUT -> GPIO 13 (EXT0 herätys) const int RTC_SQW_PIN = 2; // DS3231 SQW -> GPIO 2 (EXT1 herätys) // Telegram asetukset const char* telegramBotToken = "OMA_TELEGRAM_BOT_TOKEN"; // Korvaa omalla bot tokenilla const char* telegramChatID = "OMA_TELEGRAM_CHAT_ID"; // Korvaa omalla chat ID:llä // Ajastusasetukset const long NTP_OFFSET = 7200; // GMT+2 (Suomi normaaliaika), vaihda 10800 kesäaikaan (GMT+3) const char* NTP_SERVER = "pool.ntp.org"; const int MAX_WIFI_RETRIES = 15; // WiFi-yhteyden yritysten maksimimäärä // Kameran konfiguraatio (Freenove ESP32-Wrover CAM / vastaava) // Tarkista pinnit oman levysi dokumentaatiosta! #define CAM_PIN_PWDN 32 #define CAM_PIN_RESET -1 // Tai 33 joissain levyissä #define CAM_PIN_XCLK 0 #define CAM_PIN_SIOD 26 // SCCB SDA #define CAM_PIN_SIOC 27 // SCCB SCL #define CAM_PIN_D0 35 #define CAM_PIN_D1 34 #define CAM_PIN_D2 39 #define CAM_PIN_D3 36 #define CAM_PIN_D4 21 // Huom: Tämä on myös I2C SDA RTC:lle, varmista ettei konfliktoi jos molemmat aktiivisia yhtäaikaa #define CAM_PIN_D5 19 #define CAM_PIN_D6 18 #define CAM_PIN_D7 5 #define CAM_PIN_VSYNC 25 #define CAM_PIN_HREF 23 #define CAM_PIN_PCLK 22 // Huom: Tämä on myös I2C SCL RTC:lle, varmista ettei konfliktoi jos molemmat aktiivisia yhtäaikaa // <b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b>* // RTClib ja komponentti-instanssit // <b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b>* RTC_DS3231 rtc; RTC_DATA_ATTR int bootCount = 0; // Tallennetaan RTC-muistiin, säilyy deep sleepin yli // <b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b>* // Funktiot // <b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b><b></b>* // Alusta kamera bool initCamera() { camera_config_t config; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = CAM_PIN_D0; config.pin_d1 = CAM_PIN_D1; config.pin_d2 = CAM_PIN_D2; config.pin_d3 = CAM_PIN_D3; config.pin_d4 = CAM_PIN_D4; config.pin_d5 = CAM_PIN_D5; config.pin_d6 = CAM_PIN_D6; config.pin_d7 = CAM_PIN_D7; config.pin_vsync = CAM_PIN_VSYNC; config.pin_href = CAM_PIN_HREF; config.pin_pclk = CAM_PIN_PCLK; config.pin_xclk = CAM_PIN_XCLK; config.pin_siod = CAM_PIN_SIOD; // SDA config.pin_sioc = CAM_PIN_SIOC; // SCL config.pin_pwdn = CAM_PIN_PWDN; config.pin_reset = CAM_PIN_RESET; config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG; // Lähetetään JPEG-kuvia // Valitse resoluutio tarpeen mukaan (vaikuttaa tiedostokokoon ja lähetykseen) config.frame_size = FRAMESIZE_UXGA; // 1600x1200 //config.frame_size = FRAMESIZE_SVGA; // 800x600 (pienempi, nopeampi lähetys) config.jpeg_quality = 12; // 10-63 (pienempi = parempi laatu, suurempi tiedosto) config.fb_count = 1; // Käytä yhtä kuvapuskuria kerrallaan // Kamera inittöinti esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Kameran inittöinti epäonnistui (0x%x)\n", err); return false; } Serial.println("Kamera alustettu."); return true; } // Vapauta kameran resurssit void deinitCamera() { esp_err_t err = esp_camera_deinit(); if (err != ESP_OK) { Serial.printf("Kameran deinittöinti epäonnistui (0x%x)\n", err); } else { Serial.println("Kamera vapautettu."); } } // Yhdistä Wi-Fiin useilla yrityksillä bool connectToWiFi() { if (WiFi.status() == WL_CONNECTED) { Serial.println("WiFi on jo yhdistetty."); return true; } Serial.print("Yhdistetään WiFiin..."); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); int retries = 0; while (WiFi.status() != WL_CONNECTED && retries < MAX_WIFI_RETRIES) { delay(1000); Serial.print("."); retries++; } if (WiFi.status() == WL_CONNECTED) { Serial.println("\nWiFi yhdistetty!"); Serial.print("IP-osoite: "); Serial.println(WiFi.localIP()); return true; } else { Serial.println("\nWiFi yhdistäminen epäonnistui."); WiFi.disconnect(true); // Sammuta WiFi WiFi.mode(WIFI_OFF); // Aseta WiFi pois päältä virransäästön vuoksi return false; } } // Synkronoi aika NTP-palvelimelta bool syncTimeNTP() { if (WiFi.status() != WL_CONNECTED) { Serial.println("WiFi ei ole yhdistetty, ajan synkronointi epäonnistui."); return false; } Serial.println("Synkronoidaan aika NTP:ltä..."); configTime(NTP_OFFSET, 0, NTP_SERVER); // Offset, Daylight offset (0), NTP server struct tm timeinfo; if (!getLocalTime(&timeinfo, 10000)) { // Yritä 10 sekuntia Serial.println("Ajan haku NTP:ltä epäonnistui."); return false; } Serial.println("Aika haettu NTP:ltä:"); Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S"); // Tulosta haettu aika return true; } // Aseta RTC:n aika NTP:ltä haettuun aikaan void setRTCTimeFromNTP() { struct tm timeinfo; if (getLocalTime(&timeinfo)) { Serial.println("Asetetaan RTC:n aika NTP:n mukaan."); // Muunna struct tm -> DateTime objekti RTClibille DateTime now = DateTime(timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); rtc.adjust(now); Serial.println("RTC:n aika asetettu."); } else { Serial.println("Ajan asetus RTC:lle epäonnistui (NTP-aikaa ei saatu)."); } } // Aseta DS3231 Alarm 1 laukeamaan seuraavana päivänä klo 12:00 void setDailyAlarm() { DateTime now = rtc.now(); // Laske seuraava klo 12:00 ajankohta DateTime nextAlarm = DateTime(now.year(), now.month(), now.day(), 12, 0, 0); // Jos kello on jo 12:00 tai yli tänään, aseta hälytys huomiselle if (now >= nextAlarm) { nextAlarm = nextAlarm + TimeSpan(1, 0, 0, 0); // Lisää yksi päivä } Serial.print("Asetetaan seuraava päivittäinen hälytys (Alarm 1): "); char buf[] = "YYYY-MM-DD hh:mm:ss"; Serial.println(nextAlarm.toString(buf)); // Tyhjennä vanhat hälytysliput RTC:stä if (rtc.alarmFired(1)) { rtc.clearAlarm(1); Serial.println("Tyhjennettiin Alarm 1 lippu."); } if (rtc.alarmFired(2)) { rtc.clearAlarm(2); Serial.println("Tyhjennettiin Alarm 2 lippu."); } // Poista vanhat hälytykset käytöstä varmuuden vuoksi rtc.disableAlarm(1); rtc.disableAlarm(2); // Aseta hälytys 1: laukeaa kun tunti, minuutit ja sekunnit täsmäävät (DS3231_A1_Hour) // Tässä käytetään tarkinta mahdollista: kun päivä, tunti, minuutit ja sekunnit täsmäävät // DS3231_A1_Date tarkoittaa, että kaikki kentät (Date, Hour, Min, Sec) täsmäävät // Jos halutaan vain tietty aika päivittäin, DS3231_A1_Hour (tai vastaava, tarkista kirjaston toteutus) on oikeampi. // RTClib:n setAlarm1 tukee eri moodeja. Kokeillaan DS3231_A1_Date. // Vaihtoehtoisesti, jos halutaan joka päivä klo 12:00:00, pitää ehkä asettaa vain tunti, min, sek ja käyttää sopivaa moodia. // Testataan <code>DS3231_A1_Hour(laukeaa kun h, m, s täsmäävät) if (!rtc.setAlarm1(nextAlarm, DS3231_A1_Hour)) { // TAI DS3231_A1_Date riippuen kirjaston toteutuksesta ja halutusta tarkkuudesta Serial.println("Hälytyksen 1 asetus epäonnistui!"); } else { Serial.println("Hälytys 1 asetettu onnistuneesti."); rtc.enableAlarm(1); // Aktivoi hälytys 1 } // Varmista, että SQW-pinni on konfiguroitu antamaan hälytyssignaali (interrupt), ei 1Hz pulssia rtc.writeSqwPinMode(DS3231_SquareWave1Hz); // Testaa ensin pulssilla //rtc.writeSqwPinMode(DS3231_OFF); // Kytke 1Hz pois, jotta hälytys toimii (voi vaatia INTCN bitin asettamista) // RTClib saattaa hoitaa tämän automaattisesti enableAlarm/setAlarm yhteydessä. Tarkista kirjaston dokumentaatio/koodi. // Yleensä halutaan, että SQW-pinni menee matalaksi hälytyksen lauetessa. // Varmista, ettärtc.alarmInterrupt(1, true);tai vastaava on kutsuttu, jos kirjasto sitä vaatii. } // Lähetä kuva Telegramiin käyttäen HTTPClientia bool sendPhotoToTelegram(camera_fb_t *fb, const char* caption) { if (WiFi.status() != WL_CONNECTED) { Serial.println("WiFi ei ole yhdistetty, kuvan lähetys epäonnistui."); return false; } Serial.println("Lähetetään kuvaa Telegramiin..."); HTTPClient http; String url = "https://api.telegram.org/bot" + String(telegramBotToken) + "/sendPhoto"; String boundary = "--------------------------" + String(random(0xffffffff), HEX); // Uniikki rajaaja // Aloita HTTP-pyyntö http.begin(url); http.addHeader("Content-Type", "multipart/form-data; boundary=" + boundary); // Rakenna multipart/form-data payload String start_request = "--" + boundary + "\r\n"; start_request += "Content-Disposition: form-data; name=\"chat_id\"\r\n\r\n"; start_request += String(telegramChatID) + "\r\n"; String photo_header = "--" + boundary + "\r\n"; photo_header += "Content-Disposition: form-data; name=\"photo\"; filename=\"esp32-cam.jpg\"\r\n"; photo_header += "Content-Type: image/jpeg\r\n\r\n"; String end_request = "\r\n--" + boundary + "--\r\n"; // Laske kokonaispituus size_t total_len = start_request.length() + photo_header.length() + fb->len + end_request.length(); http.addHeader("Content-Length", String(total_len)); // Lähetä pyynnön osat: alkuosa + kuvadata + loppuosa WiFiClient * stream = http.getStreamPtr(); // Hae streami stream->print(start_request); // Lähetä alkuosa stream->print(photo_header); // Lähetä kuvan headerit stream->write(fb->buf, fb->len); // Lähetä kuvan data stream->print(end_request); // Lähetä loppuosa // Lähetä POST-pyyntö ja odota vastausta int httpCode = http.POST(""); // Tyhjä string, koska data lähetettiin streamin kautta bool success = false; if (httpCode > 0) { Serial.printf("Telegram vastauskoodi: %d\n", httpCode); if (httpCode == HTTP_CODE_OK) { String payload = http.getString(); Serial.println("Vastaus:"); Serial.println(payload); Serial.println("Kuva lähetetty onnistuneesti Telegramiin."); success = true; } else { Serial.println("Kuvan lähetys epäonnistui Telegramiin (palvelinvirhe)."); String payload = http.getString(); Serial.println("Virheviesti:"); Serial.println(payload); } } else { Serial.printf("Virhe HTTP-pyynnössä: %s\n", http.errorToString(httpCode).c_str()); } http.end(); return success; } // Ota useita kuvia tietyllä viiveellä ja lähetä ne void handlePhotoTaking(int count, int delay_ms, bool discard_first, const char* base_caption) { if (!initCamera()) { Serial.println("Kameran alustus epäonnistui kuvanottoa varten."); return; } camera_fb_t *fb = NULL; for (int i = 0; i < count; i++) { Serial.printf("Otetaan kuva %d/%d...\n", i + 1, count); fb = esp_camera_fb_get(); // Ota kuva if (!fb) { Serial.println("Kameran kuvan ottaminen epäonnistui!"); continue; // Yritä seuraavaa kuvaa } Serial.printf("Kuva %d otettu, koko: %zu tavua\n", i + 1, fb->len); bool should_send = true; if (i == 0 && discard_first) { Serial.println("Hylätään ensimmäinen kuva."); should_send = false; } if (should_send) { // Muodosta kuvateksti char caption[100]; snprintf(caption, sizeof(caption), "%s %d/%d", base_caption, i + 1, count); // Yritä lähettää kuva if (connectToWiFi()) { // Varmista WiFi-yhteys ennen lähetystä sendPhotoToTelegram(fb, caption); // Pieni viive ennen seuraavaa kuvaa/lähetystä, jos tarpeen delay(1000); } else { Serial.println("Kuvaa ei voida lähettää, WiFi-yhteys epäonnistui."); } } esp_camera_fb_return(fb); // Vapauta kuvapuskuri fb = NULL; // Nollaa osoitin // Odota ennen seuraavan kuvan ottamista (jos count > 1) if (i < count - 1 && delay_ms > 0) { Serial.printf("Odotetaan %d ms ennen seuraavaa kuvaa...\n", delay_ms); delay(delay_ms); } } // Vapauta kameran resurssit kuvasarjan jälkeen deinitCamera(); } // Mene Deep Sleep -tilaan ja määritä herätyslähteet void enterDeepSleep() { Serial.println("Mennään syväuneen (deep sleep)..."); Serial.flush(); // Varmista, että kaikki sarjaporttiviestit on lähetetty // Konfiguroi herätys PIR-anturista (EXT0) // Herätys HIGH-tasosta (kun PIR havaitsee liikettä) esp_sleep_enable_ext0_wakeup(gpio_num_t(PIR_PIN), 1); // Varmista, ettei pinni jää kellumaan (vaikka PIR:ssa on yleensä ylösveto) rtc_gpio_pullup_en(gpio_num_t(PIR_PIN)); rtc_gpio_pulldown_dis(gpio_num_t(PIR_PIN)); // Konfiguroi herätys RTC:n hälytyksestä (EXT1) // Herätys LOW-tasosta (kun DS3231 SQW -pinni menee matalaksi hälytyksen lauetessa) // Tarvitaan bittimaski EXT1:lle, joka sisältää vain RTC_SQW_PIN:n uint64_t ext1_wakeup_mask = 1ULL << RTC_SQW_PIN; esp_sleep_enable_ext1_wakeup(ext1_wakeup_mask, ESP_EXT1_WAKEUP_ALL_LOW); // Herää kun MASKIN pinnit ovat LOW // Mene syväuneen esp_deep_sleep_start(); } // * // SETUP - Ajetaan kerran käynnistyksen tai herätyksen jälkeen // * void setup() { Serial.begin(115200); Serial.setDebugOutput(true); // Näytä debug-viestit Serial.println("\n\nESP32-CAM Käynnistyy..."); bootCount++; Serial.println("Boot count: " + String(bootCount)); // Alusta I2C RTC:lle Wire.begin(); // Oletus SDA=21, SCL=22 if (!rtc.begin()) { Serial.println("RTC:tä ei löytynyt tai alustus epäonnistui! Tarkista kytkentä."); // Tässä voisi harkita virhetilaan siirtymistä tai jatkamista ilman RTC:tä } else { Serial.println("RTC alustettu."); // Varmista, että SQW-pinni ei anna 1Hz pulssia, jos se häiritsee hälytystä rtc.writeSqwPinMode(DS3231_OFF); Serial.println("RTC SQW 1Hz pulssi kytketty pois."); // Tyhjennä mahdolliset vanhat hälytysliput heti alussa if (rtc.alarmFired(1)) { Serial.println("Herätessä Alarm 1 lippu oli päällä, tyhjennetään..."); rtc.clearAlarm(1); } if (rtc.alarmFired(2)) { Serial.println("Herätessä Alarm 2 lippu oli päällä, tyhjennetään..."); rtc.clearAlarm(2); } } // Tarkista herätyssyytä esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause(); switch(wakeup_reason) { case ESP_SLEEP_WAKEUP_EXT0: // Herätys EXT0-pinnistä (PIR) Serial.println("Herätys: PIR-liike havaittu (EXT0)."); // Toiminnot: Ota 2 kuvaa, hylkää 1., lähetä 2. handlePhotoTaking(2, 500, true, "Liike havaittu"); // 2 kuvaa, 0.5s väli, hylkää eka break; case ESP_SLEEP_WAKEUP_EXT1: // Herätys EXT1-pinnistä (RTC SQW) Serial.println("Herätys: RTC Hälytys (EXT1)."); // Tarkista RTC tila ja aika if (rtc.begin()) { // Varmista RTC yhteys uudelleen if (rtc.lostPower()) { Serial.println("VAROITUS: RTC menetti virrat, aika voi olla väärä!"); // Yritä synkronoida aika uudelleen NTP:ltä if (connectToWiFi() && syncTimeNTP()) { setRTCTimeFromNTP(); } else { Serial.println("Aikaa ei voitu päivittää NTP:ltä RTC-virran menetyksen jälkeen."); } } else { Serial.println("RTC aika OK."); char buf[] = "YYYY-MM-DD hh:mm:ss"; Serial.print("Nykyinen RTC aika: "); Serial.println(rtc.now().toString(buf)); } // Tyhjennä hälytyslippu RTC:stä, jotta se voi laueta uudelleen rtc.clearAlarm(1); Serial.println("RTC Alarm 1 lippu tyhjennetty."); // Aseta seuraava hälytys huomiselle klo 12:00 setDailyAlarm(); } else { Serial.println("RTC:hen ei saatu yhteyttä hälytyksen jälkeen."); } // Toiminnot: Ota 5 kuvaa 30s välein, hylkää 1., lähetä loput 4 handlePhotoTaking(5, 30000, true, "Ajastettu kuva"); // 5 kuvaa, 30s väli, hylkää eka break; case ESP_SLEEP_WAKEUP_TIMER: Serial.println("Herätys: Ajastin (ei käytössä tässä projektissa)."); break; case ESP_SLEEP_WAKEUP_UNDEFINED: // Ensimmäinen käynnistys tai virtakatko default: // Muut syyt Serial.println("Herätys: Ensimmäinen käynnistys tai tuntematon syy."); Serial.println("Suoritetaan ensikäynnistysasetukset."); if (rtc.begin()) { // Varmista RTC yhteys if (rtc.lostPower() || bootCount == 1) { // Jos virta katkennut TAI ensimmäinen boot Serial.println("RTC virta katkennut tai ensikäynnistys, yritetään synkronoida aika."); if (connectToWiFi() && syncTimeNTP()) { setRTCTimeFromNTP(); } else { Serial.println("Ajan synkronointi epäonnistui ensikäynnistyksessä."); // Tässä voisi asettaa oletusajan tai jättää ajan asettamatta } } else { Serial.println("RTC aika vaikuttaa olevan tallessa."); char buf[] = "YYYY-MM-DD hh:mm:ss"; Serial.print("Nykyinen RTC aika: "); Serial.println(rtc.now().toString(buf)); } // Asetetaan ensimmäinen päivittäinen hälytys joka tapauksessa setDailyAlarm(); } else { Serial.println("RTC:hen ei saatu yhteyttä ensikäynnistyksessä."); } break; } // Kaikkien toimintojen jälkeen siirry takaisin syväuneen enterDeepSleep(); } // * // LOOP - Ei käytetä syväunen kanssa // * void loop() { // Tämä funktio ei ajaudu, kun laite on syväunessa. // Kaikki logiikka tapahtuu setup() -funktiossa herätyksen jälkeen. delay(10000); // Pieni viive, jos jostain syystä päädytään tänne }
Koodin selitys ja huomioitavaa
- Kirjastot: Varmista, että kaikki tarvittavat kirjastot (
RTClib.h, ESP32-levytuki,HTTPClienton osa ESP32 Arduino-ydintä) ovat asennettuna Arduino IDE:ssä. - Asetukset: Muista korvata paikkamerkit (
OMA_WIFI_SSID,OMA_WIFI_SALASANA,OMA_TELEGRAM_BOT_TOKEN,OMA_TELEGRAM_CHAT_ID) omilla arvoillasi. - Pinnit: Tarkista ja mukauta
PIR_PIN,RTC_SQW_PINja erityisesti kameran pinnimääritykset (CAM_PIN_...) vastaamaan käyttämääsi Freenove ESP32-CAM -levyä. setup()logiikka: Koska laite käyttää syväunta, kaikki toimintalogiikka on sijoitettusetup()-funktioon, joka ajetaan aina heräämisen jälkeen.loop()-funktiota ei käytännössä suoriteta.- Herätyssyyn tunnistus:
esp_sleep_get_wakeup_cause()tunnistaa, mikä laukaisi herätyksen (PIR vai RTC). - Ensikäynnistys & RTC: Koodi tarkistaa, onko RTC:n virta katkennut (
rtc.lostPower()) tai onko kyseessä ensimmäinen käynnistys (bootCount). Jos näin on, se yrittää synkronoida ajan NTP-palvelimelta WiFi-yhteyden kautta. - Päivittäinen hälytys:
setDailyAlarm()laskee seuraavan klo 12:00 ajankohdan ja asettaa DS3231:n hälytyksen (Alarm 1) käyttäenRTClib.h-kirjaston funktioita. Hälytyslippu (rtc.clearAlarm(1)) on tärkeä tyhjentää herätyksen jälkeen. - Kuvien otto ja lähetys:
handlePhotoTaking()-funktio hoitaa kuvien ottamisen, ensimmäisen kuvan hylkäämisen ja lopujen lähettämisen Telegramiin.sendPhotoToTelegram()käyttääHTTPClient-kirjastoa ja manuaalisesti rakennettuamultipart/form-data-pyyntöä. Tämä on monimutkaisempi tapa kuin valmiin Telegram-kirjaston käyttö, mutta ei vaadi lisäkirjastojen asentamista. - Syväuni:
enterDeepSleep()konfiguroi EXT0 (PIR) ja EXT1 (RTC SQW) herätyslähteet ja siirtää ESP32:n syväuneen. - Virheiden käsittely: Koodi sisältää perustason virheiden tarkistuksia (esim. WiFi-yhteys, kameran alustus, RTC-yhteys), mutta sitä voi parantaa lisäämällä robustimpaa virheiden käsittelyä tarpeen mukaan.
Projektin tekijät ja suorituskyky
Tämän tyyppisessä projektissa useat tekijät vaikuttavat lopputuloksen luotettavuuteen ja suorituskykyyn. Alla oleva kaavio arvioi eri osa-alueiden suhteellista merkitystä tai vaativuutta tässä projektissa.
Kaavio korostaa, että erityisesti kytkennän tarkkuus ja virrankulutuksen hallinta syväunen avulla ovat keskeisiä onnistumiselle. Myös koodin logiikan ja WiFi-yhteyden luotettavuuden varmistaminen vaativat huomiota.
Videoesimerkki ESP32-CAM ja PIR -projektista
Vaikka tämä video ei täysin vastaa tämän oppaan projektia (erityisesti RTC:n ja Telegramin osalta), se antaa hyvän kuvan siitä, miten ESP32-CAM ja PIR-anturi toimivat yhdessä liikkeen tunnistamisessa ja kuvan ottamisessa. Se voi auttaa visualisoimaan projektin perusosia.
Videossa esitellään ESP32-CAM:n ja PIR-anturin yhdistelmää, joka tallentaa kuvia SD-kortille liikettä havaittaessa. Perusperiaate liikkeestä heräämisestä ja kuvan ottamisesta on sama kuin tässä projektissa, vaikka tallennus/lähetystapa on eri.
Usein kysytyt kysymykset (FAQ)
Miksi ensimmäinen kuva hylätään?
Monet kameramoduulit, mukaan lukien ESP32-CAM:ssa käytetyt, tarvitsevat hetken "lämmetäkseen" tai säätääkseen automaattisesti valotusta ja valkotasapainoa heräämisen jälkeen. Ensimmäinen otettu kuva voi siksi olla alivalottunut, ylivalottunut tai väreiltään vääristynyt. Hylkäämällä ensimmäisen kuvan ja ottamalla seuraavan/seuraavat heti perään varmistetaan parempi ja tasalaatuisempi kuvanlaatu lähetettäväksi.
Miten saan selville Telegram Bot Tokenin ja Chat ID:n?
Bot Token: Luo uusi botti Telegramissa keskustelemalla "BotFather":n kanssa. BotFather antaa sinulle uniikin token-avaimen, kun luot botin. Säilytä tämä token turvassa.
Chat ID: Tämä on sen keskustelun tunniste, johon haluat botin lähettävän viestejä (se voi olla sinun henkilökohtainen chat tai ryhmä). Helpoin tapa saada oma Chat ID on lähettää viesti `@get_id_bot` -nimiselle botille Telegramissa. Jos haluat lähettää viestejä ryhmään, lisää bottisi ensin ryhmään ja lähetä sitten komento `/my_id @ryhman_botti_nimi` ryhmään (missä `@ryhman_botti_nimi` on sinun bottisi nimi).
Voiko PIR-anturin herkkyyttä tai viivettä säätää?
Kyllä, useimmissa HC-SR501 PIR -moduuleissa on kaksi potentiometriä (säätövastusta), joilla voi hienosäätää sen toimintaa:
- Herkkyyden säätö: Määrittää, kuinka kaukaa tai kuinka pieni liike laukaisee tunnistuksen.
- Viiveen säätö (Time Delay): Määrittää, kuinka kauan OUT-signaali pysyy HIGH-tilassa liikkeen havaitsemisen jälkeen (tyypillisesti muutamasta sekunnista useisiin minuutteihin).
Kokeilemalla näitä säätöjä voit optimoida anturin toiminnan ympäristöösi sopivaksi ja vähentää vääriä hälytyksiä.
Mitä jos WiFi-yhteys katkeaa kuvien lähetyksen aikana?
Toimitettu koodi yrittää varmistaa WiFi-yhteyden ennen jokaista kuvan lähetysyritystä (`sendPhotoToTelegram` kutsuu `connectToWiFi`). Jos yhteys katkeaa tai ei muodostu, kuvaa ei lähetetä ja funktio palauttaa `false`. Tässä versiossa koodi ei yritä lähettää epäonnistunutta kuvaa uudelleen myöhemmin. Kehittyneemmässä versiossa voisi toteuttaa logiikan, joka tallentaa epäonnistuneet kuvat (esim. SD-kortille, jos käytössä) ja yrittää lähettää ne seuraavan kerran, kun yhteys on käytettävissä.
Viitteet
Tämä vastaus perustuu osittain seuraaviin lähteisiin ja yleisiin ESP32-CAM, RTC ja PIR -tietokantoihin:
- ESP32-CAM PIR Motion Detector with Photo Capture - Random Nerd Tutorials
- Getting Started with Freenove ESP32-WROVER-CAM Board - Random Nerd Tutorials
- ESP32 DS3231 Precision RTC - Random Nerd Tutorials
- Motion Activated ESP32 CAM with PIR Sensor - Makerguides
- ESP32 Motion Sensor Tutorial (HC-SR501) - ESP32 IO
- RTClib Library Documentation - Adafruit (GitHub)
Suositellut lisähaut
Syvennä osaamistasi tutkimalla näitä aiheita:
Last updated April 23, 2025