51  Lab: Network Packet Simulator

Key Concepts
  • Packet Simulator: Software (e.g., NS-3, Cooja, OMNeT++) that models network behaviour as discrete packet transmission events without physical hardware
  • Simulation Time: The simulated clock inside the simulator, which may run faster or slower than wall-clock time depending on model complexity
  • Radio Model: The simulator’s representation of wireless channel behaviour, including path loss, multipath, and interference
  • Mobility Model: A mathematical description of how nodes move over time during a simulation (e.g., random waypoint, Gauss-Markov)
  • Traffic Generator: A simulation component that creates packet flows at specified rates to load the simulated network
  • Trace File: A log of all simulated events (packet send, receive, drop) used for post-simulation analysis
  • Confidence Interval: A statistical range within which the true mean of a simulation metric lies with specified probability; computed over multiple independent runs

51.1 In 60 Seconds

This hands-on ESP32 lab simulates network packet transmission using LEDs to visualize packet states (transmitting, received, success, error). You will construct packets with headers, payloads, and checksums, then observe how error detection works in practice by introducing deliberate corruption and watching checksum verification catch the errors.

51.2 Learning Objectives

By completing this lab, you will be able to:

  • Construct a packet structure: Identify and explain the components of a network packet (header, payload, checksum) and build one in ESP32 C++ code
  • Demonstrate transmission states: Apply LED indicators to represent distinct stages of packet transmission through a finite state machine
  • Implement error detection: Calculate and verify checksums for data integrity using the ones’ complement algorithm (RFC 1071)
  • Analyze packet communication: Apply serial output inspection to identify transmission errors and differentiate between corrupted and valid packets
  • Calculate protocol overhead: Evaluate the trade-off between error detection strength (checksum vs CRC32) and bandwidth efficiency for constrained IoT links

A network packet is a small chunk of data that travels across a network, like a postcard traveling through the postal system. Each packet carries an address (header), the message itself (payload), and a verification stamp (checksum) to make sure nothing was damaged in transit. In this lab, you will simulate sending packets between devices, watching LEDs indicate what is happening at each step. It is a hands-on way to understand what normally happens invisibly inside your Wi-Fi network.

51.3 Components Needed

Component Quantity Purpose
ESP32 DevKit 1 Microcontroller for packet simulation
Red LED 1 Error indicator (checksum failure)
Green LED 1 Success indicator (valid packet)
Yellow LED 1 Transmission in progress
Blue LED 1 Packet received indicator
220 ohm Resistors 4 Current limiting for LEDs
Breadboard 1 Circuit assembly
Jumper Wires Several Connections

51.4 Wokwi Simulator

Use the embedded simulator below to build and test your packet simulator circuit. Click “Start Simulation” to begin.

51.5 Circuit Diagram

Circuit diagram showing ESP32 DevKit connected to four LEDs through 220 ohm resistors: red LED on GPIO 2 for errors, green LED on GPIO 4 for success, yellow LED on GPIO 5 for transmission, and blue LED on GPIO 18 for packet received, all connected to common ground
Figure 51.1: Circuit diagram showing ESP32 connections to four LED indicators for packet state visualization

51.6 Packet Overhead and Efficiency

Before diving into the code, consider how packet structure affects transmission efficiency. For a packet with a 4-byte header, a variable-length payload, and a 2-byte checksum, the overhead ratio depends on how much payload you send relative to the fixed header and checksum bytes.

Try It: Packet Overhead Calculator

51.7 Complete Code

Copy this code into the Wokwi editor and upload to the ESP32.

/*
 * Network Packet Simulator for ESP32
 *
 * This program demonstrates basic networking concepts:
 * - Packet structure (header, payload, checksum)
 * - Transmission state visualization with LEDs
 * - Error detection using checksum verification
 *
 * LED Indicators:
 * - Red (GPIO 2):    Error - checksum verification failed
 * - Green (GPIO 4):  Success - packet transmitted/received correctly
 * - Yellow (GPIO 5): Transmitting - packet in transit
 * - Blue (GPIO 18):  Received - packet arrival indicator
 */

