Sesuai rencana arsitektur yang dikembangkan, maka langkah selanjutnya adalah bagaimana memerintahkan aktuator bekerja ketika terdeteksi adanya person.
Tindakannya cukup sederhana, yaitu menyalakan LED dan Buzzer ketika pesan yang diterima mendeteksi adanya manusia/person.
Di sini kita melibatkan mikrokontroller ESP32 yang bertindak sebagai subscriber MQTT. Jika menerima pesan dengan topic “Person”, selanjutnya adalah meng-filter pesan tersebut untuk memastikan bahwa aktuator bekerja ketika terdeteksi person saja.
Perlu diingat tugas lain dari ESP32 adalah melakukan dekripsi AES terlebih dahulu, sebelum mengolah pesan yang diterima.
Berikut adalah rancangan wiring hardwarenya:
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>
#include <mbedtls/aes.h>
#include <mbedtls/base64.h>
// ========== KONFIGURASI ==========
const char* ssid = "Kos Putra Lindisanti";
const char* wifi_password = "poltekubsahabatan";
const char* mqtt_host = "c0099a6e70884169bfc6b2f482c29e2b.s1.eu.hivemq.cloud";
const int mqtt_port = 8883;
const char* mqtt_user = "dodit";
const char* mqtt_pass = "Workshoppbl2026";
const char* topic_person = "Person";
// AES Key & IV (sama dengan yang digunakan dosen)
const char* AES_KEY = "16bytekey1234567"; // 16 chars = 128 bits
const char* AES_IV = "16byteiv12345678"; // 16 chars IV
// GPIO pins
const int BUZZER_PIN = 4;
const int LED_PIN = 5;
// OLED
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
WiFiClientSecure wifiClient;
PubSubClient mqttClient(wifiClient);
// ========== PROTOTYPE ==========
void setup_wifi();
void mqttCallback(char* topic, byte* payload, unsigned int length);
void reconnectMQTT();
void showMessage(const char* msg);
void activateAlarm(bool on);
String decryptAES_CBC(String ciphertext_b64);
// ========== SETUP ==========
void setup() {
Serial.begin(115200);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
activateAlarm(false);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("SSD1306 gagal");
} else {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("Starting...");
display.display();
}
setup_wifi();
wifiClient.setInsecure(); // Abaikan sertifikat untuk testing
mqttClient.setServer(mqtt_host, mqtt_port);
mqttClient.setCallback(mqttCallback);
}
void setup_wifi() {
Serial.print("Connecting to Wi-Fi");
WiFi.begin(ssid, wifi_password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWi-Fi connected, IP: " + WiFi.localIP().toString());
}
// ========== MQTT CALLBACK (dengan dekripsi) ==========
void mqttCallback(char* topic, byte* payload, unsigned int length) {
char ciphertext[length + 1];
memcpy(ciphertext, payload, length);
ciphertext[length] = '\0';
String encryptedMsg = String(ciphertext);
Serial.print("Received encrypted (Base64): ");
Serial.println(encryptedMsg);
// Dekripsi AES
String decryptedJson = decryptAES_CBC(encryptedMsg);
if (decryptedJson.length() == 0) {
Serial.println("Decryption failed!");
showMessage("Decrypt error");
return;
}
Serial.print("Decrypted JSON: ");
Serial.println(decryptedJson);
// Parse JSON hasil dekripsi
DynamicJsonDocument doc(512);
DeserializationError error = deserializeJson(doc, decryptedJson);
if (error) {
Serial.print("JSON parse error: ");
Serial.println(error.c_str());
showMessage("JSON error");
return;
}
int num_person = doc["num_person"] | 0;
bool personDetected = (num_person > 0);
if (personDetected) {
Serial.printf("Person detected! count = %d\n", num_person);
char buf[32];
snprintf(buf, sizeof(buf), "Person Detected: %d", num_person);
showMessage(buf);
activateAlarm(true);
delay(3000);
activateAlarm(false);
} else {
Serial.println("No person detected");
showMessage("No person");
activateAlarm(false);
}
}
// ========== FUNGSI DEKRIPSI AES-128-CBC ==========
String decryptAES_CBC(String ciphertext_b64) {
// 1. Decode Base64
size_t max_decoded_len = ciphertext_b64.length() * 3 / 4 + 4;
unsigned char ciphertext[max_decoded_len];
size_t actual_len;
int ret = mbedtls_base64_decode(ciphertext, max_decoded_len, &actual_len,
(const unsigned char*)ciphertext_b64.c_str(),
ciphertext_b64.length());
if (ret != 0) {
Serial.println("Base64 decode error");
return "";
}
// 2. Siapkan buffer plaintext
unsigned char plaintext[actual_len + 1];
mbedtls_aes_context aes;
mbedtls_aes_init(&aes);
mbedtls_aes_setkey_dec(&aes, (const unsigned char*)AES_KEY, 128);
// 3. Salin IV (karena mbedtls_aes_crypt_cbc akan mengubahnya)
unsigned char iv_copy[16];
memcpy(iv_copy, AES_IV, 16);
// 4. Dekripsi
mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_DECRYPT, actual_len,
iv_copy, ciphertext, plaintext);
mbedtls_aes_free(&aes);
// 5. Hapus padding PKCS#7
int pad_len = plaintext[actual_len - 1];
if (pad_len > 0 && pad_len <= 16 && pad_len <= actual_len) {
plaintext[actual_len - pad_len] = '\0';
} else {
plaintext[actual_len] = '\0';
}
return String((char*)plaintext);
}
// ========== ALARM & OLED ==========
void activateAlarm(bool on) {
digitalWrite(LED_PIN, on ? HIGH : LOW);
digitalWrite(BUZZER_PIN, on ? HIGH : LOW);
}
void showMessage(const char* msg) {
display.clearDisplay();
display.setCursor(0, 0);
display.setTextSize(2);
display.println(msg);
display.display();
}
// ========== MQTT RECONNECT ==========
void reconnectMQTT() {
while (!mqttClient.connected()) {
Serial.print("Connecting to MQTT broker...");
if (mqttClient.connect("ESP32_Encrypted", mqtt_user, mqtt_pass)) {
Serial.println("connected");
mqttClient.subscribe(topic_person);
} else {
Serial.print("failed, rc=");
Serial.print(mqttClient.state());
Serial.println(" retry in 5s");
delay(5000);
}
}
}
// ========== MAIN LOOP ==========
void loop() {
if (!mqttClient.connected()) {
reconnectMQTT();
}
mqttClient.loop();
}
Penjelasan kode:
Kode ini adalah implementasi ESP32 sebagai subscriber MQTT yang menerima notifikasi deteksi person dari broker HiveMQ. Ini adalah komponen aktuator dalam sistem pengawasan CCTV yang telah dikembangkan sebelumnya. Sesuai arsitektur sistem, ESP32 memiliki dua tugas utama:
Mendekripsi pesan AES128 yang diterima dari broker MQTT
Mengaktifkan aktuator (LED dan Buzzer) ketika terdeteksi adanya person
#include <WiFi.h> // Koneksi WiFi untuk ESP32
#include <WiFiClientSecure.h> // Koneksi TLS/SSL ke MQTT
#include <PubSubClient.h> // MQTT client untuk subscribe/publish
#include <ArduinoJson.h> // Parsing JSON dari pesan terdekripsi
#include <Wire.h> // Komunikasi I2C untuk OLED
#include <Adafruit_SSD1306.h> // Library OLED 128x64
#include <Adafruit_GFX.h> // Graphics library untuk OLED
#include <mbedtls/aes.h> // AES enkripsi/dekripsi dari mbedtls
#include <mbedtls/base64.h> // Base64 decode (karena pesan terenkripsi dalam Base64)
Penjelasan Library:
const char* ssid = "Kos Putra Lindisanti"; // Ganti dengan SSID WiFi Anda
const char* wifi_password = "poltekubsahabatan"; // Ganti dengan password WiFi Anda
Penjelasan: Kredensial jaringan WiFi yang akan digunakan ESP32 untuk terhubung ke internet. WAJIB disesuaikan dengan jaringan Anda.
const char* mqtt_host = "c0099a6e70884169bfc6b2f482c29e2b.s1.eu.hivemq.cloud";
const int mqtt_port = 8883; // Port TLS untuk koneksi aman
const char* mqtt_user = "pbl2026"; // Ganti dengan username MQTT Anda
const char* mqtt_pass = "Pbl123456789#"; // Ganti dengan password MQTT Anda
const char* topic_person = "Person"; // Topic untuk subscribe notifikasi person
Penjelasan:
Port 8883 = MQTT over TLS (koneksi terenkripsi)
Topic "Person" = Sama dengan topic yang digunakan service Python untuk mengirim notifikasi
Kredensial MQTT : Harus sama dengan yang digunakan service Python
cpp
const char* AES_KEY = "16bytekey1234567"; // 16 chars = 128 bits
const char* AES_IV = "16byteiv12345678"; // 16 chars IV
Penjelasan:
Key dan IV harus SAMA PERSIS dengan yang digunakan di service Python
Key dan IV berukuran 16 karakter (128 bit) karena AES128
ESP32 akan menggunakan kunci ini untuk mendekripsi pesan dari broker MQTT
const int BUZZER_PIN = 4; // Buzzer terhubung ke GPIO 4
const int LED_PIN = 5; // LED terhubung ke GPIO 5
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
Penjelasan:
Buzzer PIN 4: Output untuk suara alarm
LED PIN 5: Output untuk lampu indikator
OLED 128x64: Menampilkan status dan jumlah person yang terdeteksi
WiFiClientSecure wifiClient; // Client dengan dukungan TLS
PubSubClient mqttClient(wifiClient); // MQTT client menggunakan secure connection
Penjelasan: WiFiClientSecure memungkinkan koneksi TLS ke broker MQTT (port 8883). Ini berbeda dengan WebSocket yang digunakan website.
void setup() {
Serial.begin(115200); // Inisialisasi serial monitor
pinMode(BUZZER_PIN, OUTPUT); // Set buzzer sebagai output
pinMode(LED_PIN, OUTPUT); // Set LED sebagai output
activateAlarm(false); // Matikan alarm saat start
// Inisialisasi OLED
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("SSD1306 gagal");
} else {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("Starting...");
display.display();
}
setup_wifi(); // Koneksi ke WiFi
// Abaikan sertifikat untuk testing (HANYA untuk development!)
wifiClient.setInsecure();
// Konfigurasi MQTT
mqttClient.setServer(mqtt_host, mqtt_port);
mqttClient.setCallback(mqttCallback);
}
Penjelasan Urutan Setup:
Serial monitor untuk debugging
Konfigurasi pin GPIO
Inisialisasi OLED display (alamat I2C 0x3C)
Koneksi ke WiFi
setInsecure() : Mengabaikan verifikasi sertifikat (untuk testing - TIDAK DISARANKAN untuk production)
Setup MQTT dengan callback function
void setup_wifi() {
Serial.print("Connecting to Wi-Fi");
WiFi.begin(ssid, wifi_password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWi-Fi connected, IP: " + WiFi.localIP().toString());
}
Penjelasan: Loop hingga ESP32 berhasil terhubung ke WiFi, lalu menampilkan IP address.
String decryptAES_CBC(String ciphertext_b64) {
// STEP 1: DECODE BASE64
size_t max_decoded_len = ciphertext_b64.length() * 3 / 4 + 4;
unsigned char ciphertext[max_decoded_len];
size_t actual_len;
int ret = mbedtls_base64_decode(ciphertext, max_decoded_len, &actual_len,
(const unsigned char*)ciphertext_b64.c_str(),
ciphertext_b64.length());
if (ret != 0) {
Serial.println("Base64 decode error");
return "";
}
// STEP 2: SIAPKAN MbedTLS AES CONTEXT
unsigned char plaintext[actual_len + 1];
mbedtls_aes_context aes;
mbedtls_aes_init(&aes);
mbedtls_aes_setkey_dec(&aes, (const unsigned char*)AES_KEY, 128); // Set key 128 bit
// STEP 3: SALIN IV (karena fungsi crypt_cbc akan memodifikasinya)
unsigned char iv_copy[16];
memcpy(iv_copy, AES_IV, 16);
// STEP 4: LAKUKAN DEKRIPSI CBC
mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_DECRYPT, actual_len,
iv_copy, ciphertext, plaintext);
mbedtls_aes_free(&aes);
// STEP 5: HAPUS PADDING PKCS#7
int pad_len = plaintext[actual_len - 1];
if (pad_len > 0 && pad_len <= 16 && pad_len <= actual_len) {
plaintext[actual_len - pad_len] = '\0';
} else {
plaintext[actual_len] = '\0';
}
return String((char*)plaintext);
}
Penjelasan Detail Proses Dekripsi:
Alur Data:
Pesan MQTT (Base64) → Base64 Decode → Ciphertext (binary)
→ AES128 CBC Decrypt → Plaintext (JSON dengan padding)
→ Hapus padding → JSON asli
void mqttCallback(char* topic, byte* payload, unsigned int length) {
// STEP 1: EKSTRAK PESAN TERENKRIPSI
char ciphertext[length + 1];
memcpy(ciphertext, payload, length);
ciphertext[length] = '\0';
String encryptedMsg = String(ciphertext);
Serial.print("Received encrypted (Base64): ");
Serial.println(encryptedMsg);
// STEP 2: DEKRIPSI AES
String decryptedJson = decryptAES_CBC(encryptedMsg);
if (decryptedJson.length() == 0) {
Serial.println("Decryption failed!");
showMessage("Decrypt error");
return;
}
Serial.print("Decrypted JSON: ");
Serial.println(decryptedJson);
// STEP 3: PARSE JSON
DynamicJsonDocument doc(512);
DeserializationError error = deserializeJson(doc, decryptedJson);
if (error) {
Serial.print("JSON parse error: ");
Serial.println(error.c_str());
showMessage("JSON error");
return;
}
// STEP 4: EKSTRAK JUMLAH PERSON
int num_person = doc["num_person"] | 0;
bool personDetected = (num_person > 0);
// STEP 5: AKTIFKAN AKTUATOR JIKA ADA PERSON
if (personDetected) {
Serial.printf("Person detected! count = %d\n", num_person);
char buf[32];
snprintf(buf, sizeof(buf), "Person Detected: %d", num_person);
showMessage(buf);
activateAlarm(true); // NYALAKAN LED dan BUZZER
delay(3000); // Tahan selama 3 detik
activateAlarm(false); // MATIKAN LED dan BUZZER
} else {
Serial.println("No person detected");
showMessage("No person");
activateAlarm(false);
}
}
Penjelasan Alur Callback:
Terima pesan dari topic "Person" dalam bentuk Base64 terenkripsi
Dekripsi pesan menggunakan fungsi decryptAES_CBC()
Parse JSON hasil dekripsi untuk mendapatkan nilai num_person
Cek kondisi: apakah num_person > 0?
Aktuasi: Jika ada person → LED dan Buzzer menyala 3 detik, Jika tidak ada → semua mati
// Sebelum dekripsi (Base64):
"VGVzdCBkYXRhIGVuY3J5cHRlZA=="
// Sesudah dekripsi (JSON):
{
"event": "person_detected",
"message": "Kamera mendeteksi manusia",
"timestamp": "2026-05-05T10:30:00",
"num_person": 2,
"camera_url": "rtsp://192.168.134.229:1945/"
}
cpp
void activateAlarm(bool on) {
digitalWrite(LED_PIN, on ? HIGH : LOW); // LED menyala jika on = true
digitalWrite(BUZZER_PIN, on ? HIGH : LOW); // Buzzer berbunyi jika on = true
}
Penjelasan: Fungsi sederhana untuk mengontrol dua aktuator sekaligus:
HIGH (3.3V) → LED menyala, Buzzer berbunyi
LOW (0V) → LED mati, Buzzer diam
void showMessage(const char* msg) {
display.clearDisplay(); // Hapus tampilan sebelumnya
display.setCursor(0, 0); // Posisi kursor (0,0) - kiri atas
display.setTextSize(2); // Ukuran font 2x (lebih besar)
display.println(msg); // Tulis pesan
display.display(); // Refresh tampilan
}
Penjelasan: Menampilkan pesan di OLED 128x64 dengan ukuran font besar agar mudah dibaca.
Contoh tampilan:
┌────────────────┐
│ Person Detected: │
│ 2 │
└────────────────┘
void reconnectMQTT() {
while (!mqttClient.connected()) {
Serial.print("Connecting to MQTT broker...");
if (mqttClient.connect("ESP32_Encrypted", mqtt_user, mqtt_pass)) {
Serial.println("connected");
mqttClient.subscribe(topic_person); // Subscribe ke topic Person
} else {
Serial.print("failed, rc=");
Serial.print(mqttClient.state());
Serial.println(" retry in 5s");
delay(5000);
}
}
}
Penjelasan:
Client ID "ESP32_Encrypted" : Identifier unik untuk ESP32 di broker
Subscribe ke topic "Person" : Setelah koneksi berhasil, langsung subscribe
Auto-retry: Jika gagal, coba lagi setiap 5 detik
void loop() {
if (!mqttClient.connected()) {
reconnectMQTT(); // Cek dan reconnect jika perlu
}
mqttClient.loop(); // Proses incoming MQTT messages
}
Penjelasan:
Setiap iterasi loop memeriksa koneksi MQTT
mqttClient.loop() memproses pesan yang masuk dan menjaga koneksi tetap hidup
1. ESP32 Power ON
↓
2. Setup:
- Inisialisasi Serial
- Set pin GPIO (LED, Buzzer)
- Inisialisasi OLED
↓
3. Koneksi WiFi
↓
4. Koneksi MQTT TLS ke HiveMQ (port 8883)
↓
5. Subscribe ke topic "Person"
↓
6. ┌────────────────────────────────┐
│ LOOP UTAMA: │
│ - Terima pesan MQTT (Base64 encrypted) │
│ - Decode Base64 │
│ - Dekripsi AES128 CBC │
│ - Hapus padding PKCS#7 │
│ - Parse JSON │
│ - Ekstrak "num_person" │
│ │
│ IF num_person > 0: │
│ - Tampilkan "Person Detected: X" di OLED │
│ - LED ON │
│ - Buzzer ON │
│ - Delay 3000ms │
│ - LED OFF │
│ - Buzzer OFF │
│ ELSE: │
│ - Tampilkan "No person" │
│ - LED OFF, Buzzer OFF │
└─────────────────────────────────┘
↓
7. Jika koneksi MQTT putus → reconnect otomatis
Penting: ESP32 adalah satu-satunya komponen selain service Python yang melakukan operasi enkripsi/dekripsi. Website tidak perlu karena sudah didekripsi oleh proxy.
"VGVzdCBkYXRhIGVuY3J5cHRlZA=="
Binary ciphertext (16 bytes kelipatan)
{"event":"person_detected","message":"Kamera mendeteksi manusia","timestamp":"2026-05-05T10:30:00","num_person":3,"camera_url":"rtsp://192.168.130.87:1945/"}
num_person = 3 // ESP32 mengambil nilai ini
Sesuaikan kredensial di bagian konfigurasi:
SSID dan password WiFi
MQTT username & password
AES Key & IV (harus sama dengan service Python)
Upload kode ke ESP32 menggunakan Arduino IDE
Buka Serial Monitor (115200 baud) untuk melihat log
Jalankan service Python (deteksi person)
Lihat LED menyala dan Buzzer berbunyi saat person terdeteksi
OLED menampilkan "Person Detected: X" atau "No person"
ESP32 bertindak sebagai subscriber MQTT terenkripsi dan aktuator fisik dalam sistem pengawasan person. Keunikan implementasi ini adalah:
ESP32 melakukan dekripsi sendiri - Tidak bergantung pada server lain
Realtime response - Alarm menyala segera saat person terdeteksi
Visual feedback - OLED memberikan konfirmasi visual
Secure communication - Semua pesan MQTT terenkripsi AES128
Ini melengkapi arsitektur end-to-end: Kamera → Deteksi → Enkripsi → MQTT → Dekripsi → Aktuasi