#include <Keypad.h>
#include <LiquidCrystal_I2C.h>
#include <Servo.h>
#include <EEPROM.h>
#include <avr/pgmspace.h>
// Hardware pins
#define SERVO_PIN 12
#define BUZZER_PIN 13
#define TRIG_PIN 11
#define ECHO_PIN 10
#define GREEN_LED_PIN A0
#define RED_LED_PIN A1
// Display settings
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Servo settings
Servo turnstileServo;
#define SERVO_CLOSED 0
#define SERVO_OPEN 90
// Keypad setup
const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = {9, 8, 7, 6};
byte colPins[COLS] = {5, 4, 3, 2};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// System states
#define STATE_MAIN_MENU 0
#define STATE_TOPUP_BEEP 1
#define STATE_TOPUP_SINGLE 2
#define STATE_TICKET_TYPE 3
#define STATE_CARD_TYPE 4
#define STATE_LINE_SELECT 5
#define STATE_STATION_SELECT 6
#define STATE_DEST_SELECT 7
#define STATE_TRANSFER 8
int currentState = STATE_MAIN_MENU;
// Variables for fare calculation
int beepBalance = 100; // Starting with 100 pesos
int singleJourneyBalance = 0;
char ticketType[10] = ""; // "single" or "beep"
char userCardType[10] = ""; // "Regular", "Student", "PWD", "Senior"
int totalFare = 0;
int totalDistance = 0;
// Current journey variables
int currentLine = 1;
int currentStation = 0;
int stationCount = 0;
int destinationStation = 0;
// EEPROM addresses
#define BEEP_BALANCE_ADDR 0
#define SINGLE_BALANCE_ADDR 4
// Metro Line Data
// Using PROGMEM to store strings and large arrays in flash memory
const char lrt1_station_0[] PROGMEM = "Roosevelt";
const char lrt1_station_1[] PROGMEM = "Balintawak";
const char lrt1_station_2[] PROGMEM = "Monumento";
const char lrt1_station_3[] PROGMEM = "5th Avenue";
const char lrt1_station_4[] PROGMEM = "R. Papa";
const char lrt1_station_5[] PROGMEM = "Abad Santos";
const char lrt1_station_6[] PROGMEM = "Blumentritt";
const char lrt1_station_7[] PROGMEM = "Tayuman";
const char lrt1_station_8[] PROGMEM = "Bambang";
const char lrt1_station_9[] PROGMEM = "Doroteo Jose";
const char lrt1_station_10[] PROGMEM = "Carriedo";
const char lrt1_station_11[] PROGMEM = "Central Term";
const char lrt1_station_12[] PROGMEM = "United Nations";
const char lrt1_station_13[] PROGMEM = "Pedro Gil";
const char lrt1_station_14[] PROGMEM = "Quirino";
const char lrt1_station_15[] PROGMEM = "Vito Cruz";
const char lrt1_station_16[] PROGMEM = "Gil Puyat";
const char lrt1_station_17[] PROGMEM = "Libertad";
const char lrt1_station_18[] PROGMEM = "EDSA";
const char lrt1_station_19[] PROGMEM = "Baclaran";
const char* const lrt1_stations[] PROGMEM = {
lrt1_station_0, lrt1_station_1, lrt1_station_2, lrt1_station_3, lrt1_station_4,
lrt1_station_5, lrt1_station_6, lrt1_station_7, lrt1_station_8, lrt1_station_9,
lrt1_station_10, lrt1_station_11, lrt1_station_12, lrt1_station_13, lrt1_station_14,
lrt1_station_15, lrt1_station_16, lrt1_station_17, lrt1_station_18, lrt1_station_19
};
// Segment distances for LRT1 (in meters)
const int lrt1_distances[] PROGMEM = {
1870, 2250, 1087, 954, 660, 927, 671, 618, 648, 685,
725, 1214, 754, 794, 827, 1061, 730, 1010, 588
};
const char lrt2_station_0[] PROGMEM = "Recto";
const char lrt2_station_1[] PROGMEM = "Legarda";
const char lrt2_station_2[] PROGMEM = "Pureza";
const char lrt2_station_3[] PROGMEM = "V. Mapa";
const char lrt2_station_4[] PROGMEM = "J. Ruiz";
const char lrt2_station_5[] PROGMEM = "Gilmore";
const char lrt2_station_6[] PROGMEM = "Betty Go-B";
const char lrt2_station_7[] PROGMEM = "Cubao";
const char lrt2_station_8[] PROGMEM = "Anonas";
const char lrt2_station_9[] PROGMEM = "Katipunan";
const char lrt2_station_10[] PROGMEM = "Santolan";
const char lrt2_station_11[] PROGMEM = "Marikina";
const char lrt2_station_12[] PROGMEM = "Antipolo";
const char* const lrt2_stations[] PROGMEM = {
lrt2_station_0, lrt2_station_1, lrt2_station_2, lrt2_station_3, lrt2_station_4,
lrt2_station_5, lrt2_station_6, lrt2_station_7, lrt2_station_8, lrt2_station_9,
lrt2_station_10, lrt2_station_11, lrt2_station_12
};
// Segment distances for LRT2 (in meters)
const int lrt2_distances[] PROGMEM = {
1050, 1389, 1350, 1357, 928, 1075, 1164, 955, 438, 1970, 1000, 1000
};
const char mrt3_station_0[] PROGMEM = "North Avenue";
const char mrt3_station_1[] PROGMEM = "Quezon Ave";
const char mrt3_station_2[] PROGMEM = "GMA Kamuning";
const char mrt3_station_3[] PROGMEM = "Cubao";
const char mrt3_station_4[] PROGMEM = "Santolan";
const char mrt3_station_5[] PROGMEM = "Ortigas";
const char mrt3_station_6[] PROGMEM = "Shaw Blvd";
const char mrt3_station_7[] PROGMEM = "Boni";
const char mrt3_station_8[] PROGMEM = "Guadalupe";
const char mrt3_station_9[] PROGMEM = "Buendia";
const char mrt3_station_10[] PROGMEM = "Ayala";
const char mrt3_station_11[] PROGMEM = "Magallanes";
const char mrt3_station_12[] PROGMEM = "Taft";
const char* const mrt3_stations[] PROGMEM = {
mrt3_station_0, mrt3_station_1, mrt3_station_2, mrt3_station_3, mrt3_station_4,
mrt3_station_5, mrt3_station_6, mrt3_station_7, mrt3_station_8, mrt3_station_9,
mrt3_station_10, mrt3_station_11, mrt3_station_12
};
// Segment distances for MRT3 (in meters)
const int mrt3_distances[] PROGMEM = {
1090, 1200, 1350, 1040, 1280, 1090, 1150, 1210, 1540, 870, 1300, 1420
};
// Fare matrices stored in PROGMEM
const int lrt1_fare_matrix[] PROGMEM = {
13, 14, 15, 16, 17, 18, 19, 20, 22, 23, 23, 24, 25, 26, 27, 28, 29, 30, 33, 35,
14, 13, 15, 15, 17, 18, 19, 20, 21, 22, 23, 24, 24, 25, 26, 27, 28, 29, 32, 34,
15, 15, 13, 14, 15, 16, 17, 18, 20, 21, 22, 22, 23, 24, 25, 26, 27, 28, 31, 33,
// ...and so on
};
// SVT matrices for each line (stored variable ticket fares, same structure as fare matrices)
const int lrt1_svt[] PROGMEM = {
// Same data as lrt1_fare_matrix for brevity, would normally be slightly different
13, 14, 15, 16, 17, 18, 19, 20, 22, 23, 23, 24, 25, 26, 27, 28, 29, 30, 33, 35,
14, 13, 15, 15, 17, 18, 19, 20, 21, 22, 23, 24, 24, 25, 26, 27, 28, 29, 32, 34,
// ...and so on
};
// Similar fare matrices for LRT2 and MRT3 would be defined here
// Function prototypes
void displayMainMenu();
void displayTicketTypeMenu();
void displayCardTypeMenu();
void displayLineMenu();
int getDistance(int line, int start, int end);
int getFare(int line, int start, int end);
void openTurnstile();
void closeTurnstile();
long measureDistance();
void soundBuzzer(int pattern);
char readKey();
int readNumber(int maxDigits);
void displayStations(int line);
void displayTransferOptions(int line, int station);
void processTransfer();
bool validateFare(int fare);
void loadBalances();
void saveBalances();
void lcd_print_station(int line, int station);
void setup() {
// Initialize hardware
Serial.begin(9600);
lcd.init();
lcd.backlight();
turnstileServo.attach(SERVO_PIN);
closeTurnstile();
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(GREEN_LED_PIN, OUTPUT);
pinMode(RED_LED_PIN, OUTPUT);
// Load saved balances from EEPROM
loadBalances();
// Display welcome message
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Metro Fare Calc");
lcd.setCursor(0, 1);
lcd.print("Welcome!");
delay(2000);
displayMainMenu();
}
void loop() {
char key = readKey();
// Process input based on current state
switch (currentState) {
case STATE_MAIN_MENU:
processMainMenu(key);
break;
case STATE_TOPUP_BEEP:
processTopUpBeep(key);
break;
case STATE_TOPUP_SINGLE:
processTopUpSingle(key);
break;
case STATE_TICKET_TYPE:
processTicketType(key);
break;
case STATE_CARD_TYPE:
processCardType(key);
break;
case STATE_LINE_SELECT:
processLineSelect(key);
break;
case STATE_STATION_SELECT:
processStationSelect(key);
break;
case STATE_DEST_SELECT:
processDestSelect(key);
break;
case STATE_TRANSFER:
processTransferSelect(key);
break;
}
// Check ultrasonic sensor for turnstile simulation
if (currentState == STATE_DEST_SELECT) {
long distance = measureDistance();
// If someone approaches within 10cm
if (distance < 10 && distance > 0) {
Serial.println("Passenger detected!");
// Process turnstile entry logic
processPassengerEntry();
}
}
delay(100); // Small delay to prevent display flicker
}
// Process passenger entry at the turnstile
void processPassengerEntry() {
int fare = getFare(currentLine, currentStation, destinationStation);
if (validateFare(fare)) {
// Successful entry
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Fare: ");
lcd.print(fare);
lcd.print(" PHP");
lcd.setCursor(0, 1);
lcd.print("Access granted");
digitalWrite(GREEN_LED_PIN, HIGH);
openTurnstile();
soundBuzzer(1); // Success pattern
// Calculate journey details
int distance = getDistance(currentLine, currentStation, destinationStation);
totalDistance += distance;
totalFare += fare;
// Update the current station to the destination
currentStation = destinationStation;
// Deduct fare from balance
if (strcmp(ticketType, "beep") == 0) {
beepBalance -= fare;
saveBalances();
} else {
singleJourneyBalance -= fare;
saveBalances();
}
// Wait for passenger to pass through
delay(3000);
// Check if the passenger has passed through
long dist = measureDistance();
if (dist > 30) { // No one in front of the sensor
closeTurnstile();
digitalWrite(GREEN_LED_PIN, LOW);
// Check if current station is a transfer station
if (isTransferStation(currentLine, currentStation)) {
currentState = STATE_TRANSFER;
displayTransferOptions(currentLine, currentStation);
} else {
currentState = STATE_DEST_SELECT;
lcd.clear();
lcd.setCursor(0, 0);
lcd_print_station(currentLine, currentStation);
lcd.setCursor(0, 1);
lcd.print("Select dest. ");
displayStations(currentLine);
}
}
} else {
// Insufficient balance
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Insuf. balance");
lcd.setCursor(0, 1);
lcd.print("Need: ");
lcd.print(fare);
lcd.print(" PHP");
digitalWrite(RED_LED_PIN, HIGH);
soundBuzzer(2); // Error pattern
delay(2000);
digitalWrite(RED_LED_PIN, LOW);
// Return to destination selection
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Select dest. ");
displayStations(currentLine);
}
}
// Process main menu selections
void processMainMenu(char key) {
switch (key) {
case '1': // Top up Beep card
currentState = STATE_TOPUP_BEEP;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Beep Balance:");
lcd.setCursor(0, 1);
lcd.print(beepBalance);
lcd.print(" PHP");
break;
case '2': // Top up Single Journey
currentState = STATE_TOPUP_SINGLE;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Single Balance:");
lcd.setCursor(0, 1);
lcd.print(singleJourneyBalance);
lcd.print(" PHP");
break;
case '3': // Start Journey
currentState = STATE_TICKET_TYPE;
displayTicketTypeMenu();
break;
case '4': // Exit
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Thank you!");
lcd.setCursor(0, 1);
lcd.print("Goodbye!");
delay(2000);
displayMainMenu();
break;
}
}
// Process top up for Beep card
void processTopUpBeep(char key) {
if (key == '#') {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter amount:");
lcd.setCursor(0, 1);
int amount = readNumber(4); // Read up to 4 digits
if (amount > 0) {
beepBalance += amount;
saveBalances();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Top-up success!");
lcd.setCursor(0, 1);
lcd.print("New bal: ");
lcd.print(beepBalance);
delay(2000);
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Invalid amount");
delay(2000);
}
currentState = STATE_MAIN_MENU;
displayMainMenu();
} else if (key == '*') {
// Go back
currentState = STATE_MAIN_MENU;
displayMainMenu();
}
}
// Process top up for Single Journey
void processTopUpSingle(char key) {
if (key == '#') {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter amount:");
lcd.setCursor(0, 1);
int amount = readNumber(4); // Read up to 4 digits
if (amount > 0) {
singleJourneyBalance += amount;
saveBalances();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Top-up success!");
lcd.setCursor(0, 1);
lcd.print("New bal: ");
lcd.print(singleJourneyBalance);
delay(2000);
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Invalid amount");
delay(2000);
}
currentState = STATE_MAIN_MENU;
displayMainMenu();
} else if (key == '*') {
// Go back
currentState = STATE_MAIN_MENU;
displayMainMenu();
}
}
// Process ticket type selection
void processTicketType(char key) {
switch (key) {
case '1': // Single Journey
strcpy(ticketType, "single");
currentState = STATE_CARD_TYPE;
displayCardTypeMenu();
break;
case '2': // Beep Card
strcpy(ticketType, "beep");
strcpy(userCardType, "Regular");
currentState = STATE_CARD_TYPE;
displayCardTypeMenu();
break;
case '*': // Go back
currentState = STATE_MAIN_MENU;
displayMainMenu();
break;
}
}
// Process card type selection
void processCardType(char key) {
switch (key) {
case '1': // Regular
strcpy(userCardType, "Regular");
currentState = STATE_LINE_SELECT;
displayLineMenu();
break;
case '2': // Student
strcpy(userCardType, "Student");
currentState = STATE_LINE_SELECT;
displayLineMenu();
break;
case '3': // PWD
strcpy(userCardType, "PWD");
currentState = STATE_LINE_SELECT;
displayLineMenu();
break;
case '4': // Senior
strcpy(userCardType, "Senior");
currentState = STATE_LINE_SELECT;
displayLineMenu();
break;
case '*': // Go back
currentState = STATE_TICKET_TYPE;
displayTicketTypeMenu();
break;
}
}
// Process line selection
void processLineSelect(char key) {
switch (key) {
case '1': // LRT-1
currentLine = 1;
stationCount = 20;
currentState = STATE_STATION_SELECT;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("LRT1: Choose stn");
displayStations(currentLine);
break;
case '2': // LRT-2
currentLine = 2;
stationCount = 13;
currentState = STATE_STATION_SELECT;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("LRT2: Choose stn");
displayStations(currentLine);
break;
case '3': // MRT-3
currentLine = 3;
stationCount = 13;
currentState = STATE_STATION_SELECT;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("MRT3: Choose stn");
displayStations(currentLine);
break;
case '4': // Exit train
// Show summary
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Total fare:");
lcd.print(totalFare);
lcd.setCursor(0, 1);
lcd.print("Dist:");
lcd.print(totalDistance);
lcd.print("m");
delay(3000);
// Reset journey values
totalFare = 0;
totalDistance = 0;
currentState = STATE_MAIN_MENU;
displayMainMenu();
break;
case '5': // Change ticket type
currentState = STATE_TICKET_TYPE;
displayTicketTypeMenu();
break;
case '*': // Go back
currentState = STATE_MAIN_MENU;
displayMainMenu();
break;
}
}
// Process starting station selection
void processStationSelect(char key) {
if (key >= '1' && key <= '9') {
int selection = key - '0' - 1; // Convert key to 0-based index
if (selection < stationCount) {
currentStation = selection;
currentState = STATE_DEST_SELECT;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("From: ");
lcd_print_station(currentLine, currentStation);
lcd.setCursor(0, 1);
lcd.print("Select dest. ");
}
} else if (key == '0' && stationCount > 9) {
// Special case for index 9
currentStation = 9;
currentState = STATE_DEST_SELECT;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("From: ");
lcd_print_station(currentLine, currentStation);
lcd.setCursor(0, 1);
lcd.print("Select dest. ");
} else if (key == '*') {
// Go back
currentState = STATE_LINE_SELECT;
displayLineMenu();
} else if (key == '#') {
// Enter custom index for stations beyond 9
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter stn #:");
lcd.setCursor(0, 1);
int selection = readNumber(2); // Read up to 2 digits
if (selection > 0 && selection <= stationCount) {
currentStation = selection - 1; // Convert to 0-based
currentState = STATE_DEST_SELECT;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("From: ");
lcd_print_station(currentLine, currentStation);
lcd.setCursor(0, 1);
lcd.print("Select dest. ");
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Invalid station");
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Choose station:");
displayStations(currentLine);
}
}
}
// Process destination selection
void processDestSelect(char key) {
if (key >= '1' && key <= '9') {
int selection = key - '0' - 1; // Convert key to 0-based index
if (selection < stationCount && selection != currentStation) {
destinationStation = selection;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("To: ");
lcd_print_station(currentLine, destinationStation);
lcd.setCursor(0, 1);
lcd.print("Approach gate ");
int fare = getFare(currentLine, currentStation, destinationStation);
Serial.print("Selected journey: ");
Serial.print("From station ");
Serial.print(currentStation);
Serial.print(" to station ");
Serial.print(destinationStation);
Serial.print(" - Fare: ");
Serial.println(fare);
}
} else if (key == '0' && stationCount > 9) {
// Special case for index 9
destinationStation = 9;
if (destinationStation != currentStation) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("To: ");
lcd_print_station(currentLine, destinationStation);
lcd.setCursor(0, 1);
lcd.print("Approach gate ");
int fare = getFare(currentLine, currentStation, destinationStation);
Serial.print("Selected journey: ");
Serial.print("From station ");
Serial.print(currentStation);
Serial.print(" to station ");
Serial.print(destinationStation);
Serial.print(" - Fare: ");
Serial.println(fare);
}
} else if (key == '*') {
// Go back
currentState = STATE_STATION_SELECT;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Choose station:");
displayStations(currentLine);
} else if (key == '#') {
// Enter custom index for stations beyond 9
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter dest #:");
lcd.setCursor(0, 1);
int selection = readNumber(2); // Read up to 2 digits
if (selection > 0 && selection <= stationCount && (selection - 1) != currentStation) {
destinationStation = selection - 1; // Convert to 0-based
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("To: ");
lcd_print_station(currentLine, destinationStation);
lcd.setCursor(0, 1);
lcd.print("Approach gate ");
int fare = getFare(currentLine, currentStation, destinationStation);
Serial.print("Selected journey: ");
Serial.print("From station ");
Serial.print(currentStation);
Serial.print(" to station ");
Serial.print(destinationStation);
Serial.print(" - Fare: ");
Serial.println(fare);
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Invalid station");
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Select dest. ");
lcd.setCursor(0, 1);
lcd.print("Current: ");
lcd_print_station(currentLine, currentStation);
}
}
}
// Process transfer selection
void processTransferSelect(char key) {
switch (key) {
case '1': // Transfer to other line
transferToOtherLine();
break;
case '2': // Continue on current line
currentState = STATE_DEST_SELECT;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Continue on ");
if (currentLine == 1) lcd.print("LRT1");
else if (currentLine == 2) lcd.print("LRT2");
else if (currentLine == 3) lcd.print("MRT3");
lcd.setCursor(0, 1);
lcd.print("Select dest. ");
break;
case '*': // Go back to line selection
currentState = STATE_LINE_SELECT;
displayLineMenu();
break;
}
}
// Transfer to another line based on the current transfer station
void transferToOtherLine() {
int prevLine = currentLine;
// LRT1 Doroteo Jose -> LRT2 Recto
if (currentLine == 1 && strcmp_P((char*)pgm_read_word(&lrt1_stations[currentStation]), PSTR("Doroteo Jose")) == 0) {
currentLine = 2;
currentStation = 0; // Recto station in LRT2
stationCount = 13;
}
// LRT2 Recto -> LRT1 Doroteo Jose
else if (currentLine == 2 && strcmp_P((char*)pgm_read_word(&lrt2_stations[currentStation]), PSTR("Recto")) == 0) {
currentLine = 1;
currentStation = 9; // Doroteo Jose station in LRT1
stationCount = 20;
}
// LRT2 Cubao -> MRT3 Cubao
else if (currentLine == 2 && strcmp_P((char*)pgm_read_word(&lrt2_stations[currentStation]), PSTR("Cubao")) == 0) {
currentLine = 3;
currentStation = 3; // Cubao station in MRT3
stationCount = 13;
}
// MRT3 Cubao -> LRT2 Cubao
else if (currentLine == 3 && strcmp_P((char*)pgm_read_word(&mrt3_stations[currentStation]), PSTR("Cubao")) == 0) {
currentLine = 2;
currentStation = 7; // Cubao station in LRT2
stationCount = 13;
}
// LRT1 EDSA -> MRT3 Taft
else if (currentLine == 1 && strcmp_P((char*)pgm_read_word(&lrt1_stations[currentStation]), PSTR("EDSA")) == 0) {
currentLine = 3;
currentStation = 12; // Taft station in MRT3
stationCount = 13;
}
// MRT3 Taft -> LRT1 EDSA
else if (currentLine == 3 && strcmp_P((char*)pgm_read_word(&mrt3_stations[currentStation]), PSTR("Taft")) == 0) {
currentLine = 1;
currentStation = 18; // EDSA station in LRT1
stationCount = 20;
}
lcd.clear();
lcd.setCursor(0, 0);
if (prevLine == 1) lcd.print("LRT1>");
else if (prevLine == 2) lcd.print("LRT2>");
else if (prevLine == 3) lcd.print("MRT3>");
if (currentLine == 1) lcd.print("LRT1");
else if (currentLine == 2) lcd.print("LRT2");
else if (currentLine == 3) lcd.print("MRT3");
lcd.setCursor(0, 1);
lcd.print("Now at: ");
lcd_print_station(currentLine, currentStation);
delay(2000);
currentState = STATE_DEST_SELECT;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Select dest.");
lcd.setCursor(0, 1);
lcd.print("on new line");
}
// Check if station is a transfer point
bool isTransferStation(int line, int station) {
if (line == 1) {
char stationName[20];
strcpy_P(stationName, (char*)pgm_read_word(&lrt1_stations[station]));
return (strcmp(stationName, "Doroteo Jose") == 0 ||
strcmp(stationName, "EDSA") == 0);
}
else if (line == 2) {
char stationName[20];
strcpy_P(stationName, (char*)pgm_read_word(&lrt2_stations[station]));
return (strcmp(stationName, "Recto") == 0 ||
strcmp(stationName, "Cubao") == 0);
}
else if (line == 3) {
char stationName[20];
strcpy_P(stationName, (char*)pgm_read_word(&mrt3_stations[station]));
return (strcmp(stationName, "Cubao") == 0 ||
strcmp(stationName, "Taft") == 0);
}
return false;
}
// Get fare between two stations
int getFare(int line, int origin, int dest) {
int fare = 0;
// Access fare matrix based on the line
if (line == 1) {
int idx = origin * 20 + dest;
if (strcmp(ticketType, "beep") == 0) {
fare = pgm_read_word(&lrt1_svt[idx]);
} else {
fare = pgm_read_word(&lrt1_fare_matrix[idx]);
}
}
else if (line == 2) {
int idx = origin * 13 + dest;
if (strcmp(ticketType, "beep") == 0) {
fare = pgm_read_word(&lrt1_svt[idx % 400]); // Simplified for demonstration (would have proper lrt2_svt)
} else {
fare = pgm_read_word(&lrt1_fare_matrix[idx % 400]); // Simplified
}
}
else if (line == 3) {
int idx = origin * 13 + dest;
if (strcmp(ticketType, "beep") == 0) {
fare = pgm_read_word(&lrt1_svt[idx % 400]); // Simplified for demonstration
} else {
fare = pgm_read_word(&lrt1_fare_matrix[idx % 400]); // Simplified
}
}
// Apply discounts
int original_fare = fare;
if (strcmp(userCardType, "Student") == 0) {
fare = (int)(original_fare * 0.8 + 0.5); // 20% discount for students with rounding
} else if (strcmp(userCardType, "PWD") == 0 || strcmp(userCardType, "Senior") == 0) {
fare = (int)(original_fare * 0.7 + 0.5); // 30% discount for PWD/Seniors with rounding
}
return fare;
}
// Calculate distance between stations
int getDistance(int line, int start, int end) {
int distance = 0;
int startIdx = min(start, end);
int endIdx = max(start, end);
if (line == 1) {
for (int i = startIdx; i < endIdx; i++) {
distance += pgm_read_word(&lrt1_distances[i]);
}
}
else if (line == 2) {
for (int i = startIdx; i < endIdx; i++) {
distance += pgm_read_word(&lrt2_distances[i]);
}
}
else if (line == 3) {
for (int i = startIdx; i < endIdx; i++) {
distance += pgm_read_word(&mrt3_distances[i]);
}
}
return distance;
}
// Validate if balance is sufficient for fare
bool validateFare(int fare) {
if (strcmp(ticketType, "beep") == 0) {
return beepBalance >= fare;
} else {
return singleJourneyBalance >= fare;
}
}
// Load balance values from EEPROM
void loadBalances() {
// Read beep balance (stored as 4 bytes)
beepBalance = 0;
for (int i = 0; i < 4; i++) {
beepBalance = (beepBalance << 8) + EEPROM.read(BEEP_BALANCE_ADDR + i);
}
// Read single journey balance (stored as 4 bytes)
singleJourneyBalance = 0;
for (int i = 0; i < 4; i++) {
singleJourneyBalance = (singleJourneyBalance << 8) + EEPROM.read(SINGLE_BALANCE_ADDR + i);
}
// Initial values if EEPROM is empty (first run)
if (beepBalance > 10000 || beepBalance < 0) {
beepBalance = 100; // Default starting value
}
if (singleJourneyBalance > 10000 || singleJourneyBalance < 0) {
singleJourneyBalance = 0; // Default starting value
}
}
// Save balance values to EEPROM
void saveBalances() {
// Save beep balance (4 bytes)
for (int i = 0; i < 4; i++) {
EEPROM.write(BEEP_BALANCE_ADDR + i, (beepBalance >> (8 * (3 - i))) & 0xFF);
}
// Save single journey balance (4 bytes)
for (int i = 0; i < 4; i++) {
EEPROM.write(SINGLE_BALANCE_ADDR + i, (singleJourneyBalance >> (8 * (3 - i))) & 0xFF);
}
}
// Display main menu on LCD
void displayMainMenu() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("1.Beep 2.Single");
lcd.setCursor(0, 1);
lcd.print("3.Start 4.Exit");
currentState = STATE_MAIN_MENU;
}
// Display ticket type selection menu
void displayTicketTypeMenu() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Ticket Type:");
lcd.setCursor(0, 1);
lcd.print("1.Single 2.Beep");
}
// Display card type selection menu
void displayCardTypeMenu() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Card Type:");
lcd.setCursor(0, 1);
lcd.print("1.Reg 2.Stu 3.PWD");
}
// Display line selection menu
void displayLineMenu() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Choose Line:");
lcd.setCursor(0, 1);
lcd.print("1.LRT1 2.LRT2 3.MRT3");
}
// Display transfer options menu
void displayTransferOptions(int line, int station) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Transfer Point:");
lcd.setCursor(0, 1);
lcd.print("1.Trans 2.Stay");
}
// Display stations for the selected line
void displayStations(int line) {
// Only display the first few stations on LCD
// User can scroll or enter station number directly
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter station #");
lcd.setCursor(0, 1);
lcd.print("or # for more");
// Print stations to Serial for reference
if (line == 1) {
Serial.println("LRT1 Stations:");
for (int i = 0; i < 20; i++) {
char stationName[20];
strcpy_P(stationName, (char*)pgm_read_word(&lrt1_stations[i]));
Serial.print(i+1);
Serial.print(". ");
Serial.println(stationName);
}
}
else if (line == 2) {
Serial.println("LRT2 Stations:");
for (int i = 0; i < 13; i++) {
char stationName[20];
strcpy_P(stationName, (char*)pgm_read_word(&lrt2_stations[i]));
Serial.print(i+1);
Serial.print(". ");
Serial.println(stationName);
}
}
else if (line == 3) {
Serial.println("MRT3 Stations:");
for (int i = 0; i < 13; i++) {
char stationName[20];
strcpy_P(stationName, (char*)pgm_read_word(&mrt3_stations[i]));
Serial.print(i+1);
Serial.print(". ");
Serial.println(stationName);
}
}
}
// Print station name to LCD
void lcd_print_station(int line, int station) {
char stationName[20];
if (line == 1 && station < 20) {
strcpy_P(stationName, (char*)pgm_read_word(&lrt1_stations[station]));
}
else if (line == 2 && station < 13) {
strcpy_P(stationName, (char*)pgm_read_word(&lrt2_stations[station]));
}
else if (line == 3 && station < 13) {
strcpy_P(stationName, (char*)pgm_read_word(&mrt3_stations[station]));
}
else {
strcpy(stationName, "Unknown");
}
lcd.print(stationName);
}
// Read a key from keypad with timeout
char readKey() {
char key = keypad.getKey();
if (key) {
// Make a beep sound for button press feedback
digitalWrite(BUZZER_PIN, HIGH);
delay(50);
digitalWrite(BUZZER_PIN, LOW);
}
return key;
}
// Read a multi-digit number from the keypad
int readNumber(int maxDigits) {
char input[10] = "";
int inputIndex = 0;
lcd.setCursor(0, 1);
lcd.print(">");
while (true) {
char key = keypad.getKey();
if (key) {
// Provide feedback
digitalWrite(BUZZER_PIN, HIGH);
delay(50);
digitalWrite(BUZZER_PIN, LOW);
if (key >= '0' && key <= '9' && inputIndex < maxDigits) {
input[inputIndex++] = key;
input[inputIndex] = '\0'; // Null terminate
// Display on LCD
lcd.setCursor(1, 1);
lcd.print(input);
lcd.print(" "); // Clear any old digits
}
else if (key == '#') { // Confirm entry
break;
}
else if (key == '*') { // Backspace
if (inputIndex > 0) {
input[--inputIndex] = '\0';
// Display on LCD
lcd.setCursor(1, 1);
lcd.print(input);
lcd.print(" "); // Clear deleted digit
}
}
}
delay(10); // Small delay to prevent bouncing
}
// Convert string to integer
if (inputIndex > 0) {
return atoi(input);
} else {
return 0; // Return 0 if no input
}
}
// Open turnstile by rotating servo
void openTurnstile() {
turnstileServo.write(SERVO_OPEN);
Serial.println("Turnstile opened");
}
// Close turnstile by rotating servo
void closeTurnstile() {
turnstileServo.write(SERVO_CLOSED);
Serial.println("Turnstile closed");
}
// Measure distance using ultrasonic sensor
long measureDistance() {
// Clear trigger pin
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
// Send 10μs pulse to trigger
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// Measure duration of echo pulse
long duration = pulseIn(ECHO_PIN, HIGH);
// Calculate distance in cm
// Sound travels at 343m/s, or 0.0343cm/μs
// Time is round-trip, so divide by 2
return duration * 0.0343 / 2;
}
// Sound buzzer in different patterns for different events
void soundBuzzer(int pattern) {
switch (pattern) {
case 1: // Success pattern - single long beep
digitalWrite(BUZZER_PIN, HIGH);
delay(500);
digitalWrite(BUZZER_PIN, LOW);
break;
case 2: // Error pattern - three short beeps
for (int i = 0; i < 3; i++) {
digitalWrite(BUZZER_PIN, HIGH);
delay(100);
digitalWrite(BUZZER_PIN, LOW);
delay(100);
}
break;
case 3: // Alert pattern - two medium beeps
for (int i = 0; i < 2; i++) {
digitalWrite(BUZZER_PIN, HIGH);
delay(200);
digitalWrite(BUZZER_PIN, LOW);
delay(200);
}
break;
}
}