#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <ESP32Servo.h>
// ── Pins ────────────────────────────────────────────────
#define TFT_CS 15
#define TFT_RST 4
#define TFT_DC 2
#define TFT_MOSI 23
#define TFT_SCK 18
#define POT_PIN 34
#define BUZZER_PIN 25
#define SERVO_PIN 26
// ── Colors ──────────────────────────────────────────────
#define COL_SKY 0x0318
#define COL_GROUND 0x2945
#define COL_WHITE ILI9341_WHITE
#define COL_YELLOW ILI9341_YELLOW
#define COL_ORANGE 0xFD00
#define COL_RED ILI9341_RED
#define COL_BLACK ILI9341_BLACK
#define COL_GRAY 0x8410
// ── Objects ─────────────────────────────────────────────
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
Servo myServo;
// ── State ───────────────────────────────────────────────
float planeY = 40.0;
float prevPlaneY = 40.0;
bool crashed = false;
unsigned long crashTime = 0;
enum State { QUIET, WARNING, DANGER, CRASH_STATE };
State currentState = QUIET;
State prevState = QUIET;
// ── Helpers ─────────────────────────────────────────────
uint16_t stateColor(State s) {
switch (s) {
case QUIET: return COL_WHITE;
case WARNING: return COL_YELLOW;
case DANGER: return COL_ORANGE;
case CRASH_STATE: return COL_RED;
}
return COL_WHITE;
}
const char* stateLabel(State s) {
switch (s) {
case QUIET: return "QUIET";
case WARNING: return "WARNING";
case DANGER: return "DANGER!";
case CRASH_STATE: return "CRASHED!";
}
return "";
}
void drawPlane(int x, int y, uint16_t color) {
tft.fillRect(x, y + 4, 24, 6, color);
tft.fillTriangle(x + 24, y + 4, x + 24, y + 9, x + 32, y + 6, color);
tft.fillTriangle(x + 6, y, x + 18, y, x + 14, y + 4, color);
tft.fillTriangle(x, y + 1, x + 6, y + 1, x + 4, y + 4, color);
}
void erasePlane(int x, int y) {
tft.fillRect(x - 2, y - 2, 40, 18, COL_SKY);
}
void drawBackground(State s) {
tft.fillScreen(COL_SKY);
tft.fillRect(0, 278, 240, 42, COL_GROUND);
tft.drawLine(0, 278, 240, 278, COL_WHITE);
for (int i = 0; i < 30; i++) {
int sx = (i * 73 + 11) % 230;
int sy = (i * 37 + 7) % 60 + 10;
tft.drawPixel(sx, sy, COL_GRAY);
}
tft.setTextSize(2);
tft.setTextColor(stateColor(s));
tft.setCursor(8, 8);
tft.print(stateLabel(s));
}
void drawNoiseBar(int noiseLevel) {
tft.fillRect(220, 40, 16, 230, COL_BLACK);
tft.drawRect(220, 40, 16, 230, COL_GRAY);
int fillH = map(noiseLevel, 0, 100, 0, 228);
uint16_t barColor;
if (noiseLevel < 50) barColor = COL_WHITE;
else if (noiseLevel < 75) barColor = COL_YELLOW;
else barColor = COL_RED;
tft.fillRect(221, 40 + (228 - fillH), 14, fillH, barColor);
tft.setTextSize(1);
tft.setTextColor(COL_WHITE);
tft.setCursor(216, 30);
tft.print("dB");
}
void drawNoiseLabel(int noiseLevel, uint16_t color) {
tft.fillRect(0, 283, 140, 16, COL_GROUND);
tft.setTextSize(2);
tft.setTextColor(color);
tft.setCursor(4, 284);
tft.print("Noise: ");
tft.print(noiseLevel);
tft.print("%");
}
void drawCrashScreen() {
tft.fillScreen(COL_BLACK);
tft.fillCircle(120, 160, 60, COL_RED);
tft.fillCircle(120, 160, 40, COL_ORANGE);
tft.fillCircle(120, 160, 20, COL_YELLOW);
tft.setTextSize(3);
tft.setTextColor(COL_WHITE);
tft.setCursor(30, 80);
tft.print("CRASHED!");
tft.setTextSize(2);
tft.setTextColor(COL_YELLOW);
tft.setCursor(10, 240);
tft.print("please be quiet :)");
}
void playCrashSound() {
for (int f = 900; f > 80; f -= 30) {
tone(BUZZER_PIN, f, 20);
delay(20);
}
noTone(BUZZER_PIN);
}
// ── Setup ───────────────────────────────────────────────
void setup() {
Serial.begin(115200);
tft.begin();
tft.setRotation(0);
myServo.attach(SERVO_PIN);
myServo.write(0);
pinMode(BUZZER_PIN, OUTPUT);
noTone(BUZZER_PIN);
drawBackground(QUIET);
drawNoiseBar(0);
drawNoiseLabel(0, COL_WHITE);
}
// ── Loop ────────────────────────────────────────────────
void loop() {
int potRaw = analogRead(POT_PIN);
int noiseLevel = map(potRaw, 0, 4095, 0, 100);
State newState;
if (noiseLevel < 40) newState = QUIET;
else if (noiseLevel < 65) newState = WARNING;
else newState = DANGER;
if (crashed) {
if (millis() - crashTime > 3500) {
crashed = false;
planeY = 40.0;
prevPlaneY = 40.0;
currentState = QUIET;
prevState = CRASH_STATE;
myServo.write(0);
}
delay(50);
return;
}
float targetY = map(noiseLevel, 0, 100, 40, 265);
planeY += (targetY - planeY) * 0.07;
if (planeY >= 263) {
crashed = true;
crashTime = millis();
currentState = CRASH_STATE;
myServo.write(180);
drawCrashScreen();
playCrashSound();
delay(50);
return;
}
if (newState != currentState) {
currentState = newState;
drawBackground(currentState);
}
erasePlane(90, (int)prevPlaneY);
drawPlane(90, (int)planeY, stateColor(currentState));
prevPlaneY = planeY;
drawNoiseBar(noiseLevel);
drawNoiseLabel(noiseLevel, stateColor(currentState));
myServo.write(map(noiseLevel, 0, 100, 0, 180));
if (currentState == DANGER) {
tone(BUZZER_PIN, 440, 80);
}
Serial.println(noiseLevel);
delay(40);
}