/** * 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(); } } }