Zigbee Device Control in Java

Introduction

Zigbee is a wireless communication protocol designed for low-power, low-data-rate applications like home automation, IoT devices, and industrial control systems. This guide covers Zigbee device control in Java using various libraries and frameworks.

Zigbee Protocol Overview

Zigbee Network Architecture

public enum ZigbeeNetworkRole {
COORDINATOR(0x00),      // Forms the network, most capable device
ROUTER(0x01),           // Extends network range, can route messages
END_DEVICE(0x02);       // Battery-powered, sleeps most of the time
private final int code;
ZigbeeNetworkRole(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}
public enum ZigbeeProfile {
HOME_AUTOMATION(0x0104),
SMART_ENERGY(0x0109),
LIGHT_LINK(0xC05E),
GREEN_POWER(0xA1E0);
private final int id;
ZigbeeProfile(int id) {
this.id = id;
}
public int getId() {
return id;
}
}

Zigbee Communication Libraries

Using jZigate (Java Zigbee Gateway)

public class ZigbeeGatewayManager {
private static final Logger logger = LoggerFactory.getLogger(ZigbeeGatewayManager.class);
private ZigbeeGateway gateway;
private Map<String, ZigbeeDevice> devices;
private ZigbeeMessageListener messageListener;
public ZigbeeGatewayManager(String port, int baudRate) {
this.devices = new ConcurrentHashMap<>();
initializeGateway(port, baudRate);
}
private void initializeGateway(String port, int baudRate) {
try {
// Initialize serial connection to Zigbee gateway (like ZiGate, CC2531)
SerialPort serialPort = new SerialPort(port);
if (serialPort.openPort()) {
serialPort.setParams(baudRate, 8, 1, 0);
serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
this.gateway = new ZigbeeGateway(serialPort);
setupMessageListener();
logger.info("Zigbee gateway initialized on port: {}", port);
}
} catch (SerialPortException e) {
logger.error("Failed to initialize Zigbee gateway", e);
}
}
private void setupMessageListener() {
this.messageListener = new ZigbeeMessageListener() {
@Override
public void onDeviceAnnounce(ZigbeeDevice device) {
logger.info("New device announced: {}", device.getIEEEAddress());
devices.put(device.getIEEEAddress(), device);
notifyDeviceDiscovery(device);
}
@Override
public void onAttributeReport(AttributeReport report) {
logger.debug("Attribute report from {}: cluster={}, attribute={}, value={}",
report.getDeviceAddress(), report.getClusterId(), 
report.getAttributeId(), report.getValue());
updateDeviceState(report.getDeviceAddress(), report);
notifyAttributeUpdate(report);
}
@Override
public void onNetworkJoined(NetworkJoinEvent event) {
logger.info("Device joined network: {}", event.getDeviceAddress());
// Perform device interview to discover capabilities
interviewDevice(event.getDeviceAddress());
}
};
gateway.addMessageListener(messageListener);
}
public void startNetwork() {
if (gateway != null) {
gateway.startNetwork();
gateway.permitJoin(60); // Allow joining for 60 seconds
logger.info("Zigbee network started, permitting join for 60 seconds");
}
}
public void stopNetwork() {
if (gateway != null) {
gateway.permitJoin(0); // Disable joining
gateway.stopNetwork();
logger.info("Zigbee network stopped");
}
}
private void interviewDevice(String deviceAddress) {
// Query device basic information
gateway.sendZigbeeCommand(new ReadAttributeCommand(
deviceAddress, 
ZigbeeCluster.BASIC, 
Arrays.asList(0x0000, 0x0001, 0x0002, 0x0003, 0x0004) // Basic cluster attributes
));
}
public void sendCommand(String deviceAddress, ZigbeeCommand command) {
if (gateway != null && devices.containsKey(deviceAddress)) {
gateway.sendZigbeeCommand(command);
logger.debug("Command sent to device {}: {}", deviceAddress, command);
} else {
logger.warn("Device not found or gateway not available: {}", deviceAddress);
}
}
// Event notification for application listeners
private List<ZigbeeEventListener> eventListeners = new CopyOnWriteArrayList<>();
public void addEventListener(ZigbeeEventListener listener) {
eventListeners.add(listener);
}
private void notifyDeviceDiscovery(ZigbeeDevice device) {
for (ZigbeeEventListener listener : eventListeners) {
listener.onDeviceDiscovered(device);
}
}
private void notifyAttributeUpdate(AttributeReport report) {
for (ZigbeeEventListener listener : eventListeners) {
listener.onAttributeUpdated(report);
}
}
}

Device Management

Zigbee Device Representation

public class ZigbeeDevice {
private final String ieeeAddress;
private final String networkAddress;
private final String manufacturer;
private final String model;
private final Set<ZigbeeCluster> inputClusters;
private final Set<ZigbeeCluster> outputClusters;
private final Map<DeviceEndpoint, EndpointInfo> endpoints;
private DeviceStatus status;
private Instant lastSeen;
public ZigbeeDevice(String ieeeAddress, String networkAddress) {
this.ieeeAddress = ieeeAddress;
this.networkAddress = networkAddress;
this.inputClusters = new HashSet<>();
this.outputClusters = new HashSet<>();
this.endpoints = new HashMap<>();
this.status = DeviceStatus.UNKNOWN;
}
public void addEndpoint(DeviceEndpoint endpoint, EndpointInfo info) {
endpoints.put(endpoint, info);
inputClusters.addAll(info.getInputClusters());
outputClusters.addAll(info.getOutputClusters());
}
public boolean supportsCluster(ZigbeeCluster cluster) {
return inputClusters.contains(cluster) || outputClusters.contains(cluster);
}
public boolean supportsCommand(ZigbeeCluster cluster, int commandId) {
EndpointInfo endpoint = getFirstEndpointForCluster(cluster);
return endpoint != null && endpoint.supportsCommand(cluster, commandId);
}
private EndpointInfo getFirstEndpointForCluster(ZigbeeCluster cluster) {
return endpoints.values().stream()
.filter(ep -> ep.getInputClusters().contains(cluster) || 
ep.getOutputClusters().contains(cluster))
.findFirst()
.orElse(null);
}
// Getters
public String getIEEEAddress() { return ieeeAddress; }
public String getNetworkAddress() { return networkAddress; }
public String getManufacturer() { return manufacturer; }
public String getModel() { return model; }
public DeviceStatus getStatus() { return status; }
public Instant getLastSeen() { return lastSeen; }
public void setStatus(DeviceStatus status) {
this.status = status;
this.lastSeen = Instant.now();
}
}
public class DeviceEndpoint {
private final String deviceAddress;
private final int endpointId;
public DeviceEndpoint(String deviceAddress, int endpointId) {
this.deviceAddress = deviceAddress;
this.endpointId = endpointId;
}
// Getters, equals, hashCode
}
public class EndpointInfo {
private final int profileId;
private final int deviceId;
private final int deviceVersion;
private final Set<ZigbeeCluster> inputClusters;
private final Set<ZigbeeCluster> outputClusters;
public EndpointInfo(int profileId, int deviceId, int deviceVersion) {
this.profileId = profileId;
this.deviceId = deviceId;
this.deviceVersion = deviceVersion;
this.inputClusters = new HashSet<>();
this.outputClusters = new HashSet<>();
}
public void addInputCluster(ZigbeeCluster cluster) {
inputClusters.add(cluster);
}
public void addOutputCluster(ZigbeeCluster cluster) {
outputClusters.add(cluster);
}
public boolean supportsCommand(ZigbeeCluster cluster, int commandId) {
// Implementation would check if cluster supports the command
return inputClusters.contains(cluster) || outputClusters.contains(cluster);
}
// Getters
public Set<ZigbeeCluster> getInputClusters() { return inputClusters; }
public Set<ZigbeeCluster> getOutputClusters() { return outputClusters; }
}
public enum DeviceStatus {
UNKNOWN, ONLINE, OFFLINE, JOINING, INTERVIEWING
}

Zigbee Clusters and Commands

Common Zigbee Clusters

public enum ZigbeeCluster {
// Basic clusters
BASIC(0x0000),
POWER_CONFIGURATION(0x0001),
DEVICE_TEMPERATURE(0x0002),
IDENTIFY(0x0003),
// Lighting clusters
ON_OFF(0x0006),
LEVEL_CONTROL(0x0008),
COLOR_CONTROL(0x0300),
// Measurement clusters
TEMPERATURE_MEASUREMENT(0x0402),
HUMIDITY_MEASUREMENT(0x0405),
PRESSURE_MEASUREMENT(0x0403),
// Security clusters
IAS_ZONE(0x0500),
IAS_WARNING(0x0502);
private final int id;
ZigbeeCluster(int id) {
this.id = id;
}
public int getId() {
return id;
}
public static ZigbeeCluster fromId(int id) {
for (ZigbeeCluster cluster : values()) {
if (cluster.id == id) {
return cluster;
}
}
return null;
}
}

Command Framework

public abstract class ZigbeeCommand {
protected final String deviceAddress;
protected final int endpoint;
protected final ZigbeeCluster cluster;
protected final int commandId;
protected final byte[] payload;
public ZigbeeCommand(String deviceAddress, int endpoint, 
ZigbeeCluster cluster, int commandId, byte[] payload) {
this.deviceAddress = deviceAddress;
this.endpoint = endpoint;
this.cluster = cluster;
this.commandId = commandId;
this.payload = payload != null ? payload.clone() : new byte[0];
}
public abstract byte[] toZigbeeFrame();
// Getters
public String getDeviceAddress() { return deviceAddress; }
public int getEndpoint() { return endpoint; }
public ZigbeeCluster getCluster() { return cluster; }
public int getCommandId() { return commandId; }
public byte[] getPayload() { return payload.clone(); }
}
public class OnOffCommand extends ZigbeeCommand {
private static final int ON_COMMAND = 0x01;
private static final int OFF_COMMAND = 0x00;
private static final int TOGGLE_COMMAND = 0x02;
public static OnOffCommand on(String deviceAddress, int endpoint) {
return new OnOffCommand(deviceAddress, endpoint, ON_COMMAND);
}
public static OnOffCommand off(String deviceAddress, int endpoint) {
return new OnOffCommand(deviceAddress, endpoint, OFF_COMMAND);
}
public static OnOffCommand toggle(String deviceAddress, int endpoint) {
return new OnOffCommand(deviceAddress, endpoint, TOGGLE_COMMAND);
}
private OnOffCommand(String deviceAddress, int endpoint, int command) {
super(deviceAddress, endpoint, ZigbeeCluster.ON_OFF, command, new byte[0]);
}
@Override
public byte[] toZigbeeFrame() {
// Convert to Zigbee application frame
ByteBuffer buffer = ByteBuffer.allocate(10 + payload.length);
buffer.put((byte) 0x01); // Frame control
buffer.putShort((short) cluster.getId());
buffer.put((byte) commandId);
buffer.put(payload);
return buffer.array();
}
}
public class LevelControlCommand extends ZigbeeCommand {
public static LevelControlCommand moveToLevel(String deviceAddress, int endpoint, 
int level, int transitionTime) {
byte[] payload = new byte[3];
payload[0] = (byte) level;
payload[1] = (byte) (transitionTime & 0xFF);
payload[2] = (byte) ((transitionTime >> 8) & 0xFF);
return new LevelControlCommand(deviceAddress, endpoint, 0x00, payload);
}
public static LevelControlCommand move(String deviceAddress, int endpoint, 
MoveMode mode, int rate) {
byte[] payload = new byte[2];
payload[0] = (byte) mode.getValue();
payload[1] = (byte) rate;
return new LevelControlCommand(deviceAddress, endpoint, 0x01, payload);
}
public static LevelControlCommand step(String deviceAddress, int endpoint, 
StepMode mode, int stepSize, 
int transitionTime) {
byte[] payload = new byte[4];
payload[0] = (byte) mode.getValue();
payload[1] = (byte) stepSize;
payload[2] = (byte) (transitionTime & 0xFF);
payload[3] = (byte) ((transitionTime >> 8) & 0xFF);
return new LevelControlCommand(deviceAddress, endpoint, 0x02, payload);
}
private LevelControlCommand(String deviceAddress, int endpoint, 
int commandId, byte[] payload) {
super(deviceAddress, endpoint, ZigbeeCluster.LEVEL_CONTROL, commandId, payload);
}
@Override
public byte[] toZigbeeFrame() {
ByteBuffer buffer = ByteBuffer.allocate(10 + payload.length);
buffer.put((byte) 0x01); // Frame control
buffer.putShort((short) cluster.getId());
buffer.put((byte) commandId);
buffer.put(payload);
return buffer.array();
}
public enum MoveMode {
UP(0x00), DOWN(0x01);
private final int value;
MoveMode(int value) { this.value = value; }
public int getValue() { return value; }
}
public enum StepMode {
UP(0x00), DOWN(0x01);
private final int value;
StepMode(int value) { this.value = value; }
public int getValue() { return value; }
}
}
public class ColorControlCommand extends ZigbeeCommand {
public static ColorControlCommand moveToHue(String deviceAddress, int endpoint, 
int hue, HueDirection direction, 
int transitionTime) {
byte[] payload = new byte[3];
payload[0] = (byte) hue;
payload[1] = (byte) direction.getValue();
payload[2] = (byte) transitionTime;
return new ColorControlCommand(deviceAddress, endpoint, 0x00, payload);
}
public static ColorControlCommand moveToColor(String deviceAddress, int endpoint, 
int x, int y, int transitionTime) {
byte[] payload = new byte[5];
payload[0] = (byte) (x & 0xFF);
payload[1] = (byte) ((x >> 8) & 0xFF);
payload[2] = (byte) (y & 0xFF);
payload[3] = (byte) ((y >> 8) & 0xFF);
payload[4] = (byte) transitionTime;
return new ColorControlCommand(deviceAddress, endpoint, 0x07, payload);
}
public static ColorControlCommand moveToColorTemperature(String deviceAddress, 
int endpoint, 
int colorTemperature, 
int transitionTime) {
byte[] payload = new byte[3];
payload[0] = (byte) (colorTemperature & 0xFF);
payload[1] = (byte) ((colorTemperature >> 8) & 0xFF);
payload[2] = (byte) transitionTime;
return new ColorControlCommand(deviceAddress, endpoint, 0x0A, payload);
}
private ColorControlCommand(String deviceAddress, int endpoint, 
int commandId, byte[] payload) {
super(deviceAddress, endpoint, ZigbeeCluster.COLOR_CONTROL, commandId, payload);
}
@Override
public byte[] toZigbeeFrame() {
ByteBuffer buffer = ByteBuffer.allocate(10 + payload.length);
buffer.put((byte) 0x01); // Frame control
buffer.putShort((short) cluster.getId());
buffer.put((byte) commandId);
buffer.put(payload);
return buffer.array();
}
public enum HueDirection {
SHORTEST_DISTANCE(0x00), LONGEST_DISTANCE(0x01), UP(0x02), DOWN(0x03);
private final int value;
HueDirection(int value) { this.value = value; }
public int getValue() { return value; }
}
}

Device-Specific Implementations

Smart Bulb Controller

public class SmartBulbController {
private final ZigbeeGatewayManager gateway;
private final String deviceAddress;
private final int endpoint;
private boolean powerState;
private int brightness; // 0-254
private int colorTemperature; // Kelvin
private int hue; // 0-254
private int saturation; // 0-254
public SmartBulbController(ZigbeeGatewayManager gateway, String deviceAddress, int endpoint) {
this.gateway = gateway;
this.deviceAddress = deviceAddress;
this.endpoint = endpoint;
}
public void turnOn() {
ZigbeeCommand command = OnOffCommand.on(deviceAddress, endpoint);
gateway.sendCommand(deviceAddress, command);
this.powerState = true;
}
public void turnOff() {
ZigbeeCommand command = OnOffCommand.off(deviceAddress, endpoint);
gateway.sendCommand(deviceAddress, command);
this.powerState = false;
}
public void toggle() {
ZigbeeCommand command = OnOffCommand.toggle(deviceAddress, endpoint);
gateway.sendCommand(deviceAddress, command);
this.powerState = !powerState;
}
public void setBrightness(int level) {
if (level < 0) level = 0;
if (level > 254) level = 254;
ZigbeeCommand command = LevelControlCommand.moveToLevel(
deviceAddress, endpoint, level, 10 // 1 second transition
);
gateway.sendCommand(deviceAddress, command);
this.brightness = level;
}
public void setColorTemperature(int kelvin) {
if (kelvin < 2000) kelvin = 2000;
if (kelvin > 6500) kelvin = 6500;
// Convert Kelvin to Mired (reciprocal megakelvin)
int mired = 1000000 / kelvin;
ZigbeeCommand command = ColorControlCommand.moveToColorTemperature(
deviceAddress, endpoint, mired, 10
);
gateway.sendCommand(deviceAddress, command);
this.colorTemperature = kelvin;
}
public void setHueSaturation(int hue, int saturation) {
if (hue < 0) hue = 0;
if (hue > 254) hue = 254;
if (saturation < 0) saturation = 0;
if (saturation > 254) saturation = 254;
ZigbeeCommand command = ColorControlCommand.moveToHue(
deviceAddress, endpoint, hue, 
ColorControlCommand.HueDirection.SHORTEST_DISTANCE, 10
);
gateway.sendCommand(deviceAddress, command);
this.hue = hue;
this.saturation = saturation;
}
public void setScene(int sceneId) {
// Recall scene command
byte[] payload = new byte[] { (byte) sceneId };
ZigbeeCommand command = new ZigbeeCommand(
deviceAddress, endpoint, ZigbeeCluster.SCENES, 0x05, payload
);
gateway.sendCommand(deviceAddress, command);
}
public void updateState() {
// Read current state from device
ReadAttributeCommand readOnOff = new ReadAttributeCommand(
deviceAddress, endpoint, ZigbeeCluster.ON_OFF, 0x0000
);
gateway.sendCommand(deviceAddress, readOnOff);
ReadAttributeCommand readLevel = new ReadAttributeCommand(
deviceAddress, endpoint, ZigbeeCluster.LEVEL_CONTROL, 0x0000
);
gateway.sendCommand(deviceAddress, readLevel);
}
// Getters
public boolean isPoweredOn() { return powerState; }
public int getBrightness() { return brightness; }
public int getColorTemperature() { return colorTemperature; }
public int getHue() { return hue; }
public int getSaturation() { return saturation; }
}

Temperature Sensor Controller

public class TemperatureSensorController {
private final ZigbeeGatewayManager gateway;
private final String deviceAddress;
private final int endpoint;
private float temperature;
private float humidity;
private float batteryLevel;
private Instant lastUpdate;
public TemperatureSensorController(ZigbeeGatewayManager gateway, 
String deviceAddress, int endpoint) {
this.gateway = gateway;
this.deviceAddress = deviceAddress;
this.endpoint = endpoint;
}
public void configureReporting() {
// Configure temperature reporting
ConfigureReportingCommand tempReporting = new ConfigureReportingCommand(
deviceAddress, endpoint, ZigbeeCluster.TEMPERATURE_MEASUREMENT,
0x0000, // MeasuredValue attribute
10, 300, 10 // min interval, max interval, reportable change
);
gateway.sendCommand(deviceAddress, tempReporting);
// Configure battery reporting
ConfigureReportingCommand batteryReporting = new ConfigureReportingCommand(
deviceAddress, endpoint, ZigbeeCluster.POWER_CONFIGURATION,
0x0020, // BatteryPercentage attribute
3600, 7200, 1 // Report every 1-2 hours, or 1% change
);
gateway.sendCommand(deviceAddress, batteryReporting);
}
public void updateReadings() {
// Read current measurements
ReadAttributeCommand readTemperature = new ReadAttributeCommand(
deviceAddress, endpoint, ZigbeeCluster.TEMPERATURE_MEASUREMENT, 
Arrays.asList(0x0000) // MeasuredValue
);
gateway.sendCommand(deviceAddress, readTemperature);
ReadAttributeCommand readBattery = new ReadAttributeCommand(
deviceAddress, endpoint, ZigbeeCluster.POWER_CONFIGURATION,
Arrays.asList(0x0020) // BatteryPercentage
);
gateway.sendCommand(deviceAddress, readBattery);
}
public void handleAttributeReport(AttributeReport report) {
switch (report.getClusterId()) {
case 0x0402: // Temperature Measurement
if (report.getAttributeId() == 0x0000) {
this.temperature = parseTemperature(report.getValue());
this.lastUpdate = Instant.now();
}
break;
case 0x0405: // Humidity Measurement
if (report.getAttributeId() == 0x0000) {
this.humidity = parseHumidity(report.getValue());
this.lastUpdate = Instant.now();
}
break;
case 0x0001: // Power Configuration
if (report.getAttributeId() == 0x0020) {
this.batteryLevel = parseBatteryLevel(report.getValue());
}
break;
}
}
private float parseTemperature(byte[] value) {
if (value.length >= 2) {
int raw = ((value[1] & 0xFF) << 8) | (value[0] & 0xFF);
return raw / 100.0f; // Temperature in degrees Celsius
}
return Float.NaN;
}
private float parseHumidity(byte[] value) {
if (value.length >= 2) {
int raw = ((value[1] & 0xFF) << 8) | (value[0] & 0xFF);
return raw / 100.0f; // Humidity in percentage
}
return Float.NaN;
}
private float parseBatteryLevel(byte[] value) {
if (value.length >= 1) {
return value[0] & 0xFF; // Battery percentage 0-100
}
return Float.NaN;
}
// Getters
public float getTemperature() { return temperature; }
public float getHumidity() { return humidity; }
public float getBatteryLevel() { return batteryLevel; }
public Instant getLastUpdate() { return lastUpdate; }
}

Using Apache Camel for Zigbee Integration

Camel Route for Zigbee Device Management

public class ZigbeeCamelRoute extends RouteBuilder {
private static final Logger logger = LoggerFactory.getLogger(ZigbeeCamelRoute.class);
@Override
public void configure() throws Exception {
// Route for processing Zigbee messages
from("zigbee:gateway?port=/dev/ttyUSB0&baudRate=115200")
.choice()
.when(header("zigbee.messageType").isEqualTo("device_announce"))
.process(new DeviceAnnounceProcessor())
.to("direct:deviceDiscovery")
.when(header("zigbee.messageType").isEqualTo("attribute_report"))
.process(new AttributeReportProcessor())
.to("direct:attributeUpdate")
.when(header("zigbee.messageType").isEqualTo("network_join"))
.process(new NetworkJoinProcessor())
.to("direct:networkJoin")
.end();
// Device discovery handling
from("direct:deviceDiscovery")
.process(exchange -> {
ZigbeeDevice device = exchange.getIn().getBody(ZigbeeDevice.class);
logger.info("Discovered new device: {}", device.getIEEEAddress());
// Store device in registry
exchange.getContext().getRegistry().bind(
"device." + device.getIEEEAddress(), device);
// Start device interview
exchange.getIn().setHeader("interviewDevice", device.getIEEEAddress());
})
.to("direct:startInterview");
// Attribute update handling
from("direct:attributeUpdate")
.process(exchange -> {
AttributeReport report = exchange.getIn().getBody(AttributeReport.class);
// Update device state in registry
String deviceKey = "device." + report.getDeviceAddress();
ZigbeeDevice device = exchange.getContext()
.getRegistry().lookupByNameAndType(deviceKey, ZigbeeDevice.class);
if (device != null) {
updateDeviceState(device, report);
}
// Convert to IoT data format
exchange.getIn().setBody(convertToIoTData(report));
})
.to("mqtt:iot/data?publishTopicName=sensors/${header.deviceAddress}");
// Device interview process
from("direct:startInterview")
.delay(1000) // Wait a bit after device announcement
.process(exchange -> {
String deviceAddress = exchange.getIn().getHeader("interviewDevice", String.class);
performDeviceInterview(exchange.getContext(), deviceAddress);
});
}
private void performDeviceInterview(CamelContext context, String deviceAddress) {
try {
ZigbeeGatewayManager gateway = context.getRegistry()
.lookupByNameAndType("zigbeeGateway", ZigbeeGatewayManager.class);
// Query basic device information
ReadAttributeCommand basicInfo = new ReadAttributeCommand(
deviceAddress, 1, ZigbeeCluster.BASIC,
Arrays.asList(0x0000, 0x0001, 0x0003, 0x0004, 0x0005)
);
gateway.sendCommand(deviceAddress, basicInfo);
// Query active endpoints
gateway.sendCommand(deviceAddress, new ActiveEndpointsCommand(deviceAddress));
} catch (Exception e) {
logger.error("Failed to interview device: {}", deviceAddress, e);
}
}
private IoTData convertToIoTData(AttributeReport report) {
IoTData data = new IoTData();
data.setDeviceId(report.getDeviceAddress());
data.setTimestamp(Instant.now());
data.setClusterId(report.getClusterId());
data.setAttributeId(report.getAttributeId());
data.setValue(report.getValue());
return data;
}
private void updateDeviceState(ZigbeeDevice device, AttributeReport report) {
// Update device state based on attribute reports
device.setStatus(DeviceStatus.ONLINE);
device.setLastSeen(Instant.now());
}
}
// Processor classes
public class DeviceAnnounceProcessor implements Processor {
@Override
public void process(Exchange exchange) throws Exception {
byte[] message = exchange.getIn().getBody(byte[].class);
ZigbeeDevice device = parseDeviceAnnounce(message);
exchange.getIn().setBody(device);
exchange.getIn().setHeader("zigbee.messageType", "device_announce");
}
private ZigbeeDevice parseDeviceAnnounce(byte[] message) {
// Parse device announce message
ByteBuffer buffer = ByteBuffer.wrap(message);
String ieeeAddress = parseIEEEAddress(buffer);
String networkAddress = parseNetworkAddress(buffer);
return new ZigbeeDevice(ieeeAddress, networkAddress);
}
private String parseIEEEAddress(ByteBuffer buffer) {
byte[] address = new byte[8];
buffer.get(address);
return bytesToHex(address);
}
private String parseNetworkAddress(ByteBuffer buffer) {
return String.format("%04X", buffer.getShort());
}
private String bytesToHex(byte[] bytes) {
StringBuilder hex = new StringBuilder();
for (byte b : bytes) {
hex.append(String.format("%02X", b));
}
return hex.toString();
}
}

Security and Encryption

Zigbee Security Framework

public class ZigbeeSecurityManager {
private final KeyStore keyStore;
private final Map<String, ZigbeeDeviceKey> deviceKeys;
public ZigbeeSecurityManager() {
this.deviceKeys = new ConcurrentHashMap<>();
this.keyStore = initializeKeyStore();
}
public void installDeviceKey(String deviceAddress, byte[] networkKey, byte[] linkKey) {
ZigbeeDeviceKey deviceKey = new ZigbeeDeviceKey(deviceAddress, networkKey, linkKey);
deviceKeys.put(deviceAddress, deviceKey);
// Store key securely
storeKeySecurely(deviceAddress, deviceKey);
}
public byte[] encryptMessage(String deviceAddress, byte[] message) {
ZigbeeDeviceKey deviceKey = deviceKeys.get(deviceAddress);
if (deviceKey == null) {
throw new SecurityException("No security key for device: " + deviceAddress);
}
try {
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(deviceKey.getNetworkKey(), "AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
return cipher.doFinal(message);
} catch (Exception e) {
throw new SecurityException("Failed to encrypt message", e);
}
}
public byte[] decryptMessage(String deviceAddress, byte[] encryptedMessage) {
ZigbeeDeviceKey deviceKey = deviceKeys.get(deviceAddress);
if (deviceKey == null) {
throw new SecurityException("No security key for device: " + deviceAddress);
}
try {
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(deviceKey.getNetworkKey(), "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
return cipher.doFinal(encryptedMessage);
} catch (Exception e) {
throw new SecurityException("Failed to decrypt message", e);
}
}
public boolean verifyMessageIntegrity(String deviceAddress, byte[] message, byte[] mic) {
ZigbeeDeviceKey deviceKey = deviceKeys.get(deviceAddress);
if (deviceKey == null) {
return false;
}
try {
// Calculate MIC (Message Integrity Code)
byte[] calculatedMic = calculateMIC(deviceKey.getNetworkKey(), message);
return Arrays.equals(calculatedMic, mic);
} catch (Exception e) {
return false;
}
}
private byte[] calculateMIC(byte[] key, byte[] message) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(key, "HmacSHA256");
mac.init(keySpec);
return mac.doFinal(message);
} catch (Exception e) {
throw new SecurityException("Failed to calculate MIC", e);
}
}
private KeyStore initializeKeyStore() {
try {
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(null, null); // Initialize empty keystore
return ks;
} catch (Exception e) {
throw new SecurityException("Failed to initialize key store", e);
}
}
private void storeKeySecurely(String deviceAddress, ZigbeeDeviceKey deviceKey) {
try {
KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(
new SecretKeySpec(deviceKey.getNetworkKey(), "AES"));
KeyStore.ProtectionParameter protection = 
new KeyStore.PasswordProtection("password".toCharArray());
keyStore.setEntry("device-" + deviceAddress, entry, protection);
} catch (Exception e) {
throw new SecurityException("Failed to store key securely", e);
}
}
}
public class ZigbeeDeviceKey {
private final String deviceAddress;
private final byte[] networkKey;
private final byte[] linkKey;
private final Instant created;
public ZigbeeDeviceKey(String deviceAddress, byte[] networkKey, byte[] linkKey) {
this.deviceAddress = deviceAddress;
this.networkKey = networkKey.clone();
this.linkKey = linkKey.clone();
this.created = Instant.now();
}
// Getters
public byte[] getNetworkKey() { return networkKey.clone(); }
public byte[] getLinkKey() { return linkKey.clone(); }
public String getDeviceAddress() { return deviceAddress; }
public Instant getCreated() { return created; }
public void clear() {
Arrays.fill(networkKey, (byte) 0);
Arrays.fill(linkKey, (byte) 0);
}
}

Testing and Simulation

Zigbee Device Simulator for Testing

public class ZigbeeDeviceSimulator {
private final Map<String, SimulatedDevice> simulatedDevices;
private final ZigbeeGatewayManager gateway;
public ZigbeeDeviceSimulator(ZigbeeGatewayManager gateway) {
this.gateway = gateway;
this.simulatedDevices = new ConcurrentHashMap<>();
setupTestDevices();
}
private void setupTestDevices() {
// Simulate a smart bulb
SimulatedDevice bulb = new SimulatedDevice(
"00:15:8D:00:02:AB:CD:EF", // IEEE Address
"0x1234", // Network Address
ZigbeeProfile.HOME_AUTOMATION,
DeviceType.DIMMABLE_LIGHT
);
bulb.addEndpoint(1, new EndpointInfo(0x0104, 0x0100, 1)
.addInputCluster(ZigbeeCluster.ON_OFF)
.addInputCluster(ZigbeeCluster.LEVEL_CONTROL)
.addInputCluster(ZigbeeCluster.COLOR_CONTROL));
simulatedDevices.put(bulb.getIEEEAddress(), bulb);
// Simulate a temperature sensor
SimulatedDevice sensor = new SimulatedDevice(
"00:15:8D:00:02:AB:CD:FE",
"0x1235",
ZigbeeProfile.HOME_AUTOMATION,
DeviceType.TEMPERATURE_SENSOR
);
sensor.addEndpoint(1, new EndpointInfo(0x0104, 0x0302, 1)
.addInputCluster(ZigbeeCluster.TEMPERATURE_MEASUREMENT)
.addInputCluster(ZigbeeCluster.POWER_CONFIGURATION));
simulatedDevices.put(sensor.getIEEEAddress(), sensor);
}
public void simulateDeviceJoin(String deviceAddress) {
SimulatedDevice device = simulatedDevices.get(deviceAddress);
if (device != null) {
// Simulate device joining the network
gateway.getEventListener().onNetworkJoined(
new NetworkJoinEvent(deviceAddress, device.getNetworkAddress())
);
// Simulate device announcement
gateway.getEventListener().onDeviceAnnounce(device);
logger.info("Simulated device join: {}", deviceAddress);
}
}
public void simulateAttributeReport(String deviceAddress, int clusterId, 
int attributeId, Object value) {
SimulatedDevice device = simulatedDevices.get(deviceAddress);
if (device != null) {
AttributeReport report = new AttributeReport(
deviceAddress, clusterId, attributeId, convertValueToBytes(value)
);
gateway.getEventListener().onAttributeReport(report);
logger.debug("Simulated attribute report from {}: {}.{} = {}", 
deviceAddress, clusterId, attributeId, value);
}
}
private byte[] convertValueToBytes(Object value) {
if (value instanceof Boolean) {
return new byte[] { (byte) (((Boolean) value) ? 0x01 : 0x00) };
} else if (value instanceof Integer) {
int intValue = (Integer) value;
return new byte[] { 
(byte) (intValue & 0xFF), 
(byte) ((intValue >> 8) & 0xFF) 
};
} else if (value instanceof Float) {
// Convert float to IEEE 754 half-precision or use scaling
float floatValue = (Float) value;
int scaled = Math.round(floatValue * 100); // Scale for temperature
return new byte[] { 
(byte) (scaled & 0xFF), 
(byte) ((scaled >> 8) & 0xFF) 
};
}
return new byte[0];
}
public void simulateTemperatureReading(String deviceAddress, float temperature) {
simulateAttributeReport(deviceAddress, 0x0402, 0x0000, temperature);
}
public void simulateButtonPress(String deviceAddress) {
// Simulate button press (On/Off toggle)
simulateAttributeReport(deviceAddress, 0x0006, 0x0000, true);
}
}
public class SimulatedDevice extends ZigbeeDevice {
private final DeviceType deviceType;
private final Map<String, Object> attributes;
public SimulatedDevice(String ieeeAddress, String networkAddress, 
ZigbeeProfile profile, DeviceType deviceType) {
super(ieeeAddress, networkAddress);
this.deviceType = deviceType;
this.attributes = new ConcurrentHashMap<>();
initializeDefaultAttributes();
}
private void initializeDefaultAttributes() {
switch (deviceType) {
case DIMMABLE_LIGHT:
attributes.put("onOff", false);
attributes.put("brightness", 254);
attributes.put("colorTemperature", 370);
break;
case TEMPERATURE_SENSOR:
attributes.put("temperature", 21.5f);
attributes.put("battery", 95);
break;
}
}
public void setAttribute(String attribute, Object value) {
attributes.put(attribute, value);
}
public Object getAttribute(String attribute) {
return attributes.get(attribute);
}
public DeviceType getDeviceType() {
return deviceType;
}
}
public enum DeviceType {
DIMMABLE_LIGHT, COLOR_LIGHT, TEMPERATURE_SENSOR, 
DOOR_SENSOR, MOTION_SENSOR, SMART_PLUG
}

Spring Boot Integration

Spring Boot Configuration

@Configuration
@EnableConfigurationProperties(ZigbeeProperties.class)
public class ZigbeeAutoConfiguration {
@Bean
@ConditionalOnProperty(name = "zigbee.enabled", havingValue = "true")
public ZigbeeGatewayManager zigbeeGatewayManager(ZigbeeProperties properties) {
return new ZigbeeGatewayManager(
properties.getPort(), 
properties.getBaudRate()
);
}
@Bean
public ZigbeeDeviceService zigbeeDeviceService(ZigbeeGatewayManager gatewayManager) {
return new ZigbeeDeviceService(gatewayManager);
}
@Bean
public ZigbeeNetworkService zigbeeNetworkService(ZigbeeGatewayManager gatewayManager) {
return new ZigbeeNetworkService(gatewayManager);
}
}
@ConfigurationProperties(prefix = "zigbee")
@Data
public class ZigbeeProperties {
private boolean enabled = false;
private String port = "/dev/ttyUSB0";
private int baudRate = 115200;
private int networkChannel = 11;
private String networkKey;
private int permitJoinDuration = 60;
private int reconnectInterval = 30;
}
@RestController
@RequestMapping("/api/zigbee")
public class ZigbeeController {
private final ZigbeeDeviceService deviceService;
private final ZigbeeNetworkService networkService;
public ZigbeeController(ZigbeeDeviceService deviceService, 
ZigbeeNetworkService networkService) {
this.deviceService = deviceService;
this.networkService = networkService;
}
@PostMapping("/network/start")
public ResponseEntity<String> startNetwork() {
networkService.startNetwork();
return ResponseEntity.ok("Zigbee network started");
}
@PostMapping("/network/stop")
public ResponseEntity<String> stopNetwork() {
networkService.stopNetwork();
return ResponseEntity.ok("Zigbee network stopped");
}
@PostMapping("/network/permit-join")
public ResponseEntity<String> permitJoin(@RequestParam int duration) {
networkService.permitJoin(duration);
return ResponseEntity.ok("Join permitted for " + duration + " seconds");
}
@GetMapping("/devices")
public ResponseEntity<List<ZigbeeDevice>> getDevices() {
return ResponseEntity.ok(deviceService.getDiscoveredDevices());
}
@PostMapping("/devices/{deviceId}/on")
public ResponseEntity<String> turnOnDevice(@PathVariable String deviceId) {
deviceService.turnOnDevice(deviceId);
return ResponseEntity.ok("Device turned on");
}
@PostMapping("/devices/{deviceId}/off")
public ResponseEntity<String> turnOffDevice(@PathVariable String deviceId) {
deviceService.turnOffDevice(deviceId);
return ResponseEntity.ok("Device turned off");
}
@PostMapping("/devices/{deviceId}/brightness")
public ResponseEntity<String> setBrightness(@PathVariable String deviceId, 
@RequestParam int level) {
deviceService.setDeviceBrightness(deviceId, level);
return ResponseEntity.ok("Brightness set to " + level);
}
}
@Service
public class ZigbeeDeviceService {
private final ZigbeeGatewayManager gateway;
private final Map<String, SmartBulbController> bulbControllers;
private final Map<String, TemperatureSensorController> sensorControllers;
public ZigbeeDeviceService(ZigbeeGatewayManager gateway) {
this.gateway = gateway;
this.bulbControllers = new ConcurrentHashMap<>();
this.sensorControllers = new ConcurrentHashMap<>();
setupEventListeners();
}
private void setupEventListeners() {
gateway.addEventListener(new ZigbeeEventListener() {
@Override
public void onDeviceDiscovered(ZigbeeDevice device) {
// Auto-create appropriate controller based on device type
createDeviceController(device);
}
@Override
public void onAttributeUpdated(AttributeReport report) {
// Route attribute updates to appropriate controller
routeAttributeUpdate(report);
}
});
}
private void createDeviceController(ZigbeeDevice device) {
// Determine device type based on clusters and create appropriate controller
if (device.supportsCluster(ZigbeeCluster.ON_OFF) && 
device.supportsCluster(ZigbeeCluster.LEVEL_CONTROL)) {
SmartBulbController controller = new SmartBulbController(
gateway, device.getIEEEAddress(), 1
);
bulbControllers.put(device.getIEEEAddress(), controller);
} else if (device.supportsCluster(ZigbeeCluster.TEMPERATURE_MEASUREMENT)) {
TemperatureSensorController controller = new TemperatureSensorController(
gateway, device.getIEEEAddress(), 1
);
sensorControllers.put(device.getIEEEAddress(), controller);
controller.configureReporting();
}
}
public void turnOnDevice(String deviceId) {
SmartBulbController controller = bulbControllers.get(deviceId);
if (controller != null) {
controller.turnOn();
}
}
public void setDeviceBrightness(String deviceId, int level) {
SmartBulbController controller = bulbControllers.get(deviceId);
if (controller != null) {
controller.setBrightness(level);
}
}
public List<ZigbeeDevice> getDiscoveredDevices() {
return gateway.getDiscoveredDevices();
}
}

This comprehensive Zigbee device control implementation in Java provides:

  1. Gateway Management - Communication with Zigbee coordinators
  2. Device Discovery - Automatic detection and interviewing of devices
  3. Command Framework - Standardized commands for common device types
  4. Security - Encryption and message integrity
  5. Spring Boot Integration - REST API for device control
  6. Testing Support - Device simulation for development
  7. Apache Camel Integration - Enterprise integration patterns

The architecture supports various Zigbee device types including lights, sensors, and smart plugs, with extensibility for custom device types.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper