13  NB-IoT Lab Simulation

Hands-On ESP32 Simulation of NB-IoT Concepts

In 60 Seconds

This interactive lab uses an ESP32 in Wokwi to simulate NB-IoT behaviors including the network attach procedure, PSM and eDRX timer configurations, coverage enhancement repetitions, and uplink data transmission, letting you observe state transitions and calculate battery life projections without requiring actual cellular hardware.

13.1 Learning Objectives

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

  • Trace the NB-IoT attach procedure: Map each step of cell search, RRC connection, and authentication in simulation output
  • Configure PSM timers: Set T3412 and T3324 timer values and predict their effect on sleep/wake behavior
  • Differentiate PSM vs eDRX trade-offs: Evaluate how each power mode affects current draw, downlink latency, and battery longevity
  • Assess Coverage Enhancement impact: Quantify how CE level repetitions trade power consumption for signal reliability
  • Calculate battery life projections: Derive real-world battery consumption estimates from simulation power profiles

This lab simulates NB-IoT device behavior using an ESP32 microcontroller. You will experience the connection process, power saving modes, and data transmission patterns that real NB-IoT devices use. It is a hands-on way to understand cellular IoT without needing an actual cellular network or SIM card.

“You do not need a real cellular network to learn NB-IoT!” announced Max the Microcontroller, loading the Wokwi simulator. “This ESP32 simulation mimics exactly what a real NB-IoT modem does – cell search, authentication, PSM timers, coverage enhancement, and data transmission. Same state machines, same timing, same power calculations.”

Sammy the Sensor leaned in. “What will I see?” Max explained, “You will watch the attach procedure step by step: the device searches for a cell, establishes a radio connection, authenticates with the network, and activates a data bearer. Then you can toggle between PSM and eDRX to see how sleep patterns change.”

“The power calculation is the most eye-opening part,” said Bella the Battery. “The simulation shows exact current draw in each state: 220 milliamps during transmission, 46 milliamps while receiving, and just 3 microamps in PSM sleep. When you see those numbers multiplied by time, you understand why PSM configuration makes the difference between a few months and 10 years of battery life.”

Lila the LED encouraged experimentation. “Try changing the TAU timer and see how battery life projections change. Try enabling Coverage Enhancement mode and watch how repetitions improve reliability but increase power draw. The simulator lets you explore trade-offs that would take weeks to test on real hardware.”

13.2 Lab Overview

This interactive lab provides a hands-on simulation of NB-IoT concepts using an ESP32 microcontroller in Wokwi. While actual NB-IoT communication requires specialized cellular modules and carrier connectivity, this simulation demonstrates the key concepts and procedures that real NB-IoT devices perform.

What You Will Explore:

  • Network Attach Procedure: Trace each step of the multi-step process devices use to connect to cellular networks
  • PSM (Power Saving Mode): Demonstrate how devices achieve 10+ year battery life through deep sleep states
  • eDRX (Extended Discontinuous Reception): Evaluate the trade-offs between power saving and downlink responsiveness
  • Coverage Extension Modes: Measure how signal repetition enables deep indoor penetration at the cost of energy
  • Uplink Data Transmission: Examine the overhead involved in sending small IoT payloads
  • Network Status Monitoring: Interpret signal quality, network registration, and connection state indicators

Prerequisites: Basic understanding of C/C++ programming and familiarity with microcontroller concepts.

13.3 Lab Environment

The simulation below runs on an ESP32 in Wokwi’s browser-based environment. The code simulates NB-IoT modem behavior, including realistic timing, state machines, and power calculations.

How to Use This Lab
  1. Copy the code from the section below into the Wokwi editor
  2. Click “Start Simulation” to run the NB-IoT simulation
  3. Open the Serial Monitor (115200 baud) to see detailed output
  4. Observe the state transitions as the simulated device attaches to the network
  5. Try the challenge exercises to deepen your understanding

13.4 Complete NB-IoT Simulation Code

Copy this code into the Wokwi editor to run the simulation:

/*
 * NB-IoT Fundamentals Lab - ESP32 Simulation
 *
 * This simulation demonstrates key NB-IoT concepts:
 * - Network attach procedure (PLMN search, RRC connection, authentication)
 * - Power Saving Mode (PSM) with T3412 and T3324 timers
 * - Extended Discontinuous Reception (eDRX) cycles
 * - Coverage Enhancement (CE) levels and repetitions
 * - Uplink data transmission with control plane optimization
 * - Signal quality monitoring (RSRP, RSRQ, SINR)
 *
 * Educational Purpose: Understanding NB-IoT without actual cellular hardware
 *
 * Author: IoT Class Lab
 * License: MIT
 */

#include <Arduino.h>

// ============================================================================
// NB-IoT Configuration Constants
// ============================================================================

// Network Configuration
#define PLMN_MCC "310"          // Mobile Country Code (US)
#define PLMN_MNC "410"          // Mobile Network Code (AT&T)
#define BAND_NUMBER 12          // LTE Band 12 (700 MHz) - common for NB-IoT
#define EARFCN 5110             // E-UTRA Absolute Radio Frequency Channel Number

