#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "TimerUtils.h"
// Initialize the LCD with I2C address 0x27, 20 columns and 4 rows
LiquidCrystal_I2C lcd(0x27, 20, 4);
// Hardware pin mappings
const int COIN_PINS[2] = {2, 3}; // Coin slot input pins (INT0, INT1)
const int BUZZER_PINS[2] = {12, 13}; // Buzzer output pins
const int LED_PINS[2] = {10, 11}; // LED indicator pins (ON when timer active)
const int RELAY_PINS[4] = {A0, A1, A2, A3}; // Relay pins: grouped 2 per PC (PC1: A0/A1, PC2: A2/A3)
const int TIMER_MULTIPLIER[6] = {4, 5, 6, 7, 8, 9};
// Notes for C major scale
const int NOTES[] = {262, 294, 330, 349, 392, 440, 494, 523}; // C4 to C5
// Flags
volatile bool coinInserted[2] = {false, false}; // Flags for coin insert events
bool pc_final_warning[2] = {false, false}; // Tracks if final warning was given
bool grace_given[2] = {false, false}; // Tracks if grace time has already been added
bool screen_clear[2] = {false, false};
// Timer instances for two PCs
Timer timers[2];
// Time settings
volatile float addTime = 0.25; // Time added per coin in minutes (0.25 min = 15 sec)
const float grace_time = 0.25; // Grace time added once at 1 sec remaining (15 sec)
int multiplier = 1;
unsigned long previousMillis = 0;
const unsigned long interval = 1000; // 1 second
uint32_t ChipID = 4294967295;
uint32_t CHIPID() {
uint32_t chipId = 0;
for (int i = 0; i < 4; i++) {
chipId |= (uint32_t)(eeprom_read_byte((uint8_t*)(0x0E + i)) << (i * 8));
}
Serial.println(chipId);
if (chipId != ChipID) {
lcd.setCursor(0, 0);
lcd.print("NOT VALID DEVICE ID");
lcd.setCursor(0, 1);
lcd.print("CODE has a copyright");
lcd.setCursor(0, 2);
lcd.print("contact manufacturer");
lcd.setCursor(0, 3);
lcd.print("for code detail !!!");
while (true);
} else {
lcd.setCursor(0, 0);
lcd.print("WELCOME");
}
delay(5000);
lcd.clear();
}
void playScale(int pin) {
for (int i = 0; i < 8; i++) {
tone(pin, NOTES[i]);
delay(150);
}
noTone(pin);
}
void playScaleReverse(int pin) {
for (int i = 7; i >= 0; i--) {
tone(pin, NOTES[i]);
delay(150);
}
noTone(pin);
}
// Interrupt Service Routines for coin detection
void coin1ISR() {
coinInserted[0] = true;
}
void coin2ISR() {
coinInserted[1] = true;
}
void setup() {
Serial.begin(9600);
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
CHIPID();
lcd.print(" * Pysonet05 *");
// Set all relay pins as OUTPUT
for (int i = 0; i < 4; i++) {
pinMode(RELAY_PINS[i], OUTPUT);
}
playScale(BUZZER_PINS[0]);
playScaleReverse(BUZZER_PINS[1]);
// Set coin input and LED output pins; turn off relays initially
for (int i = 0; i < 2; i++) {
controlRelayGroup(i + 1, 0, 0); // Turn off both relays in the group
pinMode(COIN_PINS[i], INPUT_PULLUP); // Use internal pull-up for coin slot pins
pinMode(LED_PINS[i], OUTPUT);
}
for (int i = 0; i < 6; i ++) {
pinMode(TIMER_MULTIPLIER[i], INPUT_PULLUP);
if (!digitalRead(TIMER_MULTIPLIER[i])) {
multiplier += 1;
}
}
// Total add time per coin
addTime = addTime * multiplier;
Serial.println("Multiplier : " + String(multiplier));
Serial.println("Total Time : " + String(addTime));
// Pass LCD instance to TimerUtils
setLCD(&lcd);
// Attach coin input interrupts (falling edge = coin insert)
attachInterrupt(digitalPinToInterrupt(COIN_PINS[0]), coin1ISR, FALLING);
attachInterrupt(digitalPinToInterrupt(COIN_PINS[1]), coin2ISR, FALLING);
}
void loop() {
// Coin insertion handling
for (int i = 0; i < 2; i++) {
if (coinInserted[i]) {
noInterrupts(); coinInserted[i] = false; interrupts(); // Safely reset flag
addTimeToTimer(timers[i], addTime); // Add time to timer
buzzer_beep(BUZZER_PINS[i], 2000, 100); // Confirmation beep // Reset grace and warning flags
pc_final_warning[i] = false;
if (grace_given[i]) {
clearLine(i + 2);
}
grace_given[i] = false;
Serial.print("Coin Slot "); Serial.print(i + 1); Serial.println(": +Time");
controlRelayGroup(i + 1, 1, 1); // Enable both relays for the PC
}
if (timers[i].active) {
// Beep between 60s to 55s remaining
if (timers[i].hours == 0 && timers[i].minutes == 0 && timers[i].seconds <= 60 && timers[i].seconds > 55) {
buzzer_beep(BUZZER_PINS[i], 5000, 500);
}
// Beep rapidly between 5s to 1s
if (timers[i].hours == 0 && timers[i].minutes == 0 && timers[i].seconds <= 5 && timers[i].seconds > 0) {
buzzer_beep(BUZZER_PINS[i], 1000, 100);
}
// Add grace time only once when timer reaches 1s
if (timers[i].hours == 0 && timers[i].minutes == 0 && timers[i].seconds == 1 && !grace_given[i]) {
addTimeToTimer(timers[i], grace_time); // Add 15s grace time
Serial.print("Grace time added to PC"); Serial.println(i + 1);
grace_given[i] = true;
controlRelayGroup(i + 1, 1, 0); // Turn off relay 2, keep relay 1 ON
}
pc_display(i, grace_given[i]); // Update timer display on LCD
screen_clear[i] = false;
digitalWrite(LED_PINS[i], LOW); // LED ON if active
} else {
if (!screen_clear[i]) {
clearLine(i + 2); // use we use screen row 2 as start
screen_clear[i] = true;
}
idle_display(i); //idle no time pc
digitalWrite(LED_PINS[i], HIGH); // LED OFF if inactive
}
// Timer end handling
if (timers[i].ended) {
Serial.print("Timer "); Serial.print(i + 1); Serial.println(" done");
timers[i].ended = false;
grace_given[i] = false;
pc_final_warning[i] = false;
controlRelayGroup(i + 1, 0, 0); // Turn off both relays
}
}
delay(100); // Short loop delay
}
// Beep helper function: tone() with delay and stop
void buzzer_beep(int buzzerPin, int tone_value, int delay_ms) {
tone(buzzerPin, tone_value);
delay(delay_ms);
noTone(buzzerPin);
}
// Show PC label and remaining time
void pc_display(int index, float grace) {
unsigned long now = millis();
const char* label = (index == 0) ? "PC1" : "PC2";
if (grace) {
label = (index == 0) ? "PC1 Shutdown" : "PC2 Shutdown";
}
int row = (index == 0) ? 2 : 3;
updateTimer(timers[index], now); // Update countdown
displayTimer(timers[index], row, label, grace); // Show on LCD
}
void idle_display(int index) {
static bool blinkState[2] = {false, false}; // one per PC
static unsigned long lastBlinkMillis[2] = {0, 0};
const unsigned long blinkInterval = 500; // 500 ms blink
unsigned long currentMillis = millis();
int row = (index == 0) ? 2 : 3;
if (currentMillis - lastBlinkMillis[index] >= blinkInterval) {
lastBlinkMillis[index] = currentMillis;
blinkState[index] = !blinkState[index]; // toggle state
if (blinkState[index]) {
const char* label = (index == 0) ? "PC1 Insert Coin" : "PC2 Insert Coin";
displayIdle(row, label);
} else {
//clearLine(row); // blank the line to simulate blink
const char* label = (index == 0) ? "PC1 " : "PC2 ";
displayIdle(row, label);
}
}
}
// Control relays for PC groups (groupNo: 1 or 2, r1/r2: 0 = off, 1 = on)
void controlRelayGroup(int groupNo, int r1_status, int r2_status) {
int baseIndex = (groupNo - 1) * 2;
if (baseIndex + 1 >= 4 || groupNo < 1) {
Serial.println("Invalid group number");
return;
}
digitalWrite(RELAY_PINS[baseIndex], r1_status ? HIGH : LOW);
digitalWrite(RELAY_PINS[baseIndex + 1], r2_status ? HIGH : LOW);
}
PC 1 Control
PC 2 Control
PC 2 Led
PC 1 Led
PC 1 Buzzer
PC 2 Buzzer