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:
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.
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
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.
2 replies on “SkyCam mit ESP32”
Moin,
wie ist Nachtbild im Vergleich zu der Pi Kamera?
Könntest du noch ein Testbild hier reinstellen?
Gruß
Hallo Jacson,
unter General -> SkyCam findest Du immer das aktuelle Bild meiner SkyCam ( https://dk1teo.com/skycam-in-jn47qu/ ). Dort befinden sich auch Zeitraffer inklusive Nachtaufnahmen.
Nachts lassen sich keine Sterne erkennen, lediglich der Mond ist als Heller Punkt erkennbar. Am Tag ist die Qualität aus meiner Sicht gut.
Eine Pi Kamera nach meiner Einschätzung ein gutes Stück besser, als die ESP32 Kamera, gerade wenn es dunkel wird.
Viele Grüße
DK1TEO