39 Zigbee Temp Monitor Lab
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:
- Zigbee Fundamentals
- Basic Arduino/ESP32 programming
- Understanding of serial communication
39.2 Prerequisites
Before starting this lab, ensure familiarity with:
- Zigbee Fundamentals and Architecture: Device roles, PAN formation, and network concepts
- Network Topologies Fundamentals: Star and mesh topology concepts
- Basic serial communication: UART, baud rates, and data framing
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
39.4 Part 1: Configure XBee Coordinator
Use XCTU software to configure the XBee module as a coordinator:
- Connect XBee to computer via USB adapter
- Open XCTU software
- Configure as Coordinator:
ID (PAN ID): 1234
CE (Coordinator Enable): 1
AP (API Mode): 2
BD (Baud Rate): 9600
- Click “Write” to save configuration
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");
}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
| 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)
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: - 0x7E → 0x7D 0x5E (escape + XOR with 0x20) - 0x7D → 0x7D 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
MeasuredValueattribute (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 |