From f8b26a90f6c09c2575e7311b9552c8821478e540 Mon Sep 17 00:00:00 2001 From: btsimonh Date: Tue, 16 May 2023 11:21:25 +0100 Subject: [PATCH] Add mutex to many camera functions. (#18655) * Add mutex to many camera functions. * Allow stream to continue after wcinit command (and other commands which reconfigure). * Adust retries on camera init, specifically log success if it retried. Shorten messages to save rom. I have seen fail of 0x103 and 0x20002 succeed on second try. --- .../xdrv_81_esp32_webcam.ino | 85 +++++++++++++++++-- 1 file changed, 76 insertions(+), 9 deletions(-) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_81_esp32_webcam.ino b/tasmota/tasmota_xdrv_driver/xdrv_81_esp32_webcam.ino index d9708abf0..ede2ecdbf 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_81_esp32_webcam.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_81_esp32_webcam.ino @@ -96,9 +96,17 @@ * remarks for AI-THINKER * GPIO0 zero must be disconnected from any wire after programming because this pin drives the cam clock and does * not tolerate any capictive load + * the AITHINKER module does not have CAM_RESET - so if you get the camera into a bad state, power off restart is the only way out. * flash led = gpio 4 * red led = gpio 33 * optional rtsp url: rtsp://xxx.xxx.xxx.xxx:8554/mjpeg/1 + * + * SH 2023-05-14 - added mutex for many webcam functions - this is to prevent multi-threaded access to the camera functions, which + * can case error 0x105 upon re-init. + * Errors 0x103 and 0xffffffff could indicate CAM_PWDN incorrect. + * + * I2C use: if USE_I2C is enabled, you can set GPIO26 to I2c_SDA/2 and GPIO27 to I2C_SCL/2, and then use the shared I2C bus 2. + * Then you can use cmd i2cscan2 to check for camera presence. */ /*********************************************************************************************/ @@ -114,6 +122,12 @@ bool HttpCheckPriviledgedAccess(bool); extern ESP8266WebServer *Webserver; +SemaphoreHandle_t WebcamMutex = nullptr;; + +// use mutex like: +// TasAutoMutex localmutex(&WebcamMutex, "somename"); +// in any function. Will wait for mutex to be clear, and auto-release when the function exits. + #define BOUNDARY "e8b8c539-047d-4777-a985-fbba6edff11e" #ifndef MAX_PICSTORE @@ -165,6 +179,7 @@ struct { /*********************************************************************************************/ void WcInterrupt(uint32_t state) { + TasAutoMutex localmutex(&WebcamMutex, "WcInterrupt"); // Stop camera ISR if active to fix TG1WDT_SYS_RESET if (!Wc.up) { return; } @@ -189,6 +204,9 @@ bool WcPinUsed(void) { // } // } } + + AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: i2c_enabled_2: %d"), TasmotaGlobal.i2c_enabled_2); + if (!PinUsed(GPIO_WEBCAM_XCLK) || !PinUsed(GPIO_WEBCAM_PCLK) || !PinUsed(GPIO_WEBCAM_VSYNC) || !PinUsed(GPIO_WEBCAM_HREF) || ((!PinUsed(GPIO_WEBCAM_SIOD) || !PinUsed(GPIO_WEBCAM_SIOC)) && !TasmotaGlobal.i2c_enabled_2) // preferred option is to reuse and share I2Cbus 2 @@ -199,6 +217,7 @@ bool WcPinUsed(void) { } void WcFeature(int32_t value) { + TasAutoMutex localmutex(&WebcamMutex, "WcFeature"); sensor_t * wc_s = esp_camera_sensor_get(); if (!wc_s) { return; } @@ -232,6 +251,7 @@ void WcFeature(int32_t value) { } void WcApplySettings() { + TasAutoMutex localmutex(&WebcamMutex, "WcApplySettings"); sensor_t * wc_s = esp_camera_sensor_get(); if (!wc_s) { return; } @@ -308,13 +328,20 @@ void WcSetDefaults(uint32_t upgrade) { } uint32_t WcSetup(int32_t fsiz) { + TasAutoMutex localmutex(&WebcamMutex, "WcSetup"); + + AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: WcSetup")); if (fsiz >= FRAMESIZE_FHD) { fsiz = FRAMESIZE_FHD - 1; } + int stream_active = Wc.stream_active; Wc.stream_active = 0; if (fsiz < 0) { - esp_camera_deinit(); - Wc.up = 0; + if (Wc.up){ + esp_camera_deinit(); + AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: Deinit fsiz %d"), fsiz); + Wc.up = 0; + } return 0; } @@ -329,6 +356,8 @@ uint32_t WcSetup(int32_t fsiz) { camera_config_t config; + memset(&config, 0, sizeof(config)); + if (WcPinUsed()) { config.pin_d0 = Pin(GPIO_WEBCAM_DATA); // Y2_GPIO_NUM; config.pin_d1 = Pin(GPIO_WEBCAM_DATA, 1); // Y3_GPIO_NUM; @@ -408,10 +437,24 @@ uint32_t WcSetup(int32_t fsiz) { AddLog(LOG_LEVEL_DEBUG, PSTR("CAM: PSRAM not found")); } - esp_err_t err = esp_camera_init(&config); + esp_err_t err; + // cannot hurt to retry... + for (int i = 0; i < 3; i++){ + err = esp_camera_init(&config); + + if (err != ESP_OK) { + AddLog(LOG_LEVEL_INFO, PSTR("CAM: InitErr 0x%x try %d"), err, (i+1)); + esp_camera_deinit(); + } else { + if (i){ + AddLog(LOG_LEVEL_INFO, PSTR("CAM: InitOK try %d"), (i+1)); + } + break; + } + } if (err != ESP_OK) { - AddLog(LOG_LEVEL_INFO, PSTR("CAM: Init failed with error 0x%x"), err); + AddLog(LOG_LEVEL_INFO, PSTR("CAM: InitErr 0x%x"), err); return 0; } @@ -440,6 +483,9 @@ uint32_t WcSetup(int32_t fsiz) { Wc.up = 1; if (psram) { Wc.up = 2; } + // restore stream_active if we setup ok. + Wc.stream_active = stream_active; + return Wc.up; } @@ -447,6 +493,8 @@ uint32_t WcSetup(int32_t fsiz) { int32_t WcSetOptions(uint32_t sel, int32_t value) { int32_t res = 0; + TasAutoMutex localmutex(&WebcamMutex, "WcSetOptions"); + sensor_t *s = esp_camera_sensor_get(); if (!s) { return -99; } @@ -556,6 +604,8 @@ int32_t WcSetOptions(uint32_t sel, int32_t value) { } uint32_t WcGetWidth(void) { + TasAutoMutex localmutex(&WebcamMutex, "WcGetWidth"); + camera_fb_t *wc_fb = esp_camera_fb_get(); if (!wc_fb) { return 0; } Wc.width = wc_fb->width; @@ -564,6 +614,7 @@ uint32_t WcGetWidth(void) { } uint32_t WcGetHeight(void) { + TasAutoMutex localmutex(&WebcamMutex, "WcGetWidth"); camera_fb_t *wc_fb = esp_camera_fb_get(); if (!wc_fb) { return 0; } Wc.height = wc_fb->height; @@ -595,6 +646,7 @@ uint32_t WcSetMotionDetect(int32_t value) { void WcDetectMotion(void) { camera_fb_t *wc_fb; uint8_t *out_buf = 0; + TasAutoMutex localmutex(&WebcamMutex, "WcDetectMotion"); if ((millis()-wc_motion.motion_ltime) > wc_motion.motion_detect) { wc_motion.motion_ltime = millis(); @@ -651,6 +703,7 @@ uint32_t WcGetFrame(int32_t bnum) { uint8_t * _jpg_buf = NULL; camera_fb_t *wc_fb = 0; bool jpeg_converted = false; + TasAutoMutex localmutex(&WebcamMutex, "WcGetFrame"); if (bnum < 0) { if (bnum < -MAX_PICSTORE) { bnum=-1; } @@ -753,6 +806,8 @@ void HandleImage(void) { response += "Content-type: image/jpeg\r\n\r\n"; Webserver->sendContent(response); + TasAutoMutex localmutex(&WebcamMutex, "HandleImage"); + if (!bnum) { size_t _jpg_buf_len = 0; uint8_t * _jpg_buf = NULL; @@ -804,6 +859,7 @@ void HandleImageBasic(void) { } } + TasAutoMutex localmutex(&WebcamMutex, "HandleImage"); camera_fb_t *wc_fb; wc_fb = esp_camera_fb_get(); // Acquire frame if (!wc_fb) { @@ -874,6 +930,9 @@ void HandleWebcamMjpegTask(void) { "\r\n"); Wc.stream_active = 2; } + + TasAutoMutex localmutex(&WebcamMutex, "HandleWebcamMjpegTask"); + if (2 == Wc.stream_active) { wc_fb = esp_camera_fb_get(); if (!wc_fb) { @@ -947,12 +1006,14 @@ void HandleWebcamRoot(void) { /*********************************************************************************************/ uint32_t WcSetStreamserver(uint32_t flag) { - if (TasmotaGlobal.global_state.network_down) { return 0; } - - Wc.stream_active = 0; + if (TasmotaGlobal.global_state.network_down) { + Wc.stream_active = 0; + return 0; + } if (flag) { if (!Wc.CamServer) { + Wc.stream_active = 0; Wc.CamServer = new ESP8266WebServer(81); Wc.CamServer->on("/", HandleWebcamRoot); Wc.CamServer->on("/cam.mjpeg", HandleWebcamMjpeg); @@ -963,6 +1024,7 @@ uint32_t WcSetStreamserver(uint32_t flag) { } } else { if (Wc.CamServer) { + Wc.stream_active = 0; Wc.CamServer->stop(); delete Wc.CamServer; Wc.CamServer = NULL; @@ -973,8 +1035,13 @@ uint32_t WcSetStreamserver(uint32_t flag) { } void WcInterruptControl() { + TasAutoMutex localmutex(&WebcamMutex, "WcInterruptControl"); + WcSetStreamserver(Settings->webcam_config.stream); - if(Wc.up == 0) {WcSetup(Settings->webcam_config.resolution);} + if(Wc.up == 0) { + WcSetup(Settings->webcam_config.resolution); + } + } /*********************************************************************************************/ @@ -1382,7 +1449,7 @@ void CmndWebcamClock(void){ } void CmndWebcamInit(void) { - Wc.up = 0; + WcSetup(Settings->webcam_config.resolution); WcInterruptControl(); ResponseCmndDone(); }