// ============================================================
// IoT ESP32 — Wokwi (satu file, copy-paste ke tab .ino utama)
// Board: ESP32 DevKit
// ============================================================
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <ESP32Servo.h>
// ── Config ──────────────────────────────────────────────────
#define WIFI_SSID "Wokwi-GUEST"
#define WIFI_PASSWORD ""
#define MQTT_HOST "8e5864607dc64981a0f08aae4e8e5cf7.s1.eu.hivemq.cloud"
#define MQTT_PORT 8883
#define MQTT_USER "audhighasu"
#define MQTT_PASS "4uf4d14n"
#define DEVICE_ID "nodemcu_001"
#define PIN_LAMP 2 // GPIO2 — LED
#define PIN_SERVO 18 // GPIO18 — Servo PWM
#define LAMP_ACTIVE_HIGH true
// ── MQTT state ──────────────────────────────────────────────
static char topicCmd[48];
static char topicStatus[48];
static char topicOnline[48];
static WiFiClientSecure wifiClient;
static PubSubClient mqttClient(wifiClient);
static Servo servo;
static int currentLamp = 0;
static int currentServo = 90;
static unsigned long lastWifiRetry = 0;
static unsigned long lastMqttRetry = 0;
static unsigned long mqttRetryDelay = 5000;
static const unsigned long MQTT_RETRY_MAX = 60000;
static unsigned long lastStatusPublish = 0;
static const unsigned long STATUS_INTERVAL_MS = 60000;
static const unsigned long WIFI_RETRY_MS = 5000;
static bool wifiLogged = false;
static void buildTopics() {
snprintf(topicCmd, sizeof(topicCmd), "iot/%s/cmd", DEVICE_ID);
snprintf(topicStatus, sizeof(topicStatus), "iot/%s/status", DEVICE_ID);
snprintf(topicOnline, sizeof(topicOnline), "iot/%s/online", DEVICE_ID);
}
static void setLamp(int value) {
currentLamp = value ? 1 : 0;
int level = LAMP_ACTIVE_HIGH ? currentLamp : !currentLamp;
digitalWrite(PIN_LAMP, level ? HIGH : LOW);
}
static void setServoAngle(int angle) {
currentServo = constrain(angle, 0, 180);
servo.write(currentServo);
}
static void publishStatus() {
if (!mqttClient.connected()) return;
StaticJsonDocument<192> doc;
doc["lamp"] = currentLamp;
doc["servo"] = currentServo;
doc["rssi"] = WiFi.RSSI();
doc["uptime"] = millis() / 1000;
char buffer[192];
serializeJson(doc, buffer, sizeof(buffer));
mqttClient.publish(topicStatus, buffer, true);
}
static void publishOnline(bool online) {
if (!mqttClient.connected()) return;
StaticJsonDocument<64> doc;
doc["online"] = online;
char buffer[64];
serializeJson(doc, buffer, sizeof(buffer));
mqttClient.publish(topicOnline, buffer, true);
}
static void onMqttMessage(char *topic, byte *payload, unsigned int length) {
Serial.print(F("MQTT ["));
Serial.print(topic);
Serial.print(F("] "));
for (unsigned int i = 0; i < length; i++) Serial.print((char)payload[i]);
Serial.println();
StaticJsonDocument<256> doc;
if (deserializeJson(doc, payload, length)) return;
bool changed = false;
if (doc["lamp"].is<int>()) {
setLamp(doc["lamp"].as<int>());
Serial.print(F("Lamp: "));
Serial.println(currentLamp ? F("ON") : F("OFF"));
changed = true;
}
if (doc["servo"].is<int>()) {
setServoAngle(doc["servo"].as<int>());
Serial.print(F("Servo: "));
Serial.println(currentServo);
changed = true;
}
if (changed) publishStatus();
}
static void hardwareInit() {
pinMode(PIN_LAMP, OUTPUT);
setLamp(0);
servo.setPeriodHertz(50);
servo.attach(PIN_SERVO, 500, 2400);
setServoAngle(90);
}
static void mqttInit() {
buildTopics();
wifiClient.setInsecure();
mqttClient.setServer(MQTT_HOST, MQTT_PORT);
mqttClient.setCallback(onMqttMessage);
mqttClient.setBufferSize(512);
}
static bool mqttConnect() {
if (mqttClient.connected()) return true;
Serial.print(F("Connecting MQTT as "));
Serial.println(DEVICE_ID);
const char *willPayload = "{\"online\":false}";
bool ok = mqttClient.connect(
DEVICE_ID, MQTT_USER, MQTT_PASS,
topicOnline, 1, true, willPayload);
if (!ok) {
Serial.print(F("MQTT failed rc="));
Serial.println(mqttClient.state());
return false;
}
Serial.println(F("MQTT connected"));
mqttClient.subscribe(topicCmd, 1);
Serial.print(F("Subscribed: "));
Serial.println(topicCmd);
publishOnline(true);
publishStatus();
mqttRetryDelay = 5000;
return true;
}
static void connectWiFi() {
if (WiFi.status() == WL_CONNECTED) return;
Serial.print(F("Connecting WiFi: "));
Serial.println(WIFI_SSID);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
}
static void mqttLoop() {
if (!mqttClient.connected()) {
unsigned long now = millis();
if (now - lastMqttRetry >= mqttRetryDelay) {
lastMqttRetry = now;
if (!mqttConnect()) {
mqttRetryDelay = min(mqttRetryDelay * 2, MQTT_RETRY_MAX);
Serial.print(F("MQTT retry in "));
Serial.print(mqttRetryDelay / 1000);
Serial.println(F("s"));
}
}
return;
}
mqttClient.loop();
unsigned long now = millis();
if (now - lastStatusPublish >= STATUS_INTERVAL_MS) {
lastStatusPublish = now;
publishStatus();
}
}
void setup() {
Serial.begin(115200);
delay(100);
Serial.println();
Serial.println(F("=== IoT ESP32 Wokwi — Lampu & Servo ==="));
Serial.print(F("Device ID: "));
Serial.println(DEVICE_ID);
hardwareInit();
mqttInit();
connectWiFi();
}
void loop() {
if (WiFi.status() != WL_CONNECTED) {
wifiLogged = false;
if (millis() - lastWifiRetry >= WIFI_RETRY_MS) {
lastWifiRetry = millis();
Serial.println(F("WiFi disconnected, retrying..."));
connectWiFi();
}
return;
}
if (!wifiLogged) {
wifiLogged = true;
Serial.print(F("WiFi OK, IP: "));
Serial.println(WiFi.localIP());
}
mqttLoop();
}