// PSM Timer Configuration (T3412 - TAU timer, T3324 - Active timer)
#define T3412_VALUE_HOURS 24    // TAU (Tracking Area Update) period
#define T3324_VALUE_SECONDS 30  // Active timer before entering PSM

// eDRX Configuration
#define EDRX_CYCLE_SECONDS 20.48  // eDRX cycle (5.12s to 2.91 hours possible)
#define PTW_SECONDS 2.56          // Paging Time Window within eDRX cycle

// Coverage Enhancement Levels
#define INITIAL_CE_LEVEL 0

// Power Consumption Values (typical for NB-IoT modules)
#define POWER_IDLE_MA 0.003       // PSM sleep: 3 uA
#define POWER_EDRX_MA 0.5         // eDRX sleep: 0.5 mA
#define POWER_RX_MA 46            // Receiving: 46 mA
#define POWER_TX_MA 220           // Transmitting (23 dBm): 220 mA
#define POWER_SEARCH_MA 80        // Cell search: 80 mA

// Timing Constants (milliseconds)
#define CELL_SEARCH_TIME_MS 3000
#define RRC_CONNECT_TIME_MS 500
#define AUTHENTICATION_TIME_MS 1000
#define TAU_PROCEDURE_TIME_MS 800
#define DATA_TRANSFER_TIME_MS 200

// ============================================================================
// Enumerations and Type Definitions
// ============================================================================

// NB-IoT Device States (based on 3GPP specifications)
enum NBIoTState {
  STATE_POWER_OFF,
  STATE_CELL_SEARCH,
  STATE_CELL_SELECTION,
  STATE_RRC_IDLE,
  STATE_RRC_CONNECTING,
  STATE_RRC_CONNECTED,
  STATE_AUTHENTICATING,
  STATE_REGISTERED,
  STATE_DATA_TRANSFER,
  STATE_PSM,
  STATE_EDRX_SLEEP,
  STATE_EDRX_PAGING
};

// Coverage Enhancement Levels
enum CELevel {
  CE_LEVEL_0 = 0,   // Normal coverage (no repetitions)
  CE_LEVEL_1 = 1,   // Robust (up to 32 repetitions)
  CE_LEVEL_2 = 2    // Extreme (up to 128 repetitions)
};

// Power Saving Mode Types
enum PowerMode {
  POWER_MODE_CONNECTED,
  POWER_MODE_EDRX_ONLY,
  POWER_MODE_PSM_ONLY,
  POWER_MODE_PSM_EDRX
};

// Network Registration Status
enum RegistrationStatus {
  REG_NOT_REGISTERED = 0,
  REG_REGISTERED_HOME = 1,
  REG_SEARCHING = 2,
  REG_DENIED = 3,
  REG_UNKNOWN = 4,
  REG_REGISTERED_ROAMING = 5
};

// ============================================================================
// Data Structures
// ============================================================================

struct SignalQuality {
  int rsrp;
  int rsrq;
  int sinr;
  int cellId;
  int earfcn;
};

struct NetworkInfo {
  char mcc[4];
  char mnc[4];
  int band;
  int tac;
  bool attached;
  RegistrationStatus regStatus;
};

struct PowerStats {
  float totalEnergyMah;
  unsigned long psmDurationMs;
  unsigned long edrxDurationMs;
  unsigned long connectedDurationMs;
  unsigned long txDurationMs;
  unsigned long rxDurationMs;
  int transmissionCount;
  int pageCheckCount;
};

struct TimerState {
  unsigned long t3412StartMs;
  unsigned long t3324StartMs;
  unsigned long edrxCycleStartMs;
  bool t3412Running;
  bool t3324Running;
  bool psmEnabled;
  bool edrxEnabled;
};

struct DataStats {
  int totalBytesSent;
  int totalBytesReceived;
  int messagesSent;
  int messagesReceived;
  float averageLatencyMs;
};

// ============================================================================
// Global Variables
// ============================================================================

NBIoTState currentState = STATE_POWER_OFF;
CELevel currentCELevel = CE_LEVEL_0;
PowerMode powerMode = POWER_MODE_PSM_EDRX;

SignalQuality signalQuality;
NetworkInfo networkInfo;
PowerStats powerStats;
TimerState timerState;
DataStats dataStats;

unsigned long stateEntryTime = 0;
unsigned long lastStatusUpdate = 0;
unsigned long simulationStartTime = 0;

int repetitionCount = 1;
bool pendingDownlink = false;
int sensorReadingCounter = 0;

// ============================================================================
// Function Declarations
// ============================================================================

void initializeModem();
void processStateMachine();
void updatePowerConsumption();
void printStatus();
void simulateSignalConditions();
void performCellSearch();
void performCellSelection();
void establishRRCConnection();
void performAuthentication();
void enterPSM();
void exitPSM();
void handleEDRXCycle();
void sendUplinkData(const char* data, int length);
void checkForDownlink();
void calculateCERepetitions();
void printStateTransition(NBIoTState from, NBIoTState to);
void printPowerModeInfo();
void printCoverageEnhancementInfo();
const char* getStateName(NBIoTState state);
const char* getCELevelName(CELevel level);

