// Robot assistant IA - test simulateur Wokwi (ESP32-S3-DevKitC-1)
// Ecran ILI9341 320x240 (yeux animes), 2 servos (pan/tilt), IMU MPU6050, bouton.
// Servos pilotes directement en PWM (la bibliotheque ESP32Servo bloque dans le simulateur).
// Non simule par Wokwi : I2S (micro, ampli) et Bluetooth.
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <Adafruit_MPU6050.h>
#include <Wire.h>
#define TFT_CS 10
#define TFT_DC 9
#define TFT_RST 8
#define I2C_SDA 1
#define I2C_SCL 2
#define SERVO_PAN 5
#define SERVO_TILT 6
#define BTN 14
// Limites de securite des servos (degres)
#define PAN_MIN 60
#define PAN_MAX 120
#define TILT_MIN 75
#define TILT_MAX 105
#define PWM_FREQ 50
#define PWM_BITS 14
#define CH_PAN 0
#define CH_TILT 1
Adafruit_ILI9341 tft(TFT_CS, TFT_DC, TFT_RST);
Adafruit_MPU6050 imu;
bool imuOk = false;
bool happy = true;
void servoInit(int pin, int ch) {
#if ESP_ARDUINO_VERSION_MAJOR >= 3
ledcAttach(pin, PWM_FREQ, PWM_BITS);
#else
ledcSetup(ch, PWM_FREQ, PWM_BITS);
ledcAttachPin(pin, ch);
#endif
}
void servoWrite(int pin, int ch, int angle) {
angle = constrain(angle, 0, 180);
uint32_t us = 500 + (uint32_t)angle * (2400 - 500) / 180;
uint32_t duty = (uint32_t)((uint64_t)us * ((1 << PWM_BITS) - 1) / 20000);
#if ESP_ARDUINO_VERSION_MAJOR >= 3
ledcWrite(pin, duty);
#else
ledcWrite(ch, duty);
#endif
}
// Dessine le visage : yeux ouverts ou fermes, bouche souriante ou neutre
void drawFace(bool eyesOpen, bool smile) {
tft.fillRect(0, 60, 320, 180, ILI9341_BLACK); // efface la zone du visage seulement
int h = eyesOpen ? 70 : 6;
int y = eyesOpen ? 90 : 122;
tft.fillRoundRect(60, y, 70, h, 20, ILI9341_CYAN);
tft.fillRoundRect(190, y, 70, h, 20, ILI9341_CYAN);
if (smile) tft.drawCircleHelper(160, 170, 40, 0x0C, ILI9341_CYAN);
else tft.drawFastHLine(130, 200, 60, ILI9341_CYAN);
}
void showStatus(const char* msg) {
tft.fillRect(0, 0, 320, 40, ILI9341_BLACK);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(2);
tft.setCursor(10, 10);
tft.print(msg);
}
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("[1] Demarrage");
Wire.begin(I2C_SDA, I2C_SCL);
pinMode(BTN, INPUT_PULLUP);
tft.begin();
tft.setRotation(1);
tft.fillScreen(ILI9341_BLACK);
Serial.println("[2] Ecran OK");
servoInit(SERVO_PAN, CH_PAN);
servoInit(SERVO_TILT, CH_TILT);
servoWrite(SERVO_PAN, CH_PAN, 90);
servoWrite(SERVO_TILT, CH_TILT, 90);
Serial.println("[3] Servos OK");
imuOk = imu.begin(0x68, &Wire);
Serial.println(imuOk ? "[4] IMU OK" : "[4] IMU NON DETECTE");
showStatus(imuOk ? "Pret" : "Pret (sans IMU)");
drawFace(true, happy);
}
void loop() {
static uint32_t tBlink = 0, tMove = 0, tImu = 0, tBtn = 0;
uint32_t now = millis();
if (now - tBlink > 3000) { // clignement
drawFace(false, happy);
delay(120);
drawFace(true, happy);
tBlink = now;
}
if (now - tMove > 2000) { // mouvement de tete dans les limites
int p = random(PAN_MIN, PAN_MAX + 1);
int t = random(TILT_MIN, TILT_MAX + 1);
servoWrite(SERVO_PAN, CH_PAN, p);
servoWrite(SERVO_TILT, CH_TILT, t);
Serial.printf("[move] pan=%d tilt=%d\n", p, t);
tMove = now;
}
if (digitalRead(BTN) == LOW && now - tBtn > 400) { // bouton : change d'humeur
happy = !happy;
drawFace(true, happy);
Serial.println(happy ? "[humeur] content" : "[humeur] neutre");
tBtn = now;
}
if (imuOk && now - tImu > 1000) { // lecture IMU
sensors_event_t a, g, tmp;
imu.getEvent(&a, &g, &tmp);
Serial.printf("[imu] acc z=%.2f m/s2\n", a.acceleration.z);
tImu = now;
}
}