An introduction to the URL programming interface

Create ubiquitous controlled access to devices using UPI

1 2 3 Page 3
Page 3 of 3

GenericX10Handler

/**
 * Generic Support for simple X10 devices.
 * X10 is a simple powerline control protocol.  This handler maps
 * URLs into X10 commands, allowing home automation control
 * from web pages via standard browsers.  See
 * <a href=ftp://ftp.scruz.net/users/cichlid/public/x10faq>
 * the X10 FAQ</a> for details of the X10 protocol.
 * <p>
 * We define the X10 commands as a set of three character ASCII strings:
 * a single character house code, followed by a 2 digit command code.
 * <p>
 * Different X10 control modules have different mechanisms for interfacing
 * to the powerline.  This abstract class encapulates the generic URL to
 * X10 mapping.  Concrete classes are expected to take the X10 mapping,
 * and do what is needed to communicate that command to a particular
 * device.
 * <p>
 * The handler accepts 2 kinds of X10 commands:
 * <dl>
 * <dt>SendX10?NNN      <dd>Sends the X10 command NNN
 * <dt>Notify           <dd>blocks until a command is seen on the
 * powerline, then returns it (in NNN format)
 * </dl>
 * For each instance of this handler,
 * we set mutexes around each command type, to insure only one HTTP
 * request is pending for each type at one time.
 * <p>
 * This handler looks for the following server properties:
 * <dl>
 * <dt>device   <dd>The name of the serial port (defaults to /dev/cua/a)
 * <dt>prefix   <dd>The url prefix
 * </dl>
 * @author      Rinaldo DiGiorgio
 * @author      Colin Stevens
 * @author      Stephen Uhler
 * @version     1.0, 09/16/1998
 */
// {@name class}

public abstract class GenericX10Handler implements Handler, Runnable {
    Server server;                                      // our server reference
    String propsPrefix;                                 // our prefix into the server properties
    public static final String DEVICE = 
        "device";                                       // the serial port to talk x10 to
    public static final String PREFIX = "prefix";       // out url prefix
    private static final int MAX_Q = 50;                // max Q size

    // {@name  mutex}

    /**
     * Create mutex objects, one for each command type;
     */
    private final Object sendMutex = new Object();
    private final Object notifyMutex = new Object();
    Vector queue = new Vector();        // The X10 response Q for Notify requests
    static Vector cmdList = new Vector();               // List of valid X10 commands

    // {@name commands}

    /**
     * The list of valid X10 commands.  This defines the command interface to
     * X10, and is subject to change!
     */
    static {
        cmdList.addElement("01");
        cmdList.addElement("02");
        cmdList.addElement("03");
        cmdList.addElement("04");
        cmdList.addElement("05");
        cmdList.addElement("06");
        cmdList.addElement("07");
        cmdList.addElement("08");
        cmdList.addElement("09");
        cmdList.addElement("10");
        cmdList.addElement("11");
        cmdList.addElement("12");
        cmdList.addElement("13");
        cmdList.addElement("14");
        cmdList.addElement("15");
        cmdList.addElement("16");
        cmdList.addElement("A0");
        cmdList.addElement("BR");
        cmdList.addElement("DI");
        cmdList.addElement("HR");
        cmdList.addElement("L0");
        cmdList.addElement("L1");
        cmdList.addElement("OF");
        cmdList.addElement("ON");
        cmdList.addElement("SR");
    }
    // {@name run}

    /**
     * Listen for asynchronous powerline commands.
     * Start a background thread to listen for commands arriving
     * asynchronously from the powerline, and put them on a Queue.
     */
    public void run() {
        while (true) {
            try {
                String s = getCommand();
                synchronized (queue) {
                    queue.addElement(s);
                    if (queue.size() > MAX_Q) {
                        log("Q too big");
                        queue.removeElementAt(0);
                    }
                    queue.notifyAll();
                }
            } catch (IOException e) {
            // ignore

            }
        }
    }
    // {@name init}

