#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <WiFi.h>
#include <PubSubClient.h>
// ------- Pins -------
#define LED_GREEN 23
#define LED_RED 19
// ------- LCD (I2C) -------
LiquidCrystal_I2C lcd(0x27, 20, 4); // 20x4 at address 0x27
// ------- Wi‑Fi (Wokwi virtual AP) -------
const char* ssid = "Wokwi-GUEST";
const char* password = "";
// ------- MQTT (EMQX Public Broker) -------
const char* mqttHost = "broker.emqx.io";
const int mqttPort = 1883;
// Unique topics for you
const char* mqttTopic = "kutrrh/joseph/parking/status"; // telemetry
const char* mqttLwt = "kutrrh/joseph/parking/lwt"; // presence
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
// ---- Global state ----
int freeSlots = 23; // initial value
int lastPublishedSlots = -1; // force first publish
// ---- Helpers ----
void connectWiFi() {
if (WiFi.status() == WL_CONNECTED) return;
Serial.print("Connecting WiFi");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(300);
Serial.print(".");
}
Serial.println();
Serial.print("WiFi connected. IP: ");
Serial.println(WiFi.localIP());
}
void connectMQTT() {
mqtt.setServer(mqttHost, mqttPort);
int attempt = 0;
while (!mqtt.connected()) {
String clientId = "esp32-parking-" + String((uint32_t)ESP.getEfuseMac(), HEX);
Serial.printf("MQTT connect try %d ... ", ++attempt);
// Set LWT via connect()
bool ok = mqtt.connect(
clientId.c_str(),
mqttLwt, // willTopic
0, // willQoS
true, // willRetain
"device:esp32-entrance-A status:offline" // willMessage
);
if (ok) {
Serial.println("OK");
// Announce online (retained)
mqtt.publish(mqttLwt, "device:esp32-entrance-A status:online", true);
break;
} else {
Serial.printf("failed rc=%d; retrying...\n", mqtt.state());
delay(min(500 * attempt, 4000)); // incremental backoff
}
}
}
void updateUI() {
// LEDs
digitalWrite(LED_GREEN, freeSlots > 0 ? HIGH : LOW);
digitalWrite(LED_RED, freeSlots > 0 ? LOW : HIGH);
// LCD
lcd.setCursor(0, 0); lcd.print("Parking Status: ");
lcd.setCursor(0, 1); lcd.print("Free Slots: ");
lcd.setCursor(12,1); lcd.print(" ");
lcd.setCursor(12,1); lcd.print(freeSlots);
}
void publishStatusIfChanged() {
if (!mqtt.connected()) return;
if (freeSlots != lastPublishedSlots) {
char json[160];
snprintf(json, sizeof(json),
"{\"device\":\"esp32-entrance-A\",\"freeSlots\":%d,\"status\":\"%s\"}",
freeSlots, freeSlots > 0 ? "OPEN" : "FULL");
bool ok = mqtt.publish(mqttTopic, json, true); // retain=true
Serial.printf("MQTT publish (changed) -> topic='%s' payload=%s | ok=%d\n",
mqttTopic, json, ok);
lastPublishedSlots = freeSlots;
}
}
// Optional: clears retained message on the broker (call once if needed)
void clearRetained() {
// Publish empty payload with retain=true to delete retained message on this topic
bool ok = mqtt.publish(mqttTopic, "", true);
Serial.printf("Cleared retained on '%s' | ok=%d\n", mqttTopic, ok);
}
void setup() {
Serial.begin(115200);
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_RED, OUTPUT);
Wire.begin(21, 22);
lcd.init();
lcd.backlight();
connectWiFi();
connectMQTT();
updateUI();
publishStatusIfChanged();
Serial.println("\nType 's0' or 's10' in Serial to change freeSlots for testing.");
}
void loop() {
if (WiFi.status() != WL_CONNECTED) connectWiFi();
if (!mqtt.connected()) connectMQTT();
mqtt.loop();
// --- Demo: read simple serial commands to change the value live ---
// Type s0 -> sets freeSlots=0
// Type s10 -> sets freeSlots=10
if (Serial.available()) {
String cmd = Serial.readStringUntil('\n');
cmd.trim();
if (cmd.equalsIgnoreCase("s0")) {
freeSlots = 0;
updateUI();
publishStatusIfChanged();
} else if (cmd.equalsIgnoreCase("s10")) {
freeSlots = 10;
updateUI();
publishStatusIfChanged();
} else if (cmd.equalsIgnoreCase("clear")) {
clearRetained(); // optional: remove retained message on broker
}
}
}