58  CoAP Review: Labs and Implementation

In 60 Seconds

This hands-on lab builds a complete CoAP environmental monitoring station on ESP32: a CoAP server exposing temperature and humidity resources from a DHT22 sensor, LED control via PUT requests, and the Observe pattern for real-time sensor updates. The lab integrates hardware wiring, CoAP resource endpoint design, and RESTful URI structure for a practical IoT system.

58.1 Learning Objectives

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

  • Construct CoAP Servers: Implement CoAP resources on ESP32 for sensor monitoring and actuator control
  • Design Resource Endpoints: Configure RESTful URI structures for temperature, humidity, and LED control
  • Apply the Observe Pattern: Implement CoAP observe mode to deliver real-time sensor updates to registered clients
  • Diagnose CoAP Communication: Analyze request/response exchanges using tools to identify and resolve protocol errors
  • Evaluate Content Negotiation Strategies: Compare CBOR and JSON payload formats and justify format selection for constrained versus web clients
  • Demonstrate Hardware Integration: Construct complete IoT systems by wiring DHT22 sensors and LEDs to ESP32 with correct pin assignments
  • 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

58.2 For Beginners: CoAP Labs Review

This review brings together all the hands-on CoAP labs and implementation exercises. It is a chance to revisit the key skills you have practiced – setting up CoAP servers, making requests, handling observations – and solidify your practical experience with this important IoT protocol.

58.3 Prerequisites

Required Reading:

Estimated Time: 45 minutes

This is Part 3 of the CoAP Comprehensive Review:

  1. Protocol Fundamentals - Message types, architecture, comparisons
  2. Observe Patterns - Push notifications, subscriptions
  3. Labs and Implementation (this chapter) - Hands-on ESP32 and Python
  4. Knowledge Assessment - Quizzes and scenario questions

58.4 Lab 1: ESP32 CoAP Environmental Monitoring Station

Build a complete CoAP server on ESP32 with DHT22 temperature/humidity sensor and LED control.

Hardware Required:

  • ESP32 Development Board
  • DHT22 Temperature/Humidity Sensor
  • LED (any color)
  • 220 Ohm Resistor
  • 10k Ohm Pull-up resistor (for DHT22)

Wiring:

Component ESP32 Pin
DHT22 VCC 3.3V
DHT22 DATA GPIO 4 (with 10k pullup to 3.3V)
DHT22 GND GND
LED Anode (+) GPIO 2 (via 220 Ohm resistor)
LED Cathode (-) GND

Complete Code:

#include <WiFi.h>
#include <coap-simple.h>
#include <DHT.h>

// WiFi credentials
const char* ssid = "YourWiFi";
const char* password = "YourPassword";

// DHT sensor configuration
#define DHTPIN 4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

// LED configuration
#define LED_PIN 2
bool ledState = false;

// CoAP server
Coap coap;

// Sensor readings (cached)
float currentTemp = 0.0;
float currentHumidity = 0.0;
unsigned long lastSensorRead = 0;
const unsigned long SENSOR_INTERVAL = 5000;  // Read every 5 seconds

// Forward declarations
void callback_temperature(CoapPacket &packet, IPAddress ip, int port);
void callback_humidity(CoapPacket &packet, IPAddress ip, int port);
void callback_led_get(CoapPacket &packet, IPAddress ip, int port);
void callback_led_put(CoapPacket &packet, IPAddress ip, int port);
void callback_discovery(CoapPacket &packet, IPAddress ip, int port);

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

  Serial.println("\n=== ESP32 CoAP Environmental Station ===");

  // Initialize LED
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);

  // Initialize DHT sensor
  dht.begin();
  Serial.println("DHT22 initialized");

  // Connect to WiFi
  Serial.print("Connecting 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());

  // Setup CoAP server resources
  coap.server(callback_discovery, "discovery");  // /.well-known/core
  coap.server(callback_temperature, "temperature");  // /temperature
  coap.server(callback_humidity, "humidity");        // /humidity
  coap.server(callback_led_get, "led");             // GET /led
  coap.server(callback_led_put, "led");             // PUT /led

  coap.start();

  Serial.println("\nCoAP server started on port 5683");
  Serial.println("\nAvailable resources:");
  Serial.println("  GET  coap://" + WiFi.localIP().toString() + "/temperature");
  Serial.println("  GET  coap://" + WiFi.localIP().toString() + "/humidity");
  Serial.println("  GET  coap://" + WiFi.localIP().toString() + "/led");
  Serial.println("  PUT  coap://" + WiFi.localIP().toString() + "/led");
  Serial.println("  GET  coap://" + WiFi.localIP().toString() + "/.well-known/core");
  Serial.println("\nReady for requests!");
}