// ============================================================================
// Setup Function
// ============================================================================

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

  Serial.println();
  Serial.println("========================================");
  Serial.println("  NB-IoT FUNDAMENTALS INTERACTIVE LAB");
  Serial.println("========================================");
  Serial.println();
  Serial.println("This simulation demonstrates:");
  Serial.println("- Network attach procedure");
  Serial.println("- PSM (Power Saving Mode)");
  Serial.println("- eDRX (Extended DRX)");
  Serial.println("- Coverage Enhancement");
  Serial.println();

  initializeModem();
  simulationStartTime = millis();

  Serial.println("Configuration:");
  Serial.printf("  PLMN: %s-%s, Band %d\n", PLMN_MCC, PLMN_MNC, BAND_NUMBER);
  Serial.printf("  T3412: %d hours, T3324: %d seconds\n",
                T3412_VALUE_HOURS, T3324_VALUE_SECONDS);
  Serial.printf("  eDRX Cycle: %.2fs, PTW: %.2fs\n",
                EDRX_CYCLE_SECONDS, PTW_SECONDS);
  Serial.println();

  printPowerModeInfo();
  Serial.println("\n>>> Initiating network attach...\n");
}

// ============================================================================
// Main Loop
// ============================================================================

void loop() {
  processStateMachine();
  updatePowerConsumption();

  // Periodic status updates (every 5 seconds)
  if (millis() - lastStatusUpdate > 5000) {
    printStatus();
    lastStatusUpdate = millis();
  }

  // Simulate sensor readings
  static unsigned long lastSensorRead = 0;
  if (currentState == STATE_REGISTERED || currentState == STATE_PSM ||
      currentState == STATE_EDRX_SLEEP) {
    if (millis() - lastSensorRead > 30000) {
      sensorReadingCounter++;

      char payload[64];
      snprintf(payload, sizeof(payload),
               "{\"id\":%d,\"temp\":%.1f,\"bat\":%d}",
               sensorReadingCounter,
               20.0 + random(-50, 50) / 10.0,
               95 - sensorReadingCounter / 10);

      Serial.println("\n========================================");
      Serial.printf(">>> SENSOR READING #%d\n", sensorReadingCounter);
      Serial.printf(">>> Payload: %s (%d bytes)\n", payload, strlen(payload));
      Serial.println("========================================");

      sendUplinkData(payload, strlen(payload));
      lastSensorRead = millis();
    }
  }

  delay(100);
}

// ============================================================================
// Modem Initialization
// ============================================================================

void initializeModem() {
  signalQuality.rsrp = -105;
  signalQuality.rsrq = -10;
  signalQuality.sinr = 5;
  signalQuality.cellId = 123;
  signalQuality.earfcn = EARFCN;

  strcpy(networkInfo.mcc, PLMN_MCC);
  strcpy(networkInfo.mnc, PLMN_MNC);
  networkInfo.band = BAND_NUMBER;
  networkInfo.tac = 12345;
  networkInfo.attached = false;
  networkInfo.regStatus = REG_NOT_REGISTERED;

  memset(&powerStats, 0, sizeof(powerStats));
  memset(&timerState, 0, sizeof(timerState));
  memset(&dataStats, 0, sizeof(dataStats));

  timerState.psmEnabled = true;
  timerState.edrxEnabled = true;

  currentState = STATE_CELL_SEARCH;
  stateEntryTime = millis();
  currentCELevel = (CELevel)INITIAL_CE_LEVEL;
}

// ============================================================================
// State Machine Processing
// ============================================================================

