/** * 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.wr3223.internal; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang.ArrayUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Base implementation for the connector to a wr3223 device. * * @author Michael Fraefel * */ public abstract class AbstractWR3223Connector { private static final Logger logger = LoggerFactory.getLogger(AbstractWR3223Connector.class); /** * Start der Nachricht */ private static byte STX = 0x02; /** * Ende der Nachricht */ private static byte ETX = 0x03; /** * Ende der Übertragung */ private static byte EOT = 0x04; /** * Anfrage / Anforderung */ private static byte ENQ = 0x05; /** * Positive Rückmeldung */ private static byte ACK = 0x06; /** * Negative Rückmeldung */ private static byte NAK = 0x15; /** * Data stream to the WR3223 */ private DataInputStream inputStream; /** * Data stream from the WR3223 */ private DataOutputStream outputStream; /** * Connect to the WR3223 * * @param inputStream * @param outputStream */ protected void connect(DataInputStream inputStream, DataOutputStream outputStream) { this.inputStream = inputStream; this.outputStream = outputStream; } /** * Close the connection to the WR3223 * * @throws IOException */ protected void close() throws IOException { if (inputStream != null) { inputStream.close(); } if (outputStream != null) { outputStream.close(); } } /** * Read data from the WR3223 controller * * @param addr Controller address * @param command Command * @return The received value. * @throws IOException */ public String read(int addr, WR3223Commands command) throws IOException { // Remove old data from the input stream if (inputStream.available() > 0) { inputStream.skipBytes(inputStream.available()); } // Write read message to the controller byte[] message = new byte[8]; message[0] = EOT; setControllerAddressToMessage(message, addr, 1); setCommandToMessage(message, command, 5); message[7] = ENQ; outputStream.write(message); outputStream.flush(); if (logger.isDebugEnabled()) { logger.debug("Write data: {}", bytesToHexString(message)); } // Read answer from controller byte[] answer = readAnswer(); int chkSum = inputStream.read(); if (logger.isDebugEnabled()) { logger.debug("Read data: {} with checksum {}.", bytesToHexString(answer), Integer.toHexString(chkSum)); } // Check answer if (answer[0] == STX && answer[answer.length - 1] == ETX) { if (chkSum == buildCheckSum(answer, 1, answer.length - 1)) { if (command.name().equals(toString(answer, 1, 2))) { return toString(answer, 3, answer.length - 2); } else { logger.error("Wrong command received. Expected {} but got {}.", command.name(), toString(answer, 1, 2)); } } else { logger.error("Checksum error. Expected {} but got {}.", chkSum, buildCheckSum(answer, 1, answer.length - 1)); } } else { logger.error("Start/end of the controller answer is wrong."); } return null; } public boolean write(int addr, WR3223Commands command, String data) throws IOException { // Check if the provided data not longer then 6 characters. if (data == null || data.length() > 6 || data.length() == 0) { throw new IllegalArgumentException("Not valid data format."); } // Remove old data from the input stream if (inputStream.available() > 0) { inputStream.skipBytes(inputStream.available()); } // Write command to the controller byte[] message = new byte[10 + data.length()]; message[0] = EOT; setControllerAddressToMessage(message, addr, 1); message[5] = STX; setCommandToMessage(message, command, 6); byte[] d = data.getBytes(); for (int ii = 0; ii < data.length(); ii++) { message[8 + ii] = d[ii]; } message[message.length - 2] = ETX; byte chkSum = (byte) buildCheckSum(message, 6, message.length - 2); message[message.length - 1] = chkSum; outputStream.write(message); outputStream.flush(); if (logger.isDebugEnabled()) { logger.debug("Write data: {}", bytesToHexString(message)); } // Read controller answer if (waitUntilDataAvailable(1, 5000l)) { int answer = inputStream.readByte(); if (logger.isDebugEnabled()) { logger.debug("Answer from WR3223 {}.", Integer.toHexString(answer)); } if (answer == ACK) { return true; } logger.error("Command {} with data {} not accepted.", command.name(), data); } else { logger.error("Timeout. No answer for command {} with data {}.", command.name(), data); } return false; } /** * Wait until the specified timeout of data are available or the timout is reached. * * @param dataCount * @param timeout * @throws IOException */ private boolean waitUntilDataAvailable(int dataCount, long timeout) throws IOException { try { long startTime = System.currentTimeMillis(); long duration = 0l; Thread.sleep(100l); while (inputStream.available() < dataCount && duration < timeout) { if (logger.isDebugEnabled()) { logger.debug("Wait for answer from WR3223. Timeout in {}ms.", timeout - duration); } Thread.sleep(500l); duration = (System.currentTimeMillis() - startTime); } } catch (InterruptedException e) { logger.error("Waiting for WR3223 was interrupted.", e); } return inputStream.available() >= dataCount; } /** * Read bytes from input stream until ETX. * * @return The extracted answer. * @throws IOException */ private byte[] readAnswer() throws IOException { List<Byte> answerList = new ArrayList<Byte>(); Byte val; do { val = inputStream.readByte(); answerList.add(val); } while (val != ETX); byte[] answer = ArrayUtils.toPrimitive(answerList.toArray(new Byte[0])); return answer; } /** * Set the command to the message. * * @param message * @param command * @param offset */ private void setCommandToMessage(byte[] message, WR3223Commands command, int offset) { byte[] commandByte = command.name().getBytes(); message[offset] = commandByte[0]; message[offset + 1] = commandByte[1]; } /** * Set the controller address to the message. * * @param message * @param addr * @param offset */ private void setControllerAddressToMessage(byte[] message, int addr, int offset) { if (addr > 99) { throw new IllegalArgumentException("The address must be between 1 and 99."); } String addrStr = String.valueOf(addr); if (addrStr.length() == 1) { addrStr = "0" + addrStr; } byte[] addrByte = addrStr.getBytes(); message[offset] = addrByte[0]; message[offset + 1] = addrByte[0]; message[offset + 2] = addrByte[1]; message[offset + 3] = addrByte[1]; } /** * Answer to string * * @param answer as bytes * @param start * @param end * @return the answer as string value. */ private String toString(byte[] answer, int start, int end) { String data = ""; for (int ii = start; ii <= end; ii++) { char ch = (char) answer[ii]; data = data + ch; } return data; } /** * Build the checksum. * * @param answer * @param start * @param end * @return checksum of the answer byte array. */ private int buildCheckSum(byte[] answer, int start, int end) { int chkSum = answer[start]; for (int i = start + 1; i <= end; i++) { chkSum = chkSum ^ answer[i]; } return chkSum; } /** * Convert the bytes to a hex string for debug messages. * * @param data * @return */ private String bytesToHexString(byte[] data) { StringBuilder sb = new StringBuilder(); sb.append("["); for (int ii = 0; ii < data.length; ii++) { String asHex = Integer.toHexString(data[ii]); if (asHex.length() == 1) { sb.append("0"); } sb.append(asHex); if (ii + 1 < data.length) { sb.append(" "); } } sb.append("]"); return sb.toString(); } }