/**
* 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.stiebelheatpump.internal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.bind.DatatypeConverter;
import org.joda.time.DateTime;
import org.openhab.binding.stiebelheatpump.protocol.DataParser;
import org.openhab.binding.stiebelheatpump.protocol.ProtocolConnector;
import org.openhab.binding.stiebelheatpump.protocol.RecordDefinition;
import org.openhab.binding.stiebelheatpump.protocol.RecordDefinition.Type;
import org.openhab.binding.stiebelheatpump.protocol.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CommunicationService {
private ProtocolConnector connector;
private static final int MAXRETRIES = 100;
private final int INPUT_BUFFER_LENGTH = 1024;
private byte buffer[] = new byte[INPUT_BUFFER_LENGTH];
public static int WAITING_TIME_BETWEEN_REQUESTS = 2000;
/** heat pump request definition */
private List<Request> heatPumpConfiguration = new ArrayList<Request>();
private List<Request> heatPumpSensorConfiguration = new ArrayList<Request>();
private List<Request> heatPumpSettingConfiguration = new ArrayList<Request>();
private List<Request> heatPumpStatusConfiguration = new ArrayList<Request>();
Request versionRequest;
DataParser parser = new DataParser();
private static final Logger logger = LoggerFactory.getLogger(CommunicationService.class);
public CommunicationService(ProtocolConnector connector) {
this.connector = connector;
this.connector.connect();
}
public CommunicationService(ProtocolConnector connector, List<Request> configuration) {
this(connector);
heatPumpConfiguration = configuration;
categorizeHeatPumpConfiguration();
}
public void finalizer() {
connector.disconnect();
}
/**
* This method reads the version information from the heat pump
*
* @return version string, e.g: 2.06
*/
public String getversion() throws StiebelHeatPumpException {
String version = "";
try {
Map<String, String> data = readData(versionRequest);
version = data.get("Version");
Thread.sleep(WAITING_TIME_BETWEEN_REQUESTS);
} catch (InterruptedException e) {
throw new StiebelHeatPumpException(e.toString());
}
return version;
}
/**
* This method reads all settings defined in the heat pump configuration
* from the heat pump
*
* @return map of heat pump setting values
*/
public Map<String, String> getSettings() throws StiebelHeatPumpException {
logger.debug("Loading Settings");
Map<String, String> data = new HashMap<String, String>();
for (Request request : heatPumpSettingConfiguration) {
logger.debug("Loading data for request {} ...", request.getName());
try {
Map<String, String> newData = readData(request);
data.putAll(newData);
Thread.sleep(WAITING_TIME_BETWEEN_REQUESTS);
} catch (InterruptedException e) {
throw new StiebelHeatPumpException(e.toString());
}
}
return data;
}
/**
* This method reads all sensor values defined in the heat pump
* configuration from the heat pump
*
* @return map of heat pump sensor values
*/
public Map<String, String> getSensors() throws StiebelHeatPumpException {
logger.debug("Loading Sensors");
Map<String, String> data = new HashMap<String, String>();
for (Request request : heatPumpSensorConfiguration) {
logger.debug("Loading data for request {} ...", request.getName());
try {
Map<String, String> newData = readData(request);
data.putAll(newData);
Thread.sleep(WAITING_TIME_BETWEEN_REQUESTS);
} catch (InterruptedException e) {
throw new StiebelHeatPumpException(e.toString());
}
}
return data;
}
/**
* This method reads all status values defined in the heat pump
* configuration from the heat pump
*
* @return map of heat pump status values
*/
public Map<String, String> getStatus() throws StiebelHeatPumpException {
logger.debug("Loading Status");
Map<String, String> data = new HashMap<String, String>();
for (Request request : heatPumpStatusConfiguration) {
logger.debug("Loading data for request {} ...", request.getName());
try {
Map<String, String> newData = readData(request);
data.putAll(newData);
Thread.sleep(WAITING_TIME_BETWEEN_REQUESTS);
} catch (InterruptedException e) {
throw new StiebelHeatPumpException(e.toString());
}
}
return data;
}
/**
* This method set the time of the heat pump to the current time
*
* @return true if time has been updated
*/
public Map<String, String> setTime() throws StiebelHeatPumpException {
startCommunication();
Map<String, String> data = new HashMap<String, String>();
Request timeRequest = null;
for (Request request : heatPumpSettingConfiguration) {
if (request.getName().equals("Time")) {
timeRequest = request;
break;
}
}
if (timeRequest == null) {
logger.warn("Could not find request definition for time settings! Skip setting time.");
return data;
}
logger.debug("Loading current time data ...");
try {
// get time from heat pump
byte[] requestMessage = createRequestMessage(timeRequest);
byte[] response = getData(requestMessage);
// get current time from local machine
DateTime dt = DateTime.now();
logger.debug("Current time is : {}", dt.toString());
String weekday = Integer.toString(dt.getDayOfWeek() - 1);
String day = Integer.toString(dt.getDayOfMonth());
String month = Integer.toString(dt.getMonthOfYear());
String year = Integer.toString(dt.getYearOfCentury());
String seconds = Integer.toString(dt.getSecondOfMinute());
String hours = Integer.toString(dt.getHourOfDay());
String minutes = Integer.toString(dt.getMinuteOfHour());
data = parser.parseRecords(response, timeRequest);
boolean updateRequired = false;
for (Map.Entry<String, String> entry : data.entrySet()) {
String entryName = entry.getKey();
String entryValue = entry.getValue();
RecordDefinition currentRecord = null;
for (RecordDefinition record : timeRequest.getRecordDefinitions()) {
if (record.getName().equals(entryName)) {
currentRecord = record;
break;
}
}
if (entryName.equals("WeekDay") && !entryValue.equals(weekday)) {
updateRequired = true;
response = parser.composeRecord(weekday, response, currentRecord);
logger.debug("WeekDay needs update from {} to {}", entryValue, weekday);
continue;
}
if (entryName.equals("Hours") && !entryValue.equals(hours)) {
updateRequired = true;
response = parser.composeRecord(hours, response, currentRecord);
logger.debug("Hours needs update from {} to {}", entryValue, hours);
continue;
}
if (entryName.equals("Minutes") && !entryValue.equals(minutes)) {
updateRequired = true;
response = parser.composeRecord(minutes, response, currentRecord);
logger.debug("Minutes needs update from {} to {}", entryValue, minutes);
continue;
}
if (entryName.equals("Seconds") && !entryValue.equals(seconds)) {
updateRequired = true;
response = parser.composeRecord(seconds, response, currentRecord);
logger.debug("Seconds needs update from {} to {}", entryValue, seconds);
continue;
}
if (entryName.equals("Year") && !entryValue.equals(year)) {
updateRequired = true;
response = parser.composeRecord(year, response, currentRecord);
logger.debug("Year needs update from {} to {}", entryValue, year);
continue;
}
if (entryName.equals("Month") && !entryValue.equals(month)) {
updateRequired = true;
response = parser.composeRecord(month, response, currentRecord);
logger.debug("Month needs update from {} to {}", entryValue, month);
continue;
}
if (entryName.equals("Day") && !entryValue.equals(day)) {
updateRequired = true;
response = parser.composeRecord(day, response, currentRecord);
logger.debug("Day needs update from {} to {}", entryValue, day);
continue;
}
}
if (updateRequired) {
Thread.sleep(WAITING_TIME_BETWEEN_REQUESTS);
logger.info("Time need update. Set time to " + dt.toString());
setData(response);
Thread.sleep(WAITING_TIME_BETWEEN_REQUESTS);
response = getData(requestMessage);
data = parser.parseRecords(response, timeRequest);
dt = DateTime.now();
logger.debug("Current time is : {}", dt.toString());
}
return data;
} catch (InterruptedException e) {
throw new StiebelHeatPumpException(e.toString());
}
}
/**
* This method looks up all files in resource and List of Request objects
* into xml file
*
* @return true if heat pump configuration for version could be found and
* loaded
*/
public List<Request> getHeatPumpConfiguration(String configFile) {
ConfigLocator configLocator = new ConfigLocator(configFile);
heatPumpConfiguration = configLocator.getConfig();
if (heatPumpConfiguration != null && !heatPumpConfiguration.isEmpty()) {
logger.info("Loaded heat pump configuration {} .", configFile);
logger.info("Configuration file contains {} requests.", heatPumpConfiguration.size());
if (categorizeHeatPumpConfiguration()) {
return heatPumpConfiguration;
}
}
logger.warn("Could not load heat pump configuration file for {}!", configFile);
return null;
}
/**
* This method categorize the heat pump configuration into setting, sensor
* and status
*
* @return true if heat pump configuration for version could be found and
* loaded
*/
private boolean categorizeHeatPumpConfiguration() {
for (Request request : heatPumpConfiguration) {
logger.debug("Request : Name -> {}, Description -> {} , RequestByte -> {}", request.getName(),
request.getDescription(),
DatatypeConverter.printHexBinary(new byte[] { request.getRequestByte() }));
if (request.getName().equalsIgnoreCase("Version")) {
versionRequest = request;
logger.debug("Loaded Request : " + versionRequest.getDescription());
continue;
}
for (RecordDefinition record : request.getRecordDefinitions()) {
if (record.getDataType() == Type.Settings && !heatPumpSettingConfiguration.contains(request)) {
heatPumpSettingConfiguration.add(request);
}
if (record.getDataType() == Type.Status && !heatPumpStatusConfiguration.contains(request)) {
heatPumpStatusConfiguration.add(request);
}
if (record.getDataType() == Type.Sensor && !heatPumpSensorConfiguration.contains(request)) {
heatPumpSensorConfiguration.add(request);
}
}
}
if (versionRequest == null) {
logger.debug("version request could not be found in configuration");
return false;
}
return true;
}
/**
* This method reads all values defined in the request from the heat pump
*
* @param request
* definition to load the values from
* @return map of heat pump values according request definition
*/
public Map<String, String> readData(Request request) throws StiebelHeatPumpException {
Map<String, String> data = new HashMap<String, String>();
logger.debug("Request : Name -> {}, Description -> {} , RequestByte -> {}", request.getName(),
request.getDescription(), DatatypeConverter.printHexBinary(new byte[] { request.getRequestByte() }));
startCommunication();
byte responseAvailable[] = new byte[0];
byte requestMessage[] = createRequestMessage(request);
boolean validData = false;
try {
while (!validData) {
responseAvailable = getData(requestMessage);
responseAvailable = parser.fixDuplicatedBytes(responseAvailable);
validData = parser.headerCheck(responseAvailable);
if (validData) {
data = parser.parseRecords(responseAvailable, request);
continue;
}
Thread.sleep(WAITING_TIME_BETWEEN_REQUESTS);
startCommunication();
}
} catch (StiebelHeatPumpException e) {
logger.error("Error reading data : {}", e.toString());
} catch (InterruptedException e) {
}
return data;
}
/**
* This method updates the parameter item of a heat pump request
*
* @param value
* the new value of the item
* @param parameter
* to be update in the heat pump
*/
public Map<String, String> setData(String value, String parameter) throws StiebelHeatPumpException {
Request updateRequest = null;
RecordDefinition updateRecord = null;
Map<String, String> data = new HashMap<String, String>();
// we lookup the right request definition that contains the parameter to
// be updated
if (parameter != null) {
for (Request request : heatPumpSettingConfiguration) {
for (RecordDefinition record : request.getRecordDefinitions()) {
if (record.getName().equalsIgnoreCase(parameter)) {
updateRecord = record;
updateRequest = request;
logger.debug("Found valid record definition {} in request {}:{}", record.getName(),
request.getName(), request.getDescription());
break;
}
}
}
}
if (updateRecord == null || updateRequest == null) {
// did not find any valid record, do nothing
logger.warn("Could not find valid record definition for {}", parameter);
return data;
}
try {
// get actual value for the corresponding request
// as we do no have individual requests for each settings we need to
// decode the new value
// into a current response , the response is available in the
// connector object
byte[] requestMessage = createRequestMessage(updateRequest);
byte[] response = getData(requestMessage);
data = parser.parseRecords(response, updateRequest);
// lookup parameter value in the data
String currentState = data.get(updateRecord.getName());
if (currentState.equals(value)) {
// current State is already same as new values!
logger.debug("Current State for {} is already {}.", parameter, value);
return data;
}
// create new set request out from the existing read response
byte[] requestUpdateMessage = parser.composeRecord(value, response, updateRecord);
logger.debug("Setting new value [{}] for parameter [{}]", value, parameter);
Thread.sleep(WAITING_TIME_BETWEEN_REQUESTS);
response = setData(requestUpdateMessage);
if (parser.setDataCheck(response)) {
logger.debug("Updated parameter {} successfully.", parameter);
} else {
logger.debug("Update for parameter {} failed!", parameter);
}
} catch (StiebelHeatPumpException e) {
logger.error("Stiebel heat pump communication error during update of value! " + e.toString());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
}
return data;
}
/**
* dumps response of connected heat pump by request byte
*
* @param requestByte
* request byte to send to heat pump
*/
public void dumpResponse(byte requestByte) {
Request request = new Request();
request.setRequestByte(requestByte);
byte requestMessage[] = createRequestMessage(request);
if (!establishRequest(requestMessage)) {
logger.info("Could not get response for request byte {} ...");
return;
}
try {
connector.write(DataParser.ESCAPE);
byte[] response = receiveData();
logger.info("Received response from heatpump: {}", DataParser.bytesToHex(response));
return;
} catch (Exception e) {
logger.error("Could not get data from heat pump! {}", e.toString());
}
return;
}
/**
* Gets data from connected heat pump
*
* @param request
* request bytes to send to heat pump
* @return response bytes from heat pump
*
* General overview of handshake between application and serial
* interface of heat pump 1. Sending request bytes , e.g.: 01 00 FD
* FC 10 03 for version request 01 -> header start 00 -> get request
* FD -> checksum of request FC -> request byte 10 03 -> Footer
* ending the communication
*
* 2. Receive a data available 10 -> ok 02 -> it does have
* data,which wants to send now
*
* 3. acknowledge sending data 10 -> ok
*
* 4. receive data until footer 01 -> header start 00 -> get request
* CC -> checksum of send data FD -> request byte 00 CE -> data ,
* e.g. short value as 2 bytes -> 206 -> 2.06 version 10 03 ->
* Footer ending the communication
*/
private byte[] getData(byte request[]) {
if (!establishRequest(request)) {
return new byte[0];
}
try {
connector.write(DataParser.ESCAPE);
byte[] response = receiveData();
return response;
} catch (Exception e) {
logger.error("Could not get data from heat pump! {}", e.toString());
return buffer;
}
}
/**
* Sets data to connected heat pump
*
* @param request
* request bytes to send to heat pump
* @return response bytes from heat pump
*
* General overview of handshake between application and serial
* interface of heat pump
*
* 1. Sending request bytes, e.g update time in heat pump 01 ->
* header start 80 -> set request F1 -> checksum of request FC ->
* request byte 00 02 0a 22 1b 0e 00 03 1a -> new values according
* record definition for time 10 03 -> Footer ending the
* communication
*
* 2. Receive response message the confirmation message is ready for
* sending 10 -> ok 02 -> it does have data ,which wants to send now
*
* 3. acknowledge sending data 10 -> ok
*
* 4. receive confirmation message until footer 01 -> header start
* 80 -> set request 7D -> checksum of send data FC -> request byte
* 10 03 -> Footer ending the communication
*/
private byte[] setData(byte[] request) throws StiebelHeatPumpException {
try {
startCommunication();
establishRequest(request);
// Acknowledge sending data
connector.write(DataParser.ESCAPE);
} catch (Exception e) {
logger.error("Could not set data to heat pump! {}", e.toString());
return new byte[0];
}
// finally receive data
return receiveData();
}
/**
* This method start the communication for the request It send the initial
* handshake and expects a response
*/
private void startCommunication() throws StiebelHeatPumpException {
logger.debug("Sending start communication");
byte response;
try {
connector.write(DataParser.STARTCOMMUNICATION);
response = connector.get();
} catch (Exception e) {
logger.error("heat pump communication could not be established !");
throw new StiebelHeatPumpException("heat pump communication could not be established !");
}
if (response != DataParser.ESCAPE) {
logger.warn("heat pump is communicating, but did not receive Escape message in initial handshake!");
throw new StiebelHeatPumpException(
"heat pump is communicating, but did not receive Escape message in initial handshake!");
}
}
/**
* This method establish the connection for the request It send the request
* and expects a data available response
*
* @param request
* to be send to heat pump
* @return true if data are available from heatpump
*/
private boolean establishRequest(byte[] request) {
int numBytesReadTotal = 0;
boolean dataAvailable = false;
int requestRetry = 0;
int retry = 0;
try {
while (requestRetry < MAXRETRIES) {
connector.write(request);
retry = 0;
byte singleByte;
while ((!dataAvailable) & (retry < MAXRETRIES)) {
try {
singleByte = connector.get();
} catch (Exception e) {
retry++;
continue;
}
buffer[numBytesReadTotal] = singleByte;
numBytesReadTotal++;
if (buffer[0] != DataParser.DATAAVAILABLE[0] || buffer[1] != DataParser.DATAAVAILABLE[1]) {
continue;
}
dataAvailable = true;
return true;
}
logger.debug("retry request!");
startCommunication();
}
if (!dataAvailable) {
logger.warn("heat pump has no data available for request!");
return false;
}
} catch (Exception e1) {
logger.error("Could not get data from heat pump! {}", e1.toString());
return false;
}
return true;
}
/**
* This method receive the response from the heat pump It receive single
* bytes until the end of message s detected
*
* @return bytes representing the data send from heat pump
*/
private byte[] receiveData() {
byte singleByte;
int numBytesReadTotal;
int retry;
buffer = new byte[INPUT_BUFFER_LENGTH];
retry = 0;
numBytesReadTotal = 0;
boolean endOfMessage = false;
while (!endOfMessage & retry < MAXRETRIES) {
try {
singleByte = connector.get();
} catch (Exception e) {
// reconnect and try again to send request
retry++;
continue;
}
buffer[numBytesReadTotal] = singleByte;
numBytesReadTotal++;
if (numBytesReadTotal > 4 && buffer[numBytesReadTotal - 2] == DataParser.ESCAPE
&& buffer[numBytesReadTotal - 1] == DataParser.END) {
// we have reached the end of the response
endOfMessage = true;
logger.debug("reached end of response message.");
break;
}
}
byte[] responseBuffer = new byte[numBytesReadTotal];
System.arraycopy(buffer, 0, responseBuffer, 0, numBytesReadTotal);
return responseBuffer;
}
/**
* This creates the request message ready to be send to heat pump
*
* @param request
* object containing necessary information to build request
* message
* @return request message byte[]
*/
private byte[] createRequestMessage(Request request) {
short checkSum;
byte[] requestMessage = new byte[] { DataParser.HEADERSTART, DataParser.GET, (byte) 0x00,
request.getRequestByte(), DataParser.ESCAPE, DataParser.END };
try {
// prepare request message
checkSum = parser.calculateChecksum(requestMessage);
requestMessage[2] = parser.shortToByte(checkSum)[0];
requestMessage = parser.addDuplicatedBytes(requestMessage);
} catch (StiebelHeatPumpException e) {
}
return requestMessage;
}
}