This review series covers CoAP across four focused chapters: protocol fundamentals (message types, 87% bandwidth reduction vs HTTP), Observe patterns (99% energy savings vs polling), hands-on ESP32 labs, and knowledge assessment quizzes. CoAP provides RESTful semantics over UDP with a 4-byte binary header, optional reliability via CON/NON messages, and DTLS security.
Learning Objectives
By the end of this review series, you will be able to:
Synthesize CoAP protocol knowledge across message types, observe patterns, and security mechanisms into a coherent design model
Evaluate CoAP vs HTTP and MQTT trade-offs and justify protocol selection for specific IoT deployment scenarios
Compare CON and NON message types by calculating energy consumption, reliability trade-offs, and appropriate use cases for each
Apply CoAP design patterns including block-wise transfers and resource discovery to constrained-device architectures
Implement CoAP solutions on ESP32 and Python platforms with DTLS security and Observe-based notifications
Calculate bandwidth, energy, and latency savings to assess CoAP’s quantitative advantages over HTTP in large-scale sensor deployments
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
For Beginners: CoAP Review
This comprehensive review covers everything about CoAP (Constrained Application Protocol), the web protocol designed specifically for tiny IoT devices. If HTTP is a luxury car with all the features, CoAP is a compact city car – it does the essential job of web communication but with much less fuel (bandwidth) and engine power (processing).
Overview
The Constrained Application Protocol (CoAP) is a lightweight RESTful protocol designed for IoT devices that uses UDP instead of TCP for lower power consumption. This comprehensive review series covers protocol fundamentals, implementation techniques, and practical applications across four focused chapters.
What You Will Learn:
CoAP message types (CON, NON, ACK, RST) and their trade-offs
Protocol efficiency: 87% bandwidth reduction vs HTTP
Observe pattern for 99% energy savings compared to polling
Hands-on implementation on ESP32 and Python
Security with DTLS, multicast discovery, and block-wise transfers
Review Chapters
This review is organized into four focused chapters for efficient learning:
1. Protocol Fundamentals
CoAP Review: Protocol Fundamentals
Core protocol concepts and architecture:
Common misconception: “UDP is unreliable” (debunked with data)
Message types: CON, NON, ACK, RST with energy/reliability trade-offs
CoAP vs HTTP comparison: 87% bandwidth reduction, 7.5x lower latency
4-byte binary header efficiency vs HTTP text headers
Block1 vs Block2 option usage for large transfers
Estimated Time: 25 minutes
2. Observe Patterns
CoAP Review: Observe Patterns
Push notifications and subscriptions:
Observe mechanism: registration, notifications, deregistration
Performance analysis: 99% energy savings vs polling
NAT/firewall timeout workarounds
Sequence number handling with wraparound
Conditional observe for battery optimization
Estimated Time: 25 minutes
3. Labs and Implementation
CoAP Review: Labs and Implementation
Hands-on development experience:
ESP32 CoAP server with DHT22 sensor and LED control
Python smart home automation server
Resource discovery with .well-known/core
Content negotiation (CBOR, JSON, plain text)
Wokwi simulator for interactive testing
Estimated Time: 45 minutes
4. Knowledge Assessment
CoAP Review: Knowledge Assessment
Test your understanding:
12 fundamentals quiz questions
12 scenario-based decision questions
3 deep-dive understanding checks
Real-world protocol selection guidance
Energy and bandwidth calculations
Estimated Time: 30 minutes
Quick Reference
Message Type Selection
CON
3.0 mJ
99.99%
Critical commands, alarms, configuration
NON
1.5 mJ
90-95%
Frequent sensor readings, status updates
Scenario: 1,000 sensors send 10-byte readings every 5 minutes.
Message overhead: \[
\begin{align}
\text{CoAP total} &= 4 + 4 + 8 + 10 = 26 \text{ bytes} \\
\text{HTTP total} &= 150 + 10 = 160 \text{ bytes} \\
\text{Overhead reduction} &= \frac{160 - 26}{160} = 83.75\%
\end{align}
\]
Daily bandwidth: \[
\begin{align}
\text{Messages/day/sensor} &= \frac{24 \times 60}{5} = 288 \\
\text{Total messages/day} &= 1{,}000 \times 288 = 288{,}000 \\
\text{CoAP bandwidth} &= 288{,}000 \times 26 = 7.49 \text{ MB/day} \\
\text{HTTP bandwidth} &= 288{,}000 \times 160 = 46.08 \text{ MB/day} \\
\text{Annual savings} &= (46.08 - 7.49) \times 365 = 14{,}084 \text{ MB = 13.76 GB}
\end{align}
\]
Battery life (AA 2,500 mAh): \[
\begin{align}
\text{CoAP energy/msg} &= 0.11 \text{ mAh} \\
\text{HTTP energy/msg} &= 1.25 \text{ mAh} \\
\text{Daily consumption (CoAP)} &= 288 \times 0.11 = 31.68 \text{ mAh} \\
\text{Battery life} &= \frac{2{,}500}{31.68} = 78.9 \text{ days (CoAP)} \\
\text{Battery life} &= \frac{2{,}500}{288 \times 1.25} = 6.9 \text{ days (HTTP)} \\
\text{Life extension} &= 78.9 / 6.9 = 11.4\times
\end{align}
\]
Protocol Comparison
Header Size
4 bytes
100+ bytes
25x smaller
Transaction Size
22 bytes
165 bytes
87% reduction
Latency
20ms
150ms
7.5x faster
Energy per Request
0.11mAh
1.25mAh
11x less
Interactive Calculators
CoAP vs HTTP Bandwidth Calculator
Calculate bandwidth savings when using CoAP instead of HTTP for sensor data transmission.
Show code
viewof sensors_count = Inputs. range ([1 , 10000 ], {
step : 1 ,
value : 1000 ,
label : "Number of Sensors"
})
viewof interval_minutes = Inputs. range ([1 , 60 ], {
step : 1 ,
value : 5 ,
label : "Polling Interval (minutes)"
})
viewof payload_size = Inputs. range ([1 , 100 ], {
step : 1 ,
value : 10 ,
label : "Payload Size (bytes)"
})
// Calculations
messages_per_day = Math . floor ((24 * 60 ) / interval_minutes)
total_messages_per_day = sensors_count * messages_per_day
coap_overhead = 4 + 4 + 8 // CoAP header + UDP + IP
http_overhead = 150 // Typical HTTP headers
coap_message_size = coap_overhead + payload_size
http_message_size = http_overhead + payload_size
coap_daily_mb = (total_messages_per_day * coap_message_size) / (1024 * 1024 )
http_daily_mb = (total_messages_per_day * http_message_size) / (1024 * 1024 )
bandwidth_savings_percent = ((http_daily_mb - coap_daily_mb) / http_daily_mb * 100 ). toFixed (1 )
annual_savings_gb = ((http_daily_mb - coap_daily_mb) * 365 / 1024 ). toFixed (2 )
html `<div style="background: #f8f9fa; border-left: 4px solid #16A085; padding: 16px; margin: 16px 0; border-radius: 4px;">
<h4 style="color: #2C3E50; margin-top: 0;">Results</h4>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; color: #2C3E50;">
<div>
<strong style="color: #16A085;">Messages per Day:</strong><br>
${ messages_per_day. toLocaleString ()} per sensor<br>
${ total_messages_per_day. toLocaleString ()} total
</div>
<div>
<strong style="color: #E67E22;">Message Size:</strong><br>
CoAP: ${ coap_message_size} bytes<br>
HTTP: ${ http_message_size} bytes
</div>
<div>
<strong style="color: #3498DB;">Daily Bandwidth:</strong><br>
CoAP: ${ coap_daily_mb. toFixed (2 )} MB<br>
HTTP: ${ http_daily_mb. toFixed (2 )} MB
</div>
<div>
<strong style="color: #9B59B6;">Savings:</strong><br>
${ bandwidth_savings_percent} % reduction<br>
${ annual_savings_gb} GB/year saved
</div>
</div>
</div>`
CoAP Message Energy Calculator
Compare energy consumption between Confirmable (CON) and Non-confirmable (NON) messages.
Show code
viewof message_type = Inputs. radio (
["CON" , "NON" ],
{value : "NON" , label : "Message Type" }
)
viewof tx_current = Inputs. range ([50 , 200 ], {
step : 10 ,
value : 120 ,
label : "TX Current (mA)"
})
viewof tx_time = Inputs. range ([10 , 500 ], {
step : 10 ,
value : 50 ,
label : "TX Time (ms)"
})
viewof rx_current = Inputs. range ([20 , 100 ], {
step : 10 ,
value : 50 ,
label : "RX Current (mA)"
})
viewof rx_time = Inputs. range ([10 , 200 ], {
step : 10 ,
value : 100 ,
label : "RX Time (ms)"
})
// Energy calculations (converting to mAh)
tx_energy = (tx_current * tx_time) / 3600000
rx_energy = (rx_current * rx_time) / 3600000
// CON messages have both TX and RX, NON only TX
total_energy = message_type === "CON" ? tx_energy + rx_energy : tx_energy
energy_per_message_uah = (total_energy * 1000 ). toFixed (3 )
// Battery life estimation (2500 mAh coin cell)
battery_capacity = 2500
messages_per_day_calc = Inputs. bind (Inputs. range ([1 , 1000 ], {
step : 1 ,
value : 100 ,
label : "Messages per Day"
}), viewof messages_per_day_input)
daily_energy = total_energy * messages_per_day_input
battery_life_days = battery_capacity / daily_energy
html `<div style="background: #f8f9fa; border-left: 4px solid #E67E22; padding: 16px; margin: 16px 0; border-radius: 4px;">
<h4 style="color: #2C3E50; margin-top: 0;">Energy Analysis</h4>
<div style="color: #2C3E50; line-height: 1.8;">
<div style="margin-bottom: 8px;">
<strong style="color: #16A085;">TX Energy:</strong> ${ (tx_energy * 1000 ). toFixed (3 )} µAh
${ message_type === "CON" ? `<br><strong style="color: #16A085;">RX Energy:</strong> ${ (rx_energy * 1000 ). toFixed (3 )} µAh` : "" }
</div>
<div style="padding: 12px; background: white; border-radius: 4px; margin: 8px 0;">
<strong style="color: #E67E22; font-size: 1.1em;">Total per Message:</strong>
<span style="color: #2C3E50; font-size: 1.2em; font-weight: bold;"> ${ energy_per_message_uah} µAh</span>
</div>
<div style="font-size: 0.9em; color: #7F8C8D;">
${ message_type === "CON"
? "✓ Confirmable: Includes acknowledgment reception"
: "✓ Non-confirmable: Transmission only, no ACK" }
</div>
</div>
</div>`
Show code
viewof messages_per_day_input = Inputs. range ([1 , 1000 ], {
step : 1 ,
value : 100 ,
label : "Messages per Day (for battery estimate)"
})
html `<div style="background: #f8f9fa; border-left: 4px solid #3498DB; padding: 16px; margin: 16px 0; border-radius: 4px;">
<h4 style="color: #2C3E50; margin-top: 0;">Battery Life Estimate (2,500 mAh)</h4>
<div style="color: #2C3E50; line-height: 1.8;">
<div><strong style="color: #16A085;">Daily Energy:</strong> ${ (daily_energy * 1000 ). toFixed (2 )} mAh</div>
<div style="padding: 12px; background: white; border-radius: 4px; margin: 8px 0;">
<strong style="color: #3498DB; font-size: 1.1em;">Battery Life:</strong>
<span style="color: #2C3E50; font-size: 1.2em; font-weight: bold;"> ${ battery_life_days. toFixed (0 )} days</span>
<span style="color: #7F8C8D;"> ( ${ (battery_life_days / 365 ). toFixed (1 )} years)</span>
</div>
</div>
</div>`
Battery Life Comparison: Polling vs Observe
Compare battery life between traditional polling and CoAP Observe pattern.
Show code
viewof observe_sensors = Inputs. range ([1 , 1000 ], {
step : 1 ,
value : 200 ,
label : "Number of Sensors"
})
viewof observe_battery = Inputs. range ([500 , 20000 ], {
step : 500 ,
value : 2500 ,
label : "Battery Capacity (mAh)"
})
viewof polling_interval_observe = Inputs. range ([1 , 60 ], {
step : 1 ,
value : 10 ,
label : "Polling Interval (minutes)"
})
viewof change_frequency = Inputs. range ([1 , 50 ], {
step : 1 ,
value : 5 ,
label : "Data Changes (% of time)"
})
// Polling calculation
polling_messages = Math . floor ((24 * 60 ) / polling_interval_observe)
polling_energy_per_msg = 0.0082 // mAh (from worked example)
polling_daily_energy = polling_messages * polling_energy_per_msg
polling_battery_life_days = observe_battery / polling_daily_energy
// Observe calculation
observe_messages = Math . floor ((polling_messages * change_frequency) / 100 )
observe_energy_per_msg = 0.0018 // mAh (NON message)
observe_daily_energy = observe_messages * observe_energy_per_msg + 0.24 // +sleep current
observe_battery_life_days = observe_battery / observe_daily_energy
// Comparison
battery_life_improvement = (observe_battery_life_days / polling_battery_life_days). toFixed (1 )
energy_savings_percent = ((polling_daily_energy - observe_daily_energy) / polling_daily_energy * 100 ). toFixed (1 )
html `<div style="background: #f8f9fa; border: 2px solid #2C3E50; padding: 20px; margin: 16px 0; border-radius: 8px;">
<h4 style="color: #2C3E50; margin-top: 0; border-bottom: 2px solid #16A085; padding-bottom: 8px;">
Polling vs Observe Comparison
</h4>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 16px;">
<div style="background: white; padding: 16px; border-radius: 4px; border-left: 4px solid #E67E22;">
<h5 style="color: #E67E22; margin-top: 0;">HTTP Polling</h5>
<div style="color: #2C3E50; line-height: 1.8;">
<div><strong>Messages/day:</strong> ${ polling_messages} </div>
<div><strong>Daily energy:</strong> ${ polling_daily_energy. toFixed (2 )} mAh</div>
<div style="font-size: 1.2em; margin-top: 8px;">
<strong style="color: #E67E22;">Battery life:</strong><br>
<span style="font-weight: bold;"> ${ polling_battery_life_days. toFixed (0 )} days</span>
<span style="color: #7F8C8D;"> ( ${ (polling_battery_life_days / 365 ). toFixed (1 )} yrs)</span>
</div>
</div>
</div>
<div style="background: white; padding: 16px; border-radius: 4px; border-left: 4px solid #16A085;">
<h5 style="color: #16A085; margin-top: 0;">CoAP Observe</h5>
<div style="color: #2C3E50; line-height: 1.8;">
<div><strong>Messages/day:</strong> ${ observe_messages} </div>
<div><strong>Daily energy:</strong> ${ observe_daily_energy. toFixed (2 )} mAh</div>
<div style="font-size: 1.2em; margin-top: 8px;">
<strong style="color: #16A085;">Battery life:</strong><br>
<span style="font-weight: bold;"> ${ observe_battery_life_days. toFixed (0 )} days</span>
<span style="color: #7F8C8D;"> ( ${ (observe_battery_life_days / 365 ). toFixed (1 )} yrs)</span>
</div>
</div>
</div>
</div>
<div style="background: linear-gradient(135deg, #16A085 0%, #3498DB 100%); color: white; padding: 16px; border-radius: 4px; margin-top: 16px; text-align: center;">
<div style="font-size: 0.9em; margin-bottom: 4px;">IMPROVEMENT WITH COAP OBSERVE</div>
<div style="font-size: 2em; font-weight: bold;"> ${ battery_life_improvement} ×</div>
<div style="font-size: 0.9em; margin-top: 4px;"> ${ energy_savings_percent} % energy savings</div>
</div>
</div>`
When to Use CoAP vs MQTT
Direct device-to-device
Cloud integration (AWS, Azure)
RESTful API needed
Publish-subscribe pattern
No broker infrastructure
Multiple subscribers
Battery life critical
Persistent sessions needed
Constrained networks (LoRaWAN)
Reliable networks (Wi-Fi, LTE)
Prerequisites
Required Chapters:
Recommended Reading:
Learning Path
Recommended sequence for maximum learning:
Start with Protocol Fundamentals for core concepts
Continue to Observe Patterns for push notifications
Practice with Labs and Implementation for hands-on experience
Validate with Knowledge Assessment to test understanding
Total Estimated Time: 2 hours 5 minutes
See Also
Related Topics:
Hands-On Learning:
Deployment Guidance:
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.