%%{init: {'theme': 'base', 'themeVariables': {'primaryColor':'#2C3E50','primaryTextColor':'#fff','primaryBorderColor':'#16A085','lineColor':'#16A085','secondaryColor':'#E67E22'}}}%%
stateDiagram-v2
[*] --> DISCONNECTED
DISCONNECTED --> CONNECTING: connect()
CONNECTING --> CONNECTED: ACK received
CONNECTING --> DISCONNECTED: timeout / error
CONNECTED --> CONNECTED: send/receive data
CONNECTED --> DISCONNECTING: disconnect()
CONNECTED --> DISCONNECTED: error / timeout
DISCONNECTING --> DISCONNECTED: FIN-ACK received
DISCONNECTING --> DISCONNECTED: timeout
note right of CONNECTED: Keep-alive<br/>monitors health
note right of CONNECTING: Retry with<br/>backoff on failure
745 Connection State Management and Reliability Lab
By the end of this section, you will be able to:
- Design connection state machines: Build robust connection management with proper state transitions
- Implement keep-alive mechanisms: Monitor connection health for long-lived IoT sessions
- Handle connection failures: Detect and recover from unexpected disconnections
- Build a complete reliable transport: Implement all five reliability pillars in working code
- Measure reliability metrics: Track delivery rate, retry overhead, and throughput
- Tune reliability parameters: Adjust settings based on observed network conditions
745.1 Prerequisites
Before diving into this chapter, you should be familiar with:
- Error Detection: CRC and checksums for detecting corrupted data
- Retry and Sequencing: Exponential backoff and sequence number handling
- Reliability Overview: The parent chapter introducing all five reliability pillars
- C/C++ programming: The lab uses Arduino/ESP32 code
Long-lived IoT connections are fragile. NAT routers timeout idle connections after 30-120 seconds. Cellular networks may silently drop sessions. WiFi access points reboot for updates. Without proper connection state management, your device may believe it is connected while the network path is broken - leading to lost data and unresponsive actuators.
745.2 Connection State Machines
Connection state machines track the lifecycle of a communication session. Proper state management prevents resource leaks, handles unexpected disconnections, and ensures clean shutdown.
745.2.1 Basic Connection States
745.2.2 State Transition Rules
| Current State | Event | Next State | Action |
|---|---|---|---|
| DISCONNECTED | connect() | CONNECTING | Send CONNECT message |
| CONNECTING | ACK received | CONNECTED | Start keep-alive timer |
| CONNECTING | Timeout (after retries) | DISCONNECTED | Report failure |
| CONNECTED | Data to send | CONNECTED | Send with reliability |
| CONNECTED | Keep-alive timeout | DISCONNECTED | Clean up resources |
| CONNECTED | disconnect() | DISCONNECTING | Send DISCONNECT |
| DISCONNECTING | ACK received | DISCONNECTED | Clean up complete |
| DISCONNECTING | Timeout | DISCONNECTED | Force clean up |
745.2.3 Implementation Pattern
enum ConnectionState {
STATE_DISCONNECTED = 0,
STATE_CONNECTING = 1,
STATE_CONNECTED = 2,
STATE_DISCONNECTING= 3
};
ConnectionState currentState = STATE_DISCONNECTED;
void transitionState(ConnectionState newState) {
// Log transition for debugging
Serial.printf("[STATE] %s -> %s\n",
stateNames[currentState],
stateNames[newState]);
// Perform exit actions for old state
switch (currentState) {
case STATE_CONNECTED:
stopKeepaliveTimer();
break;
// ... other exit actions
}
// Update state
currentState = newState;
// Perform entry actions for new state
switch (newState) {
case STATE_CONNECTED:
startKeepaliveTimer();
resetSequenceNumbers();
break;
// ... other entry actions
}
}745.3 Keep-Alive Mechanism
Long-lived connections may go silent during idle periods. Keep-alive messages verify the connection is still healthy.
745.3.1 Keep-Alive Protocol
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor':'#2C3E50','primaryTextColor':'#fff','primaryBorderColor':'#16A085','lineColor':'#16A085','secondaryColor':'#E67E22'}}}%%
sequenceDiagram
participant D as Device
participant S as Server
Note over D,S: Connected - data flowing
D->>S: Data message
S-->>D: ACK
Note over D,S: 30 seconds of silence...
D->>S: KEEPALIVE ping
S-->>D: KEEPALIVE response
Note over D: Connection healthy
Note over D,S: 30 more seconds...
D->>S: KEEPALIVE ping
Note over S: Server crashed!
Note over D: Timeout waiting for response
D->>S: KEEPALIVE retry 1
Note over D: Still no response...
D->>S: KEEPALIVE retry 2
Note over D: 3 failures = connection dead
D->>D: Transition to DISCONNECTED
745.3.2 Protocol-Specific Keep-Alive
| Protocol | Mechanism | Typical Interval | Notes |
|---|---|---|---|
| MQTT | PINGREQ/PINGRESP | 30-300s | Configurable, broker enforces |
| CoAP | Empty CON message | 30-120s | Optional, not standardized |
| TCP | TCP keepalive | 2 hours (default) | OS-level, often disabled for IoT |
| WebSocket | Ping/Pong frames | 30-60s | Built into protocol |
NAT routers and firewalls often drop “idle” connections after 30-120 seconds. Your keep-alive interval must be shorter than the shortest NAT timeout in the path, or connections will silently break.
Recommendation: Use 25-30 second keep-alive intervals for cellular IoT.
745.4 Reliability Lab: Build a Complete Reliable Transport System
This hands-on lab demonstrates all five reliability pillars by building a complete reliable messaging system on an ESP32. You will implement CRC error detection, sequence numbering, acknowledgments, exponential backoff retries, and connection state management - the same mechanisms used by TCP and CoAP internally.
745.4.1 What You Will Learn
By completing this lab, you will gain practical experience with:
- CRC-16 calculation and verification: Detecting bit-level corruption in transmitted data
- Exponential backoff with jitter: Implementing congestion-aware retry logic
- Sequence number management: Tracking message order and detecting duplicates
- ACK/NACK protocol design: Building a bidirectional acknowledgment system
- Connection state machines: Managing session lifecycle with proper state transitions
- Real-time reliability statistics: Measuring throughput, loss rate, and retry overhead
745.4.2 Components Needed
| Component | Quantity | Purpose |
|---|---|---|
| ESP32 DevKit | 1 | Microcontroller running reliability simulation |
| Green LED | 1 | Successful transmission indicator (ACK received) |
| Red LED | 1 | Error indicator (timeout, CRC failure, max retries) |
| Yellow LED | 1 | Transmission in progress indicator |
| Blue LED | 1 | Connection state indicator (connected/disconnected) |
| 220 ohm Resistors | 4 | Current limiting for LEDs |
| Push Button | 1 | Trigger manual message send |
| 10K ohm Resistor | 1 | Button pull-down |
| Breadboard | 1 | Circuit assembly |
| Jumper Wires | Several | Connections |
745.4.3 Wokwi Simulator
Use the embedded simulator below to build and test your reliable transport system. Click “Start Simulation” to begin.
- Click inside the simulator frame first to give it focus
- Press the green “Play” button to start simulation
- Open the Serial Monitor (set to 115200 baud) to see detailed output
- You can add components by clicking the “+” button
- Copy the code below into the editor, replacing any default code
745.4.4 Circuit Diagram
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#E67E22', 'secondaryColor': '#16A085', 'tertiaryColor': '#E8F6F3', 'fontSize': '14px'}}}%%
graph LR
subgraph ESP32["ESP32 DevKit"]
GPIO2["GPIO 2"]
GPIO4["GPIO 4"]
GPIO5["GPIO 5"]
GPIO18["GPIO 18"]
GPIO15["GPIO 15"]
GND["GND"]
V3["3.3V"]
end
subgraph LEDs["Status Indicators"]
GREEN["Green LED<br/>(TX Success)"]
RED["Red LED<br/>(Error/Timeout)"]
YELLOW["Yellow LED<br/>(Transmitting)"]
BLUE["Blue LED<br/>(Connected)"]
end
subgraph Input["User Input"]
BTN["Push Button<br/>(Send Message)"]
PULL["10K Pull-down"]
end
GPIO4 -->|"220 ohm"| GREEN
GPIO2 -->|"220 ohm"| RED
GPIO5 -->|"220 ohm"| YELLOW
GPIO18 -->|"220 ohm"| BLUE
GREEN --> GND
RED --> GND
YELLOW --> GND
BLUE --> GND
V3 --> BTN
BTN --> GPIO15
GPIO15 --> PULL
PULL --> GND
style ESP32 fill:#2C3E50,stroke:#16A085,color:#fff
style LEDs fill:#E8F6F3,stroke:#16A085,color:#2C3E50
style Input fill:#E8F6F3,stroke:#16A085,color:#2C3E50
style GREEN fill:#2ECC71,stroke:#27AE60,color:#fff
style RED fill:#E74C3C,stroke:#C0392B,color:#fff
style YELLOW fill:#F1C40F,stroke:#F39C12,color:#2C3E50
style BLUE fill:#3498DB,stroke:#2980B9,color:#fff
745.4.5 Complete Code
Copy this code into the Wokwi editor:
/*
* ============================================================================
* RELIABILITY AND ERROR HANDLING LAB - ESP32
* ============================================================================
*
* This comprehensive lab demonstrates the five pillars of reliable IoT
* communication:
*
* 1. ERROR DETECTION: CRC-16 checksum calculation and verification
* 2. SEQUENCE NUMBERS: Packet ordering, loss detection, duplicate handling
* 3. ACKNOWLEDGMENTS: ACK/NACK protocol with configurable timeouts
* 4. RETRY WITH BACKOFF: Exponential backoff with random jitter
* 5. CONNECTION STATE: State machine for connection lifecycle
*
* LED Indicators:
* - Green (GPIO 4): Successful transmission (ACK received)
* - Red (GPIO 2): Error condition (timeout, CRC fail, max retries)
* - Yellow (GPIO 5): Transmission in progress
* - Blue (GPIO 18): Connection state (ON = connected)
*
* Button (GPIO 15): Manual message send trigger
*
* Serial Commands:
* - 'h' or '?': Show help
* - 's': Send test message
* - 'c': Toggle connection state
* - 'v': Toggle verbose mode
* - 'r': Reset statistics
* - 'p': Print current statistics
* - '1'-'9': Set packet loss percentage (1=10%, 5=50%, etc.)
*
* Author: IoT Education Platform
* License: MIT
* ============================================================================
*/
#include <Arduino.h>
// ============================================================================
// PIN DEFINITIONS
// ============================================================================
#define LED_SUCCESS 4 // Green LED - ACK received, transmission OK
#define LED_ERROR 2 // Red LED - Timeout, CRC fail, or max retries
#define LED_TRANSMIT 5 // Yellow LED - Transmission in progress
#define LED_CONNECTED 18 // Blue LED - Connection state indicator
#define BTN_SEND 15 // Push button for manual message send
// ============================================================================
// RELIABILITY CONFIGURATION
// ============================================================================
#define INITIAL_TIMEOUT_MS 500 // Initial ACK timeout (milliseconds)
#define MAX_TIMEOUT_MS 8000 // Maximum timeout after backoff
#define MAX_RETRIES 5 // Maximum retry attempts before failure
#define JITTER_PERCENT 25 // Random jitter (0-25% of timeout)
#define DEFAULT_LOSS_PERCENT 20 // Default simulated packet loss rate
#define KEEPALIVE_INTERVAL_MS 10000 // Connection keep-alive interval
#define KEEPALIVE_TIMEOUT_MS 3000 // Time to wait for keep-alive response
// ============================================================================
// CRC-16 POLYNOMIAL (CRC-CCITT)
// ============================================================================
#define CRC16_POLYNOMIAL 0x1021
#define CRC16_INITIAL 0xFFFF
// ============================================================================
// MESSAGE TYPES
// ============================================================================
enum MessageType : uint8_t {
MSG_DATA = 0x01, // Data payload message
MSG_ACK = 0x02, // Positive acknowledgment
MSG_NACK = 0x03, // Negative acknowledgment (CRC error, etc.)
MSG_CONNECT = 0x10, // Connection request
MSG_CONNACK = 0x11, // Connection acknowledgment
MSG_DISCONNECT= 0x12, // Disconnect notification
MSG_KEEPALIVE = 0x20, // Keep-alive ping
MSG_KEEPALIVE_ACK = 0x21 // Keep-alive response
};
// ============================================================================
// CONNECTION STATES
// ============================================================================
enum ConnectionState : uint8_t {
STATE_DISCONNECTED = 0,
STATE_CONNECTING = 1,
STATE_CONNECTED = 2,
STATE_DISCONNECTING= 3
};
const char* stateNames[] = {
"DISCONNECTED",
"CONNECTING",
"CONNECTED",
"DISCONNECTING"
};
// ============================================================================
// MESSAGE STRUCTURE (72 bytes total)
// ============================================================================
struct ReliableMessage {
// Header (8 bytes)
uint8_t type; // Message type (MSG_DATA, MSG_ACK, etc.)
uint8_t flags; // Bit flags: 0x01=requires_ack, 0x02=is_retransmit
uint16_t sequenceNum; // Sender's sequence number (0-65535)
uint16_t ackNum; // Acknowledgment number (for ACK/NACK)
uint8_t payloadLen; // Length of payload (0-63)
uint8_t reserved; // Reserved for future use
// Payload (64 bytes max)
char payload[64]; // Message data
// Trailer (2 bytes)
uint16_t crc16; // CRC-16 checksum of header + payload
};
// ============================================================================
// RELIABILITY STATISTICS
// ============================================================================
struct ReliabilityStats {
// Transmission counts
uint32_t messagesSent;
uint32_t messagesDelivered;
uint32_t messagesFailed;
// Acknowledgment counts
uint32_t acksReceived;
uint32_t nacksReceived;
uint32_t ackTimeouts;
// Retry statistics
uint32_t retransmissions;
uint32_t totalRetryDelayMs;
// Error detection
uint32_t crcErrors;
uint32_t duplicatesDetected;
uint32_t outOfOrderDetected;
// Simulated network conditions
uint32_t simulatedDrops;
uint32_t simulatedCorruptions;
// Connection statistics
uint32_t connectionAttempts;
uint32_t connectionSuccesses;
uint32_t keepalivesSent;
uint32_t keepalivesTimedOut;
// Timing
unsigned long startTime;
unsigned long lastMessageTime;
};
// ============================================================================
// GLOBAL STATE
// ============================================================================
ConnectionState currentState = STATE_DISCONNECTED;
uint16_t nextSequenceNum = 0;
uint16_t expectedSequenceNum = 0;
uint16_t lastAckedSequence = 0xFFFF;
ReliabilityStats stats;
int packetLossPercent = DEFAULT_LOSS_PERCENT;
int corruptionPercent = 5; // 5% chance of bit corruption
bool verboseMode = true;
bool autoSendMode = true;
unsigned long lastKeepaliveTime = 0;
unsigned long lastButtonPress = 0;
// ============================================================================
// FUNCTION PROTOTYPES
// ============================================================================
// LED Functions
void initializePins();
void setLED(int pin, bool state);
void blinkLED(int pin, int times, int delayMs);
void updateConnectionLED();
// CRC Functions
uint16_t calculateCRC16(const uint8_t* data, size_t length);
uint16_t calculateMessageCRC(ReliableMessage* msg);
bool verifyCRC(ReliableMessage* msg);
void corruptRandomBit(ReliableMessage* msg);
// Message Functions
void createDataMessage(ReliableMessage* msg, const char* payload);
void createAckMessage(ReliableMessage* ack, uint16_t ackNum);
void createNackMessage(ReliableMessage* nack, uint16_t nackNum, const char* reason);
void createConnectMessage(ReliableMessage* msg);
void createKeepaliveMessage(ReliableMessage* msg);
void printMessage(ReliableMessage* msg, const char* direction);
// Network Simulation
bool simulatePacketLoss();
bool simulateCorruption();
int calculateBackoff(int retryCount);
// Reliability Core
bool sendWithReliability(const char* data);
bool sendMessage(ReliableMessage* msg, bool requiresAck);
void processReceivedMessage(ReliableMessage* msg);
// Connection Management
bool connect();
void disconnect();
void handleKeepalive();
void transitionState(ConnectionState newState);
// Statistics
void printStatistics();
void resetStatistics();
void printHelp();
// Command handler
void handleCommand(char cmd);
// ============================================================================
// SETUP
// ============================================================================
void setup() {
Serial.begin(115200);
delay(1000);
// Initialize random seed
randomSeed(analogRead(0) + micros());
// Initialize GPIO
initializePins();
// Initialize statistics
resetStatistics();
// Print banner
Serial.println();
Serial.println("========================================================================");
Serial.println(" RELIABILITY AND ERROR HANDLING LAB - ESP32 ");
Serial.println("========================================================================");
Serial.println();
Serial.println("This lab demonstrates the five pillars of reliable IoT communication:");
Serial.println();
Serial.println(" 1. CRC-16 Error Detection - Detect bit corruption in transit");
Serial.println(" 2. Sequence Numbers - Detect loss, duplicates, reordering");
Serial.println(" 3. ACK/NACK Protocol - Confirm or reject message delivery");
Serial.println(" 4. Exponential Backoff - Congestion-aware retry strategy");
Serial.println(" 5. Connection State Machine - Manage session lifecycle");
Serial.println();
Serial.println("------------------------------------------------------------------------");
Serial.println("CONFIGURATION:");
Serial.printf(" Initial Timeout: %d ms\n", INITIAL_TIMEOUT_MS);
Serial.printf(" Maximum Timeout: %d ms\n", MAX_TIMEOUT_MS);
Serial.printf(" Maximum Retries: %d\n", MAX_RETRIES);
Serial.printf(" Jitter: %d%%\n", JITTER_PERCENT);
Serial.printf(" Packet Loss Rate: %d%%\n", packetLossPercent);
Serial.printf(" Corruption Rate: %d%%\n", corruptionPercent);
Serial.printf(" Keep-Alive Interval:%d ms\n", KEEPALIVE_INTERVAL_MS);
Serial.println("------------------------------------------------------------------------");
Serial.println();
// LED test sequence
Serial.println("Testing LEDs...");
blinkLED(LED_SUCCESS, 2, 150);
blinkLED(LED_ERROR, 2, 150);
blinkLED(LED_TRANSMIT, 2, 150);
blinkLED(LED_CONNECTED, 2, 150);
Serial.println("LED test complete.");
Serial.println();
printHelp();
Serial.println();
Serial.println("Attempting initial connection...");
Serial.println();
// Attempt initial connection
if (connect()) {
Serial.println("==> Connected successfully! Ready to send messages.");
} else {
Serial.println("==> Connection failed. Type 'c' to retry.");
}
Serial.println();
}
// ============================================================================
// MAIN LOOP
// ============================================================================
void loop() {
// Handle serial commands
if (Serial.available()) {
char cmd = Serial.read();
handleCommand(cmd);
}
// Handle button press
if (digitalRead(BTN_SEND) == HIGH) {
if (millis() - lastButtonPress > 500) { // Debounce
lastButtonPress = millis();
if (currentState == STATE_CONNECTED) {
static int buttonMsgCount = 0;
char msgBuffer[64];
snprintf(msgBuffer, sizeof(msgBuffer), "Button press #%d at %lu ms",
++buttonMsgCount, millis());
sendWithReliability(msgBuffer);
} else {
Serial.println("[WARN] Not connected. Press 'c' to connect first.");
blinkLED(LED_ERROR, 2, 100);
}
}
}
// Handle keep-alive for connected state
if (currentState == STATE_CONNECTED) {
handleKeepalive();
}
// Auto-send test messages when connected
static unsigned long lastAutoSend = 0;
if (autoSendMode && currentState == STATE_CONNECTED) {
if (millis() - lastAutoSend > 5000) { // Every 5 seconds
lastAutoSend = millis();
static int autoMsgCount = 0;
char msgBuffer[64];
float temp = 20.0 + (random(200) / 10.0); // Random temp 20-40C
float humidity = 40.0 + (random(400) / 10.0); // Random humidity 40-80%
snprintf(msgBuffer, sizeof(msgBuffer),
"Sensor #%d: Temp=%.1fC, Humidity=%.1f%%",
++autoMsgCount, temp, humidity);
Serial.println("========================================================================");
Serial.printf(">>> AUTO-SEND MESSAGE #%d <<<\n", autoMsgCount);
Serial.println("========================================================================");
bool success = sendWithReliability(msgBuffer);
if (success) {
Serial.println("[RESULT] Message delivered successfully!");
} else {
Serial.println("[RESULT] Message delivery FAILED after max retries.");
}
// Print stats every 5 messages
if (autoMsgCount % 5 == 0) {
printStatistics();
}
Serial.println();
}
}
// Update connection LED
updateConnectionLED();
delay(10); // Small delay for stability
}
// ============================================================================
// COMMAND HANDLER
// ============================================================================
void handleCommand(char cmd) {
switch (cmd) {
case 'h':
case '?':
printHelp();
break;
case 's':
case 'S':
if (currentState == STATE_CONNECTED) {
char msg[64];
snprintf(msg, sizeof(msg), "Manual test message at %lu ms", millis());
sendWithReliability(msg);
} else {
Serial.println("[WARN] Not connected. Type 'c' to connect first.");
}
break;
case 'c':
case 'C':
if (currentState == STATE_DISCONNECTED) {
connect();
} else if (currentState == STATE_CONNECTED) {
disconnect();
} else {
Serial.printf("[INFO] Currently in state: %s\n", stateNames[currentState]);
}
break;
case 'v':
case 'V':
verboseMode = !verboseMode;
Serial.printf("[CONFIG] Verbose mode: %s\n", verboseMode ? "ON" : "OFF");
break;
case 'a':
case 'A':
autoSendMode = !autoSendMode;
Serial.printf("[CONFIG] Auto-send mode: %s\n", autoSendMode ? "ON" : "OFF");
break;
case 'r':
case 'R':
resetStatistics();
Serial.println("[INFO] Statistics reset.");
break;
case 'p':
case 'P':
printStatistics();
break;
case '0':
packetLossPercent = 0;
Serial.println("[CONFIG] Packet loss: 0% (perfect network)");
break;
case '1': case '2': case '3': case '4': case '5':
case '6': case '7': case '8': case '9':
packetLossPercent = (cmd - '0') * 10;
Serial.printf("[CONFIG] Packet loss: %d%%\n", packetLossPercent);
break;
case '\n':
case '\r':
// Ignore newlines
break;
default:
Serial.printf("[WARN] Unknown command: '%c'. Type 'h' for help.\n", cmd);
break;
}
}
// ============================================================================
// PIN INITIALIZATION
// ============================================================================
void initializePins() {
// Configure LED pins as outputs
pinMode(LED_SUCCESS, OUTPUT);
pinMode(LED_ERROR, OUTPUT);
pinMode(LED_TRANSMIT, OUTPUT);
pinMode(LED_CONNECTED, OUTPUT);
// Configure button as input
pinMode(BTN_SEND, INPUT);
// Initialize all LEDs to off
digitalWrite(LED_SUCCESS, LOW);
digitalWrite(LED_ERROR, LOW);
digitalWrite(LED_TRANSMIT, LOW);
digitalWrite(LED_CONNECTED, LOW);
}
// ============================================================================
// LED FUNCTIONS
// ============================================================================
void setLED(int pin, bool state) {
digitalWrite(pin, state ? HIGH : LOW);
}
void blinkLED(int pin, int times, int delayMs) {
for (int i = 0; i < times; i++) {
digitalWrite(pin, HIGH);
delay(delayMs);
digitalWrite(pin, LOW);
delay(delayMs);
}
}
void updateConnectionLED() {
// Blue LED indicates connection state
static unsigned long lastBlink = 0;
static bool blinkState = false;
switch (currentState) {
case STATE_DISCONNECTED:
setLED(LED_CONNECTED, false);
break;
case STATE_CONNECTING:
case STATE_DISCONNECTING:
// Blink slowly during transitions
if (millis() - lastBlink > 250) {
lastBlink = millis();
blinkState = !blinkState;
setLED(LED_CONNECTED, blinkState);
}
break;
case STATE_CONNECTED:
setLED(LED_CONNECTED, true);
break;
}
}
// ============================================================================
// CRC-16 CALCULATION (CRC-CCITT)
// ============================================================================
uint16_t calculateCRC16(const uint8_t* data, size_t length) {
uint16_t crc = CRC16_INITIAL;
for (size_t i = 0; i < length; i++) {
crc ^= ((uint16_t)data[i] << 8);
for (int bit = 0; bit < 8; bit++) {
if (crc & 0x8000) {
crc = (crc << 1) ^ CRC16_POLYNOMIAL;
} else {
crc = crc << 1;
}
}
}
return crc;
}
uint16_t calculateMessageCRC(ReliableMessage* msg) {
// CRC covers header (excluding CRC field) and payload
size_t crcLength = 8 + msg->payloadLen; // Header (8 bytes) + payload
return calculateCRC16((uint8_t*)msg, crcLength);
}
bool verifyCRC(ReliableMessage* msg) {
uint16_t calculated = calculateMessageCRC(msg);
bool valid = (calculated == msg->crc16);
if (!valid && verboseMode) {
Serial.printf(" [CRC] MISMATCH! Calculated: 0x%04X, Received: 0x%04X\n",
calculated, msg->crc16);
}
return valid;
}
void corruptRandomBit(ReliableMessage* msg) {
// Corrupt a random bit in the payload (simulating transmission error)
if (msg->payloadLen > 0) {
int byteIndex = random(msg->payloadLen);
int bitIndex = random(8);
msg->payload[byteIndex] ^= (1 << bitIndex);
if (verboseMode) {
Serial.printf(" [CORRUPT] Flipped bit %d in payload byte %d\n",
bitIndex, byteIndex);
}
}
}
// ============================================================================
// MESSAGE CREATION
// ============================================================================
void createDataMessage(ReliableMessage* msg, const char* payload) {
memset(msg, 0, sizeof(ReliableMessage));
msg->type = MSG_DATA;
msg->flags = 0x01; // Requires ACK
msg->sequenceNum = nextSequenceNum;
msg->ackNum = 0;
msg->payloadLen = min((int)strlen(payload), 63);
msg->reserved = 0;
strncpy(msg->payload, payload, msg->payloadLen);
msg->payload[msg->payloadLen] = '\0';
msg->crc16 = calculateMessageCRC(msg);
}
void createAckMessage(ReliableMessage* ack, uint16_t ackNum) {
memset(ack, 0, sizeof(ReliableMessage));
ack->type = MSG_ACK;
ack->flags = 0;
ack->sequenceNum = 0;
ack->ackNum = ackNum;
ack->payloadLen = 0;
ack->reserved = 0;
ack->crc16 = calculateMessageCRC(ack);
}
void createNackMessage(ReliableMessage* nack, uint16_t nackNum, const char* reason) {
memset(nack, 0, sizeof(ReliableMessage));
nack->type = MSG_NACK;
nack->flags = 0;
nack->sequenceNum = 0;
nack->ackNum = nackNum;
nack->payloadLen = min((int)strlen(reason), 63);
nack->reserved = 0;
strncpy(nack->payload, reason, nack->payloadLen);
nack->crc16 = calculateMessageCRC(nack);
}
void createConnectMessage(ReliableMessage* msg) {
memset(msg, 0, sizeof(ReliableMessage));
msg->type = MSG_CONNECT;
msg->flags = 0x01; // Requires ACK
msg->sequenceNum = 0;
msg->payloadLen = 0;
msg->crc16 = calculateMessageCRC(msg);
}
void createKeepaliveMessage(ReliableMessage* msg) {
memset(msg, 0, sizeof(ReliableMessage));
msg->type = MSG_KEEPALIVE;
msg->flags = 0x01; // Requires ACK
msg->sequenceNum = nextSequenceNum;
msg->payloadLen = 0;
msg->crc16 = calculateMessageCRC(msg);
}
// ============================================================================
// MESSAGE PRINTING
// ============================================================================
void printMessage(ReliableMessage* msg, const char* direction) {
if (!verboseMode) return;
const char* typeName;
switch (msg->type) {
case MSG_DATA: typeName = "DATA"; break;
case MSG_ACK: typeName = "ACK"; break;
case MSG_NACK: typeName = "NACK"; break;
case MSG_CONNECT: typeName = "CONNECT"; break;
case MSG_CONNACK: typeName = "CONNACK"; break;
case MSG_DISCONNECT:typeName = "DISCONNECT"; break;
case MSG_KEEPALIVE: typeName = "KEEPALIVE"; break;
case MSG_KEEPALIVE_ACK: typeName = "KEEPALIVE_ACK"; break;
default: typeName = "UNKNOWN"; break;
}
Serial.println();
Serial.printf(" +-------------- %s MESSAGE --------------+\n", direction);
Serial.printf(" | Type: %-26s |\n", typeName);
Serial.printf(" | Flags: 0x%02X (ACK_REQ=%d, RETX=%d) |\n",
msg->flags, (msg->flags & 0x01), ((msg->flags & 0x02) >> 1));
Serial.printf(" | Sequence: %-26d |\n", msg->sequenceNum);
Serial.printf(" | Ack Number: %-26d |\n", msg->ackNum);
Serial.printf(" | Payload Len: %-26d |\n", msg->payloadLen);
if (msg->payloadLen > 0) {
if (msg->payloadLen <= 30) {
Serial.printf(" | Payload: %-26s |\n", msg->payload);
} else {
char truncated[28];
strncpy(truncated, msg->payload, 24);
truncated[24] = '\0';
strcat(truncated, "...");
Serial.printf(" | Payload: %-26s |\n", truncated);
}
}
Serial.printf(" | CRC-16: 0x%04X |\n", msg->crc16);
Serial.println(" +-------------------------------------------+");
}
// ============================================================================
// NETWORK SIMULATION
// ============================================================================
bool simulatePacketLoss() {
int roll = random(100);
bool lost = (roll < packetLossPercent);
if (lost) {
stats.simulatedDrops++;
if (verboseMode) {
Serial.printf(" [NETWORK] Packet DROPPED (roll=%d < %d%%)\n",
roll, packetLossPercent);
}
}
return lost;
}
bool simulateCorruption() {
int roll = random(100);
bool corrupted = (roll < corruptionPercent);
if (corrupted) {
stats.simulatedCorruptions++;
if (verboseMode) {
Serial.printf(" [NETWORK] Packet CORRUPTED (roll=%d < %d%%)\n",
roll, corruptionPercent);
}
}
return corrupted;
}
int calculateBackoff(int retryCount) {
// Exponential backoff: initial * 2^retry
int baseTimeout = INITIAL_TIMEOUT_MS * (1 << retryCount);
baseTimeout = min(baseTimeout, (int)MAX_TIMEOUT_MS);
// Add random jitter (0 to JITTER_PERCENT% of timeout)
int jitter = random((baseTimeout * JITTER_PERCENT) / 100);
return baseTimeout + jitter;
}
// ============================================================================
// RELIABLE SEND WITH RETRY
// ============================================================================
bool sendWithReliability(const char* data) {
if (currentState != STATE_CONNECTED) {
Serial.println("[ERROR] Cannot send: not connected");
return false;
}
ReliableMessage msg;
ReliableMessage response;
int retryCount = 0;
bool delivered = false;
// Create the data message
createDataMessage(&msg, data);
printMessage(&msg, "SEND");
while (retryCount <= MAX_RETRIES && !delivered) {
// Calculate timeout for this attempt
int currentTimeout = calculateBackoff(retryCount);
// Mark as retransmission if this is a retry
if (retryCount > 0) {
msg.flags |= 0x02; // Set retransmit flag
stats.retransmissions++;
stats.totalRetryDelayMs += currentTimeout;
if (verboseMode) {
Serial.println();
Serial.printf(" [RETRY] Attempt %d/%d (timeout=%dms, backoff=2^%d)\n",
retryCount, MAX_RETRIES, currentTimeout, retryCount);
}
}
// Turn on transmit LED
setLED(LED_TRANSMIT, true);
if (verboseMode) {
Serial.printf(" [TX] Sending SEQ=%d, waiting up to %dms for ACK...\n",
msg.sequenceNum, currentTimeout);
}
stats.messagesSent++;
// Simulate network transmission delay
delay(50);
// Turn off transmit LED
setLED(LED_TRANSMIT, false);
// ===== SIMULATE NETWORK CHANNEL =====
// Check if packet was lost in transit
if (simulatePacketLoss()) {
// Packet lost - wait for timeout
if (verboseMode) {
Serial.printf(" [WAIT] Waiting %dms for ACK (packet was lost)...\n",
currentTimeout);
}
delay(currentTimeout);
// Timeout occurred
setLED(LED_ERROR, true);
delay(150);
setLED(LED_ERROR, false);
stats.ackTimeouts++;
if (verboseMode) {
Serial.println(" [TIMEOUT] No ACK received - will retry");
}
retryCount++;
continue;
}
// Check if packet was corrupted
if (simulateCorruption()) {
corruptRandomBit(&msg);
}
// Packet arrived at receiver - process it
setLED(LED_TRANSMIT, true);
delay(30);
setLED(LED_TRANSMIT, false);
// Receiver verifies CRC
if (!verifyCRC(&msg)) {
// CRC failed - receiver sends NACK
stats.crcErrors++;
stats.nacksReceived++;
createNackMessage(&response, msg.sequenceNum, "CRC_ERROR");
printMessage(&response, "RECV");
if (verboseMode) {
Serial.println(" [NACK] CRC error detected by receiver - will retry");
}
setLED(LED_ERROR, true);
delay(150);
setLED(LED_ERROR, false);
// Restore original message for retry
msg.crc16 = calculateMessageCRC(&msg);
retryCount++;
continue;
}
if (verboseMode) {
Serial.println(" [RX] Message received and CRC verified OK");
}
// Check sequence number at receiver
if (msg.sequenceNum < expectedSequenceNum &&
!(msg.sequenceNum == 0 && expectedSequenceNum > 60000)) {
// Duplicate detected (not wraparound)
stats.duplicatesDetected++;
if (verboseMode) {
Serial.printf(" [RX] DUPLICATE (expected SEQ >= %d, got %d) - ACK anyway\n",
expectedSequenceNum, msg.sequenceNum);
}
} else if (msg.sequenceNum > expectedSequenceNum) {
// Out of order (gap detected)
stats.outOfOrderDetected++;
if (verboseMode) {
Serial.printf(" [RX] OUT OF ORDER (expected SEQ=%d, got %d)\n",
expectedSequenceNum, msg.sequenceNum);
}
expectedSequenceNum = msg.sequenceNum + 1;
} else {
// Normal in-order delivery
expectedSequenceNum = msg.sequenceNum + 1;
}
// ===== RECEIVER SENDS ACK =====
// Check if ACK is lost on the return path
if (simulatePacketLoss()) {
if (verboseMode) {
Serial.println(" [NETWORK] ACK was LOST in transit!");
Serial.printf(" [WAIT] Waiting %dms for ACK...\n", currentTimeout);
}
delay(currentTimeout);
setLED(LED_ERROR, true);
delay(150);
setLED(LED_ERROR, false);
stats.ackTimeouts++;
retryCount++;
continue;
}
// ACK received successfully
createAckMessage(&response, msg.sequenceNum);
printMessage(&response, "RECV");
stats.acksReceived++;
stats.messagesDelivered++;
lastAckedSequence = msg.sequenceNum;
if (verboseMode) {
Serial.printf(" [ACK] Received ACK for SEQ=%d\n", response.ackNum);
}
setLED(LED_SUCCESS, true);
delay(200);
setLED(LED_SUCCESS, false);
// Success - increment sequence number for next message
nextSequenceNum++;
delivered = true;
}
if (!delivered) {
stats.messagesFailed++;
Serial.printf(" [FAIL] Message delivery failed after %d retries\n", MAX_RETRIES);
blinkLED(LED_ERROR, 3, 100);
}
stats.lastMessageTime = millis();
return delivered;
}
// ============================================================================
// CONNECTION MANAGEMENT
// ============================================================================
void transitionState(ConnectionState newState) {
if (verboseMode) {
Serial.printf("[STATE] %s -> %s\n", stateNames[currentState], stateNames[newState]);
}
currentState = newState;
updateConnectionLED();
}
bool connect() {
if (currentState != STATE_DISCONNECTED) {
Serial.printf("[WARN] Cannot connect: already in state %s\n",
stateNames[currentState]);
return false;
}
transitionState(STATE_CONNECTING);
stats.connectionAttempts++;
Serial.println("[CONNECT] Initiating connection...");
ReliableMessage connectMsg;
createConnectMessage(&connectMsg);
printMessage(&connectMsg, "SEND");
// Simulate connection handshake with retries
int retryCount = 0;
while (retryCount < 3) {
setLED(LED_TRANSMIT, true);
delay(100);
setLED(LED_TRANSMIT, false);
// Check for simulated loss
if (simulatePacketLoss()) {
int timeout = calculateBackoff(retryCount);
if (verboseMode) {
Serial.printf(" [WAIT] Connection request lost, waiting %dms...\n", timeout);
}
delay(timeout);
retryCount++;
continue;
}
// Connection successful
if (verboseMode) {
Serial.println(" [RX] CONNACK received");
}
transitionState(STATE_CONNECTED);
stats.connectionSuccesses++;
lastKeepaliveTime = millis();
nextSequenceNum = 0;
expectedSequenceNum = 0;
setLED(LED_SUCCESS, true);
delay(300);
setLED(LED_SUCCESS, false);
Serial.println("[CONNECT] Connection established successfully!");
return true;
}
// Connection failed
transitionState(STATE_DISCONNECTED);
Serial.println("[CONNECT] Connection FAILED after 3 attempts");
blinkLED(LED_ERROR, 3, 150);
return false;
}
void disconnect() {
if (currentState != STATE_CONNECTED) {
Serial.printf("[WARN] Cannot disconnect: in state %s\n",
stateNames[currentState]);
return;
}
transitionState(STATE_DISCONNECTING);
Serial.println("[DISCONNECT] Initiating graceful disconnect...");
ReliableMessage disconnectMsg;
memset(&disconnectMsg, 0, sizeof(ReliableMessage));
disconnectMsg.type = MSG_DISCONNECT;
disconnectMsg.crc16 = calculateMessageCRC(&disconnectMsg);
printMessage(&disconnectMsg, "SEND");
setLED(LED_TRANSMIT, true);
delay(100);
setLED(LED_TRANSMIT, false);
// Don't wait for ACK on disconnect (best effort)
transitionState(STATE_DISCONNECTED);
Serial.println("[DISCONNECT] Disconnected.");
}
void handleKeepalive() {
if (currentState != STATE_CONNECTED) return;
if (millis() - lastKeepaliveTime >= KEEPALIVE_INTERVAL_MS) {
stats.keepalivesSent++;
if (verboseMode) {
Serial.println("[KEEPALIVE] Sending keep-alive ping...");
}
ReliableMessage keepalive;
createKeepaliveMessage(&keepalive);
setLED(LED_TRANSMIT, true);
delay(30);
setLED(LED_TRANSMIT, false);
// Simulate keep-alive response
if (!simulatePacketLoss()) {
lastKeepaliveTime = millis();
if (verboseMode) {
Serial.println("[KEEPALIVE] Response received - connection healthy");
}
} else {
stats.keepalivesTimedOut++;
if (verboseMode) {
Serial.println("[KEEPALIVE] No response - connection may be degraded");
}
// After multiple failed keepalives, disconnect
static int missedKeepalives = 0;
missedKeepalives++;
if (missedKeepalives >= 3) {
Serial.println("[KEEPALIVE] Connection lost - 3 consecutive failures");
transitionState(STATE_DISCONNECTED);
missedKeepalives = 0;
blinkLED(LED_ERROR, 3, 100);
}
}
}
}
// ============================================================================
// STATISTICS
// ============================================================================
void printStatistics() {
unsigned long elapsed = (millis() - stats.startTime) / 1000;
if (elapsed == 0) elapsed = 1;
float deliveryRate = stats.messagesSent > 0 ?
(float)stats.messagesDelivered * 100.0 / stats.messagesSent : 0;
float avgRetriesPerMsg = stats.messagesDelivered > 0 ?
(float)stats.retransmissions / stats.messagesDelivered : 0;
float throughput = (float)stats.messagesDelivered / elapsed;
Serial.println();
Serial.println("+======================================================================+");
Serial.println("| RELIABILITY STATISTICS |");
Serial.println("+======================================================================+");
Serial.println("| TRANSMISSION |");
Serial.printf("| Messages Sent: %-10lu |\n",
stats.messagesSent);
Serial.printf("| Messages Delivered: %-10lu |\n",
stats.messagesDelivered);
Serial.printf("| Messages Failed: %-10lu |\n",
stats.messagesFailed);
Serial.printf("| Delivery Rate: %-10.1f%% |\n",
deliveryRate);
Serial.println("+----------------------------------------------------------------------+");
Serial.println("| ACKNOWLEDGMENTS |");
Serial.printf("| ACKs Received: %-10lu |\n",
stats.acksReceived);
Serial.printf("| NACKs Received: %-10lu |\n",
stats.nacksReceived);
Serial.printf("| ACK Timeouts: %-10lu |\n",
stats.ackTimeouts);
Serial.println("+----------------------------------------------------------------------+");
Serial.println("| RETRY STATISTICS |");
Serial.printf("| Total Retransmissions:%-10lu |\n",
stats.retransmissions);
Serial.printf("| Avg Retries/Message: %-10.2f |\n",
avgRetriesPerMsg);
Serial.printf("| Total Retry Delay: %-10lu ms |\n",
stats.totalRetryDelayMs);
Serial.println("+----------------------------------------------------------------------+");
Serial.println("| ERROR DETECTION |");
Serial.printf("| CRC Errors Detected: %-10lu |\n",
stats.crcErrors);
Serial.printf("| Duplicates Detected: %-10lu |\n",
stats.duplicatesDetected);
Serial.printf("| Out-of-Order: %-10lu |\n",
stats.outOfOrderDetected);
Serial.println("+----------------------------------------------------------------------+");
Serial.println("| NETWORK SIMULATION |");
Serial.printf("| Packets Dropped: %-10lu |\n",
stats.simulatedDrops);
Serial.printf("| Packets Corrupted: %-10lu |\n",
stats.simulatedCorruptions);
Serial.printf("| Current Loss Rate: %-10d%% |\n",
packetLossPercent);
Serial.println("+----------------------------------------------------------------------+");
Serial.println("| CONNECTION |");
Serial.printf("| Connection Attempts: %-10lu |\n",
stats.connectionAttempts);
Serial.printf("| Connection Successes: %-10lu |\n",
stats.connectionSuccesses);
Serial.printf("| Keep-alives Sent: %-10lu |\n",
stats.keepalivesSent);
Serial.printf("| Keep-alives Failed: %-10lu |\n",
stats.keepalivesTimedOut);
Serial.printf("| Current State: %-10s |\n",
stateNames[currentState]);
Serial.println("+----------------------------------------------------------------------+");
Serial.println("| PERFORMANCE |");
Serial.printf("| Elapsed Time: %-10lu seconds |\n",
elapsed);
Serial.printf("| Throughput: %-10.2f messages/sec |\n",
throughput);
Serial.println("+======================================================================+");
Serial.println();
}
void resetStatistics() {
memset(&stats, 0, sizeof(ReliabilityStats));
stats.startTime = millis();
nextSequenceNum = 0;
expectedSequenceNum = 0;
}
void printHelp() {
Serial.println();
Serial.println("------------------------------------------------------------------------");
Serial.println("COMMANDS:");
Serial.println(" h / ? Show this help menu");
Serial.println(" s Send a test message manually");
Serial.println(" c Connect (if disconnected) or Disconnect (if connected)");
Serial.println(" v Toggle verbose mode (detailed logging)");
Serial.println(" a Toggle auto-send mode (periodic test messages)");
Serial.println(" r Reset statistics");
Serial.println(" p Print current statistics");
Serial.println(" 0-9 Set packet loss percentage (0=0%, 5=50%, 9=90%)");
Serial.println("------------------------------------------------------------------------");
Serial.println("LED INDICATORS:");
Serial.println(" Green - Successful transmission (ACK received)");
Serial.println(" Red - Error (timeout, CRC failure, max retries)");
Serial.println(" Yellow - Transmission in progress");
Serial.println(" Blue - Connection state (solid=connected, blink=transitioning)");
Serial.println("------------------------------------------------------------------------");
Serial.println("BUTTON: Press to send a message manually");
Serial.println("------------------------------------------------------------------------");
}745.4.6 Step-by-Step Instructions
Step 1: Set Up the Circuit
- Open the Wokwi simulator above (or visit wokwi.com/projects/new/esp32)
- Add four LEDs to the breadboard:
- Green LED (success indicator)
- Red LED (error indicator)
- Yellow LED (transmission indicator)
- Blue LED (connection state indicator)
- Add four 220 ohm resistors for the LEDs
- Add one push button and one 10K ohm pull-down resistor
- Wire the connections as shown in the circuit diagram:
- GPIO 4 to Green LED anode (through 220 ohm resistor)
- GPIO 2 to Red LED anode (through 220 ohm resistor)
- GPIO 5 to Yellow LED anode (through 220 ohm resistor)
- GPIO 18 to Blue LED anode (through 220 ohm resistor)
- All LED cathodes to GND
- 3.3V to push button, button to GPIO 15
- GPIO 15 to GND through 10K ohm pull-down resistor
Step 2: Upload and Run the Code
- Copy the complete code into the Wokwi code editor
- Click “Start Simulation” to begin
- Open the Serial Monitor (set to 115200 baud)
- The system will automatically connect and start sending test messages
Step 3: Observe the Five Reliability Pillars
| Pillar | What to Observe |
|---|---|
| CRC-16 | Watch for “CRC verified OK” or “CRC MISMATCH” in serial output |
| Sequence Numbers | Each message shows SEQ=N incrementing |
| ACK/NACK | See ACK messages received after successful delivery |
| Exponential Backoff | On retries, timeout doubles: 500ms, 1000ms, 2000ms, 4000ms |
| Connection State | Blue LED shows connection status; state transitions logged |
Step 4: Experiment with Network Conditions
Use serial commands to change simulation parameters:
| Command | Effect |
|---|---|
0 |
Perfect network (0% loss) |
3 |
Moderate loss (30%) |
7 |
High loss (70%) |
v |
Toggle verbose mode |
p |
Print statistics |
745.4.7 Understanding the Code
uint16_t calculateCRC16(const uint8_t* data, size_t length) {
uint16_t crc = CRC16_INITIAL; // 0xFFFF
for (size_t i = 0; i < length; i++) {
crc ^= ((uint16_t)data[i] << 8);
for (int bit = 0; bit < 8; bit++) {
if (crc & 0x8000) {
crc = (crc << 1) ^ CRC16_POLYNOMIAL; // 0x1021
} else {
crc = crc << 1;
}
}
}
return crc;
}This implements CRC-CCITT, the same algorithm used in Bluetooth, USB, and many IoT protocols. The polynomial 0x1021 provides excellent burst error detection.
int calculateBackoff(int retryCount) {
// Exponential: initial * 2^retry
int baseTimeout = INITIAL_TIMEOUT_MS * (1 << retryCount);
baseTimeout = min(baseTimeout, (int)MAX_TIMEOUT_MS);
// Random jitter: 0 to 25% of timeout
int jitter = random((baseTimeout * JITTER_PERCENT) / 100);
return baseTimeout + jitter;
}The (1 << retryCount) efficiently calculates 2^n. Jitter prevents collision storms when multiple devices retry simultaneously.
void transitionState(ConnectionState newState) {
Serial.printf("[STATE] %s -> %s\n",
stateNames[currentState], stateNames[newState]);
currentState = newState;
updateConnectionLED();
}The state machine ensures proper lifecycle: DISCONNECTED -> CONNECTING -> CONNECTED -> DISCONNECTING -> DISCONNECTED. Invalid transitions are rejected with warnings.
745.4.8 Challenge Exercises
Modify the code to support a sliding window with selective retransmission:
#define WINDOW_SIZE 4
uint16_t windowBase = 0;
bool acked[WINDOW_SIZE] = {false};
// Only retransmit specifically NACKed packets,
// not the entire windowGoal: Increase throughput by sending multiple messages before waiting for ACKs.
Implement CRC-32 alongside CRC-16 and compare:
uint32_t calculateCRC32(const uint8_t* data, size_t length);
// Add a configuration option to switch between CRC-16 and CRC-32
// Measure the CPU time differenceGoal: Understand the trade-off between error detection capability and computational overhead.
Add support for three QoS levels similar to MQTT:
- QoS 0: Fire and forget (no ACK)
- QoS 1: At least once (ACK required)
- QoS 2: Exactly once (two-phase handshake)
enum QoSLevel { QOS_0, QOS_1, QOS_2 };
bool sendWithQoS(const char* data, QoSLevel qos);Goal: Understand how different reliability levels affect latency and overhead.
745.4.9 Expected Outcomes
After completing this lab, you should observe:
| Metric | Expected Value (20% loss) | Meaning |
|---|---|---|
| Delivery Rate | 95-100% | Retries recover most losses |
| Avg Retries/Message | 0.3-0.5 | Some messages need 1-2 retries |
| CRC Errors | 2-5% of messages | Corruption detected and recovered |
| Throughput | ~0.15 msg/sec | Limited by timeouts and retries |
Key Insights:
- Reliability has a cost: Higher packet loss means more retries, more delay, and more power consumption
- CRC catches corruption: Bit errors are detected before corrupted data reaches the application
- Backoff prevents congestion: Exponential backoff reduces network load during problems
- State machines prevent leaks: Proper connection management ensures clean lifecycle
745.5 Knowledge Check
Question: A device in STATE_CONNECTED receives no keep-alive response 3 times in a row. What should happen?
- Continue in CONNECTED state
- Transition to CONNECTING and retry
- Transition to DISCONNECTED
- Transition to DISCONNECTING then DISCONNECTED
Click for answer
Answer: C) Transition to DISCONNECTED
After multiple keep-alive failures, the connection should be considered dead. Transitioning directly to DISCONNECTED (not through DISCONNECTING) is appropriate because there is no remote peer to notify - the connection has already failed.Question: A device connected through a NAT router with 60-second idle timeout uses 90-second keep-alive intervals. What will likely happen?
- Connection works normally
- NAT drops the connection silently between keep-alives
- NAT sends an error message to the device
- Keep-alive interval automatically adjusts
Click for answer
Answer: B) NAT drops the connection silently between keep-alives
NAT routers silently drop idle connections when their timeout expires. With 90-second keep-alive and 60-second NAT timeout, the NAT table entry expires before the next keep-alive. The device believes it is connected, but packets no longer reach the server.745.6 Summary
This chapter covered connection state management and provided a comprehensive hands-on lab:
Connection State Machine: - Four states: DISCONNECTED, CONNECTING, CONNECTED, DISCONNECTING - Proper transitions prevent resource leaks - Keep-alive monitors connection health
Lab Implementation: - CRC-16 error detection with polynomial division - Exponential backoff with configurable jitter - Sequence numbers for loss/duplicate detection - Full statistics tracking
Key Trade-offs:
| Feature | Benefit | Cost |
|---|---|---|
| Keep-alive | Detects dead connections | Power, bandwidth |
| More retries | Higher delivery rate | Latency, power |
| Longer timeout | Fewer false timeouts | Slower failure detection |
What is Next: Apply these concepts to specific protocols in chapters on CoAP Confirmable messages, MQTT QoS levels, and TCP optimization for IoT.
- Reliability Overview: Parent chapter with all five reliability pillars
- Error Detection: CRC and checksums deep dive
- Retry and Sequencing: Exponential backoff and sequence numbers
- CoAP Features: Confirmable messages in practice
- MQTT QoS: Quality of Service implementation