001/* 002 * $HeadURL: svn://svn.webarts.bc.ca/open/trunk/projects/WebARTS/ca/bc/webarts/tools/isy/IsyInsteonClient.java $ 003 * $Revision: 1030 $ 004 * $Date: 2015-11-08 18:40:15 -0800 (Sun, 08 Nov 2015) $ 005 * $Author: tgutwin $ 006 */ 007/* 008 * ----------------------------------------------------------------- 009 * IsyInsteonClient.java 010 * -------------- 011 * 012 * Based on the UDI example class 013 * Copyright (C) 2010 WebARTS Design, North Vancouver Canada 014 * http://www.webarts.ca 015 * 016 * This program is free software; you can redistribute it and/or modify 017 * it under the terms of Version 2 of the GNU General Public License as 018 * published by the Free Software Foundation. 019 * 020 * This program is distributed in the hope that it will be useful, 021 * but WITHOUT ANY WARRANTY; without even the implied warranty of 022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 023 * GNU General Public License for more details. 024 * 025 * You should have received a copy of the GNU General Public License 026 * along with this program; if not, write to the Free Software 027 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 028 * ----------------------------------------------------------------- 029 */ 030package ca.bc.webarts.tools.isy; 031 032import java.io.BufferedReader; 033import java.io.IOException; 034import java.io.InputStreamReader; 035import java.util.ArrayList; 036import java.util.Enumeration; 037import java.util.NoSuchElementException; 038import java.util.StringTokenizer; 039// import java.net.InetSocketAddress; 040 041import com.nanoxml.XMLElement; 042import com.udi.isy.jsdk.insteon.ISYInsteonClient; 043import com.universaldevices.client.NoDeviceException; 044import com.universaldevices.common.Constants; 045import com.universaldevices.common.UDClientStatus; 046import com.universaldevices.common.UDUtil; 047import com.universaldevices.common.properties.UDProperty; 048import com.universaldevices.device.model.IModelChangeListener; 049import com.universaldevices.device.model.ProductInfo; 050import com.universaldevices.device.model.UDControl; 051import com.universaldevices.device.model.UDFolder; 052import com.universaldevices.device.model.UDGroup; 053import com.universaldevices.device.model.UDNode; 054import com.universaldevices.common.Constants; 055import com.universaldevices.resources.errormessages.Errors; 056import com.universaldevices.security.upnp.UPnPSecurity; 057import com.universaldevices.soap.UDHTTPResponse; 058// import com.universaldevices.upnp.UDControlPoint; 059import com.universaldevices.upnp.UDProxyDevice; 060import com.universaldevices.device.model.NetworkConfig; 061 062/** 063 * This class implements a very simple ISY client which 064 * prints out events as they occur in ISY 065 * 066 */ 067public class IsyInsteonClient extends ISYInsteonClient 068{ 069 private String myUserId = "admin"; 070 /* DO NOT cahange the next line, it get changed at compile time to remove the password */ 071 private String myPassword = "11smiles"; 072 public static UDProxyDevice myConnectedDevice_=null; 073 private NetworkConfig isyNetworkConfig_ = null; 074 private boolean debug_ = false; 075 076 private ArrayList <NodeChangeMessage> recentNodeChanges_ = null ; 077 private boolean makingNodeChanges = false; // this is the node change lock 078 079 /** 080 * Constructor 081 * Registers this class as IModelChangeListener 082 * 083 * @see IModelChangeListener 084 * 085 */ 086 public IsyInsteonClient() 087 { 088 super(); 089 } 090 091 public IsyInsteonClient(boolean debugOn) 092 { 093 super(); 094 debug_=debugOn; 095 } 096 097 public void setDebug(boolean d) 098 { 099 debug_=d; 100 } 101 102 public void debugOn() 103 { 104 debug_=true; 105 } 106 107 public void debugOff() 108 { 109 debug_=false; 110 } 111 112 public void debugToggle() 113 { 114 debug_=!debug_; 115 } 116 117 public void unsubscribeFromEvents() 118 { 119 com.universaldevices.upnp.UDProxyDevice isyDevice = getDevice(); 120 if (isyDevice!=null) isyDevice.unsubscribeFromEvents() ; 121 } 122 123 124 /** 125 * Send SOAP request to the connected device. 126 * @param urlPath is the soap urlpath ie "/web/nodescnf.xml" 127 **/ 128 private void submitSoapRequest(String serviceName) 129 { 130 if (myConnectedDevice_!=null) 131 { 132 String url = ""; 133 StringBuffer body=new StringBuffer(); 134 body.append("<reportURL>"); 135 body.append(url); 136 body.append("</reportURL><duration>"); 137 body.append(Constants.UD_SUBSCRIPTION_DURATION); 138 body.append("</duration>"); 139 140 UDHTTPResponse res = myConnectedDevice_.submitSOAPRequest(serviceName,body,UPnPSecurity.SIGN_WITH_HMAC_KEY,false,false); 141 if (!res.opStat || res.body == null) 142 { 143 Errors.showError(res.status,null,myConnectedDevice_); 144 } 145 System.out.println(res.body); 146 } 147 } 148 149 150 /** 151 * This method is called when a new ISY is announced or discovered 152 * on the network. For this sample, we simply authenticate ourselves 153 */ 154 public void onNewDeviceAnnounced(UDProxyDevice device) 155 { 156 //if (debug_) 157 System.out.println("NEW DEVICE: " + device.getFriendlyName()); 158 159 } 160 161 162 /** 163 * This method is invoked when ISY goes into Linking mode 164 */ 165 public void onDiscoveringNodes() 166 { 167 if (debug_) System.out.println("I am in Linking Mode ..."); 168 } 169 170 171 /** 172 * This method is invoked when ISY is no longer in Linking mode 173 */ 174 public void onNodeDiscoveryStopped() 175 { 176 if (debug_) System.out.println("I am no longer in Linking mode ..."); 177 178 } 179 180 181 /** 182 * This method is invoked when a group/scene is removed 183 */ 184 public void onGroupRemoved(String groupAddress) 185 { 186 if (debug_) System.out.println("Scene: " + groupAddress + " was removed by someone or something!"); 187 188 } 189 190 191 /** 192 * This method is invoked when a group/scene is renamed 193 */ 194 public void onGroupRenamed(UDGroup group) 195 { 196 if (debug_) System.out.println("Scene: " + group.address + " was renamed to " + group.name); 197 198 } 199 200 201 /** 202 * This method snoops (retrieves) the list of recent node change messages that came in; WITHOUT removing them from the list. 203 * This method is Thread safe - it mutexes the access to the ArrayList. 204 * 205 * @return An ArralList filled with the NodeChangeMessages since the last time the getRecentNodeChanges method was called or null if empty. 206 */ 207 public ArrayList<NodeChangeMessage> snoopRecentNodeChanges(){return getRecentNodeChanges(false);} 208 209 210 /** 211 * This method gets (and then clears) the list of recent node change messages that came in. 212 * This method is Thread safe - it mutexes the access to the ArrayList. 213 * 214 * @return An ArralList filled with the NodeChangeMessages since the last time this method was called or null if empty. 215 */ 216 public ArrayList<NodeChangeMessage> getRecentNodeChanges(){return getRecentNodeChanges(true);} 217 218 219 /** 220 * This method gets (and then clears) the list of recent node change messages that came in. 221 * This method is Thread safe - it mutexes the access to the ArrayList. 222 * 223 * @param clearList flags if this method should clear the list after retrieval. 224 * @return An ArralList filled with the NodeChangeMessages since the last time this method was called or null if empty. 225 */ 226 private ArrayList<NodeChangeMessage> getRecentNodeChanges(boolean clearList) 227 { 228 ArrayList<NodeChangeMessage> retVal = null; 229 boolean waitingForLock = true; 230 while( recentNodeChanges_!=null && waitingForLock) 231 { 232 if (makingNodeChanges) 233 { 234 UDUtil.sleep(100); 235 } 236 else 237 { 238 waitingForLock = false; 239 makingNodeChanges=true; 240 retVal =new ArrayList<NodeChangeMessage> ( recentNodeChanges_); 241 if (clearList) recentNodeChanges_=null; 242 makingNodeChanges=false; 243 } 244 } 245 246 return retVal; 247 } 248 249 250 /** 251 * This method add the latest model change message to the stack to be consumed. 252 */ 253 private void addToChangeMessageStack( UDNode node, UDControl control, Object value) 254 { 255 NodeChangeMessage msg = new NodeChangeMessage(node, control, value); 256 257 boolean waitingForLock = true; 258 while(waitingForLock) 259 { 260 if (makingNodeChanges) 261 { 262 UDUtil.sleep(100); 263 } 264 else 265 { 266 waitingForLock = false; 267 makingNodeChanges=true; 268 if (recentNodeChanges_==null) recentNodeChanges_ = new ArrayList<NodeChangeMessage> ( ); 269 recentNodeChanges_.add(msg); 270 makingNodeChanges=false; 271 } 272 } 273 } 274 275 276 /** 277 * This method is invoked everytime there's a change in the state of a control for 278 * a node (Insteon Device). 279 */ 280 public void onModelChanged(UDControl control, Object value, UDNode node) 281 { 282 283 //if (debug_) System.out.print("Someone or something changed the model: "); 284 //if (debug_&& control!=null && node!=null) System.out.print(control.label+ " to "+value+" at "+node.name); 285 286 if (debug_) System.out.print("."); 287 288 // stack the message 289 addToChangeMessageStack(node, control, value); 290 291 } 292 293 294 /** 295 * This method is invoked when the network is renamed. Network is the top most node in 296 * the tree in our applet. 297 */ 298 public void onNetworkRenamed(String newName) 299 { 300 if (debug_) System.out.println("Ah, the network was renamed to " + newName); 301 } 302 303 /** 304 * This method is called when a new group/scene has been created. 305 */ 306 public void onNewGroup(UDGroup newGroup) 307 { 308 if (debug_) System.out.println("Yummy: we now have a new scene with address " + newGroup.address + " and name " + newGroup.name); 309 } 310 311 /** 312 * This method is called when a new node (Insteon Device) has been added 313 */ 314 public void onNewNode(UDNode newNode) 315 { 316 if (debug_) System.out.println("Yummy: we now have a new Insteon device with address " + newNode.address + " and name " + newNode.name); 317 318 } 319 /** 320 * This method is called when an Insteon Device does not correctly communicate with ISY 321 */ 322 public void onNodeError(UDNode node) 323 { 324 if (debug_) System.out.println("What's going on? The Insteon device at address " + 325 node.address + " and name " + node.name + 326 " is no longer responding to my communication attempts!"); 327 328 } 329 /** 330 * This method is called with a node is enabled or disabled 331 * @param node 332 * @param b 333 */ 334 public void onNodeEnabled(UDNode node, boolean b) 335 { 336 if (debug_) System.out.println(String.format("Node %s is now %s", node.name, b ? "enabled" : "disabled")); 337 } 338 339 /** 340 * This method is called when a node (Insteon Device) has been permanently removed from ISY 341 */ 342 public void onNodeRemoved(String nodeAddress) 343 { 344 if (debug_) System.out.println("Whooah ... node with address " + nodeAddress + " was permanently removed from ISY"); 345 346 } 347 /** 348 * This method is called when a node (Insteon Device) is removed from a scene 349 */ 350 public void onNodeRemovedFromGroup(UDNode node, UDGroup group) 351 { 352 if (debug_) System.out.println("Insteon device with address " + node.address + " and name " + 353 node.name + " is no longer part of the " + group.name + " scene!"); 354 355 } 356 357 /** 358 * This method is called when a node's role changes in the given group (master/slave role). 359 */ 360 public void onNodeToGroupRoleChanged(UDNode node, UDGroup group, char new_role) 361 { 362 System.out.println("Insteon device with address " + node.address + " now has a new role in group with address " + group.address + " : "); 363 if (new_role == Constants.UD_LINK_MODE_MASTER) 364 { 365 if (debug_) System.out.println("Controller/Master"); 366 } 367 else 368 { 369 if (debug_) System.out.println("Responder/Slave"); 370 } 371 } 372 373 /** 374 * This method is invoked when a node (Insteon Device) is renamed 375 */ 376 public void onNodeRenamed(UDNode node) 377 { 378 if (debug_) System.out.println("Insteon device with address " + node.address + " was renamed to " + node.name); 379 380 } 381 /** 382 * This method is invoked when a node (Insteon Device) has been moved to a scene as controller/master 383 */ 384 public void onNodeMovedAsMaster(UDNode node, UDGroup group) 385 { 386 if (debug_) System.out.println("Insteon device " + node.name + " is now part of the " + group.name + " scene as a master/controller"); 387 388 } 389 /** 390 * This method is invoked when a node (Insteon Device) has been moved to a scene as responder/slave 391 */ 392 public void onNodeMovedAsSlave(UDNode node, UDGroup group) 393 { 394 if (debug_) System.out.println("Insteon device " + node.name + " is now part of the " + group.name + " scene as a slave/responder"); 395 396 } 397 398 399 /** 400 * This method is invoked with the library does not receive announcements from ISY and considers it offline 401 */ 402 public void onDeviceOffLine() 403 { 404 if (debug_) System.out.println("oo; ISY is offLine. Did you unplug it?"); 405 406 } 407 408 409 /** 410 * This method is invoked when a currently known ISY (UDProxyDevice) is back on line. 411 */ 412 public void onDeviceOnLine() 413 { 414 if (debug_) System.out.println(" Hooray: ISY is on line ..."); 415 myConnectedDevice_ = getDevice(); 416 if (myConnectedDevice_ == null) 417 { 418 return; 419 } 420 if (myConnectedDevice_.isSecurityEnabled() || myConnectedDevice_.securityLevel > UPnPSecurity.NO_SECURITY) 421 { 422 if (myConnectedDevice_.isAuthenticated && myConnectedDevice_.isOnline) 423 { 424 return; 425 } 426 try 427 { 428 if (debug_) System.out.print(" AUTHENTICATING/SUBSCRIBING ("+myConnectedDevice_.uuid+")..."); 429 authenticate(myUserId, myPassword); 430 myConnectedDevice_.subscribeToEvents(true); 431 if (debug_) System.out.println(" DONE"); 432 isyNetworkConfig_ = myConnectedDevice_.getNetworkConfig(); 433 } 434 catch (NoDeviceException e) 435 { 436 System.err.println("This should never happen!"); 437 } 438 } 439 else 440 { 441 // just subscribe to events 442 if (debug_) System.out.print(" SUBSCRIBING ... "); 443 myConnectedDevice_.subscribeToEvents(true); 444 if (debug_) System.out.println("DONE"); 445 } 446 } 447 448 449 /** 450 * This method is invoked when the state of the system (whether or not busy) is changed 451 * @param busy - whether or not ISY is busy 452 */ 453 public void onSystemStatus(boolean busy) 454 { 455 if (busy) 456 { 457 if (debug_) System.out.println("I am busy now; please give me some reprieve and don't ask me for more!"); 458 } 459 else 460 { 461 if (debug_) System.out.println("I am ready and at your service"); 462 } 463 } 464 465 466 /** 467 * This method is invoked when internet access is disabled on ISY 468 */ 469 public void onInternetAccessDisabled() 470 { 471 if (debug_) System.out.println("You can no longer reach me through the internet"); 472 473 } 474 475 476 /** 477 * This method is invoked with internet access is enabled on ISY 478 * @param url - the external fully qualified url through which ISY can be accessed 479 */ 480 public void onInternetAccessEnabled(String url) 481 { 482 if (debug_) System.out.println("You can now reach me remotely at: " + url); 483 484 } 485 486 487 /** 488 * This method is invoked when trigger status changes. 489 * @param arg1 - the status 490 * @param arg2 - extra information 491 */ 492 public void onTriggerStatus(String arg1, XMLElement arg2) 493 { 494 if (debug_) System.out.println("Trigger status changed: " + arg1 +": "+ arg2.getContents()); 495 496 } 497 498 499 public void onDeviceSpecific(String arg1, String node, XMLElement arg2) 500 { 501 if (debug_) System.out.println("Device Specific action: "); 502 if (debug_) System.out.println(arg2.toString()); 503 504 } 505 506 507 public void onProgress(String arg1, XMLElement arg2) 508 { 509 if (debug_) System.out.println("Progress Report:"); 510 if (debug_) System.out.println(arg2.toString()); 511 } 512 513 514 /* ****************************************************************** */ 515 /** Helper to tell if authenticated and ready to process **/ 516 public boolean isReadyToGo() 517 { 518 boolean retVal = false; 519 //System.out.print("^"); 520 if (myConnectedDevice_!=null) 521 { 522 //System.out.print("^"); 523 if (myConnectedDevice_ != null) 524 { 525 //System.out.print("^"); 526 if (myConnectedDevice_.isSecurityEnabled() || myConnectedDevice_.securityLevel > UPnPSecurity.NO_SECURITY) 527 { 528 //System.out.print("^"); 529 if (myConnectedDevice_.isAuthenticated && myConnectedDevice_.isOnline) 530 { 531 //System.out.print("^"); 532 retVal = isISYReady(); 533 } 534 } 535 else 536 { 537 //retVal =( myConnectedDevice_.isOnline && isISYReady()); 538 } 539 } 540 } 541 //System.out.println(":"+retVal); 542 return retVal; 543 } 544 545 546 /** 547 * Implement any cleanup Routines necessary here 548 */ 549 @Override 550 public void cleanUp() 551 { 552 if (debug_) System.out.println("Clean up IsyClient static objects you have around"); 553 unsubscribeFromEvents(); 554 getDevice().Stop(); 555 super.cleanUp(); 556 557 } 558 559 560 @Override 561 public void onSystemConfigChanged(String event, XMLElement eventInfo) 562 { 563 if (debug_) System.out.println("\nSystem configuration changed"); 564 565 } 566 567 568 @Override 569 public void onFolderRemoved(String folderAddress) 570 { 571 if (debug_) System.out.println(String.format("Folder removed %s", folderAddress)); 572 573 } 574 575 576 @Override 577 public void onFolderRenamed(UDFolder folder) 578 { 579 if (debug_) System.out.println(String.format("Folder renamed %s, new name %s", folder.address, folder.name)); 580 581 } 582 583 584 @Override 585 public void onNewFolder(UDFolder folder) 586 { 587 if (debug_) System.out.println(String.format("New Folder %s, name %s", folder.address, folder.name)); 588 589 } 590 591 592 @Override 593 public void onNodeHasPendingDeviceWrites(UDNode node, boolean hasPending) 594 { 595 if (debug_) System.out.println(String.format("Node %s, %s pending device writes", node.name, hasPending ? "has" : "does not have")); 596 597 } 598 599 600 @Override 601 public void onNodeIsWritingToDevice(UDNode node, boolean isWriting) 602 { 603 if (debug_) System.out.println(String.format("Node %s, %s being programmed", node.name, isWriting ? "is" : "is not")); 604 605 } 606 607 608 @Override 609 public void onNodeParentChanged(UDNode node, UDNode newParent) 610 { 611 if (debug_) System.out.println(String.format("Node %s, has new parent %s", node.name, newParent.name)); 612 613 } 614 615 616 @Override 617 public void onNodePowerInfoChanged(UDNode node) 618 { 619 if (debug_) System.out.println("Not supported "); 620 621 } 622 623 624 /* (non-Javadoc) 625 * @see com.universaldevices.device.model.IModelChangeListener#onNodeDeviceIdChanged(com.universaldevices.upnp.UDProxyDevice, com.universaldevices.device.model.UDNode) 626 */ 627 @Override 628 public void onNodeDeviceIdChanged(UDProxyDevice device, UDNode node) 629 { 630 // TODO Auto-generated method stub 631 632 } 633 634 635 /* (non-Javadoc) 636 * @see com.universaldevices.device.model.IModelChangeListener#onNodeDevicePropertiesRefreshed(com.universaldevices.upnp.UDProxyDevice, com.universaldevices.device.model.UDNode) 637 */ 638 @Override 639 public void onNodeDevicePropertiesRefreshed(UDProxyDevice device, UDNode node) 640 { 641 // TODO Auto-generated method stub 642 643 } 644 645 646 /* (non-Javadoc) 647 * @see com.universaldevices.device.model.IModelChangeListener#onNodeDevicePropertiesRefreshedComplete(com.universaldevices.upnp.UDProxyDevice) 648 */ 649 @Override 650 public void onNodeDevicePropertiesRefreshedComplete( UDProxyDevice proxyDevice) 651 { 652 // TODO Auto-generated method stub 653 654 } 655 656 657 /* (non-Javadoc) 658 * @see com.universaldevices.device.model.IModelChangeListener#onNodeDevicePropertyChanged(com.universaldevices.upnp.UDProxyDevice, com.universaldevices.device.model.UDNode, com.universaldevices.common.properties.UDProperty) 659 */ 660 @Override 661 public void onNodeDevicePropertyChanged(UDProxyDevice device, UDNode node, UDProperty<?> property) 662 { 663 // TODO Auto-generated method stub 664 665 } 666 667 668 /* (non-Javadoc) 669 * @see com.universaldevices.device.model.IModelChangeListener#onNodeRevised(com.universaldevices.upnp.UDProxyDevice, com.universaldevices.device.model.UDNode) 670 */ 671 @Override 672 public void onNodeRevised(UDProxyDevice device, UDNode node) 673 { 674 // TODO Auto-generated method stub 675 676 } 677 678 679 /* (non-Javadoc) 680 * @see com.universaldevices.device.model.IModelChangeListener#onNodeErrorCleared(com.universaldevices.upnp.UDProxyDevice, com.universaldevices.device.model.UDNode) 681 */ 682 @Override 683 public void onNodeErrorCleared(UDProxyDevice arg0, UDNode arg1) 684 { // as of jsdk 3.1.17 685 // TODO Auto-generated method stub 686 687 } 688 689 690 /* (non-Javadoc) 691 * @see com.universaldevices.device.model.IModelChangeListener#onLinkerEvent(com.universaldevices.upnp.UDProxyDevice, java.lang.String, com.nanoxml.XMLElement) 692 */ 693 @Override 694 public void onLinkerEvent(UDProxyDevice arg0, String arg1, XMLElement arg2) 695 { // as of jsdk 4.2.18 696 // TODO Auto-generated method stub 697 698 } 699 700 701 /* (non-Javadoc) 702 * @see com.universaldevices.device.model.IModelChangeListener#onNodeSupportedTypeInfoChanged(com.universaldevices.upnp.UDProxyDevice, java.lang.String) 703 */ 704 @Override 705 public void onNodeSupportedTypeInfoChanged(UDProxyDevice arg0, String arg1) 706 { // as of jsdk 4.2.18 707 // TODO Auto-generated method stub 708 709 } 710 711}