Chapters

23 CoAP Lab: Message Construction

coap
implementation

23.1 Start With the Decision

A CoAP packet packs its type, token, code, and ID into a few bytes. Build each field before sending it.

23.2 Route Overview

This is part 2 of 2. Review CoAP Lab: Stack and Trace Contracts for the preceding evidence.

23.3 Learning Objectives

  • Encode CoAP header fields and token length into message bytes.
  • Match tokens and message IDs across replies and retries.

23.4 Chapter Roadmap

  • Try It: CoAP Message Byte Builder
  • Checkpoint: ESP32 Packet Evidence
  • Response Code Reference
  • Knowledge Check: CoAP Response Codes
  • Basic Simulator: CoAP UDP Communication
  • Interactive Simulator: CoAP-Style UDP Communication
  • Learning Points
  • Visual Reference: CoAP Message Structure
  • Visual: CoAP Message Structure
  • Alternative View: CoAP vs HTTP Header Comparison
  • Common Mistake: Misunderstanding CoAP Token vs Message ID Correlation
  • Try It: Token vs Message ID Correlation Visualizer
  • Checkpoint: Correlation Boundary
  • Common Pitfalls
  • 1. Using Confirmable Messages for Every CoAP Request
  • 2. Ignoring CoAP Proxy Caching Semantics
  • 3. Forgetting DTLS Session Management
  • Label the Diagram
  • Order the Steps
  • Concept Relationships
  • See Also
  • Match the Concepts
  • What’s Next
  • Advanced Lab Consolidation Notes
  • Checkpoint: Lab Readiness Evidence
  • Summary
  • Key Takeaway
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().

Broker BexCheckpoint: ESP32 Packet Evidence
  • You now know where the ESP32 callback extracts payload bytes and decodes the response-code class and detail.
  • You can connect coap.get(), coap.put(), and coap.post() calls to resource paths already tested in Python.
  • You have a byte-level header view that explains what the library sends before the payload leaves the device.

Once packets can be decoded, response codes become the lab’s acceptance language. Use the table below to make server behavior explicit.

23.5 Response Code Reference

CoAP response codes follow a Class.Detail format similar to HTTP:

CodeMeaningHTTP Equivalent
2.01Created201 Created
2.02Deleted200 OK (for DELETE)
2.03Valid304 Not Modified
2.04Changed200 OK (for PUT)
2.05Content200 OK (for GET)
4.00Bad Request400 Bad Request
4.01Unauthorized401 Unauthorized
4.04Not Found404 Not Found
4.05Method Not Allowed405 Method Not Allowed
5.00Internal Server Error500 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")
Knowledge Check: CoAP Response Codes

23.6 Basic Simulator: CoAP UDP Communication

Interactive Simulator: CoAP-Style UDP Communication

What This Simulates: ESP32 demonstrating CoAP’s lightweight UDP request/response pattern

Run it: Before you open the Wokwi build, drive the same request/response in the browser workbench below. Step through a Piggybacked CON GET, then a Separate response and a NON request/response, and watch the Current Packet Format and Event Trace show how CON, NON, ACK, and RST differ on the wire. Try Duplicate CON handling and a Reset message to expose the edge cases the serial monitor alone hides, and adjust Token length, Payload bytes, and Uri-Path segments to feel the 4-byte-header economy. The Wokwi steps below then reproduce the same exchange on the ESP32.

CoAP Communication Pattern:

Client (ESP32)
    |
    | GET /temperature
    | 4-byte header over UDP 5683
    v
Server (Simulated)
    |
    | 2.05 Content
    | Temp: 23.5C
    v
Client receives response

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:

Read these points as one connected sequence: start with UDP Transport - Connectionless, lightweight communication; then 4-Byte Headers - Minimal overhead compared to HTTP; then Request/Response - RESTful pattern like HTTP GET; then Message IDs - Tracking requests and responses; and finish with No Handshake - Direct communication without TCP overhead.

  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

23.7 Visual Reference: CoAP Message Structure

Before decoding a capture, inspect the layout in Figure 23.1 to locate the fixed boundary and the fields whose lengths are carried inside the message. This prevents options or payload bytes from being mistaken for header fields.