void processStateMachine() {
  unsigned long timeInState = millis() - stateEntryTime;
  NBIoTState previousState = currentState;

  switch (currentState) {
    case STATE_CELL_SEARCH:
      if (timeInState >= CELL_SEARCH_TIME_MS) {
        performCellSearch();
        currentState = STATE_CELL_SELECTION;
        stateEntryTime = millis();
        printStateTransition(previousState, currentState);
      }
      break;

    case STATE_CELL_SELECTION:
      if (timeInState >= 500) {
        performCellSelection();
        currentState = STATE_RRC_CONNECTING;
        stateEntryTime = millis();
        printStateTransition(previousState, currentState);
      }
      break;

    case STATE_RRC_CONNECTING:
      if (timeInState >= RRC_CONNECT_TIME_MS) {
        establishRRCConnection();
        currentState = STATE_AUTHENTICATING;
        stateEntryTime = millis();
        printStateTransition(previousState, currentState);
      }
      break;

    case STATE_AUTHENTICATING:
      if (timeInState >= AUTHENTICATION_TIME_MS) {
        performAuthentication();
        currentState = STATE_REGISTERED;
        stateEntryTime = millis();
        networkInfo.attached = true;
        networkInfo.regStatus = REG_REGISTERED_HOME;
        printStateTransition(previousState, currentState);

        timerState.t3412StartMs = millis();
        timerState.t3412Running = true;
        timerState.t3324StartMs = millis();
        timerState.t3324Running = true;

        Serial.println("\n*** SUCCESSFULLY ATTACHED TO NETWORK ***\n");
        Serial.printf("PSM timer T3324: %d seconds until sleep\n", T3324_VALUE_SECONDS);
      }
      break;

    case STATE_REGISTERED:
      if (timerState.t3324Running) {
        unsigned long t3324Elapsed = millis() - timerState.t3324StartMs;
        if (t3324Elapsed >= (T3324_VALUE_SECONDS * 1000)) {
          timerState.t3324Running = false;

          if (timerState.psmEnabled) {
            enterPSM();
            currentState = STATE_PSM;
            stateEntryTime = millis();
            printStateTransition(previousState, currentState);
          } else if (timerState.edrxEnabled) {
            currentState = STATE_EDRX_SLEEP;
            stateEntryTime = millis();
            timerState.edrxCycleStartMs = millis();
            printStateTransition(previousState, currentState);
          }
        }
      }
      break;

    case STATE_PSM:
      handleEDRXCycle();
      break;

    case STATE_EDRX_SLEEP:
      handleEDRXCycle();
      break;

    case STATE_EDRX_PAGING:
      if (timeInState >= (PTW_SECONDS * 1000)) {
        checkForDownlink();
        powerStats.pageCheckCount++;

        if (pendingDownlink) {
          currentState = STATE_RRC_CONNECTING;
          pendingDownlink = false;
        } else {
          currentState = timerState.psmEnabled ? STATE_PSM : STATE_EDRX_SLEEP;
          timerState.edrxCycleStartMs = millis();
        }
        stateEntryTime = millis();
        printStateTransition(previousState, currentState);
      }
      break;

    case STATE_DATA_TRANSFER:
      if (timeInState >= DATA_TRANSFER_TIME_MS * repetitionCount) {
        Serial.println("  Data transfer complete");
        powerStats.transmissionCount++;

        timerState.t3324StartMs = millis();
        timerState.t3324Running = true;

        currentState = STATE_REGISTERED;
        stateEntryTime = millis();
        printStateTransition(previousState, currentState);
        Serial.printf("  T3324 restarted: %d sec until PSM\n", T3324_VALUE_SECONDS);
      }
      break;

    default:
      break;
  }
}

// ============================================================================
// Network Procedures
// ============================================================================

void performCellSearch() {
  Serial.println("--- CELL SEARCH ---");
  Serial.println("1. Scanning NB-IoT bands...");
  Serial.println("2. Detecting NPSS/NSSS...");
  Serial.println("3. Reading MIB-NB...");
  Serial.println("4. Reading SIB1-NB, SIB2-NB...");

  simulateSignalConditions();

  Serial.printf("\nFound cell: PCI=%d, EARFCN=%d\n",
                signalQuality.cellId, signalQuality.earfcn);
  Serial.printf("Signal: RSRP=%d dBm, RSRQ=%d dB, SINR=%d dB\n",
                signalQuality.rsrp, signalQuality.rsrq, signalQuality.sinr);
}

void performCellSelection() {
  Serial.println("\n--- CELL SELECTION ---");

  if (signalQuality.rsrp >= -110) {
    currentCELevel = CE_LEVEL_0;
    Serial.println("Coverage: NORMAL (CE Level 0)");
  } else if (signalQuality.rsrp >= -130) {
    currentCELevel = CE_LEVEL_1;
    Serial.println("Coverage: EXTENDED (CE Level 1)");
  } else {
    currentCELevel = CE_LEVEL_2;
    Serial.println("Coverage: EXTREME (CE Level 2)");
  }

  calculateCERepetitions();
}

void establishRRCConnection() {
  Serial.println("\n--- RRC CONNECTION ---");
  Serial.println("1. Sending NPRACH preamble...");
  Serial.printf("   (Repetitions: %d for CE Level %d)\n", repetitionCount, currentCELevel);
  Serial.println("2. Received RAR...");
  Serial.println("3. Sending RRC Connection Request...");
  Serial.println("4. Received RRC Connection Setup...");
}

void performAuthentication() {
  Serial.println("\n--- AUTHENTICATION ---");
  Serial.println("1. EPS-AKA procedure...");
  Serial.println("2. NAS Security Mode Command...");
  Serial.println("3. Attach Accept received...");
  Serial.println("4. Attach Complete sent...");
}

// ============================================================================
// Power Saving Functions
// ============================================================================

void enterPSM() {
  Serial.println("\n*** ENTERING PSM ***");
  Serial.println("Radio: OFF");
  Serial.printf("Power: %.1f uA\n", POWER_IDLE_MA * 1000);
  Serial.println("Device is now UNREACHABLE");
}

void exitPSM() {
  unsigned long psmDuration = millis() - stateEntryTime;
  powerStats.psmDurationMs += psmDuration;
  Serial.printf("\n>>> Exiting PSM (slept %.1f sec)\n", psmDuration / 1000.0);
}

void handleEDRXCycle() {
  unsigned long edrxElapsed = millis() - timerState.edrxCycleStartMs;

  if (edrxElapsed >= (EDRX_CYCLE_SECONDS * 1000)) {
    NBIoTState previousState = currentState;
    currentState = STATE_EDRX_PAGING;
    timerState.edrxCycleStartMs = millis();

    Serial.println("\n--- eDRX Paging Window ---");
    Serial.println("Checking for downlink...");

    stateEntryTime = millis();
    printStateTransition(previousState, currentState);
  }
}

