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.

Categories
Raspberry Pi

DL0FIS – Raspberry Pi – Kameras mit Sensoren – Revision

Link zu den Webcams und Wetterdaten hier

Link zum A18, auf dessen Clubstation (DL0FIS), die Kameras installiert sind hier

Einleitung

Nach einigen Jahren haben die Kameras mit Sensoren bei DL0FIS immer unzuverlässiger gearbeitet, bis hin zum Totalausfall beider Raspberry Pis.

Daher mussten die Kameras abgebaut und revisioniert werden. Meistens ist es “nur” die SD-Karte, sollte man meinen. Doch nach einigen Jahren der Witterung ausgesetzt, war es nicht nur die SD Karte, die defekt war.

Bestandsaufnahme

Der DHT22 Sensor genauso wie der BME280 Sensor sind durch die Witterungseinflüsse komplett “abgegammelt”.

Defekter DHT22 Sensor – Im Hintergrund zu sehen: der damals noch schlecht gedruckte “Wetterschutz” für den Sensor.
Defekter DHT22 Sensor – Innenleben

Bei einer der Kameras hat sich der Kleber der Scheibe gelöst, somit konnte Wasser ins Gehäuse eindringen – Den Raspberry Pi hat das nicht zerstört, da das Wasser im Gehäuse nie hoch stand.

Vorderseite der Kamera, bei der sich der Kleber des Frontglases gelöst hat (ohne Glas) – Links erkennt man gut die Moosbildung durch eindringende Feuchtigkeit.

Bei einer der Kameras habe ich Pappe statt einer 3D gedruckten Kamerarückseite verwendet, auch diese hat sich sehr verzogen. Davon habe ich kein Foto gemacht, hier nochmal das Original bzw. wie es beim Einbau aussah:

Die eingebauten Step-Down Wandler (damals noch mit schlecht gedruckten Gehäuse) funktionieren weiterhin zuverlässig.

Step-Down Wandler in der Gehäuserückseite einer Kamera.

Die SD-Karte ist auch defekt und muss ersetzt werden.

Hardware

Bisher war die eine Kamera mit einem DHT22 Sensor und die andere mit einem BME280 Sensor für die Messung der Umgebung ausgestattet. Sowie eine der Kameras mit Sensoren für die Messung der Innentemperatur.

Im Zuge der Revision habe ich mich dazu entschieden die Innensensoren zu entfernen, sowie nur noch eine der Kameras mit einem Außensensor (BME280) auszustatten.

Neuer Außensensor – BME280

Die Schutzhülle des Sensors habe ich neu gestaltet, sodass der Schutz für den Sensor verbessert wird. Den Schutz habe ich im Anschluss lackiert, um den ABS Kunststoff etwas gegen die UV Strahlung zu schützen.

BME280 Sensor Schutz lackiert inklusive Bohrungen für die Luftzirkulation.
3D Modell – BME280 Sensor Schutz – Der mehrwandige Schutz soll verhindern, dass Wasser bei Wind zum Sensor gelangt.

Die Front mit der Glasscheibe habe ich auch repariert bzw. die Scheibe mit etwas Acryl befestigt.

Befestigte Scheibe in der Kamerafront

Als Kamerarückseite habe ich eine 3D gedrucktes Teil eingesetzt, dass ich mit schwarzer Farbe lackiert habe, da ABS Kunststoff dazu neigen kann bei Sonneneinstrahlung aus zu bleichen. Die Kamerarückseite ist deshalb montiert, damit es auf der Scheibe keine Reflexionen gibt.

Kamerarückseite ohne eingesetzte Kamera, aufgeraut, damit der Lack besser hält.
Lackierte Kamerarückseite ohne eingesetzte Kamera.

Das Kameragehäuse hat eine Art “Schlitten” integriert, auf dem der Raspberry Pi installiert war und wieder installiert wird.

Der Kamerahalter ist separat gedruckt und wird mittels Aceton an die Kamerarückseite “geklebt”. Darin wird die Kamera mit zwei kleinen Klebetropfen nochmals fixiert. Alle Teile sind aus ABS gedruckt.

Montierte Kamera von der Rückseite

Bisher hatte der Raspberry Pi ein Gehäuse, dass auch oben zu war. Das neue “Gehäuse” ist ausschließlich eine Halterung, die oben offen ist, damit die Abwärme des Raspberry Pis besser in das Gehäuse abgegeben werden kann.

Raspberry Pi in der Halterung
3D Modell – Raspberry Pi 2 – Halterung

Die Stromversorgung bleibt wie bisher auch über den Step Down Wandler bestehen. Allerdings war es bisher so, dass ein langes LAN-Kabel von unten bis in die Kamera geführt wurde. Das ist sehr wartungsunfreundlich, deshalb wurde das geändert, dazu später. Da ich keine flexiblen LAN-Kabel für den Außeneinsatz gefunden habe, habe ich ein flexibles Kabel für innen mit einem für den Außeneinsatz geeigneten, selbst verschweißenden Band umwickelt und durch eine Kabeldurchführung ins Gehäuse geführt.

Schwarz mit Band eingewickelt das Netzwerkkabel.

Wie bereits erwähnt, werden kurze Netzwerkkabel verwendet, um die Kameras mit dem Netzwerk zu verbinden.

Zum Mast hoch geht nur noch ein Cat7 Kabel, das für den Außeneinsatz geeignet ist. Am Mast befindet sich ein Verteiler, an dem das achtdrähtige Cat7 Kabel über ein Patchpanel im inneren der Box in zwei LAN-Verbindungen aufgesplittet wird, die dann jeweils zu den Kameras gehen. Das Gehäuse wird mittels einer Masthalterung am Mast befestigt.

