674  IoT Protocol Review: Hands-On Labs

674.1 Learning Objectives

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

  • Analyze Protocol Overhead: Calculate header sizes and efficiency for different protocol stacks
  • Build Packet Analyzers: Implement ESP32 tools to inspect protocol structures
  • Debug Protocol Issues: Use low-level analysis to diagnose communication problems
  • Optimize Message Size: Design efficient payloads minimizing protocol overhead

674.2 Prerequisites

Required Chapters: - Protocol Review: Visual Summaries - Protocol stack architecture - IoT Protocols Fundamentals - Core concepts

Technical Background: - Protocol stack concepts - C/C++ programming for ESP32 - Understanding of bytes and bits

Hardware Required: - ESP32 development board - USB cable - Computer with Arduino IDE

Estimated Time: 25 minutes

What is protocol overhead? Protocol overhead is the β€œwrapper” that protocols add to your actual data. Like shipping a small item in a big box with lots of packing material, protocols add headers that can be much larger than your actual payload.

Why does this matter? For IoT devices: - Battery life: More bytes = more radio time = shorter battery life - Network capacity: Less overhead = more devices per gateway - Cost: Less airtime = lower cellular/network costs

Example: Sending a 4-byte temperature reading (23.5C) might require: - 66 bytes total with uncompressed IPv6 - 26 bytes total with 6LoWPAN compression - That’s 40 bytes saved per message!

674.3 Lab 1: ESP32 Protocol Packet Analyzer

Time: ~20 min | Level: Advanced | Code: P07.C06.U01

Objective: Analyze IoT protocol packets to understand overhead and compare different protocol stacks.

674.3.1 Code

#include <Wi-Fi.h>

// Simulated protocol headers
struct ProtocolHeaders {
  // Data Link Layer
  uint8_t mac_dst[6];      // 6 bytes
  uint8_t mac_src[6];      // 6 bytes
  uint16_t mac_type;       // 2 bytes
  // Total Ethernet: 14 bytes (without FCS)

  // Network Layer (IPv6 simplified)
  uint8_t ip_version;      // 4 bits + traffic class/flow
  uint16_t ip_payload_len; // 2 bytes
  uint8_t ip_next_header;  // 1 byte
  uint8_ip_hop_limit;      // 1 byte
  uint8_t ip_src[16];      // 16 bytes
  uint8_t ip_dst[16];      // 16 bytes
  // Total IPv6: 40 bytes

  // Transport Layer (UDP)
  uint16_t udp_src_port;   // 2 bytes
  uint16_t udp_dst_port;   // 2 bytes
  uint16_t udp_length;     // 2 bytes
  uint16_t udp_checksum;   // 2 bytes
  // Total UDP: 8 bytes

  // Application Layer (CoAP minimal)
  uint8_t coap_version_type_tkl;  // 1 byte
  uint8_t coap_code;              // 1 byte
  uint16_t coap_message_id;       // 2 bytes
  // Total CoAP: 4 bytes minimum

  // Payload
  uint8_t payload[4];     // 4 bytes actual data
};

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

  Serial.println("\n╔════════════════════════════════════════╗");
  Serial.println("β•‘  IoT Protocol Packet Analyzer          β•‘");
  Serial.println("β•‘  ESP32 Protocol Overhead Inspector     β•‘");
  Serial.println("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n");

  // Analyze different protocol stacks
  analyzeProtocolStacks();

  // Demonstrate 6LoWPAN compression benefit
  demonstrate6LoWPANCompression();

  // Show payload efficiency
  analyzePayloadEfficiency();
}

void loop() {
  // Analysis runs once
}

