1 package iotruntime.zigbee;
4 import java.io.IOException;
5 import java.net.DatagramPacket;
6 import java.net.DatagramSocket;
7 import java.net.InetAddress;
8 import java.net.SocketException;
9 import java.net.UnknownHostException;
10 import java.util.concurrent.atomic.AtomicBoolean;
11 import java.util.List;
12 import java.util.ArrayList;
14 import java.util.HashMap;
15 import java.util.concurrent.ConcurrentHashMap;
17 import java.util.HashSet;
18 import java.nio.charset.StandardCharsets;
19 import java.util.concurrent.Semaphore;
21 import iotruntime.slave.IoTZigbeeAddress;
22 import iotruntime.slave.IoTDeviceAddress;
26 * @author Ali Younis <ayounis @ uci.edu>, Changwoo Lee, Jiawei Gu
30 public final class IoTZigbee {
32 public final int SOCKET_SEND_BUFFER_SIZE = 1024;
33 public final int SOCKET_RECEIVE_BUFFER_SIZE = 1024;
34 public final int SHORT_ADDRESS_UPDATE_TIME_MSEC = 10000;
35 public final int SHORT_ADDRESS_UPDATE_TIME_FAST_MSEC = 500;
36 public final int RESEND_WAIT_TIME = 500;
39 * IoTZigbee class properties
42 // UDP connection stuff
43 private final String strHostAddress;
44 private final int iSrcPort;
45 private final int iDstPort;
46 private DatagramSocket socket; // the socket interface that we are guarding
47 private boolean didClose; // make sure that the clean up was done correctly
49 private final IoTZigbeeAddress zigbeeAddress;
51 // list that holds the callbacks
52 private List<IoTZigbeeCallback> callbackList = new ArrayList<IoTZigbeeCallback>();
55 * IoTZigbee class concurrency and concurrency control
57 private Thread receiveThread = null;
59 private AtomicBoolean endTask = new AtomicBoolean(false);
60 private AtomicBoolean didSuccesfullySendAddress = new AtomicBoolean(false);
65 public IoTZigbee(IoTDeviceAddress iotDevAdd, IoTZigbeeAddress zigAddress) throws SocketException, IOException, InterruptedException {
67 strHostAddress = iotDevAdd.getHostAddress();
68 iSrcPort = iotDevAdd.getSourcePortNumber();
69 iDstPort = iotDevAdd.getDestinationPortNumber();
71 zigbeeAddress = zigAddress;
73 socket = new DatagramSocket(iSrcPort);
74 socket.setSendBufferSize(SOCKET_SEND_BUFFER_SIZE);
75 socket.setReceiveBufferSize(SOCKET_RECEIVE_BUFFER_SIZE);
77 receiveThread = new Thread(new Runnable() {
82 receiveThread.start();
85 public void init() throws IOException {
86 while (!didSuccesfullySendAddress.get()) {
91 Thread.sleep(RESEND_WAIT_TIME);
92 } catch (Exception e) {
98 // Created by Changwoo
99 public void sendChangeSwtichRequest(int packetId, int clusterId, int profileId, int value, int deviceEndpoint) throws IOException {
100 String message = "type: zcl_change_switch_request\n";
101 message += "packet_id: " + String.format("%04x", packetId) + "\n";
102 message += "value: " + String.format("%01x", value) + "\n";
103 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
104 message += "profile_id: " + String.format("%04x", profileId) + "\n";
105 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
106 message += "device_endpoint: " + String.format("%02x", deviceEndpoint) + "\n";
107 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
108 socket.send(sendPacket);
112 public void sendLockOrUnlockDoorRequest(int packetId, int clusterId, int profileId, int deviceEndpoint, int value) throws IOException {
113 String message = "type: zcl_lock_or_unlock_door_request\n";
114 message += "packet_id: " + String.format("%04x", packetId) + "\n";
115 message += "value: " + String.format("%01x", value) + "\n";
116 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
117 message += "profile_id: " + String.format("%04x", profileId) + "\n";
118 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
119 message += "device_endpoint: " + String.format("%02x", deviceEndpoint) + "\n";
120 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
121 socket.send(sendPacket);
125 public void sendReadDoorStatusRequest(int packetId, int clusterId, int profileId, int deviceEndpoint, int framecontrol,
126 int commandframe, int attribute_id) throws IOException {
127 String message = "type: zcl_read_door_status_request\n";
128 message += "packet_id: " + String.format("%04x", packetId) + "\n";
129 message += "framecontrol: " + String.format("%02x", framecontrol) + "\n";
130 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
131 message += "profile_id: " + String.format("%04x", profileId) + "\n";
132 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
133 message += "device_endpoint: " + String.format("%02x", deviceEndpoint) + "\n";
134 message += "commandframe: " + String.format("%02x", commandframe) + "\n";
135 message += "attribute_id: " + String.format("%04x", attribute_id) + "\n";
136 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
137 socket.send(sendPacket);
140 // Created by Changwoo
141 public void sendBroadcastingRouteRecordRequest(int packetId) throws IOException {
142 String message = "type: zdo_broadcast_route_record_request\n";
143 message += "packet_id: " + String.format("%04x", packetId) + "\n";
144 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
145 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
146 socket.send(sendPacket);
149 // Created by Changwoo
150 public void sendEnrollmentResponse(int packetId, int clusterId, int profileId, int deviceEndpoint) throws IOException {
151 String message = "type: zcl_enrollment_response\n";
152 message += "packet_id: " + String.format("%04x", packetId) + "\n";
153 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
154 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
155 message += "profile_id: " + String.format("%04x", profileId) + "\n";
156 message += "device_endpoint: " + String.format("%02x", deviceEndpoint) + "\n";
157 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
158 socket.send(sendPacket);
161 // Created by Changwoo
162 public void sendWriteAttributesCommand(int packetId, int clusterId, int profileId, int deviceEndpoint) throws IOException {
163 String message = "type: zcl_write_attributes\n";
164 message += "packet_id: " + String.format("%04x", packetId) + "\n";
165 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
166 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
167 message += "profile_id: " + String.format("%04x", profileId) + "\n";
168 message += "device_endpoint: " + String.format("%02x", deviceEndpoint) + "\n";
169 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
170 socket.send(sendPacket);
173 // Created by Changwoo
174 public void sendManagementPermitJoiningRequest(int packetId, int clusterId, int deviceEndpoint) throws IOException {
175 String message = "type: management_permit_joining_request\n";
176 message += "packet_id: " + String.format("%04x", packetId) + "\n";
177 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
178 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
179 message += "device_endpoint: " + String.format("%02x", deviceEndpoint) + "\n";
180 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
181 socket.send(sendPacket);
184 public void sendBindRequest(int packetId, int clusterId, int deviceEndpoint) throws IOException {
185 String message = "type: zdo_bind_request\n";
186 message += "packet_id: " + String.format("%04x", packetId) + "\n";
187 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
188 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
189 message += "device_endpoint: " + String.format("%02x", deviceEndpoint) + "\n";
190 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
191 socket.send(sendPacket);
194 public void sendUnBindRequest(int packetId, int clusterId, int deviceEndpoint) throws IOException {
195 String message = "type: zdo_unbind_request\n";
196 message += "packet_id: " + String.format("%04x", packetId) + "\n";
197 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
198 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
199 message += "device_endpoint: " + String.format("%02x", deviceEndpoint) + "\n";
200 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
201 socket.send(sendPacket);
204 public void sendReadAttributesCommand(int packetId, int clusterId, int profileId, int deviceEndpoint, List<Integer> attributeIds) throws IOException {
205 String message = "type: zcl_read_attributes\n";
206 message += "packet_id: " + String.format("%04x", packetId) + "\n";
207 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
208 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
209 message += "profile_id: " + String.format("%04x", profileId) + "\n";
210 message += "device_endpoint: " + String.format("%02x", deviceEndpoint) + "\n";
212 message += "attribute_ids: ";
214 for (Integer i : attributeIds) {
215 message += String.format("%04x", i) + ",";
218 message = message.substring(0, message.length() - 1);
221 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
222 socket.send(sendPacket);
225 public void sendConfigureReportingCommand(int packetId, int clusterId, int profileId, int src_endpoint, int dest_endpoint,
226 int attributeId, int dataType, int minReportingInterval, int maxReportingInterval, byte[] reportableChange) throws IOException {
227 String message = "type: zcl_configure_reporting\n";
228 message += "packet_id: " + String.format("%04x", packetId) + "\n";
229 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
230 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
231 message += "profile_id: " + String.format("%04x", profileId) + "\n";
232 message += "src_endpoint: " + String.format("%02x", src_endpoint) + "\n";
233 message += "device_endpoint: " + String.format("%02x", dest_endpoint) + "\n";
234 message += "attribute_id: " + String.format("%04x", attributeId) + "\n";
235 message += "data_type: " + String.format("%02x", dataType) + "\n";
236 message += "min_reporting_interval: " + String.format("%04x", minReportingInterval) + "\n";
237 message += "max_reporting_interval: " + String.format("%04x", maxReportingInterval) + "\n";
239 if (reportableChange != null) {
240 message += "reportable_change: ";
241 for (Byte b : reportableChange) {
242 message += String.format("%02x", (int)b);
247 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
248 socket.send(sendPacket);
251 public void sendConfigureReportingCommand(int packetId, int clusterId, int profileId, int dest_endpoint, int attributeId, int dataType,
252 int minReportingInterval, int maxReportingInterval, byte[] reportableChange) throws IOException {
253 sendConfigureReportingCommand(packetId, clusterId, profileId, 0x00, dest_endpoint, attributeId, dataType, minReportingInterval,
254 maxReportingInterval, reportableChange);
257 public void registerCallback(IoTZigbeeCallback callbackTo) {
258 callbackList.add(callbackTo);
261 public void close() throws InterruptedException {
264 // wait for the threads to end
265 receiveThread.join();
272 * close() called by the garbage collector right before trashing object
274 public void Finalize() throws SocketException, InterruptedException {
278 throw new SocketException("Socket not closed before object destruction, must call close method.");
282 private void sendDeviceAddress() throws IOException {
283 String message = "type: send_address\n";
284 message += "packet_id: 00\n";
285 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
286 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
287 socket.send(sendPacket);
290 private void receieveWorker() {
291 while (!(endTask.get())) {
293 byte[] recBuffer = new byte[SOCKET_RECEIVE_BUFFER_SIZE];
295 DatagramPacket recPacket = new DatagramPacket(recBuffer, recBuffer.length);
296 socket.receive(recPacket);
298 // Convert the UDP data into a string format
299 String dataString = new String(recPacket.getData());
301 // split the data by line so we can start procesisng
302 String[] lines = dataString.split("\n");
304 Map<String, String> packetData = new HashMap<String, String>();
305 for (String line : lines) {
308 String trimmedLine = line.trim();
309 // make sure this is a valid data line and not just blank
310 if (trimmedLine.length() == 0) {
314 // Split the data into parts
315 String[] parts = trimmedLine.split(":");
316 parts[0] = parts[0].trim();
317 parts[1] = parts[1].trim();
318 packetData.put(parts[0], parts[1]);
321 if (packetData.get("type").equals("send_address_response")) {
322 didSuccesfullySendAddress.set(true);
325 IoTZigbeeMessage callbackMessage = null;
327 // Created by Changwoo
328 if (packetData.get("type").equals("zcl_zone_status_change_notification")){
329 int packetId = Integer.parseInt(packetData.get("packet_id"), 16);
330 int clusterId = Integer.parseInt(packetData.get("cluster_id"), 16);
331 int profileId = Integer.parseInt(packetData.get("profile_id"), 16);
332 int status = Integer.parseInt(packetData.get("status"), 10);
333 boolean successOrFail = false;
334 if(packetData.get("attributes").equals("success")) successOrFail=true;
335 callbackMessage = new IoTZigbeeMessageZclZoneStatusChangeNotification(packetId, clusterId, profileId, status, successOrFail);
337 // Created by Changwoo
338 } else if (packetData.get("type").equals("zcl_write_attributes_response")) {
340 int packetId = Integer.parseInt(packetData.get("packet_id"), 16);
341 int clusterId = Integer.parseInt(packetData.get("cluster_id"), 16);
342 int profileId = Integer.parseInt(packetData.get("profile_id"), 16);
343 boolean successOrFail = false;
344 if(packetData.get("attributes").equals("success")) successOrFail=true;
346 callbackMessage = new IoTZigbeeMessageZclWriteAttributesResponse(packetId, clusterId, profileId, successOrFail);
348 } else if (packetData.get("type").equals("zcl_read_attributes_response")) {
349 int packetId = Integer.parseInt(packetData.get("packet_id"), 16);
350 int clusterId = Integer.parseInt(packetData.get("cluster_id"), 16);
351 int profileId = Integer.parseInt(packetData.get("profile_id"), 16);
353 List<IoTZigbeeMessageZclReadAttributesResponse.Attribute> attrList = new
354 ArrayList<IoTZigbeeMessageZclReadAttributesResponse.Attribute>();
356 String[] attributes = packetData.get("attributes").split(";");
357 for (String attr : attributes) {
359 String[] parts = attr.split(",");
361 if (parts.length == 2) {
362 parts[0] = parts[0].trim();
363 parts[1] = parts[1].trim();
365 IoTZigbeeMessageZclReadAttributesResponse.Attribute at = new
366 IoTZigbeeMessageZclReadAttributesResponse.Attribute(Integer.parseInt(parts[0], 16), 0, false, null);
369 parts[0] = parts[0].trim();
370 parts[1] = parts[1].trim();
371 parts[2] = parts[2].trim();
372 parts[3] = parts[3].trim();
373 IoTZigbeeMessageZclReadAttributesResponse.Attribute at = new
374 IoTZigbeeMessageZclReadAttributesResponse.Attribute(Integer.parseInt(parts[0], 16),
375 Integer.parseInt(parts[1], 16), true, hexStringToByteArray(parts[3]));
380 callbackMessage = new IoTZigbeeMessageZclReadAttributesResponse(packetId, clusterId, profileId, attrList);
382 } else if (packetData.get("type").equals("zcl_configure_reporting_response")) {
383 int packetId = Integer.parseInt(packetData.get("packet_id"), 16);
384 int clusterId = Integer.parseInt(packetData.get("cluster_id"), 16);
385 int profileId = Integer.parseInt(packetData.get("profile_id"), 16);
387 if (packetData.get("attributes").equals("all_success")) {
388 callbackMessage = new IoTZigbeeMessageZclConfigureReportingResponse(packetId, clusterId, profileId, true, null);
390 List<IoTZigbeeMessageZclConfigureReportingResponse.Attribute> attrList = new
391 ArrayList<IoTZigbeeMessageZclConfigureReportingResponse.Attribute>();
393 String[] attributes = packetData.get("attributes").split(";");
394 for (String attr : attributes) {
396 String[] parts = attr.split(",");
397 parts[0] = parts[0].trim();
398 parts[1] = parts[1].trim();
399 parts[2] = parts[2].trim();
400 IoTZigbeeMessageZclConfigureReportingResponse.Attribute at = new
401 IoTZigbeeMessageZclConfigureReportingResponse.Attribute(Integer.parseInt(parts[0], 16),
402 parts[1].equals("success"), parts[2].equals("reported"));
405 callbackMessage = new IoTZigbeeMessageZclConfigureReportingResponse(packetId, clusterId, profileId, false, attrList);
408 } else if (packetData.get("type").equals("zcl_report_attributes")) {
409 int packetId = Integer.parseInt(packetData.get("packet_id"), 16);
410 int clusterId = Integer.parseInt(packetData.get("cluster_id"), 16);
411 int profileId = Integer.parseInt(packetData.get("profile_id"), 16);
413 List<IoTZigbeeMessageZclReportAttributes.Attribute> attrList = new ArrayList<IoTZigbeeMessageZclReportAttributes.Attribute>();
415 String[] attributes = packetData.get("attributes").split(";");
416 for (String attr : attributes) {
418 String[] parts = attr.split(",");
420 parts[0] = parts[0].trim();
421 parts[1] = parts[1].trim();
422 parts[2] = parts[2].trim();
423 IoTZigbeeMessageZclReportAttributes.Attribute at = new
424 IoTZigbeeMessageZclReportAttributes.Attribute(Integer.parseInt(parts[0], 16),
425 Integer.parseInt(parts[1], 16), hexStringToByteArray(parts[2]));
429 callbackMessage = new IoTZigbeeMessageZclReportAttributes(packetId, clusterId, profileId, attrList);
431 } else if (packetData.get("type").equals("zcl_read_attributes")) {
432 int packetId = Integer.parseInt(packetData.get("packet_id"), 16);
433 boolean success = packetData.get("response").equals("success");
436 callbackMessage = new IoTZigbeeMessageZclReadAttributes(packetId, success, "");
438 callbackMessage = new IoTZigbeeMessageZclReadAttributes(packetId, success, packetData.get("reason"));
441 } else if (packetData.get("type").equals("zcl_configure_reporting")) {
442 int packetId = Integer.parseInt(packetData.get("packet_id"), 16);
443 boolean success = packetData.get("response").equals("success");
446 callbackMessage = new IoTZigbeeMessageZclConfigureReporting(packetId, success, "");
448 callbackMessage = new IoTZigbeeMessageZclConfigureReporting(packetId, success, packetData.get("reason"));
451 } else if (packetData.get("type").equals("zdo_bind_request")) {
452 int packetId = Integer.parseInt(packetData.get("packet_id"), 16);
453 boolean success = packetData.get("response").equals("success");
456 callbackMessage = new IoTZigbeeMessageZdoBindResponse(packetId, success, "");
458 callbackMessage = new IoTZigbeeMessageZdoBindResponse(packetId, success, packetData.get("reason"));
462 else if (packetData.get("type").equals("zdo_unbind_request")) {
463 int packetId = Integer.parseInt(packetData.get("packet_id"), 16);
464 boolean success = packetData.get("response").equals("success");
467 callbackMessage = new IoTZigbeeMessageZdoUnBindResponse(packetId, success, "");
469 callbackMessage = new IoTZigbeeMessageZdoUnBindResponse(packetId, success, packetData.get("reason"));
473 if (callbackMessage != null) {
474 for (IoTZigbeeCallback c : callbackList) {
475 c.newMessageAvailable(callbackMessage);
480 } catch (Exception e) {
486 public static String changeHexEndianness(String hexData) {
488 List<String> pairedValues = new ArrayList<String>();
489 for (int i = 0; i < hexData.length(); i += 2) {
490 String part = hexData.substring(i, Math.min(i + 2, hexData.length()));
491 pairedValues.add(part);
494 String retString = "";
495 for (int i = (pairedValues.size() - 1); i >= 0; i--) {
496 retString += pairedValues.get(i);
501 // taken from: http://stackoverflow.com/questions/140131/convert-a-string-representation-of-a-hex-dump-to-a-byte-array-using-java
502 public static byte[] hexStringToByteArray(String s) {
503 int len = s.length();
504 byte[] data = new byte[len / 2];
505 for (int i = 0; i < len; i += 2) {
506 data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
507 + Character.digit(s.charAt(i + 1), 16));