Categories
ESP32 Mast

SkyCam mit ESP32

Link zum aktuellen Kamerabild: SkyCam bei Heiligenberg in JN47qu

Mittlerweile sind in meinem Gartenhaus allerlei Sensoren installiert. Was allerdings noch fehlt ist eine Art Webcam. Zunächst ist die Frage was man auf einer Webcam sehen möchte. Mich interessiert, wenn ich nicht daheim bin, wie das Wetter zuhause ist. Ob es bewölkt ist, regnet oder der Himmel frei von Wolken ist. Was für mich nicht in Frage kam, ist eine Webcam, mit der man meinen Garten sieht. Warum also nicht eine Webcam, die den Himmel ablichtet. So entstand die Idee eine SkyCam zu bauen. Dafür sollte die Kamera zudem Weitwinkel können, sonst ist das Sichtfeld in den Himmel doch etwas eingeschränkt.

Für mich wichtig war, dass die Kamera günstig und zuverlässig ist. Nicht wichtig waren mir nächtliche Aufnahmen des Sternenhimmels. Ich wollte kein Betriebssystem (wie bei einem RaspberryPi) mit SD-Karte, die kaputt gehen kann, pflegen und ggf. tauschen.

Somit entschloss ich mich ein ESP32 Kameramodul mit Weitwinkel zu nehmen.

Kamera

Als Kamera habe ich einen ES32 Cam verwendet. Das Modul ist relativ günstig und auch in Weitwinkel zu bekommen. Ohne Weitwinkel sieht ein Modul in etwa so aus:

ESP32 CAM mit Standardobjektiv

Platine

Da ich die Kamera auf einem Mast installieren wollte und nicht 5V bis zum Mast hochführen wollte, habe ich mir eine Platine gestaltet, auf dem ein Step-Down Wandler sitzt, der alles zwischen 6 V und ~25V akzeptiert.

Auf der Platine sind die Anschlüsse zum Flashen des ESP32 zudem extrahiert.

https://aisler.net/p/NCICYTNB (Werbelink)

Bestückung:

  • C1, C2, C3 : 10nF
  • L1 : 10 uH
  • DC-Wandler : RECOM R-78E50-05

Mit dem Brücken der Kontakte Flash-EN kann der ESP32 geflasht werden. Mit dem Brücken der Kontakte Step-Down-EN wird der Ausgang des Step-Down Wandlers mit dem Versorgungsspannunsgeingang des ESP32 verbunden. Es ist in jedem Fall zu vermeiden Step-Down-EN gebrückt zu haben und den ESP anderweitig z.B. via Flash-Kabel mit einer weiteren Spannungsquelle, wie beispielsweise einem Computer verbunden zu haben. Dies kann zur Beschädigung führen.

Gehäuse

Danach musste ich mir überlegen in was für ein Gehäuse ich für die Kamera nehmen möchte. Um den Himmel zu beobachten bietet sich ein “Dome” einer Überwachungskamera an. Diese können aus diversen Quellen bezogen werden. Meiner ist aus Acrylglas. Der Innendurchmesser beträgt ~76 mm, der Außendurchmesser ~85mm. Den Unterbau, in dem sich die Kamera mit dem ESP befindet habe ich selbst gestaltet, 3D gedruckt und ganz wichtig; angeschliffen und mit Farbe besprüht, um eine gewisse UV Festigkeit zu erreichen. Als Material habe ich ABS verwendet. Das gedruckte Gehäuse besteht aus zwei Teilen. Einem Oberteil, in das der “Dome” eingepasst wird, in den die Kamera schaut, sowie einem unteren Teil, der auf den Mast gesteckt und fest mit dem oberen Teil verbunden wird.

SkyCam gedrucktes Oberteil Foto 1 – die beiden Öffnungen links und rechts der Kameraöffnung sind für Silikagel damit es trocken bleibt.
SkyCam gedrucktes Oberteil Foto 2 – die Kamera wird mitsamt der Platine von unten in das Oberteil eingepasst.
SkyCam gedrucktes Unterteil Foto 1 – diese Seite wird auf dem Mast gesteckt.
SkyCam gedrucktes Unteritel Foto 2 – Diese Seite wird am Oberteil befestigt.

