Bug: en v16 se elimino if(!espReady)return del loop() y no se restauro en v17. Resultado: bytes basura del bootloader ESP32 (ej 0x42='B') se ejecutaban como backward() antes y despues del handshake. Fixes: - Restaurado bool espReady con guard if(!espReady)return en loop() - Restaurado handler 'G' en loop() para reinicio del ESP32: espReady=false + stopMotors() inmediato + nuevo handshake - Watchdog ligero en ESP8266 (solo millis(), sin FreeRTOS): si no llega comando de movimiento en 1.5s -> para motores Evita que el coche siga moviendose si el ESP32 crashea Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
264 lines
7.4 KiB
C++
264 lines
7.4 KiB
C++
#include <Servo.h>
|
|
#include <ESP8266WiFi.h>
|
|
#include <WiFiClientSecureBearSSL.h>
|
|
#include <ESP8266HTTPClient.h>
|
|
#include <ESP8266httpUpdate.h>
|
|
|
|
// ================================================================
|
|
// OTA CONFIG
|
|
// ================================================================
|
|
#define FW_VERSION 18
|
|
#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
|
|
}
|
|
|
|
String base = String(GITEA_HOST) + "/" + GITEA_OWNER + "/" + GITEA_REPO
|
|
+ "/raw/branch/" + GITEA_BRANCH + "/firmware/esp8266/";
|
|
String token = "?token=" + String(GITEA_TOKEN);
|
|
|
|
// ── Bloque de scope: libera el cliente BearSSL antes de la descarga ──
|
|
// BearSSL ocupa ~20KB de heap. Si los dos clientes coexisten el heap
|
|
// se agota y ESPhttpUpdate falla silenciosamente.
|
|
// Scope block: destruye el cliente BearSSL antes de crear el de descarga
|
|
// Libera ~20KB de heap (BearSSL con buffers por defecto) para la descarga
|
|
int remoteVer = 0;
|
|
{
|
|
BearSSL::WiFiClientSecure client;
|
|
client.setInsecure();
|
|
HTTPClient http;
|
|
http.begin(client, base + "version.txt" + token);
|
|
http.setTimeout(10000);
|
|
int code = http.GET();
|
|
if (code == HTTP_CODE_OK) {
|
|
String body = http.getString();
|
|
body.trim();
|
|
remoteVer = body.toInt();
|
|
}
|
|
http.end();
|
|
} // client destruido aquí — heap recuperado
|
|
|
|
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;
|
|
|
|
void waitForReady() {
|
|
delay(300);
|
|
while (Serial.available()) Serial.read();
|
|
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();
|
|
espReady = true;
|
|
// 2 pitidos: conexión entre placas lista
|
|
for (int i = 0; i < 2; i++) {
|
|
digitalWrite(buzzerPin, HIGH); delay(180);
|
|
digitalWrite(buzzerPin, LOW); delay(180);
|
|
}
|
|
return;
|
|
}
|
|
buf = "";
|
|
} else if (c != '\r' && buf.length() < 8) {
|
|
buf += c;
|
|
}
|
|
}
|
|
delay(10);
|
|
}
|
|
espReady = true; // timeout: arranca igualmente
|
|
}
|
|
|
|
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 "GO\n" del ESP32-CAM antes de procesar comandos
|
|
waitForReady();
|
|
}
|
|
|
|
// Watchdog ligero en ESP8266: si no llega comando de movimiento en 1.5s → para
|
|
// Evita que el coche siga moviéndose si el ESP32 se cuelga o reinicia
|
|
unsigned long lastMoveMs = 0;
|
|
bool motorRunning = false;
|
|
|
|
void loop() {
|
|
// Watchdog: sin comandos durante 1.5s → para motores
|
|
if (motorRunning && (millis() - lastMoveMs > 1500)) {
|
|
stopMotors();
|
|
motorRunning = false;
|
|
}
|
|
|
|
if (Serial.available()) {
|
|
char c = Serial.read();
|
|
|
|
// Reinicio del ESP32-CAM: manda "GO\n" de nuevo
|
|
if (c == 'G') {
|
|
espReady = false;
|
|
stopMotors();
|
|
motorRunning = false;
|
|
Serial.readStringUntil('\n');
|
|
Serial.print("OK\n");
|
|
delay(50);
|
|
while (Serial.available()) Serial.read();
|
|
espReady = true;
|
|
return;
|
|
}
|
|
|
|
// Ignorar todo hasta que el handshake esté completo
|
|
if (!espReady) return;
|
|
|
|
if (c == 'F') { forward(); lastMoveMs = millis(); motorRunning = true; }
|
|
else if (c == 'B') { backward(); lastMoveMs = millis(); motorRunning = true; }
|
|
else if (c == 'L') { left(); lastMoveMs = millis(); motorRunning = true; }
|
|
else if (c == 'R') { right(); lastMoveMs = millis(); motorRunning = true; }
|
|
else if (c == 'S') { stopMotors(); motorRunning = false; }
|
|
|
|
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);
|
|
}
|