// ============================================================================
// Data Transfer Functions
// ============================================================================

void sendUplinkData(const char* data, int length) {
  if (currentState == STATE_PSM) {
    exitPSM();
  }

  Serial.println("\n--- UPLINK TRANSMISSION ---");
  Serial.printf("Payload: %d bytes\n", length);
  Serial.printf("CE Level %d: %dx repetitions\n", currentCELevel, repetitionCount);
  Serial.printf("TX time: %d ms\n", DATA_TRANSFER_TIME_MS * repetitionCount);

  dataStats.totalBytesSent += length;
  dataStats.messagesSent++;

  NBIoTState previousState = currentState;
  currentState = STATE_DATA_TRANSFER;
  stateEntryTime = millis();
  powerStats.txDurationMs += DATA_TRANSFER_TIME_MS * repetitionCount;
  printStateTransition(previousState, currentState);
}

void checkForDownlink() {
  int chance = random(100);
  if (chance < 5) {
    pendingDownlink = true;
    Serial.println("  Paging received - downlink pending!");
  } else {
    pendingDownlink = false;
    Serial.println("  No pending downlink");
  }
}

// ============================================================================
// Coverage Enhancement Functions
// ============================================================================

void calculateCERepetitions() {
  switch (currentCELevel) {
    case CE_LEVEL_0: repetitionCount = 1; break;
    case CE_LEVEL_1: repetitionCount = 8; break;
    case CE_LEVEL_2: repetitionCount = 64; break;
  }
  printCoverageEnhancementInfo();
}

void simulateSignalConditions() {
  int scenario = random(4);
  switch (scenario) {
    case 0:
      signalQuality.rsrp = -85 + random(-10, 10);
      signalQuality.rsrq = -8 + random(-3, 3);
      signalQuality.sinr = 15 + random(-5, 5);
      Serial.println("Scenario: Outdoor - normal coverage");
      break;
    case 1:
      signalQuality.rsrp = -105 + random(-10, 10);
      signalQuality.rsrq = -12 + random(-3, 3);
      signalQuality.sinr = 5 + random(-3, 3);
      Serial.println("Scenario: Indoor - moderate");
      break;
    case 2:
      signalQuality.rsrp = -125 + random(-10, 10);
      signalQuality.rsrq = -16 + random(-3, 3);
      signalQuality.sinr = -2 + random(-3, 3);
      Serial.println("Scenario: Deep indoor - basement");
      break;
    case 3:
      signalQuality.rsrp = -138 + random(-5, 5);
      signalQuality.rsrq = -19 + random(-2, 2);
      signalQuality.sinr = -8 + random(-5, 5);
      Serial.println("Scenario: Extreme - near MCL limit");
      break;
  }
}

// ============================================================================
// Power Consumption Tracking
// ============================================================================

void updatePowerConsumption() {
  static unsigned long lastUpdate = 0;
  unsigned long now = millis();
  unsigned long elapsed = now - lastUpdate;

  if (lastUpdate == 0) {
    lastUpdate = now;
    return;
  }

  float hours = elapsed / 3600000.0;
  float energyUsed = 0;

  switch (currentState) {
    case STATE_PSM:
      energyUsed = hours * POWER_IDLE_MA;
      powerStats.psmDurationMs += elapsed;
      break;
    case STATE_EDRX_SLEEP:
      energyUsed = hours * POWER_EDRX_MA;
      powerStats.edrxDurationMs += elapsed;
      break;
    case STATE_EDRX_PAGING:
    case STATE_RRC_IDLE:
      energyUsed = hours * POWER_RX_MA;
      powerStats.rxDurationMs += elapsed;
      break;
    case STATE_CELL_SEARCH:
      energyUsed = hours * POWER_SEARCH_MA;
      break;
    case STATE_DATA_TRANSFER:
      energyUsed = hours * POWER_TX_MA;
      break;
    default:
      energyUsed = hours * POWER_RX_MA;
      powerStats.connectedDurationMs += elapsed;
      break;
  }

  powerStats.totalEnergyMah += energyUsed;
  lastUpdate = now;
}

// ============================================================================
// Status Display Functions
// ============================================================================

void printStatus() {
  unsigned long runtime = millis() - simulationStartTime;
  float runtimeHours = runtime / 3600000.0;

  Serial.println("\n========== STATUS ==========");
  Serial.printf("State: %s\n", getStateName(currentState));
  Serial.printf("Runtime: %.2f hours\n", runtimeHours);
  Serial.printf("Registration: %s\n", networkInfo.attached ? "ATTACHED" : "DETACHED");
  Serial.printf("CE Level: %d (%s)\n", currentCELevel, getCELevelName(currentCELevel));
  Serial.printf("Signal: RSRP=%d dBm\n", signalQuality.rsrp);
  Serial.printf("Messages TX: %d, Bytes: %d\n",
                dataStats.messagesSent, dataStats.totalBytesSent);

  float avgCurrentMa = (powerStats.totalEnergyMah / runtimeHours);
  float batteryLifeYears = (2400.0 / avgCurrentMa) / (24 * 365);

  Serial.printf("Avg current: %.3f mA\n", avgCurrentMa);
  Serial.printf("Projected battery life: %.1f years (2400mAh)\n", batteryLifeYears);
  Serial.println("============================");
}

