Lewati ke isi

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?

  1. Efisiensi Bandwidth: Header MQTT hanya 2 bytes, sangat efisien dibanding HTTP
  2. Connection Oriented: Menggunakan TCP/IP untuk koneksi yang reliable
  3. Many-to-Many Communication: Satu publisher dapat berkomunikasi dengan banyak subscriber
  4. Decoupling: Publisher dan Subscriber tidak perlu tahu keberadaan satu sama lain
  5. 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:

  1. Broker: Server pusat yang menerima semua pesan dan mendistribusikan ke subscriber
  2. Publisher: Client yang mengirim (publish) data ke broker
  3. Subscriber: Client yang menerima (subscribe) data dari broker
  4. Topic: Channel atau kategori untuk mengorganisir pesan
  5. 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:

  1. Mosquitto (Eclipse)
  2. Open source dan gratis
  3. Ringan dan cepat
  4. Cocok untuk deployment lokal
  5. Tersedia untuk Windows, Linux, macOS

  6. HiveMQ

  7. Enterprise-grade broker
  8. Tersedia versi cloud gratis
  9. Scalable dan reliable
  10. Dashboard yang user-friendly

  11. EMQX

  12. High-performance broker
  13. Mendukung jutaan koneksi
  14. Clustering support
  15. Open source dan enterprise version

  16. CloudMQTT / HiveMQ Cloud

  17. Managed cloud broker
  18. Tidak perlu setup server
  19. Gratis untuk development
  20. 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)

  1. Kunjungi https://www.hivemq.com/mqtt-cloud-broker/
  2. Klik "Sign Up" atau "Get Started Free"
  3. Isi form registrasi dengan email
  4. Verifikasi email
  5. Login ke dashboard HiveMQ Cloud
  6. Klik "Create Cluster"
  7. Pilih "Free Plan"
  8. 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

  1. Download MQTT Explorer dari http://mqtt-explorer.com/
  2. Install aplikasi sesuai OS Anda
  3. 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

  1. Klik "CONNECT"
  2. Jika berhasil, Anda akan melihat status "Connected"
  3. Di panel "Publish", masukkan:
  4. Topic: test/hello
  5. Message: Hello from MQTT Explorer
  6. Klik "PUBLISH"
  7. 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:

>>> import umqtt.simple
>>> print("Library tersedia!")

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

  1. Upload file wifi_config.py dan mqtt_publisher.py ke ESP32
  2. Edit WIFI_SSID dan WIFI_PASSWORD sesuai WiFi Anda
  3. Di REPL, jalankan:

    >>> import mqtt_publisher
    

  4. Amati output di serial terminal

  5. Buka MQTT Explorer dan subscribe ke topic iot/esp32/test
  6. 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:

  1. Upload dan jalankan mqtt_pubsub.py di ESP32
  2. Buka MQTT Explorer
  3. Subscribe ke topic iot/esp32/sensor
  4. Publish command ke topic iot/esp32/command:
  5. Message: STATUS
  6. Message: PING
  7. 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/Temp berbeda dengan sensor/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()

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()