// LED Pin Definitions
#define LED_ERROR     2   // Red LED - Error indicator
#define LED_SUCCESS   4   // Green LED - Success indicator
#define LED_TRANSMIT  5   // Yellow LED - Transmission in progress
#define LED_RECEIVED  18  // Blue LED - Packet received

// Packet Structure Constants
#define HEADER_SIZE   4   // Bytes for header (version, type, length, sequence)
#define MAX_PAYLOAD   32  // Maximum payload size in bytes
#define CHECKSUM_SIZE 2   // 16-bit checksum

// Packet Types
#define PKT_DATA      0x01  // Data packet
#define PKT_ACK       0x02  // Acknowledgment packet
#define PKT_NACK      0x03  // Negative acknowledgment
#define PKT_PING      0x04  // Ping/heartbeat packet

// Protocol Version
#define PROTOCOL_VERSION 0x01

// Structure representing a network packet
struct Packet {
  // Header fields (4 bytes)
  uint8_t version;      // Protocol version
  uint8_t type;         // Packet type (DATA, ACK, NACK, PING)
  uint8_t length;       // Payload length
  uint8_t sequence;     // Sequence number for ordering

  // Payload (variable length, max 32 bytes)
  uint8_t payload[MAX_PAYLOAD];

  // Checksum (2 bytes)
  uint16_t checksum;
};

// Global variables
uint8_t sequenceNumber = 0;
bool simulateError = false;  // Toggle to simulate transmission errors

// Function prototypes
void initializeLEDs();
void setLEDState(int led, bool state);
void blinkLED(int led, int times, int delayMs);
uint16_t calculateChecksum(Packet* pkt);
bool verifyChecksum(Packet* pkt);
void createPacket(Packet* pkt, uint8_t type, const char* data);
void transmitPacket(Packet* pkt);
void receivePacket(Packet* pkt);
void printPacketDetails(Packet* pkt, const char* label);
void demonstratePacketFlow();

void setup() {
  // Initialize serial communication for debugging
  Serial.begin(115200);
  delay(1000);

  Serial.println("\n");
  Serial.println("========================================");
  Serial.println("   Network Packet Simulator v1.0");
  Serial.println("   ESP32 IoT Learning Lab");
  Serial.println("========================================\n");

  // Initialize LED pins
  initializeLEDs();

  // Startup LED test sequence
  Serial.println("Testing LEDs...");
  blinkLED(LED_ERROR, 2, 200);
  blinkLED(LED_SUCCESS, 2, 200);
  blinkLED(LED_TRANSMIT, 2, 200);
  blinkLED(LED_RECEIVED, 2, 200);

  Serial.println("\nLED Test Complete!");
  Serial.println("- Red:    Error indicator");
  Serial.println("- Green:  Success indicator");
  Serial.println("- Yellow: Transmission indicator");
  Serial.println("- Blue:   Received indicator\n");

  delay(1000);
}

void loop() {
  // Demonstrate packet transmission and reception
  demonstratePacketFlow();

  // Wait before next demonstration cycle
  Serial.println("\n--- Waiting 5 seconds before next cycle ---\n");
  delay(5000);
}

// Initialize all LED pins as outputs
void initializeLEDs() {
  pinMode(LED_ERROR, OUTPUT);
  pinMode(LED_SUCCESS, OUTPUT);
  pinMode(LED_TRANSMIT, OUTPUT);
  pinMode(LED_RECEIVED, OUTPUT);

  // Turn off all LEDs initially
  digitalWrite(LED_ERROR, LOW);
  digitalWrite(LED_SUCCESS, LOW);
  digitalWrite(LED_TRANSMIT, LOW);
  digitalWrite(LED_RECEIVED, LOW);
}

// Set LED state (on/off)
void setLEDState(int led, bool state) {
  digitalWrite(led, state ? HIGH : LOW);
}

// Blink an LED a specified number of times
void blinkLED(int led, int times, int delayMs) {
  for (int i = 0; i < times; i++) {
    digitalWrite(led, HIGH);
    delay(delayMs);
    digitalWrite(led, LOW);
    delay(delayMs);
  }
}

