/*
Infotainment System in City Railway - Wokwi IoT Simulation
Author: armanyrs + Copilot
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C LCD(0x27, 16, 2);
#define BTN_PIN 39 // VN pin (with external pullup)
#define BUZZER_PIN 13
const char* stations[] = {"Depok", "Manggarai", "Jakarta Kota"};
const int numStations = sizeof(stations) / sizeof(stations[0]);
enum State {
SHOW_ROUTE, // Animasi rute berjalan
SHOW_WARNING // Peringatan pintu, buzzer aktif
};
State state = SHOW_ROUTE; // Mulai langsung animasi rute
bool nextIsOpen = false; // Siklus: tutup → buka → tutup → buka dst
int currentStation = 0;
int direction = 1; // 1: forward, -1: backward
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50;
bool lastButtonState = false;
bool buttonState = false;
unsigned long lastAnimTime = 0;
const unsigned long animSpeed = 150; // ms per scroll step
String routeText = "";
int routeOffset = 0;
unsigned long warningShownAt = 0;
const unsigned long warningDuration = 1200;
void setup() {
Serial.begin(115200);
pinMode(BTN_PIN, INPUT);
pinMode(BUZZER_PIN, OUTPUT);
LCD.init();
LCD.backlight();
welcomeScreen();
delay(1500);
updateRouteText();
displayRouteText(true);
}
void loop() {
handleButton();
handleState();
}
// Button press handler with debounce logic
void handleButton() {
bool reading = !digitalRead(BTN_PIN); // LOW when pressed
if (reading != lastButtonState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
if (reading != buttonState) {
buttonState = reading;
if (buttonState && state == SHOW_ROUTE) { // Button pressed, hanya saat animasi rute
// Masuk mode warning
state = SHOW_WARNING;
warningShownAt = millis();
LCD.clear();
if (nextIsOpen) {
advanceStation();
LCD.setCursor(0, 0);
LCD.print("Pintu dibuka");
} else {
LCD.setCursor(0, 0);
LCD.print("Pintu ditutup");
}
soundBuzzer();
nextIsOpen = !nextIsOpen; // Ganti siklus tutup/buka
}
}
}
lastButtonState = reading;
}
// State handler: animasi rute atau warning pintu
void handleState() {
if (state == SHOW_ROUTE) {
handleAnimation();
} else if (state == SHOW_WARNING) {
// Tunggu warning selesai, lalu kembali ke animasi rute
if (millis() - warningShownAt > warningDuration) {
LCD.clear();
updateRouteText();
displayRouteText(true);
state = SHOW_ROUTE;
}
}
}
// Buzzer: beep 3x
void soundBuzzer() {
for (int i = 0; i < 3; i++) {
digitalWrite(BUZZER_PIN, HIGH);
delay(120);
digitalWrite(BUZZER_PIN, LOW);
delay(120);
}
}
// Advance route station and handle direction
void advanceStation() {
currentStation += direction;
if (currentStation >= numStations - 1) {
direction = -1;
} else if (currentStation <= 0) {
direction = 1;
}
}
// Prepare scrolling route text
void updateRouteText() {
int nextStation = currentStation + direction;
routeText = String(stations[currentStation]) + " -> " + String(stations[nextStation]);
routeText = " " + routeText + " "; // pad for smooth scroll
routeOffset = 0;
}
// Animate route text: scroll from right to left
void handleAnimation() {
if (millis() - lastAnimTime > animSpeed) {
lastAnimTime = millis();
displayRouteText(false);
}
}
// Show route text at current offset
void displayRouteText(bool reset) {
if (reset) routeOffset = 0;
LCD.setCursor(0, 0);
LCD.print("Rute Kereta: ");
LCD.setCursor(0, 1);
int txtLen = routeText.length();
if (routeOffset + 16 <= txtLen) {
LCD.print(routeText.substring(routeOffset, routeOffset + 16));
} else {
LCD.print(routeText.substring(routeOffset));
for (int i = 0; i < 16 - (txtLen - routeOffset); i++) LCD.print(" ");
}
routeOffset++;
if (routeOffset > txtLen - 16) {
routeOffset = 0;
}
}
// Initial welcome screen
void welcomeScreen() {
LCD.setCursor(0, 0);
LCD.print("Infotainment");
LCD.setCursor(0, 1);
LCD.print("City Railway");
}