CoAP message format diagram showing the fixed header bit fields (Ver, Type, TKL, Code, Message ID), token, options, payload marker, and payload.
Figure 23.1: CoAP messages combine a fixed header with an optional token, options, payload marker, and payload.

Read Figure 23.1 from left to right. The first four bytes always provide version, type, token length, code, and Message ID. TKL then tells the decoder how many token bytes follow; delta-encoded options continue until the optional 0xFF payload marker, after which the remaining bytes are payload. That order is the lab’s packet-decoding checklist.

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

The compact field layout matters only when compared on the wire. Inspect Figure 23.2 to distinguish CoAP’s binary application header from the transport setup and textual headers in the HTTP example.

Side-by-side comparison of CoAP and HTTP protocol efficiency: HTTP/1.1 request/response headers (~71/~88 bytes) plus TCP+TLS transport versus CoAP’s 4-byte binary header.
Figure 23.2: CoAP reduces header and transport overhead relative to HTTP/1.1 for constrained request-response exchanges.

In Figure 23.2, inspect the HTTP side first: request and response metadata are textual, and TCP plus TLS add connection and security exchanges. Then compare the CoAP side’s four-byte fixed header and optional fields over UDP. The lesson is not that every complete CoAP packet is four bytes; it is that CoAP keeps the fixed application framing small and pays only for fields used by that exchange.

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.

Broker BexCheckpoint: Correlation Boundary
  • You now know the boundary between MID duplicate detection and Token request-response correlation.
  • You can explain why Observe notifications keep the original Token even when each notification has a new MID.
  • You have a concrete failure pattern to look for when a client drops valid updates from a working server.

The remaining checks turn those protocol facts into operational judgment before you reuse this lab pattern in a production prototype.

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-2000 ms overhead. Use DTLS session resumption (RFC 5077) to reduce reconnection to 1 roundtrip after the initial handshake.

23.8 Concept Relationships

This implementation chapter bridges theory to practice:

Foundation Knowledge:

Read these points as one connected sequence: start with CoAP Fundamentals and Architecture - REST principles, message types, UDP transport; then CoAP Message Format - Response codes (2.05, 4.04, etc.), Content-Format options; and finish with CoAP Methods and Patterns - GET/PUT/POST usage, CON vs NON selection.

Programming Concepts:

Read these points as one connected sequence: start with Python for IoT - Async/await patterns for aiocoap; then Arduino Programming - ESP32 development basics; and finish with Network Programming - UDP communication patterns.

Implementation Details:

Read these points as one connected sequence: start with Token matching for request-response correlation; then Resource handler registration (render_get, render_put); then Observe pattern (observe=0 option, notification loop); and finish with Response code selection (2.01 Created, 2.04 Changed, 2.05 Content).

  • 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:

Read these points as one connected sequence: start with CoAP Advanced Features Lab - Full Wokwi simulation with Observe/Block; then CoAP API Design - Production-ready best practices; and finish with Security Implementation - Adding DTLS to CoAP servers.

23.9 See Also

Python CoAP Resources:

Read these points as one connected sequence: start with aiocoap Documentation - Official library docs; then Python Async Programming - Asyncio fundamentals; and finish with CoAP.me Public Test Server - Test your client implementations.

ESP32 CoAP Resources:

Read these points as one connected sequence: start with ESP32 Arduino Core - Framework documentation; then coap-simple Library - Arduino CoAP library; and finish with ESP32 Network Setup - Wi-Fi configuration.

Testing and Debugging:

Read these points as one connected sequence: start with Wireshark CoAP Dissector Reference - Protocol analysis; then Copper Plugin for Firefox - CoAP client/browser; and finish with coap-client CLI Tool - Command-line testing.

Alternative Implementations:

Read these points as one connected sequence: start with libcoap (C) - Lightweight C library for embedded systems; then node-coap (JavaScript) - Node.js implementation; and finish with GoCoAP (Go) - High-performance Go library.

23.10 What’s Next

CoAP Implementation Stack and Trace Contracts

Focus: Stack selection, handler mapping, trace evidence, Token correlation, CON/NON policy, and DTLS session reuse.

Why read it: Convert the lab code into an acceptance contract you can verify with coap-client, Wireshark, and server logs.

CoAP Advanced Features Lab

