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}