void analyzeProtocolStacks() {
  Serial.println("═══════════════════════════════════════════════════");
  Serial.println("PROTOCOL STACK OVERHEAD ANALYSIS (4-byte payload)");
  Serial.println("═══════════════════════════════════════════════════\n");

  // Stack 1: CoAP/UDP/IPv6/Ethernet (uncompressed)
  printStackAnalysis(
    "CoAP/UDP/IPv6/Ethernet",
    14,  // Ethernet
    40,  // IPv6
    8,   // UDP
    4,   // CoAP
    4    // Payload
  );

  // Stack 2: CoAP/UDP/IPv6/802.15.4 with 6LoWPAN
  printStackAnalysis(
    "CoAP/UDP/IPv6/802.15.4 (6LoWPAN)",
    8,   // 802.15.4 compressed
    6,   // IPv6 compressed
    4,   // UDP compressed
    4,   // CoAP
    4    // Payload
  );

  // Stack 3: MQTT/TCP/IPv6/Ethernet
  printStackAnalysis(
    "MQTT/TCP/IPv6/Ethernet",
    14,  // Ethernet
    40,  // IPv6
    20,  // TCP minimum
    2,   // MQTT minimum
    4    // Payload
  );

  // Stack 4: HTTP/TCP/IPv4/Ethernet
  printStackAnalysis(
    "HTTP/TCP/IPv4/Ethernet",
    14,  // Ethernet
    20,  // IPv4
    20,  // TCP
    50,  // HTTP minimum
    4    // Payload
  );
}

void printStackAnalysis(const char* name, int datalink, int network,
                       int transport, int application, int payload) {
  int total_overhead = datalink + network + transport + application;
  int total_packet = total_overhead + payload;
  float efficiency = (float)payload / total_packet * 100.0;

  Serial.println("─────────────────────────────────────────────────");
  Serial.printf("Stack: %s\n", name);
  Serial.println("─────────────────────────────────────────────────");
  Serial.printf("Data Link:    %3d bytes\n", datalink);
  Serial.printf("Network:      %3d bytes\n", network);
  Serial.printf("Transport:    %3d bytes\n", transport);
  Serial.printf("Application:  %3d bytes\n", application);
  Serial.println("                ─────────");
  Serial.printf("Total Overhead: %3d bytes\n", total_overhead);
  Serial.printf("Payload:        %3d bytes\n", payload);
  Serial.println("                ═════════");
  Serial.printf("TOTAL PACKET:   %3d bytes\n\n", total_packet);

  Serial.printf("Payload Efficiency: %.2f%%\n", efficiency);
  Serial.printf("Overhead Ratio: %.2fΓ— payload\n\n", (float)total_overhead/payload);
}

void demonstrate6LoWPANCompression() {
  Serial.println("\n═══════════════════════════════════════════════════");
  Serial.println("6LoWPAN COMPRESSION BENEFIT");
  Serial.println("═══════════════════════════════════════════════════\n");

  Serial.println("IPv6 Header Compression:");
  Serial.println("  Uncompressed IPv6: 40 bytes");
  Serial.println("    - Version (4 bits)");
  Serial.println("    - Traffic Class (8 bits)");
  Serial.println("    - Flow Label (20 bits)");
  Serial.println("    - Payload Length (16 bits)");
  Serial.println("    - Next Header (8 bits)");
  Serial.println("    - Hop Limit (8 bits)");
  Serial.println("    - Source Address (128 bits = 16 bytes)");
  Serial.println("    - Destination Address (128 bits = 16 bytes)");
  Serial.println();

  Serial.println("  6LoWPAN Compressed: 6 bytes (85% reduction!)");
  Serial.println("    - Context-based compression");
  Serial.println("    - Link-local addresses derived from MAC");
  Serial.println("    - Omit hop limit (use default)");
  Serial.println("    - Omit flow label (not needed)");
  Serial.println();

  Serial.println("Total Savings:");
  Serial.println("  Before: 40 + 25 = 65 bytes (IPv6 + 802.15.4)");
  Serial.println("  After:   6 +  8 = 14 bytes (compressed)");
  Serial.println("  Savings: 51 bytes = 78% reduction");
  Serial.println();
}

void analyzePayloadEfficiency() {
  Serial.println("═══════════════════════════════════════════════════");
  Serial.println("PAYLOAD SIZE IMPACT ON EFFICIENCY");
  Serial.println("═══════════════════════════════════════════════════\n");

  Serial.println("CoAP/UDP/IPv6/802.15.4 (6LoWPAN compressed):");
  Serial.println("Fixed overhead: 22 bytes\n");

  Serial.println("Payload   Total Packet   Efficiency");
  Serial.println("(bytes)   (bytes)        (%)");
  Serial.println("──────────────────────────────────");

  int overhead = 22;
  int payloads[] = {4, 8, 16, 32, 64, 105};  // 105 = max for 127-byte packet

  for (int i = 0; i < 6; i++) {
    int payload = payloads[i];
    int total = overhead + payload;
    float efficiency = (float)payload / total * 100.0;

    char line[50];
    sprintf(line, "%4d      %4d           %.1f%%", payload, total, efficiency);
    Serial.println(line);
  }

  Serial.println("\nβœ“ Key Insight: Larger payloads dramatically improve efficiency!");
  Serial.println("  Aggregate multiple sensor readings when possible.");
}

674.3.2 Expected Serial Output

╔════════════════════════════════════════╗
β•‘  IoT Protocol Packet Analyzer          β•‘
β•‘  ESP32 Protocol Overhead Inspector     β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

═══════════════════════════════════════════════════
PROTOCOL STACK OVERHEAD ANALYSIS (4-byte payload)
═══════════════════════════════════════════════════

─────────────────────────────────────────────────
Stack: CoAP/UDP/IPv6/Ethernet
─────────────────────────────────────────────────
Data Link:     14 bytes
Network:       40 bytes
Transport:      8 bytes
Application:    4 bytes
                ─────────
Total Overhead:  66 bytes
Payload:         4 bytes
                ═════════
TOTAL PACKET:   70 bytes

Payload Efficiency: 5.71%
Overhead Ratio: 16.50Γ— payload

[... similar for other stacks ...]

═══════════════════════════════════════════════════
6LoWPAN COMPRESSION BENEFIT
═══════════════════════════════════════════════════

IPv6 Header Compression:
  Uncompressed IPv6: 40 bytes
    - Version (4 bits)
    - Traffic Class (8 bits)
    - Flow Label (20 bits)
    - Payload Length (16 bits)
    - Next Header (8 bits)
    - Hop Limit (8 bits)
    - Source Address (128 bits = 16 bytes)
    - Destination Address (128 bits = 16 bytes)

  6LoWPAN Compressed: 6 bytes (85% reduction!)
    - Context-based compression
    - Link-local addresses derived from MAC
    - Omit hop limit (use default)
    - Omit flow label (not needed)

Total Savings:
  Before: 40 + 25 = 65 bytes (IPv6 + 802.15.4)
  After:   6 +  8 = 14 bytes (compressed)
  Savings: 51 bytes = 78% reduction

═══════════════════════════════════════════════════
PAYLOAD SIZE IMPACT ON EFFICIENCY
═══════════════════════════════════════════════════

CoAP/UDP/IPv6/802.15.4 (6LoWPAN compressed):
Fixed overhead: 22 bytes

Payload   Total Packet   Efficiency
(bytes)   (bytes)        (%)
──────────────────────────────────
   4        26           15.4%
   8        30           26.7%
  16        38           42.1%
  32        54           59.3%
  64        86           74.4%
 105       127           82.7%

βœ“ Key Insight: Larger payloads dramatically improve efficiency!
  Aggregate multiple sensor readings when possible.

674.3.3 Lab Analysis

TipKey Observations from Lab 1
  1. 6LoWPAN compression is essential: Reduces IPv6 overhead from 40 bytes to 6 bytes (85% reduction)

  2. Protocol stack matters more than application header: MQTT’s 2-byte header is smaller than CoAP’s 4-byte header, but MQTT requires TCP (20 bytes) vs UDP (8 bytes)

  3. Payload aggregation improves efficiency:

    • 4-byte payload: 15.4% efficiency
    • 64-byte payload: 74.4% efficiency
    • Consider batching sensor readings!
  4. HTTP is unsuitable for constrained devices: 104+ bytes of overhead for tiny payloads

674.4 Lab 2: Overhead Calculator Tool

Objective: Create a reusable calculator to compare protocol efficiency.

674.4.1 Python Implementation

#!/usr/bin/env python3
"""
IoT Protocol Overhead Calculator
Compare different protocol stacks for efficiency analysis
"""

