303 lines
8.5 KiB
C++
303 lines
8.5 KiB
C++
/**
|
|
* Claude's Eyes - Main Program
|
|
*
|
|
* Autonomous exploration robot controlled by Claude AI
|
|
*
|
|
* Hardware:
|
|
* - Waveshare ESP32-S3-Touch-LCD-2
|
|
* - OV5640 Camera (120° wide-angle)
|
|
* - Freenove 4WD Car Kit
|
|
*
|
|
* Created: December 2025
|
|
* By: Stefan (HackerSoft) & Claude (Anthropic)
|
|
*/
|
|
|
|
#include <Arduino.h>
|
|
#include <WiFi.h>
|
|
#include "config.h"
|
|
#include "camera.h"
|
|
#include "motor_control.h"
|
|
#include "servo_control.h"
|
|
#include "ultrasonic.h"
|
|
#include "imu.h"
|
|
#include "display.h"
|
|
#include "webserver.h"
|
|
|
|
// State machine
|
|
enum RobotState {
|
|
STATE_INITIALIZING,
|
|
STATE_CONNECTING,
|
|
STATE_READY,
|
|
STATE_EXPLORING,
|
|
STATE_OBSTACLE,
|
|
STATE_ERROR
|
|
};
|
|
|
|
RobotState currentState = STATE_INITIALIZING;
|
|
unsigned long lastCommandTime = 0;
|
|
unsigned long lastStatusUpdate = 0;
|
|
|
|
// Forward declarations
|
|
void setupWiFi();
|
|
void handleSafety();
|
|
void handleTouchInput();
|
|
void updateStatusDisplay();
|
|
|
|
void setup() {
|
|
// Initialize serial
|
|
Serial.begin(DEBUG_BAUD_RATE);
|
|
while (!Serial && millis() < 3000) {
|
|
delay(10);
|
|
}
|
|
Serial.println("\n\n");
|
|
Serial.println("╔═══════════════════════════════════════╗");
|
|
Serial.println("║ Claude's Eyes v1.0 ║");
|
|
Serial.println("║ Autonomous Exploration Robot ║");
|
|
Serial.println("╚═══════════════════════════════════════╝");
|
|
Serial.println();
|
|
|
|
// Initialize display first (for visual feedback)
|
|
Serial.println("[Main] Initializing display...");
|
|
if (Display.begin()) {
|
|
Display.showMessage("Starting...", TFT_CYAN);
|
|
}
|
|
delay(500);
|
|
|
|
// Initialize camera
|
|
Serial.println("[Main] Initializing camera...");
|
|
if (!Camera.begin()) {
|
|
Serial.println("[Main] WARNING: Camera init failed!");
|
|
Display.showMessage("Camera Error!", TFT_RED);
|
|
delay(2000);
|
|
} else {
|
|
Serial.println("[Main] Camera ready");
|
|
}
|
|
|
|
// Initialize motors
|
|
Serial.println("[Main] Initializing motors...");
|
|
if (!Motors.begin()) {
|
|
Serial.println("[Main] WARNING: Motors init failed!");
|
|
} else {
|
|
Serial.println("[Main] Motors ready");
|
|
}
|
|
|
|
// Initialize servos
|
|
Serial.println("[Main] Initializing servos...");
|
|
if (!Servos.begin()) {
|
|
Serial.println("[Main] WARNING: Servos init failed!");
|
|
} else {
|
|
Serial.println("[Main] Servos ready");
|
|
// Center the camera
|
|
Servos.center();
|
|
}
|
|
|
|
// Initialize ultrasonic sensor
|
|
Serial.println("[Main] Initializing ultrasonic sensor...");
|
|
if (!Ultrasonic.begin()) {
|
|
Serial.println("[Main] WARNING: Ultrasonic init failed!");
|
|
} else {
|
|
Serial.println("[Main] Ultrasonic ready");
|
|
}
|
|
|
|
// Initialize IMU
|
|
Serial.println("[Main] Initializing IMU...");
|
|
if (!IMU.begin()) {
|
|
Serial.println("[Main] WARNING: IMU init failed!");
|
|
} else {
|
|
Serial.println("[Main] IMU ready");
|
|
}
|
|
|
|
// Connect to WiFi
|
|
currentState = STATE_CONNECTING;
|
|
Display.showMessage("Connecting WiFi...", TFT_YELLOW);
|
|
setupWiFi();
|
|
|
|
// Start webserver
|
|
Serial.println("[Main] Starting webserver...");
|
|
if (!WebServer.begin()) {
|
|
Serial.println("[Main] ERROR: WebServer failed!");
|
|
currentState = STATE_ERROR;
|
|
Display.showMessage("Server Error!", TFT_RED);
|
|
return;
|
|
}
|
|
|
|
// All systems go!
|
|
currentState = STATE_READY;
|
|
Display.setMode(MODE_STATUS);
|
|
Display.showEmoji(EMOJI_HAPPY);
|
|
delay(1000);
|
|
Display.setMode(MODE_STATUS);
|
|
|
|
Serial.println();
|
|
Serial.println("╔═══════════════════════════════════════╗");
|
|
Serial.println("║ SYSTEM READY ║");
|
|
Serial.printf( "║ IP: %-30s ║\n", WiFi.localIP().toString().c_str());
|
|
Serial.println("║ Waiting for Claude... ║");
|
|
Serial.println("╚═══════════════════════════════════════╝");
|
|
Serial.println();
|
|
|
|
lastCommandTime = millis();
|
|
lastStatusUpdate = millis();
|
|
}
|
|
|
|
void loop() {
|
|
unsigned long now = millis();
|
|
|
|
// Update sensors
|
|
Ultrasonic.update();
|
|
IMU.update();
|
|
Servos.update();
|
|
Motors.update();
|
|
|
|
// Handle safety checks
|
|
handleSafety();
|
|
|
|
// Handle touch input
|
|
handleTouchInput();
|
|
|
|
// Update display periodically
|
|
if (now - lastStatusUpdate >= DISPLAY_UPDATE_INTERVAL) {
|
|
lastStatusUpdate = now;
|
|
updateStatusDisplay();
|
|
}
|
|
|
|
// Command timeout safety
|
|
if (Motors.isMoving() && (now - lastCommandTime > COMMAND_TIMEOUT_MS)) {
|
|
Serial.println("[Main] Command timeout - stopping");
|
|
Motors.stop();
|
|
}
|
|
|
|
// Small delay to prevent watchdog issues
|
|
delay(1);
|
|
}
|
|
|
|
void setupWiFi() {
|
|
Serial.printf("[WiFi] Connecting to %s", WIFI_SSID);
|
|
|
|
WiFi.mode(WIFI_STA);
|
|
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
|
|
|
|
int attempts = 0;
|
|
while (WiFi.status() != WL_CONNECTED && attempts < 30) {
|
|
delay(500);
|
|
Serial.print(".");
|
|
attempts++;
|
|
|
|
// Update display
|
|
if (attempts % 2 == 0) {
|
|
char msg[32];
|
|
snprintf(msg, sizeof(msg), "WiFi... %d", attempts);
|
|
Display.showMessage(msg, TFT_YELLOW);
|
|
}
|
|
}
|
|
|
|
if (WiFi.status() == WL_CONNECTED) {
|
|
Serial.println(" Connected!");
|
|
Serial.printf("[WiFi] IP Address: %s\n", WiFi.localIP().toString().c_str());
|
|
Serial.printf("[WiFi] Signal strength: %d dBm\n", WiFi.RSSI());
|
|
|
|
char msg[32];
|
|
snprintf(msg, sizeof(msg), "IP: %s", WiFi.localIP().toString().c_str());
|
|
Display.showMessage(msg, TFT_GREEN);
|
|
delay(2000);
|
|
} else {
|
|
Serial.println(" FAILED!");
|
|
Serial.println("[WiFi] Could not connect to WiFi");
|
|
|
|
// Start AP mode as fallback
|
|
Serial.println("[WiFi] Starting AP mode...");
|
|
WiFi.mode(WIFI_AP);
|
|
WiFi.softAP("ClaudesEyes", "claude2025");
|
|
Serial.printf("[WiFi] AP IP: %s\n", WiFi.softAPIP().toString().c_str());
|
|
|
|
Display.showMessage("AP: ClaudesEyes", TFT_ORANGE);
|
|
delay(2000);
|
|
}
|
|
}
|
|
|
|
void handleSafety() {
|
|
// Check for obstacles
|
|
if (Ultrasonic.isDanger() && Motors.isMoving()) {
|
|
MotorDirection dir = Motors.getCurrentDirection();
|
|
if (dir == DIR_FORWARD) {
|
|
Serial.println("[Safety] Obstacle too close - stopping!");
|
|
Motors.stop();
|
|
Display.showEmoji(EMOJI_SURPRISED);
|
|
|
|
// Set Claude text about obstacle
|
|
WebServer.setClaudeText("Hindernis erkannt! Ich halte an.");
|
|
}
|
|
}
|
|
|
|
// Check if robot is tilted
|
|
if (IMU.isTilted() && Motors.isMoving()) {
|
|
Serial.println("[Safety] Robot tilted - stopping!");
|
|
Motors.stop();
|
|
Display.showEmoji(EMOJI_CONFUSED);
|
|
}
|
|
|
|
// Check if upside down
|
|
if (IMU.isUpsideDown()) {
|
|
Serial.println("[Safety] Robot upside down!");
|
|
Motors.stop();
|
|
Motors.setEnabled(false); // Disable motors completely
|
|
Display.showMessage("HILFE!", TFT_RED);
|
|
}
|
|
}
|
|
|
|
void handleTouchInput() {
|
|
TouchButton btn = Display.checkTouch();
|
|
|
|
if (btn == BTN_NONE) return;
|
|
|
|
lastCommandTime = millis();
|
|
|
|
switch (btn) {
|
|
case BTN_STOP:
|
|
Serial.println("[Touch] STOP pressed");
|
|
Motors.stop();
|
|
Display.showEmoji(EMOJI_SLEEPY);
|
|
break;
|
|
|
|
case BTN_FORWARD:
|
|
Serial.println("[Touch] Forward pressed");
|
|
Motors.move(DIR_FORWARD, 50, 1000);
|
|
break;
|
|
|
|
case BTN_BACKWARD:
|
|
Serial.println("[Touch] Backward pressed");
|
|
Motors.move(DIR_BACKWARD, 50, 1000);
|
|
break;
|
|
|
|
case BTN_LEFT:
|
|
Serial.println("[Touch] Left pressed");
|
|
Motors.move(DIR_LEFT, 50, 500);
|
|
break;
|
|
|
|
case BTN_RIGHT:
|
|
Serial.println("[Touch] Right pressed");
|
|
Motors.move(DIR_RIGHT, 50, 500);
|
|
break;
|
|
|
|
case BTN_HOME:
|
|
Serial.println("[Touch] Home pressed");
|
|
// TODO: Implement return to base
|
|
Display.showMessage("Coming home!", TFT_CYAN);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void updateStatusDisplay() {
|
|
if (Display.isInitialized()) {
|
|
Display.updateStatus(
|
|
Motors.getDirectionString(),
|
|
Ultrasonic.getLastDistance(),
|
|
WebServer.getBatteryPercent(),
|
|
WiFi.RSSI()
|
|
);
|
|
}
|
|
}
|