Home‎ > ‎Tom's Ideas And Notes‎ > ‎

Program/consume Isy-99 Web Services in Java using JAX-WS

posted Dec 16, 2012, 7:27 PM by Tom Gutwin   [ updated Feb 2, 2013, 4:46 PM ]
ISY99i
This post discusses accessing the Universal Devices ISY-99 controller with Web Services using Java.
The Universal Devices ISY series of Insteon controllers are beautiful pieces of hardware and software. I have discussed them before.

As a developer, the ISY provides...
  • A full Java SDK so you can write java code and talk to them directly. (My preferred method)
  • They expose many commands, controls & queries via Web Services.
  • They also have a module to interface with a connected ELK security system through Web Services.
    Unhappy
    • The ELK module however does not have a direct Java API (like the ISY)
    • So the only way to communicate with it (using Java) is through its UDI-Elk Web Services.
Universal Devices (UDI) has more information on its developer page.

This will show you how to use Java API for XML - WebServices (JAX-WS) to parse the UDI web services (and UDI Elk web services) wsdl files and turn them into Java object classes.
JAX-WS has an import tool (wsimport) that reads a W3C compliant wsdl file and turns its “services” into Java objects so all you have to do is call the appropriate Java classes.

For example; once the processing is complete, you can use a Java class like the following to access the ISY:

package ca.bc.webarts.tools.isy;

/*
* These are all generated from the JAX-WS wsimport tool
*/
import ca.bc.webarts.tools.isy.webservices.DateTime;
import ca.bc.webarts.tools.isy.webservices.Empty;
import ca.bc.webarts.tools.isy.webservices.UDIServices;
import ca.bc.webarts.tools.isy.webservices.UDIServicesPortType;
import ca.bc.webarts.tools.isy.webservices.Variable;

import java.util.List;
import java.util.Map;
import javax.xml.ws.BindingProvider;

public class IsyWSClient
{
 private String isyUsername_ = "admin";
 private String isyPassword_ = "yourPasswordGoesHere";
 private UDIServices service_ = new UDIServices();
 private UDIServicesPortType port_ = service_.getUDIServicesPort();

   public static void main(String[] args)
   {
     IsyWSClient instance = new IsyWSClient();

       Map<String, Object> reqCtx = ((BindingProvider) instance.port_).getRequestContext();
       reqCtx.put(BindingProvider.USERNAME_PROPERTY, instance.isyUsername_);
       reqCtx.put(BindingProvider.PASSWORD_PROPERTY, instance.isyPassword_);

       DateTime dt = instance.port_.getSystemDateTime(new Empty());
       System.out.println("NTP time=" + dt.getNTP());
   }
}

Online Documentation

Before I start, here are some references to some good background information and tools, specific to processing Web Services in Java.

Requirements

or

I used NetBeans and will document the process using NetBeans.

Process

Basic setup

Download and install NetBeans from www.netbeans.org
Create a new ‘Java Application’ Project...

Name Your Project and NEW Class...

Right-click on the your new project in the projects listed along the left (to bring up the context menu) and
click Properties then select Libraries to Add Library JAX-WS 1.1 ...

You should now have an template Class (without a main method) in your editing window...

Now before you start writing your Java app, we need to get the UDI Web Services loaded from the wsdl file.

WSDL Import

Changes to the wsdl file

