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>
245 lines
6.7 KiB
C++
245 lines
6.7 KiB
C++
#include <Servo.h>
|
|
#include <ESP8266WiFi.h>
|
|
#include <WiFiClientSecureBearSSL.h>
|
|
#include <ESP8266HTTPClient.h>
|
|
#include <ESP8266httpUpdate.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"
|
|
// WiFi — mismas credenciales que usa el ESP32-CAM
|
|
#define WIFI_SSID "Natxo"
|
|
#define WIFI_PASS "Suprak1llm1ndS23S"
|
|
// ================================================================
|
|
|
|
// ==== Pines motores ====
|
|
const int ENA = D1;
|
|
const int ENB = D5;
|
|
const int IN1 = D6;
|
|
const int IN2 = D7;
|
|
const int IN3 = D2;
|
|
const int IN4 = D0;
|
|
|
|
// ==== Pines servos ====
|
|
const int SERVO_X_PIN = D3;
|
|
const int SERVO_Y_PIN = D4;
|
|
|
|
// ==== Pines buzzer ====
|
|
const int buzzerPin = D8;
|
|
|
|
Servo servoX;
|
|
Servo servoY;
|
|
|
|
int currentSpeed = 150;
|
|
int posX = 90;
|
|
int posY = 90;
|
|
|
|
// ── OTA: comprueba si hay firmware nuevo y actualiza si hace falta ──
|
|
static void checkOTA() {
|
|
WiFi.mode(WIFI_STA);
|
|
WiFi.begin(WIFI_SSID, WIFI_PASS);
|
|
|
|
int tries = 0;
|
|
while (WiFi.status() != WL_CONNECTED && tries < 20) {
|
|
delay(500);
|
|
tries++;
|
|
}
|
|
|
|
if (WiFi.status() != WL_CONNECTED) {
|
|
WiFi.disconnect();
|
|
WiFi.mode(WIFI_OFF);
|
|
return; // sin WiFi, arranque normal
|
|
}
|
|
|
|
BearSSL::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/esp8266/";
|
|
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) {
|
|
body.trim();
|
|
int remoteVer = body.toInt();
|
|
if (remoteVer > FW_VERSION) {
|
|
// 4 pitidos = ESP8266 actualizando
|
|
pinMode(buzzerPin, OUTPUT);
|
|
for (int i = 0; i < 4; i++) {
|
|
digitalWrite(buzzerPin, HIGH);
|
|
delay(150);
|
|
digitalWrite(buzzerPin, LOW);
|
|
delay(150);
|
|
}
|
|
delay(300);
|
|
|
|
// Fuerza pines de motor a LOW antes del flash para minimizar glitch de GPIO
|
|
analogWrite(ENA, 0); analogWrite(ENB, 0);
|
|
digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
|
|
digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);
|
|
|
|
BearSSL::WiFiClientSecure updateClient;
|
|
updateClient.setInsecure();
|
|
ESPhttpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
|
ESPhttpUpdate.rebootOnUpdate(true);
|
|
ESPhttpUpdate.update(updateClient, base + "firmware.bin" + token);
|
|
// Si llega aquí, el update falló — continúa arranque normal
|
|
}
|
|
}
|
|
|
|
WiFi.disconnect();
|
|
WiFi.mode(WIFI_OFF); // apaga radio para ahorrar energía
|
|
}
|
|
|
|
bool espReady = false;
|
|
|
|
// ── Handshake con ESP32-CAM ──────────────────────────────────────
|
|
// Descarta basura del bootloader ROM (115200 baud → bytes aleatorios),
|
|
// luego espera exactamente "GO\n" y responde "OK\n".
|
|
void waitForReady() {
|
|
// Descarta todo lo que haya en el buffer (logs de arranque del ESP32)
|
|
delay(300);
|
|
while (Serial.available()) Serial.read();
|
|
|
|
// Espera "GO\n" del ESP32-CAM (máx 90s por si está haciendo OTA)
|
|
unsigned long timeout = millis() + 90000UL;
|
|
String buf = "";
|
|
while (millis() < timeout) {
|
|
while (Serial.available()) {
|
|
char c = Serial.read();
|
|
if (c == '\n') {
|
|
buf.trim();
|
|
if (buf == "GO") {
|
|
Serial.print("OK\n");
|
|
delay(50);
|
|
while (Serial.available()) Serial.read(); // flush post-handshake
|
|
espReady = true;
|
|
return;
|
|
}
|
|
buf = "";
|
|
} else if (c != '\r') {
|
|
if (buf.length() < 8) buf += c;
|
|
}
|
|
}
|
|
delay(10);
|
|
}
|
|
espReady = true; // timeout: arranca igual
|
|
}
|
|
|
|
void setup() {
|
|
// 1. Pines y parada inmediata
|
|
pinMode(ENA, OUTPUT); pinMode(ENB, OUTPUT);
|
|
pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT);
|
|
pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT);
|
|
stopMotors();
|
|
|
|
// 2. Serial temprano (el ESP32 puede mandar el pitido de OTA)
|
|
Serial.begin(9600);
|
|
|
|
// 3. OTA via WiFi
|
|
checkOTA();
|
|
|
|
// 4. Resto de periféricos
|
|
analogWriteRange(255);
|
|
analogWriteFreq(1000);
|
|
servoX.attach(SERVO_X_PIN);
|
|
servoY.attach(SERVO_Y_PIN);
|
|
pinMode(buzzerPin, OUTPUT);
|
|
servoX.write(posX);
|
|
servoY.write(posY);
|
|
|
|
// 5. Esperar señal '!' del ESP32-CAM antes de procesar comandos
|
|
waitForReady();
|
|
}
|
|
|
|
void loop() {
|
|
if (Serial.available()) {
|
|
char c = Serial.read();
|
|
|
|
// Detecta re-arranque del ESP32-CAM: envía "GO\n" de nuevo
|
|
if (c == 'G') {
|
|
Serial.readStringUntil('\n'); // consume "O\n"
|
|
Serial.print("OK\n");
|
|
delay(50);
|
|
while (Serial.available()) Serial.read();
|
|
espReady = true;
|
|
return;
|
|
}
|
|
|
|
// Ignorar todo hasta completar el handshake
|
|
if (!espReady) return;
|
|
|
|
if (c == 'F') forward();
|
|
else if (c == 'B') backward();
|
|
else if (c == 'L') left();
|
|
else if (c == 'R') right();
|
|
else if (c == 'S') stopMotors();
|
|
|
|
else if (c == 'V') {
|
|
int val = Serial.parseInt();
|
|
if (val >= 0 && val <= 255) currentSpeed = val;
|
|
}
|
|
else if (c == 'X') {
|
|
int val = Serial.parseInt();
|
|
if (val >= 0 && val <= 180) { posX = val; servoX.write(posX); }
|
|
}
|
|
else if (c == 'Y') {
|
|
int val = Serial.parseInt();
|
|
if (val >= 0 && val <= 180) { posY = val; servoY.write(posY); }
|
|
}
|
|
else if (c == 'H') {
|
|
int val = Serial.parseInt();
|
|
digitalWrite(buzzerPin, val > 0 ? HIGH : LOW);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ==== Funciones de movimiento ====
|
|
void forward() {
|
|
analogWrite(ENA, currentSpeed);
|
|
analogWrite(ENB, currentSpeed);
|
|
digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH);
|
|
digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH);
|
|
}
|
|
|
|
void backward() {
|
|
analogWrite(ENA, currentSpeed);
|
|
analogWrite(ENB, currentSpeed);
|
|
digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);
|
|
digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);
|
|
}
|
|
|
|
void left() {
|
|
analogWrite(ENA, currentSpeed);
|
|
analogWrite(ENB, currentSpeed);
|
|
digitalWrite(IN1, LOW); digitalWrite(IN2, HIGH);
|
|
digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);
|
|
}
|
|
|
|
void right() {
|
|
analogWrite(ENA, currentSpeed);
|
|
analogWrite(ENB, currentSpeed);
|
|
digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);
|
|
digitalWrite(IN3, LOW); digitalWrite(IN4, HIGH);
|
|
}
|
|
|
|
void stopMotors() {
|
|
analogWrite(ENA, 0);
|
|
analogWrite(ENB, 0);
|
|
digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
|
|
digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);
|
|
}
|