# Protocol header sizes (bytes)
HEADERS = {
    # Data Link Layer
    'ethernet': 14,
    '802.15.4': 25,
    '802.15.4_compressed': 8,
    'ble': 10,
    'lorawan': 13,

    # Network Layer
    'ipv4': 20,
    'ipv6': 40,
    '6lowpan': 6,  # Compressed IPv6

    # Transport Layer
    'tcp': 20,
    'udp': 8,
    'udp_compressed': 4,  # With 6LoWPAN NHC

    # Application Layer
    'http': 50,  # Minimum
    'mqtt': 2,   # Minimum fixed header
    'coap': 4,   # Base header
    'amqp': 8,   # Frame header
}

# Common protocol stacks
STACKS = {
    'http_tcp_ipv4_eth': ['ethernet', 'ipv4', 'tcp', 'http'],
    'mqtt_tcp_ipv6_wifi': ['ethernet', 'ipv6', 'tcp', 'mqtt'],
    'mqtt_tcp_6lowpan': ['802.15.4_compressed', '6lowpan', 'tcp', 'mqtt'],
    'coap_udp_ipv6_eth': ['ethernet', 'ipv6', 'udp', 'coap'],
    'coap_udp_6lowpan': ['802.15.4_compressed', '6lowpan', 'udp_compressed', 'coap'],
    'lorawan_mac': ['lorawan'],  # LoRaWAN uses MAC-layer addressing
}

def calculate_overhead(stack_name):
    """Calculate total overhead for a protocol stack."""
    if stack_name not in STACKS:
        raise ValueError(f"Unknown stack: {stack_name}")

    stack = STACKS[stack_name]
    total = sum(HEADERS[layer] for layer in stack)
    return total, stack

def compare_stacks(payload_size):
    """Compare all stacks for a given payload size."""
    print(f"\n{'='*60}")
    print(f"PROTOCOL STACK COMPARISON (Payload: {payload_size} bytes)")
    print(f"{'='*60}\n")

    results = []
    for name in STACKS:
        overhead, layers = calculate_overhead(name)
        total = overhead + payload_size
        efficiency = (payload_size / total) * 100
        results.append((name, overhead, total, efficiency, layers))

    # Sort by efficiency (descending)
    results.sort(key=lambda x: x[3], reverse=True)

    print(f"{'Stack':<25} {'Overhead':>10} {'Total':>8} {'Efficiency':>12}")
    print("-" * 60)

    for name, overhead, total, eff, layers in results:
        print(f"{name:<25} {overhead:>10}B {total:>7}B {eff:>11.1f}%")

    # Best and worst
    best = results[0]
    worst = results[-1]
    print(f"\nβœ“ Best: {best[0]} ({best[3]:.1f}% efficiency)")
    print(f"βœ— Worst: {worst[0]} ({worst[3]:.1f}% efficiency)")
    print(f"  Difference: {worst[2] - best[2]} bytes, {worst[3]/best[3]:.1f}x less efficient")

    return results

def battery_life_estimate(stack_name, payload_size, tx_interval_sec,
                         battery_mah=2000, tx_current_ma=20,
                         data_rate_bps=250000):
    """Estimate battery life for a protocol stack."""
    overhead, _ = calculate_overhead(stack_name)
    total_bytes = overhead + payload_size

    # Calculate transmission time per message
    tx_time_sec = (total_bytes * 8) / data_rate_bps

    # Messages per day
    messages_per_day = 86400 / tx_interval_sec

    # Daily active time
    daily_active_sec = messages_per_day * tx_time_sec

    # Daily energy consumption (mAh)
    daily_mah = (tx_current_ma * daily_active_sec) / 3600

    # Add sleep current (5 uA typical)
    sleep_mah = (0.005 * (86400 - daily_active_sec)) / 3600

    total_daily_mah = daily_mah + sleep_mah

    # Battery life in days
    battery_days = battery_mah / total_daily_mah
    battery_years = battery_days / 365

    return {
        'total_bytes': total_bytes,
        'tx_time_ms': tx_time_sec * 1000,
        'daily_messages': messages_per_day,
        'daily_mah': total_daily_mah,
        'battery_days': battery_days,
        'battery_years': battery_years
    }

