53  CoAP Implementation Labs

In 60 Seconds

This lab chapter provides working code for CoAP servers and clients: Python implementations using aiocoap with async resource handlers, Arduino ESP32 implementations using the coap-simple library, and techniques for handling GET/PUT/observe requests and parsing response codes. Both platforms demonstrate RESTful IoT communication patterns ready to adapt for your own sensor and actuator projects.

53.1 Learning Objectives

By the end of this chapter, you will be able to:

  • Construct CoAP Servers: Implement Python CoAP servers with async resource handlers using aiocoap, registering GET, PUT, and POST endpoints
  • Develop CoAP Clients: Create Python clients that send GET, PUT, POST, and Observe requests and process response payloads
  • Configure Embedded CoAP: Adapt and deploy CoAP client code on Arduino ESP32 using the coap-simple library
  • Distinguish Response Codes: Interpret CoAP Class.Detail response codes (2.xx, 4.xx, 5.xx) and select the correct code for each operation
  • Diagnose Token vs MID Correlation: Explain why Token-based matching is required for Observe notifications and justify its use over Message ID matching
  • Calculate Protocol Overhead: Compare CoAP and HTTP message sizes and assess the energy savings for battery-powered IoT deployments

Key Concepts

  • CoAP: Constrained Application Protocol — REST-style request/response protocol using UDP instead of TCP
  • Confirmable Message (CON): Requires ACK from recipient — provides reliable delivery over UDP at the cost of one roundtrip
  • Non-confirmable Message (NON): Fire-and-forget UDP datagram — lowest latency, no delivery guarantee
  • Observe Option: CoAP extension enabling publish/subscribe: client registers to receive notifications on resource changes
  • Block-wise Transfer: Fragmentation mechanism for transferring payloads larger than a single CoAP datagram
  • Token: Client-generated value matching responses to requests — enables concurrent request/response pairing
  • DTLS: Datagram TLS — CoAP’s security layer providing encryption and authentication over UDP

53.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:

Practical Development:

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

53.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.py

CoAP’s efficiency shines in power-constrained deployments. Consider a battery-powered temperature sensor using this server pattern:

Message overhead analysis:

  • CoAP request: 4-byte header + 2-byte token + 6-byte URI (/temp) + 1-byte marker = 13 bytes
  • Response: 4-byte header + 2-byte token + 5-byte payload (22.5) = 11 bytes
  • Total exchange: 24 bytes

Compare to HTTP: - HTTP request: GET /temp HTTP/1.1\r\nHost: server\r\n\r\n = ~45 bytes - Response: HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n22.5 = ~52 bytes - Total: 97 bytes (4× larger)

Radio transmission time (250 kbps LoRa): - CoAP: \(t_{CoAP} = \frac{24 \times 8}{250,000} = 0.768\) ms - HTTP: \(t_{HTTP} = \frac{97 \times 8}{250,000} = 3.104\) ms

Battery impact (CR2032, 225 mAh @ 3V): - CoAP radio cost: 0.768 ms × 20 mA = 4.27 µAh per reading - HTTP radio cost: 3.104 ms × 20 mA = 17.24 µAh per reading - At 1 reading/minute: CoAP daily energy = 6,149 µAh/day vs HTTP = 24,826 µAh/day - Battery life difference: CoAP runs ~4× longer on same battery (≈36.6 days vs ≈9.1 days)

Interactive Calculator: CoAP vs HTTP Message Overhead

Try This:

  • Adjust payload size to see how overhead percentage changes
  • Notice how CoAP’s fixed 4-byte header maintains efficiency even with small payloads
  • Compare with/without HTTP headers to understand real-world savings
Interactive Calculator: Battery Life Estimator

Try This:

  • Increase data rate to see how frequent transmissions drain battery faster
  • Compare different battery capacities (CR2032 vs larger cells)
  • Notice how radio efficiency impacts both protocols equally, but CoAP’s smaller messages still win
Interactive Tool: CoAP Response Code Lookup

