Command Line To ISY-99i Insteon Devices (Written in Java)

Post date: Nov 6, 2011 11:24:24 PM

ISY-99i

I wrote a small Java app that can access and control the Insteon devices in a Universal Devices ISY-99i controller.This allows me (or you) to script the control of any device in your house from a simple command line call or use its methods in your own Java app.For Example:>java -jar isy99CLI.jar on HallLights or >java -jar isy99CLI.jar dumpSyntax:java -jar isy99CLI.jar <command> <deviceName> where <command> is one of: dump, dim, bright, toggle, on, off

  where <deviceName> is the name of an Insteon device your 99i knows about
the dump command simply prints out details of your 99i and a list of all known devices.

You can now include commands in your crontab, or other script files.

Better yet... instantiate the class in your own Java program and use the built in methods to do whatever you like.

NOTE: the default ISY-99i username/password is hardcoded into the IsyInsteonClient.java (admin/admin). If you have not changed your 99i from the defaults the jar should work out of the box, else you need to change it and recompile to run.

A jar file is attached that you can run directly on the commandline. It has all the source files in it as well so unzip it and take a look.

It uses the com.udi.* com.universaldevices.* and some other helper classes, but here is the main code for the ca.bc.webarts.tools.isy.ISY99CLI class.

ca.bc.webarts.tools.isy.ISY99CLI

/*
 *  ISY99CLI -- A simple commandline access that turns on/off Insteon
 *  devices and scenes..
 *
 *  Copyright (C) 2011 WebARTS Design, North Vancouver Canada
 *  http://www.webarts.ca
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of Version 2 of the GNU General Public License as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package ca.bc.webarts.tools.isy;
import ca.bc.webarts.widgets.Util;
import java.io.File;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.Vector;
import com.udi.insteon.client.InsteonConstants;
import com.universaldevices.client.NoDeviceException;
import com.universaldevices.device.model.ProductInfo;
import com.universaldevices.device.model.UDGroup;
import com.universaldevices.device.model.UDNode;
import com.universaldevices.resources.errormessages.Errors;
/**
 *  A simple wraps the sending commands to an Insteon device on or off
 *  via a Universal Devices ISy-99i. This app requires
 *  a few other libraries: webarts  as well as the UDI JSDK
 *  implementation on your system.
 *
 * @author    tgutwin
 */