void loop() {
  // Read sensor periodically
  unsigned long now = millis();
  if (now - lastSensorRead >= SENSOR_INTERVAL) {
    lastSensorRead = now;

    currentTemp = dht.readTemperature();
    currentHumidity = dht.readHumidity();

    if (isnan(currentTemp) || isnan(currentHumidity)) {
      Serial.println("Failed to read from DHT sensor!");
    }
  }

  // Process CoAP requests
  coap.loop();

  delay(10);
}

// Resource discovery (/.well-known/core)
void callback_discovery(CoapPacket &packet, IPAddress ip, int port) {
  Serial.println("\n-> CoAP Request: GET /.well-known/core");

  String response = "</temperature>;ct=0;rt=\"sensor\","
                   "</humidity>;ct=0;rt=\"sensor\","
                   "</led>;ct=0;rt=\"actuator\";if=\"light\"";

  coap.sendResponse(ip, port, packet.messageid, response.c_str());

  Serial.println("<- Response: 2.05 CONTENT");
  Serial.println("   Resources: " + String(3));
}

// Temperature resource
void callback_temperature(CoapPacket &packet, IPAddress ip, int port) {
  Serial.println("\n-> CoAP Request: GET /temperature");

  // Build JSON response
  String json = "{";
  json += "\"value\":" + String(currentTemp, 1) + ",";
  json += "\"unit\":\"celsius\",";
  json += "\"timestamp\":" + String(millis());
  json += "}";

  coap.sendResponse(ip, port, packet.messageid, json.c_str());

  Serial.println("<- Response: 2.05 CONTENT");
  Serial.println("   Temperature: " + String(currentTemp, 1) + " C");
}

// Humidity resource
void callback_humidity(CoapPacket &packet, IPAddress ip, int port) {
  Serial.println("\n-> CoAP Request: GET /humidity");

  // Build JSON response
  String json = "{";
  json += "\"value\":" + String(currentHumidity, 1) + ",";
  json += "\"unit\":\"percent\",";
  json += "\"timestamp\":" + String(millis());
  json += "}";

  coap.sendResponse(ip, port, packet.messageid, json.c_str());

  Serial.println("<- Response: 2.05 CONTENT");
  Serial.println("   Humidity: " + String(currentHumidity, 1) + "%");
}

// LED GET resource
void callback_led_get(CoapPacket &packet, IPAddress ip, int port) {
  if (packet.code == COAP_GET) {
    Serial.println("\n-> CoAP Request: GET /led");

    String json = "{";
    json += "\"state\":\"" + String(ledState ? "on" : "off") + "\",";
    json += "\"pin\":" + String(LED_PIN);
    json += "}";

    coap.sendResponse(ip, port, packet.messageid, json.c_str());

    Serial.println("<- Response: 2.05 CONTENT");
    Serial.println("   LED State: " + String(ledState ? "ON" : "OFF"));
  }
}