// Calculate 16-bit checksum using ones' complement sum
// This is a simplified version of the Internet Checksum (RFC 1071)
uint16_t calculateChecksum(Packet* pkt) {
  uint32_t sum = 0;

  // Add header bytes
  sum += pkt->version;
  sum += pkt->type;
  sum += pkt->length;
  sum += pkt->sequence;

  // Add payload bytes
  for (int i = 0; i < pkt->length; i++) {
    sum += pkt->payload[i];
  }

  // Fold 32-bit sum into 16-bit (handle overflow)
  while (sum >> 16) {
    sum = (sum & 0xFFFF) + (sum >> 16);
  }

  // Return one's complement
  return ~((uint16_t)sum);
}

// Verify packet checksum by recomputing and comparing
bool verifyChecksum(Packet* pkt) {
  uint16_t stored = pkt->checksum;
  pkt->checksum = 0;
  uint16_t computed = calculateChecksum(pkt);
  pkt->checksum = stored;

  return (stored == computed);
}

// Create a new packet with specified type and data
void createPacket(Packet* pkt, uint8_t type, const char* data) {
  // Clear packet memory
  memset(pkt, 0, sizeof(Packet));

  // Set header fields
  pkt->version = PROTOCOL_VERSION;
  pkt->type = type;
  pkt->sequence = sequenceNumber++;

  // Copy payload data
  if (data != NULL) {
    pkt->length = min(strlen(data), (size_t)MAX_PAYLOAD);
    memcpy(pkt->payload, data, pkt->length);
  } else {
    pkt->length = 0;
  }

  // Calculate and set checksum
  pkt->checksum = calculateChecksum(pkt);
}

// Simulate packet transmission
void transmitPacket(Packet* pkt) {
  Serial.println(">>> TRANSMITTING PACKET >>>");

  // Turn on transmission LED
  setLEDState(LED_TRANSMIT, true);

  // Print packet being sent
  printPacketDetails(pkt, "TX");

  // Simulate transmission delay (represents actual network latency)
  Serial.println("    Transmitting...");
  delay(500);

  // Simulate network effects
  if (simulateError) {
    Serial.println("    [!] Simulating network error - corrupting data");
    // Corrupt one byte of payload to demonstrate error detection
    if (pkt->length > 0) {
      pkt->payload[0] ^= 0xFF;  // Flip all bits of first byte
    }
  }

  delay(500);

  // Turn off transmission LED
  setLEDState(LED_TRANSMIT, false);

  Serial.println("    Transmission complete.\n");
}

// Simulate packet reception and verification
void receivePacket(Packet* pkt) {
  Serial.println("<<< RECEIVING PACKET <<<");

  // Turn on received LED
  setLEDState(LED_RECEIVED, true);
  delay(300);

  // Print received packet
  printPacketDetails(pkt, "RX");

  // Verify checksum
  Serial.println("    Verifying checksum...");
  delay(300);

  bool checksumValid = verifyChecksum(pkt);

  if (checksumValid) {
    Serial.println("    [OK] Checksum VALID - Packet integrity confirmed!");

    // Success indication
    setLEDState(LED_RECEIVED, false);
    setLEDState(LED_SUCCESS, true);
    blinkLED(LED_SUCCESS, 3, 150);
    setLEDState(LED_SUCCESS, false);
  } else {
    Serial.println("    [ERROR] Checksum INVALID - Packet corrupted!");
    Serial.println("    [ERROR] Request retransmission (NACK)");

    // Error indication
    setLEDState(LED_RECEIVED, false);
    setLEDState(LED_ERROR, true);
    blinkLED(LED_ERROR, 5, 100);
    setLEDState(LED_ERROR, false);
  }

  Serial.println();
}

