39  Zigbee Temp Monitor Lab

In 60 Seconds

This hands-on lab builds a real Zigbee temperature monitoring network using XBee S2C modules, an ESP32 coordinator, and Arduino end devices with DHT22 sensors. You will configure XBee modules via XCTU software, implement API Mode 2 frame parsing for reliable binary data, and create a coordinator that aggregates sensor readings with RSSI signal strength monitoring. The network reports temperature, humidity, and signal quality every 10 seconds.

39.1 Learning Objectives

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

  • Configure XBee S2C modules for coordinator and end device roles using XCTU software with API Mode 2
  • Construct a multi-node Zigbee sensor network by wiring DHT22 sensors to Arduino end devices
  • Parse XBee API Mode 2 frames, including escape sequence handling, for reliable binary data transfer
  • Integrate an ESP32 coordinator that aggregates temperature and humidity readings from multiple end devices
  • Evaluate network health by interpreting RSSI signal strength, sensor counts, and abnormal reading alerts

What is this lab? A practical hands-on project building a Zigbee temperature and humidity monitoring network with real hardware.

When to use:

  • When learning Zigbee coordinator/end device communication
  • For understanding XBee API mode programming
  • Before deploying production sensor networks

Key Topics:

Topic Focus
XBee Configuration XCTU software, API mode
Coordinator Role ESP32 as central hub
End Device Role Arduino with sensors
Data Aggregation Parsing and displaying readings

Prerequisites:

39.2 Prerequisites

Before starting this lab, ensure familiarity with:

39.3 Hardware Requirements

To complete this lab, you will need:

  • ESP32 Development Board
  • XBee S2C module (configured as coordinator)
  • 2x Arduino Uno + XBee modules (end devices)
  • 2x DHT22 temperature/humidity sensors
  • XBee Explorer USB adapter
  • Jumper wires
  • Breadboard
Zigbee temperature monitoring lab setup showing ESP32 coordinator, Arduino end devices with DHT22 sensors, and XBee wireless communication
Figure 39.1: Zigbee temperature monitoring lab setup showing hardware connections and data flow. Coordinator station (green) includes ESP32 development board connected via UART (GPIO 16/17) to XBee S2C coordinator module configured for PAN ID 0x1234 on channel 25, with USB serial monitor at 115200 baud for output. Two end device stations (orange) each contain Arduino Uno with SoftwareSerial (pins 10/11) connected to XBee end device modules and DHT22 temperature/humidity sensors on pin 2. Configuration section (navy) shows XCTU software used with XBee Explorer USB adapter to program XBee modules. Dotted lines represent wireless Zigbee mesh communication at 2.4 GHz between coordinator and end devices. Data flow sequence: DHT22 sensors measure temperature and humidity, Arduino formats data as “TEMP:22.5,HUMID:45” strings, XBee modules transmit to coordinator, ESP32 receives and parses data, network summary displays every 30 seconds showing all active sensors with their readings, battery levels, and RSSI signal strength.

39.4 Part 1: Configure XBee Coordinator

Use XCTU software to configure the XBee module as a coordinator:

  1. Connect XBee to computer via USB adapter
  2. Open XCTU software
  3. Configure as Coordinator:
ID (PAN ID): 1234
CE (Coordinator Enable): 1
AP (API Mode): 2
BD (Baud Rate): 9600
  1. Click “Write” to save configuration
API Mode 2

API Mode 2 (AP=2) uses escape sequences for reliable binary data transfer. Four special bytes are escaped: 0x7E (frame delimiter), 0x7D (escape character), 0x11 (XON), and 0x13 (XOFF). Each is replaced with 0x7D followed by the byte XORed with 0x20, preventing these control bytes from appearing as raw values in data payloads.

39.5 Part 2: ESP32 Coordinator Code

The ESP32 acts as the network coordinator, receiving data from all end devices:

#include <HardwareSerial.h>

// XBee connected to Serial2
#define XBEE_RX 16
#define XBEE_TX 17

HardwareSerial XBeeSerial(2);

struct SensorReading {
  uint16_t nodeAddress;
  float temperature;
  float humidity;
  unsigned long timestamp;
  int rssi;
};

SensorReading sensorData[10];
int sensorCount = 0;

void setup() {
  Serial.begin(115200);
  XBeeSerial.begin(9600, SERIAL_8N1, XBEE_RX, XBEE_TX);

  Serial.println("\n=== Zigbee Coordinator - Temperature Network ===");
  Serial.println("Waiting for sensor data...\n");

  delay(2000);  // Wait for XBee initialization
}

