Koneksi Alternatif IoT dengan Wokwi
Pada pertemuan kali ini akan membahas berbagai protokol komunikasi IoT selain MQTT. Mahasiswa akan mempelajari protokol HTTP/REST API, WebSocket, dan CoAP, beserta implementasi praktis menggunakan ESP32 di Wokwi simulator. Setiap protokol memiliki karakteristik dan use case yang berbeda dalam ekosistem IoT.
Alat dan Bahan
Simulator:
- Wokwi Online Platform (https://wokwi.com)
- Akun Wokwi (gratis, login dengan Google/GitHub)
Software:
- Web Browser (Chrome, Firefox, Edge)
- Postman atau Thunder Client (untuk test REST API)
- Python 3.x (untuk mock server)
- Text Editor untuk mencatat konfigurasi
Server/Backend:
- HTTP: RequestBin (https://requestbin.com) - untuk test HTTP POST
- WebSocket: WebSocket.org Echo Server
- CoAP: CoAP.me Public Server (coap://coap.me)
Network:
- Koneksi Internet untuk akses Wokwi dan public servers
- WiFi connection akan di-mock oleh Wokwi simulator
Bagian 1: Pengenalan Protokol IoT
1.1 Perbandingan Protokol IoT
Sebelum mulai praktikum, penting memahami karakteristik masing-masing protokol.
Tabel Perbandingan:
| Aspek | HTTP/REST | MQTT | WebSocket | CoAP |
|---|---|---|---|---|
| Arsitektur | Request-Response | Pub-Sub | Full-Duplex | Request-Response |
| Transport | TCP | TCP | TCP | UDP |
| Overhead | Tinggi | Rendah | Sedang | Sangat Rendah |
| Bandwidth | Besar | Kecil | Sedang | Sangat Kecil |
| Real-time | ✗ | ✓ | ✓✓ | ✗ |
| Power Consumption | Tinggi | Rendah | Sedang | Sangat Rendah |
| Use Case | Web API, CRUD | Telemetry, Command | Chat, Live Update | Constrained Device |
| Port Default | 80/443 | 1883/8883 | 80/443 | 5683/5684 |
Kapan Menggunakan Protokol Tertentu:
HTTP/REST:
✓ Integration dengan web services
✓ CRUD operations (Create, Read, Update, Delete)
✓ Device yang tidak battery-powered
✗ Real-time monitoring
✗ Constrained devices (low power/bandwidth)
MQTT:
✓ Sensor data streaming
✓ Command and control
✓ Battery-powered devices
✓ Unreliable network
✗ Request-response patterns
✗ Large payload (> 256 MB)
WebSocket:
✓ Bi-directional communication
✓ Real-time updates (chat, dashboard)
✓ Live streaming data
✗ Simple request-response
✗ Low-power devices
CoAP:
✓ Constrained devices (memory, power)
✓ Low bandwidth networks (2G, NB-IoT)
✓ UDP-based communication
✗ Complex state management
✗ High-frequency updates
1.2 Arsitektur Komunikasi
Diagram Perbandingan Arsitektur:
HTTP/REST (Request-Response):
┌──────────┐ ┌──────────┐
│ Device │ ─── HTTP POST → │ Server │
│ (ESP32) │ │ API │
│ │ ← HTTP Response ─│ │
└──────────┘ └──────────┘
Polling: Device request berulang
MQTT (Publish-Subscribe):
┌──────────┐ ┌──────────┐
│ Device 1 │ ─── Publish ───→ │ Broker │
└──────────┘ └────┬─────┘
│
┌──────────┐ │
│ Device 2 │ ←──── Subscribe ──────┘
└──────────┘
Push: Broker forward messages
WebSocket (Full-Duplex):
┌──────────┐ ════════════════ ┌──────────┐
│ Device │ ← Send/Receive → │ Server │
│ (ESP32) │ ════════════════ │ │
└──────────┘ └──────────┘
Persistent: Connection always open
CoAP (Request-Response over UDP):
┌──────────┐ ┌──────────┐
│ Device │ ─── CoAP GET ──→ │ Server │
│ (ESP32) │ │ CoAP │
│ │ ← CoAP Response ─│ │
└──────────┘ └──────────┘
UDP: Lightweight, connectionless
1.3 Setup Project Wokwi
Praktikum 1.1: Persiapan Project
Langkah 1: Buat Project Baru
- Login ke Wokwi (https://wokwi.com)
- Klik "New Project"
- Pilih template "ESP32"
- Rename project: "IoT Protocols Comparison"
Langkah 2: Setup Hardware
Kita akan gunakan setup sederhana: ESP32 + DHT22 + LED
- Klik "+" (Add part)
- Tambahkan DHT22 sensor
- Tambahkan Red LED
- Tambahkan Resistor 220Ω
Wiring:
| Component | Pin | ESP32 Pin |
|---|---|---|
| DHT22 VCC | 3V3 | |
| DHT22 DATA | GPIO 15 | |
| DHT22 GND | GND | |
| LED Anode (+) | → Resistor → | GPIO 2 |
| LED Cathode (-) | GND |
Langkah 3: Verify Diagram
Setelah wiring selesai, diagram seharusnya seperti ini:
DHT22
┌─┬─┬─┐
│ │ │ │
3V3│ │GND
15
┌─────────┐
│ ESP32 │
│ │
│ 2 GND │
└─┬───┬───┘
│ │
[R220] │
│ │
[LED] │
└───┘
Bagian 2: HTTP/REST API Protocol
HTTP adalah protokol request-response yang paling umum digunakan untuk komunikasi web. Dalam IoT, HTTP digunakan untuk mengirim data sensor ke server atau mengambil konfigurasi dari cloud.
2.1 Pengenalan HTTP/REST
Konsep Dasar HTTP:
- Request Methods:
GET: Mengambil data dari serverPOST: Mengirim data ke server (paling umum untuk IoT)PUT: Update data di server-
DELETE: Hapus data di server -
HTTP Status Codes:
200 OK: Request berhasil201 Created: Resource dibuat400 Bad Request: Request invalid404 Not Found: Resource tidak ditemukan500 Server Error: Error di server
REST API Structure:
HTTP Method + Endpoint + Headers + Body
Example:
POST /api/sensor/data HTTP/1.1
Host: example.com
Content-Type: application/json
{
"temperature": 25.5,
"humidity": 60.2,
"device_id": "esp32-001"
}
2.2 Implementasi HTTP POST
Praktikum 2.1: Kirim Data Sensor via HTTP
Kita akan implementasi HTTP POST untuk mengirim data sensor ke RequestBin (service untuk test HTTP requests).
Langkah 1: Setup RequestBin
- Buka browser, akses: https://requestbin.com
- Klik "Create a RequestBin"
- Copy URL endpoint yang diberikan (contoh:
https://requestbin.com/r/xxxxx) - Catat URL ini untuk digunakan di code
Langkah 2: Code HTTP Client
Buka main.py di Wokwi, hapus isi sebelumnya, dan mulai dengan imports:
# ============================================
# HTTP/REST Protocol - IoT Data Sender
# Platform: Wokwi ESP32 Simulator
# ============================================
import network
import urequests as requests # HTTP library untuk MicroPython
import ujson as json
import time
from machine import Pin
import dht
print("\n" + "="*50)
print("HTTP/REST Protocol Implementation")
print("Platform: Wokwi ESP32")
print("="*50)
Langkah 3: Konfigurasi
Tambahkan konfigurasi di bawah imports:
# ============================================
# CONFIGURATION
# ============================================
# WiFi Configuration
WIFI_SSID = "Wokwi-GUEST"
WIFI_PASSWORD = ""
# HTTP API Configuration
# Ganti dengan URL RequestBin Anda!
API_ENDPOINT = "https://requestbin.com/r/xxxxx" # TODO: Update ini!
API_TIMEOUT = 10 # Timeout dalam detik
# Headers untuk HTTP request
HEADERS = {
'Content-Type': 'application/json',
'User-Agent': 'ESP32-Wokwi/1.0'
}
# Timing Configuration
SEND_INTERVAL = 10 # Kirim data setiap 10 detik
print("[CONFIG] Configuration loaded")
print(f"[CONFIG] API Endpoint: {API_ENDPOINT}")
print(f"[CONFIG] Send interval: {SEND_INTERVAL}s")
⚠️ PENTING: Ganti API_ENDPOINT dengan URL RequestBin yang Anda dapatkan!
Langkah 4: Hardware Setup
Tambahkan setup hardware:
# ============================================
# HARDWARE SETUP
# ============================================
# LED setup - GPIO 2
led = Pin(2, Pin.OUT)
led.value(0)
print("[HARDWARE] LED initialized on GPIO 2")
# DHT22 setup - GPIO 15
dht_sensor = dht.DHT22(Pin(15))
print("[HARDWARE] DHT22 sensor initialized on GPIO 15")
# State variables
last_send_time = 0
send_count = 0
Langkah 5: WiFi Connection Function
Tambahkan fungsi untuk connect WiFi:
# ============================================
# WIFI CONNECTION
# ============================================
def connect_wifi():
"""Connect to WiFi network"""
print("\n[WIFI] Connecting to WiFi...")
print(f"[WIFI] SSID: {WIFI_SSID}")
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
print("[WIFI] Waiting for connection...")
max_wait = 20
while max_wait > 0:
if wlan.isconnected():
break
time.sleep(1)
max_wait -= 1
print(".", end="")
print() # New line
if wlan.isconnected():
print("[WIFI] ✓ Connected successfully!")
print(f"[WIFI] IP Address: {wlan.ifconfig()[0]}")
return True
else:
print("[WIFI] ✗ Connection failed!")
return False
Langkah 6: Sensor Reading Function
Tambahkan fungsi untuk baca sensor:
# ============================================
# SENSOR FUNCTIONS
# ============================================
def read_sensor():
"""
Read temperature and humidity from DHT22
Returns: dict with sensor data or None if error
"""
try:
dht_sensor.measure()
time.sleep(0.5)
temp = dht_sensor.temperature()
hum = dht_sensor.humidity()
if temp is None or hum is None:
print("[SENSOR] ✗ Failed to read sensor")
return None
data = {
'temperature': round(temp, 1),
'humidity': round(hum, 1)
}
print(f"[SENSOR] ✓ Read: Temp={data['temperature']}°C, Humidity={data['humidity']}%")
return data
except Exception as e:
print(f"[SENSOR] ✗ Error: {e}")
return None
Langkah 7: HTTP POST Function
Ini adalah fungsi utama untuk kirim data via HTTP:
# ============================================
# HTTP FUNCTIONS
# ============================================
def send_http_post(sensor_data):
"""
Send sensor data via HTTP POST
Args:
sensor_data: dict with temperature and humidity
Returns:
bool: True if success, False if failed
"""
global send_count
if sensor_data is None:
print("[HTTP] ✗ Cannot send - no sensor data")
return False
# Blink LED untuk indicate sending
led.value(1)
try:
# Prepare payload
payload = {
'device_id': 'esp32-wokwi-001',
'temperature': sensor_data['temperature'],
'humidity': sensor_data['humidity'],
'timestamp': time.time(),
'count': send_count
}
# Convert ke JSON
payload_json = json.dumps(payload)
print(f"\n[HTTP] Sending POST request...")
print(f"[HTTP] Endpoint: {API_ENDPOINT}")
print(f"[HTTP] Payload: {payload_json}")
# Send HTTP POST request
response = requests.post(
API_ENDPOINT,
data=payload_json,
headers=HEADERS,
timeout=API_TIMEOUT
)
# Check response
print(f"[HTTP] Status Code: {response.status_code}")
if response.status_code in [200, 201]:
print("[HTTP] ✓ Data sent successfully!")
send_count += 1
# Print response body jika ada
if response.text:
print(f"[HTTP] Response: {response.text[:100]}") # First 100 chars
success = True
else:
print(f"[HTTP] ✗ Request failed with status {response.status_code}")
success = False
# Cleanup
response.close()
# LED off
led.value(0)
return success
except Exception as e:
print(f"[HTTP] ✗ Exception: {e}")
led.value(0)
return False
Penjelasan Code:
- LED Indicator: LED nyala saat sending, mati setelah selesai
- Payload Structure: Include device_id, sensor data, timestamp, dan counter
- requests.post(): Library
urequestsuntuk HTTP request - Error Handling: Try-catch untuk handle network errors
- Response Validation: Check status code untuk verify success
Langkah 8: Main Program
Tambahkan main loop untuk integrate semua:
# ============================================
# MAIN PROGRAM
# ============================================
def main():
"""Main program loop"""
global last_send_time
print("\n" + "="*50)
print("Starting HTTP IoT Application")
print("="*50)
# Connect WiFi
if not connect_wifi():
print("[ERROR] WiFi failed - stopping")
return
time.sleep(2)
print("\n" + "="*50)
print("System Ready!")
print("="*50)
print(f"[INFO] Sending data every {SEND_INTERVAL} seconds")
print("[INFO] Press STOP to exit")
print("="*50 + "\n")
last_send_time = time.time()
loop_count = 0
# Main loop
while True:
try:
loop_count += 1
current_time = time.time()
# Check if it's time to send
if (current_time - last_send_time) >= SEND_INTERVAL:
print(f"\n{'='*50}")
print(f"Loop #{loop_count}")
print(f"{'='*50}")
# Read sensor
sensor_data = read_sensor()
# Send via HTTP
if sensor_data:
success = send_http_post(sensor_data)
if success:
print("[STATUS] ✓ Cycle completed successfully")
else:
print("[STATUS] ✗ Cycle failed")
# Update timing
last_send_time = current_time
# Small delay
time.sleep(0.5)
except KeyboardInterrupt:
print("\n[INFO] Program stopped by user")
break
except Exception as e:
print(f"[ERROR] Main loop error: {e}")
time.sleep(5)
# Cleanup
led.value(0)
print("[INFO] Program ended")
# Entry point
if __name__ == "__main__":
main()
Langkah 9: Test HTTP POST
- Update API_ENDPOINT dengan URL RequestBin Anda
- Klik "Play" di Wokwi
- Tunggu WiFi connect
- Setiap 10 detik, ESP32 akan send data
- Lihat Serial Monitor untuk logs
Expected Output:
==================================================
HTTP/REST Protocol Implementation
Platform: Wokwi ESP32
==================================================
[CONFIG] Configuration loaded
[CONFIG] API Endpoint: https://requestbin.com/r/xxxxx
[CONFIG] Send interval: 10s
[HARDWARE] LED initialized on GPIO 2
[HARDWARE] DHT22 sensor initialized on GPIO 15
[WIFI] Connecting to WiFi...
[WIFI] SSID: Wokwi-GUEST
[WIFI] ✓ Connected successfully!
[WIFI] IP Address: 192.168.1.100
==================================================
System Ready!
==================================================
[INFO] Sending data every 10 seconds
[INFO] Press STOP to exit
==================================================
==================================================
Loop #1
==================================================
[SENSOR] ✓ Read: Temp=24.5°C, Humidity=55.3%
[HTTP] Sending POST request...
[HTTP] Endpoint: https://requestbin.com/r/xxxxx
[HTTP] Payload: {"device_id":"esp32-wokwi-001","temperature":24.5,"humidity":55.3,"timestamp":123456,"count":0}
[HTTP] Status Code: 200
[HTTP] ✓ Data sent successfully!
[STATUS] ✓ Cycle completed successfully
Langkah 10: Verify di RequestBin
- Buka RequestBin URL di browser
- Refresh page
- Akan melihat request yang masuk dengan:
- Method: POST
- Headers: Content-Type, User-Agent
- Body: JSON dengan sensor data
Screenshot RequestBin akan show:
POST /r/xxxxx
Headers:
Content-Type: application/json
User-Agent: ESP32-Wokwi/1.0
Body:
{
"device_id": "esp32-wokwi-001",
"temperature": 24.5,
"humidity": 55.3,
"timestamp": 123456,
"count": 0
}
2.3 HTTP GET Request
Praktikum 2.2: Retrieve Configuration via HTTP GET
Sekarang kita implementasi HTTP GET untuk mengambil konfigurasi dari server.
Use Case: ESP32 request update interval atau threshold dari server.
Langkah 1: Tambahkan Config Endpoint
Tambahkan di section CONFIGURATION:
# GET endpoint untuk ambil config
CONFIG_ENDPOINT = "https://api.github.com/zen" # Public API untuk test
# Atau bisa pakai JSONPlaceholder: "https://jsonplaceholder.typicode.com/todos/1"
Langkah 2: HTTP GET Function
Tambahkan fungsi di section HTTP FUNCTIONS:
def fetch_config_http():
"""
Fetch configuration from server via HTTP GET
Returns: dict with config or None if failed
"""
try:
print(f"\n[HTTP] Sending GET request...")
print(f"[HTTP] Endpoint: {CONFIG_ENDPOINT}")
# Send GET request
response = requests.get(
CONFIG_ENDPOINT,
headers=HEADERS,
timeout=API_TIMEOUT
)
print(f"[HTTP] Status Code: {response.status_code}")
if response.status_code == 200:
print("[HTTP] ✓ Config retrieved successfully!")
# Parse response
try:
config = json.loads(response.text)
print(f"[HTTP] Config: {config}")
response.close()
return config
except:
# Jika bukan JSON, return sebagai text
print(f"[HTTP] Response (text): {response.text}")
response.close()
return {'message': response.text}
else:
print(f"[HTTP] ✗ Request failed")
response.close()
return None
except Exception as e:
print(f"[HTTP] ✗ Exception: {e}")
return None
Langkah 3: Update Main Loop
Modify main() untuk fetch config di awal:
# Dalam main(), setelah WiFi connect, tambahkan:
# Fetch initial config
print("\n[CONFIG] Fetching configuration from server...")
config = fetch_config_http()
if config:
print("[CONFIG] ✓ Configuration loaded")
# Bisa update SEND_INTERVAL based on config
# SEND_INTERVAL = config.get('interval', SEND_INTERVAL)
else:
print("[CONFIG] ⚠ Using default configuration")
Test GET Request:
- Run simulator
- Setelah WiFi connect, akan fetch config
- Lihat response di Serial Monitor
Bagian 3: WebSocket Protocol
WebSocket adalah protokol full-duplex yang memungkinkan komunikasi bi-directional real-time antara client dan server. Berbeda dengan HTTP yang request-response, WebSocket maintain persistent connection.
3.1 Pengenalan WebSocket
Karakteristik WebSocket:
- Persistent Connection: Connection tetap terbuka, tidak perlu reconnect
- Bi-directional: Client dan server bisa send message kapan saja
- Low Latency: Tidak ada overhead HTTP handshake setiap request
- Real-time: Ideal untuk chat, live updates, streaming
WebSocket Handshake:
Client → Server:
GET /socket HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Server → Client:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
[Connection upgraded ke WebSocket]
[Both sides can now send messages anytime]
Diagram Communication:
HTTP (Polling): WebSocket:
Client ─── Request ──→ Client ═══════════ Server
←── Response ─ │ Send/Receive │
(wait) │ ═══════════ │
Client ─── Request ──→ │ Persistent │
←── Response ─ └═════════════════┘
(repeat)
Overhead: High Overhead: Low
Latency: High Latency: Very Low
3.2 Implementasi WebSocket Client
Praktikum 3.1: WebSocket Communication
Kita akan gunakan WebSocket.org Echo Server untuk test. Server ini akan echo back semua message yang dikirim.
Langkah 1: Create New File
Untuk WebSocket, kita buat file baru agar tidak conflict dengan HTTP code.
- Di Wokwi, buat tab baru atau clear main.py
- Nama file: websocket_client.py
Langkah 2: Import dan Configuration
# ============================================
# WebSocket Protocol - Real-time Communication
# Platform: Wokwi ESP32 Simulator
# ============================================
import network
import time
import socket
import ubinascii
import uhashlib
import urandom
from machine import Pin
import dht
print("\n" + "="*50)
print("WebSocket Protocol Implementation")
print("Platform: Wokwi ESP32")
print("="*50)
# ============================================
# CONFIGURATION
# ============================================
# WiFi Configuration
WIFI_SSID = "Wokwi-GUEST"
WIFI_PASSWORD = ""
# WebSocket Server Configuration
WS_HOST = "echo.websocket.org"
WS_PORT = 80
WS_PATH = "/"
# Timing
HEARTBEAT_INTERVAL = 30 # Send ping every 30s
SEND_INTERVAL = 5 # Send sensor data every 5s
print("[CONFIG] Configuration loaded")
print(f"[CONFIG] WebSocket Server: ws://{WS_HOST}:{WS_PORT}")
Langkah 3: Hardware Setup
# ============================================
# HARDWARE SETUP
# ============================================
led = Pin(2, Pin.OUT)
led.value(0)
print("[HARDWARE] LED initialized on GPIO 2")
dht_sensor = dht.DHT22(Pin(15))
print("[HARDWARE] DHT22 sensor initialized on GPIO 15")
# State
ws_connected = False
last_ping_time = 0
last_send_time = 0
Langkah 4: WiFi Function
# ============================================
# WIFI CONNECTION
# ============================================
def connect_wifi():
"""Connect to WiFi"""
print("\n[WIFI] Connecting...")
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
max_wait = 20
while max_wait > 0 and not wlan.isconnected():
time.sleep(1)
max_wait -= 1
if wlan.isconnected():
print("[WIFI] ✓ Connected!")
print(f"[WIFI] IP: {wlan.ifconfig()[0]}")
return True
return False
Langkah 5: WebSocket Handshake
WebSocket dimulai dengan HTTP upgrade handshake:
# ============================================
# WEBSOCKET FUNCTIONS
# ============================================
def generate_websocket_key():
"""Generate random WebSocket key"""
random_bytes = bytes([urandom.getrandbits(8) for _ in range(16)])
return ubinascii.b2a_base64(random_bytes).decode().strip()
def websocket_handshake(sock):
"""
Perform WebSocket handshake
Returns: True if success, False if failed
"""
print("[WS] Performing handshake...")
# Generate random key
key = generate_websocket_key()
# Build handshake request
handshake = (
f"GET {WS_PATH} HTTP/1.1\r\n"
f"Host: {WS_HOST}\r\n"
f"Upgrade: websocket\r\n"
f"Connection: Upgrade\r\n"
f"Sec-WebSocket-Key: {key}\r\n"
f"Sec-WebSocket-Version: 13\r\n"
f"\r\n"
)
# Send handshake
sock.send(handshake.encode())
print("[WS] Handshake request sent")
# Wait for response
try:
response = sock.recv(1024).decode()
print(f"[WS] Response received ({len(response)} bytes)")
# Check for 101 Switching Protocols
if "101" in response and "Upgrade" in response:
print("[WS] ✓ Handshake successful!")
return True
else:
print("[WS] ✗ Handshake failed")
print(f"[WS] Response: {response[:200]}")
return False
except Exception as e:
print(f"[WS] ✗ Handshake error: {e}")
return False
Langkah 6: WebSocket Frame Format
WebSocket menggunakan frame format khusus. Untuk simplicity, kita buat fungsi helper:
def create_websocket_frame(payload, opcode=0x01):
"""
Create WebSocket frame
opcode: 0x01 = text, 0x02 = binary, 0x08 = close, 0x09 = ping
"""
# Convert payload to bytes
if isinstance(payload, str):
payload = payload.encode()
payload_len = len(payload)
# Frame header
frame = bytearray()
frame.append(0x80 | opcode) # FIN bit + opcode
# Payload length
if payload_len <= 125:
frame.append(0x80 | payload_len) # MASK bit + length
elif payload_len <= 65535:
frame.append(0x80 | 126)
frame.extend(payload_len.to_bytes(2, 'big'))
else:
frame.append(0x80 | 127)
frame.extend(payload_len.to_bytes(8, 'big'))
# Masking key (required for client)
mask = bytes([urandom.getrandbits(8) for _ in range(4)])
frame.extend(mask)
# Masked payload
masked = bytearray(payload_len)
for i in range(payload_len):
masked[i] = payload[i] ^ mask[i % 4]
frame.extend(masked)
return bytes(frame)
def send_websocket_message(sock, message):
"""Send text message via WebSocket"""
try:
frame = create_websocket_frame(message, opcode=0x01)
sock.send(frame)
print(f"[WS] ✓ Sent: {message}")
return True
except Exception as e:
print(f"[WS] ✗ Send error: {e}")
return False
Langkah 7: Read WebSocket Messages
def read_websocket_message(sock, timeout=1):
"""
Read incoming WebSocket message
Returns: message string or None
"""
try:
# Set non-blocking with timeout
sock.settimeout(timeout)
# Read frame header (minimum 2 bytes)
header = sock.recv(2)
if len(header) < 2:
return None
# Parse first byte
fin = (header[0] & 0x80) != 0
opcode = header[0] & 0x0F
# Parse second byte
masked = (header[1] & 0x80) != 0
payload_len = header[1] & 0x7F
# Extended payload length
if payload_len == 126:
ext_len = sock.recv(2)
payload_len = int.from_bytes(ext_len, 'big')
elif payload_len == 127:
ext_len = sock.recv(8)
payload_len = int.from_bytes(ext_len, 'big')
# Read masking key (if masked - servers don't mask)
if masked:
mask = sock.recv(4)
# Read payload
payload = bytearray()
while len(payload) < payload_len:
chunk = sock.recv(min(4096, payload_len - len(payload)))
if not chunk:
break
payload.extend(chunk)
# Unmask if needed
if masked:
for i in range(len(payload)):
payload[i] ^= mask[i % 4]
# Handle opcodes
if opcode == 0x01: # Text frame
message = payload.decode('utf-8')
print(f"[WS] ✓ Received: {message}")
return message
elif opcode == 0x08: # Close frame
print("[WS] ⚠ Close frame received")
return None
elif opcode == 0x09: # Ping frame
print("[WS] ⚠ Ping received")
# Should send pong
return None
elif opcode == 0x0A: # Pong frame
print("[WS] ✓ Pong received")
return None
return None
except Exception as e:
# Timeout or error - normal in non-blocking mode
return None
Langkah 8: Sensor Function
# ============================================
# SENSOR FUNCTIONS
# ============================================
def read_sensor():
"""Read DHT22 sensor"""
try:
dht_sensor.measure()
time.sleep(0.5)
temp = dht_sensor.temperature()
hum = dht_sensor.humidity()
if temp is None or hum is None:
return None
data = {
'temperature': round(temp, 1),
'humidity': round(hum, 1)
}
print(f"[SENSOR] ✓ Temp={data['temperature']}°C, Hum={data['humidity']}%")
return data
except Exception as e:
print(f"[SENSOR] ✗ Error: {e}")
return None
Langkah 9: Main Program dengan WebSocket
# ============================================
# MAIN PROGRAM
# ============================================
def main():
"""Main WebSocket client program"""
global ws_connected, last_ping_time, last_send_time
print("\n" + "="*50)
print("Starting WebSocket IoT Application")
print("="*50)
# Connect WiFi
if not connect_wifi():
print("[ERROR] WiFi failed")
return
time.sleep(2)
# Connect WebSocket
print(f"\n[WS] Connecting to ws://{WS_HOST}:{WS_PORT}")
try:
# Create socket
addr = socket.getaddrinfo(WS_HOST, WS_PORT)[0][-1]
sock = socket.socket()
sock.connect(addr)
print("[WS] TCP connection established")
# Perform handshake
if not websocket_handshake(sock):
print("[ERROR] WebSocket handshake failed")
sock.close()
return
ws_connected = True
print("\n" + "="*50)
print("WebSocket Connected!")
print("="*50)
print("[INFO] Sending sensor data every 5 seconds")
print("[INFO] Echo server will reply with same message")
print("[INFO] Press STOP to exit")
print("="*50 + "\n")
last_ping_time = time.time()
last_send_time = time.time()
msg_count = 0
# Main loop
while ws_connected:
try:
current_time = time.time()
# Send heartbeat ping
if (current_time - last_ping_time) >= HEARTBEAT_INTERVAL:
ping_frame = create_websocket_frame("ping", opcode=0x09)
sock.send(ping_frame)
print("[WS] ❤ Heartbeat ping sent")
last_ping_time = current_time
# Send sensor data
if (current_time - last_send_time) >= SEND_INTERVAL:
msg_count += 1
print(f"\n{'='*50}")
print(f"Message #{msg_count}")
print(f"{'='*50}")
# Blink LED
led.value(1)
# Read sensor
sensor_data = read_sensor()
if sensor_data:
# Format message
import ujson as json
message = json.dumps({
'device_id': 'esp32-wokwi-001',
'temperature': sensor_data['temperature'],
'humidity': sensor_data['humidity'],
'timestamp': int(current_time),
'count': msg_count
})
# Send via WebSocket
send_websocket_message(sock, message)
led.value(0)
last_send_time = current_time
# Check for incoming messages
incoming = read_websocket_message(sock, timeout=0.1)
if incoming:
print(f"[WS] Echo response: {incoming}")
# Small delay
time.sleep(0.1)
except KeyboardInterrupt:
print("\n[INFO] Interrupted by user")
break
except Exception as e:
print(f"[ERROR] Loop error: {e}")
ws_connected = False
break
# Cleanup
print("\n[WS] Closing connection...")
try:
close_frame = create_websocket_frame("", opcode=0x08)
sock.send(close_frame)
time.sleep(0.5)
except:
pass
sock.close()
print("[WS] Connection closed")
except Exception as e:
print(f"[ERROR] Connection error: {e}")
finally:
led.value(0)
print("[INFO] Program ended")
# Entry point
if __name__ == "__main__":
main()
Langkah 10: Test WebSocket
- Copy semua code WebSocket ke main.py
- Klik "Play" di Wokwi
- Tunggu WebSocket handshake
- Setiap 5 detik, ESP32 kirim sensor data
- Echo server akan reply dengan message yang sama
Expected Output:
==================================================
WebSocket Protocol Implementation
Platform: Wokwi ESP32
==================================================
[CONFIG] Configuration loaded
[CONFIG] WebSocket Server: ws://echo.websocket.org:80
[HARDWARE] LED initialized on GPIO 2
[HARDWARE] DHT22 sensor initialized on GPIO 15
[WIFI] Connecting...
[WIFI] ✓ Connected!
[WIFI] IP: 192.168.1.100
[WS] Connecting to ws://echo.websocket.org:80
[WS] TCP connection established
[WS] Performing handshake...
[WS] Handshake request sent
[WS] Response received (234 bytes)
[WS] ✓ Handshake successful!
==================================================
WebSocket Connected!
==================================================
[INFO] Sending sensor data every 5 seconds
[INFO] Echo server will reply with same message
[INFO] Press STOP to exit
==================================================
==================================================
Message #1
==================================================
[SENSOR] ✓ Temp=24.5°C, Hum=55.3%
[WS] ✓ Sent: {"device_id":"esp32-wokwi-001","temperature":24.5,"humidity":55.3,"timestamp":123456,"count":1}
[WS] ✓ Received: {"device_id":"esp32-wokwi-001","temperature":24.5,"humidity":55.3,"timestamp":123456,"count":1}
[WS] Echo response: {"device_id":"esp32-wokwi-001","temperature":24.5,"humidity":55.3,"timestamp":123456,"count":1}
==================================================
Message #2
==================================================
[SENSOR] ✓ Temp=24.6°C, Hum=55.1%
[WS] ✓ Sent: {"device_id":"esp32-wokwi-001","temperature":24.6,"humidity":55.1,"timestamp":123461,"count":2}
[WS] ✓ Received: {"device_id":"esp32-wokwi-001","temperature":24.6,"humidity":55.1,"timestamp":123461,"count":2}
[WS] Echo response: {"device_id":"esp32-wokwi-001","temperature":24.6,"humidity":55.1,"timestamp":123461,"count":2}
[WS] ❤ Heartbeat ping sent
Keuntungan WebSocket: - ✓ Bi-directional: Server bisa push data tanpa polling - ✓ Real-time: Latency sangat rendah - ✓ Persistent: Connection tetap open, tidak perlu reconnect - ✓ Efficient: Tidak ada overhead HTTP headers per message
Bagian 4: CoAP Protocol
CoAP (Constrained Application Protocol) adalah protokol khusus untuk constrained devices dengan memory dan power terbatas. CoAP menggunakan UDP dan dirancang mirip HTTP tapi jauh lebih lightweight.
4.1 Pengenalan CoAP
Karakteristik CoAP:
- Transport: UDP (bukan TCP)
- Message Size: Sangat kecil (4 bytes header minimum)
- Methods: GET, POST, PUT, DELETE (mirip HTTP)
- Reliability: Optional confirmable messages
- Resource Discovery: Built-in resource discovery
Perbandingan dengan HTTP:
| Aspek | HTTP | CoAP |
|---|---|---|
| Transport | TCP | UDP |
| Header Size | ~200-500 bytes | 4-10 bytes |
| Message Type | Request-Response | CON, NON, ACK, RST |
| Default Port | 80/443 | 5683/5684 |
| Bandwidth | High | Very Low |
| Ideal For | Web services | Sensor networks |
CoAP Message Types:
CON (Confirmable):
Client ─── CON Request ──→ Server
←──── ACK Response ─
Reliable, perlu acknowledgment
NON (Non-confirmable):
Client ─── NON Request ──→ Server
←──── NON Response ─
Unreliable, no acknowledgment
ACK (Acknowledgment):
Confirm CON message received
RST (Reset):
Reject message / reset connection
CoAP Message Format:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Ver| T | TKL | Code | Message ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Token (if any, TKL bytes) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options (if any) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 1 1 1 1 1 1 1| Payload (if any) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Ver: Version (2 bits) = 01
T: Type (2 bits) = CON(0), NON(1), ACK(2), RST(3)
TKL: Token Length (4 bits)
Code: Method/Response Code (8 bits)
4.2 Implementasi CoAP Client
Praktikum 4.1: CoAP GET dan POST
Kita akan implementasi simple CoAP client untuk communicate dengan public CoAP server.
Langkah 1: Create CoAP Client File
Buat file baru atau clear main.py:
# ============================================
# CoAP Protocol - Constrained Application Protocol
# Platform: Wokwi ESP32 Simulator
# ============================================
import network
import time
import socket
import struct
import urandom
from machine import Pin
import dht
print("\n" + "="*50)
print("CoAP Protocol Implementation")
print("Platform: Wokwi ESP32")
print("="*50)
# ============================================
# CONFIGURATION
# ============================================
# WiFi Configuration
WIFI_SSID = "Wokwi-GUEST"
WIFI_PASSWORD = ""
# CoAP Server Configuration
# Menggunakan coap.me public test server
COAP_SERVER = "coap.me"
COAP_PORT = 5683
# CoAP Constants
COAP_VERSION = 1
COAP_TYPE_CON = 0 # Confirmable
COAP_TYPE_NON = 1 # Non-confirmable
COAP_TYPE_ACK = 2 # Acknowledgment
COAP_TYPE_RST = 3 # Reset
# CoAP Method Codes
COAP_GET = 1
COAP_POST = 2
COAP_PUT = 3
COAP_DELETE = 4
# CoAP Response Codes
COAP_CREATED = 65 # 2.01
COAP_DELETED = 66 # 2.02
COAP_VALID = 67 # 2.03
COAP_CHANGED = 68 # 2.04
COAP_CONTENT = 69 # 2.05
# Timing
SEND_INTERVAL = 10 # Send data every 10 seconds
print("[CONFIG] Configuration loaded")
print(f"[CONFIG] CoAP Server: coap://{COAP_SERVER}:{COAP_PORT}")
Langkah 2: Hardware Setup
# ============================================
# HARDWARE SETUP
# ============================================
led = Pin(2, Pin.OUT)
led.value(0)
print("[HARDWARE] LED initialized on GPIO 2")
dht_sensor = dht.DHT22(Pin(15))
print("[HARDWARE] DHT22 sensor initialized on GPIO 15")
# State
message_id = urandom.getrandbits(16)
Langkah 3: WiFi Function
# ============================================
# WIFI CONNECTION
# ============================================
def connect_wifi():
"""Connect to WiFi"""
print("\n[WIFI] Connecting...")
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
max_wait = 20
while max_wait > 0 and not wlan.isconnected():
time.sleep(1)
max_wait -= 1
if wlan.isconnected():
print("[WIFI] ✓ Connected!")
print(f"[WIFI] IP: {wlan.ifconfig()[0]}")
return True
print("[WIFI] ✗ Connection failed")
return False
Langkah 4: CoAP Message Builder
# ============================================
# COAP FUNCTIONS
# ============================================
def build_coap_message(msg_type, code, token=b'', uri_path='', payload=b''):
"""
Build CoAP message packet
Args:
msg_type: COAP_TYPE_CON, NON, ACK, or RST
code: Method code (GET, POST, etc) or response code
token: Token bytes (optional)
uri_path: URI path string (e.g., "sensor/data")
payload: Payload bytes
Returns:
bytes: Complete CoAP packet
"""
global message_id
# Increment message ID
message_id = (message_id + 1) & 0xFFFF
# Build header (4 bytes)
# Byte 0: Ver(2) | Type(2) | TKL(4)
tkl = len(token) & 0x0F
byte0 = (COAP_VERSION << 6) | (msg_type << 4) | tkl
# Byte 1: Code (class.detail)
byte1 = code
# Bytes 2-3: Message ID
header = struct.pack('!BBH', byte0, byte1, message_id)
# Add token
packet = header + token
# Add options (Uri-Path)
if uri_path:
# Split path by '/'
path_segments = [seg for seg in uri_path.split('/') if seg]
option_number = 11 # Uri-Path option number
for segment in path_segments:
seg_bytes = segment.encode('utf-8')
seg_len = len(seg_bytes)
# Option header: delta(4 bits) | length(4 bits)
option_header = (option_number << 4) | seg_len
packet += bytes([option_header]) + seg_bytes
option_number = 0 # Subsequent same options have delta 0
# Add payload marker and payload
if payload:
packet += b'\xFF' # Payload marker
if isinstance(payload, str):
payload = payload.encode('utf-8')
packet += payload
return packet
def parse_coap_response(data):
"""
Parse CoAP response packet
Returns:
dict with: type, code, message_id, token, payload
"""
if len(data) < 4:
return None
# Parse header
byte0 = data[0]
version = (byte0 >> 6) & 0x03
msg_type = (byte0 >> 4) & 0x03
tkl = byte0 & 0x0F
code = data[1]
message_id = struct.unpack('!H', data[2:4])[0]
# Parse token
pos = 4
token = data[pos:pos+tkl]
pos += tkl
# Skip options (simplified - just find payload marker)
payload = b''
while pos < len(data):
if data[pos] == 0xFF: # Payload marker
payload = data[pos+1:]
break
else:
# Skip option
option_delta = (data[pos] >> 4) & 0x0F
option_len = data[pos] & 0x0F
pos += 1 + option_len
return {
'type': msg_type,
'code': code,
'message_id': message_id,
'token': token,
'payload': payload
}
Langkah 5: CoAP GET Request
def coap_get(sock, server_addr, uri_path):
"""
Send CoAP GET request
Args:
sock: UDP socket
server_addr: (host, port) tuple
uri_path: Resource path (e.g., "test")
Returns:
Response payload or None
"""
try:
print(f"\n[CoAP] Sending GET request")
print(f"[CoAP] URI: coap://{COAP_SERVER}/{uri_path}")
# Build GET request (Confirmable)
token = bytes([urandom.getrandbits(8) for _ in range(4)])
packet = build_coap_message(
msg_type=COAP_TYPE_CON,
code=COAP_GET,
token=token,
uri_path=uri_path
)
print(f"[CoAP] Packet size: {len(packet)} bytes")
# Send request
sock.sendto(packet, server_addr)
print("[CoAP] ✓ Request sent")
# Wait for response
sock.settimeout(5)
data, addr = sock.recvfrom(1024)
print(f"[CoAP] ✓ Response received ({len(data)} bytes)")
# Parse response
response = parse_coap_response(data)
if response:
code_class = response['code'] >> 5
code_detail = response['code'] & 0x1F
print(f"[CoAP] Response Code: {code_class}.{code_detail:02d}")
if response['payload']:
payload_str = response['payload'].decode('utf-8', errors='ignore')
print(f"[CoAP] Payload: {payload_str}")
return payload_str
return None
except Exception as e:
print(f"[CoAP] ✗ GET error: {e}")
return None
Langkah 6: CoAP POST Request
def coap_post(sock, server_addr, uri_path, payload):
"""
Send CoAP POST request
Args:
sock: UDP socket
server_addr: (host, port) tuple
uri_path: Resource path
payload: Data to send (string or bytes)
Returns:
bool: True if success
"""
try:
print(f"\n[CoAP] Sending POST request")
print(f"[CoAP] URI: coap://{COAP_SERVER}/{uri_path}")
print(f"[CoAP] Payload: {payload}")
# Build POST request (Confirmable)
token = bytes([urandom.getrandbits(8) for _ in range(4)])
packet = build_coap_message(
msg_type=COAP_TYPE_CON,
code=COAP_POST,
token=token,
uri_path=uri_path,
payload=payload
)
print(f"[CoAP] Packet size: {len(packet)} bytes")
# Send request
sock.sendto(packet, server_addr)
print("[CoAP] ✓ Request sent")
# Wait for ACK
sock.settimeout(5)
data, addr = sock.recvfrom(1024)
print(f"[CoAP] ✓ ACK received ({len(data)} bytes)")
# Parse response
response = parse_coap_response(data)
if response:
code_class = response['code'] >> 5
code_detail = response['code'] & 0x1F
print(f"[CoAP] Response Code: {code_class}.{code_detail:02d}")
# Check for success (2.xx codes)
if code_class == 2:
print("[CoAP] ✓ POST successful!")
return True
else:
print(f"[CoAP] ✗ POST failed with code {code_class}.{code_detail:02d}")
return False
return False
except Exception as e:
print(f"[CoAP] ✗ POST error: {e}")
return False
Langkah 7: Sensor Function
# ============================================
# SENSOR FUNCTIONS
# ============================================
def read_sensor():
"""Read DHT22 sensor"""
try:
dht_sensor.measure()
time.sleep(0.5)
temp = dht_sensor.temperature()
hum = dht_sensor.humidity()
if temp is None or hum is None:
return None
data = {
'temperature': round(temp, 1),
'humidity': round(hum, 1)
}
print(f"[SENSOR] ✓ Temp={data['temperature']}°C, Hum={data['humidity']}%")
return data
except Exception as e:
print(f"[SENSOR] ✗ Error: {e}")
return None
Langkah 8: Main Program
# ============================================
# MAIN PROGRAM
# ============================================
def main():
"""Main CoAP client program"""
print("\n" + "="*50)
print("Starting CoAP IoT Application")
print("="*50)
# Connect WiFi
if not connect_wifi():
print("[ERROR] WiFi failed")
return
time.sleep(2)
# Resolve server address
print(f"\n[CoAP] Resolving {COAP_SERVER}...")
try:
addr_info = socket.getaddrinfo(COAP_SERVER, COAP_PORT)[0]
server_addr = addr_info[-1]
print(f"[CoAP] ✓ Resolved to: {server_addr[0]}:{server_addr[1]}")
except Exception as e:
print(f"[CoAP] ✗ DNS resolution failed: {e}")
return
# Create UDP socket
print("[CoAP] Creating UDP socket...")
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
print("[CoAP] ✓ Socket created")
print("\n" + "="*50)
print("CoAP Client Ready!")
print("="*50)
print("[INFO] Testing GET and POST operations")
print("[INFO] Sending sensor data every 10 seconds")
print("[INFO] Press STOP to exit")
print("="*50 + "\n")
# Test GET first
print("\n[TEST] Testing CoAP GET...")
coap_get(sock, server_addr, "test")
time.sleep(2)
last_send_time = time.time()
msg_count = 0
# Main loop
while True:
try:
current_time = time.time()
# Send sensor data via POST
if (current_time - last_send_time) >= SEND_INTERVAL:
msg_count += 1
print(f"\n{'='*50}")
print(f"Sending Message #{msg_count}")
print(f"{'='*50}")
# Blink LED
led.value(1)
# Read sensor
sensor_data = read_sensor()
if sensor_data:
# Format payload (simple text format)
payload = f"temp={sensor_data['temperature']}&hum={sensor_data['humidity']}"
# Send via CoAP POST
coap_post(sock, server_addr, "sensor", payload)
led.value(0)
last_send_time = current_time
# Small delay
time.sleep(0.5)
except KeyboardInterrupt:
print("\n[INFO] Interrupted by user")
break
except Exception as e:
print(f"[ERROR] Loop error: {e}")
time.sleep(5)
# Cleanup
print("\n[CoAP] Closing socket...")
sock.close()
led.value(0)
print("[INFO] Program ended")
# Entry point
if __name__ == "__main__":
main()
Langkah 9: Test CoAP
- Copy semua code CoAP ke main.py
- Klik "Play" di Wokwi
- Akan test GET request dulu
- Kemudian loop POST sensor data setiap 10 detik
Expected Output:
==================================================
CoAP Protocol Implementation
Platform: Wokwi ESP32
==================================================
[CONFIG] Configuration loaded
[CONFIG] CoAP Server: coap://coap.me:5683
[HARDWARE] LED initialized on GPIO 2
[HARDWARE] DHT22 sensor initialized on GPIO 15
[WIFI] Connecting...
[WIFI] ✓ Connected!
[WIFI] IP: 192.168.1.100
[CoAP] Resolving coap.me...
[CoAP] ✓ Resolved to: 134.102.218.18:5683
[CoAP] Creating UDP socket...
[CoAP] ✓ Socket created
==================================================
CoAP Client Ready!
==================================================
[INFO] Testing GET and POST operations
[INFO] Sending sensor data every 10 seconds
[INFO] Press STOP to exit
==================================================
[TEST] Testing CoAP GET...
[CoAP] Sending GET request
[CoAP] URI: coap://coap.me/test
[CoAP] Packet size: 11 bytes
[CoAP] ✓ Request sent
[CoAP] ✓ Response received (15 bytes)
[CoAP] Response Code: 2.05
[CoAP] Payload: welcome to the ETSI plugtest!
==================================================
Sending Message #1
==================================================
[SENSOR] ✓ Temp=24.5°C, Hum=55.3%
[CoAP] Sending POST request
[CoAP] URI: coap://coap.me/sensor
[CoAP] Payload: temp=24.5&hum=55.3
[CoAP] Packet size: 38 bytes
[CoAP] ✓ Request sent
[CoAP] ✓ ACK received (12 bytes)
[CoAP] Response Code: 2.01
[CoAP] ✓ POST successful!