// Print detailed packet information
void printPacketDetails(Packet* pkt, const char* label) {
  Serial.println("    +------------------------------------------+");
  Serial.printf("    | %s Packet Details                         |\n", label);
  Serial.println("    +------------------------------------------+");

  // Header section
  Serial.println("    | HEADER:                                  |");
  Serial.printf("    |   Version:  0x%02X                          |\n", pkt->version);
  Serial.printf("    |   Type:     0x%02X ", pkt->type);

  // Print packet type name
  switch(pkt->type) {
    case PKT_DATA: Serial.println("(DATA)                    |"); break;
    case PKT_ACK:  Serial.println("(ACK)                     |"); break;
    case PKT_NACK: Serial.println("(NACK)                    |"); break;
    case PKT_PING: Serial.println("(PING)                    |"); break;
    default:       Serial.println("(UNKNOWN)                 |"); break;
  }

  Serial.printf("    |   Length:   %d bytes                       |\n", pkt->length);
  Serial.printf("    |   Sequence: %d                             |\n", pkt->sequence);

  // Payload section
  Serial.println("    | PAYLOAD:                                 |");
  if (pkt->length > 0) {
    Serial.print("    |   Data: \"");
    for (int i = 0; i < pkt->length && i < 20; i++) {
      if (pkt->payload[i] >= 32 && pkt->payload[i] < 127) {
        Serial.print((char)pkt->payload[i]);
      } else {
        Serial.print('.');
      }
    }
    if (pkt->length > 20) Serial.print("...");
    Serial.println("\"");

    // Hex dump of payload
    Serial.print("    |   Hex:  ");
    for (int i = 0; i < min(pkt->length, (uint8_t)8); i++) {
      Serial.printf("%02X ", pkt->payload[i]);
    }
    if (pkt->length > 8) Serial.print("...");
    Serial.println();
  } else {
    Serial.println("    |   (empty)                                |");
  }

  // Checksum section
  Serial.println("    | CHECKSUM:                                |");
  Serial.printf("    |   Value: 0x%04X                          |\n", pkt->checksum);
  Serial.println("    +------------------------------------------+");
}

// Main demonstration function
void demonstratePacketFlow() {
  static int demoStep = 0;
  Packet packet;

  Serial.println("============================================");
  Serial.printf("      DEMONSTRATION CYCLE %d\n", demoStep + 1);
  Serial.println("============================================\n");

  switch (demoStep) {
    case 0:
      // Demo 1: Simple data packet (no errors)
      Serial.println("[Demo 1] Sending DATA packet - Normal transmission\n");
      simulateError = false;
      createPacket(&packet, PKT_DATA, "Hello IoT World!");
      transmitPacket(&packet);
      receivePacket(&packet);
      break;

    case 1:
      // Demo 2: Sensor reading packet
      Serial.println("[Demo 2] Sending sensor data packet\n");
      simulateError = false;
      createPacket(&packet, PKT_DATA, "TEMP:25.5C,HUM:65%");
      transmitPacket(&packet);
      receivePacket(&packet);
      break;

    case 2:
      // Demo 3: Corrupted packet (simulated error)
      Serial.println("[Demo 3] Simulating CORRUPTED packet\n");
      simulateError = true;
      createPacket(&packet, PKT_DATA, "Critical Alert!");
      transmitPacket(&packet);
      receivePacket(&packet);
      simulateError = false;
      break;

    case 3:
      // Demo 4: Ping packet
      Serial.println("[Demo 4] Sending PING packet (heartbeat)\n");
      simulateError = false;
      createPacket(&packet, PKT_PING, NULL);
      transmitPacket(&packet);
      receivePacket(&packet);
      break;

    case 4:
      // Demo 5: ACK packet
      Serial.println("[Demo 5] Sending ACK packet\n");
      simulateError = false;
      createPacket(&packet, PKT_ACK, "OK");
      transmitPacket(&packet);
      receivePacket(&packet);
      break;
  }

  // Cycle through demos
  demoStep = (demoStep + 1) % 5;
}

51.8 Step-by-Step Instructions

51.8.1 Step 1: Set Up the Circuit

  1. Open the Wokwi simulator above
  2. Add an ESP32 DevKit to your workspace
  3. Add 4 LEDs (red, green, yellow, blue) to the breadboard
  4. Add 4 x 220 ohm resistors for current limiting
  5. Connect each LED through its resistor to the specified GPIO pins:
    • Red LED: GPIO 2
    • Green LED: GPIO 4
    • Yellow LED: GPIO 5
    • Blue LED: GPIO 18
  6. Connect all LED cathodes (short legs) to GND

51.8.2 Step 2: Upload the Code

  1. Copy the complete code above
  2. Paste it into the Wokwi code editor (replacing any existing code)
  3. Click the “Start Simulation” button

51.8.3 Step 3: Observe the Output