    /**
     * Make sure we can communicate with the X10 device, and start the
     * background thread.
     */
    public boolean init(Server server, String prefix) {
        propsPrefix = prefix;
        this.server = server;
        String deviceName = server.props.getProperty(propsPrefix + DEVICE, 
                "/dev/cua/a");
        if (!open(deviceName)) {
            log("Failed to open " + deviceName);
            return false;
        } else {
            log("Opening " + deviceName);
            Thread listener = new Thread(this);
            listener.setDaemon(true);
            listener.start();
            return true;
        }
    }
    /**
     * Get the sub-command name.  X10 url's have the form:
     * urlPrefix/command?x10
     * where:
     * <dl>
     * <dt>urlPrefix    <dd> the url prefix, such as /X10/
     * <dt>command      <dd> the command class, either "SendX10" or "Notify"
     * <dt>x10  <dd> the command to send to the powerline
     * </dl>
     * <pre>
     * Example:  /X10/SendX10?AON turns the selected unit for
     * house code "A" on.
     * </pre>
     * 
     * @param request       the http request
     * @return              true - request was completely handled
     */
    // {@name respond}

    public boolean respond(Request request) throws IOException {
        String urlPrefix = request.props.getProperty(propsPrefix + PREFIX, 
                "/X10/");
        if (!request.url.startsWith(urlPrefix)) {
            log("Not my prefix: " + request.url);
            return false;
        }
        // {@name parse}

        StringTokenizer fields = new StringTokenizer(request.url.substring(1), 
                "/");
        String command;     // our X10 command class

        try {
            fields.nextToken();     // skip the prefix

            command = fields.nextToken();
        } catch (NoSuchElementException e) {
            request.sendError(404, "No X10 command supplied");
            return true;
        }
        if (fields.hasMoreTokens()) {
            request.sendError(404, "Sub-commands are not supported");
            return true;
        }
        log("Args=" + request.query + " Command=" + command);
        // {@name send}

        /*
         * Check the command for validity, and send it to the powerline.
         */
        if (command.equals("SendX10")) {
            synchronized (sendMutex) {
                String query = request.query.toUpperCase();
                char houseCode = query.charAt(0);
                if (query.length() != 3) {
                    request.sendError(404, 
                                      "Invalid Query length (must be 3)");
                } else if (houseCode < 'A' || houseCode > 'P') {
                    request.sendError(404, "Invalid housecode, not A-P");
                } else if (!cmdList.contains(query.substring(1))) {
                    request.sendError(404, "Invalid cmd, try: " + cmdList);
                } else {
                    sendCommand(query);
                    request.sendResponse("ok=" + query, "text/plain");
                }
            }
        // {@name notify}

        /*
         * Wait until a command appears on the powerline,
         * then deliver it.
         */
        } else if (command.equals("Notify")) {
            synchronized (notifyMutex) {
                try {
                    synchronized (queue) {
                        while (queue.size() == 0) {
                            queue.wait();
                        }
                        request.sendResponse((String) queue.elementAt(0), 
                                             "text/plain");
                        queue.removeElementAt(0);
                    }
                } catch (InterruptedException e) {
                    request.sendError(500, e.toString());
                }
            }
        } else {
            request.sendError(404, "Invalid command: " + command);
        }
        return (true);
    }
    // {@name log}

    /**
     * Convenience class for logging server messages.
     * @param message   The message to log on the console
     */
    protected void log(String message) {
        server.log(null, Server.LOG_INFORMATIONAL, 
                   propsPrefix + " :" + message);
    }
    // {@name abstract}

    /**
     * Open the X10 device.
     * An attempt should be made to communicate with the device .
     * This is called once for each device (in init).  The implementation may
     * consult with this.Server and this.propsPrefix if any device
     * specific info needs to be extracted from the server properties.
     * @params          deviceName:  The name of the X10 device to open
     * @returns         true, if the X10 device is responding properly
     */
    protected abstract boolean open(String deviceName);
    /**
     * Close the X10 device
     * Currently, this is never called.
     */
    protected abstract void close();
    /**
     * Return an asynchronous powerline command.
     * This is expected to block until a command is read from the
     * powerline.  The 3-letter (2-way style) X10 command is returned.
     * This is called repeatedly by the handler if any clients are
     * waiting for notification.  It normally blocks waiting for
     * input from the X10 device controller.
     * 
     * @return  The 3 letter X10 command
     */
    protected abstract String getCommand() throws IOException;
    /**
     * Send an X10 command to the powerline.
     * Every reaonable attempt (e.g. retries) should be made to actually
     * deliver the command successfully.
     * @param command   The 3-letter X10 command to send to the
     * powerline (in 2-way format)
     */
    protected abstract void sendCommand(String command) throws IOException;
}

TwoWayHandler.java

This second file is the implementation of the interface required to support a specific interface to the power line, like a TW523. The documentation contains pointers to more information.