Offenes Gehäuse für die LAN-Anschlüsse, links und in der Mitte die beiden schwarzen Anschlüsse, an die die Kameras angeschlossen werden, ganz rechts die Kabeldurchführung für das Cat7 Außenkabel.

Nachdem sich Erfahrungsgemäß zum Teil hohe Temperaturen im Gehäuse bilden können, vornehmlich im Sommer, habe ich eine entsprechende Durchlüftung im Gehäuse eingebaut. Die Belüftung muss so beschaffen sein, dass kein Regen in der Gehäuse eindringen kann, auch wenn hohe Windgeschwindigkeiten vorliegen. Das habe ich so gelöst, dass ich im Inneren des Gehäuses “Türme” eingebaut habe, die zwar Luftzirkulation zulassen, nicht jedoch das eindringen von Wasser. Genauso gibt es in der Mitte unter dem Raspberry Pi eine mit einem Kunststoffplättchen teilüberdeckte Öffnung, durch die ggf. eindringendes Wasser entweichen kann (nicht zu sehen).

In weiß zu sehen die Belüftungsöffnungen

Und so sieht die fertige Kamera offen aus:

Fertige Kamera offen von oben

Nachdem der Innenausbau fertig ist, habe ich noch das Vorderteil und Hinterteil abgeschliffen und lackiert, da diese von der Witterung angegriffen waren. Zudem habe ich das “Dach” aus Aluminium abgeschliffen und lackiert (weiß + Klarlack), da es nicht mehr schön aussah.

Fertige Kamera von der Seite
Fertige Kamera von vorne

Am Mast montiert sehen die Kameras mit der LAN-Box wie folgt aus:

Foto: DF1GT – Kameras am Mast montiert.

Software

Auf dem Raspberry Pi läuft Raspbian.

Ich könnte hier einiges über die eingesetzten Technologien und Software schreiben. Das wirklich wichtige ist, dass häufig wechselnde Daten nicht auf die SD Karte geschrieben werden sollten, sondern in eine RAM-Disk. So sind viele meiner Logs, sowie das Webcambild konfiguriert.

Beispielauszug für die Einrichtung eines Webcambildes auf einer RAM-Disk:

#!/bin/sh


mkdir /mnt/RAMDisk

echo "" >> /etc/fstab
echo "tmpfs /mnt/RAMDisk tmpfs nodev,nosuid,size=5M 0 0" >> /etc/fstab
echo "" >> /etc/fstab

mount -a

df

Danach könnte das Ergebnis wie folgt aussehen, was der Befehl “df” auswirft:

Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/root       15019440 2223292  12151476  16% /
devtmpfs          413172       0    413172   0% /dev
tmpfs             446452       0    446452   0% /dev/shm
tmpfs             178584     572    178012   1% /run
tmpfs               5120       4      5116   1% /run/lock
tmpfs               5120     448      4672   9% /mnt/RAMDisk
/dev/mmcblk0p1    258095   50413    207682  20% /boot
tmpfs              89288       0     89288   0% /run/user/0

Das “/mnt/RAMDisk” mit dem Filesystem “tmpfs” ist jetzt unsere RAMDisk. Da man aber nicht immer den Pfad der “Dateien” wechseln möchte bieten sich symbolische Verknüfungen an, so möchte ich mein Webcambild im Ordner “/home/pi/htdocs” vorfinden. Dafür muss ich zunächst die bereits angelegten Dateien entfernen und dann einen symbolischen Link zur RAMDisk setzen.

mkdir /home/pi/htdocs
rm /home/pi/htdocs/current.jpg
rm /home/pi/htdocs/current.jpg~
ln -s /mnt/RAMDisk/current.jpg /home/pi/htdocs/current.jpg

Das Bild kann ich dann unter dem Link “/home/pi/htdocs/current.jpg” aufrufen, beispielsweise über den Webserver auf dem Raspberry Pi.

Über “ln -s /mnt/RAMDisk/[…] […]” lassen sich weitere Verknüpfungen für Dateien anlegen, die in der RAMDisk abgelegt werden sollen.

Danach muss ich nur noch dem “raspistill” Tool mitteilen, dass es das Webcambild zukünftig in der RAMDisk ablegen soll. Dafür habe ich mein stündlich laufendes Script entsprechend modifiziert.

#!/bin/sh

killall raspivid
killall raspistill

sleep 2

nohup raspistill -o /mnt/RAMDisk/current.jpg -h 720 -w 1280 -tl 2000 -t 4000000 -q 80 -vf -hf -ex auto -n > /dev/null

Fertig. – Zur Erklärung: Ich starte über “nohup” das Tool “raspistill” im Hintergrund, sodass das Script in Gänze durchlaufen kann. Zudem muss ich die Parameter “-vf”, sowie “-hf” anhängen, um das Bild so zu drehen, dass es nicht auf dem Kopf steht, da das Kameramodul kopfstehend eingebaut wurde. Das Script wird stündlich über einen Cronjob gestartet.

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.

Categories
Raspberry Pi

Raspberry Pi 2 – Camera and Temperature / Humidity / Pressure Sensor

DHT22 + BME280
DHT22 + BME280
DHT22
Categories
Raspberry Pi

Raspberry Pi – Camera and Temperature / Humidity Sensor

DHT22
DHT22
DHT22
DHT22
DHT22