Try This:

  • Select different response codes to understand when each is used
  • Notice the pattern: 2.xx = success, 4.xx = client error, 5.xx = server error
  • Compare CoAP codes to their HTTP equivalents

53.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())
Try It: CoAP Resource Tree Explorer

Preview how a URI path appears in the CoAP resource tree and how it maps to root.add_resource() in Python.

53.4 Python CoAP Client

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

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

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

53.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())
Try It: Observe Pattern Timeline Simulator

Visualize the message exchange sequence between a CoAP client and server during an Observe subscription. Adjust parameters to see how CON/NON message types and notification intervals affect the communication pattern.

Objective: Build a CoAP-like RESTful sensor server on ESP32 that demonstrates GET, PUT, and Observe patterns – the same request/response semantics used by the Python examples above, running directly on a microcontroller.

Paste this code into the Wokwi editor:

#include <WiFi.h>

// Simulated CoAP resource registry
struct CoapResource {
  const char* path;
  float value;
  bool observable;
  int observeCount;
};

CoapResource resources[] = {
  {"/temperature", 22.5, true, 0},
  {"/humidity", 65.0, true, 0},
  {"/config/interval", 60.0, false, 0},
  {"/led", 0.0, false, 0}
};
const int NUM_RESOURCES = 4;

// CoAP response codes (RFC 7252)
const char* COAP_205_CONTENT = "2.05 Content";
const char* COAP_204_CHANGED = "2.04 Changed";
const char* COAP_201_CREATED = "2.01 Created";
const char* COAP_404_NOT_FOUND = "4.04 Not Found";
const char* COAP_405_NOT_ALLOWED = "4.05 Method Not Allowed";

unsigned long lastObserve = 0;
int messageId = 1000;

void setup() {
  Serial.begin(115200);
  delay(1000);

  Serial.println("=== CoAP RESTful Sensor Server ===");
  Serial.println("Demonstrating CoAP methods on ESP32\n");

  // Show resource discovery (/.well-known/core)
  discoverResources();

  // Demonstrate GET requests
  Serial.println("\n--- GET Requests ---");
  coapGet("/temperature");
  coapGet("/humidity");
  coapGet("/config/interval");
  coapGet("/nonexistent");

  // Demonstrate PUT requests
  Serial.println("\n--- PUT Requests (Update Resources) ---");
  coapPut("/config/interval", 30.0);
  coapPut("/led", 1.0);
  coapGet("/config/interval");  // Verify change

  // Demonstrate POST (create reading)
  Serial.println("\n--- POST Request (Submit Reading) ---");
  coapPost("/readings", 23.7);

  // Demonstrate Observe pattern
  Serial.println("\n--- Observe Pattern (Subscribe to Updates) ---");
  Serial.println("Registering observer for /temperature...");
  Serial.println("MID=" + String(messageId++) + " Token=0xAB | Observe=0 (Register)");
  Serial.println("Response: " + String(COAP_205_CONTENT) + " | Observe=1\n");

  Serial.println("Sending notifications every 2 seconds...\n");
}

void loop() {
  // Simulate Observe notifications
  if (millis() - lastObserve > 2000) {
    lastObserve = millis();

    // Update sensor values with drift
    resources[0].value += random(-10, 11) * 0.1;
    resources[1].value += random(-5, 6) * 0.1;
    resources[0].observeCount++;

    Serial.printf("Observe #%d | MID=%d | /temperature = %.1f C\n",
                  resources[0].observeCount, messageId++,
                  resources[0].value);

    // Show CON vs NON comparison
    if (resources[0].observeCount % 5 == 0) {
      Serial.println("  ^ CON (Confirmable) - requires ACK");
      Serial.println("    Client ACK received (MID=" + String(messageId - 1) + ")");
    } else {
      Serial.println("  ^ NON (Non-confirmable) - no ACK needed");
    }

    // Show message size comparison
    if (resources[0].observeCount == 3) {
      Serial.println("\n--- Protocol Overhead Comparison ---");
      Serial.println("CoAP message: 4B header + 2B token + 8B options + 4B payload = 18 bytes");
      Serial.println("HTTP equivalent: ~200 bytes (GET /temperature HTTP/1.1 + headers)");
      Serial.printf("CoAP efficiency: %.0f%% less overhead\n\n",
                    (1.0 - 18.0 / 200.0) * 100);
    }

    if (resources[0].observeCount >= 10) {
      Serial.println("\nDeregistering observer (Observe=1)...");
      Serial.println("Observer removed. Server stops notifications.\n");
      Serial.println("=== CoAP Demo Complete ===");
      while (1) delay(1000);
    }
  }
}

