/** * 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.heatmiser.internal; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.net.Socket; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; //import javax.xml.bind.DatatypeConverter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Heatmiser network communications connector. * Maintains the IP connection and reconnects on error. * If responses stop, the connection is reconnected. * * @author Chris Jackson * @since 1.4.0 */ public class HeatmiserConnector { private static final Logger logger = LoggerFactory.getLogger(HeatmiserConnector.class); private static List<HeatmiserEventListener> _listeners = new ArrayList<HeatmiserEventListener>(); private String ipAddress; private int ipPort; private Socket socket = null; private InputStream in = null; private OutputStream out = null; Thread inputThread = null; // The connectionStateCount is used to keep track of errors. It counts up by 1 for each message sent // and down by 2 for each message received. Thus if it ever gets "too high" then <50% of messages // are receiving a response. int connectionStateCount = 0; public enum States { SEARCHING, LENGTH1, LENGTH2, RECEIVING } public HeatmiserConnector() { } public void connect(String address, int port) throws IOException { ipAddress = new String(address); ipPort = port; doConnect(); } private void doConnect() throws IOException { try { socket = new Socket(ipAddress, ipPort); in = socket.getInputStream(); out = socket.getOutputStream(); } catch (UnknownHostException e) { logger.error("Can't find host: {}:{}.", ipAddress, ipPort); } catch (IOException e) { logger.error("Couldn't get I/O for the connection to: {}:{}.", ipAddress, ipPort); return; } inputThread = new InputReader(in); inputThread.start(); connectionStateCount = 0; } public void disconnect() { if (socket == null) { return; } logger.debug("Interrupt connection"); inputThread.interrupt(); logger.debug("Close connection"); try { out.close(); } catch (IOException e) { logger.error("Error closing Heatmiser connection: ", e.getMessage()); } socket = null; in = null; out = null; inputThread = null; logger.debug("Ready"); } /** * Sends a message * * @param data data to send */ public void sendMessage(byte[] data) { if (socket == null) { logger.debug("Heatmiser disconnected: Performing reconnect"); try { doConnect(); } catch (IOException e) { logger.error("Error reconnecting Heatmiser"); } } if (socket == null) { return; } // Increment the state counter by 1 connectionStateCount++; try { out.write(data); out.flush(); } catch (IOException e) { logger.error("HEATMISER: Error sending message " + e.getMessage()); disconnect(); } } public synchronized void addEventListener(HeatmiserEventListener listener) { _listeners.add(listener); } public synchronized void removeEventListener(HeatmiserEventListener listener) { _listeners.remove(listener); } /** * Data receive thread */ public class InputReader extends Thread { InputStream in; public InputReader(InputStream in) { this.in = in; } @Override public void interrupt() { super.interrupt(); try { in.close(); } catch (IOException e) { logger.error("Error reading Heatmiser connection: ", e.getMessage()); } // quietly close } @Override public void run() { final int dataBufferMaxLen = 256; byte[] dataBuffer = new byte[dataBufferMaxLen]; int msgLen = 0; int index = 0; States state = States.SEARCHING; try { byte[] tmpData = new byte[150]; int len = -1; while ((len = in.read(tmpData)) > 0) { for (int i = 0; i < len; i++) { if (index >= dataBufferMaxLen) { // too many bytes received, try to find new start state = States.SEARCHING; } if (state == States.SEARCHING && (tmpData[i] & 0xff) == 0x81) { state = States.LENGTH1; index = 0; dataBuffer[index++] = tmpData[i]; } else if (state == States.LENGTH1) { state = States.LENGTH2; msgLen = tmpData[i]; dataBuffer[index++] = tmpData[i]; } else if (state == States.LENGTH2) { state = States.RECEIVING; msgLen += tmpData[i] * 256; dataBuffer[index++] = tmpData[i]; } else if (state == States.RECEIVING) { dataBuffer[index++] = tmpData[i]; if (index == msgLen) { // whole message received, send an event byte[] msg = new byte[msgLen]; for (int j = 0; j < msgLen; j++) { msg[j] = dataBuffer[j]; } HeatmiserResponseEvent event = new HeatmiserResponseEvent(this); // Decrement the state counter by 2 if (connectionStateCount <= 2) { connectionStateCount = 0; } else { connectionStateCount -= 2; } try { Iterator<HeatmiserEventListener> iterator = _listeners.iterator(); while (iterator.hasNext()) { iterator.next().packetReceived(event, msg); } } catch (Exception e) { logger.error("Event listener error", e); } // find new start state = States.SEARCHING; } } } } } catch (InterruptedIOException e) { Thread.currentThread().interrupt(); logger.error("Interrupted via InterruptedIOException"); } catch (IOException e) { logger.error("Reading from network failed", e); } logger.debug("Ready reading from network"); } } }