org.openhab.binding.insteonplm.internal.driver.hub.HubIOStream.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.insteonplm.internal.driver.hub.HubIOStream.java

Source

/**
 * Copyright (c) 2010-2016 by the respective copyright holders.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package org.openhab.binding.insteonplm.internal.driver.hub;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.ByteBuffer;

import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.util.EntityUtils;
import org.openhab.binding.insteonplm.internal.driver.IOStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Implements IOStream for a Hub 2014 device
 * 
 * @author Daniel Pfrommer
 * @since 1.7.0
 *
 */
public class HubIOStream extends IOStream implements Runnable {
    private static final Logger logger = LoggerFactory.getLogger(HubIOStream.class);

    /** time between polls (in milliseconds */
    private int m_pollTime = 1000;

    private String m_host = null;
    private int m_port = -1;
    private String m_user = null;
    private String m_pass = null;

    private DefaultHttpClient m_client = null;
    private Thread m_pollThread = null;

    // index of the last byte we have read in the buffer
    private int m_bufferIdx = -1;

    /**
     * Constructor for HubIOStream
     * 
     * @param host host name of hub device
     * @param port port to connect to
     * @param pollTime time between polls (in milliseconds)
     * @param user hub user name
     * @param pass hub password
     */
    public HubIOStream(String host, int port, int pollTime, String user, String pass) {
        m_host = host;
        m_port = port;
        m_pollTime = pollTime;
        m_user = user;
        m_pass = pass;
    }

    @Override
    public boolean open() {
        m_client = new DefaultHttpClient();
        if (m_user != null && m_pass != null) {
            m_client.getCredentialsProvider().setCredentials(new AuthScope(m_host, m_port),
                    new UsernamePasswordCredentials(m_user, m_pass));
        }
        HttpConnectionParams.setConnectionTimeout(m_client.getParams(), 5000);

        m_in = new HubInputStream();

        m_pollThread = new Thread(this);
        m_pollThread.start();

        m_out = new HubOutputStream();
        return true;
    }

    @Override
    public void close() {
        m_pollThread.interrupt();
        m_client = null;

        try {
            m_in.close();
            m_out.close();
        } catch (IOException e) {
            logger.error("failed to close streams", e);
        }
    }

    /**
     * Fetches the latest status buffer from the Hub
     * 
     * @return string with status buffer
     * @throws IOException
     */
    private synchronized String bufferStatus() throws IOException {
        String result = getURL("/buffstatus.xml");
        String[] parts = result.split("<BS>");
        if (parts.length > 1) {
            result = parts[1].split("</BS>")[0].trim();
        } else if (result.startsWith("401 Unauthorized:")) {
            logger.error("bad username or password. See bottom label of hub for correct login");
            throw new IOException("login credentials incorrect");
        } else {
            logger.error("got invalid buffer status: {}", result);
            throw new IOException("malformed bufferstatus.xml");
        }
        return result;
    }

    /**
     * Sends command to Hub to clear the status buffer
     * 
     * @throws IOException
     */
    private synchronized void clearBuffer() throws IOException {
        getURL("/1?XB=M=1");
        m_bufferIdx = 0;
    }

    /**
     * Sends Insteon message (byte array) as a readable ascii string to the Hub
     * 
     * @param msg byte array representing the Insteon message
     * @throws IOException in case of I/O error
     */
    public synchronized void write(ByteBuffer msg) throws IOException {
        poll(); // fetch the status buffer before we send out commands
        clearBuffer(); // clear the status buffer explicitly.

        StringBuilder b = new StringBuilder();
        while (msg.remaining() > 0) {
            b.append(String.format("%02x", msg.get()));
        }
        String hexMSG = b.toString();
        getURL("/3?" + hexMSG + "=I=3");
    }