void discoverResources() {
  Serial.println("GET /.well-known/core (Resource Discovery)");
  Serial.println("Response: " + String(COAP_205_CONTENT));
  Serial.println("Content-Format: application/link-format\n");
  for (int i = 0; i < NUM_RESOURCES; i++) {
    Serial.printf("  <%s>;rt=\"sensor\";if=\"core.s\"",
                  resources[i].path);
    if (resources[i].observable) Serial.print(";obs");
    Serial.println();
  }
}

CoapResource* findResource(const char* path) {
  for (int i = 0; i < NUM_RESOURCES; i++) {
    if (strcmp(resources[i].path, path) == 0) return &resources[i];
  }
  return NULL;
}

void coapGet(const char* path) {
  Serial.printf("GET %s | MID=%d\n", path, messageId++);
  CoapResource* res = findResource(path);
  if (res) {
    Serial.printf("  Response: %s | %.1f\n", COAP_205_CONTENT, res->value);
  } else {
    Serial.printf("  Response: %s\n", COAP_404_NOT_FOUND);
  }
}

void coapPut(const char* path, float value) {
  Serial.printf("PUT %s | MID=%d | Payload: %.1f\n", path, messageId++, value);
  CoapResource* res = findResource(path);
  if (res) {
    res->value = value;
    Serial.printf("  Response: %s | Updated to %.1f\n", COAP_204_CHANGED, value);
  } else {
    Serial.printf("  Response: %s\n", COAP_404_NOT_FOUND);
  }
}

void coapPost(const char* path, float value) {
  Serial.printf("POST %s | MID=%d | Payload: {temp: %.1f}\n",
                path, messageId++, value);
  Serial.printf("  Response: %s | Location: /readings/1\n", COAP_201_CREATED);
}

What to Observe:

  1. Resource Discovery (/.well-known/core) lists all available resources with attributes – this is how CoAP clients find endpoints without configuration
  2. Response Codes match HTTP semantics: 2.05 Content (200 OK), 2.04 Changed (204), 2.01 Created (201), 4.04 Not Found (404)
  3. Observe Pattern sends server-push notifications every 2 seconds – every 5th notification uses CON (Confirmable) requiring an ACK, the rest use NON (fire-and-forget)
  4. Overhead comparison shows CoAP uses ~18 bytes vs HTTP’s ~200 bytes for the same temperature reading – a 91% reduction ideal for constrained IoT devices

53.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();
}

53.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);
}

53.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);
}
Try It: CoAP Message Byte Builder

Construct a CoAP message byte-by-byte and see exactly how the 4-byte header is encoded. This helps you understand what the coap-simple library constructs behind the scenes when you call coap.get() or coap.put().

53.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")
Check Your Understanding: CoAP Response Codes

53.7 Basic Simulator: CoAP UDP Communication

Interactive Simulator: CoAP-Style 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)

Learning Points

What You’ll Observe:

  1. UDP Transport - Connectionless, lightweight communication
  2. 4-Byte Headers - Minimal overhead compared to HTTP
  3. Request/Response - RESTful pattern like HTTP GET
  4. Message IDs - Tracking requests and responses
  5. No Handshake - Direct communication without TCP overhead

53.8 Visual Reference: CoAP Message Structure

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

Diagram illustrating HTTP

This diagram compares header sizes: HTTP requires 200+ bytes of headers while CoAP achieves the same REST functionality with just 4 bytes.

Common Mistake: Misunderstanding CoAP Token vs Message ID Correlation