Watch the Serial Monitor for detailed packet information:

  1. Startup: All LEDs blink in sequence during self-test
  2. Transmission: Yellow LED lights during packet sending
  3. Reception: Blue LED indicates packet arrival
  4. Success: Green LED blinks 3 times for valid checksum
  5. Error: Red LED blinks 5 times for corrupted packets

51.8.4 Step 4: Understand the Packet Structure

Study the Serial Monitor output to identify:

+------------------------------------------+
| TX Packet Details                        |
+------------------------------------------+
| HEADER:                                  |
|   Version:  0x01                         | <-- Protocol version
|   Type:     0x01 (DATA)                  | <-- Packet type
|   Length:   16 bytes                     | <-- Payload size
|   Sequence: 0                            | <-- Order tracking
| PAYLOAD:                                 |
|   Data: "Hello IoT World!"               | <-- Actual data
|   Hex:  48 65 6C 6C 6F 20 49 6F ...      | <-- Binary representation
| CHECKSUM:                                |
|   Value: 0xF8E2                          | <-- Error detection code
+------------------------------------------+
Quick Check: Packet Header Fields

51.8.5 Step 5: Experiment with Errors

The demo automatically shows a corrupted packet in cycle 3. Observe how:

  • The checksum verification fails
  • The red LED indicates an error
  • A NACK (negative acknowledgment) would be sent in a real system

51.9 Expected Output

When running the simulation, your Serial Monitor should display:

========================================
   Network Packet Simulator v1.0
   ESP32 IoT Learning Lab
========================================

Testing LEDs...

LED Test Complete!
- Red:    Error indicator
- Green:  Success indicator
- Yellow: Transmission indicator
- Blue:   Received indicator

============================================
      DEMONSTRATION CYCLE 1
============================================

[Demo 1] Sending DATA packet - Normal transmission

>>> TRANSMITTING PACKET >>>
    +------------------------------------------+
    | TX Packet Details                        |
    +------------------------------------------+
    | HEADER:                                  |
    |   Version:  0x01                         |
    |   Type:     0x01 (DATA)                  |
    |   Length:   16 bytes                     |
    |   Sequence: 0                            |
    | PAYLOAD:                                 |
    |   Data: "Hello IoT World!"               |
    |   Hex:  48 65 6C 6C 6F 20 49 6F ...      |
    | CHECKSUM:                                |
    |   Value: 0xF8E2                          |
    +------------------------------------------+
    Transmitting...
    Transmission complete.

<<< RECEIVING PACKET <<<
    [... packet details ...]
    Verifying checksum...
    [OK] Checksum VALID - Packet integrity confirmed!

--- Waiting 5 seconds before next cycle ---

51.10 Challenge Exercises

Challenge 1: Add Packet Acknowledgment

Modify the code to implement a complete ACK/NACK system:

  1. After receiving a valid packet, automatically create and send an ACK packet
  2. After receiving a corrupted packet, send a NACK packet requesting retransmission
  3. Add a retry counter that gives up after 3 failed attempts

Hint: Create a new function sendAcknowledgment(bool success, uint8_t seqNum) that creates and transmits the appropriate response.

Challenge 2: Implement Sequence Number Validation

Network packets can arrive out of order. Add sequence number tracking:

  1. Keep track of the last received sequence number
  2. Detect and report out-of-order packets
  3. Detect and report duplicate packets
  4. Add a purple LED (GPIO 19) that blinks for sequence errors

Hint: Store lastReceivedSequence as a global variable and compare incoming packets against it.

Challenge 3: Create a Two-ESP32 Network

For advanced learners with two ESP32 boards:

  1. Connect two ESP32s via serial (TX1-RX2, RX1-TX2)
  2. One ESP32 acts as sender, one as receiver
  3. Implement actual packet transmission over the serial line
  4. Add a button to trigger manual packet sending
  5. Display received messages on an OLED screen

Hint: Use Serial2.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN) for the second serial port.

51.11 Checksum Overhead Trade-offs

In resource-constrained IoT systems, every byte counts. Adding error detection increases packet size but prevents silent data corruption. The following worked example quantifies this trade-off.

Scenario: An industrial IoT sensor transmits 10-byte temperature readings every second over an IEEE 802.15.4 wireless link (250 kbps) with a 1% packet error rate.