    /**
     * Polls the Hub web interface to fetch the status buffer
     * 
     * @throws IOException if something goes wrong with I/O
     */
    public synchronized void poll() throws IOException {
        String buffer = bufferStatus(); // fetch via http call
        logger.trace("poll: {}", buffer);
        //
        // The Hub maintains a ring buffer where the last two digits (in hex!) represent
        // the position of the last byte read.
        //
        String data = buffer.substring(0, buffer.length() - 2); // pure data w/o index pointer

        int nIdx = -1;
        try {
            nIdx = Integer.parseInt(buffer.substring(buffer.length() - 2, buffer.length()), 16);
        } catch (NumberFormatException e) {
            m_bufferIdx = -1;
            logger.error("invalid buffer size received in line: {}", buffer);
            return;
        }

        if (m_bufferIdx == -1) {
            // this is the first call or first call after error, no need for buffer copying
            m_bufferIdx = nIdx;
            return; // XXX why return here????
        }

        StringBuilder msg = new StringBuilder();
        if (nIdx < m_bufferIdx) {
            msg.append(data.substring(m_bufferIdx + 1, data.length()));
            msg.append(data.substring(0, nIdx));
            logger.trace("wrap around: copying new data on: {}", msg.toString());
        } else {
            msg.append(data.substring(m_bufferIdx, nIdx));
            logger.trace("no wrap:      appending new data: {}", msg.toString());
        }
        if (msg.length() != 0) {
            ByteBuffer buf = ByteBuffer.wrap(s_hexStringToByteArray(msg.toString()));
            ((HubInputStream) m_in).handle(buf);
        }
        m_bufferIdx = nIdx;
    }

    /**
     * Helper method to fetch url from http server
     * 
     * @param resource the url
     * @return contents returned by http server
     * @throws IOException
     */
    private String getURL(String resource) throws IOException {
        synchronized (m_client) {
            StringBuilder b = new StringBuilder();
            b.append("http://");
            b.append(m_host);
            if (m_port != -1) {
                b.append(":").append(m_port);
            }
            b.append(resource);

            HttpGet get = new HttpGet(b.toString());
            HttpResponse res = m_client.execute(get);
            String html = EntityUtils.toString(res.getEntity());
            return html;
        }
    }

    /**
     * Entry point for thread
     */
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                poll();
            } catch (IOException e) {
                logger.error("got exception while polling: {}", e.toString());
            }
            try {
                Thread.sleep(m_pollTime);
            } catch (InterruptedException e) {
                break;
            }
        }
    }

    /**
     * Helper function to convert an ascii hex string (received from hub)
     * into a byte array
     * 
     * @param s string received from hub
     * @return simple byte array
     */
    public static byte[] s_hexStringToByteArray(String s) {
        return new BigInteger(s, 16).toByteArray();
    }

    /**
     * Implements an InputStream for the Hub 2014
     * 
     * @author Daniel Pfrommer
     *
     */
    public class HubInputStream extends InputStream {

        // A buffer to keep bytes while we are waiting for the inputstream to read
        private ReadByteBuffer m_buffer = new ReadByteBuffer(1024);

        public HubInputStream() {
        }

        public void handle(ByteBuffer buffer) throws IOException {
            // Make sure we cleanup as much space as possible
            m_buffer.makeCompact();
            m_buffer.add(buffer.array());
        }

        @Override
        public int read() throws IOException {
            return m_buffer.get();
        }

        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            return m_buffer.get(b, off, len);
        }

        @Override
        public void close() throws IOException {
        }
    }

    /**
     * Implements an OutputStream for the Hub 2014
     * 
     * @author Daniel Pfrommer
     *
     */
    public class HubOutputStream extends OutputStream {
        private ByteArrayOutputStream m_out = new ByteArrayOutputStream();

        @Override
        public void write(int b) {
            m_out.write(b);
            flushBuffer();
        }

        @Override
        public void write(byte[] b, int off, int len) {
            m_out.write(b, off, len);
            flushBuffer();
        }

        private void flushBuffer() {
            ByteBuffer buffer = ByteBuffer.wrap(m_out.toByteArray());
            try {
                HubIOStream.this.write(buffer);
            } catch (IOException e) {
                logger.error("failed to write to hub: {}", e.toString());
            }
            m_out.reset();
        }
    }
}