#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <SPI.h>
#include <MFRC522.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <WiFiClientSecure.h>
// ==================== LCD I2C ====================
LiquidCrystal_I2C lcd(0x27, 16, 2);
// ==================== RFID ====================
#define RST_PIN 16
#define SS_PIN 5
MFRC522 mfrc522(SS_PIN, RST_PIN);
// ==================== LED & BUZZER ====================
#define LED_STATUS 2
#define LED_RFID 4
#define BUZZER 32
// ==================== WIFI ====================
const char* ssid = "Wokwi-GUEST";
const char* password = "";
// ==================== HIVEMQ CLOUD (MQTT over TLS) ====================
const char* mqtt_server = "4aae686ba80149179bb7924bf13d5e5c.s1.eu.hivemq.cloud";
const int mqtt_port = 8883;
const char* mqtt_user = "ThankMazic";
const char* mqtt_password = "ThankDat5x6";
// MQTT Topics
const String topic_bus_location = "bus/tracking/location";
const String topic_bus_transaction = "bus/tracking/transaction";
const String topic_bus_status = "bus/tracking/status";
const String topic_bus_emergency = "bus/tracking/emergency";
// ==================== THÔNG TIN XE ====================
const String busId = "BUS_05";
const String routeId = "ROUTE_05";
const int fareAmount = 7000;
int passengerCount = 0;
int totalFareCollected = 0;
int tapCountAtCurrentStop = 0;
// ==================== MQTT CLIENT ====================
WiFiClientSecure espClient;
PubSubClient client(espClient);
// ==================== CẤU HÌNH THỜI GIAN DỪNG ====================
const int DEFAULT_STOP_TIME = 25000;
// ==================== THỜI GIAN GỬI DỮ LIỆU ====================
const unsigned long SEND_INTERVAL_MS = 250; // 0.25 giây
// ==================== FORWARD DECLARATION ====================
void sendStopStatus(bool isStopped);
void sendLocationUpdate();
void sendTransaction(String cardId);
void sendRealtimeData(); // Hàm mới để gửi dữ liệu thời gian thực
// ==================== GPS SIMULATOR ====================
struct BusStop {
float lat;
float lng;
String name;
int waitTime;
};
BusStop busRoute[] = {
{21.0285, 105.8542, "Buu Dien HN", DEFAULT_STOP_TIME},
{21.0240, 105.8525, "BV Viet Duc", DEFAULT_STOP_TIME},
{21.0190, 105.8520, "Nha hat Lon", DEFAULT_STOP_TIME},
{21.0140, 105.8500, "Trang Tien", DEFAULT_STOP_TIME},
{21.0100, 105.8480, "Ben xe Giap Bat", DEFAULT_STOP_TIME},
{21.0060, 105.8450, "Kim Dong", DEFAULT_STOP_TIME},
{21.0020, 105.8420, "Giai Phong", DEFAULT_STOP_TIME},
{20.9980, 105.8380, "DH Bach Khoa", DEFAULT_STOP_TIME},
{20.9940, 105.8340, "Le Thanh Nghi", DEFAULT_STOP_TIME},
{20.9900, 105.8320, "Bach Mai", DEFAULT_STOP_TIME},
{20.9860, 105.8300, "Truong Dinh", DEFAULT_STOP_TIME},
{20.9820, 105.8280, "Times City", DEFAULT_STOP_TIME},
{20.9780, 105.8250, "Tan Mai", DEFAULT_STOP_TIME},
{20.9750, 105.8220, "Ben xe Nuoc Ngam", DEFAULT_STOP_TIME}
};
int totalStops = sizeof(busRoute) / sizeof(busRoute[0]);
class BusGPSSimulator {
private:
int currentStopIndex;
float currentLat, currentLng, progress, speedKmh;
bool isMoving;
unsigned long lastUpdate, stopStart;
float calculateDistance(float lat1, float lng1, float lat2, float lng2) {
float R = 6371;
float dLat = (lat2 - lat1) * PI / 180;
float dLng = (lng2 - lng1) * PI / 180;
float a = sin(dLat/2) * sin(dLat/2) +
cos(lat1 * PI / 180) * cos(lat2 * PI / 180) *
sin(dLng/2) * sin(dLng/2);
float c = 2 * atan2(sqrt(a), sqrt(1-a));
return R * c;
}
public:
BusGPSSimulator() {
currentStopIndex = 0;
currentLat = busRoute[0].lat;
currentLng = busRoute[0].lng;
progress = 0;
isMoving = false;
lastUpdate = 0;
stopStart = millis();
speedKmh = 0;
}
void update() {
unsigned long now = millis();
if (now - lastUpdate < 100) return;
lastUpdate = now;
if (isMoving) {
int nextStop = (currentStopIndex + 1) % totalStops;
float distance = calculateDistance(
busRoute[currentStopIndex].lat, busRoute[currentStopIndex].lng,
busRoute[nextStop].lat, busRoute[nextStop].lng);
float progressIncrement = (speedKmh / 3.6) / (distance * 1000);
progress += progressIncrement;
if (progress >= 1.0) {
progress = 0;
isMoving = false;
stopStart = now;
speedKmh = 0;
currentLat = busRoute[currentStopIndex].lat;
currentLng = busRoute[currentStopIndex].lng;
tapCountAtCurrentStop = 0;
Serial.println("\n🚌 XE DỪNG TẠI: " + busRoute[currentStopIndex].name);
Serial.println("⏱️ Có 25 giây để lên xe!");
sendStopStatus(true);
} else {
currentLat = busRoute[currentStopIndex].lat +
(busRoute[nextStop].lat - busRoute[currentStopIndex].lat) * progress;
currentLng = busRoute[currentStopIndex].lng +
(busRoute[nextStop].lng - busRoute[currentStopIndex].lng) * progress;
}
} else {
if (now - stopStart >= busRoute[currentStopIndex].waitTime) {
isMoving = true;
speedKmh = random(25, 45);
currentStopIndex = (currentStopIndex + 1) % totalStops;
Serial.println("\n🚌 XE KHỞI HÀNH!");
Serial.print("📊 Đã có ");
Serial.print(passengerCount);
Serial.print(" khách, thu ");
Serial.print(totalFareCollected);
Serial.println(" VND");
sendStopStatus(false);
}
}
}
bool isAtStop() { return !isMoving; }
String getCurrentStop() { return busRoute[currentStopIndex].name; }
String getNextStop() { return busRoute[(currentStopIndex + 1) % totalStops].name; }
float getSpeed() { return speedKmh; }
float getLatitude() { return currentLat; }
float getLongitude() { return currentLng; }
int getRemainingStopTime() {
if (!isMoving) {
unsigned long elapsed = millis() - stopStart;
int waitTime = busRoute[currentStopIndex].waitTime;
if (elapsed < waitTime) return (waitTime - elapsed) / 1000;
}
return 0;
}
};
BusGPSSimulator gps;
// ==================== MQTT CALLBACK ====================
void mqttCallback(char* topic, byte* payload, unsigned int length) {
String message;
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.print("📨 Nhận MQTT [");
Serial.print(topic);
Serial.print("]: ");
Serial.println(message);
}
// ==================== KẾT NỐI MQTT VỚI SSL (Insecure Mode) ====================
void reconnectMQTT() {
while (!client.connected()) {
Serial.print("📡 Kết nối MQTT qua TLS (insecure mode)...");
String clientId = "ESP32_Bus_" + busId;
espClient.setInsecure();
if (client.connect(clientId.c_str(), mqtt_user, mqtt_password)) {
Serial.println(" ✅ Đã kết nối HiveMQ Cloud!");
client.subscribe("bus/tracking/command");
// Gửi thông báo online
StaticJsonDocument<200> doc;
doc["bus_id"] = busId;
doc["route_id"] = routeId;
doc["status"] = "online";
doc["timestamp"] = millis();
char buffer[200];
serializeJson(doc, buffer);
client.publish("bus/tracking/status", buffer);
Serial.println("📤 Đã gửi trạng thái online");
} else {
Serial.print(" ❌ Thất bại, rc=");
Serial.print(client.state());
Serial.println(" thử lại sau 5 giây...");
delay(5000);
}
}
}
// ==================== GỬI DỮ LIỆU THỜI GIAN THỰC (MỖI 0.25s) ====================
void sendRealtimeData() {
if (!client.connected()) return;
StaticJsonDocument<512> doc; // Tăng kích thước để chứa nhiều dữ liệu hơn
// Thông tin cơ bản
doc["bus_id"] = busId;
doc["route_id"] = routeId;
doc["timestamp"] = millis();
// Thông tin vị trí và trạng thái
doc["lat"] = gps.getLatitude();
doc["lng"] = gps.getLongitude();
doc["speed"] = gps.getSpeed();
doc["is_stopped"] = gps.isAtStop();
doc["passengers"] = passengerCount;
doc["total_fare"] = totalFareCollected;
// Thông tin điểm dừng
if (gps.isAtStop()) {
doc["current_stop"] = gps.getCurrentStop();
doc["remaining_time"] = gps.getRemainingStopTime();
doc["tap_count_at_stop"] = tapCountAtCurrentStop;
} else {
doc["current_stop"] = gps.getCurrentStop();
doc["next_stop"] = gps.getNextStop();
}
// Thông tin hành trình
doc["fare_amount"] = fareAmount;
// Thêm sequence number để theo dõi gói tin
static unsigned long sequence = 0;
doc["seq"] = sequence++;
char buffer[512];
serializeJson(doc, buffer);
// Gửi lên topic location (cập nhật liên tục)
client.publish(topic_bus_location.c_str(), buffer);
// In ra Serial để debug (có thể comment lại nếu muốn)
// Serial.print("📤 RT["); Serial.print(sequence-1); Serial.print("]: ");
// Serial.println(buffer);
}
// ==================== GỬI TRẠNG THÁI DỪNG/KHỞI HÀNH ====================
void sendStopStatus(bool isStopped) {
if (!client.connected()) return;
StaticJsonDocument<256> doc;
doc["bus_id"] = busId;
doc["route_id"] = routeId;
doc["is_stopped"] = isStopped;
doc["stop_name"] = gps.getCurrentStop();
doc["passengers"] = passengerCount;
doc["timestamp"] = millis();
if (isStopped) {
doc["remaining_time"] = gps.getRemainingStopTime();
} else {
doc["next_stop"] = gps.getNextStop();
doc["speed"] = gps.getSpeed();
}
char buffer[256];
serializeJson(doc, buffer);
client.publish(topic_bus_status.c_str(), buffer);
Serial.println("📤 Đã gửi trạng thái lên MQTT");
}
// ==================== GỬI GIAO DỊCH QUẸT THẺ ====================
void sendTransaction(String cardId) {
if (!client.connected()) return;
StaticJsonDocument<256> doc;
doc["bus_id"] = busId;
doc["route_id"] = routeId;
doc["card_id"] = cardId;
doc["fare"] = fareAmount;
doc["stop_name"] = gps.getCurrentStop();
doc["passengers"] = passengerCount;
doc["total_fare"] = totalFareCollected;
doc["tap_count_at_stop"] = tapCountAtCurrentStop;
doc["timestamp"] = millis();
char buffer[256];
serializeJson(doc, buffer);
client.publish(topic_bus_transaction.c_str(), buffer);
}
// ==================== GỬI CẢNH BÁO KHẨN CẤP ====================
void sendEmergencyAlert() {
if (!client.connected()) return;
StaticJsonDocument<256> doc;
doc["bus_id"] = busId;
doc["route_id"] = routeId;
doc["alert_type"] = "emergency";
doc["lat"] = gps.getLatitude();
doc["lng"] = gps.getLongitude();
doc["stop_name"] = gps.getCurrentStop();
doc["passengers"] = passengerCount;
doc["timestamp"] = millis();
char buffer[256];
serializeJson(doc, buffer);
client.publish(topic_bus_emergency.c_str(), buffer);
Serial.println("🚨 Đã gửi cảnh báo khẩn cấp!");
}
// ==================== MÔ PHỎNG QUẸT THẺ ====================
void simulateCardTap(String cardId) {
if (!gps.isAtStop()) {
Serial.println("❌ Xe đang chạy, không thể quẹt thẻ!");
return;
}
passengerCount++;
totalFareCollected += fareAmount;
tapCountAtCurrentStop++;
for(int i = 0; i < 2; i++) {
digitalWrite(LED_RFID, HIGH);
tone(BUZZER, 1800, 80);
delay(50);
digitalWrite(LED_RFID, LOW);
delay(30);
}
noTone(BUZZER);
Serial.print("💳 [#");
Serial.print(tapCountAtCurrentStop);
Serial.print("] Thẻ: ");
Serial.print(cardId);
Serial.print(" | Tổng: ");
Serial.print(passengerCount);
Serial.print(" khách | Còn ");
Serial.print(gps.getRemainingStopTime());
Serial.println(" giây");
sendTransaction(cardId);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Khach #" + String(tapCountAtCurrentStop));
lcd.setCursor(0, 1);
lcd.print("Da len: " + String(passengerCount));
delay(800);
updateLCD();
}
// ==================== ĐỌC RFID ====================
void readRFID() {
if (!gps.isAtStop()) return;
if (!mfrc522.PICC_IsNewCardPresent()) return;
if (!mfrc522.PICC_ReadCardSerial()) return;
String cardId = "";
for (byte i = 0; i < mfrc522.uid.size; i++) {
if (mfrc522.uid.uidByte[i] < 0x10) cardId += "0";
cardId += String(mfrc522.uid.uidByte[i], HEX);
}
cardId.toUpperCase();
simulateCardTap(cardId);
mfrc522.PICC_HaltA();
}
// ==================== CẬP NHẬT LCD ====================
void updateLCD() {
static unsigned long lastUpdate = 0;
if (millis() - lastUpdate < 200) return;
lastUpdate = millis();
lcd.clear();
if (gps.isAtStop()) {
lcd.setCursor(0, 0);
String stop = gps.getCurrentStop();
if (stop.length() > 14) stop = stop.substring(0, 14);
lcd.print(stop);
lcd.setCursor(0, 1);
lcd.print("Quet:" + String(tapCountAtCurrentStop) + " Con:" + String(gps.getRemainingStopTime()) + "s");
} else {
lcd.setCursor(0, 0);
lcd.print("-> " + gps.getNextStop().substring(0, 14));
lcd.setCursor(0, 1);
lcd.print(String(gps.getSpeed(), 0) + "km/h KH:" + String(passengerCount));
}
}
// ==================== XỬ LÝ LỆNH SERIAL ====================
void handleSerialCommand() {
if (!Serial.available()) return;
String cmd = Serial.readString();
cmd.trim();
cmd.toLowerCase();
if (cmd == "tap" || cmd == "t") {
simulateCardTap("SERIAL_" + String(millis() % 10000));
}
else if (cmd == "status" || cmd == "s") {
Serial.println("\n=== TRẠNG THÁI XE ===");
Serial.print("MQTT: ");
Serial.println(client.connected() ? "✅ Đã kết nối" : "❌ Mất kết nối");
Serial.print("Trạng thái: ");
if (gps.isAtStop()) {
Serial.print("DỪNG tại ");
Serial.println(gps.getCurrentStop());
Serial.print("Thời gian còn: ");
Serial.print(gps.getRemainingStopTime());
Serial.println(" giây");
} else {
Serial.print("ĐANG CHẠY tới ");
Serial.println(gps.getNextStop());
Serial.print("Tốc độ: ");
Serial.print(gps.getSpeed());
Serial.println(" km/h");
}
Serial.print("Số khách: ");
Serial.println(passengerCount);
Serial.print("Doanh thu: ");
Serial.print(totalFareCollected);
Serial.println(" VND");
Serial.println("====================\n");
}
else if (cmd == "emergency" || cmd == "e") {
sendEmergencyAlert();
}
else if (cmd.startsWith("autotap")) {
int num = cmd.substring(7).toInt();
if (num <= 0) num = 10;
Serial.print("🚀 Tự động quẹt ");
Serial.print(num);
Serial.println(" thẻ...");
for (int i = 1; i <= num; i++) {
simulateCardTap("AUTO_" + String(i));
delay(200);
}
Serial.println("✅ Hoàn thành!\n");
}
else if (cmd == "help" || cmd == "h") {
Serial.println("\n=== LỆNH ĐIỀU KHIỂN ===");
Serial.println("tap - Mô phỏng quẹt 1 thẻ");
Serial.println("status - Xem trạng thái xe");
Serial.println("emergency - Gửi cảnh báo khẩn cấp");
Serial.println("autotap N - Tự động quẹt N thẻ");
Serial.println("help - Hiển thị trợ giúp");
Serial.println("========================\n");
}
else {
Serial.println("❌ Không hiểu lệnh. Gõ 'help' để xem danh sách.");
}
}
// ==================== KHỞI TẠO ====================
void setup() {
Serial.begin(115200);
delay(100);
Wire.begin(21, 22);
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Bus System");
lcd.setCursor(0, 1);
lcd.print("MQTT + RFID");
pinMode(LED_STATUS, OUTPUT);
pinMode(LED_RFID, OUTPUT);
pinMode(BUZZER, OUTPUT);
for(int i = 0; i < 2; i++) {
digitalWrite(LED_STATUS, HIGH);
digitalWrite(LED_RFID, HIGH);
tone(BUZZER, 1000, 100);
delay(150);
digitalWrite(LED_STATUS, LOW);
digitalWrite(LED_RFID, LOW);
delay(150);
}
noTone(BUZZER);
SPI.begin();
mfrc522.PCD_Init();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi");
lcd.setCursor(0, 1);
lcd.print("Connecting...");
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
attempts++;
digitalWrite(LED_STATUS, !digitalRead(LED_STATUS));
}
if (WiFi.status() == WL_CONNECTED) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi OK!");
digitalWrite(LED_STATUS, HIGH);
espClient.setInsecure();
client.setServer(mqtt_server, mqtt_port);
client.setCallback(mqttCallback);
lcd.setCursor(0, 1);
lcd.print("MQTT...");
reconnectMQTT();
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi Failed!");
lcd.setCursor(0, 1);
lcd.print("Offline Mode");
digitalWrite(LED_STATUS, LOW);
}
delay(1500);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(busId + " " + routeId);
lcd.setCursor(0, 1);
lcd.print("Fare: " + String(fareAmount) + "VND");
delay(1500);
Serial.println("\n╔══════════════════════════════════════════════════════════╗");
Serial.println("║ BUS TRACKING SYSTEM READY - REAL-TIME ║");
Serial.println("╠══════════════════════════════════════════════════════════╣");
Serial.println("║ MQTT: HiveMQ Cloud (TLS/SSL - Insecure Mode) ║");
Serial.println("║ Topic: bus/tracking/location (cập nhật mỗi 0.25s) ║");
Serial.println("╠══════════════════════════════════════════════════════════╣");
Serial.println("║ 💡 LỆNH: tap, status, autotap N, emergency ║");
Serial.println("╚══════════════════════════════════════════════════════════╝\n");
}
// ==================== VÒNG LẶP CHÍNH ====================
void loop() {
static unsigned long lastSendTime = 0;
unsigned long currentTime = millis();
// Kiểm tra và duy trì kết nối MQTT
if (!client.connected()) {
reconnectMQTT();
}
client.loop();
// Cập nhật GPS
gps.update();
// Đọc RFID
readRFID();
// Cập nhật LCD
updateLCD();
// GỬI DỮ LIỆU THỜI GIAN THỰC MỖI 0.25 GIÂY
if (currentTime - lastSendTime >= SEND_INTERVAL_MS) {
sendRealtimeData();
lastSendTime = currentTime;
}
// Xử lý lệnh từ Serial
handleSerialCommand();
// Delay nhỏ để tránh quá tải CPU
delay(10);
}