Im Unterteil habe ich eine Sechskantöffnung verwendet, da ich dort eine Wasserdichte Kabeldurchführung für das Stromversorgungskabel verwende.

Der “Dome” wird mit Fugmasse am Oberteil eingelassen, die beiden Fächer im Oberteil sind für Silikagel Kugeln, damit der “Dome” nicht beschlägt.

Endmontage

Fertig am Mast installierte SykCam
Oberteil mit Silikagel und Tape mit “Löchern” damit die Kugeln nicht bei der Montage am Mast durch die Kuppel fliegen. – Das gesamte Oberteil ist lackiert, der Teil in der Kuppel jedoch NICHT mit Klarlack versehen, um reflexionen zu vermeiden.
Lackiertes Unterteil mit Kabeldurchführung. – Gut zu erkennen ist, dass der Druck ursprünglich blau war und schwarz lackiert wurde.
Montiert auf dem Mast, noch mit Schutzfolie.
Auch die Unterseite (an der das Unterteil mit Schrauben am Oberteil befestigt ist) wurde abgedichtet.

Software

Die Kamera stellt einen HTTP-Server zur Verfügung. Das Kamerabild muss von einem Server im Heimnetzwerk aktiv abgeholt werden. Ich empfehle die Kamera niemals von außerhalb des Heimnetzwerks erreichbar zu machen.

#include <WebServer.h>
#include <TaskScheduler.h>

#define CAMERA_MODEL_AI_THINKER
#include "camera_pins.h"
#include "WiFi.h"
#include "esp_camera.h"
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "soc/soc.h"           // Disable brownour problems
#include "soc/rtc_cntl_reg.h"  // Disable brownour problems
#include "driver/rtc_io.h"

#include <FS.h>

// --------- WIFI -----------

#define STASSID    "SSID"
#define STAPSK     "PWD"

#define DEVICENAME "ESP32-SkyCamName"

unsigned long previousMillis = 0;
unsigned long interval = 30000;

// --------- END WIFI -------

// --------- INITS -------

const char* ssid = STASSID;
const char* password = STAPSK;
const char* deviceName = DEVICENAME;

WebServer server(80);
StaticJsonDocument<1024> jsonDocument;
char jsonBuffer[1024];

// --------- END INITS -------

// --------- SCHEDULER BEGIN -------

void checkFreeRam();
Task scheduleCheckFreeRam(21*1000, TASK_FOREVER, &checkFreeRam);

void wifiReconnectCheck();
Task scheduleWifiReconnectCheck(8*60*1000, TASK_FOREVER, &wifiReconnectCheck);

Scheduler runner;

// --------- SCHEDULER END ---------

// ------- PINS ----------
static int morsePin = 2;
// ------- END PINS ----------

// --------- Variables ---------
int freeHeap = 0;
// --------- END Variables ---------

void setup()
{
  initSerial();
  initWifi();
  initCamera();
  initPinModes();
  initTimeClient();
  initServer();
  initSchedules();
  checkFreeRam();
}

void loop()
{
  server.handleClient();
  runner.execute();
}

void initSerial()
{
  Serial.begin(115200); 
  while(!Serial){} // Waiting for serial connection
  Serial.println();
}

void initWifi()
{
  Serial.println("WiFi init");
  Serial.print("Wifi: ");
  Serial.println(ssid);
  //Serial.print("WifiPW: ");
  //Serial.println(password);
  
  Serial.println("turn wifi off...");
  WiFi.mode(WIFI_OFF);
  delay(10);
  //WiFi.forceSleepBegin();
  delay(200);
  //WiFi.forceSleepWake();
  WiFi.mode(WIFI_STA);
  delay(250);
  
  WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);    
  delay(200); 
  //WiFi.mode(WIFI_STA);
  Serial.println("setting hostname");
  WiFi.setHostname(deviceName);
  delay(200); 
  Serial.println("Connecting to WiFi..");
  WiFi.begin(ssid, password);
  delay(200); 

  int iCounter = 0;
  int iMax = 30;
  while (WiFi.status() != WL_CONNECTED && iCounter < iMax)
  {
    digitalWrite(morsePin, LOW);
    delay(500);
    digitalWrite(morsePin, HIGH);
    delay(500);
    Serial.print(".");
    iCounter++;
  }
  
  Serial.println(WiFi.localIP()); 
}

