Konsep dan Arsitektur MQTT untuk ESP32
Pada pertemuan kali ini akan membahas konsep dan arsitektur MQTT (Message Queuing Telemetry Transport) yang merupakan protokol komunikasi ringan untuk IoT. Mahasiswa akan mempelajari komponen-komponen MQTT seperti Broker, Publisher, Subscriber, sistem Topik, Quality of Service (QoS), dan format Payload. Praktikum akan dilakukan menggunakan ESP32 dengan MicroPython.
Alat dan Bahan
Hardware:
- ESP32 Development Board (NodeMCU ESP32 atau ESP32 DevKit)
- Kabel USB Type-C atau Micro-USB
- Komputer/Laptop dengan koneksi internet
- LED (opsional untuk visualisasi)
- Resistor 220Ω (opsional)
Software:
- MicroPython firmware pada ESP32 (sudah terinstal)
- Visual Studio Code dengan Pymakr
- MQTT Broker (pilih salah satu):
- Mosquitto (lokal)
- HiveMQ Cloud (cloud/gratis)
- MQTT Dashboard (test.mosquitto.org)
- MQTT Client untuk testing:
- MQTT Explorer
- MQTTX
- Atau aplikasi MQTT mobile
Bagian 1: Pengenalan MQTT
1.1 Apa itu MQTT?
MQTT (Message Queuing Telemetry Transport) adalah protokol messaging ringan yang dirancang khusus untuk komunikasi Machine-to-Machine (M2M) dan Internet of Things (IoT). Protokol ini dikembangkan oleh Dr. Andy Stanford-Clark dari IBM dan Arlen Nipper dari Arcom (sekarang Cirrus Link) pada tahun 1999.
Karakteristik MQTT:
- Lightweight: Overhead protokol sangat kecil (minimal 2 bytes)
- Bandwidth Efficient: Cocok untuk jaringan dengan bandwidth terbatas
- Reliable: Mendukung tiga level Quality of Service (QoS)
- Bi-directional: Komunikasi dua arah antara client dan broker
- Scalable: Dapat menangani ribuan client secara bersamaan
- Security: Mendukung TLS/SSL untuk enkripsi
Mengapa Menggunakan MQTT untuk IoT?
- Efisiensi Bandwidth: Header MQTT hanya 2 bytes, sangat efisien dibanding HTTP
- Connection Oriented: Menggunakan TCP/IP untuk koneksi yang reliable
- Many-to-Many Communication: Satu publisher dapat berkomunikasi dengan banyak subscriber
- Decoupling: Publisher dan Subscriber tidak perlu tahu keberadaan satu sama lain
- Quality of Service: Garansi pengiriman pesan sesuai kebutuhan
1.2 Perbandingan MQTT dengan Protokol Lain
Praktikum 1.1: Memahami Perbandingan Protokol
Perbandingan MQTT vs HTTP:
| Aspek | MQTT | HTTP |
|---|---|---|
| Arsitektur | Publish/Subscribe | Request/Response |
| Overhead | 2 bytes minimum | Ratusan bytes |
| Connection | Persistent | Request-based |
| Real-time | Excellent | Good |
| Bandwidth | Sangat efisien | Kurang efisien |
| Power Consumption | Rendah | Tinggi |
| Use Case | IoT, real-time data | Web, API |
Contoh Skenario Penggunaan:
Skenario 1: Sensor Suhu
├─ MQTT: Sensor publish suhu setiap detik → 2 bytes overhead
└─ HTTP: Sensor POST data setiap detik → 200+ bytes overhead
Skenario 2: Kontrol Lampu
├─ MQTT: Subscribe topik "rumah/lampu/ruangtamu" → instant notification
└─ HTTP: Polling setiap detik "/api/lampu/status" → delay + bandwidth waste
Kesimpulan: MQTT lebih efisien untuk komunikasi IoT yang frequent dan real-time
1.3 Arsitektur MQTT
MQTT menggunakan arsitektur Publish/Subscribe yang berbeda dengan model Request/Response tradisional. Arsitektur ini memungkinkan komunikasi yang lebih fleksibel dan scalable.
Praktikum 1.2: Memahami Arsitektur MQTT
Model Arsitektur:
┌─────────────────────────────────────────────────────────────┐
│ MQTT ARCHITECTURE │
└─────────────────────────────────────────────────────────────┘
Publisher 1 Subscriber 1
│ ▲
│ PUBLISH │
│ Topic: "sensor/suhu" │
│ Payload: "25.5" │
▼ │
┌────────────────────────────┐ │
│ │ SUBSCRIBE │
│ MQTT BROKER │ "sensor/suhu" │
│ (Message Distributor) │◄────────────────────┘
│ │
└────────────────────────────┘
│ ▲
│ │
▼ │ SUBSCRIBE
Subscriber 2 Publisher 2
(Receives data) (Sends data)
Komponen Utama:
- Broker: Server pusat yang menerima semua pesan dan mendistribusikan ke subscriber
- Publisher: Client yang mengirim (publish) data ke broker
- Subscriber: Client yang menerima (subscribe) data dari broker
- Topic: Channel atau kategori untuk mengorganisir pesan
- Message: Data yang dikirim (terdiri dari topic dan payload)
Bagian 2: MQTT Broker
2.1 Apa itu MQTT Broker?
MQTT Broker adalah server pusat dalam arsitektur MQTT yang bertindak sebagai perantara (intermediary) antara publisher dan subscriber. Broker bertanggung jawab untuk: - Menerima semua pesan dari publisher - Memfilter pesan berdasarkan topic - Mendistribusikan pesan ke subscriber yang sesuai - Mengelola koneksi client - Mengatur QoS (Quality of Service)
Praktikum 2.1: Memahami Peran Broker
Analogi Broker:
Broker MQTT seperti Kantor Pos:
Publisher = Pengirim Surat
├─ Mengirim surat dengan alamat (topic)
└─ Tidak perlu tahu siapa yang akan menerima
Broker = Kantor Pos
├─ Menerima semua surat
├─ Memilah berdasarkan alamat
├─ Mendistribusikan ke penerima yang tepat
└─ Menjamin pengiriman sesuai layanan (QoS)
Subscriber = Penerima Surat
├─ Mendaftar untuk menerima surat dari alamat tertentu
└─ Menerima surat otomatis ketika ada
2.2 Jenis-jenis MQTT Broker
Ada berbagai pilihan MQTT Broker yang dapat digunakan, baik yang berjalan lokal maupun di cloud.
Praktikum 2.2: Mengenal Broker Options
Broker Populer:
- Mosquitto (Eclipse)
- Open source dan gratis
- Ringan dan cepat
- Cocok untuk deployment lokal
-
Tersedia untuk Windows, Linux, macOS
-
HiveMQ
- Enterprise-grade broker
- Tersedia versi cloud gratis
- Scalable dan reliable
-
Dashboard yang user-friendly
-
EMQX
- High-performance broker
- Mendukung jutaan koneksi
- Clustering support
-
Open source dan enterprise version
-
CloudMQTT / HiveMQ Cloud
- Managed cloud broker
- Tidak perlu setup server
- Gratis untuk development
- Ideal untuk testing dan prototyping
Perbandingan Broker:
| Broker | Deployment | Scalability | Gratis? | Complexity |
|---|---|---|---|---|
| Mosquitto | Local | Medium | Yes | Low |
| HiveMQ Cloud | Cloud | High | Yes (limited) | Very Low |
| EMQX | Local/Cloud | Very High | Yes (CE) | Medium |
| test.mosquitto.org | Cloud | Low | Yes | Very Low |
2.3 Setup MQTT Broker untuk Praktikum
Untuk praktikum kali ini, kita akan menggunakan dua metode: broker publik untuk testing cepat, dan HiveMQ Cloud untuk production-ready testing.
Praktikum 2.3: Setup Broker
Metode 1: Menggunakan Public Broker (test.mosquitto.org)
Public broker ini dapat langsung digunakan tanpa registrasi, cocok untuk testing awal:
Broker Address: test.mosquitto.org
Port: 1883 (non-SSL)
Port: 8883 (SSL)
Username: (tidak diperlukan)
Password: (tidak diperlukan)
Catatan:
- Broker ini publik, semua orang bisa akses
- Jangan kirim data sensitif
- Cocok untuk learning dan testing
Metode 2: Setup HiveMQ Cloud (Recommended)
- Kunjungi https://www.hivemq.com/mqtt-cloud-broker/
- Klik "Sign Up" atau "Get Started Free"
- Isi form registrasi dengan email
- Verifikasi email
- Login ke dashboard HiveMQ Cloud
- Klik "Create Cluster"
- Pilih "Free Plan"
- Tunggu cluster dibuat (2-3 menit)
Setelah cluster siap, catat informasi berikut:
Cluster URL: xxxxxxxx.s2.eu.hivemq.cloud
Port: 8883 (SSL/TLS)
Username: (yang Anda buat)
Password: (yang Anda buat)
Metode 3: Install Mosquitto Lokal (Opsional)
Untuk Windows: 1. Download Mosquitto dari https://mosquitto.org/download/ 2. Install dengan wizard 3. Jalankan service Mosquitto 4. Broker berjalan di localhost:1883
Untuk Linux:
sudo apt update
sudo apt install mosquitto mosquitto-clients
sudo systemctl start mosquitto
sudo systemctl enable mosquitto
2.4 Testing Koneksi Broker
Sebelum menggunakan ESP32, kita akan test koneksi broker menggunakan MQTT client di komputer.
Praktikum 2.4: Test Broker dengan MQTT Explorer
Langkah 1: Install MQTT Explorer
- Download MQTT Explorer dari http://mqtt-explorer.com/
- Install aplikasi sesuai OS Anda
- Buka MQTT Explorer
Langkah 2: Koneksi ke Broker
Untuk Public Broker (test.mosquitto.org):
Connection Name: Test Mosquitto
Host: test.mosquitto.org
Port: 1883
Username: (kosongkan)
Password: (kosongkan)
Untuk HiveMQ Cloud:
Connection Name: HiveMQ Cloud
Host: xxxxxxxx.s2.eu.hivemq.cloud
Port: 8883
Username: (username Anda)
Password: (password Anda)
Protocol: mqtts:// (SSL/TLS)
Langkah 3: Test Publish dan Subscribe
- Klik "CONNECT"
- Jika berhasil, Anda akan melihat status "Connected"
- Di panel "Publish", masukkan:
- Topic:
test/hello - Message:
Hello from MQTT Explorer - Klik "PUBLISH"
- Pesan akan muncul di panel topic tree
Langkah 4: Verifikasi dengan Client Lain
Buka tab MQTT Explorer kedua atau gunakan device lain:
1. Connect ke broker yang sama
2. Subscribe ke topic test/hello
3. Publish message dari client pertama
4. Verifikasi message diterima di client kedua
Bagian 3: Publisher dan Subscriber
3.1 Konsep Publisher
Publisher adalah client MQTT yang mengirimkan (publish) data ke broker. Publisher tidak perlu tahu siapa yang akan menerima data tersebut, dia hanya mengirim ke topic tertentu.
Karakteristik Publisher:
- Independent: Tidak perlu tahu tentang subscriber
- One-to-Many: Satu publisher dapat mengirim ke banyak subscriber
- Asynchronous: Mengirim data tanpa menunggu response
- Topic-based: Mengirim data ke topic spesifik
Praktikum 3.1: Membuat Publisher Sederhana di ESP32
Langkah 1: Install Library MQTT
MicroPython memiliki library umqtt untuk komunikasi MQTT. Buat file umqtt_simple.py dan upload ke ESP32, atau gunakan library built-in jika sudah tersedia.
Test ketersediaan library:
Jika error, download library dari: https://github.com/micropython/micropython-lib/tree/master/micropython/umqtt.simple
Langkah 2: Konfigurasi WiFi
Buat file wifi_config.py:
# wifi_config.py - Konfigurasi WiFi
import network
import utime
# Konfigurasi WiFi
WIFI_SSID = "nama_wifi_anda"
WIFI_PASSWORD = "password_wifi_anda"
def connect_wifi():
"""Koneksi ke WiFi"""
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
print('Connecting to WiFi...')
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
# Tunggu koneksi
timeout = 10
while not wlan.isconnected() and timeout > 0:
print('.', end='')
utime.sleep(1)
timeout -= 1
print()
if wlan.isconnected():
print('WiFi Connected!')
print('IP Address:', wlan.ifconfig()[0])
return True
else:
print('WiFi Connection Failed!')
return False
def disconnect_wifi():
"""Disconnect dari WiFi"""
wlan = network.WLAN(network.STA_IF)
wlan.disconnect()
wlan.active(False)
print('WiFi Disconnected')
Langkah 3: Membuat Publisher Dasar
Buat file mqtt_publisher.py:
# mqtt_publisher.py - MQTT Publisher sederhana
from umqtt.simple import MQTTClient
import utime
import machine
# Konfigurasi MQTT Broker
MQTT_BROKER = "test.mosquitto.org"
MQTT_PORT = 1883
MQTT_CLIENT_ID = "esp32_publisher_001"
# Topic untuk publish
TOPIC_TEST = "iot/esp32/test"
def create_mqtt_client():
"""Membuat MQTT client"""
client = MQTTClient(
client_id=MQTT_CLIENT_ID,
server=MQTT_BROKER,
port=MQTT_PORT
)
return client
def publish_message(client, topic, message):
"""Publish message ke broker"""
try:
client.publish(topic, message)
print(f"Published: {message} to {topic}")
return True
except Exception as e:
print(f"Publish failed: {e}")
return False
def main():
"""Program utama publisher"""
print("=== MQTT Publisher Demo ===")
# Buat MQTT client
client = create_mqtt_client()
try:
# Connect ke broker
print(f"Connecting to {MQTT_BROKER}...")
client.connect()
print("Connected to broker!")
# Publish beberapa pesan
for i in range(5):
message = f"Hello from ESP32 #{i+1}"
publish_message(client, TOPIC_TEST, message)
utime.sleep(2)
print("Publishing complete!")
except Exception as e:
print(f"Error: {e}")
finally:
# Disconnect dari broker
try:
client.disconnect()
print("Disconnected from broker")
except:
pass
# Jalankan program
if __name__ == "__main__":
from wifi_config import connect_wifi
if connect_wifi():
main()
Langkah 4: Jalankan Publisher
- Upload file
wifi_config.pydanmqtt_publisher.pyke ESP32 - Edit
WIFI_SSIDdanWIFI_PASSWORDsesuai WiFi Anda -
Di REPL, jalankan:
-
Amati output di serial terminal
- Buka MQTT Explorer dan subscribe ke topic
iot/esp32/test - Verifikasi message diterima
3.2 Konsep Subscriber
Subscriber adalah client MQTT yang menerima (subscribe) data dari broker. Subscriber mendaftar ke topic tertentu dan akan menerima semua pesan yang dipublish ke topic tersebut.
Karakteristik Subscriber:
- Topic-based: Subscribe ke topic spesifik
- Callback-driven: Menerima data melalui callback function
- Persistent: Tetap listen meskipun tidak ada data
- Multiple topics: Dapat subscribe ke banyak topic sekaligus
Praktikum 3.2: Membuat Subscriber Sederhana
Langkah 1: Membuat Subscriber Dasar
Buat file mqtt_subscriber.py:
# mqtt_subscriber.py - MQTT Subscriber sederhana
from umqtt.simple import MQTTClient
import utime
# Konfigurasi MQTT
MQTT_BROKER = "test.mosquitto.org"
MQTT_PORT = 1883
MQTT_CLIENT_ID = "esp32_subscriber_001"
# Topic untuk subscribe
TOPIC_TEST = "iot/esp32/test"
def on_message(topic, msg):
"""Callback function saat menerima message"""
print(f"\nMessage received!")
print(f"Topic: {topic.decode()}")
print(f"Message: {msg.decode()}")
print("-" * 40)
def create_subscriber():
"""Membuat MQTT subscriber client"""
client = MQTTClient(
client_id=MQTT_CLIENT_ID,
server=MQTT_BROKER,
port=MQTT_PORT
)
# Set callback function
client.set_callback(on_message)
return client
def main():
"""Program utama subscriber"""
print("=== MQTT Subscriber Demo ===")
client = create_subscriber()
try:
# Connect ke broker
print(f"Connecting to {MQTT_BROKER}...")
client.connect()
print("Connected!")
# Subscribe ke topic
client.subscribe(TOPIC_TEST)
print(f"Subscribed to: {TOPIC_TEST}")
print("Waiting for messages... (Ctrl+C to stop)")
# Loop untuk menerima message
while True:
client.check_msg() # Check untuk message baru
utime.sleep(0.1)
except KeyboardInterrupt:
print("\nStopped by user")
except Exception as e:
print(f"Error: {e}")
finally:
try:
client.disconnect()
print("Disconnected from broker")
except:
pass
# Jalankan program
if __name__ == "__main__":
from wifi_config import connect_wifi
if connect_wifi():
main()
Langkah 2: Test Publisher dan Subscriber
Untuk test yang lengkap, Anda memerlukan dua ESP32 atau satu ESP32 + MQTT Explorer:
Setup 1: Dua ESP32
ESP32-1: Jalankan mqtt_subscriber.py
ESP32-2: Jalankan mqtt_publisher.py
Hasil: ESP32-1 akan menerima message dari ESP32-2
Setup 2: ESP32 + MQTT Explorer
ESP32: Jalankan mqtt_subscriber.py
MQTT Explorer: Publish message ke topic iot/esp32/test
Hasil: ESP32 akan menerima dan display message
3.3 Publisher dan Subscriber dalam Satu Program
Dalam aplikasi IoT yang kompleks, sebuah device sering bertindak sebagai publisher dan subscriber sekaligus.
Praktikum 3.3: Membuat PubSub Combined
Buat file mqtt_pubsub.py:
# mqtt_pubsub.py - Publisher dan Subscriber dalam satu program
from umqtt.simple import MQTTClient
import utime
import machine
# Konfigurasi MQTT
MQTT_BROKER = "test.mosquitto.org"
MQTT_PORT = 1883
MQTT_CLIENT_ID = "esp32_pubsub_001"
# Topics
TOPIC_SENSOR = "iot/esp32/sensor" # Untuk publish data sensor
TOPIC_COMMAND = "iot/esp32/command" # Untuk terima command
class MQTTPubSub:
"""Class untuk Publisher-Subscriber"""
def __init__(self, broker, port, client_id):
self.broker = broker
self.port = port
self.client_id = client_id
self.client = None
self.connected = False
def on_message(self, topic, msg):
"""Callback untuk message yang diterima"""
topic_str = topic.decode()
msg_str = msg.decode()
print(f"\n>>> Received on {topic_str}: {msg_str}")
# Handle command
if topic_str == TOPIC_COMMAND:
self.handle_command(msg_str)
def handle_command(self, command):
"""Handle command yang diterima"""
print(f"Processing command: {command}")
if command == "STATUS":
self.publish(TOPIC_SENSOR, "Device is running OK")
elif command == "PING":
self.publish(TOPIC_SENSOR, "PONG")
else:
print(f"Unknown command: {command}")
def connect(self):
"""Connect ke MQTT broker"""
try:
self.client = MQTTClient(
self.client_id,
self.broker,
self.port
)
self.client.set_callback(self.on_message)
self.client.connect()
self.connected = True
print(f"Connected to {self.broker}")
return True
except Exception as e:
print(f"Connection failed: {e}")
self.connected = False
return False
def subscribe(self, topic):
"""Subscribe ke topic"""
if self.connected:
self.client.subscribe(topic)
print(f"Subscribed to: {topic}")
def publish(self, topic, message):
"""Publish message"""
if self.connected:
self.client.publish(topic, message)
print(f"Published to {topic}: {message}")
def check_messages(self):
"""Check untuk message baru"""
if self.connected:
self.client.check_msg()
def disconnect(self):
"""Disconnect dari broker"""
if self.client:
self.client.disconnect()
print("Disconnected from broker")
def main():
"""Program utama"""
print("=== MQTT Pub-Sub Demo ===")
# Buat instance PubSub
mqtt = MQTTPubSub(MQTT_BROKER, MQTT_PORT, MQTT_CLIENT_ID)
# Connect dan subscribe
if mqtt.connect():
mqtt.subscribe(TOPIC_COMMAND)
try:
counter = 0
while True:
# Check untuk message masuk
mqtt.check_messages()
# Publish data sensor setiap 5 detik
if counter % 50 == 0: # 50 * 0.1s = 5s
sensor_data = f"Temperature: {25 + (counter % 10)}"
mqtt.publish(TOPIC_SENSOR, sensor_data)
counter += 1
utime.sleep(0.1)
except KeyboardInterrupt:
print("\nStopped by user")
finally:
mqtt.disconnect()
if __name__ == "__main__":
from wifi_config import connect_wifi
if connect_wifi():
main()
Test Combined PubSub:
- Upload dan jalankan
mqtt_pubsub.pydi ESP32 - Buka MQTT Explorer
- Subscribe ke topic
iot/esp32/sensor - Publish command ke topic
iot/esp32/command: - Message:
STATUS - Message:
PING - Amati response dari ESP32
Bagian 4: Sistem Topik (Topic)
4.1 Konsep Topic dalam MQTT
Topic adalah string UTF-8 yang digunakan broker untuk memfilter pesan ke subscriber yang tepat. Topic adalah konsep fundamental dalam MQTT yang memungkinkan komunikasi many-to-many yang terorganisir.
Karakteristik Topic:
- Hierarchical: Menggunakan struktur seperti path dengan separator
/ - Case-sensitive:
Sensor/Tempberbeda dengansensor/temp - Flexible: Dapat memiliki banyak level
- Wildcard support: Mendukung
+dan#untuk pattern matching
Praktikum 4.1: Memahami Struktur Topic
Contoh Struktur Topic yang Baik:
Struktur Hierarki Topic untuk Smart Home:
rumah/
├── ruangtamu/
│ ├── lampu/status
│ ├── lampu/brightness
│ ├── ac/temperature
│ └── sensor/motion
├── kamar/
│ ├── lampu/status
│ ├── sensor/temperature
│ └── sensor/humidity
└── dapur/
├── lampu/status
└── sensor/gas
Penjelasan:
- Level 1: Lokasi utama (rumah)
- Level 2: Ruangan spesifik
- Level 3: Device atau sensor
- Level 4: Attribute atau data type
Best Practices untuk Naming Topic:
# ✓ GOOD - Jelas dan terstruktur
"indonesia/yogyakarta/kampus/lab1/sensor/temperature"
"building/floor2/room201/light/status"
"factory/line1/machine3/sensor/vibration"
# ✗ BAD - Tidak terstruktur
"tempData"
"sensor1"
"myTopic123"
# ✓ GOOD - Menggunakan lowercase
"sensor/temperature"
# ✗ BAD - Mixed case inconsistent
"Sensor/Temperature"
"SENSOR/temp"
4.2 Topic Wildcards
MQTT menyediakan dua jenis wildcard untuk subscribe ke multiple topic dengan satu subscription.
Praktikum 4.2: Menggunakan Wildcards
Single Level Wildcard (+):
Wildcard + menggantikan tepat satu level dalam topic hierarchy.
Contoh Topic yang Ada:
- rumah/ruangtamu/lampu
- rumah/kamar/lampu
- rumah/dapur/lampu
- rumah/ruangtamu/ac
Subscribe Pattern: rumah/+/lampu
Akan Menerima:
✓ rumah/ruangtamu/lampu
✓ rumah/kamar/lampu
✓ rumah/dapur/lampu
✗ rumah/ruangtamu/ac (bukan 'lampu')
✗ rumah/ruangtamu/kamar/lampu (lebih dari satu level)
Multi Level Wildcard (#):
Wildcard # menggantikan semua level setelahnya. Harus di posisi terakhir.
Subscribe Pattern: rumah/#
Akan Menerima:
✓ rumah/ruangtamu/lampu
✓ rumah/kamar/ac
✓ rumah/dapur/sensor/gas
✓ rumah/ruangtamu/sensor/temp/current
✓ Semua topic yang dimulai dengan "rumah/"
Subscribe Pattern: rumah/ruangtamu/#
Akan Menerima:
✓ rumah/ruangtamu/lampu
✓ rumah/ruangtamu/ac
✓ rumah/ruangtamu/sensor/temp
✗ rumah/kamar/lampu (bukan ruangtamu)
Implementasi Wildcard di ESP32:
# mqtt_wildcard.py - Contoh penggunaan wildcard
from umqtt.simple import MQTTClient
import utime
MQTT_BROKER = "test.mosquitto.org"
MQTT_CLIENT_ID = "esp32_wildcard_test"
def on_message(topic, msg):
"""Callback untuk message"""
print(f"Topic: {topic.decode()}")
print(f"Message: {msg.decode()}")
print("-" * 40)
def test_wildcards():
"""Test berbagai wildcard patterns"""
client = MQTTClient(MQTT_CLIENT_ID, MQTT_BROKER)
client.set_callback(on_message)
client.connect()
# Subscribe dengan berbagai pattern
patterns = [
b"iot/sensor/+/temperature", # Semua sensor temperature
b"iot/device/#", # Semua subtopic device
b"iot/+/status" # Semua status dari device
]
print("Subscribing to patterns:")
for pattern in patterns:
client.subscribe(pattern)
print(f" - {pattern.decode()}")
print("\nListening for messages...")
try:
while True:
client.check_msg()
utime.sleep(0.1)
except KeyboardInterrupt:
print("\nStopped")
client.disconnect()
if __name__ == "__main__":
from wifi_config import connect_wifi
if connect_wifi():
test_wildcards()
4.3 Topic Reserved dan Special Characters
Beberapa topic memiliki makna khusus dalam MQTT dan tidak boleh digunakan untuk aplikasi biasa.
Praktikum 4.3: Memahami Reserved Topics
Topics yang Diawali dengan $ (System Topics):
$SYS/broker/clients/connected
$SYS/broker/messages/received
$SYS/broker/uptime
$SYS/broker/version
Karakteristik:
- Diawali dengan $
- Digunakan untuk informasi sistem broker
- Tidak diterima saat subscribe dengan #
- Reserved untuk broker implementation
Aturan Karakter dalam Topic:
# ✓ ALLOWED Characters
"sensor/data" # Alphanumeric
"sensor_data" # Underscore
"sensor-data" # Hyphen
"sensor.data" # Dot
"sensor123" # Numbers
"température" # UTF-8 characters
# ✗ NOT ALLOWED (akan error atau unexpected behavior)
"sensor data" # Spasi
"sensor+data" # Plus (reserved for wildcard)
"sensor#data" # Hash (reserved for wildcard)
"" # Empty topic
# ⚠ AVOID (secara teknis diperbolehkan tapi tidak recommended)
"sensor/data/" # Trailing slash
"/sensor/data" # Leading slash
4.4 Designing Topic Structure
Merancang struktur topic yang baik adalah kunci untuk sistem MQTT yang scalable dan maintainable.
Praktikum 4.4: Merancang Topic Structure
Langkah 1: Identifikasi Hierarchy
Contoh Kasus: Monitoring Greenhouse
Level 1: Organization/Location
├─ greenhouse
Level 2: Zone/Area
├─ zone1
├─ zone2
└─ zone3
Level 3: Device Type
├─ sensor
├─ actuator
└─ controller
Level 4: Measurement/Command
├─ temperature
├─ humidity
├─ status
└─ control
Langkah 2: Implementasi Topic Structure
Buat file topic_structure.py:
# topic_structure.py - Implementasi topic structure
class TopicBuilder:
"""Helper class untuk membuat topic structure"""
def __init__(self, base="iot"):
self.base = base
def sensor(self, location, sensor_type, measurement):
"""Generate topic untuk sensor data"""
return f"{self.base}/{location}/sensor/{sensor_type}/{measurement}"
def actuator(self, location, actuator_type, command):
"""Generate topic untuk actuator control"""
return f"{self.base}/{location}/actuator/{actuator_type}/{command}"
def status(self, location, device_id):
"""Generate topic untuk device status"""
return f"{self.base}/{location}/device/{device_id}/status"
def alert(self, location, alert_type):
"""Generate topic untuk alerts"""
return f"{self.base}/{location}/alert/{alert_type}"
# Contoh penggunaan
def demo_topic_structure():
"""Demo penggunaan topic builder"""
builder = TopicBuilder("greenhouse")
# Generate berbagai topics
topics = [
builder.sensor("zone1", "dht22", "temperature"),
builder.sensor("zone1", "dht22", "humidity"),
builder.sensor("zone2", "soil", "moisture"),
builder.actuator("zone1", "fan", "control"),
builder.actuator("zone2", "pump", "control"),
builder.status("zone1", "esp32_001"),
builder.alert("zone1", "temperature_high")
]
print("Generated Topics:")
for topic in topics:
print(f" - {topic}")
return topics
# Demo
if __name__ == "__main__":
demo_topic_structure()
Langkah 3: Implementasi di ESP32
# mqtt_structured_topics.py
from umqtt.simple import MQTTClient
import utime
from topic_structure import TopicBuilder
MQTT_BROKER = "test.mosquitto.org"
MQTT_CLIENT_ID = "esp32_structured"
def main():
"""Demo structured topics"""
builder = TopicBuilder("myfarm")
client = MQTTClient(MQTT_CLIENT_ID, MQTT_BROKER)
client.connect()
print("Publishing structured data...")
# Publish sensor data dengan struktur yang jelas
topics_data = [
(builder.sensor("greenhouse1", "temp", "celsius"), "25.5"),
(builder.sensor("greenhouse1", "humidity", "percent"), "65"),
(builder.sensor("greenhouse2", "temp", "celsius"), "26.2"),
(builder.status("greenhouse1", "esp32_001"), "online"),
]
for topic, data in topics_data:
client.publish(topic, data)
print(f"Published: {topic} = {data}")
utime.sleep(1)
client.disconnect()
print("Done!")
if __name__ == "__main__":
from wifi_config import connect_wifi
if connect_wifi():
main()
Bagian 5: Quality of Service (QoS)
5.1 Konsep QoS dalam MQTT
Quality of Service (QoS) adalah agreement antara sender dan receiver tentang garansi pengiriman pesan. MQTT mendefinisikan tiga level QoS yang dapat dipilih sesuai kebutuhan aplikasi.
Praktikum 5.1: Memahami QoS Levels
QoS 0 - At Most Once (Fire and Forget):
Karakteristik:
- Pesan dikirim tanpa acknowledgment
- Tidak ada retry jika gagal
- Paling cepat dan efisien
- Bisa kehilangan pesan jika jaringan bermasalah
Flow:
Publisher → Broker → Subscriber
↓ ↓ ↓
Send Forward Receive
(no confirmation)
Use Case:
- Data sensor yang update frequent
- Data yang tidak critical (temperature updates every second)
- Bandwidth sangat terbatas
QoS 1 - At Least Once (Acknowledged Delivery):
Karakteristik:
- Pesan dijamin sampai minimal satu kali
- Ada acknowledgment (PUBACK)
- Bisa ada duplikasi pesan
- Balance antara reliability dan overhead
Flow:
Publisher → Broker → Subscriber
↓ ↓ ↓
PUBLISH PUBLISH PUBLISH
↑ ↑ ↑
PUBACK PUBACK PUBACK
Use Case:
- Data penting tapi toleran terhadap duplikasi
- Command yang perlu konfirmasi
- Data yang tidak boleh hilang
QoS 2 - Exactly Once (Assured Delivery):
Karakteristik:
- Pesan dijamin sampai tepat satu kali
- Tidak ada duplikasi
- Paling lambat (4-way handshake)
- Overhead paling besar
Flow:
Publisher → Broker → Subscriber
↓ ↓ ↓
PUBLISH PUBLISH PUBLISH
↑ ↑ ↑
PUBREC PUBREC PUBREC
↓ ↓ ↓
PUBREL PUBREL PUBREL
↑ ↑ ↑
PUBCOMP PUBCOMP PUBCOMP
Use Case:
- Financial transactions
- Critical commands (unlock door)
- Data yang tidak boleh duplikasi
Perbandingan QoS:
| Aspek | QoS 0 | QoS 1 | QoS 2 |
|---|---|---|---|
| Garansi | None | At least once | Exactly once |
| Speed | Fastest | Medium | Slowest |
| Bandwidth | Minimal | Medium | High |
| Duplikasi | Possible loss | Possible | No |
| Use Case | Non-critical | Important | Critical |
5.2 Implementasi QoS di MicroPython
Library umqtt.simple di MicroPython mendukung QoS 0 dan QoS 1. Untuk QoS 2, perlu library umqtt.robust.
Praktikum 5.2: Testing QoS Levels
Langkah 1: QoS 0 Implementation
# qos_0_test.py - Testing QoS 0
from umqtt.simple import MQTTClient
import utime
MQTT_BROKER = "test.mosquitto.org"
MQTT_CLIENT_ID = "esp32_qos0_test"
TOPIC = "iot/qos/test"
def test_qos_0():
"""Test QoS 0 - Fire and Forget"""
print("=== Testing QoS 0 ===")
client = MQTTClient(MQTT_CLIENT_ID, MQTT_BROKER)
client.connect()
# Publish dengan QoS 0 (default)
for i in range(10):
message = f"QoS 0 Message #{i+1}"
client.publish(TOPIC, message, qos=0)
print(f"Sent: {message}")
utime.sleep(0.5)
client.disconnect()
print("QoS 0 test complete!")
print("\nNote: Beberapa pesan mungkin hilang jika koneksi tidak stabil")
if __name__ == "__main__":
from wifi_config import connect_wifi
if connect_wifi():
test_qos_0()
Langkah 2: QoS 1 Implementation
# qos_1_test.py - Testing QoS 1
from umqtt.simple import MQTTClient
import utime
MQTT_BROKER = "test.mosquitto.org"
MQTT_CLIENT_ID = "esp32_qos1_test"
TOPIC = "iot/qos/test"
def test_qos_1():
"""Test QoS 1 - At Least Once"""
print("=== Testing QoS 1 ===")
client = MQTTClient(MQTT_CLIENT_ID, MQTT_BROKER)
client.connect()
# Publish dengan QoS 1
for i in range(10):
message = f"QoS 1 Message #{i+1}"
client.publish(TOPIC, message, qos=1)
print(f"Sent (QoS 1): {message}")
utime.sleep(1) # Delay lebih lama untuk acknowledgment
client.disconnect()
print("QoS 1 test complete!")
print("\nNote: Semua pesan dijamin terkirim (mungkin ada duplikasi)")
if __name__ == "__main__":
from wifi_config import connect_wifi
if connect_wifi():
test_qos_1()
Langkah 3: Comparing QoS Performance
# qos_comparison.py - Membandingkan performa QoS
from umqtt.simple import MQTTClient
import utime
MQTT_BROKER = "test.mosquitto.org"
MQTT_CLIENT_ID = "esp32_qos_compare"
TOPIC = "iot/qos/performance"
def measure_publish_time(client, message, qos):
"""Mengukur waktu publish dengan QoS tertentu"""
start = utime.ticks_ms()
client.publish(TOPIC, message, qos=qos)
end = utime.ticks_ms()
return utime.ticks_diff(end, start)
def compare_qos():
"""Bandingkan performa QoS 0 dan QoS 1"""
print("=== QoS Performance Comparison ===")
client = MQTTClient(MQTT_CLIENT_ID, MQTT_BROKER)
client.connect()
# Test QoS 0
print("\nTesting QoS 0 (10 messages)...")
qos0_times = []
for i in range(10):
time_taken = measure_publish_time(
client,
f"QoS 0 Test {i+1}",
qos=0
)
qos0_times.append(time_taken)
utime.sleep(0.1)
# Test QoS 1
print("Testing QoS 1 (10 messages)...")
qos1_times = []
for i in range(10):
time_taken = measure_publish_time(
client,
f"QoS 1 Test {i+1}",
qos=1
)
qos1_times.append(time_taken)
utime.sleep(0.1)
# Hasil
avg_qos0 = sum(qos0_times) / len(qos0_times)
avg_qos1 = sum(qos1_times) / len(qos1_times)
print("\n=== Results ===")
print(f"QoS 0 Average: {avg_qos0:.2f} ms")
print(f"QoS 1 Average: {avg_qos1:.2f} ms")
print(f"Difference: {avg_qos1 - avg_qos0:.2f} ms")
print(f"QoS 1 is {(avg_qos1/avg_qos0):.2f}x slower")
client.disconnect()
if __name__ == "__main__":
from wifi_config import connect_wifi
if connect_wifi():
compare_qos()
5.3 Memilih QoS yang Tepat
Pemilihan QoS yang tepat sangat penting untuk balance antara reliability dan performance.
Praktikum 5.3: QoS Selection Guidelines
Decision Tree untuk Memilih QoS:
# qos_decision_helper.py
class QoSSelector:
"""Helper untuk memilih QoS yang tepat"""
@staticmethod
def select_qos(
is_critical=False,
allow_loss=True,
allow_duplicate=True,
bandwidth_limited=False
):
"""
Memilih QoS berdasarkan requirements
Parameters:
- is_critical: Apakah data sangat penting?
- allow_loss: Boleh kehilangan data?
- allow_duplicate: Boleh duplikasi?
- bandwidth_limited: Bandwidth terbatas?
"""
# QoS 2 - Untuk data critical yang tidak boleh duplikasi
if is_critical and not allow_duplicate:
return 2, "QoS 2 - Exactly Once (Critical, No Duplicate)"
# QoS 1 - Untuk data penting yang toleran duplikasi
elif is_critical or not allow_loss:
return 1, "QoS 1 - At Least Once (Important Data)"
# QoS 0 - Untuk data non-critical atau bandwidth terbatas
else:
return 0, "QoS 0 - At Most Once (Fast, Non-Critical)"
@staticmethod
def print_recommendation(data_type, qos, reason):
"""Print rekomendasi QoS"""
print(f"\nData Type: {data_type}")
print(f"Recommended QoS: {qos}")
print(f"Reason: {reason}")
print("-" * 50)
# Contoh penggunaan
def demo_qos_selection():
"""Demo pemilihan QoS"""
selector = QoSSelector()
print("=== QoS Selection Examples ===")
# Sensor temperature yang update tiap detik
qos, reason = selector.select_qos(
is_critical=False,
allow_loss=True,
allow_duplicate=True,
bandwidth_limited=True
)
selector.print_recommendation("Temperature Sensor (1s update)", qos, reason)
# Command untuk unlock door
qos, reason = selector.select_qos(
is_critical=True,
allow_loss=False,
allow_duplicate=False,
bandwidth_limited=False
)
selector.print_recommendation("Door Lock Command", qos, reason)
# Alert system
qos, reason = selector.select_qos(
is_critical=True,
allow_loss=False,
allow_duplicate=True,
bandwidth_limited=False
)
selector.print_recommendation("Security Alert", qos, reason)
# Status heartbeat
qos, reason = selector.select_qos(
is_critical=False,
allow_loss=True,
allow_duplicate=True,
bandwidth_limited=True
)
selector.print_recommendation("Device Heartbeat", qos, reason)
if __name__ == "__main__":
demo_qos_selection()
Bagian 6: Payload dan Data Format
6.1 Konsep Payload
Payload adalah data aktual yang dikirimkan dalam MQTT message. MQTT tidak membatasi format payload - bisa berupa text, JSON, binary, atau format lainnya.
Karakteristik Payload:
- Format-agnostic: Tidak ada format yang dipaksakan
- Size limit: Biasanya dibatasi oleh broker (default 256MB)
- Encoding: UTF-8 untuk text, binary untuk data lain
Praktikum 6.1: Memahami Payload Types
Jenis-jenis Payload:
# payload_types.py - Berbagai jenis payload
# 1. Plain Text Payload
text_payload = "Temperature: 25.5C"
# 2. Numeric Payload (as string)
numeric_payload = "25.5"
# 3. Boolean Payload
boolean_payload = "true" # atau "1", "ON", "yes"
# 4. JSON Payload
import ujson
json_payload = ujson.dumps({
"sensor": "DHT22",
"temperature": 25.5,
"humidity": 65,
"timestamp": 1234567890
})
# 5. CSV Payload
csv_payload = "25.5,65,1234567890"
# 6. Binary Payload
binary_payload = bytes([0x01, 0x02, 0x03, 0x04])
def demo_payloads():
"""Demo berbagai jenis payload"""
print("=== Payload Types Demo ===\n")
print(f"1. Text: {text_payload}")
print(f" Size: {len(text_payload)} bytes\n")
print(f"2. Numeric: {numeric_payload}")
print(f" Size: {len(numeric_payload)} bytes\n")
print(f"3. Boolean: {boolean_payload}")
print(f" Size: {len(boolean_payload)} bytes\n")
print(f"4. JSON: {json_payload}")
print(f" Size: {len(json_payload)} bytes\n")
print(f"5. CSV: {csv_payload}")
print(f" Size: {len(csv_payload)} bytes\n")
print(f"6. Binary: {binary_payload.hex()}")
print(f" Size: {len(binary_payload)} bytes")
if __name__ == "__main__":
demo_payloads()
6.2 JSON Payload (Recommended)
JSON adalah format payload yang paling populer untuk MQTT karena readable, structured, dan widely supported.
Praktikum 6.2: Working with JSON Payload
Langkah 1: JSON Encoding
# json_payload.py - Bekerja dengan JSON payload
import ujson
import utime
class SensorData:
"""Class untuk sensor data dengan JSON encoding"""
def __init__(self, sensor_id, sensor_type):
self.sensor_id = sensor_id
self.sensor_type = sensor_type
def create_payload(self, **measurements):
"""
Membuat JSON payload dari measurements
Example:
create_payload(temperature=25.5, humidity=65)
"""
payload = {
"sensor_id": self.sensor_id,
"sensor_type": self.sensor_type,
"timestamp": utime.time(),
"data": measurements
}
return ujson.dumps(payload)
def parse_payload(self, json_string):
"""Parse JSON payload"""
try:
data = ujson.loads(json_string)
return data
except:
print("Error parsing JSON")
return None
# Demo
def demo_json_payload():
"""Demo JSON payload"""
print("=== JSON Payload Demo ===\n")
# Buat sensor data object
sensor = SensorData("ESP32_001", "DHT22")
# Create payload
payload1 = sensor.create_payload(
temperature=25.5,
humidity=65
)
print("Payload 1 (Temperature & Humidity):")
print(payload1)
print()
# Create payload dengan lebih banyak data
payload2 = sensor.create_payload(
temperature=26.2,
humidity=68,
pressure=1013.25,
light=450
)
print("Payload 2 (Multiple Sensors):")
print(payload2)
print()
# Parse payload
parsed = sensor.parse_payload(payload2)
if parsed:
print("Parsed Data:")
print(f" Sensor ID: {parsed['sensor_id']}")
print(f" Temperature: {parsed['data']['temperature']}°C")
print(f" Humidity: {parsed['data']['humidity']}%")
if __name__ == "__main__":
demo_json_payload()
Langkah 2: MQTT dengan JSON Payload
# mqtt_json_publisher.py
from umqtt.simple import MQTTClient
import ujson
import utime
import random
MQTT_BROKER = "test.mosquitto.org"
MQTT_CLIENT_ID = "esp32_json_pub"
TOPIC = "iot/sensor/data"
class IoTDevice:
"""IoT Device yang publish JSON data"""
def __init__(self, device_id, location):
self.device_id = device_id
self.location = location
def read_sensors(self):
"""Simulasi pembacaan sensor"""
# Simulasi dengan random values
return {
"temperature": round(20 + random.random() * 10, 2),
"humidity": round(40 + random.random() * 40, 2),
"pressure": round(1000 + random.random() * 50, 2)
}
def create_message(self):
"""Buat message JSON lengkap"""
sensor_data = self.read_sensors()
message = {
"device_id": self.device_id,
"location": self.location,
"timestamp": utime.time(),
"sensors": sensor_data,
"status": "online"
}
return ujson.dumps(message)
def main():
"""Main program"""
print("=== MQTT JSON Publisher ===")
# Inisialisasi device
device = IoTDevice("ESP32_LAB01", "Yogyakarta")
# Connect ke broker
client = MQTTClient(MQTT_CLIENT_ID, MQTT_BROKER)
client.connect()
print(f"Connected to {MQTT_BROKER}")
try:
# Publish data setiap 3 detik
for i in range(10):
message = device.create_message()
client.publish(TOPIC, message)
print(f"\nPublished #{i+1}:")
print(message)
utime.sleep(3)
except KeyboardInterrupt:
print("\nStopped by user")
finally:
client.disconnect()
print("Disconnected")
if __name__ == "__main__":
from wifi_config import connect_wifi
if connect_wifi():
main()
Langkah 3: MQTT JSON Subscriber dengan Parsing
# mqtt_json_subscriber.py
from umqtt.simple import MQTTClient
import ujson
import utime
MQTT_BROKER = "test.mosquitto.org"
MQTT_CLIENT_ID = "esp32_json_sub"
TOPIC = "iot/sensor/data"
def on_message(topic, msg):
"""Callback dengan JSON parsing"""
print("\n" + "="*50)
print("Message Received!")
print("="*50)
try:
# Parse JSON
data = ujson.loads(msg.decode())
# Display parsed data dengan format rapi
print(f"Device ID: {data['device_id']}")
print(f"Location: {data['location']}")
print(f"Status: {data['status']}")
print(f"\nSensor Readings:")
sensors = data['sensors']
print(f" Temperature: {sensors['temperature']}°C")
print(f" Humidity: {sensors['humidity']}%")
print(f" Pressure: {sensors['pressure']} hPa")
# Contoh: Alert jika temperature tinggi
if sensors['temperature'] > 28:
print("\n⚠️ ALERT: Temperature is high!")
except Exception as e:
print(f"Error parsing message: {e}")
print(f"Raw message: {msg.decode()}")
print("="*50)
def main():
"""Main subscriber"""
print("=== MQTT JSON Subscriber ===")
client = MQTTClient(MQTT_CLIENT_ID, MQTT_BROKER)
client.set_callback(on_message)
client.connect()
client.subscribe(TOPIC)
print(f"Subscribed to: {TOPIC}")
print("Waiting for messages...\n")
try:
while True:
client.check_msg()
utime.sleep(0.1)
except KeyboardInterrupt:
print("\nStopped")
client.disconnect()
if __name__ == "__main__":
from wifi_config import connect_wifi
if connect_wifi():
main()
6.3 Optimasi Payload Size
Untuk IoT devices dengan bandwidth terbatas, optimasi ukuran payload sangat penting.
Praktikum 6.3: Payload Optimization
Perbandingan Format Payload:
# payload_optimization.py
import ujson
def compare_payload_formats():
"""Membandingkan ukuran berbagai format payload"""
# Data yang sama dalam berbagai format
temp = 25.5
humidity = 65
pressure = 1013.25
# Format 1: JSON dengan key lengkap
json_verbose = ujson.dumps({
"temperature": temp,
"humidity": humidity,
"pressure": pressure,
"unit_temperature": "celsius",
"unit_humidity": "percent",
"unit_pressure": "hPa"
})
# Format 2: JSON dengan key pendek
json_compact = ujson.dumps({
"t": temp,
"h": humidity,
"p": pressure
})
# Format 3: CSV
csv_format = f"{temp},{humidity},{pressure}"
# Format 4: Plain text
text_format = f"{temp}|{humidity}|{pressure}"
# Format 5: Binary (4 bytes float each = 12 bytes total)
# Untuk demo, kita hitung size-nya saja
binary_size = 12 # 3 floats × 4 bytes
# Hasil
print("=== Payload Size Comparison ===\n")
print(f"1. JSON Verbose: {len(json_verbose)} bytes")
print(f" Content: {json_verbose}\n")
print(f"2. JSON Compact: {len(json_compact)} bytes")
print(f" Content: {json_compact}\n")
print(f"3. CSV Format: {len(csv_format)} bytes")
print(f" Content: {csv_format}\n")
print(f"4. Text Format: {len(text_format)} bytes")
print(f" Content: {text_format}\n")
print(f"5. Binary Format: ~{binary_size} bytes")
print(f" (Most compact but not human-readable)\n")
print("Recommendation:")
print("- JSON Compact: Best balance (readable + small)")
print("- CSV: Good for simple data")
print("- Binary: Best for bandwidth-critical applications")
if __name__ == "__main__":
compare_payload_formats()
Best Practices untuk Payload:
# payload_best_practices.py
class PayloadOptimizer:
"""Helper untuk optimasi payload"""
@staticmethod
def create_efficient_payload(sensor_data):
"""
Membuat payload yang efisien
Guidelines:
- Gunakan key pendek (1-2 karakter)
- Hindari nested object jika tidak perlu
- Bulatkan angka desimal (2 digit cukup)
- Hindari redundant information
"""
import ujson
# Bad practice: verbose keys
bad = {
"temperature_celsius": sensor_data["temp"],
"humidity_percentage": sensor_data["humid"],
"atmospheric_pressure_hectopascal": sensor_data["press"]
}
# Good practice: compact keys
good = {
"t": round(sensor_data["temp"], 2),
"h": round(sensor_data["humid"], 2),
"p": round(sensor_data["press"], 2)
}
print(f"Bad Practice Size: {len(ujson.dumps(bad))} bytes")
print(f"Good Practice Size: {len(ujson.dumps(good))} bytes")
print(f"Saved: {len(ujson.dumps(bad)) - len(ujson.dumps(good))} bytes")
return ujson.dumps(good)
# Demo
if __name__ == "__main__":
optimizer = PayloadOptimizer()
test_data = {
"temp": 25.547,
"humid": 65.234,
"press": 1013.253
}
efficient_payload = optimizer.create_efficient_payload(test_data)
print(f"\nEfficient Payload: {efficient_payload}")
Bagian 7: Retained Messages dan Last Will
7.1 Retained Messages
Retained message adalah fitur MQTT yang menyimpan message terakhir di broker untuk topic tertentu. Subscriber baru akan langsung menerima retained message saat subscribe.
Praktikum 7.1: Understanding Retained Messages
Konsep Retained Message:
Scenario tanpa Retained Message:
1. Publisher send: "Device Status: Online" → Broker
2. Broker forward ke active subscribers
3. Subscriber baru connect → Tidak tahu status sebelumnya
4. Harus tunggu update berikutnya
Scenario dengan Retained Message:
1. Publisher send (retained=True): "Device Status: Online" → Broker
2. Broker simpan message dan forward
3. Subscriber baru connect → Langsung terima "Device Status: Online"
4. Selalu tahu status terakhir
Use Cases:
✓ Device status (online/offline)
✓ Configuration settings
✓ Last known sensor value
✓ System state
Implementasi Retained Message:
# mqtt_retained.py - Demo Retained Messages
from umqtt.simple import MQTTClient
import utime
MQTT_BROKER = "test.mosquitto.org"
MQTT_CLIENT_ID = "esp32_retained_demo"
TOPIC_STATUS = "iot/device/status"
TOPIC_CONFIG = "iot/device/config"
def publish_retained_messages():
"""Publish messages dengan retained flag"""
print("=== Publishing Retained Messages ===")
client = MQTTClient(MQTT_CLIENT_ID, MQTT_BROKER)
client.connect()
# Publish device status (retained)
status_msg = "online"
client.publish(TOPIC_STATUS, status_msg, retain=True)
print(f"Published (retained): {TOPIC_STATUS} = {status_msg}")
# Publish configuration (retained)
config_msg = "mode:auto,interval:60"
client.publish(TOPIC_CONFIG, config_msg, retain=True)
print(f"Published (retained): {TOPIC_CONFIG} = {config_msg}")
# Publish sensor data (tidak retained - karena data selalu berubah)
sensor_msg = "temp:25.5"
client.publish("iot/device/sensor", sensor_msg, retain=False)
print(f"Published (normal): iot/device/sensor = {sensor_msg}")
client.disconnect()
print("\nRetained messages tersimpan di broker!")
print("Subscriber baru akan langsung menerima status & config")
def subscribe_retained():
"""Subscribe dan terima retained messages"""
print("\n=== Subscribing to Receive Retained Messages ===")
client = MQTTClient("esp32_sub_retained", MQTT_BROKER)
def on_message(topic, msg):
print(f"Received: {topic.decode()} = {msg.decode()}")
client.set_callback(on_message)
client.connect()
# Subscribe ke topics dengan retained messages
client.subscribe(TOPIC_STATUS)
client.subscribe(TOPIC_CONFIG)
print("Subscribed! Checking for retained messages...")
# Check messages (retained akan langsung diterima)
for i in range(10):
client.check_msg()
utime.sleep(0.5)
client.disconnect()
def clear_retained_message():
"""Cara menghapus retained message"""
print("\n=== Clearing Retained Message ===")
client = MQTTClient("esp32_clear", MQTT_BROKER)
client.connect()
# Publish empty message dengan retained=True untuk hapus
client.publish(TOPIC_STATUS, "", retain=True)
print(f"Cleared retained message from {TOPIC_STATUS}")
client.disconnect()
# Demo
if __name__ == "__main__":
from wifi_config import connect_wifi
if connect_wifi():
# Test 1: Publish retained messages
publish_retained_messages()
utime.sleep(2)
# Test 2: Subscribe dan terima retained
subscribe_retained()
# Test 3: Clear retained (opsional)
# clear_retained_message()
7.2 Last Will and Testament (LWT)
Last Will adalah fitur MQTT yang memungkinkan client menentukan message yang akan dikirim broker jika client terputus secara tidak normal.
Praktikum 7.2: Implementing Last Will
Konsep Last Will:
Normal Disconnect:
Device → Broker: "DISCONNECT" message
Broker: Tidak kirim LWT message
Abnormal Disconnect (network error, crash, power loss):
Device: (koneksi terputus tiba-tiba)
Broker: Deteksi client tidak respond
Broker: Publish LWT message yang sudah ditentukan
Subscribers: Terima notifikasi bahwa device offline
Use Cases:
✓ Device availability monitoring
✓ Automatic offline notification
✓ System health monitoring
✓ Trigger automatic recovery
Implementasi Last Will:
# mqtt_last_will.py - Demo Last Will Testament
from umqtt.simple import MQTTClient
import utime
import machine
MQTT_BROKER = "test.mosquitto.org"
TOPIC_STATUS = "iot/device/status"
TOPIC_WILL = "iot/device/lastwill"
class DeviceWithLWT:
"""Device dengan Last Will Testament"""
def __init__(self, client_id):
self.client_id = client_id
self.client = None
def connect_with_lwt(self):
"""Connect dengan Last Will configuration"""
print(f"Connecting with Last Will...")
# Buat client dengan LWT
self.client = MQTTClient(
client_id=self.client_id,
server=MQTT_BROKER,
keepalive=60
)
# Set Last Will Message
# Syntax: set_last_will(topic, message, retain, qos)
lwt_message = f"{self.client_id} disconnected unexpectedly!"
self.client.set_last_will(
topic=TOPIC_WILL,
msg=lwt_message,
retain=True,
qos=1
)
# Connect ke broker
self.client.connect()
print("Connected with LWT configured")
# Publish status online
self.publish_status("online")
def publish_status(self, status):
"""Publish device status"""
message = f"{self.client_id}: {status}"
self.client.publish(TOPIC_STATUS, message, retain=True)
print(f"Status: {status}")
def normal_disconnect(self):
"""Disconnect normal - LWT tidak akan dikirim"""
print("\nPerforming normal disconnect...")
self.publish_status("offline")
self.client.disconnect()
print("Disconnected normally (LWT not sent)")
def simulate_crash(self):
"""Simulasi crash - LWT akan dikirim"""
print("\nSimulating device crash...")
print("(Force closing connection)")
# Force close tanpa proper disconnect
# Di real scenario: power loss, network error, etc.
# Untuk simulasi, kita bisa reset ESP32
utime.sleep(1)
machine.reset() # Hard reset - LWT akan trigger
def monitor_lwt():
"""Monitor Last Will messages"""
print("=== Monitoring Last Will Messages ===")
client = MQTTClient("esp32_lwt_monitor", MQTT_BROKER)
def on_message(topic, msg):
topic_str = topic.decode()
msg_str = msg.decode()
print(f"\n{'='*50}")
if topic_str == TOPIC_WILL:
print("⚠️ LAST WILL MESSAGE RECEIVED!")
print(f"Message: {msg_str}")
else:
print(f"Status Update: {msg_str}")
print('='*50)
client.set_callback(on_message)
client.connect()
# Subscribe ke status dan last will
client.subscribe(TOPIC_STATUS)
client.subscribe(TOPIC_WILL)
print(f"Monitoring topics:")
print(f" - {TOPIC_STATUS}")
print(f" - {TOPIC_WILL}")
print("\nWaiting for messages...")
try:
while True:
client.check_msg()
utime.sleep(0.1)
except KeyboardInterrupt:
print("\nMonitoring stopped")
client.disconnect()
def demo_lwt():
"""Demo Last Will"""
print("=== Last Will Testament Demo ===")
print("\nPilih mode:")
print("1. Normal disconnect (LWT tidak dikirim)")
print("2. Simulate crash (LWT dikirim)")
print("3. Monitor LWT messages")
# Untuk demo, kita jalankan normal disconnect
device = DeviceWithLWT("esp32_device_001")
device.connect_with_lwt()
# Simulasi device bekerja
print("\nDevice running...")
for i in range(5):
print(f"Heartbeat {i+1}")
utime.sleep(2)
# Normal disconnect
device.normal_disconnect()
print("\nUntuk test LWT yang sebenarnya:")
print("1. Jalankan monitor_lwt() di ESP32 lain/MQTT Explorer")
print("2. Jalankan device.simulate_crash() atau cabut power ESP32")
print("3. Monitor akan terima LWT message")
if __name__ == "__main__":
from wifi_config import connect_wifi
if connect_wifi():
demo_lwt()
7.3 Kombinasi Retained dan Last Will
Kombinasi retained message dan last will sangat powerful untuk monitoring device status.
Praktikum 7.3: Status Monitoring System
# mqtt_status_system.py - Complete status monitoring
from umqtt.simple import MQTTClient
import utime
import ujson
MQTT_BROKER = "test.mosquitto.org"
CLIENT_ID = "esp32_smart_device"
class SmartDevice:
"""Smart device dengan status monitoring lengkap"""
def __init__(self, device_id, location):
self.device_id = device_id
self.location = location
self.client = None
# Topics
self.topic_status = f"device/{device_id}/status"
self.topic_lwt = f"device/{device_id}/lwt"
self.topic_data = f"device/{device_id}/data"
def connect(self):
"""Connect dengan LWT dan retained message"""
print(f"Connecting {self.device_id}...")
self.client = MQTTClient(
client_id=CLIENT_ID,
server=MQTT_BROKER,
keepalive=60
)
# Setup Last Will (retained)
lwt_payload = ujson.dumps({
"device_id": self.device_id,
"status": "offline",
"reason": "unexpected_disconnect",
"timestamp": utime.time()
})
self.client.set_last_will(
topic=self.topic_lwt,
msg=lwt_payload,
retain=True,
qos=1
)
self.client.connect()
print("Connected!")
# Publish online status (retained)
self.publish_status("online")
def publish_status(self, status):
"""Publish device status dengan retained flag"""
payload = ujson.dumps({
"device_id": self.device_id,
"location": self.location,
"status": status,
"timestamp": utime.time()
})
self.client.publish(
self.topic_status,
payload,
retain=True,
qos=1
)
print(f"Status published: {status}")
def publish_data(self, data):
"""Publish sensor data (tidak retained)"""
payload = ujson.dumps({
"device_id": self.device_id,
"data": data,
"timestamp": utime.time()
})
self.client.publish(
self.topic_data,
payload,
retain=False, # Data selalu berubah
qos=0
)
def run(self):
"""Main loop device"""
print(f"\n{self.device_id} is running...")
print("Press Ctrl+C for normal shutdown")
print("Or reset board to trigger LWT\n")
try:
counter = 0
while True:
# Publish sensor data
sensor_data = {
"temperature": 25 + (counter % 5),
"humidity": 60 + (counter % 10)
}
self.publish_data(sensor_data)
print(f"Data sent: temp={sensor_data['temperature']}°C")
counter += 1
utime.sleep(5)
except KeyboardInterrupt:
print("\n\nNormal shutdown initiated...")
self.shutdown()
def shutdown(self):
"""Normal shutdown"""
# Publish offline status
self.publish_status("offline")
# Clear LWT (publish empty retained message)
self.client.publish(self.topic_lwt, "", retain=True)
# Disconnect
self.client.disconnect()
print("Shutdown complete")
class DeviceMonitor:
"""Monitor untuk device status"""
def __init__(self):
self.client = None
self.devices = {}
def connect(self):
"""Connect monitor"""
self.client = MQTTClient("esp32_monitor", MQTT_BROKER)
self.client.set_callback(self.on_message)
self.client.connect()
# Subscribe ke semua device topics
self.client.subscribe("device/+/status")
self.client.subscribe("device/+/lwt")
self.client.subscribe("device/+/data")
print("Monitor connected and subscribed")
def on_message(self, topic, msg):
"""Handle incoming messages"""
topic_str = topic.decode()
try:
data = ujson.loads(msg.decode())
device_id = data.get("device_id", "unknown")
if "/status" in topic_str:
self.handle_status(device_id, data)
elif "/lwt" in topic_str:
self.handle_lwt(device_id, data)
elif "/data" in topic_str:
self.handle_data(device_id, data)
except Exception as e:
print(f"Error parsing message: {e}")
def handle_status(self, device_id, data):
"""Handle status update"""
status = data.get("status")
print(f"\n[STATUS] {device_id}: {status}")
self.devices[device_id] = status
def handle_lwt(self, device_id, data):
"""Handle Last Will message"""
print(f"\n⚠️ [LWT] {device_id} disconnected unexpectedly!")
print(f" Reason: {data.get('reason')}")
self.devices[device_id] = "offline"
def handle_data(self, device_id, data):
"""Handle sensor data"""
sensor_data = data.get("data", {})
temp = sensor_data.get("temperature")
humid = sensor_data.get("humidity")
print(f"[DATA] {device_id}: {temp}°C, {humid}%")
def run(self):
"""Run monitor"""
print("\n=== Device Monitor Running ===")
print("Monitoring all devices...\n")
try:
while True:
self.client.check_msg()
utime.sleep(0.1)
except KeyboardInterrupt:
print("\nMonitor stopped")
self.client.disconnect()
# Main program
def main():
"""Main program"""
print("=== MQTT Status Monitoring System ===")
print("\nPilih mode:")
print("1. Run as Device")
print("2. Run as Monitor")
# Untuk demo, jalankan sebagai device
mode = 1 # Ganti ke 2 untuk monitor mode
if mode == 1:
device = SmartDevice("ESP32_001", "Lab IoT")
device.connect()
device.run()
else:
monitor = DeviceMonitor()
monitor.connect()
monitor.run()
if __name__ == "__main__":
from wifi_config import connect_wifi
if connect_wifi():
main()