ESP32-CAM: - handshakeTask() corre ahora en tarea FreeRTOS separada. setup() termina de inmediato, el servidor web arranca sin bloqueos. El watchdog de hardware ya no puede dispararse (antes setup() bloqueaba hasta 30s con readStringUntil que no cedia el CPU). ESP8266: - Antes del flash OTA, fuerza ENA=ENB=0 e IN1-IN4=LOW para reducir el glitch de pines GPIO durante el proceso de escritura en flash. (La solucion definitiva es resistencias pull-down en ENA/ENB.) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
183 lines
5.7 KiB
C++
183 lines
5.7 KiB
C++
#define CAMERA_DEBUG 0
|
|
#include "esp_camera.h"
|
|
#include <WiFi.h>
|
|
#include <WiFiManager.h>
|
|
#include <HTTPClient.h>
|
|
#include <HTTPUpdate.h>
|
|
#include <WiFiClientSecure.h>
|
|
#include "board_config.h"
|
|
|
|
// ================================================================
|
|
// OTA CONFIG
|
|
// ================================================================
|
|
#define FW_VERSION 9
|
|
#define GITEA_HOST "https://git.nacho.myds.me"
|
|
#define GITEA_OWNER "Natxo"
|
|
#define GITEA_REPO "Arduino-Car"
|
|
#define GITEA_BRANCH "main"
|
|
#define GITEA_TOKEN "d3b0b32eb0311c9b4ef569f22c8392c373ff3f9f"
|
|
// ================================================================
|
|
|
|
const char *ssid = "Natxo";
|
|
const char *password = "Suprak1llm1ndS23S";
|
|
|
|
void startCameraServer();
|
|
void setupLedFlash();
|
|
|
|
// ── Handshake bidireccional — tarea FreeRTOS (no bloquea setup) ─
|
|
// Corre en paralelo con el servidor web. Envía "GO\n" cada 300ms
|
|
// hasta recibir "OK\n" o agotar 30s.
|
|
static void handshakeTask(void *) {
|
|
unsigned long deadline = millis() + 30000UL;
|
|
String buf = "";
|
|
while (millis() < deadline) {
|
|
Serial.print("GO\n");
|
|
unsigned long t = millis();
|
|
while (millis() - t < 300) {
|
|
while (Serial.available()) {
|
|
char c = Serial.read();
|
|
if (c == '\n') {
|
|
buf.trim();
|
|
if (buf == "OK") { vTaskDelete(NULL); return; }
|
|
buf = "";
|
|
} else if (c != '\r' && buf.length() < 8) {
|
|
buf += c;
|
|
}
|
|
}
|
|
vTaskDelay(pdMS_TO_TICKS(5));
|
|
}
|
|
}
|
|
vTaskDelete(NULL); // timeout
|
|
}
|
|
|
|
// ── OTA: comprueba si hay firmware nuevo y actualiza si hace falta ──
|
|
static void checkOTA() {
|
|
WiFiClientSecure client;
|
|
client.setInsecure(); // cert Let's Encrypt/autofirmado — OK para LAN privada
|
|
HTTPClient http;
|
|
|
|
String base = String(GITEA_HOST) + "/" + GITEA_OWNER + "/" + GITEA_REPO
|
|
+ "/raw/branch/" + GITEA_BRANCH + "/firmware/esp32cam/";
|
|
String token = "?token=" + String(GITEA_TOKEN);
|
|
|
|
http.begin(client, base + "version.txt" + token);
|
|
http.setTimeout(10000);
|
|
int code = http.GET();
|
|
String body;
|
|
if (code == HTTP_CODE_OK) body = http.getString();
|
|
http.end();
|
|
|
|
if (code != HTTP_CODE_OK) return; // Gitea no disponible, continúa normal
|
|
|
|
body.trim();
|
|
int remoteVer = body.toInt();
|
|
if (remoteVer <= FW_VERSION) return; // ya tenemos la última versión
|
|
|
|
// 3 pitidos = ESP32-CAM actualizando (via Serial al ESP8266)
|
|
for (int i = 0; i < 3; i++) {
|
|
Serial.print("H1");
|
|
delay(150);
|
|
Serial.print("H0");
|
|
delay(150);
|
|
}
|
|
delay(300);
|
|
|
|
// Hay versión nueva → descargar y flashear
|
|
WiFiClientSecure updateClient;
|
|
updateClient.setInsecure();
|
|
httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
|
httpUpdate.rebootOnUpdate(true);
|
|
httpUpdate.update(updateClient, base + "firmware.bin" + token);
|
|
// Si llega aquí, el update falló — continúa arranque normal
|
|
}
|
|
|
|
void setup() {
|
|
Serial.begin(9600);
|
|
Serial.setDebugOutput(false);
|
|
|
|
// ── Cámara ──────────────────────────────────────────────────────
|
|
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.frame_size = FRAMESIZE_UXGA;
|
|
config.pixel_format = PIXFORMAT_JPEG;
|
|
config.grab_mode = CAMERA_GRAB_LATEST; // siempre el frame más reciente → menos lag
|
|
config.fb_location = CAMERA_FB_IN_PSRAM;
|
|
config.jpeg_quality = 12;
|
|
config.fb_count = 1;
|
|
|
|
if (config.pixel_format == PIXFORMAT_JPEG) {
|
|
if (psramFound()) {
|
|
config.jpeg_quality = 10;
|
|
config.fb_count = 2;
|
|
} else {
|
|
config.frame_size = FRAMESIZE_SVGA;
|
|
config.fb_location = CAMERA_FB_IN_DRAM;
|
|
config.fb_count = 2;
|
|
config.jpeg_quality = 18;
|
|
}
|
|
}
|
|
|
|
esp_err_t err = esp_camera_init(&config);
|
|
if (err != ESP_OK) return;
|
|
|
|
sensor_t *s = esp_camera_sensor_get();
|
|
if (s->id.PID == OV3660_PID) {
|
|
s->set_vflip(s, 1);
|
|
s->set_brightness(s, 1);
|
|
s->set_saturation(s, -2);
|
|
}
|
|
if (config.pixel_format == PIXFORMAT_JPEG) {
|
|
s->set_framesize(s, FRAMESIZE_QVGA);
|
|
}
|
|
|
|
#if defined(CAMERA_MODEL_M5STACK_WIDE) || defined(CAMERA_MODEL_M5STACK_ESP32CAM)
|
|
s->set_vflip(s, 1);
|
|
s->set_hmirror(s, 1);
|
|
#endif
|
|
#if defined(CAMERA_MODEL_ESP32S3_EYE)
|
|
s->set_vflip(s, 1);
|
|
#endif
|
|
|
|
#if defined(LED_GPIO_NUM)
|
|
setupLedFlash();
|
|
#endif
|
|
|
|
// ── WiFi ─────────────────────────────────────────────────────────
|
|
WiFiManager wm;
|
|
wm.setDebugOutput(false);
|
|
if (!wm.autoConnect("CocheRC", "12345678")) {
|
|
ESP.restart();
|
|
}
|
|
|
|
// ── OTA (después de WiFi, antes de iniciar el servidor) ──────────
|
|
checkOTA();
|
|
|
|
// ── Servidor cámara ───────────────────────────────────────────────
|
|
startCameraServer();
|
|
|
|
// Handshake en tarea separada — no bloquea setup() ni el watchdog
|
|
xTaskCreate(handshakeTask, "handshake", 4096, NULL, 1, NULL);
|
|
}
|
|
|
|
void loop() {
|
|
delay(10000);
|
|
}
|