void printStateTransition(NBIoTState from, NBIoTState to) {
  Serial.printf("\n>>> State: %s -> %s\n", getStateName(from), getStateName(to));
}

void printPowerModeInfo() {
  Serial.println("--- POWER MODE INFO ---");
  Serial.println("PSM: Deep sleep (~3 uA), radio OFF");
  Serial.println("eDRX: Light sleep (~0.5 mA), periodic wake");
  Serial.println("Current config: PSM + eDRX combined");
}

void printCoverageEnhancementInfo() {
  Serial.printf("\nCE Analysis: RSRP=%d dBm, Level=%d, Rep=%dx\n",
                signalQuality.rsrp, currentCELevel, repetitionCount);
}

// ============================================================================
// Utility Functions
// ============================================================================

const char* getStateName(NBIoTState state) {
  switch (state) {
    case STATE_POWER_OFF:      return "POWER_OFF";
    case STATE_CELL_SEARCH:    return "CELL_SEARCH";
    case STATE_CELL_SELECTION: return "CELL_SELECTION";
    case STATE_RRC_IDLE:       return "RRC_IDLE";
    case STATE_RRC_CONNECTING: return "RRC_CONNECTING";
    case STATE_RRC_CONNECTED:  return "RRC_CONNECTED";
    case STATE_AUTHENTICATING: return "AUTHENTICATING";
    case STATE_REGISTERED:     return "REGISTERED";
    case STATE_DATA_TRANSFER:  return "DATA_TRANSFER";
    case STATE_PSM:            return "PSM_SLEEP";
    case STATE_EDRX_SLEEP:     return "eDRX_SLEEP";
    case STATE_EDRX_PAGING:    return "eDRX_PAGING";
    default:                   return "UNKNOWN";
  }
}

const char* getCELevelName(CELevel level) {
  switch (level) {
    case CE_LEVEL_0: return "NORMAL";
    case CE_LEVEL_1: return "EXTENDED";
    case CE_LEVEL_2: return "EXTREME";
    default:         return "UNKNOWN";
  }
}

13.5 Understanding the Simulation Output

As the simulation runs, observe these key behaviors:

13.5.1 State Machine Progression

The simulation follows the real NB-IoT attach procedure:

  1. CELL_SEARCH: Device scans for NB-IoT cells using NPSS/NSSS synchronization signals
  2. CELL_SELECTION: Evaluates signal quality and selects appropriate Coverage Enhancement level
  3. RRC_CONNECTING: Establishes Radio Resource Control connection via NPRACH
  4. AUTHENTICATING: Performs EPS-AKA mutual authentication with the network
  5. REGISTERED: Successfully attached - T3324 (active timer) starts countdown
  6. PSM_SLEEP: Deep sleep mode (3 uA) - radio completely off
  7. eDRX cycles: Periodic wake-ups to check for downlink messages

13.6 Challenge Exercises

Challenge 1: Modify PSM Timing

Objective: Understand the impact of T3324 (active timer) on battery life.

Task:

  1. Change T3324_VALUE_SECONDS from 30 to 5
  2. Observe how quickly the device enters PSM after data transmission
  3. Calculate the battery life improvement

Questions to answer:

  • What is the trade-off of a very short T3324?
  • When would you want a longer T3324 value?
Challenge 2: Simulate Deep Indoor Coverage

Objective: Experience CE Level 2 (extreme coverage) behavior.

Task:

  1. Modify the simulateSignalConditions() function to always return scenario 3 (extreme coverage)
  2. Observe the increased repetition count
  3. Note the impact on transmission time and power consumption

Modification:

void simulateSignalConditions() {
  // Force extreme coverage scenario
  signalQuality.rsrp = -138;
  signalQuality.rsrq = -19;
  signalQuality.sinr = -8;
  Serial.println("Scenario: Extreme coverage - near MCL limit");
}
Challenge 3: Compare Power Modes

Objective: Quantify the difference between PSM-only, eDRX-only, and combined modes.

Task:

  1. Run simulation with timerState.edrxEnabled = false (PSM only)
  2. Run with timerState.psmEnabled = false (eDRX only)
  3. Compare battery life projections from the status reports

Analysis questions:

  • Which mode provides best battery life?
  • Which mode allows fastest downlink response?
  • What use cases favor each approach?

13.7 Expected Outcomes

After completing this lab, you should be able to:

  • Explain the NB-IoT attach procedure including cell search and authentication
  • Describe PSM operation and how T3412/T3324 timers control sleep behavior
  • Compare PSM vs eDRX trade-offs for different application requirements
  • Calculate battery life impact of different power-saving configurations
  • Identify Coverage Enhancement levels based on signal quality measurements
  • Understand repetition trade-offs between reliability and power/capacity

13.8 Interactive: CE Level Battery Impact Explorer

13.9 Simulation vs Reality

Important Differences

This simulation demonstrates NB-IoT concepts but differs from real deployments:

