#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <ArduinoJson.h>
// ================= NETWORK =================
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* mqtt_server = "broker.emqx.io"; // Switched to a more stable public broker
// Unique topic to prevent interference from others
const char* topic_req = "wokwibank/atm999_secure/request";
const char* topic_res = "wokwibank/atm999_secure/response";
WiFiClient espClient;
PubSubClient mqtt(espClient);
LiquidCrystal_I2C lcd(0x27, 20, 4);
// ================= DATABASE =================
StaticJsonDocument<1024> db;
unsigned long lastActivityTime = 0;
bool showingActivity = false;
void initDatabase() {
db["atm_host"]["id"] = "ATM001";
db["atm_host"]["cash_available"] = 10000;
JsonArray accounts = db.createNestedArray("accounts");
JsonObject acc1 = accounts.createNestedObject();
acc1["acc_no"] = "1234";
acc1["pin"] = "0000";
acc1["balance"] = 5000; // Fixed: Gave the account money to withdraw!
JsonObject acc2 = accounts.createNestedObject();
acc2["acc_no"] = "9876";
acc2["pin"] = "1111";
acc2["balance"] = 200;
}
// ================= LCD =================
void displayIdle() {
lcd.clear();
lcd.setCursor(0, 0); lcd.print("--- SERVER ONLINE ---");
lcd.setCursor(0, 1); lcd.print("Listening on:");
lcd.setCursor(0, 2); lcd.print("atm999_secure/req");
showingActivity = false;
}
// ================= MQTT CALLBACK =================
void callback(char* topic, byte* payload, unsigned int length) {
if (String(topic) != topic_req) return;
StaticJsonDocument<512> req;
DeserializationError error = deserializeJson(req, payload, length);
if (error) {
Serial.println("Invalid JSON Received!");
return;
}
String accNo = req["acc"].as<String>();
String pin = req["pin"].as<String>();
String type = req["type"].as<String>();
int amount = req["amount"] | 0;
lcd.clear();
lcd.setCursor(0, 0); lcd.print("Incoming Request");
lcd.setCursor(0, 1); lcd.print("Acc:" + accNo);
lcd.setCursor(0, 2); lcd.print("Type:" + type + " Amt:" + String(amount));
StaticJsonDocument<256> res;
res["status"] = "error";
res["msg"] = "Invalid Account/PIN";
res["balance"] = 0;
for (JsonObject acc : db["accounts"].as<JsonArray>()) {
if (acc["acc_no"] == accNo && acc["pin"] == pin) {
int bal = acc["balance"];
int atmCash = db["atm_host"]["cash_available"];
if (type == "B") {
res["status"] = "success";
res["msg"] = "Balance OK";
res["balance"] = bal;
}
else if (type == "W") {
if (atmCash < amount) {
res["msg"] = "ATM Out of Cash";
}
else if (bal < amount) {
res["msg"] = "Insufficient Funds";
}
else {
acc["balance"] = bal - amount;
db["atm_host"]["cash_available"] = atmCash - amount;
res["status"] = "success";
res["msg"] = "Take Cash";
res["balance"] = acc["balance"];
}
}
else if (type == "D") {
acc["balance"] = bal + amount;
db["atm_host"]["cash_available"] = atmCash + amount;
res["status"] = "success";
res["msg"] = "Deposit OK";
res["balance"] = acc["balance"];
}
break;
}
}
char buffer[256];
serializeJson(res, buffer);
Serial.println("Sending Response: ");
Serial.println(buffer);
mqtt.publish(topic_res, buffer);
lcd.setCursor(0, 3);
lcd.print("Response Sent");
lastActivityTime = millis();
showingActivity = true;
}
// ================= MQTT =================
void reconnect() {
while (!mqtt.connected()) {
lcd.clear();
lcd.setCursor(0, 0); lcd.print("Connecting MQTT...");
// Better randomness to prevent boot collisions
String clientId = "ATM_Server_" + String(random(0xffffff), HEX);
if (mqtt.connect(clientId.c_str())) {
Serial.println("MQTT Connected");
mqtt.subscribe(topic_req);
displayIdle();
} else {
lcd.setCursor(0, 2); lcd.print("Retrying...");
delay(1000);
}
}
}
// ================= SETUP =================
void setup() {
Serial.begin(115200);
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0); lcd.print("Booting Server...");
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(500); }
mqtt.setServer(mqtt_server, 1883);
mqtt.setCallback(callback);
mqtt.setBufferSize(512); // Give it plenty of breathing room
initDatabase();
// Seed random using analog noise to prevent duplicate Client IDs
randomSeed(analogRead(34) + micros());
reconnect();
}
// ================= LOOP =================
void loop() {
if (!mqtt.connected()) reconnect();
mqtt.loop();
if (showingActivity && (millis() - lastActivityTime > 2500)) {
displayIdle();
}
}