public class ISY99CLI
{
  /*  A Class holder for its name (used in Logging).  */
  private static String className_ = "ISY99CLI";
  /**  A holder for this clients System File Separator.  */
  public final static String SYSTEM_FILE_SEPERATOR = File.separator;
  /**  A holder for this clients System line termination separator.  */
  public final static String SYSTEM_LINE_SEPERATOR =
                                           System.getProperty("line.separator");
  private static IsyInsteonClient myISY =  null;
  private boolean stillRunning_ = false;
  private String[] insteonAddresses_ = {"14.2B.31.1"};
  private String lastDevice_="";
  private int numNodes_ = insteonAddresses_.length;
  private Thread startupThread_ = new Thread ()
      {
        public void run()
        {
          try
          {
            myISY.start();
          }
          catch (Exception ex)
          {
            // nothing
          }
        }
      };
    public boolean stillRunning()
    {
      return this.stillRunning_;
    }
    public boolean startIsy( )
    {
      startupThread_.start();
      //myISY.start();
      int timeOut = 0;
      Util.sleep(2000);
      timeOut+=2000;
      while (!myISY.isReadyToGo() )// && timeOut < 10000)
      {
        Util.sleep(1000);
        timeOut+=1000;
      }
      this.stillRunning_ = myISY.isReadyToGo();
      Util.sleep(2000);
      return this.stillRunning_ ;
    }
    /**
     * Cleans   up any ISY cleanup procedures in prep for exit.
     */
  protected  void isyCleanup()
  {
    System.out.println("Cleaning up before Exit.");
    try
    {
      myISY.stop();
      //startupThread_.stop();
      this.stillRunning_ = false;
    }
    catch (Exception ex)
    {
      // nothing
    }
    System.out.println("done!");
  }
    /**
     * Notifies the user of a syntax error
     */
  protected void syntaxError()
  {
    System.err.println("Syntax error. Try again.");
  }
  /**  Constructor * */
  public ISY99CLI()
  {
    myISY = new IsyInsteonClient();
  }
  /**
   *  The main program for the ISY99Buttons class.
   *
   * @param  arg  The command line arguments
   */
  public static void main( String[] arg )
  {
    System.out.println( "Starting "+className_ );
    if (arg.length==0)
    {
      System.out.println(" commands can be:\n dump, dim, bright, toggle, on, off DeviceName");
    }
    else
    {
      ISY99CLI instance = new ISY99CLI();
      Errors.addErrorListener(new MyISYErrorHandler());
      String command = "";
      String device = "";
      String cmdOption = "";
      try
      {
        System.out.println("starting ISY");
        instance.startIsy(); // this starts and BLOCKS until started
        //System.out.println("waiting for the ISY to settle ...");
        //Thread.sleep(500);
        System.out.println("continuing...");
        if (instance.stillRunning_  && instance.myISY.isReadyToGo())
        {
          instance.loadNodeAddresses();
          // System.out.println(dumpIsyProductInfo(isyProductInfo).toString());
          //instance.startIsy(); // this starts and BLOCKS until started
            System.out.println("Commanded to do: "+arg[0]);
            command = arg[0];
            if(!command.equals("dump")) device = arg[1];
            //System.out.println("Action From: "+command);
            boolean dim = command.trim().startsWith("dim");
            boolean bright = command.trim().startsWith("bright");
            if(command.equals("dump"))
            {
              com.universaldevices.upnp.UDProxyDevice isyDevice = instance.myISY.getDevice();
              ProductInfo isyProductInfo = isyDevice.getProductInfo();
              System.out.println(dumpIsyProductInfo(isyProductInfo).toString());
              System.out.println("  Switchable Nodes");
              System.out.println("  ~~~~~~~~~~~~~~~~");
              for (int i=0; i< instance.insteonAddresses_.length; i++)
              {
                System.out.println("    "+ instance.getNodeName(instance.insteonAddresses_[i])+" ("+instance.insteonAddresses_[i]+")");
              }
            }
            else if (dim || bright)
            {
              String deviceAddress = instance.getNodeAddress(device);
                if (dim)
                  {
                    //myISY.dimDevice(lastDevice_);
                    instance.myISY.dimDevice(deviceAddress.replace("."," "));
                  }
                else if (bright)
                  {
                    //myISY.brightenDevice(lastDevice_);
                    instance.myISY.brightenDevice(deviceAddress.replace("."," "));
                  }
            }
            else if(command.equals("toggle"))
            {
              String deviceAddress = instance.getNodeAddress(device);
              System.out.print("Toggle: "+device+ " ("+deviceAddress+") " );
              {
                String deviceStatus = instance.getStatus(instance.getNode(deviceAddress));
                // System.out.println("  to turn  "+(onOffToggle_.getState()?"On":"Off") );
                if (deviceStatus!=null) System.out.println("  to turn  "+(deviceStatus.equals("0")?"On":"Off") );
                System.out.println(" Device Status: "+deviceStatus);
                //processCommand( onOffCommand+" "+Util.tokenReplace(deviceAddress," ",".") );
                //if (onOffToggle_.getState())
                if (deviceStatus==null||deviceStatus.equals("0"))
                  {
                    //myISY.turnDeviceOn(deviceAddress);
                    instance.myISY.turnDeviceOn(deviceAddress.replace("."," "));
                  }
                else
                  {
                    //myISY.turnDeviceOff(deviceAddress);
                    instance.myISY.turnDeviceOff(deviceAddress.replace("."," "));
                  }
              }
            }
            else if(command.equals("on")||command.equals("off"))
            {
              String deviceAddress = instance.getNodeAddress(device);
              if (command.equals("on"))
              {
                //myISY.turnDeviceOn(deviceAddress);
                instance.myISY.turnDeviceOn(deviceAddress.replace("."," "));
              }
              else
              {
                //myISY.turnDeviceOff(deviceAddress);
                instance.myISY.turnDeviceOff(deviceAddress.replace("."," "));
              }
              String deviceStatus = instance.getStatus(instance.getNode(deviceAddress));
              if (deviceStatus!=null) System.out.println(device + " is now "+(deviceStatus.equals("0")?"Off":"On") );
            }
        }
      }
      catch (Exception e)
      {
        e.printStackTrace();
      }
      finally
      {
        instance.isyCleanup();
      }
    }
  }
  public static StringBuilder dumpIsyProductInfo(ProductInfo prod)
  {
    StringBuilder retVal = new StringBuilder("\n=================\nISY Product Info\n=================\n");
    if (prod!=null)
    {
      retVal.append("   - Description: ");
      retVal.append(prod.getDesc());
      retVal.append((prod.isIrEnabled()?" with IR enabled":" without IR enabled"));
      retVal.append(SYSTEM_LINE_SEPERATOR);
      retVal.append("   - ProductId: ");
      retVal.append(prod.getProductId());
      retVal.append(SYSTEM_LINE_SEPERATOR);
      retVal.append("   - SSL Certificate URL: ");
      retVal.append(prod.getSSLCertificateURL());
      retVal.append(SYSTEM_LINE_SEPERATOR);
      retVal.append(SYSTEM_LINE_SEPERATOR);
      retVal.append("  Enabled Features");
      retVal.append(SYSTEM_LINE_SEPERATOR);
      retVal.append("  ~~~~~~~~~~~~~~~~");
      retVal.append(SYSTEM_LINE_SEPERATOR);
      retVal.append("   - D2d: ");
      retVal.append((prod.isD2dEnabled()?"YES":"NO"));
      retVal.append(SYSTEM_LINE_SEPERATOR);
      retVal.append("   - Zigbee SEP Device: ");
      retVal.append((prod.isZigbeeSEPDeviceEnabled()?"YES":"NO"));
      retVal.append(SYSTEM_LINE_SEPERATOR);
      retVal.append("   - BroadBand SEP Device: ");
      retVal.append((prod.isBroadBandSEPDeviceEnabled()?"YES":"NO"));
      retVal.append(SYSTEM_LINE_SEPERATOR);
      retVal.append("   - Any SEP Device: ");
      retVal.append((prod.isAnySEPDeviceEnabled()?"YES":"NO"));
      retVal.append(SYSTEM_LINE_SEPERATOR);
      retVal.append("   - Open DR: ");
      retVal.append((prod.isOpenDREnabled()?"YES":"NO"));
      retVal.append(SYSTEM_LINE_SEPERATOR);
      retVal.append("   - URL Access: ");
      retVal.append((prod.isURLAccessEnabled()?"YES":"NO"));
      retVal.append(SYSTEM_LINE_SEPERATOR);
      retVal.append(SYSTEM_LINE_SEPERATOR);
      retVal.append("  Modules");
      retVal.append(SYSTEM_LINE_SEPERATOR);
      retVal.append("  ~~~~~~~");
      retVal.append(SYSTEM_LINE_SEPERATOR);
      retVal.append("   - Web: ");
      retVal.append((prod.isWebModulesEnabled()?"YES":"NO"));
      retVal.append(SYSTEM_LINE_SEPERATOR);
      retVal.append("   - Weather: ");
      retVal.append((prod.isWeatherEnabled()?"YES":"NO"));
      retVal.append(SYSTEM_LINE_SEPERATOR);
      retVal.append("   - A10: ");
      retVal.append((prod.isInsteonA10Enabled()?"YES":"NO"));
      retVal.append(SYSTEM_LINE_SEPERATOR);
      retVal.append("   - Energy Monitoring: ");
      retVal.append((prod.isEnergyMonitoringEnabled()?"YES":"NO"));
      retVal.append(SYSTEM_LINE_SEPERATOR);
    }
    return retVal;
  }
    /**
     * Returns all <code>UDNode</code>s that are associated in the client.
     * @return the  Enumeration UDNode if found, null otherwise
     */
  protected Enumeration <UDNode> getNodes()// throws com.universaldevices.client.NoDeviceException
  {
    Enumeration <UDNode> isyNodes = null;
    try
    {
      isyNodes = myISY.getNodes().elements();
    }
    catch (Exception ex)  // probably com.universaldevices.client.NoDeviceException
    {
      // send empty
    }
    return isyNodes;
    //return myISY.getNodes().elements();
  }
    /**
     * Returns all NON-ControlLinc or Non-SwitchLinc <code>UDNode</code>s that are associated in the client.
     * @return the  Enumeration UDNode if found, null otherwise
     */
  protected Enumeration <UDNode> getMyNodes()// throws com.universaldevices.client.NoDeviceException
  {
    Enumeration <UDNode> isyNodes = null;
    Vector <UDNode> myNodes =new Vector<UDNode>();
    try
    {
      isyNodes = myISY.getNodes().elements();
      UDNode node = null;
      while ( isyNodes.hasMoreElements())
      {
        node = (UDNode) isyNodes.nextElement();
        // System.out.println(" Check To Add "+node.address + ":" + node.name+" type="+node.typeReadable+"  -->"+ !node.typeReadable.startsWith("00."));
        if (!node.typeReadable.startsWith("00.00") && /* ContolLinc */
            !node.typeReadable.startsWith("00.05") && /* RemoteLinc */
            !node.typeReadable.startsWith("01.09")   /* SwitchLinc */ )
          myNodes.add(node);
      }
    }
    catch (Exception ex)  // probably com.universaldevices.client.NoDeviceException
    {
      // send empty
    }
    return myNodes.elements();
  }
  /**
   * Counts all the NON-ContolLinc Nodes that the ISY is aware of.
   * @return the number of  NON-ContolLinc nodes  that the ISY is aware of.
   **/
  private int countNodes()
  {
    int retVal = 0;
    try
    {
      Enumeration <UDNode> e = getNodes();
      UDNode node = null;
      while ( e.hasMoreElements())
      {
        node = e.nextElement();
        retVal++;
      }
    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }
    return retVal;
  }
  /**
   * Counts all the NON-ContolLinc Nodes that the ISY is aware of.
   * @return the number of  NON-ContolLinc nodes  that the ISY is aware of.
   **/
  private int countMyNodes()
  {
    int retVal = 0;
    try
    {
      Enumeration <UDNode> e = getMyNodes();
      UDNode node = null;
      while ( e.hasMoreElements())
      {
        node = e.nextElement();
        retVal++;
      }
    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }
    return retVal;
  }
  /**
   * Puts all the Node Address at this ISY into the insteonAddresses_ array so they can get assigned to buttons.
   **/
  private void loadNodeAddresses()
  {
    try
    {
      int numNodes = countMyNodes();
      insteonAddresses_ = new String[numNodes];
      System.out.println("Loading "+numNodes+" Controllable Devices");
      int count = 0;
      Enumeration <UDNode> e = getMyNodes();
      while (e.hasMoreElements())
      {
        UDNode node = e.nextElement();
        insteonAddresses_[count++] = Util.tokenReplace(node.address," ",".");
        //System.out.println("                 "+node.address + ":" + node.name+" type="+node.typeReadable);
      }
      numNodes_ = numNodes;
    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }
  }
    /**
     * Returns the current value of Insteon Devicse (its state)
     * @param tk - the StringTokenzier
     */
  protected void processStatus(StringTokenizer tk) {
    String tmp = tk.nextToken();        //device address
    UDNode node = getNode(tmp);
    if (node == null) {
      return;
    }
    tmp = (String) myISY.getCurrValue(node, InsteonConstants.DEVICE_STATUS);
    System.out.println("The current status for " + node.address + "/" + node.name + " is " + tmp);
  }
    /**
     * Returns the current value of an Insteon Device (its state)
     * @param node - the UDNode to query
     * @return the status
     **/
  protected String getStatus(UDNode node ) {
    if (node == null) {
      return "";
    }
    String tmp = (String) myISY.getCurrValue(node, InsteonConstants.DEVICE_STATUS);
    //stem.out.println("The current status for " + node.address + "/" + node.name + " is " + tmp);
    return tmp;
  }
  /** Duh!
   * @param the node's address to query.
   * @return the name assigned to the node at the address requested.
   **/
  protected String getNodeName(String address)
  {
    if (address == null)
    {
      System.err.println("Missing Device/Scene address");
      return "";
    }
    UDNode node = getNode(address);
    return node.name;
  }
  /** Duh!
   * @param the node's address to query.
   * @return the name assigned to the node at the address requested.
   **/
  protected String getNodeAddress(String nodeName)
  {
    String retVal ="";
    UDNode node = null;
    if (nodeName == null)
    {
      System.err.println("Missing Device/Scene address");
      return "";
    }
    for (int i=0; i< insteonAddresses_.length; i++)
    {
      node = getNode(insteonAddresses_[i]);
      if(node.name.equals(nodeName))
      {
        retVal = node.address;
        retVal = retVal.replace(" ",".");
      }
    }
    //retVal.replace(" ",".");
    return retVal;
  }
    /**
     * Returns a <code>UDGroup</code> or a <code>UDNode</code> based on the
     * given address
     * @param address - the address of the node/scene to be retrieved
     * @return the UDNode if found, null otherwise
     */
  protected UDNode getNode(String address)
  {
    if (address == null)
    {
      System.err.println("Missing Device/Scene address");
      return null;
    }
    if (address.indexOf(".") > 0)
    {
            //this is an insteon device
      address = address.replace(".", " ");            //normalize it to our format
      try
      {
        UDNode node = myISY.getNodes().get(address);
        if (node == null)
        {
          System.err.println("Address points to a non existing Insteon Device");
          return null;
        }
        return node;
      }
      catch (NoDeviceException e)
      {
        System.out.println(e);
        return null;
      }
    }
    //this is an insteon scene
    try
    {
      UDGroup group = myISY.getGroups().get(address);
      if (group == null)
      {
        System.err.println("Address points to a non-existing scene");
        return null;
      }
      return group;
    }
    catch (Exception e)
    {
      System.err.println(e);
      return null;
    }
  }
  protected char INSTEON_MASTER_MODE = 0xF0;
  protected char INSTEON_SLAVE_MODE = 0x0F;
    /**
     * Returns the mode based on the input
     * @param mode
     * @return - the mode (INSTEON_MASTER_MODE, INSTEON_SLAVE_MODE)
     */
  protected char getMode(String mode)
  {
    if (mode.equals("M"))
    {
      return INSTEON_MASTER_MODE;
    }
    if (mode.equals("S"))
    {
      return INSTEON_SLAVE_MODE;
    }
    System.err.println("Please specify M (for Master mode) or S (for Slave mode)");
    return 0;
  }
    /**
     * Processes an Insteon command
     * @param cmd - the command to be processed
     * @param tk - the StringTokenizer
     */
  protected void processInsteonCommand(String cmd, StringTokenizer tk)
  {
    String address = tk.nextToken();
    System.out.println("tk addr:"+address);
    UDNode node = getNode(address);
    if (node == null)
    {
      return;
    }
        //it's a group
    if (node instanceof UDGroup)
    {
      myISY.changeGroupState(cmd, null, node.address);
    }
    else
    {
      myISY.changeNodeState(cmd, null, node.address);
    }
  }
  /**
   *  Abstracts the actual sending of the ISY Command.
   *
   * @param  command       This apps Command (not necessarily an Insteon Command (but could Be)
   */
  public void processCommand( String command)
  {
    if (command == null || command.length() < 1) {
      return;
    }
    System.out.println("Button Command:"+command);
    StringTokenizer tk = new StringTokenizer(command, " ");
    try
    {
      String cmd = tk.nextToken();
      if (  cmd.startsWith(InsteonConstants.DEVICE_ON) ||
            cmd.startsWith(InsteonConstants.DEVICE_OFF) ||
            cmd.startsWith(InsteonConstants.DEVICE_FAST_ON) ||
            cmd.startsWith(InsteonConstants.DEVICE_FAST_OFF) ||
            cmd.startsWith(InsteonConstants.LIGHT_DIM) ||
            cmd.startsWith(InsteonConstants.LIGHT_BRIGHT))
      {
        System.out.println("Processing Cmd:"+cmd+" tk:"+tk.toString());
        processInsteonCommand(cmd, tk);
      }
      else if (cmd.equalsIgnoreCase("StartLinking"))
      {
         myISY.startLinking();
      }
      else if (cmd.equalsIgnoreCase("StopLinking"))
      {
         myISY.stopLinking();
      }
      else if (cmd.startsWith("SetLinkingMode"))
      {
        //setLinkingMode(tk);
      }
      else if (cmd.startsWith("Rename"))
      {
        //processRename(tk);
      }
      else if (cmd.startsWith("Delete"))
      {
       // processDelete(tk);
      }
      else if (cmd.startsWith("Remove"))
      {
        //processRemoveFromScene(tk);
      }
      else if (cmd.startsWith("Move"))
      {
       // processMove(tk);
      }
      else if (cmd.startsWith("NewScene"))
      {
       // processNewScene(tk);
      }
      else if (cmd.startsWith("SetSceneOnLevel"))
      {
        //processSceneOnLevel(tk);
      }
      else if (cmd.startsWith("SetSceneRampRate"))
      {
       // processSceneRampRate(tk);
      }
      else if (cmd.startsWith("SetSceneControllerOnLevel"))
      {
       // processSceneControllerOnLevel(tk);
      }
      else if (cmd.startsWith("SetSceneControllerRampRate"))
      {
        //processSceneControllerRampRate(tk);
      }
      else if (cmd.equalsIgnoreCase("ListNodes"))
      {
       // processListNodes();
      }
      else if (cmd.equalsIgnoreCase("ListScenes"))
      {
       // processListScenes();
      }
      else if (cmd.startsWith("GetStatus"))
      {
        //processStatus(tk);
      }
      else if (cmd.equalsIgnoreCase("Exit"))
      {
        myISY.stop();
        System.exit(0);
      }
      else
      {
        syntaxError();
      }
    } // try
    catch (Exception e)
    {
      e.printStackTrace();
      syntaxError();
    }
  }
}

Have fun!

tom