What this simulation shows accurately:

  • State machine progression and timing relationships
  • Power consumption ratios between states
  • Coverage Enhancement level selection logic
  • PSM and eDRX timer behavior

What requires real hardware/network:

  • Actual RF signal measurement (RSRP, RSRQ, SINR)
  • Real carrier authentication and SIM provisioning
  • True power consumption measurements
  • Network-negotiated timer values (carriers may override requests)
  • SCEF integration for Control Plane optimization

For hands-on NB-IoT development:

  • Use modules like Quectel BC95, u-blox SARA-N2, or Nordic nRF9160
  • Obtain NB-IoT SIM cards from carriers (AT&T, Verizon, T-Mobile in US)
  • Consider IoT platforms like Soracom, Hologram, or 1NCE for connectivity

Scenario: You ran the NB-IoT simulation for 2 hours and observed the following power consumption profile. Use this data to project battery life for a real deployment with a 5000 mAh battery.

Simulation Output (2-hour window):

Total Transmissions: 8 uplink messages
Total Active Time: 304 seconds
Average interval: 15 minutes between transmissions
Coverage Enhancement: Level 1 (moderate, 32 repetitions)

Power breakdown:
- Cell search: 3 sec @ 80 mA = 240 mAs = 0.067 mAh (1×)
- Authentication: 6 sec @ 50 mA = 300 mAs = 0.083 mAh (1×)
- Transmission: 8 sec @ 220 mA = 1,760 mAs = 0.489 mAh (8×)
- Active window: 30 sec @ 15 mA = 450 mAs = 0.125 mAh (8×)
- PSM sleep: 7,171 sec @ 0.003 mA = 21.5 mAs = 0.006 mAh
Total consumed: 0.067 + 0.083 + (8 × 0.489) + (8 × 0.125) + 0.006 = 5.062 mAh

Step 1: Extract daily energy consumption

Simulation shows 8 transmissions in 2 hours (120 minutes). Scale to 24 hours: - Transmissions per day: 8 × (24/2) = 96 transmissions - This represents a sensor reporting every 15 minutes

Energy per transmission cycle (from simulation): - TX: 0.489 mAh - Active window: 0.125 mAh - PSM sleep (between transmissions): 15 min × 0.003 mA × (1/60) = 0.00075 mAh

Energy per day: - Transmission energy: 96 × 0.489 = 46.94 mAh - Active window energy: 96 × 0.125 = 12.00 mAh - Sleep energy: (24 hours - 96 × 38 sec/3600) × 0.003 = (24 - 1.01) × 0.003 = 0.069 mAh - Daily total: 46.94 + 12.00 + 0.069 = 59.01 mAh/day

Battery life: 5000 mAh / 59.01 mAh/day = 84.7 days = 2.8 months

Step 2: Compare to more realistic reporting frequency

The simulation defaults to frequent transmissions for demonstration. Real sensors typically report less often:

Scenario A: Hourly reporting (24 TX/day)

TX energy: 24 × 0.489 = 11.74 mAh
Active: 24 × 0.125 = 3.00 mAh
Sleep: (24 - 24 × 38/3600) × 0.003 = 0.072 mAh
Daily: 11.74 + 3.00 + 0.072 = 14.81 mAh/day
Battery life: 5000 / 14.81 = **337 days = 11 months**

Scenario B: Daily reporting (1 TX/day)

TX energy: 1 × 0.489 = 0.489 mAh
Active: 1 × 0.125 = 0.125 mAh
Sleep: (24 - 1 × 38/3600) × 0.003 = 0.072 mAh
Daily: 0.489 + 0.125 + 0.072 = 0.686 mAh/day
Battery life: 5000 / 0.686 = **7,289 days = 20 years**

Step 3: Account for Coverage Enhancement impact

The simulation used CE Level 1 (32 repetitions). If sensors are in deep coverage (CE Level 2, 128 repetitions), transmission energy increases 4×:

Daily reporting with CE Level 2:

TX energy: 1 × (0.489 × 4) = 1.956 mAh
Active: 1 × 0.125 = 0.125 mAh
Sleep: 0.072 mAh
Daily: 1.956 + 0.125 + 0.072 = 2.153 mAh/day
Battery life: 5000 / 2.153 = **2,322 days = 6.4 years**

Coverage Enhancement Level 2 uses 128 repetitions vs 1 at CE0, increasing transmission time by \(128\times\). For daily transmission at 0.489 mAh in CE0:

\[E_{CE2} = 0.489 \times 128 \times \frac{1}{32} = 1.956 \text{ mAh}\]

(The \(\frac{1}{32}\) factor accounts for CE1 baseline with 32 repetitions.) With total daily consumption 2.153 mAh, battery life = \(\frac{5000}{2.153} \approx 2,322\) days. This shows CE2 reduces battery life from 20 years (CE0) to 6.4 years – a 3.1× penalty for deep indoor coverage.

Step 4: Create deployment table

Reporting Interval CE Level 0 (Normal) CE Level 1 (Extended) CE Level 2 (Extreme)
15 minutes (96/day) 3.5 months 2.8 months 0.7 months
1 hour (24/day) 14 months 11 months 2.8 months
6 hours (4/day) 7 years 5.5 years 1.4 years
Daily (1/day) 25 years 20 years 6.4 years

