3 import java.io.IOException;
4 import java.net.DatagramPacket;
5 import java.net.DatagramSocket;
6 import java.net.InetAddress;
7 import java.net.SocketException;
8 import java.net.UnknownHostException;
9 import java.util.concurrent.atomic.AtomicBoolean;
10 import java.util.List;
11 import java.util.ArrayList;
13 import java.util.HashMap;
14 import java.util.concurrent.ConcurrentHashMap;
16 import java.util.HashSet;
17 import java.nio.charset.StandardCharsets;
18 import java.util.concurrent.Semaphore;
23 * @author Ali Younis <ayounis @ uci.edu>, Changwoo Lee <changwl2 @ uci.edu>
25 * @since 2016-04-12, 2016-10-28
27 public class IoTZigbee {
29 public final int SOCKET_SEND_BUFFER_SIZE = 1024;
30 public final int SOCKET_RECEIVE_BUFFER_SIZE = 1024;
31 public final int SHORT_ADDRESS_UPDATE_TIME_MSEC = 10000;
32 public final int SHORT_ADDRESS_UPDATE_TIME_FAST_MSEC = 500;
33 public final int RESEND_WAIT_TIME = 500;
36 * IoTZigbee class properties
39 // UDP connection stuff
40 private final String strHostAddress;
41 private final int iSrcPort;
42 private final int iDstPort;
43 private DatagramSocket socket; // the socket interface that we are guarding
44 private boolean didClose; // make sure that the clean up was done correctly
46 private final IoTZigbeeAddress zigbeeAddress;
48 // list that holds the callbacks
49 private List<IoTZigbeeCallback> callbackList = new ArrayList<IoTZigbeeCallback>();
52 * IoTZigbee class concurrency and concurrency control
54 private Thread receiveThread = null;
56 private AtomicBoolean endTask = new AtomicBoolean(false);
57 private AtomicBoolean didSuccesfullySendAddress = new AtomicBoolean(false);
62 public IoTZigbee(IoTDeviceAddress iotDevAdd, IoTZigbeeAddress zigAddress) throws SocketException, IOException, InterruptedException {
64 strHostAddress = iotDevAdd.getHostAddress();
65 iSrcPort = iotDevAdd.getSourcePortNumber();
66 iDstPort = iotDevAdd.getDestinationPortNumber();
68 zigbeeAddress = zigAddress;
70 socket = new DatagramSocket(iSrcPort);
71 socket.setSendBufferSize(SOCKET_SEND_BUFFER_SIZE);
72 socket.setReceiveBufferSize(SOCKET_RECEIVE_BUFFER_SIZE);
74 receiveThread = new Thread(new Runnable() {
79 receiveThread.start();
82 public void init() throws IOException {
83 while (!didSuccesfullySendAddress.get()) {
88 Thread.sleep(RESEND_WAIT_TIME);
89 } catch (Exception e) {
95 public void sendChangeSwtichRequest(int packetId, int clusterId, int profileId, int value, int deviceEndpoint) throws IOException {
96 String message = "type: zcl_change_switch_request\n";
97 message += "packet_id: " + String.format("%04x", packetId) + "\n";
98 message += "value: " + String.format("%01x", value) + "\n";
99 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
100 message += "profile_id: " + String.format("%04x", profileId) + "\n";
101 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
102 message += "device_endpoint: " + String.format("%02x", deviceEndpoint) + "\n";
103 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
104 socket.send(sendPacket);
108 public void sendLockOrUnlockDoorRequest(int packetId, int clusterId, int profileId, int deviceEndpoint, int value) throws IOException {
109 String message = "type: zcl_lock_or_unlock_door_request\n";
110 message += "packet_id: " + String.format("%04x", packetId) + "\n";
111 message += "value: " + String.format("%01x", value) + "\n";
112 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
113 message += "profile_id: " + String.format("%04x", profileId) + "\n";
114 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
115 message += "device_endpoint: " + String.format("%02x", deviceEndpoint) + "\n";
116 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
117 socket.send(sendPacket);
121 public void sendReadDoorStatusRequest(int packetId, int clusterId, int profileId, int deviceEndpoint, int framecontrol, int commandframe, int attribute_id) throws IOException {
122 String message = "type: zcl_read_door_status_request\n";
123 message += "packet_id: " + String.format("%04x", packetId) + "\n";
124 message += "framecontrol: " + String.format("%02x", framecontrol) + "\n";
125 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
126 message += "profile_id: " + String.format("%04x", profileId) + "\n";
127 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
128 message += "device_endpoint: " + String.format("%02x", deviceEndpoint) + "\n";
129 message += "commandframe: " + String.format("%02x", commandframe) + "\n";
130 message += "attribute_id: " + String.format("%04x", attribute_id) + "\n";
131 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
132 socket.send(sendPacket);
136 public void sendBroadcastingRouteRecordRequest(int packetId) throws IOException {
137 String message = "type: zdo_broadcast_route_record_request\n";
138 message += "packet_id: " + String.format("%04x", packetId) + "\n";
139 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
140 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
141 socket.send(sendPacket);
145 public void sendEnrollmentResponse(int packetId, int clusterId, int profileId, int deviceEndpoint) throws IOException {
146 String message = "type: zcl_enrollment_response\n";
147 message += "packet_id: " + String.format("%04x", packetId) + "\n";
148 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
149 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
150 message += "profile_id: " + String.format("%04x", profileId) + "\n";
151 message += "device_endpoint: " + String.format("%02x", deviceEndpoint) + "\n";
152 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
153 socket.send(sendPacket);
157 public void sendWriteAttributesCommand(int packetId, int clusterId, int profileId, int deviceEndpoint) throws IOException {
158 String message = "type: zcl_write_attributes\n";
159 message += "packet_id: " + String.format("%04x", packetId) + "\n";
160 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
161 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
162 message += "profile_id: " + String.format("%04x", profileId) + "\n";
163 message += "device_endpoint: " + String.format("%02x", deviceEndpoint) + "\n";
164 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
165 socket.send(sendPacket);
169 public void sendManagementPermitJoiningRequest(int packetId, int clusterId, int deviceEndpoint) throws IOException {
170 String message = "type: management_permit_joining_request\n";
171 message += "packet_id: " + String.format("%04x", packetId) + "\n";
172 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
173 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
174 message += "device_endpoint: " + String.format("%02x", deviceEndpoint) + "\n";
175 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
176 socket.send(sendPacket);
179 public void sendBindRequest(int packetId, int clusterId, int deviceEndpoint) throws IOException {
180 String message = "type: zdo_bind_request\n";
181 message += "packet_id: " + String.format("%04x", packetId) + "\n";
182 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
183 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
184 message += "device_endpoint: " + String.format("%02x", deviceEndpoint) + "\n";
185 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
186 socket.send(sendPacket);
189 public void sendUnBindRequest(int packetId, int clusterId, int deviceEndpoint) throws IOException {
190 String message = "type: zdo_unbind_request\n";
191 message += "packet_id: " + String.format("%04x", packetId) + "\n";
192 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
193 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
194 message += "device_endpoint: " + String.format("%02x", deviceEndpoint) + "\n";
195 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
196 socket.send(sendPacket);
199 public void sendReadAttributesCommand(int packetId, int clusterId, int profileId, int deviceEndpoint, List<Integer> attributeIds) throws IOException {
200 String message = "type: zcl_read_attributes\n";
201 message += "packet_id: " + String.format("%04x", packetId) + "\n";
202 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
203 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
204 message += "profile_id: " + String.format("%04x", profileId) + "\n";
205 message += "device_endpoint: " + String.format("%02x", deviceEndpoint) + "\n";
207 message += "attribute_ids: ";
209 for (Integer i : attributeIds) {
210 message += String.format("%04x", i) + ",";
213 message = message.substring(0, message.length() - 1);
216 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
217 socket.send(sendPacket);
220 public void sendConfigureReportingCommand(int packetId, int clusterId, int profileId, int src_endpoint, int dest_endpoint, int attributeId, int dataType, int minReportingInterval, int maxReportingInterval, byte[] reportableChange) throws IOException {
221 String message = "type: zcl_configure_reporting\n";
222 message += "packet_id: " + String.format("%04x", packetId) + "\n";
223 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
224 message += "cluster_id: " + String.format("%04x", clusterId) + "\n";
225 message += "profile_id: " + String.format("%04x", profileId) + "\n";
226 message += "src_endpoint: " + String.format("%02x", src_endpoint) + "\n";
227 message += "device_endpoint: " + String.format("%02x", dest_endpoint) + "\n";
228 message += "attribute_id: " + String.format("%04x", attributeId) + "\n";
229 message += "data_type: " + String.format("%02x", dataType) + "\n";
230 message += "min_reporting_interval: " + String.format("%04x", minReportingInterval) + "\n";
231 message += "max_reporting_interval: " + String.format("%04x", maxReportingInterval) + "\n";
233 if (reportableChange != null) {
234 message += "reportable_change: ";
235 for (Byte b : reportableChange) {
236 message += String.format("%02x", (int)b);
241 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
242 socket.send(sendPacket);
245 public void sendConfigureReportingCommand(int packetId, int clusterId, int profileId, int dest_endpoint, int attributeId, int dataType, int minReportingInterval, int maxReportingInterval, byte[] reportableChange) throws IOException {
246 sendConfigureReportingCommand(packetId, clusterId, profileId, 0x00, dest_endpoint, attributeId, dataType, minReportingInterval, maxReportingInterval, reportableChange);
249 public void registerCallback(IoTZigbeeCallback callbackTo) {
251 callbackList.add(callbackTo);
255 public void close() throws InterruptedException {
258 // wait for the threads to end
259 receiveThread.join();
266 * close() called by the garbage collector right before trashing object
268 public void Finalize() throws SocketException, InterruptedException {
272 throw new SocketException("Socket not closed before object destruction, must call close method.");
276 private void sendDeviceAddress() throws IOException {
277 String message = "type: send_address\n";
278 message += "packet_id: 00\n";
279 message += "device_address_long: " + zigbeeAddress.getAddress() + "\n";
280 System.out.println(message);
281 DatagramPacket sendPacket = new DatagramPacket(message.getBytes(), message.getBytes().length, InetAddress.getByName(strHostAddress), iDstPort);
282 socket.send(sendPacket);
285 private void receieveWorker() {
286 while (!(endTask.get())) {
288 byte[] recBuffer = new byte[SOCKET_RECEIVE_BUFFER_SIZE];
290 DatagramPacket recPacket = new DatagramPacket(recBuffer, recBuffer.length);
291 socket.receive(recPacket);
293 // Convert the UDP data into a string format
294 String dataString = new String(recPacket.getData());
296 // split the data by line so we can start procesisng
297 String[] lines = dataString.split("\n");
299 Map<String, String> packetData = new HashMap<String, String>();
300 for (String line : lines) {
303 String trimmedLine = line.trim();
304 // make sure this is a valid data line and not just blank
305 if (trimmedLine.length() == 0) {
309 // Split the data into parts
310 String[] parts = trimmedLine.split(":");
311 parts[0] = parts[0].trim();
312 parts[1] = parts[1].trim();
313 packetData.put(parts[0], parts[1]);
316 if (packetData.get("type").equals("send_address_response")) {
317 didSuccesfullySendAddress.set(true);
320 IoTZigbeeMessage callbackMessage = null;
324 if (packetData.get("type").equals("zcl_match_descriptor_response")) {
325 System.out.println("zcl_match_descriptor_response message arrives");
327 int packetId = Integer.parseInt(packetData.get("packet_id"), 16);
328 int clusterId = Integer.parseInt(packetData.get("cluster_id"), 16);
329 int profileId = Integer.parseInt(packetData.get("profile_id"), 16);
330 boolean successOrFail = false;
331 if(packetData.get("attributes").equals("success")) successOrFail=true;
333 callbackMessage = new IoTZigbeeMessageMatchDescriptorResponse(packetId, clusterId, profileId, successOrFail);
339 if (packetData.get("type").equals("zcl_zone_status_change_notification")){
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 int status = Integer.parseInt(packetData.get("status"), 10);
344 boolean successOrFail = false;
345 if(packetData.get("attributes").equals("success")) successOrFail=true;
346 callbackMessage = new IoTZigbeeMessageZclZoneStatusChangeNotification(packetId, clusterId, profileId, status, successOrFail);
349 }else if (packetData.get("type").equals("zcl_change_switch_response")){
350 int packetId = Integer.parseInt(packetData.get("packet_id"), 16);
351 int clusterId = Integer.parseInt(packetData.get("cluster_id"), 16);
352 int profileId = Integer.parseInt(packetData.get("profile_id"), 16);
353 int status = Integer.parseInt(packetData.get("status"), 10);
354 boolean successOrFail = false;
355 if(packetData.get("attributes").equals("success")) successOrFail=true;
356 callbackMessage = new IoTZigbeeMessageZclChangeSwitchResponse(packetId, clusterId, profileId, status, successOrFail);
359 } else if (packetData.get("type").equals("zcl_write_attributes_response")) {
361 int packetId = Integer.parseInt(packetData.get("packet_id"), 16);
362 int clusterId = Integer.parseInt(packetData.get("cluster_id"), 16);
363 int profileId = Integer.parseInt(packetData.get("profile_id"), 16);
364 boolean successOrFail = false;
365 if(packetData.get("attributes").equals("success")) successOrFail=true;
367 callbackMessage = new IoTZigbeeMessageZclWriteAttributesResponse(packetId, clusterId, profileId, successOrFail);
369 } else if (packetData.get("type").equals("zcl_read_attributes_response")) {
370 int packetId = Integer.parseInt(packetData.get("packet_id"), 16);
371 int clusterId = Integer.parseInt(packetData.get("cluster_id"), 16);
372 int profileId = Integer.parseInt(packetData.get("profile_id"), 16);
374 List<IoTZigbeeMessageZclReadAttributesResponse.Attribute> attrList = new ArrayList<IoTZigbeeMessageZclReadAttributesResponse.Attribute>();
376 String[] attributes = packetData.get("attributes").split(";");
377 for (String attr : attributes) {
379 String[] parts = attr.split(",");
381 if (parts.length == 2) {
382 parts[0] = parts[0].trim();
383 parts[1] = parts[1].trim();
385 IoTZigbeeMessageZclReadAttributesResponse.Attribute at = new IoTZigbeeMessageZclReadAttributesResponse.Attribute(Integer.parseInt(parts[0], 16), 0, false, null);
388 parts[0] = parts[0].trim();
389 parts[1] = parts[1].trim();
390 parts[2] = parts[2].trim();
391 parts[3] = parts[3].trim();
392 IoTZigbeeMessageZclReadAttributesResponse.Attribute at = new IoTZigbeeMessageZclReadAttributesResponse.Attribute(Integer.parseInt(parts[0], 16), Integer.parseInt(parts[1], 16), true, hexStringToByteArray(parts[3]));
397 callbackMessage = new IoTZigbeeMessageZclReadAttributesResponse(packetId, clusterId, profileId, attrList);
399 } else if (packetData.get("type").equals("zcl_configure_reporting_response")) {
400 int packetId = Integer.parseInt(packetData.get("packet_id"), 16);
401 int clusterId = Integer.parseInt(packetData.get("cluster_id"), 16);
402 int profileId = Integer.parseInt(packetData.get("profile_id"), 16);
404 if (packetData.get("attributes").equals("all_success")) {
405 callbackMessage = new IoTZigbeeMessageZclConfigureReportingResponse(packetId, clusterId, profileId, true, null);
407 List<IoTZigbeeMessageZclConfigureReportingResponse.Attribute> attrList = new ArrayList<IoTZigbeeMessageZclConfigureReportingResponse.Attribute>();
409 String[] attributes = packetData.get("attributes").split(";");
410 for (String attr : attributes) {
412 String[] parts = attr.split(",");
413 parts[0] = parts[0].trim();
414 parts[1] = parts[1].trim();
415 parts[2] = parts[2].trim();
416 IoTZigbeeMessageZclConfigureReportingResponse.Attribute at = new IoTZigbeeMessageZclConfigureReportingResponse.Attribute(Integer.parseInt(parts[0], 16), parts[1].equals("success"), parts[2].equals("reported"));
419 callbackMessage = new IoTZigbeeMessageZclConfigureReportingResponse(packetId, clusterId, profileId, false, attrList);
422 } else if (packetData.get("type").equals("zcl_report_attributes")) {
423 int packetId = Integer.parseInt(packetData.get("packet_id"), 16);
424 int clusterId = Integer.parseInt(packetData.get("cluster_id"), 16);
425 int profileId = Integer.parseInt(packetData.get("profile_id"), 16);
427 List<IoTZigbeeMessageZclReportAttributes.Attribute> attrList = new ArrayList<IoTZigbeeMessageZclReportAttributes.Attribute>();
429 String[] attributes = packetData.get("attributes").split(";");
430 for (String attr : attributes) {
432 String[] parts = attr.split(",");
434 parts[0] = parts[0].trim();
435 parts[1] = parts[1].trim();
436 parts[2] = parts[2].trim();
437 IoTZigbeeMessageZclReportAttributes.Attribute at = new IoTZigbeeMessageZclReportAttributes.Attribute(Integer.parseInt(parts[0], 16), Integer.parseInt(parts[1], 16), hexStringToByteArray(parts[2]));
441 callbackMessage = new IoTZigbeeMessageZclReportAttributes(packetId, clusterId, profileId, attrList);
443 } else if (packetData.get("type").equals("zcl_read_attributes")) {
444 int packetId = Integer.parseInt(packetData.get("packet_id"), 16);
445 boolean success = packetData.get("response").equals("success");
448 callbackMessage = new IoTZigbeeMessageZclReadAttributes(packetId, success, "");
450 callbackMessage = new IoTZigbeeMessageZclReadAttributes(packetId, success, packetData.get("reason"));
453 } else if (packetData.get("type").equals("zcl_configure_reporting")) {
454 int packetId = Integer.parseInt(packetData.get("packet_id"), 16);
455 boolean success = packetData.get("response").equals("success");
458 callbackMessage = new IoTZigbeeMessageZclConfigureReporting(packetId, success, "");
460 callbackMessage = new IoTZigbeeMessageZclConfigureReporting(packetId, success, packetData.get("reason"));
463 } else if (packetData.get("type").equals("zdo_bind_request")) {
464 int packetId = Integer.parseInt(packetData.get("packet_id"), 16);
465 boolean success = packetData.get("response").equals("success");
468 callbackMessage = new IoTZigbeeMessageZdoBindResponse(packetId, success, "");
470 callbackMessage = new IoTZigbeeMessageZdoBindResponse(packetId, success, packetData.get("reason"));
474 else if (packetData.get("type").equals("zdo_unbind_request")) {
475 int packetId = Integer.parseInt(packetData.get("packet_id"), 16);
476 boolean success = packetData.get("response").equals("success");
479 callbackMessage = new IoTZigbeeMessageZdoUnBindResponse(packetId, success, "");
481 callbackMessage = new IoTZigbeeMessageZdoUnBindResponse(packetId, success, packetData.get("reason"));
485 if (callbackMessage != null) {
486 for (IoTZigbeeCallback c : callbackList) {
487 c.newMessageAvailable(callbackMessage);
494 } catch (Exception e) {
500 public static String changeHexEndianness(String hexData) {
502 List<String> pairedValues = new ArrayList<String>();
503 for (int i = 0; i < hexData.length(); i += 2) {
504 String part = hexData.substring(i, Math.min(i + 2, hexData.length()));
505 pairedValues.add(part);
508 String retString = "";
509 for (int i = (pairedValues.size() - 1); i >= 0; i--) {
510 retString += pairedValues.get(i);
515 // taken from: http://stackoverflow.com/questions/140131/convert-a-string-representation-of-a-hex-dump-to-a-byte-array-using-java
516 public static byte[] hexStringToByteArray(String s) {
517 int len = s.length();
518 byte[] data = new byte[len / 2];
519 for (int i = 0; i < len; i += 2) {
520 data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
521 + Character.digit(s.charAt(i + 1), 16));