Ja, es geht hier um Cannabis und einer ESP32-Cam (mit OV5640) die über Daisy-Chain (wird so von den Herstellen genannt) die LED-Grow-Lampe zu steuern. Um Energie zu sparen und da es ein Kamera ist, auch noch alle Zeit lang ein Foto machen. Daraus kann man dann einen Zeitrafferfilm machen.
Inzwischen ist noch ein DHT22 hinzu gekommen, um die Temperatur und Luftfeuchtigkeit zu messen und dokumentieren.
Um genügend Pins frei zu haben wird die SD-Karte im 1-Bit-Modus betrieben.
Da die Kamera, wenn alles angeschlossen ist, nicht bootet. Was ganz normal ist, da einige Pins zum booten bestimmte zustände (High/Low) haben müssen. Hier habe ich einfach einen "CD4066B CMOS Quad Bilateral Switch" genommen. Der soll, jetzt noch nicht im Programm und auch nicht in der Schaltung, dann die Signale als erstes im Frei geben (DHT22 und DAC anschließen) kurz warten und dann das Programm ausführen.
#include "esp_camera.h"
#include "FS.h"
#include "SD_MMC.h"
#include <WiFi.h>
#include <WebServer.h>
#include <EEPROM.h>
#include <Wire.h>
#include <DHTesp.h> // DHT22 Bibliothek
// WiFi credentials
const char* ssid = "XXXXXXXXXX";
const char* password = "XXXXXXXXXXXX";
// WebServer
WebServer server(80);
// Camera settings
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
// DHT22 Einstellungen
#define DHTPIN 16 // Verwende GPIO 4 für den DHT22 ist aber auch Flashlight, das sollte abgeklebt oder ausgelötet werden. Oder GPIO 16, hier Bootbedingung beachten.
DHTesp dht; // DHT22 (Temperatur- und Luftfeuchtigkeitsmodul) Bibliothek
// I2C Einstellungen für PCF8591
#define SDA_PIN 12 // GPIO 12 für SDA nutzbar da SD-Karte im 1-Bit modus läuft
#define SCL_PIN 13 // GPIO 13 für SCL nutzbar da SD-Karte im 1-Bit modus läuft
#define PCF8591_I2C_ADDRESS 0x48 // Standardadresse über jumper setzbar
int analogOutput = 0; // Initalisierung des analogen Ausgangs
float analogPercent = 0; // Prozentskalierung
// Timers und SD-Karte
unsigned long lastPictureTime = 0;
int pictureInterval = 900000; // Standart minutes (900000 ms)
int pictureCounter = 0;
String lastImagePath = "";
// EEPROM Einstellungen um den Analogwert zu speichern (damit nach neustart nicht wieder auf 0 steht)
#define EEPROM_SIZE 1
// Funktion zur Konfiguration der Kamera
void startCameraServer() {
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_sccb_sda = SIOD_GPIO_NUM;
config.pin_sccb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
if (psramFound()) {
config.frame_size = FRAMESIZE_UXGA;
config.jpeg_quality = 10;
config.fb_count = 2;
} else {
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
}
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
return;
}
}
// Foto aufnehmen und auf SD-Karte speichern
void captureAndSavePicture() {
camera_fb_t *fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Camera capture failed");
return;
}
// Einzigartiger Benennung der Bilder
String path = "/picture" + String(pictureCounter++) + ".jpg";
Serial.println("Saving picture to: " + path);
// Öffne die Datei im Schreibmodus
File file = SD_MMC.open(path.c_str(), FILE_WRITE);
if (!file) {
Serial.println("Failed to open file in writing mode");
esp_camera_fb_return(fb); // Gib den Framebuffer sofort frei, wenn das Speichern fehlschlägt
return;
}
// Schreibe das Bild in die Datei
file.write(fb->buf, fb->len);
file.close(); // Schließe die Datei nach dem Schreiben
esp_camera_fb_return(fb); // Gib den Framebuffer nach dem Schreiben frei
// delay(100); // Kurze Verzögerung für das Dateisystem
lastImagePath = path; // Setze den Pfad für das neueste Bild
Serial.println("Picture saved: " + path);
}
// WebServer Hauptseite
void handleRoot() {
String html = "<html><body>";
html += "<h1>ESP32-CAM</h1>";
// Zeige das letzte Bild
if (lastImagePath != "") {
html += "<h2>Last Image</h2>";
//html += "<img src=\"/image?path=" + lastImagePath + "\" width=\"320\">"; // Bildpfad mit neuer Route
html += "<img src=\"/image?path=" + lastImagePath + "×tamp=" + String(millis()) + "\" width=\"320\">";
}
// Schaltfläche zur manuellen Bildaufnahme
html += "<button onclick=\"takePicture()\">Manuelles Bild aufnehmen</button>";
html += "<script>";
html += "function takePicture() {";
html += " fetch('/capture').then(response => {";
html += " if (response.ok) {";
html += " alert('Bild wurde aufgenommen!');";
html += " setTimeout(() => location.reload(), 500);"; // Seite neu laden nach 500ms, um das neue Bild anzuzeigen
html += " } else {";
html += " alert('Fehler beim Aufnehmen des Bildes.');";
html += " }";
html += " });";
html += "}";
html += "</script>";
// Intervall Einstellungen
html += "<h2>Set Picture Interval (min)</h2>";
html += "<form action=\"/setinterval\" method=\"POST\">";
html += "<input type=\"number\" name=\"interval\" min=\"15\" max=\"120\" step=\"15\" value=\"" + String(pictureInterval / 60000) + "\">";
html += "<input type=\"submit\" value=\"Set Interval\">";
html += "</form>";
// Analoger Ausgang
html += "<h2>Analog Output (" + String(analogPercent) + "%)</h2>";
html += "<form action=\"/setanalog\" method=\"POST\">";
html += "<input type=\"range\" name=\"analog\" min=\"0\" max=\"100\" step=\"1\" value=\"" + String(analogPercent) + "\">";
html += "<input type=\"submit\" value=\"Set Value\">";
html += "</form>";
// DHT22 Datenanzeige
TempAndHumidity data = dht.getTempAndHumidity();
html += "<h2>Temperature: " + String(data.temperature) + " °C</h2>";
html += "<h2>Humidity: " + String(data.humidity) + " %</h2>";
html += "</body></html>";
server.send(200, "text/html", html);
}
// Funktion des intervall Schalters
void handleSetInterval() {
if (server.hasArg("interval")) {
pictureInterval = server.arg("interval").toInt() * 60000; // convert to ms
lastPictureTime = millis(); // reset timer
}
server.sendHeader("Location", "/");
server.send(303);
}
// Funktion für den analog Ausgng
void handleSetAnalog() {
if (server.hasArg("analog")) {
analogPercent = server.arg("analog").toFloat();
analogOutput = map(analogPercent, 0, 100, 0, 255); // scale to 8-bit
// Senden der DAC Daten zum PCF8591
Wire.beginTransmission(PCF8591_I2C_ADDRESS);
Wire.write(0x40); // Byte zur Konfiguration des analogen Ausgangs
Wire.write(analogOutput); // DAC Ausgangs Daten (0-255)
Wire.endTransmission();
EEPROM.write(0, analogOutput);
EEPROM.commit();
}
server.sendHeader("Location", "/");
server.send(303);
}
void handleCapture() {
captureAndSavePicture(); // Bild aufnehmen und speichern
lastImagePath = "/picture" + String(pictureCounter - 1) + ".jpg"; // Aktualisiere Pfad für das letzte Bild
server.send(200, "text/plain", "Bild aufgenommen");
}
void setup() {
Serial.begin(19200);
// Initialisierung EEPROM
EEPROM.begin(EEPROM_SIZE);
analogOutput = EEPROM.read(0);
analogPercent = map(analogOutput, 0, 255, 0, 100);
// Initialisierung der I2C pins zur Kommunikation
Wire.begin(SDA_PIN, SCL_PIN); // Setze I2C auf die Pins die oben gesetzt wurden (SDA und SCL)
// Schreibe gesetzte DAC Daten in den EEPROM speicher
Wire.beginTransmission(PCF8591_I2C_ADDRESS);
Wire.write(0x40); // Byte zur Konfiguration des analogen Ausgangs
Wire.write(analogOutput);
Wire.endTransmission();
// Initialisiere den DHT22 Sensor
dht.setup(DHTPIN, DHTesp::DHT22); // Initialisiere DHT22
// Initialisiere die SD-Karte im 1-Bit Modus
if (!SD_MMC.begin("/sdcard", true)) { // 1-Bit Modus aktivieren
Serial.println("SD Card Mount Failed");
return;
}
// Initialisiere Kamera
startCameraServer();
// Mit W-LAN verbinden
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
// Ausgabe der IP-Adresse im Seriellen Monitor
Serial.println("Connected to WiFi. IP address: ");
Serial.println(WiFi.localIP());
// Starte den WebServer
server.on("/", handleRoot);
server.on("/setinterval", HTTP_POST, handleSetInterval);
server.on("/setanalog", HTTP_POST, handleSetAnalog);
server.on("/capture", HTTP_GET, handleCapture); // Neue Route für manuelle Bildaufnahme
server.on("/image", HTTP_GET, []() {
if (server.hasArg("path")) {
String path = server.arg("path");
File file = SD_MMC.open(path);
if (!file) {
server.send(404, "text/plain", "Bild nicht gefunden.");
return;
}
server.streamFile(file, "image/jpeg");
file.close();
} else {
server.send(400, "text/plain", "Pfad fehlt.");
}
});
server.begin();
// Initialisierung Bildaufnahme
captureAndSavePicture();
}
void loop() {
server.handleClient();
// Fotoaufnahme intervall Steuerung
if (millis() - lastPictureTime > pictureInterval) {
captureAndSavePicture();
lastPictureTime = millis();
}
}
Wie bekomme ich es hin, das die Schaltfläche für die manuelle Aufnahme unter dem Bild ist? Und ja, ich muss noch den Pfad für das letzte Bild speichern, damit sie beim neustart nicht wieder bei 0 beginnt.
Hilfe ist definitv erwünscht! Bin SEHR bald 50 Jahre alt und hatte über 25Jahre nichts mehr mit Programmieren zu tun. Fast alles per Copy and Paste