Key Insights:

  1. PSM sleep current dominates only when transmissions are infrequent. At 96 TX/day, transmission energy is 796× larger than sleep energy.

  2. Coverage Enhancement has massive impact: CE Level 2 reduces battery life by 75% compared to normal coverage due to 128× repetitions.

  3. Optimal configuration for 10-year battery life:

    • Target: Daily reporting in normal coverage (CE Level 0)
    • Acceptable: 6-hour reporting in extended coverage (CE Level 1)
    • Not viable: Hourly reporting in extreme coverage (CE Level 2)
  4. Design trade-offs:

    • If 10-year battery life is non-negotiable → deploy indoor small cells to improve coverage to CE Level 0
    • If frequent updates are non-negotiable → accept 1-2 year battery life and plan replacement logistics
    • Most deployments: Balance at daily/6-hour reporting with CE Level 0-1 coverage

Lesson: The simulation’s 2-hour dataset accurately models power consumption mechanics. Extrapolate to real deployment by scaling transmission frequency and accounting for coverage enhancement levels observed during site surveys. Always validate projections with 1-week field test before committing to 10+ year battery claims.

13.10 Summary

  • NB-IoT attach procedure involves cell search, RRC connection, authentication, and registration
  • PSM (Power Saving Mode) achieves 3 uA sleep current by completely disabling the radio
  • T3324 controls active time before entering PSM; shorter values save power but reduce downlink opportunity
  • Coverage Enhancement uses repetitions to improve reliability in poor signal conditions
  • Battery life projection depends on sleep current, TX duration, message frequency, and CE level
  • This simulation provides conceptual understanding; real deployments require certified modules and carrier connectivity

13.11 Knowledge Check

13.12 Concept Relationships

This simulation lab brings together multiple NB-IoT concepts in executable form:

  • Network attach procedure (cell search, RRC, authentication) demonstrates the signaling overhead that dominates initial power consumption - often 10-20x the actual data transmission cost
  • PSM timers (T3412, T3324) directly control state transitions in the simulation, showing how timer configuration translates to specific wake-sleep patterns and battery consumption
  • Coverage enhancement (CE levels, repetitions) is modeled through transmission time multiplication, illustrating why a sensor in CE2 consumes 64-128x more energy than CE0
  • Power calculations integrate across all states (active, idle, PSM sleep) to produce realistic battery life projections that match field deployment data
  • State machine logic captures the event-driven nature of NB-IoT operation - devices react to timers, network events, and application triggers rather than maintaining continuous connections

Running the simulation with different parameter sets builds intuition for the multi-way trade-offs between coverage, power, latency, and downlink reachability.

13.13 See Also

Theory Behind Simulation:

Real Hardware:

Alternative Simulations:

Common Pitfalls

NB-IoT lab simulations that only verify data transmission success (packet received on server) without measuring current consumption miss the primary objective: validating power optimization. A simulated NB-IoT lab must include a current measurement phase: configure PSM parameters, transmit test data, measure the current waveform (active peak, registration current, PSM sleep current), and calculate energy per transmission. Without current measurement, the lab validates connectivity but not the IoT-critical power optimization behavior.

NB-IoT lab simulations often test only the nominal PSM flow: transmit → enter PSM → wake → transmit. They miss the failure recovery path: if the device loses registration while in PSM (due to network reboot or signal loss), it must re-register on the next wakeup. Lab exercises must include a network disconnect simulation: power off the NB-IoT modem during PSM, restart it, and verify the device re-registers, re-activates the PDP context, and resumes normal operation. Firmware that cannot recover from this scenario will be stuck offline in production.

NB-IoT simulators that only model CE Level 0 (good coverage, no repetitions) do not prepare students for real deployments where 30–50% of devices operate in CE Level 1 or 2. Lab simulations should include coverage-impaired scenarios: use a signal attenuator to reduce RSRP below -100 dBm, observe the modem automatically switching to CE Level 1/2, measure the increased current consumption due to HARQ repetitions, and compare transmission time and energy at CE Level 0 vs CE Level 2.

AT+CPSMS=1,,,, requests PSM from the network but does not guarantee activation. The network may reject PSM or grant different timer values. Lab exercises that send AT+CPSMS=1 and immediately proceed without reading AT+CPSMS? to verify the granted values are testing an unconfigured system. Always verify PSM is actually active: send AT+CPSMS? and confirm the +CPSMS: 1,,, response shows “1” (active) before proceeding to PSM energy measurement steps.

13.14 What’s Next

Next Topic Description
NB-IoT Comprehensive Review Test your simulation knowledge with comprehensive quiz questions covering attach procedures, power modes, and CE levels
NB-IoT Labs and Implementation Move from simulation to real hardware using AT commands with SIM7020 and BC66 modules
Cellular IoT Fundamentals Expand to LTE-M, 5G NR-IoT, and other cellular IoT technologies for broader context
NB-IoT Power Optimization Deep dive into advanced power management strategies beyond basic PSM and eDRX
Cellular IoT Applications Apply NB-IoT concepts to real-world deployment scenarios including smart metering and asset tracking