1 package iotcode.DlinkAlarm;
3 // Standard Java Packages
6 import java.util.concurrent.Semaphore;
7 import java.util.concurrent.atomic.AtomicBoolean;
8 import java.security.InvalidParameterException;
10 import java.util.Iterator;
11 import java.nio.charset.StandardCharsets;
13 import java.util.HashSet;
14 import java.util.List;
15 import java.util.ArrayList;
17 // HMAC digest packages
18 import javax.crypto.Mac;
19 import javax.crypto.spec.SecretKeySpec;
20 import java.io.UnsupportedEncodingException;
21 import java.security.InvalidKeyException;
22 import java.security.NoSuchAlgorithmException;
25 import iotruntime.IoTTCP;
26 import iotruntime.slave.IoTDeviceAddress;
27 import iotruntime.slave.IoTSet;
28 import iotcode.interfaces.ZoneState;
29 import iotcode.interfaces.Alarm;
31 import iotcode.annotation.*;
33 /** Class DlinkAlarm for the D-Link Alarm.
35 * @author Rahmadi Trimananda <rtrimana @ uci.edu>
40 public class DlinkAlarm implements Alarm {
42 /*******************************************************************************************************************************************
46 *******************************************************************************************************************************************/
47 private static final String STR_HMAC_ALGO = "HmacMD5";
48 private static final int INT_ALARM_SOUND_TYPE = 6;
49 private static final int INT_ALARM_VOLUME = 0;
50 private static final int INT_ALARM_MAX_DUR = 86400;
52 private String host = null;
53 private String head = null;
54 private String end = null;
55 private IoTDeviceAddress deviceAddress = null;
56 private String devicePin = null;
57 private String response = null;
58 // The following 3 are needed to send commands to the alarm/siren
59 private String cookie = null;
60 private String timeStamp = null;
61 private String privateKey = null;
64 /*******************************************************************************************************************************************
66 ** IoT Sets and Relations
68 *******************************************************************************************************************************************/
70 // IoTSet of Device Addresses.
71 // Will be filled with only 1 address.
72 @config private IoTSet<IoTDeviceAddress> alm_Addresses;
74 public DlinkAlarm(IoTSet<IoTDeviceAddress> _alm_addr, String _devicePin) {
75 alm_Addresses = _alm_addr;
76 devicePin = _devicePin;
79 public DlinkAlarm(String _devicePin) {
80 devicePin = _devicePin;
84 /*******************************************************************************************************************************************
88 *******************************************************************************************************************************************/
90 /** Method to set the state of a specified zone. Interface implementation.
92 * @param _zone [int] : zone number to set.
93 * @param _onOff [boolean] : the state to set the zone to, on or off.
94 * @param _onDurationSeconds [int]: the duration to set the state on to, if -1 then infinite.
96 * @return [void] None.
98 public void setZone(int _zone, boolean _onOff, int _onDurationSeconds) {
100 // We don't use zone at this point (for this alarm there is only 1 zone and 1 alarm)
102 // Send login request first
104 // Send login info (challenge and HMAC encrypted message)
108 if ((_onDurationSeconds == -1) || (_onDurationSeconds > 86400)) {
109 // Set alarm on to max duration
110 setSoundPlay(INT_ALARM_SOUND_TYPE, INT_ALARM_VOLUME, INT_ALARM_MAX_DUR);
112 setSoundPlay(INT_ALARM_SOUND_TYPE, INT_ALARM_VOLUME, _onDurationSeconds);
114 } else { // Else turn off
120 /** Method to get the current state of all the zones. Interface implementation.
124 * @return [List<ZoneState>] list of the states for the zones.
126 public List<ZoneState> getZoneStates() {
132 /** Method to get the number of zones this alarm can control. Interface implementation.
136 * @return [int] number of zones that can be controlled.
138 public int getNumberOfZones() {
143 /** Method to get whether or not this alarm can control durations. Interface implementation.
147 * @return [boolean] boolean if this sprinkler can do durations.
149 public boolean doesHaveZoneTimers() {
154 /** Method to initialize the alarm. Interface implementation.
158 * @return [void] None.
163 Iterator itr = alm_Addresses.iterator();
164 deviceAddress = (IoTDeviceAddress)itr.next();
165 host = deviceAddress.getHostAddress();
166 System.out.println("Address: " + host);
169 head = "<?xml version=\"1.0\" encoding=\"utf-8" +
170 "\"?><soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance" +
171 "\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=" +
172 "\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body>";
174 end = "</soap:Body></soap:Envelope>\r\n";
175 } catch (Exception e) {
180 /*******************************************************************************************************************************************
184 *******************************************************************************************************************************************/
186 private void sendLoginRequest() {
190 String body = head + "<Login xmlns=\"http://purenetworks.com/HNAP1/" +
191 "\"><Action>request</Action><Username>admin</Username><LoginPassword></LoginPassword><Captcha/></Login>" + end;
192 String postMessage = "POST /HNAP1 HTTP/1.1\r\n";
193 postMessage += "User-Agent: Java/1.8.0_144\r\n";
194 postMessage += "Host: " + host + "\r\n";
195 postMessage += "Accept: */*\r\n";
196 postMessage += "Content-Type: text/xml; charset=utf-8\r\n";
197 postMessage += "SOAPAction: \"http://purenetworks.com/HNAP1/Login\"\r\n";
198 postMessage += "Content-Length: " + Integer.toString(body.length()) + "\r\n";
199 postMessage += "\r\n";
202 // Create the communication channel
203 //IoTTCP connection = new IoTTCP(deviceAddress);
204 IoTTCP connection = new IoTTCP();
205 connection.setReuseAddress(true);
206 connection.bindAndConnect(deviceAddress, false);
208 // Get in and out communication
209 System.out.println(postMessage);
210 PrintWriter tcpOut = new PrintWriter(connection.getOutputStream(), true);
211 BufferedReader tcpIn = new BufferedReader(new InputStreamReader(connection.getInputStream()));
213 tcpOut.print(postMessage);
217 while (!tcpIn.ready()) {
220 // Wait a bit longer for data
224 while (tcpIn.ready()) {
225 response = tcpIn.readLine();
226 System.out.println("Response to Login Request: " + response);
230 } catch (Exception e) {
235 private void sendLoginInfo() {
237 // Extract challenge, public key, and cookie
238 String challenge = getTagValue(response, "Challenge");
239 cookie = "Cookie: uid=" + getTagValue(response, "Cookie");
240 String publicKey = getTagValue(response, "PublicKey") + devicePin;
242 // Generate private key and password
243 privateKey = hmacDigest(challenge, publicKey, STR_HMAC_ALGO);
244 String password = hmacDigest(challenge, privateKey, STR_HMAC_ALGO);
245 timeStamp = Long.toString(getUnixEpochSeconds());
246 // HNAP authentication
247 String authStr = timeStamp + "\"http://purenetworks.com/HNAP1/Login\"";
248 String auth = hmacDigest(authStr, privateKey, STR_HMAC_ALGO);
249 String hnap_auth = "HNAP_AUTH: " + auth + " " + timeStamp;
253 String body = head + "<Login xmlns=\"http://purenetworks.com/HNAP1/" +
254 "\"><Action>login</Action><Username>admin</Username><LoginPassword>" +
255 password + "</LoginPassword><Captcha/></Login>" + end;
256 String postMessage = "POST /HNAP1 HTTP/1.1\r\n";
257 postMessage += "User-Agent: Java/1.8.0_144\r\n";
258 postMessage += "Host: " + host + "\r\n";
259 postMessage += "Accept: */*\r\n";
260 postMessage += "Content-Type: text/xml; charset=utf-8\r\n";
261 postMessage += "SOAPAction: \"http://purenetworks.com/HNAP1/Login\"\r\n";
262 postMessage += hnap_auth + "\r\n";
263 postMessage += cookie + "\r\n";
264 postMessage += "Content-Length: " + Integer.toString(body.length()) + "\r\n";
265 postMessage += "\r\n";
268 // Create the communication channel
269 //IoTTCP connection = new IoTTCP(deviceAddress);
270 IoTTCP connection = new IoTTCP();
271 connection.setReuseAddress(true);
272 connection.bindAndConnect(deviceAddress, false);
274 // Get in and out communication
275 System.out.println(postMessage);
276 PrintWriter tcpOut = new PrintWriter(connection.getOutputStream(), true);
277 BufferedReader tcpIn = new BufferedReader(new InputStreamReader(connection.getInputStream()));
279 tcpOut.print(postMessage);
283 while (!tcpIn.ready()) {
286 // Wait a bit longer for data
290 while (tcpIn.ready()) {
291 response = tcpIn.readLine();
292 System.out.println("Response to Login Info: " + response);
296 } catch (Exception e) {
301 private void setSoundPlay(int soundType, int volume, int duration) {
305 String method = "SetSoundPlay";
306 String body = head + "<" + method + " xmlns=\"http://purenetworks.com/HNAP1/\">" +
307 "<ModuleID>1</ModuleID><Controller>1</Controller><SoundType>" + Integer.toString(soundType) +
308 "</SoundType><Volume>" + Integer.toString(volume) + "</Volume><Duration>" + Integer.toString(duration) +
309 "</Duration></" + method + ">" + end;
310 String postMessage = "POST /HNAP1 HTTP/1.1\r\n";
311 postMessage += "User-Agent: Java/1.8.0_144\r\n";
312 postMessage += "Host: " + host + "\r\n";
313 postMessage += "Accept: */*\r\n";
314 postMessage += "Content-Type: text/xml; charset=utf-8\r\n";
315 postMessage += "SOAPAction: \"http://purenetworks.com/HNAP1/" + method + "\"\r\n";
316 // HNAP authentication
317 String authStr = timeStamp + "\"http://purenetworks.com/HNAP1/" + method + "\"";
318 String auth = hmacDigest(authStr, privateKey, STR_HMAC_ALGO);
319 String hnap_auth = "HNAP_AUTH: " + auth + " " + timeStamp;
320 postMessage += hnap_auth + "\r\n";
321 postMessage += cookie + "\r\n";
322 postMessage += "Content-Length: " + Integer.toString(body.length()) + "\r\n";
323 postMessage += "\r\n";
326 System.out.println(postMessage);
328 // Create the communication channel
329 //IoTTCP connection = new IoTTCP(deviceAddress);
330 IoTTCP connection = new IoTTCP();
331 connection.setReuseAddress(true);
332 connection.bindAndConnect(deviceAddress, false);
334 // Get in and out communication
335 PrintWriter tcpOut = new PrintWriter(connection.getOutputStream(), true);
336 BufferedReader tcpIn = new BufferedReader(new InputStreamReader(connection.getInputStream()));
338 tcpOut.print(postMessage);
342 while (!tcpIn.ready()) {
345 // Wait a bit longer for data
349 while (tcpIn.ready()) {
350 response = tcpIn.readLine();
351 System.out.println("Response to SetSoundPlay: " + response);
355 } catch (Exception e) {
360 private void setAlarmDismissed() {
364 String method = "setAlarmDismissed";
365 String body = head + "<" + method + " xmlns=\"http://purenetworks.com/HNAP1/\">" +
366 "<ModuleID>1</ModuleID><Controller>1</Controller></" + method + ">" + end;
367 String postMessage = "POST /HNAP1 HTTP/1.1\r\n";
368 postMessage += "User-Agent: Java/1.8.0_144\r\n";
369 postMessage += "Host: " + host + "\r\n";
370 postMessage += "Accept: */*\r\n";
371 postMessage += "Content-Type: text/xml; charset=utf-8\r\n";
372 postMessage += "SOAPAction: \"http://purenetworks.com/HNAP1/" + method + "\"\r\n";
373 // HNAP authentication
374 String authStr = timeStamp + "\"http://purenetworks.com/HNAP1/" + method + "\"";
375 String auth = hmacDigest(authStr, privateKey, STR_HMAC_ALGO);
376 String hnap_auth = "HNAP_AUTH: " + auth + " " + timeStamp;
377 postMessage += hnap_auth + "\r\n";
378 postMessage += cookie + "\r\n";
379 postMessage += "Content-Length: " + Integer.toString(body.length()) + "\r\n";
380 postMessage += "\r\n";
383 System.out.println(postMessage);
385 // Create the communication channel
386 //IoTTCP connection = new IoTTCP(deviceAddress);
387 IoTTCP connection = new IoTTCP();
388 connection.setReuseAddress(true);
389 connection.bindAndConnect(deviceAddress, false);
391 // Get in and out communication
392 PrintWriter tcpOut = new PrintWriter(connection.getOutputStream(), true);
393 BufferedReader tcpIn = new BufferedReader(new InputStreamReader(connection.getInputStream()));
395 tcpOut.print(postMessage);
399 while (!tcpIn.ready()) {
402 // Wait a bit longer for data
406 while (tcpIn.ready()) {
407 response = tcpIn.readLine();
408 System.out.println("Response to SetSoundPlay: " + response);
412 } catch (Exception e) {
417 // Adapted from https://stackoverflow.com/questions/4076910/how-to-retrieve-element-value-of-xml-using-java
418 private String getTagValue(String xml, String tagName) {
420 return xml.split("<"+tagName+">")[1].split("</"+tagName+">")[0];
423 // Adapted from http://www.supermind.org/blog/1102/generating-hmac-md5-sha1-sha256-etc-in-java
424 private String hmacDigest(String message, String keyString, String algorithm) {
425 String digest = null;
427 SecretKeySpec key = new SecretKeySpec((keyString).getBytes("UTF-8"), algorithm);
428 Mac mac = Mac.getInstance(algorithm);
431 byte[] bytes = mac.doFinal(message.getBytes("ASCII"));
433 // Create a StringBuffer and convert the bytes into a String
434 StringBuffer hash = new StringBuffer();
435 for (int i = 0; i < bytes.length; i++) {
436 String hex = Integer.toHexString(0xFF & bytes[i]);
437 if (hex.length() == 1) {
442 // We need to convert the String to upper case
443 digest = hash.toString().toUpperCase();
444 } catch ( UnsupportedEncodingException |
445 InvalidKeyException |
446 NoSuchAlgorithmException e ) {
452 private long getUnixEpochSeconds() {
453 // Return time since January 1, 1970 00:00:00 UTC in seconds
454 return System.currentTimeMillis()/1000;