1. Apa Itu CoAP?
CoAP (Constrained Application Protocol) adalah protokol aplikasi ringan yang didefinisikan dalam RFC 7252, dirancang khusus untuk constrained devices dan constrained networks dalam ekosistem Internet of Things (IoT). CoAP menyediakan model request/response yang mirip dengan HTTP, tetapi dioptimasi untuk perangkat dengan RAM, CPU, dan daya baterai yang sangat terbatas.
Mengapa Tidak Langsung Pakai HTTP?
| Parameter | HTTP/1.1 | CoAP |
|---|---|---|
| Transport Layer | TCP | UDP |
| Overhead per request | ~200+ bytes header | ~10-20 bytes header |
| Parsing complexity | Text-based, mahal | Binary, sangat ringan |
| Minimum RAM | ~50-100 KB | ~10 KB |
| Multicast support | Tidak | Ya (built-in) |
| Asynchronous messaging | Tidak native | Confirmable/Non-confirmable |
| TLS/DTLS | TLS (TCP) | DTLS (UDP) |
| Port default | 80 / 443 | 5683 (CoAP) / 5684 (CoAPS) |
CoAP ideal untuk skenario di mana perangkat IoT perlu berkomunikasi dengan server/middleware melalui jaringan constrained (LPWAN, 6LoWPAN, BLE). Contoh: sensor suhu di pertanian, smart meter, wearable health monitor. Jika perangkat Anda sudah punya WiFi stabil dan resource cukup, MQTT atau HTTP mungkin lebih sesuai.
CoAP dalam Stack Protokol IoT
CoAP berada di layer aplikasi dan menggunakan UDP sebagai transport. Pada jaringan 6LoWPAN, CoAP dapat berjalan di atas IEEE 802.15.4 dengan bantuan adaptasi layer 6LoWPAN. Dengan demikian, CoAP dapat menjangkau perangkat di edge network yang sangat constrained.
┌─────────────────────────────────────┐ │ CoAP (Application) │ ← RFC 7252 ├─────────────────────────────────────┤ │ UDP │ ← Transport ├─────────────────────────────────────┤ │ IPv6 / 6LoWPAN │ ← Network ├─────────────────────────────────────┤ │ IEEE 802.15.4 / BLE │ ← Link └─────────────────────────────────────┘
2. Arsitektur & Message Model
CoAP menggunakan arsitektur client-server dengan komunikasi asynchronous. Setiap pesan CoAP dikirim dalam satu datagram UDP. CoAP mendefinisikan dua jenis pesan utama: Confirmable (CON) dan Non-confirmable (NON).
CoAP Message Types
| Type | Kode | Fungsi | Contoh Penggunaan |
|---|---|---|---|
| Confirmable (CON) | 0 | Pesan yang memerlukan acknowledgment | Request penting, data kritis |
| Non-confirmable (NON) | 1 | Pesan tanpa ACK, fire-and-forget | Telemetry berkala, sensor reading |
| Acknowledgment (ACK) | 2 | Konfirmasi penerimaan CON message | Response ke CON request |
| Reset (RST) | 3 | Menolak pesan, endpoint tidak mengenali context | Error handling |
CoAP Message Format
Setiap pesan CoAP memiliki header tetap 4 byte yang sangat ringkas dibandingkan HTTP header yang bisa ratusan byte:
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 (0-8 bytes, sesuai TKL) ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Options ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |1 1 1 1 1 1 1 1| Payload (jika ada) ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Ver = Version (2 bit, selalu 01 untuk CoAP v1) T = Type (2 bit: CON=0, NON=1, ACK=2, RST=3) TKL = Token Length (4 bit: 0-8 bytes) Code = Request/Response code (8 bit) MsgID = Message ID (16 bit, untuk deduplikasi)
CoAP URIs dan Content-Formats
CoAP menggunakan URI scheme coap:// (unencrypted) dan coaps:// (DTLS-encrypted). Format konten diidentifikasi dengan numeric Content-Format codes, bukan MIME types text:
# Contoh CoAP URIs coap://sensor.example.com/temperature coap://[::1]:5683/led/status coaps://gateway.local/humidity # Content-Format Codes (IANA registered) 0 - text/plain; charset=utf-8 40 - application/link-format 41 - application/xml 42 - application/octet-stream 47 - application/exi 50 - application/json 60 - application/cbor
3. Request/Response Model
Model request/response CoAP mirip dengan HTTP, menggunakan method yang sama: GET, POST, PUT, DELETE. Namun, request dan response tidak harus dalam satu siklus TCP — karena CoAP berjalan di atas UDP, ada mekanisme khusus untuk menangani reliability.
CoAP Method Codes
| Method | Code | Fungsi |
|---|---|---|
| GET | 0.01 | Mengambil resource representation |
| POST | 0.02 | Membuat resource baru atau menjalankan prosedur |
| PUT | 0.03 | Memperbarui atau membuat resource |
| DELETE | 0.04 | Menghapus resource |
| FETCH | 0.05 | Mengambil resource dengan request body (RFC 8132) |
| PATCH | 0.06 | Partial update (RFC 8132) |
| iPATCH | 0.07 | Idempotent partial update (RFC 8132) |
Response Codes
CoAP response codes menggunakan format class.detail (mirip HTTP status codes):
# Success Responses (2.xx) 2.01 - Created 2.02 - Deleted 2.03 - Valid (cache masih valid) 2.04 - Changed 2.05 - Content (equivalent HTTP 200 OK) # Client Error Responses (4.xx) 4.00 - Bad Request 4.01 - Unauthorized 4.02 - Bad Option 4.03 - Forbidden 4.04 - Not Found 4.05 - Method Not Allowed 4.06 - Not Acceptable 4.08 - Request Entity Incomplete 4.12 - Precondition Failed 4.13 - Request Entity Too Large 4.15 - Unsupported Content-Format # Server Error Responses (5.xx) 5.00 - Internal Server Error 5.01 - Not Implemented 5.02 - Bad Gateway 5.03 - Service Unavailable 5.04 - Gateway Timeout
Contoh Komunikasi CoAP
# === Skenario: Client membaca sensor suhu ===
Client Server
| |
|--- CON [0x7a21] GET /temperature --------> |
| |
|<-- ACK [0x7a21] 2.05 Content {"temp":23.5} |
| |
# === Skenario: Multicast discovery ===
Client (multicast ke ff02::1) Server A & B
| |
|--- NON [0x7a22] GET /.well-known/core ------->|
| |
|<-- NON [0x7a22] 2.05 Content [A's resources] |
|<-- NON [0x7a22] 2.05 Content [B's resources] |
# === Skenario: Reliable messaging dengan retransmit ===
Client Server
| |
|--- CON [0x1234] PUT /led Payload: "on" ---> |
| (timeout, no ACK received) |
|--- CON [0x1234] PUT /led Payload: "on" ---> | (retransmit)
| |
|<-- ACK [0x1234] 2.04 Changed |
CoAP implementasi reliability melalui Confirmable messages (CON) dengan exponential backoff retransmission. Jika CON message tidak mendapat ACK dalam ACK_TIMEOUT (default 2 detik), pesan di-retransmit hingga MAX_RETRANSMIT kali (default 4). Timeout dikalikan dengan ACK_RANDOM_FACTOR untuk menghindari retransmission storms.
4. Observe Pattern
Observe pattern (RFC 7641) memungkinkan client untuk mendaftar sebagai observer pada suatu resource. Server akan mengirimkan notifikasi setiap kali resource berubah, tanpa client perlu melakukan polling berkala. Ini sangat menghemat bandwidth dan baterai pada perangkat IoT.
Cara Kerja Observe
# Client mendaftar sebagai observer dengan menambahkan Observe option
Client Server
| |
|--- CON [0x7a21] GET /temperature |
| Observe: 0 (register) -----------------> |
| |
|<-- ACK [0x7a21] 2.05 Content |
| Observe: 1 {"temp": 23.5} |
| |
| ... suhu berubah ... |
| |
|<-- CON [0x7a22] 2.05 Content |
| Observe: 2 {"temp": 24.1} |
|--- ACK [0x7a22] ---------------------------> |
| |
| ... suhu berubah lagi ... |
| |
|<-- NON [0x7a23] 2.05 Content |
| Observe: 3 {"temp": 23.8} |
| |
Implementasi Observe di Python
import asyncio
from aiocoap import *
async def observe_temperature():
"""Register sebagai observer pada resource /temperature"""
protocol = await Context.create_client_context()
request = Message(
code=GET,
uri='coap://sensor.example.com/temperature',
observe=0 # Register sebagai observer
)
# Kirim request pertama dan dapatkan initial response
requester = protocol.request(request)
first_response = await requester.response
print(f"Suhu awal: {first_response.payload.decode()}")
# Loop untuk menerima notifikasi berikutnya
async for response in requester.observation:
print(f"Suhu berubah: {response.payload.decode()}")
# Unsubscribe jika sudah tidak perlu
if float(response.payload.decode()) > 30.0:
requester.observation.cancel()
break
asyncio.run(observe_temperature())
Observe Relationship Lifecycle
Server menyimpan daftar observer untuk setiap resource. Observer bisa berhenti berlangganan dengan dua cara: mengirim GET request dengan Observe option bernilai 1 (deregister), atau mengirim RST ke notifikasi terakhir dari server. Server juga bisa mengakhiri observe relationship jika resource dihapus atau terjadi error.
Dengan polling GET setiap 5 detik, perangkat IoT mengirim 17,280 request per hari. Dengan Observe, perangkat hanya mengirim 1 request registrasi dan menerima notifikasi saat ada perubahan. Pada sensor yang jarang berubah, ini bisa menghemat 99% bandwidth dan baterai.
5. Block Transfer
Ukuran payload default CoAP dibatasi oleh maximum UDP datagram size. Pada jaringan 6LoWPAN (IEEE 802.15.4), payload efektif hanya sekitar 60-80 bytes. Untuk mengatasi keterbatasan ini, CoAP mendefinisikan Block-Wise Transfer (RFC 7959).
Block Option
# Block Option encoding (variable length: 1-3 bytes) # Format: NUM.M.SIZE # # NUM = Block number (4/12/20 bit) — sequential numbering # M = More bit (1 bit) — 1 jika ada blok berikutnya, 0 jika terakhir # SIZE = Block size exponent (3 bit) — 2^(4+SZX) bytes # # Block Size mapping: # SZX=0 → 16 bytes SZX=4 → 256 bytes # SZX=1 → 32 bytes SZX=5 → 512 bytes # SZX=2 → 64 bytes SZX=6 → 1024 bytes # SZX=3 → 128 bytes SZX=7 → reserved # Contoh transfer file 1024 bytes dengan block size 256 bytes: # Block 0: NUM=0, M=1, SZX=4 → "0M4" = bytes 0-255 # Block 1: NUM=1, M=1, SZX=4 → "1M4" → bytes 256-511 # Block 2: NUM=2, M=1, SZX=4 → "2M4" → bytes 512-767 # Block 3: NUM=3, M=0, SZX=4 → "3_4" → bytes 768-1023 (terakhir)
Block-Wise Transfer Flow
Client Server | | |--- CON GET /firmware | | Block2: 0/0/6 (NUM=0, M=0, SZX=6) ---> | | (minta blok pertama, size 1024) | | | |<-- ACK 2.05 Content | | Block2: 0/1/6 (M=1, ada lagi) | | [bytes 0-1023] | | | |--- CON GET /firmware | | Block2: 1/0/6 -----------------------> | | | |<-- ACK 2.05 Content | | Block2: 1/1/6 | | [bytes 1024-2047] | | | |--- CON GET /firmware | | Block2: 2/0/6 -----------------------> | | | |<-- ACK 2.05 Content | | Block2: 2/0/6 (M=0, selesai) | | [bytes 2048-3000] |
Block1 untuk Upload
Selain Block2 untuk download, CoAP juga mendefinisikan Block1 untuk upload data besar ke server. Ini berguna untuk firmware update (OTA), upload konfigurasi, atau pengiriman batch data sensor.
import asyncio
from aiocoap import *
from aiocoap.numbers import ContentFormat
async def upload_firmware():
"""Upload firmware menggunakan Block1 transfer"""
context = await Context.create_client_context()
firmware_data = open('firmware.bin', 'rb').read()
block_size = 1024
total_blocks = (len(firmware_data) + block_size - 1) // block_size
for i in range(total_blocks):
start = i * block_size
end = min(start + block_size, len(firmware_data))
chunk = firmware_data[start:end]
request = Message(
code=PUT,
uri='coap://device.local/firmware',
payload=chunk,
content_format=ContentFormat.OCTETSTREAM
)
# Set Block1 option: NUM/M/SZX
is_last = (i == total_blocks - 1)
request.remote.opt.block1 = (i, not is_last, 6)
response = await context.request(request).response
print(f"Block {i+1}/{total_blocks}: {response.code}")
print("Upload selesai!")
asyncio.run(upload_firmware())
6. DTLS Security
Karena CoAP menggunakan UDP (bukan TCP), protokol keamanan standar TLS tidak bisa digunakan. Sebagai gantinya, CoAP menggunakan DTLS (Datagram Transport Layer Security) — versi TLS yang diadaptasi untuk datagram-oriented transport. CoAP dengan DTLS disebut CoAPS dan berjalan pada port 5684.
DTLS Mode pada CoAP
| Mode | Deskripsi | Kegunaan |
|---|---|---|
| NoSec | Tanpa keamanan, raw CoAP | Testing, trusted network |
| PreSharedKey (PSK) | Kunci simetris yang sudah diketahui sebelumnya | IoT device-to-gateway |
| RawPublicKey (RPK) | Pasangan kunci publik/privat tanpa sertifikat | Constrained devices |
| Certificate (X.509) | Sertifikat X.509 standar | Production, enterprise IoT |
DTLS Handshake pada CoAP
# DTLS 1.2 Handshake (mirip TLS tetapi di atas UDP) Client (IoT Device) Server (Gateway) | | |--- ClientHello --------------------> | |<-- HelloVerifyRequest (cookie) | |--- ClientHello + Cookie -----------> | |<-- ServerHello + Certificate | |<-- ServerKeyExchange | |<── ServerHelloDone | |--- ClientKeyExchange | |--- ChangeCipherSpec | |--- Finished ────────────────────────>| |<── ChangeCipherSpec | |<── Finished | | | |==== Encrypted CoAP messages =========>|
Implementasi CoAPS dengan PSK
import asyncio
from aiocoap import *
async def secure_request():
"""Mengirim CoAP request dengan DTLS (PreSharedKey)"""
context = await Context.create_client_context()
# Setup DTLS credentials
# Untuk PSK, client dan server harus share identity + key
# Implementation-specific setup via aiocoap credentials
request = Message(
code=GET,
uri='coaps://gateway.example.com:5684/sensors/data'
)
response = await context.request(request).response
print(f"Status: {response.code}")
print(f"Data: {response.payload.decode()}")
asyncio.run(secure_request())
DTLS menghadapi tantangan pada jaringan dengan NAT karena UDP tidak memiliki koneksi persistent seperti TCP. Solusinya adalah menggunakan DTLS Connection ID (CID) yang memungkinkan koneksi tetap hidup meski alamat IP/port berubah. Ini penting untuk perangkat IoT mobile atau yang sering sleep/wake.
7. CoAP vs MQTT vs HTTP
Memilih protokol yang tepat untuk proyek IoT bergantung pada karakteristik jaringan, perangkat, dan use case. Berikut perbandingan komprehensif:
| Aspek | CoAP | MQTT | HTTP/REST |
|---|---|---|---|
| Model | Request/Response + Observe | Publish/Subscribe | Request/Response |
| Transport | UDP | TCP | TCP |
| Overhead | Sangat rendah (4 byte header) | Rendah (2 byte minimum) | Tinggi (text headers) |
| Reliability | CON/ACK mechanism | QoS 0, 1, 2 | TCP built-in |
| Pattern | 1-to-1, 1-to-many (observe) | Many-to-many (broker) | 1-to-1 |
| Security | DTLS | TLS | TLS |
| Web Friendly | Ya (REST semantics) | Tidak langsung | Ya (native web) |
| Best For | Resource-constrained devices, RESTful IoT | Real-time messaging, telemetry | Web API, client-server |
8. Implementasi dengan libcoap
libcoap adalah library C yang populer untuk implementasi CoAP pada embedded systems. Library ini mendukung CoAP RFC 7252, Observe, Block Transfer, dan DTLS. Berikut contoh implementasi server CoAP sederhana:
CoAP Server dengan Python (aiocoap)
import asyncio
import json
import datetime
import aiocoap
import aiocoap.resource as resource
class TemperatureResource(resource.ObservableResource):
"""Resource /temperature dengan Observe support"""
def __init__(self):
super().__init__()
self.temperature = 23.5
self.notify()
def notify(self):
self.updated_state()
# Schedule next update setiap 10 detik
asyncio.get_event_loop().call_later(10, self.notify)
async def render_get(self, request):
data = json.dumps({
"temperature": self.temperature,
"unit": "celsius",
"timestamp": datetime.datetime.now().isoformat()
})
return aiocoap.Message(
payload=data.encode('utf-8'),
content_format=50 # application/json
)
async def render_put(self, request):
"""Update temperature via PUT"""
payload = json.loads(request.payload.decode())
self.temperature = payload.get("temperature", self.temperature)
return aiocoap.Message(
code=aiocoap.CHANGED,
payload=json.dumps({"status": "ok"}).encode()
)
class DeviceInfoResource(resource.Resource):
"""Resource /info — static device information"""
async def render_get(self, request):
info = {
"device": "ESP32-Sensor-01",
"firmware": "1.2.3",
"uptime": "24h 30m",
"battery": "87%"
}
return aiocoap.Message(
payload=json.dumps(info).encode(),
content_format=50
)
# Build resource tree
root = resource.Site()
root.add_resource(['temperature'], TemperatureResource())
root.add_resource(['info'], DeviceInfoResource())
root.add_resource(['.well-known', 'core'],
resource.WKCResource(root.get_resources_as_linkheader))
# Start CoAP server
asyncio.Task(aiocoap.Context.create_server_context(root))
asyncio.get_event_loop().run_forever()
CoAP Client di ESP32 (Arduino)
#include <WiFi.h>
#include <WiFiUdp.h>
#include <coap-simple.h>
const char* ssid = "MyNetwork";
const char* password = "MyPassword";
WiFiUDP udp;
Coap coap(udp);
// Callback saat menerima response
void callback_response(CoapPacket &packet, IPAddress ip, int port) {
char p[packet.payloadlen + 1];
memcpy(p, packet.payload, packet.payloadlen);
p[packet.payloadlen] = '\0';
Serial.print("Response dari ");
Serial.print(ip);
Serial.print(": ");
Serial.println(p);
}
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("WiFi connected!");
// Register response callback
coap.response(callback_response);
// Start CoAP client
coap.start();
// Kirim GET request ke server CoAP
coap.get(IPAddress(192, 168, 1, 100), 5683, "temperature");
}
void loop() {
coap.loop();
// Kirim request setiap 30 detik
static unsigned long lastTime = 0;
if (millis() - lastTime > 30000) {
lastTime = millis();
coap.get(IPAddress(192, 168, 1, 100), 5683, "temperature");
}
}
- libcoap (C) — Library C mature, banyak digunakan di embedded
- aiocoap (Python) — Async Python implementation, bagus untuk prototyping
- Eclipse Californium (Java) — Enterprise-grade Java implementation
- CoAP.js (Node.js) — JavaScript implementation untuk server dan client
- libcoap-minimal (C) — Untuk microcontroller sangat constrained