package sunlabs.brazil.x10;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.comm.CommPortIdentifier;
import javax.comm.SerialPort;
/**
 * Two-way X10 protocol handler for the X10 PowerHouse TW523.
 * Implement a handler for the PowerHouse TwoWay X10 to TW523 device.
 * See the <a href=http://www.smarthome.com/1135f.html> PowerHouse
 * </a> site for more details.
 * <p>
 * This communicates with the device using javax.comm over a serial port.
 * @author      Rinaldo DiGiorgio
 * @author      Colin Stevens
 * @author      Stephen Uhler
 * @version     %V% 99/08/06
 */
public class TwoWayHandler extends GenericX10Handler {
    OutputStream output;            // output to serial port
    DataInputStream input;          // input from serial port
    SerialPort port;                // the serial port the 2way is on.
    byte[] data = new byte[4];      // The 2way command output buffer

    /**
     * Open the 2way device Make sure we can talk to it.
     *
     * @param name Port to Open.
     *
     * @return success or failure
     *
     * @see
     */
    protected boolean open(String name) {
        try {
            CommPortIdentifier id =
                CommPortIdentifier.getPortIdentifier(name);
            port = (SerialPort) id.open("X10SerialInterface", 2000);
            port.setSerialPortParams(2400, SerialPort.DATABITS_8,
                                     SerialPort.STOPBITS_2,
                                     SerialPort.PARITY_NONE);
            port.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
            input = new DataInputStream(port.getInputStream());
            output = port.getOutputStream();
            /*
             * Try to communicate with the device
             */
            byte[] send = {
                (byte) '\r'
            };
            byte[] recv = new byte[2];
            port.enableReceiveTimeout(2000);
            for (int i = 1; i < 4; i++) {
                log("Sent Hello to " + name + " (" + i + ")");
                output.write(send);
                output.flush();
                int got = input.read(recv);
                log("Got " + got + " bytes");
                if (got >= 1 && recv[0] == '?') {
                    log("2-way OK");
                    port.enableReceiveTimeout(20000);       // could block forever

                    return true;
                }
            }
        } catch (Exception e) {
            // javax.comm.NoSuchPortException
            // javax.comm.PortInUseException
            // javax.comm.UnsupportedCommOperationException
            // java.io.IOException

            log("Error contacting " + name + " " + e);
        }
        log("Can't talk to " + name);
        return false;
    }
    /**
     * Close the port
     *
     */
    protected void close() {
        port.close();
    }
    /**
     * Send an X10 command to the 2-way.  The Command is almost already
     * in 2-way format, we just need to add a \r.
     *
     * @param command a command like set HouseCode or Unit Code
     *
     * @exception IOException
     *
     */
    protected void sendCommand(String command) throws IOException {
        command += "\r";
        command.getBytes(0, 4, data, 0);
        output.write(data);
        output.flush();
        /*
         * The 2-way loses data if you talk to it too fast
         */
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            log("Sleep interrpted: " + e);
        }
    }
    /**
     * Read data from the powerline.
     *
     * @return the command form the Power line interface
     *
     * @exception IOException
     *
     * @see
     */
    protected String getCommand() throws IOException {
        while (true) {
            String s = input.readLine();
            if (s != null) {
                log("From PL: " + s);
            }
            if (s != null && s.length() > 0 && s.charAt(0) > 31) {
                return (s.substring(1));
            }
        }
    }
}

Conclusion

We have shown how to interface three different devices to the Web using the UPI approach, and we have shown that UPI complements Jini. In the introduction, we claimed that the UPI technology is easier, cheaper, and more malleable than current proprietary approaches. In the article, we also discussed some very simple handlers that can easily be expanded to support more features, such as providing the average temperature in a home, or the status of all devices on the network. These handlers can be used by folks having experience only with JavaScript and HTML, as well as by Java programmers. The UPI approach requires a minimal footprint, neutralizes browser differences, and includes support of security models.

Rinaldo Di Giorgio has been involved in the creation of a smart card URL programming interface that integrates OpenCard with the Web. He is also working on a Jini-aware Java Card infrastructure and technology for easily interfacing other devices such as X10 and weather stations to the Web. Di Giorgio has been working with Java Card technology for three years, is currently writing a book on Java Card, and is a regular column author for JavaWorld.

Learn more about this topic

  • X10What is it and how does it work? Some useful URLS:

1 2 3 Page 3
Page 3 of 3