The 1st thing to note is that JAX-WS wsimport tool is very strict and will not process the default UDI wsdl file without a few tweaks.  This is because  the udi (http://isy99/services.wsdl) or the actual wsdl file udiws30.wsdl is not 100% compliant with the W3C specification for WS-I basic profile definition R2204 -A document-literal binding in a DESCRIPTION MUST refer, in each of its soapbind:body element(s), only to wsdl:part element(s) that have been defined using the element attribute.  see http://www.ws-i.org/Profiles/BasicProfile-1.1.html#Bindings_and_Parts.

Many of the message parts refer to a type instead of element. For example...

<wsdl:message name="GetNodesConfigResponse">

     <wsdl:part name="nodes" type="uo:nodes"/>

</wsdl:message>

This is easily fixed with a small abstraction

<xsd:element name="nodes" type="uo:nodes"/>

and then use that element in the original message part...

<wsdl:message name="GetNodesConfigResponse">

       <wsdl:part name="nodes" element="u:nodes"/>

</wsdl:message>


These are simple changes.
There are ~16 times in the file. I went through and updated the udiws30.wsdl file to add the following xsd:elements:

<xsd:complexType name="UDIDefaultResponseType">

               <xsd:annotation>

                   <xsd:documentation>

                       Default status info response

                   </xsd:documentation>

               </xsd:annotation>

               <xsd:sequence>

                   <xsd:element name="status" minOccurs="1" maxOccurs="1" type="xsd:string"/>

                   <xsd:element name="info" minOccurs="0" maxOccurs="1" type="xsd:string"/>

               </xsd:sequence>

           </xsd:complexType>


           <xsd:complexType name="UDITimeResponseType">

               <xsd:attribute name="val" type="xsd:dateTime" use="required">

                   <xsd:annotation>

                       <xsd:documentation>

                           Timestamp in the form of YYYYMMDD HH:MM:SS

                       </xsd:documentation>

                   </xsd:annotation>

               </xsd:attribute>

           </xsd:complexType>


           <xsd:complexType name="UDIIntResponseType">

               <xsd:attribute name="val" type="xsd:int" use="required">

                   <xsd:annotation>

                       <xsd:documentation>

                           Return value of type integer. Currently used for IsSubscribed

                       </xsd:documentation>

                   </xsd:annotation>

               </xsd:attribute>

           </xsd:complexType>


<!-- PATCH - NEW Entries: Abstract types with the element attribute -->

           <xsd:element name="UDIDefaultResponse" type="u:UDIDefaultResponseType"/>
           <xsd:element name="UDITimeResponse" type="u:UDITimeResponseType"/>
           <xsd:element name="UDIIntResponse" type="u:UDIIntResponseType"/>
           <xsd:element name="nodes" type="uo:nodes"/>
           <xsd:element name="configuration" type="uo:configuration"/>
           <xsd:element name="SysStat" type="uo:SystemStatus"/>
           <xsd:element name="DT" type="uo:DateTime"/>
           <xsd:element name="SystemOptions" type="uo:SystemOptions"/>
           <xsd:element name="SMTPConfig" type="uo:SMTPConfiguration"/>
           <xsd:element name="DBG" type="uo:DBG"/>
           <xsd:element name="SceneProfiles" type="uo:SceneProfiles"/>
           <xsd:element name="SubscriptionResponse" type="uo:Subscription"/>
           <xsd:element name="LastError" type="uo:LastError"/>
           <xsd:element name="DDNSHost" type="uo:DDNSHost"/>
           <xsd:element name="Var" type="uo:Variable"/>
           <xsd:element name="Vars" type="uo:Variables"/>
       <!-- PATCH End: Abstract types with the element attribute -->


Then update the message parts to refer to these new elements= instead of type=  .
The following message parts with type attributes were changed to 'element':
  • <wsdl:part name="response" element="u:UDIDefaultResponse"/>
  • <wsdl:part name="timeResponse" element="u:UDITimeResponse"/>
  • <wsdl:part name="intResponse" element="u:UDIIntResponse"/>
  • <wsdl:part name="nodes" element="u:nodes"/>
  • <wsdl:part name="configuration" element="u:configuration"/>
  • <wsdl:part name="SysStat" element="u:SysStat"/>
  • <wsdl:part name="DT" element="u:DT"/>
  • <wsdl:part name="SystemOptions" element="u:SystemOptions"/>
  • <wsdl:part name="SMTPConfig" element="u:SMTPConfig"/>
  • <wsdl:part name="DBG" element="u:DBG"/>
  • <wsdl:part name="SceneProfiles" element="u:SceneProfiles"/>
  • <wsdl:part name="SubscriptionResponse" element="u:SubscriptionResponse"/>
  • <wsdl:part name="LastError" element="u:LastError"/>
  • <wsdl:part name="DDNSHost" element="u:DDNSHost"/>
  • <wsdl:part name="Var" element="u:Var"/>
  • <wsdl:part name="Vars" element="u:Vars"/>

This process should work on any version of the wsdl http://isy99/services.wsdl . Note the version of the wsdl that you get from your ISY is a small version that imports the main wsdl and defines the service and port. For ease, I suggest downloading the UDI WSDK and work on the local files contained in it.
  • My patched wsdl is attached below (udiws30_patched.wsdl)
    • Make sure you update the Endpoint URL, at the end of the file, to point at your ISY device.
  • If you want...  do a diff between the original udiws30.wsdl to be clear what changes were made

Import the updated udiws30_patched.wsdl file

Right click on the NetBeans project and click ‘New Web Service Client’
  • This will ask you for the location of the wsdl file
  • It will also ask for a package to put the resulting code
    • I used ca.bc.webarts.tools.isy.webservices

NetBeans will call the wsimport tool and automatically generate a bunch of new classes - one for each UDIServices service. You can then use/call them in your Java application.
See them along the left under Web Services References. See the Grey menu item named Generated Sources (jax-ws) - this is where all the generated Java classes reside.

Import the ELK WSDL file

The Elk web services definition file imports without any of these type/element errors!
Right click on the NetBeans project and click ‘New Web Service Client’ and point it to the udielkws1.wsdl file.

Write your Java Application

Now you can go back and write your code. Click on your Java app file in the Project/Source Packages . It should show an empty class.  Create a main method.  

Create the Service And Port

All of the services get called from the UDIServices and UDIServicesPortType so I created a class objects for these.

import ca.bc.webarts.tools.isy.webservices.UDIServices;

import ca.bc.webarts.tools.isy.webservices.UDIServicesPortType;

...


UDIServices service_ = new UDIServices(); // this classname originally comes from the wsdl file

UDIServicesPortType port_ = service_.getUDIServicesPort();


Note: your import package names will be different than my example.

HTTP Basic Authentication

The UDI ISY expects BASIC HTTP authentication that gets inserted into the HTTP header. This is easy in Java by getting the session context and adding the username/password to it.

private String isyUsername_ = "admin";

private String isyPassword_ = "yourPasswordHere";

...


/* These calls deal with hashing them into Base64 and putting them into the http header */

Map<String, Object> reqCtx = ((BindingProvider) instance.port_).getRequestContext();

reqCtx.put(BindingProvider.USERNAME_PROPERTY, instance.isyUsername_);

reqCtx.put(BindingProvider.PASSWORD_PROPERTY, instance.isyPassword_);


Call/consume Web Services

Netbeans has an easy code create feature that automatically creates the code to call your new web services.
In the editor window, right click and then click on Insert Code... and select call web service operation. a list off all the services come up, you choose which one and it will create the code to call it.  Now, the rest is the easy part because all the WebServices have been wrapped in Java methods. For example, to get the ISY Date and Time...

import ca.bc.webarts.tools.isy.webservices.DateTime;

import ca.bc.webarts.tools.isy.webservices.Empty;

    ...

DateTime dt = port_.getSystemDateTime(new Empty());

System.out.println("NTP time=" + dt.getNTP());


Sample Java App

Here (again) is my full, very simple, UDI/ISY Web Services Client.
package ca.bc.webarts.tools.isy;

/* These are all generated from the JAX-WS wsimport tool */
import ca.bc.webarts.tools.isy.webservices.DateTime;
import ca.bc.webarts.tools.isy.webservices.Empty;
import ca.bc.webarts.tools.isy.webservices.UDIServices;
import ca.bc.webarts.tools.isy.webservices.UDIServicesPortType;
import ca.bc.webarts.tools.isy.webservices.Variable;

import java.util.List;
import java.util.Map;
import javax.xml.ws.BindingProvider;

public class IsyWSClient
{
 private String isyUsername_ = "admin";
 private String isyPassword_ = "admin";
 private UDIServices service_ = new UDIServices();
 private UDIServicesPortType port_ = service_.getUDIServicesPort();

   public static void main(String[] args)
   {
       IsyWSClient instance = new IsyWSClient();

       Map<String, Object> reqCtx = ((BindingProvider) instance.port_).getRequestContext();
       reqCtx.put(BindingProvider.USERNAME_PROPERTY, instance.isyUsername_);
       reqCtx.put(BindingProvider.PASSWORD_PROPERTY, instance.isyPassword_);

       DateTime dt = instance.getSystemDateTime();
       System.out.println("NTP time=" + dt.getNTP());

       //List<Variable> isyVars = instance.getVariables(1);// 1=Integer Variable 2=State Variable
       //for (Variable variable : isyVars)
       //{
      //  System.out.println(variable.getId() + "=" + variable.getVal());
       //}
   }

   /**
    * gets a list of isy variables by type spec'd.
    * @param type 1=IntegerVariable 2=StateVariable
    * @return a list of vars
    */
   private List<Variable> getVariables(int type)
   {
     return port_.getVariables(type);
   }

   /**
    * Gets the ISY DateTime object.
    * @return the ISY DateTime object
    */
   private DateTime getSystemDateTime()
   {
     return port_.getSystemDateTime(new Empty());
 }
}

Let me know how it goes.
t u w n w b r s c
 g t i @ e a t . a

ċ
udiws30_patched.wsdl
(77k)
Tom Gutwin,
Dec 16, 2012, 7:27 PM
Comments