/********* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp32-cam-video-streaming-web-server-camera-home-assistant/ IMPORTANT!!! - Select Board "AI Thinker ESP32-CAM" - GPIO 0 must be connected to GND to upload a sketch - After connecting GPIO 0 to GND, press the ESP32-CAM on-board RESET button to put your board in flashing mode Anpassung an tuc-special sowie Allgemeinfall (nicht so'n Murks mit dem Eincompilieren von SSID+Passphrase): -Videotyp an URL *********/ #include <esp_camera.h> #include <WiFi.h> #include <esp_timer.h> #include <img_converters.h> #include <Arduino.h> #include <fb_gfx.h> #include <soc/soc.h> //disable brownout problems #include <soc/rtc_cntl_reg.h> //disable brownout problems #include <esp_http_server.h> #include <Preferences.h> #include <WiFiMulti.h> #include "input.h" struct POINTS{ int16_t x,y; }; static const POINTS framesizes[]={ // Zuordnung Enum-Nummer zu Größe im esp32 {96,96}, //0 1:1 ISDN? 9216 {160,120}, //1 4:3 QQVGA 19200 {176,144}, //2 11:9 QCIF 25344 {240,176}, //3 15:11 HQVGA 42240 {240,240}, //4 1:1 GSM? 57600 {320,240}, //5 4:3 QVGA 76800 {400,296}, //6 50:37 CIF 118400 {480,320}, //7 3:2 HVGA 153600 {640,480}, //8 4:3 VGA 307200 OV7670 {800,600}, //9 4:3 SVGA 480000 {1024,768}, //10 4:3 XGA 786432 {1280,720}, //11 16:9 HD 921600 {1280,1024}, //12 5:4 SXGA 1310720 {1600,1200}, //13 4:3 UXGA 1920000 OV2640 {1920,1080}, //14 16:9 FHD 2073600 {720,1280}, //15 9:16 P_HD 921600 {864,1536}, //16 9:16 P_3MP 1327104 {2048,1536}, //17 4:3 QXGA 3145728 {2560,1440}, //18 16:9 QHD 3686400 {2560,1600}, //19 8:5 WQXGA 4096000 {1080,1920}, //20 9:16 P_FHD 2073600 {2560,1920}, //21 4:3 QSXGA 4915200 }; static bool setupCam(framesize_t framesize, unsigned lossness) { // framesize = 0..13, lossness = 0..63 camera_config_t config; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = 5; config.pin_d1 = 18; config.pin_d2 = 19; config.pin_d3 = 21; config.pin_d4 = 36; config.pin_d5 = 39; config.pin_d6 = 34; config.pin_d7 = 35; config.pin_xclk = 0; config.pin_pclk = 22; config.pin_vsync = 25; config.pin_href = 23; config.pin_sccb_sda = 26; config.pin_sccb_scl = 27; config.pin_pwdn = 32; config.pin_reset = -1; config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG; const char*mitohne; if (psramFound()){ if (framesize>FRAMESIZE_UXGA) framesize=FRAMESIZE_UXGA; mitohne="mit"; config.frame_size = framesize; config.jpeg_quality = lossness; config.fb_count = 2; }else{ if (framesize>FRAMESIZE_SVGA) framesize=FRAMESIZE_SVGA; mitohne="ohne"; if (lossness<12) lossness=12; config.frame_size = framesize; config.jpeg_quality = lossness; config.fb_count = 1; } Serial.printf("Kamerainitialisierung %s PSRAM, %ux%u\n",mitohne,framesizes[framesize].x,framesizes[framesize].y); // Camera init esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Kamerainitialisierungs-Fehlerkode 0x%X!\n", err); return false; } return true; } #define PART_BOUNDARY "Omega" static httpd_handle_t stream_httpd; static esp_err_t stream_handler(httpd_req_t *req) { char s[32]; *s=0; httpd_req_get_url_query_str(req,s,sizeof s); unsigned fmt=13,ms=0,qual=80; // Bildgröße (13 = maximal), Millisekunden (von Bild zu Bild um WLAN-Bandbreite zu sparen) ~ Bildrate, JPEG-Qualität (in Prozent? 0..100) sscanf(s,"%u,%u,%u",&fmt,&ms,&qual); Serial.println("Stream-Handler startet."); setupCam(framesize_t(fmt),(100U-qual)>>1); // widersprüchliche Qualitätsangaben esp_err_t res = httpd_resp_set_type(req,"multipart/x-mixed-replace;boundary=" PART_BOUNDARY); bool msgdone=false; while (res == ESP_OK){ unsigned tic=millis(); camera_fb_t*fb = esp_camera_fb_get(); if (!fb) { Serial.println("Camera capture failed"); res = ESP_FAIL; break; } size_t jpg_buf_len = 0; byte*jpg_buf = 0; if (fb->width > 400 && fb->format != PIXFORMAT_JPEG) { // Größere Bilder kommen wohl manchmal unkomprimiert? if (!msgdone) { Serial.println("Manuelle JPEG-Kompression, Bilddaten kommen als Bixmap vom Sensor"); msgdone=true; } bool jpeg_converted = frame2jpg(fb, qual, &jpg_buf, &jpg_buf_len); esp_camera_fb_return(fb); fb = 0; if (!jpeg_converted){ Serial.println("JPEG compression failed"); res = ESP_FAIL; break; } } jpg_buf_len = fb->len; jpg_buf = fb->buf; char part_buf[64]; size_t hlen = snprintf(part_buf,sizeof part_buf, "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n", jpg_buf_len); if (res == ESP_OK) res=httpd_resp_send_chunk(req,part_buf,hlen); if (res == ESP_OK) res=httpd_resp_send_chunk(req,reinterpret_cast<char*>(jpg_buf),jpg_buf_len); // Binärkram durchwursteln static const char STREAM_BOUNDARY[] = "\r\n--" PART_BOUNDARY "\r\n"; // (Zusätzliche) Ende-Kennung if (res == ESP_OK) res=httpd_resp_send_chunk(req,STREAM_BOUNDARY,strlen(STREAM_BOUNDARY)); if (fb) esp_camera_fb_return(fb); else free(jpg_buf); while (millis()-tic<ms) ; // warten für konstante, reduzierte Frame-Rate (igitt! Geht das nicht besser?) }; Serial.println("Stream-Handler beendet."); return res; } static void startCameraServer(){ httpd_config_t config = HTTPD_DEFAULT_CONFIG(); config.server_port = 80; httpd_uri_t index_uri = { .uri = "/", .method = HTTP_GET, .handler = stream_handler, .user_ctx = 0 }; //Serial.printf("Starting web server on port: '%d'\n", config.server_port); if (httpd_start(&stream_httpd, &config) == ESP_OK) { httpd_register_uri_handler(stream_httpd, &index_uri); } } static WiFiMulti wifiMulti; typedef WifiAPlist_t Cred; static auto&creds=wifiMulti.APlist; static auto curcred=creds.begin(); struct CbDat{ char sbuf[64]; static KeyCbRet cb(int chr,void*p) {return (*(CbDat*)p)(chr);} KeyCbRet operator()(int); }; // Lambdas als Funktionszeiger sind möglicherweise zu plump // Daher hier ein gebasteltes Lambda KeyCbRet CbDat::operator()(int chr) { switch (chr) { case 'P'-'@': curcred = curcred==creds.begin() ? creds.end()-1 : curcred-1; return clear_call_again; case 'N'-'@': curcred = curcred==creds.end()-1 ? creds.begin() : curcred+1; return clear_call_again; case -1: strcpy(sbuf,curcred->ssid); return string_replaced; } return not_handled; } // Eigene Präferenzen mit load() und save() static struct MyPref:public Preferences{ void load(); void save(); }preferences; void MyPref::load() { begin("credentials"); for(int i=0;;i++) { char buf1[12],buf2[12]; snprintf(buf1,sizeof buf1,"ssid%u",i); snprintf(buf2,sizeof buf2,"pass%u",i); Cred cred={ getString(buf1).c_str(), getString(buf2).c_str() }; if (!cred.ssid) break; if (*cred.ssid) break; if (cred.pass && !*cred.pass) cred.pass=0; creds.push_back(cred); } } void MyPref::save() { clear(); for(size_t i=0; i<creds.size(); i++) { auto&cred=creds[i]; char buf1[12],buf2[12]; snprintf(buf1,sizeof buf1,"ssid%u",i); snprintf(buf2,sizeof buf2,"pass%u",i); putString(buf1,cred.ssid); putString(buf2,cred.pass); } } void queryCredentials() { termcap.setattr(4); // unterstreichen Serial.print("WLAN-Infrastruktur-Zugang muss konfiguriert werden!"); termcap.setattr(); // unterstreichen aus Serial.println(); int n; do n=WiFi.scanComplete(); while (n==-1); // Returnwert für "Scannen in Arbeit" (sollte vorbei sein) Serial.print(n); Serial.println(" Hotspots gefunden"); // if (n==-2) WiFi. struct Hotspot{ char encr,rssi; String ssid; int compare(Hotspot const&r) {return strcmp(ssid.c_str(),r.ssid.c_str());} bool operator==(Hotspot const&r) {return compare(r)==0;} bool operator<(Hotspot const&r) {return compare(r)<0;} }; std::vector<Hotspot> hotspots; // Dusseligerweise werden (in der Uni) viele Hotspots mit gleichem Namen gefunden. // Daher wäre die Liste theoretisch zu sortieren und Duplikate zu filtern. // Hier werden Mehrfachnennungen per Doppelschleife verhindert: Langsam aber schnell genug und RAM sparend. if (n>0) hotspots.reserve(n); for (size_t i=0; i<hotspots.size(); i++) { auto&hotspot=hotspots[i]; hotspot.encr=WiFi.encryptionType(i); hotspot.rssi=WiFi.RSSI(i); hotspot.ssid=WiFi.SSID(i); } std::sort(hotspots.begin(),hotspots.end()); hotspots.erase(std::unique(hotspots.begin(),hotspots.end()),hotspots.end()); for (auto const&hotspot:hotspots) { String lock="🔒"; if (hotspot.encr == WIFI_AUTH_OPEN) lock="🔓"; else{ auto f=std::find(creds.begin(),creds.end(),hotspot.ssid.c_str()); if (f!=creds.end()) { curcred=f; // der letzte Match wird genommen lock="🔐"; // Schlüssel vorhanden } } Serial.print(lock); Serial.println(hotspot.ssid); } // Die MAC-Adresse auszuspucken ist für tuc-special der TU Chemnitz gedacht. // Mit dieser kann man im IdM-Portal (früher: mouse.hrz) einen Zugang beantragen // und bekommt innerhalb von Sekunden auch eine Freischaltung mitsamt Passwort. Serial.print("Diese MAC-Adresse: "); Serial.println(WiFi.macAddress()); // Mit den vertikalen Kursortasten kann eine der Vorgaben aus der Liste der gefundenen WLANs ausgewählt werden CbDat cbdat; cbdat.sbuf[0]=0; if (curcred!=creds.end()) strcpy(cbdat.sbuf,curcred->ssid); // voraus ausfüllen for(;;){ input("WLAN-Netzwerkname",cbdat.sbuf,sizeof cbdat.sbuf,n ? CbDat::cb : 0, &cbdat); if (cbdat.sbuf[0]) break; } Cred cred={strdup(cbdat.sbuf),0}; input(" Zugangsschlüssel",cbdat.sbuf,sizeof cbdat.sbuf); if (*cbdat.sbuf) cred.pass=strdup(cbdat.sbuf); creds.push_back(cred); Serial.println("Angaben werden gespeichert."); preferences.save(); }; void setup() { // Wer/was auch immer noch etws VORHER in die Konsole kritzelt ... WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector Serial.begin(115200); Serial.setDebugOutput(false); // Serielle Verbindung auf Vorhandensein von PuTTY testen termcap.init(); // WLAN-Verbindung aufbauen preferences.load(); WiFi.mode(WIFI_STA); WiFi.scanNetworks(true,false); // Im Hintergrund bereits anfangen zu suchen for (auto const&cred:creds) wifiMulti.addAP(cred.ssid,cred.pass); } void loop() { switch (Serial.read()) { case 'C'-'@': preferences.clear(); [[fallthrough]]; case 'R'-'@': setup(); // ^R wirkt wie Reset } if (wifiMulti.run()!=WL_CONNECTED) queryCredentials(); for(;;) { // WiFi.begin(curcred->ssid,curcred->pass); char intime=20; // 10 Sekunden do{ if (WiFi.status() == WL_CONNECTED) break; Serial.print("."); delay(500); }while(--intime); if (intime) break; Serial.println(" Zeitüberschreitung!"); queryCredentials(); } Serial.println(); Serial.println("Verbindung mit WLAN-Router hergestellt."); Serial.print("Kamera bereit! Bild im Browser durch Aufruf von: "); termcap.setattr(1); // fett Serial.print("http://"); Serial.print(WiFi.localIP()); Serial.print("/"); termcap.setattr(); // fett aus Serial.println("Größe(0..13),Qualität(80-90),Wiederholrate(ms)"); // Start streaming web server startCameraServer(); }
Detected encoding: UTF-8 | 0 |