org.openhab.binding.stiebelheatpump.internal.CommunicationService.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.stiebelheatpump.internal.CommunicationService.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.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;
    }

}