void initPinModes()
{
  Serial.println("PIN MODES init");

  pinMode(morsePin, OUTPUT);
  digitalWrite(morsePin, LOW);
}

void initSchedules()
{
  Serial.println("SCHEDULES init");
  
  runner.init();

  runner.addTask(scheduleCheckFreeRam);
  scheduleCheckFreeRam.enable();

  runner.addTask(scheduleWifiReconnectCheck);
  scheduleWifiReconnectCheck.enable();
}

void initServer()
{
  server.on("/", sendPhoto);
  server.begin();
  Serial.println("HTTP server started");
}

void wifiReconnectCheck()
{
  unsigned long currentMillis = millis();
  // if WiFi is down, try reconnecting every CHECK_WIFI_TIME seconds
  if ((WiFi.status() != WL_CONNECTED) && (currentMillis - previousMillis >=interval))
  {
    Serial.print(millis());
    Serial.println("Reconnecting to WiFi...");
    WiFi.disconnect();
    WiFi.reconnect();
    previousMillis = currentMillis;
  }
}

void checkFreeRam()
{
  freeHeap = ESP.getFreeHeap();

  if (ESP.getFreeHeap() < 60000)
  {
    ESP.restart();
  }
}

void initCamera()
{
  Serial.println("Init Camera");
  
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  //config.xclk_freq_hz = 20000000;
  config.xclk_freq_hz = 10000000;
  config.pixel_format = PIXFORMAT_JPEG; 

  config.frame_size = FRAMESIZE_UXGA;
  config.jpeg_quality = 10;
  config.fb_count = 1;

  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) 
  {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }
}

void sendPhoto()
{
  capturePhotoAndSend();
}

// Photo BEGIN
void capturePhotoAndSend() 
{
  camera_fb_t * fb = NULL; // pointer
  bool ok = 0; // Boolean indicating if the picture has been taken correctly

  size_t _jpg_buf_len;
  uint8_t * _jpg_buf;

  Serial.println("Taking a photo...");

  do
  {
    // Take a photo with the camera
    //Serial.println("Taking a photo...");

    fb = esp_camera_fb_get();
    if (!fb)
    {
      Serial.println("Camera capture failed");
      server.send(500, "text/html", "Error 500"); 
      return;
    }

    /*if (fb->len > 0)
    {
      server.send_P(200, "image/jpg", (const char *)fb->buf, fb->len);
      Serial.println("Length: " + (String)fb->len);
      ok = true;
    }*/

    if(fb->format == PIXFORMAT_JPEG)
    {
      //frame2jpg(fb, 100, &_jpg_buf, &_jpg_buf_len);
      server.send_P(200, "image/jpg", (const char *)fb->buf, fb->len);
      ok = true;
    }
    else
    {
      //frame2jpg_cb(fb, 80, jpg_encode_stream, &jchunk)?ESP_OK:ESP_FAIL;
      frame2jpg(fb, 100, &_jpg_buf, &_jpg_buf_len);
      server.send_P(200, "image/jpg", (const char *)_jpg_buf, _jpg_buf_len);
      //server.send_P(req, NULL, 0);
      //fb_len = jchunk.len;
      ok = true;
    }

    esp_camera_fb_return(fb);
  }

  while(!ok);
}

// Photo END

Hinweis

Dieser Artikel dokumentiert lediglich meinen Aufbau. Für den Nachbau, die Nutzung einzelner Komponenten, die Platinen und den gesamten Inhalt wird die Haftung in jeglicher Form ausgeschlossen.

Leave a Reply

Your email address will not be published. Required fields are marked *




Enter Captcha Here :