// LED PUT resource
void callback_led_put(CoapPacket &packet, IPAddress ip, int port) {
  if (packet.code == COAP_PUT) {
    Serial.println("\n-> CoAP Request: PUT /led");

    // Parse payload
    String payload = String((char*)packet.payload);
    payload = payload.substring(0, packet.payloadlen);

    Serial.println("   Payload: " + payload);

    // Extract state from JSON
    if (payload.indexOf("\"on\"") >= 0 || payload.indexOf("true") >= 0) {
      ledState = true;
      digitalWrite(LED_PIN, HIGH);
    } else if (payload.indexOf("\"off\"") >= 0 || payload.indexOf("false") >= 0) {
      ledState = false;
      digitalWrite(LED_PIN, LOW);
    }

    String response = "{\"success\":true,\"state\":\"" +
                      String(ledState ? "on" : "off") + "\"}";

    coap.sendResponse(ip, port, packet.messageid, response.c_str(),
                     strlen(response.c_str()), COAP_CHANGED);

    Serial.println("<- Response: 2.04 CHANGED");
    Serial.println("   LED turned " + String(ledState ? "ON" : "OFF"));
  }
}
Try It: ESP32 CoAP Server Simulator

Experience CoAP in action! This simulator demonstrates ESP32 with sensors, showing how CoAP requests work with RESTful IoT resources. Watch the Serial Monitor for request/response logs.

What to Observe:

  • CoAP Resources: /temperature, /humidity, /led endpoints
  • Request Methods: GET for reading sensors, PUT for controlling LED
  • Resource Discovery: /.well-known/core returns available endpoints
  • Compare to HTTP: Notice the simpler message format (4-byte header vs HTTP’s text headers)

Install Library:

# Arduino IDE: Sketch -> Include Library -> Manage Libraries
# Search for: "CoAP simple ESP32" and "DHT sensor library"

Testing with Python Client:

import asyncio
from aiocoap import *

async def test_esp32_coap():
    """Test ESP32 CoAP server"""

    ESP32_IP = "192.168.1.100"  # Replace with your ESP32 IP

    protocol = await Context.create_client_context()

    # 1. Discovery
    print("1. Resource Discovery:")
    request = Message(code=Code.GET, uri=f'coap://{ESP32_IP}/.well-known/core')
    response = await protocol.request(request).response
    print(f"   {response.payload.decode('utf-8')}\n")

    # 2. Get Temperature
    print("2. Get Temperature:")
    request = Message(code=Code.GET, uri=f'coap://{ESP32_IP}/temperature')
    response = await protocol.request(request).response
    print(f"   {response.payload.decode('utf-8')}\n")

    # 3. Get Humidity
    print("3. Get Humidity:")
    request = Message(code=Code.GET, uri=f'coap://{ESP32_IP}/humidity')
    response = await protocol.request(request).response
    print(f"   {response.payload.decode('utf-8')}\n")

    # 4. Turn LED ON
    print("4. Turn LED ON:")
    request = Message(code=Code.PUT, uri=f'coap://{ESP32_IP}/led',
                     payload=b'{"state":"on"}')
    response = await protocol.request(request).response
    print(f"   {response.payload.decode('utf-8')}\n")

    await asyncio.sleep(2)

    # 5. Turn LED OFF
    print("5. Turn LED OFF:")
    request = Message(code=Code.PUT, uri=f'coap://{ESP32_IP}/led',
                     payload=b'{"state":"off"}')
    response = await protocol.request(request).response
    print(f"   {response.payload.decode('utf-8')}\n")

asyncio.run(test_esp32_coap())

Expected Output (Serial Monitor):

=== ESP32 CoAP Environmental Station ===
DHT22 initialized
Connecting to WiFi.....
WiFi connected
IP Address: 192.168.1.100

CoAP server started on port 5683

Available resources:
  GET  coap://192.168.1.100/temperature
  GET  coap://192.168.1.100/humidity
  GET  coap://192.168.1.100/led
  PUT  coap://192.168.1.100/led
  GET  coap://192.168.1.100/.well-known/core

Ready for requests!

-> CoAP Request: GET /temperature
<- Response: 2.05 CONTENT
   Temperature: 23.5 C

-> CoAP Request: PUT /led
   Payload: {"state":"on"}
<- Response: 2.04 CHANGED
   LED turned ON

58.5 Lab 2: Python CoAP Smart Home Automation Server

Build a complete smart home automation server with multiple rooms and devices.

Testing the Server:

# Terminal 1: Start server
python3 smart_home_server.py

# Terminal 2: Test with coap-client
# Install: sudo apt-get install libcoap2-bin

# 1. Discovery
coap-client -m get coap://localhost/.well-known/core

