1132  NB-IoT Lab Simulation

Hands-On ESP32 Simulation of NB-IoT Concepts

1132.1 Learning Objectives

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

  • Understand the NB-IoT attach procedure: Experience cell search, RRC connection, and authentication steps
  • Configure PSM timers: Observe T3412 and T3324 timer behavior in simulation
  • Compare PSM vs eDRX: See how different power modes affect sleep/wake patterns
  • Analyze Coverage Enhancement: Understand how repetitions improve coverage at cost of power
  • Calculate battery life: Use simulation data to project real-world battery consumption

1132.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 Learn:

  • Network Attach Procedure: Understand the multi-step process devices use to connect to cellular networks
  • PSM (Power Saving Mode): Experience how devices achieve 10+ year battery life through deep sleep states
  • eDRX (Extended Discontinuous Reception): Learn the trade-offs between power saving and downlink responsiveness
  • Coverage Extension Modes: See how signal repetition enables deep indoor penetration
  • Uplink Data Transmission: Observe the overhead involved in sending small IoT payloads
  • Network Status Monitoring: Track signal quality, network registration, and connection states

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

1132.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.

TipHow 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

1132.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";
  }
}

1132.5 Understanding the Simulation Output

As the simulation runs, observe these key behaviors:

1132.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

1132.6 Challenge Exercises

WarningChallenge 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?
WarningChallenge 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");
}
WarningChallenge 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?

1132.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

1132.8 Simulation vs Reality

ImportantImportant 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

1132.9 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

1132.10 What’s Next

Continue your NB-IoT learning journey with these related topics: