Host and gateway stacks
aiocoap (Python), libcoap (C + CLI), Californium (Java) run on Linux hosts, gateways, and cloud bridges.
A valve controller takes time to read its position, so it acknowledges a request before returning the value. The screen looks responsive, but only a packet trace can show whether the eventual response belongs to the right request. This is a useful acceptance case for the chosen stack.
Let the Trace Challenge the Demo
Picture a valve screen that shows a fresh value after every click. The packet record reveals that replies are being matched to the wrong request after a restart. A convincing screen can hide a message contract that is already unsafe.
A protocol is a shared set of rules for exchanging messages. Constrained Application Protocol (CoAP) is a compact web-style protocol for small devices. A payload is the useful data in a message. Transport Layer Security is a way to protect message traffic. Datagram Transport Layer Security (DTLS) adapts that protection to a datagram link. A gateway is the device that joins unlike networks. Record how the chosen software represents each of these duties.
Capture one read and one change from request to device state. Then lose a reply, repeat a request, reuse an old identifier, expire cached data, and restart the secure session. Match the trace to the handler result and the physical outcome.
This contract proves the tested software, settings, and path only. The deeper sections compare stack roles and trace fields and show how to turn a passing demo into a bounded release decision.
A lab can appear to work because the browser prints a value, while the packet trace shows token confusion, wasteful Confirmable messages, stale cache data, or repeated DTLS handshakes. The implementation is not accepted until the trace explains the behavior.
This page turns the lab into a release contract: choose the stack, map each handler, capture the response evidence, then debug by Message ID, Token, options, Max-Age, Observe state, and security session records.
After this page, you should be able to:
CoAP Implementation Labs gives you runnable Python and ESP32 code. This page narrows in on the implementation contract behind those examples: which stack owns the protocol machinery, what each handler must prove, how the client accepts a response, and what a packet trace should show when the implementation is correct.
The CoAP implementation proof stays concrete: a CoAP implementation is accepted only when a working handler is tied to URI, method, response code, payload, Token, Message ID, and trace evidence.
Use it when you are turning a classroom lab into a repeatable acceptance test, comparing Python and embedded implementations, debugging Observe notifications, or reviewing whether a constrained deployment is wasting battery through avoidable ACKs or repeated security handshakes.
Almost no production code assembles the 4-byte header and delta-encoded options by hand. A CoAP library does the messaging layer for you: Confirmable retransmission, deduplication, token generation, and option encoding. Your code deals in resources, methods, and payloads. On general-purpose hosts and gateways the common stacks are aiocoap (Python, asyncio — the library these labs use), libcoap (C, which also ships the handy coap-client CLI), and Eclipse Californium (Java).
On microcontrollers you reach for stacks sized to kilobytes of RAM: RIOT's gcoap and nanocoap, Contiki-NG's Erbium, Zephyr's CoAP subsystem, and Arduino/ESP32 libraries such as coap-simple used later in this chapter. They implement the same RFC 7252 on the wire, so an aiocoap client can talk to an ESP32 server — interoperability is the whole point of the standard.
Treat each lab as a contract between a client command, a URI, a handler, and the response evidence. A laptop might run coap-client -m get coap://node/temperature and expect 2.05 Content with a numeric payload; the same server might accept a PUT /led from a mobile tool and return 2.04 Changed; an Observe registration keeps the same Token while notifications report later temperature values. That framing keeps the code examples from becoming copy-paste snippets: every run should leave a response code, a payload sample, and a packet trace you can compare with the handler you wrote.
Inspect Figure to connect a client action to the stack behavior, resource handler, and evidence that should appear in a trace.
Read Figure from sensor, mobile, or gateway client into GET, PUT, or Observe, then follow the selected URI to its handler. On the return path, check response code, payload, and packet trace against the intended operation. The library owns encoding and retransmission mechanics, but the application still owns this observable contract. The labs below use that chain as their debugging order.
aiocoap (Python), libcoap (C + CLI), Californium (Java) run on Linux hosts, gateways, and cloud bridges.
RIOT gcoap/nanocoap, Contiki-NG Erbium, Zephyr CoAP, and ESP/Arduino libraries fit constrained nodes.
You register a handler per URI path and method; the library routes incoming requests to it.
You issue a request to a coap:// URI and read the response code and payload back.
On the server side you attach behavior to a path. In aiocoap you subclass Resource and implement render_get, render_put, and so on; the library reads the incoming Uri-Path options and the method Code and dispatches to the right method, which returns a representation plus a response code (a read returns 2.05 Content, a write returns 2.04 Changed). On the client side you send a request to a coap:// URI and inspect two things on what comes back: response.code to branch on success or error, and response.payload for the representation. For Observe you register once and then iterate the stream of notifications instead of polling.
Verify on the wire, not just in your logs. A small toolkit proves the exchange is doing what you think.
Make the acceptance test small enough to repeat after every edit. Start the server on UDP port 5683, run one GET for the sensor resource, one PUT for the actuator resource, one deliberately bad path, and one Observe registration if the stack supports it. The expected record is concrete: GET returns 2.05 plus a parseable value, PUT returns 2.04 or another documented success code, the bad path returns 4.04, and the Observe trace repeats the Token across notifications. If an ESP32 version and a Python version expose the same resource contract, use the same client checklist for both; only the build and deployment step changes.
The single most common implementation bug in hand-rolled correlation is matching responses by Message ID instead of Token. A piggybacked ACK does reuse the request's Message ID, which lulls people into keying on it — but a separate response and every Observe notification arrive as new messages with different Message IDs and the same Token. Code that keys on Message ID silently drops those. The rule is simple: correlate application responses by Token; the Message ID is only for message-layer ACK and duplicate handling. Good libraries already do this, which is a strong reason to use one.
Test for the bug directly: open an Observe and watch the notifications in Wireshark. You will see one Token repeated across notifications while the Message ID changes each time. If your handler only fires on the first notification, you are matching on the Message ID.
Three deployment pitfalls recur, and each is visible in a capture. First, using Confirmable for every message drains batteries — the trace shows an ACK round trip per message; send disposable telemetry as NON. Second, ignoring proxy cache semantics serves stale readings — a cache honors your Max-Age, so an over-long value returns aged data; advertise freshness that matches the real change rate. Third, re-running the DTLS handshake per request wastes energy — the capture shows a full ClientHello-through-Finished before each request; keep the session open and reuse it.
Notifications and separate responses share the Token, not the Message ID; key your dispatch on the Token.
Reserve CON for messages whose loss is unsafe; frequent readings as NON save radio-on energy.
Set freshness to the true change rate so caches and proxies never serve stale readings.
Amortize the handshake across many requests instead of renegotiating on every call.
Use illustrative identifiers: the client sends a Confirmable GET with Message ID 100 and Token A1. The server sends an empty acknowledgement for Message ID 100. That packet stops the request’s message-level retry cycle; it contains no valve-position result. Later, a separate Confirmable response arrives with Message ID 700 and the same Token A1. The client acknowledges 700 and correlates the result using A1.
Suppose the request leaves at elapsed time 0 ms, the empty acknowledgement returns at 40 ms, and the result returns at 240 ms. The trace shows a 40 ms acknowledgement delay and a 240 ms response delay. Reporting only the smaller number would hide the time the application waited for data. Neither figure measures the valve’s physical travel unless the handler contract explicitly waits for that outcome.
Restart the client after the empty acknowledgement but before the result. Its old request state may have vanished. Check that the chosen stack does not hand the late response to an unrelated new operation merely because a numeric identifier was reused. The acceptance record should pair the trace with the client’s pending-request table and reboot behaviour.
Predict what happens if the server’s final response is lost. An empty acknowledgement already proved receipt of the request, but the application still has no result. The trace must reveal the separate response’s retry behaviour and the client timeout. Next, return an error code with a readable payload. A client that prints the text as a successful valve position fails the handler contract even though the bytes decode cleanly.
These cases connect stack selection to observable duties. The library manages message exchange, but application code still checks status, units, freshness and physical meaning. Capture the handler outcome beside each packet sequence so the trace can challenge a plausible screen. The identifiers and delays here define a test, not a promise about a particular library version or device. Run it with the settings that will actually ship.
Before shipping a CoAP implementation lab or prototype, verify these records:
Read these points as one connected sequence: start with Stack choice names the library and target platform: aiocoap, libcoap, Californium, RIOT gcoap/nanocoap, Contiki-NG Erbium, Zephyr CoAP, or an ESP/Arduino library; then Resource table maps every URI and method to its handler, expected success code, expected error code, payload type, and Content-Format; then Client checks branch on response.code before trusting response.payload, and tests include at least one success path and one documented error path; then Trace evidence shows Token correlation for ordinary responses, separate responses, and Observe notifications; Message ID is used only for ACK and duplicate handling; then Message policy distinguishes CON from NON and explains which application messages can tolerate loss; then Freshness policy records Max-Age and proxy-cache behavior for resources that may be cached or observed; and finish with Security evidence shows whether the lab uses plain CoAP, DTLS, or OSCORE, and whether DTLS sessions are reused instead of renegotiated per request.
response.code before trusting response.payload, and tests include at least one success path and one documented error path.Read these points as one connected sequence: start with CoAP Implementation Labs for the parent Python and ESP32 walkthrough; then CoAP Message Format for Token, Message ID, Code, and option encoding; then CoAP Message Types for CON, NON, ACK, RST, and retransmission rules; then CoAP Observe Registration and Freshness Contracts for long-lived notification ordering and stale-value handling; and finish with CoAP DTLS and OSCORE Security Contracts for security-mode, proxy, and replay-review details.
Return to CoAP Implementation Labs, then continue to CoAP Advanced Features for block-wise transfer, discovery, caching, and multicast.
Check One Request Before Using the Card
Picture a leak sensor that reports water and accepts a command to close a valve. A protocol means shared rules for an exchange. Constrained Application Protocol (CoAP) means a compact request method for small devices. User Datagram Protocol (UDP) means sending separate messages without a lasting connection. Hypertext Transfer Protocol (HTTP) means a request-and-response format used by web systems. A payload means the useful data inside one message. Transport layer security means protection for a network exchange. Datagram Transport Layer Security (DTLS) applies it to separate messages.
Read the leak state, repeat the request, lose the reply, send a bad command, and restart the device. The receiver must tell refusal from silence and completed action from receipt.
This card recalls names, codes, and options. It does not prove identity, valve movement, or recovery. Use the deeper chapters to design the resource, protect the path, and test the final physical result.