# 2. Dashboard
coap-client -m get coap://localhost/dashboard

# 3. Get light state
coap-client -m get coap://localhost/devices/light_living

# 4. Turn light on
coap-client -m put coap://localhost/devices/light_living -e '{"power":true,"brightness":80}'

# 5. Set thermostat
coap-client -m put coap://localhost/devices/thermostat_main -e '{"target_temp":24.0}'

# 6. Turn off all lights in living room
coap-client -m put coap://localhost/rooms/Living_Room -e '{"all_lights":"off"}'

Expected Output:

=== CoAP Smart Home Automation Server ===

Server started on coap://localhost:5683

Available resources:
  GET  coap://localhost/.well-known/core
  GET  coap://localhost/dashboard

Devices:
  GET/PUT coap://localhost/devices/light_living (Living Room)
  GET/PUT coap://localhost/devices/light_bedroom (Bedroom)
  GET/PUT coap://localhost/devices/light_kitchen (Kitchen)
  GET/PUT coap://localhost/devices/thermostat_main (Living Room)

Rooms:
  GET/PUT coap://localhost/rooms/Living_Room
  GET/PUT coap://localhost/rooms/Bedroom
  GET/PUT coap://localhost/rooms/Kitchen

Example commands:
  coap-client -m get coap://localhost/dashboard
  coap-client -m get coap://localhost/devices/light_living
  coap-client -m put coap://localhost/devices/light_living -e '{"power":true}'
  coap-client -m put coap://localhost/rooms/Living_Room -e '{"all_lights":"off"}'

58.6 Worked Example: REST API Design with CoAP Content Negotiation

Worked Example: REST API Design with CoAP Content Negotiation

Scenario: A smart irrigation system exposes sensor data that must be consumed by both constrained devices (CBOR format, low bandwidth) and web dashboards (JSON format, human-readable). Design the CoAP API with proper content negotiation.

Given:

  • Resource: /sensors/soil-moisture
  • Supported formats: CBOR (ct=60), JSON (ct=50), plain text (ct=0)
  • Sensor readings: moisture percentage, battery voltage, timestamp
  • Constrained client: 6LoWPAN, 127-byte MTU
  • Web client: HTTP-CoAP proxy, prefers JSON

Steps:

  1. Define response payloads by content type:

    JSON (ct=50) - 89 bytes:

    {"moisture":45.2,"battery":3.21,"ts":"2025-01-12T10:30:00Z"}

    CBOR (ct=60) - 44 bytes:

    A3                      # map(3)
      68 6D6F6973747572    # "moisture" (8-char key)
      FB 4046999999999A00  # 45.2 (float64)
      67 62617474657279    # "battery" (7-char key)
      FB 400B70A3D70A3D71  # 3.21 (float64)
      62 7473              # "ts" (2-char key)
      1A 678A2430          # 1737193520 (uint32)

    Plain text (ct=0) - 22 bytes:

    45.2%,3.21V,1737193520
  2. Calculate message sizes with headers:

    Format Payload Header+Options Total Fits 127B MTU?
    JSON 89 B 12 B 101 B Yes
    CBOR 44 B 12 B 56 B Yes
    Text 22 B 12 B 34 B Yes
  3. Handle Accept option in request:

    Request 1 (constrained device):
    GET /sensors/soil-moisture
    Accept: 60 (CBOR)
    
    Response: 2.05 Content
    Content-Format: 60
    Payload: [44 bytes CBOR]
    
    Request 2 (web proxy):
    GET /sensors/soil-moisture
    Accept: 50 (JSON)
    
    Response: 2.05 Content
    Content-Format: 50
    Payload: [89 bytes JSON]
    
    Request 3 (no Accept option):
    GET /sensors/soil-moisture
    
    Response: 2.05 Content
    Content-Format: 60 (server default: most efficient)
    Payload: [44 bytes CBOR]
  4. Handle unsupported format request:

    Request:
    GET /sensors/soil-moisture
    Accept: 110 (XML - not supported)
    
    Response: 4.06 Not Acceptable
    Payload: "Supported formats: 0, 50, 60"

