Files
Arduino-Car/ESP8266/ESP8266.ino
Natxo1000 c1b587b0a8 firmware: v6 — handshake ! + heartbeat movimiento
ESP32-CAM:
- Envia '!' por Serial cuando el servidor esta listo
- Watchdog subido a 800ms (compatible con heartbeat 150ms)

ESP8266:
- waitForReady(): bloquea hasta recibir '!' (max 90s)
- loop(): ignora comandos hasta recibir '!' — sin movimiento al arrancar
- Si ESP32 se reinicia (OTA), detecta nuevo '!' y reanuda

Web (index.html):
- Heartbeat: reenvio del comando de movimiento cada 150ms mientras tecla/boton pulsado
- Mantiene vivo el watchdog y garantiza que el motor no pare solo
- carStop() limpia el intervalo y envia S al soltar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 12:56:50 +02:00

214 lines
5.7 KiB
C++

#include <Servo.h>
#include <ESP8266WiFi.h>
#include <WiFiClientSecureBearSSL.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>
// ================================================================
// OTA CONFIG
// ================================================================
#define FW_VERSION 6
#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);
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; // true cuando el ESP32-CAM ha enviado '!'
void waitForReady() {
// Espera la señal '!' del ESP32-CAM (máx 90s por si hace OTA)
unsigned long timeout = millis() + 90000UL;
while (millis() < timeout) {
if (Serial.available()) {
char c = Serial.read();
if (c == '!') { espReady = true; return; }
}
delay(10);
}
// Timeout: arranca igual para no quedarse bloqueado indefinidamente
espReady = true;
}
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();
// Señal de listo del ESP32-CAM (también en loop por si reinicia)
if (c == '!') { espReady = true; return; }
// Ignorar todo hasta recibir '!' — evita movimientos al arrancar
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);
}