void loop() {
  // Check for incoming data from Zigbee network
  if (XBeeSerial.available()) {
    processZigbeePacket();
  }

  // Print network summary every 30 seconds
  static unsigned long lastSummary = 0;
  if (millis() - lastSummary > 30000) {
    printNetworkSummary();
    lastSummary = millis();
  }
}

void processZigbeePacket() {
  /**
   * Process XBee API frame (API mode 2).
   * Format: 7E [Length] [Frame Type] [Data] [Checksum]
   */

  if (XBeeSerial.read() != 0x7E) return;  // Start delimiter

  // Read length (2 bytes)
  uint16_t length = XBeeSerial.read() << 8;
  length |= XBeeSerial.read();

  uint8_t frameType = XBeeSerial.read();

  if (frameType == 0x90) {  // RX packet
    // Read 64-bit source address
    uint64_t sourceAddr64 = 0;
    for (int i = 0; i < 8; i++) {
      sourceAddr64 = (sourceAddr64 << 8) | XBeeSerial.read();
    }

    // Read 16-bit network address
    uint16_t sourceAddr16 = XBeeSerial.read() << 8;
    sourceAddr16 |= XBeeSerial.read();

    // Skip receive options
    XBeeSerial.read();

    // Read RF data (temperature and humidity)
    String data = "";
    for (int i = 0; i < length - 12; i++) {
      char c = XBeeSerial.read();
      if (c != 0xFF) data += c;
    }

    // Skip checksum
    XBeeSerial.read();

    // Parse sensor data
    parseSensorData(sourceAddr16, data);
  }
}

void parseSensorData(uint16_t nodeAddr, String data) {
  /**
   * Parse sensor data format: "TEMP:22.5,HUMID:45.2,RSSI:-45"
   */

  Serial.print("\n[Received] Node 0x");
  Serial.print(nodeAddr, HEX);
  Serial.print(": ");
  Serial.println(data);

  float temp = 0, humid = 0;
  int rssi = 0;

  // Extract temperature
  int tempIdx = data.indexOf("TEMP:");
  if (tempIdx >= 0) {
    temp = data.substring(tempIdx + 5, data.indexOf(",", tempIdx)).toFloat();
  }

  // Extract humidity
  int humidIdx = data.indexOf("HUMID:");
  if (humidIdx >= 0) {
    humid = data.substring(humidIdx + 6, data.indexOf(",", humidIdx)).toFloat();
  }

  // Extract RSSI
  int rssiIdx = data.indexOf("RSSI:");
  if (rssiIdx >= 0) {
    rssi = data.substring(rssiIdx + 5).toInt();
  }

  // Store/update sensor reading
  storeSensorReading(nodeAddr, temp, humid, rssi);

  // Display formatted data
  Serial.println("====================================");
  Serial.print("  Temperature: ");
  Serial.print(temp, 1);
  Serial.println(" C");
  Serial.print("  Humidity:    ");
  Serial.print(humid, 1);
  Serial.println(" %");
  Serial.print("  RSSI:        ");
  Serial.print(rssi);
  Serial.println(" dBm");
  Serial.println("====================================");

  // Alert on abnormal readings
  if (temp > 30.0) {
    Serial.println("[ALERT] HIGH TEMPERATURE!");
  }
  if (temp < 10.0) {
    Serial.println("[ALERT] LOW TEMPERATURE!");
  }
  if (humid > 70.0) {
    Serial.println("[ALERT] HIGH HUMIDITY!");
  }
}

void storeSensorReading(uint16_t nodeAddr, float temp, float humid, int rssi) {
  /**
   * Store sensor reading in memory
   */

  // Find existing sensor or add new one
  int idx = -1;
  for (int i = 0; i < sensorCount; i++) {
    if (sensorData[i].nodeAddress == nodeAddr) {
      idx = i;
      break;
    }
  }

  if (idx == -1 && sensorCount < 10) {
    idx = sensorCount++;
  }

  if (idx >= 0) {
    sensorData[idx].nodeAddress = nodeAddr;
    sensorData[idx].temperature = temp;
    sensorData[idx].humidity = humid;
    sensorData[idx].timestamp = millis();
    sensorData[idx].rssi = rssi;
  }
}