The Error: Developers new to CoAP often confuse Message ID (MID) and Token, attempting to match responses to requests using MID alone. This causes failures with Observe notifications and separate responses.

Why It Happens: HTTP developers expect a single request-response correlation mechanism. CoAP has two: MID for duplicate detection and Token for logical request-response matching.

Example of Failure:

# WRONG: Matching by Message ID only
request_mid = 12345
response = await get_response()
if response.message_id == request_mid:  # Fails with Observe!
    process(response)

The Fix: Always use Token for request-response correlation:

# CORRECT: Matching by Token
import secrets

# Client sends request with unique token
request_token = secrets.token_bytes(4)  # e.g., 0xAB12CD34
request = Message(code=Code.GET, uri='coap://sensor/temp')
request.token = request_token

# For Observe, server sends multiple responses with DIFFERENT MIDs
# but SAME token as original request
async for response in request_handle.observation:
    if response.token == request_token:  # Correct correlation
        print(f'Update: {response.payload}')

Why This Matters:

  1. Separate Responses: Server sends empty ACK (MID=12345) immediately, then data CON (MID=12346) later. Both share the same Token.
  2. Observe Notifications: Each notification has a new MID (12347, 12348, 12349…) but the original Token throughout the subscription lifetime.
  3. Concurrent Requests: Client sends multiple requests simultaneously. Responses may arrive out-of-order. Token uniquely identifies which request each response answers.

Real Production Impact: A building automation system lost 30% of sensor readings because the client discarded Observe notifications with “mismatched” MIDs. After fixing to use Token matching, all notifications were correctly correlated.

Try It: Token vs Message ID Correlation Visualizer

See why Token-based matching works but MID-based matching fails. This simulator shows concurrent requests and Observe notifications where MIDs change but Tokens remain constant.

Common Pitfalls

CON messages require an ACK roundtrip — on lossy networks with 20% packet loss, a 4-attempt retry with exponential backoff can delay responses by 45 seconds. Use NON for periodic telemetry where data freshness matters more than guaranteed delivery; reserve CON for actuation commands.

CoAP proxies cache GET responses based on Max-Age option — a sensor returning temperature with Max-Age=60 will serve cached values for 60 seconds even if the physical reading changes. Set Max-Age to match your data freshness requirement, not the default 60 seconds.

DTLS handshake (6-8 roundtrips) dominates latency for short-lived CoAP connections — repeatedly creating new DTLS sessions for each request adds 500-2000ms overhead. Use DTLS session resumption (RFC 5077) to reduce reconnection to 1 roundtrip after the initial handshake.

53.9 Concept Relationships

This implementation chapter bridges theory to practice:

Foundation Knowledge:

Programming Concepts:

Implementation Details:

  • Token matching for request-response correlation
  • Resource handler registration (render_get, render_put)
  • Observe pattern (observe=0 option, notification loop)
  • Response code selection (2.01 Created, 2.04 Changed, 2.05 Content)

Next Steps:

53.10 See Also

Python CoAP Resources:

ESP32 CoAP Resources:

Testing and Debugging:

Alternative Implementations:

53.11 What’s Next

Chapter Focus Why Read It
CoAP Advanced Features Lab Block-wise transfer, Observe with ESP32, resource discovery via /.well-known/core Build and run a full Wokwi simulation extending the patterns from this chapter
CoAP Methods and Patterns Design decisions for GET/POST/PUT/DELETE, CON vs NON tradeoffs Deepen your understanding of when and why each method and message type is chosen
CoAP Fundamentals and Architecture Message structure, token/MID fields, UDP transport layer Revisit the protocol internals now that you have seen them in working code
CoAP API Design REST resource modelling, URI design, Content-Format selection Apply production best practices to the servers and clients you have built here
DTLS and Security Datagram TLS for CoAP, OSCORE, Pre-Shared Keys Secure the CoAP servers you implemented in this chapter for real deployments
MQTT Implementation Labs Broker-based publish/subscribe with Python and ESP32 Compare broker-mediated MQTT patterns against CoAP’s direct request/response and Observe