# Example usage
if __name__ == "__main__":
    # Compare stacks for different payload sizes
    for payload in [4, 16, 64]:
        compare_stacks(payload)

    # Battery life comparison
    print(f"\n{'='*60}")
    print("BATTERY LIFE COMPARISON (10-minute intervals, 4-byte payload)")
    print(f"{'='*60}\n")

    stacks_to_compare = ['coap_udp_6lowpan', 'mqtt_tcp_6lowpan', 'http_tcp_ipv4_eth']

    for stack in stacks_to_compare:
        result = battery_life_estimate(stack, 4, 600)  # 10 minutes
        print(f"{stack}:")
        print(f"  Packet: {result['total_bytes']} bytes, TX time: {result['tx_time_ms']:.2f}ms")
        print(f"  Daily energy: {result['daily_mah']:.4f} mAh")
        print(f"  Battery life: {result['battery_years']:.1f} years\n")

674.4.2 Expected Output

============================================================
PROTOCOL STACK COMPARISON (Payload: 4 bytes)
============================================================

Stack                        Overhead    Total   Efficiency
------------------------------------------------------------
lorawan_mac                       13B      17B        23.5%
coap_udp_6lowpan                  22B      26B        15.4%
mqtt_tcp_6lowpan                  36B      40B        10.0%
coap_udp_ipv6_eth                 66B      70B         5.7%
mqtt_tcp_ipv6_wifi                76B      80B         5.0%
http_tcp_ipv4_eth                104B     108B         3.7%

βœ“ Best: lorawan_mac (23.5% efficiency)
βœ— Worst: http_tcp_ipv4_eth (3.7% efficiency)
  Difference: 91 bytes, 6.4x less efficient

============================================================
BATTERY LIFE COMPARISON (10-minute intervals, 4-byte payload)
============================================================

coap_udp_6lowpan:
  Packet: 26 bytes, TX time: 0.83ms
  Daily energy: 0.0012 mAh
  Battery life: 45.7 years

mqtt_tcp_6lowpan:
  Packet: 40 bytes, TX time: 1.28ms
  Daily energy: 0.0018 mAh
  Battery life: 30.1 years

http_tcp_ipv4_eth:
  Packet: 108 bytes, TX time: 3.46ms
  Daily energy: 0.0050 mAh
  Battery life: 11.0 years

674.5 Lab Extension: Real-Time Packet Analysis

For real protocol analysis, use Wireshark with these filters:

CoAP Traffic:

coap && ip.dst == 192.168.1.100

MQTT Traffic:

mqtt && tcp.port == 1883

6LoWPAN over 802.15.4:

wpan && 6lowpan

Key Fields to Analyze: - Frame length (total bytes on wire) - Protocol header sizes at each layer - Payload extraction and efficiency calculation

674.6 Summary

TipKey Takeaways from Labs

ESP32 Packet Analyzer (Lab 1): - Demonstrated header sizes for common protocol stacks - 6LoWPAN compression: 40-byte IPv6 header to 6 bytes (85% reduction) - Protocol efficiency varies from 3.7% (HTTP) to 82.7% (max CoAP payload)

Overhead Calculator (Lab 2): - LoRaWAN most efficient for small payloads (23.5% at 4 bytes) - CoAP/UDP/6LoWPAN best for IP-based constrained networks - HTTP unsuitable for battery-powered IoT devices

Battery Life Impact: - Protocol choice can mean 4x difference in battery life - For frequent transmissions, use CoAP/UDP over MQTT/TCP - Aggregate payloads when possible to improve efficiency

Optimization Strategies: 1. Use 6LoWPAN for 802.15.4 networks (mandatory for efficiency) 2. Prefer UDP over TCP for constrained devices 3. Batch sensor readings to maximize payload efficiency 4. Consider LoRaWAN MAC-layer for LPWAN (no IP overhead)

674.7 What’s Next

Continue with the protocol review series:

For protocol deep dives: - MQTT: Publish-subscribe messaging - CoAP Architecture: RESTful protocol for IoT - 6LoWPAN Fundamentals: IPv6 compression details