void printNetworkSummary() {
  /**
   * Print summary of all active sensors
   */

  Serial.println("\n======================================================================");
  Serial.println("NETWORK SUMMARY");
  Serial.println("======================================================================");
  Serial.print("Active Sensors: ");
  Serial.println(sensorCount);
  Serial.println("----------------------------------------------------------------------");
  Serial.println("Node      Temperature    Humidity    RSSI      Last Update");
  Serial.println("----------------------------------------------------------------------");

  unsigned long now = millis();

  for (int i = 0; i < sensorCount; i++) {
    unsigned long age_sec = (now - sensorData[i].timestamp) / 1000;

    Serial.print("0x");
    Serial.print(sensorData[i].nodeAddress, HEX);
    Serial.print("    ");
    Serial.print(sensorData[i].temperature, 1);
    Serial.print(" C        ");
    Serial.print(sensorData[i].humidity, 1);
    Serial.print("%       ");
    Serial.print(sensorData[i].rssi);
    Serial.print("dBm    ");
    Serial.print(age_sec);
    Serial.println("s ago");
  }

  Serial.println("======================================================================\n");
}
Note on Escape Handling

The coordinator code above reads bytes directly without API Mode 2 unescaping. This simplified version works for most text payloads but can produce corrupted readings when payload bytes match escape characters (0x7E, 0x7D, 0x11, 0x13). See the Common Mistake section below for the production-ready version with proper readEscaped() handling.

39.6 Part 3: Arduino End Device Code

Each end device reads sensors and transmits to the coordinator:

#include <SoftwareSerial.h>
#include <DHT.h>

#define DHTPIN 2
#define DHTTYPE DHT22

SoftwareSerial xbee(10, 11);  // RX, TX
DHT dht(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(9600);
  xbee.begin(9600);
  dht.begin();

  Serial.println("Zigbee End Device - Temperature Sensor");

  // Wait for network join
  delay(5000);
  Serial.println("[OK] Joined Zigbee network");
}

void loop() {
  // Read sensor
  float temp = dht.readTemperature();
  float humid = dht.readHumidity();

  if (isnan(temp) || isnan(humid)) {
    Serial.println("[ERROR] Failed to read sensor");
    delay(10000);
    return;
  }

  // Read RSSI (signal strength)
  int rssi = getRSSI();

  // Format and send data
  String data = "TEMP:" + String(temp, 1) +
                ",HUMID:" + String(humid, 1) +
                ",RSSI:" + String(rssi);

  xbee.print(data);

  Serial.print("Sent: ");
  Serial.println(data);

  // Sleep for 10 seconds
  delay(10000);
}

int getRSSI() {
  // Query XBee for last RSSI (simplified)
  // In real implementation, send AT command "ATDB"
  return -random(40, 70);  // Simulate RSSI between -40 and -70 dBm
}

39.7 Expected Output

When the network is running, the coordinator displays:

=== Zigbee Coordinator - Temperature Network ===
Waiting for sensor data...

[Received] Node 0x1: TEMP:22.3,HUMID:45.8,RSSI:-52
====================================
  Temperature: 22.3 C
  Humidity:    45.8 %
  RSSI:        -52 dBm
====================================

[Received] Node 0x2: TEMP:23.1,HUMID:48.2,RSSI:-58
====================================
  Temperature: 23.1 C
  Humidity:    48.2 %
  RSSI:        -58 dBm
====================================

======================================================================
NETWORK SUMMARY
======================================================================
Active Sensors: 2
----------------------------------------------------------------------
Node      Temperature    Humidity    RSSI      Last Update
----------------------------------------------------------------------
0x1       22.3 C         45.8%       -52dBm    5s ago
0x2       23.1 C         48.2%       -58dBm    3s ago
======================================================================

39.8 Troubleshooting

Common Issues
Problem Solution
No data received Verify PAN ID matches on all devices
Garbled data Check baud rate settings (9600)
Only partial packets Ensure API mode 2 on coordinator
Sensors not joining Check coordinator CE=1 setting
Weak RSSI Move devices closer or add routers

Sammy the Sensor (a DHT22) is proud: “I measure both temperature AND humidity, and I send my readings through the Zigbee network to the Coordinator every few seconds!”

Max the Microcontroller (the ESP32 Coordinator) explains: “I receive data from multiple sensors using API mode frames. Each frame is like a carefully packaged letter – it has a start byte, length, source address, and the actual sensor data, all wrapped up so I know exactly where it came from and what it says.”