Result: The API serves the same resource in three formats based on client preference. Constrained devices receive 44-byte CBOR (51% smaller than JSON), while web dashboards get human-readable JSON. The server defaults to CBOR for maximum efficiency when no preference is specified.

Key Insight: Content negotiation via Accept option enables single-resource APIs that serve diverse clients efficiently. CBOR reduces payload size by 50-70% compared to JSON with equivalent data. Always define a server default format (prefer efficient formats like CBOR) and return 4.06 Not Acceptable with supported options for unsupported requests.

Content negotiation becomes critical when bandwidth cost scales with device count. Calculate the yearly transmission cost for 1,000 soil moisture sensors.

$ = N_{} R_{} P_{} C_{} $

Worked example: A LoRaWAN agricultural deployment with 1,000 sensors reporting every hour (24 messages/day). Carrier charges $5 per MB for uplink data.

JSON format (89 bytes per reading): Annual cost = 1,000 × 24 msg/day × 365 × 89 bytes × ($5 / 1,048,576 bytes) = $3,717.61/year.

CBOR format (44 bytes per reading): Annual cost = 1,000 × 24 msg/day × 365 × 44 bytes × ($5 / 1,048,576 bytes) = $1,837.92/year.

Savings = $3,717.61 − $1,837.92 = $1,879.69/year (51% reduction). Over 10-year sensor lifetime: $18,797 saved by using content negotiation to serve CBOR to constrained devices.

58.7 Visual Reference Gallery

CoAP message flow showing request-response pattern over UDP with confirmable messages achieving reliability through application-layer acknowledgments, including GET, PUT, POST, and DELETE methods with message ID matching and token correlation

CoAP Message Flow showing request-response pattern

This diagram illustrates CoAP’s request-response pattern over UDP, showing how confirmable messages achieve reliability through application-layer acknowledgments rather than TCP’s transport-layer guarantees.

CoAP block transfer mechanism for large payloads showing data splitting into individually acknowledged blocks with block numbering, size negotiation, and more-flag indicating continuation, enabling firmware updates over constrained networks with small MTU limits

CoAP Block Transfer for large payloads

Block transfer enables reliable large payload transmission (firmware updates, bulk data) over constrained networks with small MTU limits by splitting data into individually acknowledged blocks.

CoAP server architecture

CoAP Server with resource handlers

Understanding CoAP server architecture helps developers design efficient resource hierarchies for IoT devices, organizing sensors and actuators into logical RESTful endpoints.

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.

58.8 Summary

This chapter covered CoAP implementation and testing through comprehensive hands-on labs:

  • ESP32 CoAP Server: Built environmental monitoring station with DHT22 sensor, implementing GET/PUT endpoints for temperature, humidity, and LED control with resource discovery
  • Python Smart Home Server: Developed complete automation system with multiple device types (lights, thermostats), room-level control, and dashboard aggregation
  • Content Negotiation: Designed APIs that serve CBOR (51% smaller in this example) for constrained devices and JSON for web dashboards
  • RESTful Resource Design: Applied HTTP-like semantics (GET for sensors, PUT for actuators) to CoAP endpoints

58.9 Knowledge Check

Common Mistake: Forgetting to Handle CoAP Separate Responses

The Error: Developers implement CoAP servers that perform slow operations (database queries, sensor polling, complex calculations) but return responses using the piggybacked pattern. When processing exceeds the client’s retransmission timeout (typically 2-4 seconds), clients retransmit the request, creating duplicate operations and wasted processing.

Why It Happens: Most CoAP tutorials show simple temperature sensor examples where processing is instant (<100ms). Developers copy this pattern without considering slow operations. The CoAP library’s default behavior often sends piggybacked responses, requiring explicit configuration for separate responses.

Example of Failure:

# WRONG: Piggybacked response for slow operation
class SlowSensorResource(resource.Resource):
    async def render_get(self, request):
        # This takes 5 seconds (database query + sensor poll)
        data = await query_historical_average()  # 5 seconds

        # Client times out at 2s, retransmits request
        # Server processes BOTH requests, wastes 10 seconds total
        return Message(code=Code.CONTENT, payload=data)

