Ask me anything
Ithy Logo
Sign in to save

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.

esp32-cam-rtc-pir-telegram-guide-dcfbmvxn

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:

ESP32-CAM PIR-anturin kytkentäesimerkki

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.

mindmap root["Projektin toimintalogiikka"] id1["Käynnistys / Reset"] id1.1["Alusta komponentit (Serial, Wire, RTC)"] id1.2["Tarkista herätys syy"] id1.2.1["Ensimmäinen käynnistys / RTC virta katkennut"] id1.2.1.1["Yhdistä WiFi (useita yrityksiä)"] id1.2.1.2["Jos WiFi OK: Hae NTP-aika"] id1.2.1.3["Jos NTP OK: Tallenna aika RTC:hen"] id1.2.1.4["Aseta seuraava RTC-hälytys (klo 12:00)"] id1.2.1.5["Siirry syväuneen"] id1.2.2["Herätys PIR-liikkeestä (EXT0)"] id1.2.2.1["Yhdistä WiFi (tarvittaessa)"] id1.2.2.2["Alusta kamera"] id1.2.2.3["Ota 2 kuvaa"] id1.2.2.4["Hylkää 1. kuva"] id1.2.2.5["Lähetä 2. kuva Telegramiin"] id1.2.2.6["Sammuta kamera"] id1.2.2.7["Siirry syväuneen"] id1.2.3["Herätys RTC-hälytyksestä (EXT1)"] id1.2.3.1["Tarkista RTC:n tila (aika OK?)"] id1.2.3.2["Tyhjennä hälytyslippu"] id1.2.3.3["Aseta seuraava RTC-hälytys (klo 12:00)"] id1.2.3.4["Yhdistä WiFi (tarvittaessa)"] id1.2.3.5["Alusta kamera"] id1.2.3.6["Ota 5 kuvaa (30s välein)"] id1.2.3.7["Hylkää 1. kuva"] id1.2.3.8["Lähetä 4 kuvaa Telegramiin"] id1.2.3.9["Sammuta kamera"] id1.2.3.10["Siirry syväuneen"] id2["Syväuni (Deep Sleep)"] id2.1["Herätyslähteet aktiivisena: PIR (EXT0), RTC (EXT1)"]

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, HTTPClient on 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_PIN ja 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 sijoitettu setup()-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äen RTClib.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 rakennettua multipart/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:


Suositellut lisähaut

Syvennä osaamistasi tutkimalla näitä aiheita:


Last updated April 23, 2025

AI-assisted research. Sources, methodology and corrections · Source-checked guides and tools · Explore published answers

Ask Ithy AI