Lila the LED adds: “And each sensor sends its signal strength (RSSI) along with the data. If the RSSI drops too low, the team knows to move the sensor closer or add a Router in between!”

Bella the Battery notes: “The Arduino end devices can run on battery power since they only wake up to measure and transmit. The Coordinator is plugged in because it has to listen all the time.”

Key ideas for kids:

  • DHT22 sensor = A device that measures temperature and humidity
  • API mode = A structured way of packaging data so computers can read it easily
  • RSSI = Signal strength indicator showing how close or far a device is
  • PAN ID = A unique number that identifies your Zigbee network (like a Wi-Fi network name)
Common Mistake: Forgetting to Escape API Mode 2 Frames

The Problem: You’ve built the ESP32 coordinator with XBee API Mode 2, but temperature readings are wildly incorrect – seeing values like 125°C or -95°C when room temperature is 22°C. The logs show garbled data: “TEMP:}~,HUMID:45}”.

Why It Happens:

XBee API Mode 2 uses escape sequences to prevent data bytes from being confused with frame control bytes:

  • Frame delimiter: 0x7E (start of frame)
  • Escape byte: 0x7D

When 0x7E or 0x7D appears in the payload data, it’s escaped: - 0x7E0x7D 0x5E (escape + XOR with 0x20) - 0x7D0x7D 0x5D

If your parser reads escaped bytes literally instead of unescaping them, sensor data becomes corrupted.

Real Example:

Sensor sends: TEMP:22.5,HUMID:45

ASCII values:

T E M P : 2 2 . 5 , H U M I D : 4 5
54 45 4D 50 3A 32 32 2E 35 2C 48 55 4D 49 44 3A 34 35

If a byte happens to be 0x7E (rare but possible in binary sensor readings), XBee transmits:

... 0x7D 0x5E ... (escaped)

Incorrect Parser (reads raw bytes):

void processZigbeePacket() {
  // ... frame header parsing ...

  String data = "";
  for (int i = 0; i < length - 12; i++) {
    char c = XBeeSerial.read();  // WRONG: Reads escaped bytes directly
    data += c;
  }
  // Result: "TEMP:}^,HUMID:45"  (0x7D = '}', 0x5E = '^')
}

Correct Parser (unescapes):

void processZigbeePacket() {
  // ... frame header parsing ...

  String data = "";
  for (int i = 0; i < length - 12; i++) {
    uint8_t byte = XBeeSerial.read();

    // Check for escape sequence
    if (byte == 0x7D) {
      byte = XBeeSerial.read() ^ 0x20;  // Read next byte and XOR with 0x20
      i++;  // Consumed 2 bytes from stream
    }

    data += (char)byte;
  }
  // Result: "TEMP:22.5,HUMID:45" (correctly unescaped)
}

Full Corrected Coordinator Code:

void processZigbeePacket() {
  if (XBeeSerial.read() != 0x7E) return;

  // Read length (2 bytes, already escaped if needed)
  uint16_t length = readEscaped() << 8;
  length |= readEscaped();

  uint8_t frameType = readEscaped();

  if (frameType == 0x90) {  // RX packet
    // Read 64-bit source address (8 bytes, unescape each)
    uint64_t sourceAddr64 = 0;
    for (int i = 0; i < 8; i++) {
      sourceAddr64 = (sourceAddr64 << 8) | readEscaped();
    }

    // Read 16-bit network address (2 bytes)
    uint16_t sourceAddr16 = readEscaped() << 8;
    sourceAddr16 |= readEscaped();

    // Skip receive options (1 byte)
    readEscaped();

    // Read RF data with unescaping
    String data = "";
    int remainingBytes = length - 12;  // Subtract header bytes
    for (int i = 0; i < remainingBytes; i++) {
      uint8_t byte = readEscaped();
      if (byte != 0xFF) data += (char)byte;
    }

    // Skip checksum (1 byte)
    readEscaped();

    parseSensorData(sourceAddr16, data);
  }
}

// Helper function to read and unescape bytes
uint8_t readEscaped() {
  uint8_t byte = XBeeSerial.read();

  if (byte == 0x7D) {
    byte = XBeeSerial.read() ^ 0x20;  // Unescape
  }

  return byte;
}

Testing the Fix:

Send test data with embedded control bytes:

// On Arduino end device:
void testEscaping() {
  uint8_t testData[] = {
    'T', 'E', 'S', 'T', ':',
    0x7E,  // This will be escaped to 0x7D 0x5E
    ',', 'D', 'A', 'T', 'A'
  };
  xbee.write(testData, sizeof(testData));
}

// Expected coordinator output:
// "TEST:~,DATA"  (0x7E = '~')

Performance Note: Unescaping adds ~2-5µs per escaped byte. In a typical 20-byte sensor payload, escaping affects 0-2 bytes, adding <10µs total – negligible compared to 9600 baud transmission time (20ms for 20 bytes).

Key Insight: API Mode 2 escaping is mandatory for binary data reliability, but you MUST unescape in your parser. Don’t use API Mode 1 (no escaping) for sensor networks – if a temperature reading happens to be 0x7E in binary, it will corrupt the frame. Always implement readEscaped() helper functions for API Mode 2 parsing.

API Mode 2 frame overhead determines effective throughput. For 20-byte sensor payload at 9600 baud:

\[\text{Frame size} = 1 (\text{delimiter}) + 2 (\text{length}) + 12 (\text{header}) + 20 (\text{payload}) + 1 (\text{checksum}) = 36 \text{ bytes}\]

\[\text{Transmission time} = \frac{36 \times 10 \text{ bits}}{9600 \text{ bps}} = 37.5ms\]

With escape byte probability ~5% (1-2 bytes per frame): \(36 + 2 = 38\) bytes → 39.6ms total

Network capacity: At 10-second reporting interval, coordinator can handle \(\frac{10,000ms}{39.6ms} = 252\) sensors before queue saturation. Real deployment with 200 sensors achieves 85% capacity utilization — comfortably below saturation threshold.

Common Pitfalls

Without a minimum report interval, attribute changes on a rapidly fluctuating temperature sensor generate a report on every measurement, flooding the network. Always set a minimum reporting interval (e.g., 30 seconds) to prevent reporting storms.

The ZCL Temperature Measurement Cluster reports temperature as int16 in units of 0.01°C (hundredths of a degree). Applications expecting whole-degree Celsius values need to divide by 100. Mismatched units cause readings 100x off actual temperature.

A temperature network with 5 sensors behaves very differently from one with 50 sensors. As sensor count grows, report traffic increases, potentially overwhelming the 802.15.4 channel during rapid temperature changes.

:

Key Concepts

  • Temperature Measurement Cluster (0x0402): A Zigbee Cluster Library cluster providing the MeasuredValue attribute (temperature in 0.01°C units) and optional min/max/tolerance attributes.
  • Reporting Configuration: A Zigbee ZCL mechanism allowing controllers to configure automatic attribute reports when values change beyond a threshold or at a maximum interval.
  • Binding (Temperature Network): Creating ZCL bindings between temperature sensor endpoints and display or thermostat endpoints for direct cluster communication without coordinator involvement.
  • Attribute Reporting: Automatic ZCL messages sent from a device to bound receivers when a configured attribute changes beyond the report threshold or the maximum report interval expires.
  • CoAP vs ZCL: Temperature sensor data can be conveyed via Zigbee’s ZCL cluster protocol (native Zigbee) or via CoAP (for Thread/6LoWPAN networks); the choice affects protocol stack and interoperability.

39.9 Concept Relationships

Concept Related To How They Connect
XBee Coordinator ESP32 Integration ESP32 receives sensor data via UART serial connection
PAN ID Network Identity All devices must share same PAN ID to communicate
API Mode 2 Frame Escaping Escaped bytes prevent data corruption in binary transmission
DHT22 Sensor Arduino End Device Arduino reads temperature/humidity and transmits via XBee
RSSI Monitoring Signal Strength Tracks radio link quality for network health assessment
Frame Parsing Data Extraction ESP32 decodes API frames to extract sensor readings

39.10 What’s Next

Chapter Focus
Zigbee Lab: Network Analyzer Visualize network topology and device health using Python monitoring tools
Zigbee Lab: Mesh Simulator Interactive Wokwi simulation for hands-on Zigbee mesh routing experiments
Zigbee Wokwi Simulation Lab Overview of all Zigbee simulation labs and getting started guide
Zigbee Security Security mechanisms for protecting sensor data in Zigbee networks
Zigbee Comprehensive Review End-to-end protocol deep dive covering the full Zigbee stack
Zigbee Industrial Deployment Real-world deployment patterns for production Zigbee sensor networks