%%{init: {'theme': 'base', 'themeVariables': {'primaryColor':'#E8F4F8','primaryTextColor':'#2C3E50','primaryBorderColor':'#16A085','lineColor':'#16A085','secondaryColor':'#FEF5E7'}}}%%
packet-beta
0-1: "Ver (2b)"
2-3: "Type (2b)"
4-7: "TKL (4b)"
8-15: "Code (8b)"
16-31: "Message ID (16b)"
32-39: "Token (0-8 bytes)"
40-63: "Options (variable)"
64-95: "Payload (variable)"
title CoAP Message Structure (4-byte header + options + payload)
1230 CoAP Implementation Labs
1230.1 Learning Objectives
By the end of this chapter, you will be able to:
- Build CoAP Servers: Develop Python CoAP servers with resource handlers using aiocoap
- Implement CoAP Clients: Create Python clients that send GET, PUT, and observe requests
- Port to Embedded: Adapt CoAP examples for Arduino ESP32 with the coap-simple library
- Handle Responses: Parse CoAP response codes and payloads correctly
- Debug CoAP Traffic: Use appropriate tools to inspect message flow
1230.2 Prerequisites
Before diving into this chapter, you should be familiar with:
- CoAP Methods and Patterns: Understanding of GET/POST/PUT/DELETE methods and CON/NON message types
- CoAP Fundamentals: Knowledge of CoAP message structure, tokens, and options
- Python asyncio basics: Familiarity with async/await syntax for the aiocoap examples
- Arduino development: Basic ESP32 programming for the embedded examples
CoAP Series: - CoAP Methods and Patterns - Tradeoffs and design decisions - CoAP Advanced Features Lab - Full ESP32 Wokwi simulation with observe/block - CoAP Comprehensive Review - Assessment and quiz
Practical Development: - Prototyping Hardware - ESP32 setup guide - Network Design - Testing tools - MQTT Implementation - Compare with MQTT patterns
If you can install Python packages: 1. Install aiocoap: pip install aiocoap 2. Run the server in one terminal 3. Run the client in another terminal 4. Observe the request/response in both consoles
If you cannot install software: - Read through the code to understand the patterns - Focus on the message structure and response codes - Use the Wokwi simulation in the Advanced Features Lab
Key code patterns to understand: - async def render_get() - How servers handle GET requests - Message(code=Code.GET, uri='coap://...') - How clients build requests - await protocol.request(request).response - How clients wait for responses
1230.3 Python CoAP Server
This server exposes a /temperature resource that returns a simulated reading:
import asyncio
from aiocoap import *
class TemperatureResource(resource.Resource):
"""Example resource for temperature readings"""
async def render_get(self, request):
"""Handle GET requests - returns current temperature"""
temperature = 22.5 # Simulated sensor reading
payload = f"{temperature}".encode('utf-8')
return Message(
code=Code.CONTENT, # 2.05 Content (success)
payload=payload,
content_format=0 # text/plain
)
async def render_put(self, request):
"""Handle PUT requests to update configuration"""
print(f'Received PUT: {request.payload}')
# In real implementation, parse and apply config
return Message(code=Code.CHANGED) # 2.04 Changed
async def main():
# Create CoAP server with resource tree
root = resource.Site()
# Add temperature resource at /temperature
root.add_resource(
['temperature'],
TemperatureResource()
)
# Start server on default CoAP port (5683)
await Context.create_server_context(root)
print("CoAP server started on port 5683")
print("Resources: /temperature (GET, PUT)")
# Keep server running
await asyncio.get_running_loop().create_future()
if __name__ == "__main__":
asyncio.run(main())Install and run:
pip install aiocoap
python coap_server.py1230.3.1 Adding Multiple Resources
class ConfigResource(resource.Resource):
"""Configuration resource with GET and PUT"""
def __init__(self):
super().__init__()
self.config = {"interval": 60, "threshold": 25.0}
async def render_get(self, request):
import json
payload = json.dumps(self.config).encode('utf-8')
return Message(
code=Code.CONTENT,
payload=payload,
content_format=50 # application/json
)
async def render_put(self, request):
import json
try:
new_config = json.loads(request.payload.decode('utf-8'))
self.config.update(new_config)
print(f'Config updated: {self.config}')
return Message(code=Code.CHANGED)
except Exception as e:
return Message(code=Code.BAD_REQUEST)
class ReadingsResource(resource.Resource):
"""POST-only resource to receive sensor readings"""
async def render_post(self, request):
import json
try:
reading = json.loads(request.payload.decode('utf-8'))
print(f'New reading: {reading}')
# Store reading in database...
return Message(code=Code.CREATED) # 2.01 Created
except Exception:
return Message(code=Code.BAD_REQUEST) # 4.00 Bad Request
# In main():
root.add_resource(['config'], ConfigResource())
root.add_resource(['readings'], ReadingsResource())1230.4 Python CoAP Client
1230.4.1 Basic GET Request
import asyncio
from aiocoap import *
async def get_temperature():
"""Fetch temperature from CoAP server"""
# Create CoAP client protocol
protocol = await Context.create_client_context()
# Build GET request
request = Message(
code=Code.GET,
uri='coap://localhost/temperature'
)
try:
# Send request and wait for response
response = await protocol.request(request).response
print(f'Response Code: {response.code}')
print(f'Temperature: {response.payload.decode("utf-8")}C')
except Exception as e:
print(f'Failed to fetch: {e}')
# Run client
asyncio.run(get_temperature())1230.4.2 PUT Request for Configuration
async def update_config():
"""Send PUT request to update configuration"""
protocol = await Context.create_client_context()
request = Message(
code=Code.PUT,
uri='coap://localhost/config',
payload=b'{"interval": 30}'
)
response = await protocol.request(request).response
print(f'Update result: {response.code}')
if response.code.is_successful():
print('Configuration updated successfully')
else:
print(f'Update failed: {response.code}')
asyncio.run(update_config())1230.4.3 POST Request to Send Data
import json
async def send_reading():
"""POST a sensor reading to the server"""
protocol = await Context.create_client_context()
reading = {
"sensor_id": "temp-001",
"temperature": 23.5,
"humidity": 65.2,
"timestamp": "2025-10-24T23:30:00Z"
}
request = Message(
code=Code.POST,
uri='coap://localhost/readings',
payload=json.dumps(reading).encode('utf-8')
)
request.opt.content_format = 50 # application/json
response = await protocol.request(request).response
if response.code == Code.CREATED:
print('Reading submitted successfully')
else:
print(f'Submission failed: {response.code}')
asyncio.run(send_reading())1230.4.4 Observe Pattern (Subscribe to Updates)
async def observe_temperature():
"""Subscribe to temperature updates using Observe"""
protocol = await Context.create_client_context()
request = Message(
code=Code.GET,
uri='coap://localhost/temperature',
observe=0 # Register for notifications
)
request_handle = protocol.request(request)
# Get initial response
response = await request_handle.response
print(f'Initial temperature: {response.payload.decode("utf-8")}C')
# Wait for notifications (runs until cancelled)
print('Waiting for updates...')
async for response in request_handle.observation:
print(f'Update: {response.payload.decode("utf-8")}C')
# In real code, add break condition or timeout
asyncio.run(observe_temperature())1230.5 Arduino ESP32 CoAP Client
This example uses the coap-simple library for ESP32:
#include <WiFi.h>
#include <coap-simple.h>
// WiFi credentials
const char* ssid = "YourWiFi";
const char* password = "YourPassword";
// CoAP client instance
Coap coap;
// Server details
IPAddress serverIP(192, 168, 1, 100);
int serverPort = 5683;
// Callback for CoAP responses
void callback_response(CoapPacket &packet, IPAddress ip, int port) {
// Extract payload
char payload[packet.payloadlen + 1];
memcpy(payload, packet.payload, packet.payloadlen);
payload[packet.payloadlen] = '\0';
Serial.print("Response from ");
Serial.print(ip);
Serial.print(": ");
Serial.println(payload);
// Check response code
Serial.print("Code: ");
Serial.print(packet.code >> 5); // Class (2=Success, 4=Client Error, 5=Server Error)
Serial.print(".");
Serial.println(packet.code & 0x1F); // Detail
}
void setup() {
Serial.begin(115200);
// Connect to WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
// Register response callback
coap.response(callback_response);
// Start CoAP client
coap.start();
}
void loop() {
// Send GET request every 10 seconds
static unsigned long lastRequest = 0;
if (millis() - lastRequest > 10000) {
int msgid = coap.get(serverIP, serverPort, "temperature");
Serial.print("GET /temperature sent, MID: ");
Serial.println(msgid);
lastRequest = millis();
}
// Process incoming responses
coap.loop();
}1230.5.1 ESP32 PUT Request Example
void sendConfig() {
// Build JSON payload
char payload[64];
snprintf(payload, sizeof(payload), "{\"interval\": %d}", 30);
// Send PUT request
int msgid = coap.put(
serverIP,
serverPort,
"config",
payload,
strlen(payload)
);
Serial.print("PUT /config sent, MID: ");
Serial.println(msgid);
}1230.5.2 ESP32 POST Request Example
void sendReading() {
// Read sensor (simulated)
float temperature = 22.5 + (random(-10, 10) / 10.0);
float humidity = 60.0 + (random(-50, 50) / 10.0);
// Build JSON payload
char payload[128];
snprintf(payload, sizeof(payload),
"{\"temp\": %.1f, \"hum\": %.1f}",
temperature, humidity);
// Send POST request
int msgid = coap.post(
serverIP,
serverPort,
"readings",
payload,
strlen(payload)
);
Serial.print("POST /readings sent: ");
Serial.println(payload);
}1230.6 Response Code Reference
CoAP response codes follow a Class.Detail format similar to HTTP:
| Code | Meaning | HTTP Equivalent |
|---|---|---|
| 2.01 | Created | 201 Created |
| 2.02 | Deleted | 200 OK (for DELETE) |
| 2.03 | Valid | 304 Not Modified |
| 2.04 | Changed | 200 OK (for PUT) |
| 2.05 | Content | 200 OK (for GET) |
| 4.00 | Bad Request | 400 Bad Request |
| 4.01 | Unauthorized | 401 Unauthorized |
| 4.04 | Not Found | 404 Not Found |
| 4.05 | Method Not Allowed | 405 Method Not Allowed |
| 5.00 | Internal Server Error | 500 Internal Server Error |
# Checking response codes in Python
if response.code.is_successful():
print("Success!")
elif response.code == Code.NOT_FOUND:
print("Resource not found")
elif response.code == Code.BAD_REQUEST:
print("Invalid request format")1230.7 Basic Simulator: CoAP UDP Communication
What This Simulates: ESP32 demonstrating CoAP’s lightweight UDP request/response pattern
CoAP Communication Pattern:
Client (ESP32) Server (Simulated)
| |
|------ GET Request --->| (UDP Port 5683)
| (4-byte header) |
| |
|<----- Response -------| (2.05 Content)
| (Temp: 23.5C) |
| |
How to Use: 1. Click Start Simulation 2. Watch Serial Monitor show UDP request/response cycle 3. Observe message types (CON, NON, ACK) 4. See CoAP-style resource addressing 5. Monitor round-trip times (RTT)
1230.8 Visual Reference: CoAP Message Structure
The 4-byte fixed header contains: - Ver: Version (always 1) - Type: CON(0), NON(1), ACK(2), RST(3) - TKL: Token length (0-8 bytes) - Code: Method (GET=1, POST=2, PUT=3, DELETE=4) or response code - Message ID: 16-bit identifier for matching requests/responses
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor':'#2C3E50','primaryTextColor':'#fff','primaryBorderColor':'#16A085','lineColor':'#16A085','secondaryColor':'#E67E22'}}}%%
graph TB
subgraph HTTP["HTTP Header (~200+ bytes)"]
H1["GET /temp HTTP/1.1"]
H2["Host: sensor.local"]
H3["User-Agent: Mozilla/5.0"]
H4["Accept: application/json"]
H5["Connection: keep-alive"]
end
subgraph CoAP["CoAP Header (4 bytes)"]
C1["Ver | Type | TKL (1 byte)"]
C2["Code (1 byte)"]
C3["Message ID (2 bytes)"]
end
HTTP -.->|"50x smaller"| CoAP
style HTTP fill:#7F8C8D,stroke:#2C3E50
style CoAP fill:#16A085,stroke:#2C3E50
This diagram compares header sizes: HTTP requires 200+ bytes of headers while CoAP achieves the same REST functionality with just 4 bytes.
1230.9 Summary
This chapter provided hands-on CoAP implementation examples:
- Python Server: Built async CoAP servers with
aiocoap, handling GET, PUT, POST requests viarender_*methods - Python Client: Created clients that send requests, handle responses, and use the Observe pattern for subscriptions
- ESP32 Arduino: Ported CoAP patterns to embedded devices using the
coap-simplelibrary - Response Codes: Understood the
Class.Detailformat (2.05 Content, 4.04 Not Found, etc.) - Message Structure: Visualized the 4-byte fixed header that makes CoAP 50x smaller than HTTP
1230.10 Knowledge Check
1230.11 What’s Next
In the next chapter, CoAP Advanced Features Lab, you’ll build a comprehensive ESP32 CoAP server with: - Observe pattern with automatic notifications - Block-wise transfer for large payloads (firmware updates) - Resource discovery via /.well-known/core - Full Wokwi simulation you can run in your browser
Key Takeaways: - aiocoap provides async Python CoAP with render_* handlers - Response codes use Class.Detail format (2.xx success, 4.xx client error, 5.xx server error) - ESP32 can run CoAP clients with the coap-simple library - Observe pattern uses observe=0 option to register for notifications