The Fix: Use separate response pattern for operations > 1 second:

# CORRECT: Separate response for slow operation
class SlowSensorResource(resource.Resource):
    async def render_get(self, request):
        # Immediately send empty ACK to stop client retransmission
        request.respond_empty_ack()

        # Now process slowly without client timeout pressure
        data = await query_historical_average()  # 5 seconds

        # Send the actual response as a separate CON message
        # Client receives it reliably with its own ACK
        response = Message(code=Code.CONTENT, payload=data,
                          token=request.token,
                          mtype=CON)
        await request.context.request(response).response

Key Implementation Points:

  1. Empty ACK timing: Send within 500ms to prevent first retransmission
  2. Token preservation: Separate response must use the original request’s token for correlation
  3. New Message ID: Separate response gets its own MID, different from ACK
  4. Client handling: Client must not treat empty ACK as final response

Real-World Impact:

A smart meter deployment queried daily energy consumption (3-second database query). Without separate responses: - Client timeout: 2 seconds - Each query triggered 2-3 retransmissions before completion - Server CPU: 400% (processing 4 duplicate requests per query) - Response time: 15 seconds (processing queue backlog)

After implementing separate responses: - Empty ACK sent at 50ms (well before timeout) - Zero retransmissions - Server CPU: 100% (one query per request) - Response time: 3.2 seconds (query + network)

Detection: Monitor CoAP logs for duplicate requests with the same Token arriving <5 seconds apart. If you see this pattern, your server needs separate response handling for that resource.

58.10 Concept Relationships

How CoAP implementation labs connect to broader IoT practices:

ESP32 CoAP Server Builds On:

  • Embedded Web Servers - RESTful resource handling
  • Sensor Integration - DHT22 temperature/humidity reading
  • GPIO Control - LED actuator management

Python Smart Home Server Relates To:

  • Home Automation Architecture - Multi-device coordination
  • Resource Modeling - REST API design for devices
  • State Management - Tracking distributed device states

Content Negotiation Connects To:

  • CBOR Encoding - 50-70% smaller than JSON for sensors (51% in the soil moisture example)
  • Media Types - Accept headers and response formats
  • Constrained Network Optimization - MTU-aware design

Separate Response Pattern Relates To:

  • Async Request Handling - Long-running operations
  • Timeout Management - Preventing client retransmission
  • Database Queries - Responding to delayed processing

These Labs Enable:

  • Industrial Monitoring - CoAP for factory sensors
  • Smart Buildings - HVAC and lighting control
  • Healthcare IoT - Wearable device communication

58.11 See Also

Review Series (4 Parts):

Core CoAP Resources:

Implementation Libraries:

Hardware Integration:

  • ESP32 Sensor Labs - DHT22, BME280, etc.
  • Wokwi ESP32 Simulator - Online testing
  • GPIO Reference - ESP32 pin assignments

Related Protocols:

  • MQTT Labs - Pub/sub alternative implementations
  • HTTP REST APIs - Web service patterns
  • WebSocket Labs - Bidirectional communication

Advanced Topics:

Specifications:

58.12 What’s Next

Chapter Focus Why Read It
CoAP Review: Knowledge Assessment Scenario-based quiz questions covering all CoAP concepts Test your understanding of the protocol with applied exercises before moving to other protocols
CoAP Review: Observe Patterns Server-push notifications and real-time subscription model Deepen your grasp of the Observe extension used in this lab’s real-time sensor streaming
CoAP Review: Protocol Fundamentals Message types, reliability tiers, and architecture comparisons Review the theory behind the request/response exchanges you implemented in these labs
CoAP Implementation Labs Extended ESP32 exercises with Wokwi simulations Apply these implementation patterns to more complex hardware configurations
MQTT Labs and Implementation MQTT pub/sub broker setup and client programming Compare a broker-based protocol against CoAP’s direct resource model for IoT deployments
DTLS and Security Securing CoAP communications with DTLS and pre-shared keys Add transport-layer security to the CoAP servers you built in this chapter

58.13 Further Reading