Focus: Block-wise transfer, Observe with ESP32, and resource discovery via /.well-known/core.

Why read it: Build and run a full Wokwi simulation that extends the patterns from this chapter.

CoAP Methods and Patterns

Focus: Design decisions for GET, POST, PUT, DELETE, and CON vs NON tradeoffs.

Why read it: Deepen your understanding of when and why each method and message type is chosen.

CoAP Fundamentals and Architecture

Focus: Message structure, token/MID fields, and the UDP transport layer.

Why read it: Revisit the protocol internals now that you have seen them in working code.

CoAP API Design

Focus: REST resource modelling, URI design, and Content-Format selection.

Why read it: Apply production best practices to the servers and clients you have built here.

DTLS and Security

Focus: Datagram TLS for CoAP, OSCORE, and pre-shared keys.

Why read it: Secure the CoAP servers you implemented in this chapter for real deployments.

MQTT Implementation Labs

Focus: Broker-based publish/subscribe with Python and ESP32.

Why read it: Compare broker-mediated MQTT patterns against CoAP's direct request/response and Observe.

23.11 Advanced Lab Consolidation Notes

When extending the basic labs into a fuller ESP32-style CoAP server, keep the feature set small enough to test:

Resource or featureLab behavior to proveFailure mode to observe
/temperature with ObserveClient registers once, then receives updates only when the reading changesLost notification, cancelled observation, or stale token
/firmware with Block2Client can fetch a large payload in numbered blocks and resume from the failed blockRestarting the whole transfer after one missing block
/.well-known/coreDiscovery lists only supported resources and content formatsClient assumes hidden or unsupported paths
Configuration PUTIdempotent update can be retried without creating duplicate stateRetried POST creates repeated configuration records
CON/NON selectionCommands and rare critical alerts use CON; periodic telemetry can use NON when the next value replaces the previous oneHigh-frequency CON drains batteries, while NON commands lose safety-critical acknowledgements

For battery labs, write the message policy beside the resource table. A common hybrid is NON for routine telemetry and CON for user actions, safety alerts, firmware blocks, or configuration changes. That preserves the energy benefit of UDP while still proving delivery for messages that cannot be silently lost.

23.11.1 Block Transfer Planning Check

Before using Block2 for firmware, images, or larger JSON payloads, record the block size, total block count, resume behavior, and retry limit. A small planning record prevents the lab from hiding a production risk:

Run it: Fill this planning record from the block-wise workbench below instead of guessing. Pick a direction (Block2 download or Block1 upload) and a Scenario such as Firmware upload or Sensor log download, choose a Requested block size (SZX), and step the transfer while watching NUM, the M bit, and the Byte range advance — that tells you how many blocks a payload needs. Then run the Lossy field link and Server asks for smaller blocks scenarios and read the Event trace to confirm the client retries only the missing block rather than restarting, which is exactly the resume behavior this check must capture.

QuestionEvidence to capture
How many blocks are needed?Payload bytes divided by negotiated block size, with the final partial block noted
What happens after packet loss?Client retries only the missing block instead of restarting the whole payload
How is progress verified?Server logs block number, token, ETag or version, and final checksum
When is Block2 not enough?If payloads are frequent or very large, move the transfer to a gateway or HTTP/TLS path and keep CoAP for control

Broker BexCheckpoint: Lab Readiness Evidence
  • You now know which records to capture for resource discovery, configuration updates, Observe cancellation, Block2 resume behavior, and security setup.
  • You can choose NON for replaceable telemetry and CON for commands, alerts, firmware blocks, and configuration changes.
  • You have a final checklist for deciding whether the implementation is tested under constrained-network conditions rather than only compiled.

23.12 Summary

CoAP implementation labs turn protocol rules into testable behavior: resource handlers, confirmable messages, retransmission, Observe notifications, and security setup. The goal is to verify behavior with real requests rather than assuming the stack handles every edge case.

23.13 Key Takeaway

A CoAP implementation is ready only when resource paths, content formats, timeout handling, retransmission, Observe cancellation, and security settings have all been exercised under constrained-network conditions.

23.14 Continue Your Route

This final part closes the route from Try It: CoAP Message Byte Builder through Key Takeaway. Return to CoAP Lab: Stack and Trace Contracts or continue from the coap module index.