Given:

  • Payload: 10 bytes per packet
  • Frequency: 1 packet/second = 86,400 packets/day
  • Error rate: 1% (864 corrupted packets/day)
  • Options: No checksum vs 2-byte checksum vs 4-byte CRC32
  • Header without checksum: 4 bytes (version, type, length, sequence)

Analysis:

Option Overhead Bytes Total Bytes Overhead % Errors Detected Undetected Errors
No checksum 4 14 28.6% 0 864/day
2-byte checksum 6 16 37.5% ~863/day ~1/day
4-byte CRC32 8 18 44.4% ~864/day ~0

Energy Cost (at 10 mW transmit power, 250 kbps data rate):

  • Transmission time per byte: 8 bits / 250,000 bps = 0.032 ms/byte
  • No checksum: 14 bytes x 0.032 ms = 0.448 ms at 10 mW = 0.00124 uWh per packet
  • With 2-byte checksum: 16 bytes x 0.032 ms = 0.512 ms at 10 mW = 0.00142 uWh (+14.3%)
  • With CRC32: 18 bytes x 0.032 ms = 0.576 ms at 10 mW = 0.00160 uWh (+28.6%)

Impact Over 1 Year:

  • Undetected errors (no checksum): 315,360 corrupted readings
  • Extra energy for 2-byte checksum: ~0.16 mWh/year (negligible)
  • Trade-off: Spend a fraction of a percent more battery for 99.9% error detection

Decision: A 2-byte checksum is optimal for most IoT sensors – it catches nearly all errors with minimal overhead. CRC32 is only justified for safety-critical applications (medical devices, industrial control systems) where even a single undetected error per day is unacceptable.

Try It: Checksum Strategy Comparison

51.12 Concept Relationships

How the packet networking concepts in this lab interconnect:

Concept Relates To Key Insight
Packet Header Protocol Metadata Version, type, length, and sequence fields identify the packet and enable processing
Payload Application Data The actual sensor readings, commands, or messages being transmitted
Checksum Error Detection Mathematical verification that data was not corrupted during transmission
Sequence Numbers Ordering and Loss Detection Track packets across unreliable links, detect gaps and duplicates
ACK/NACK Reliability Confirm receipt or request retransmission to ensure delivery
Protocol Overhead Efficiency Headers and checksums consume bandwidth – minimize for constrained networks
LED State Machine Visualization Physical representation of transmission, reception, success, and error states

51.13 Match the Concepts

51.14 Order the Process

Common Pitfalls

A single simulation run produces one sample from a random process. Fix: run at least 30 independent runs with different random seeds and report the mean and 95% confidence interval.

Metrics collected during the initialisation period (when routing tables are forming) skew the results. Fix: discard the first 10–20% of simulation time as a warm-up period before recording metrics.

The default free-space path loss model ignores walls, furniture, and interference. Fix: use a more realistic model (log-distance, Two-Ray Ground, or measured empirical values) and document which model was used.

51.15 Summary

This lab demonstrated essential packet networking concepts through hands-on simulation:

  • Packet Structure: Header (version, type, length, sequence) + Payload + Checksum forms a complete self-describing data unit
  • Transmission States: Visual feedback through LEDs shows the packet lifecycle from creation through verification
  • Error Detection: Checksums catch data corruption during transmission, triggering retransmission requests
  • Protocol Design: Version fields enable backward compatibility, sequence numbers detect lost packets, and ACK/NACK mechanisms enable reliable communication over unreliable links

51.16 Knowledge Check

51.17 What’s Next

Topic Chapter Description
Network Performance Network Performance Lab Measure bandwidth, latency, and jitter with ESP32 experiments
Packet Journey Game Packet Journey Game Interactive adventure tying together all networking concepts through gamified learning
TCP/IP Protocol TCP/IP Fundamentals How real TCP protocols implement packet structure, flow control, and error handling
Error Detection Error Detection Methods CRC, checksums, and forward error correction techniques compared
Protocol Overhead Transport Overhead Analysis Minimising header waste in constrained IoT networks
Routing Structures Network Topologies Fundamentals How packets route through star, mesh, and tree network structures