org.openhab.binding.km200.internal.KM200Comm.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.km200.internal.KM200Comm.java

Source

/**
 * Copyright (c) 2010-2019 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.km200.internal;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.openhab.binding.km200.KM200BindingProvider;
import org.openhab.core.items.Item;
import org.openhab.core.library.items.DateTimeItem;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.StringItem;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.io.ByteStreams;

/**
 * The KM200Comm class does the communication to the device and does any encryption/decryption/converting jobs
 *
 * @author Markus Eckhardt
 *
 * @since 1.9.0
 */

class KM200Comm {

    private static final Logger logger = LoggerFactory.getLogger(KM200Comm.class);
    private HttpClient client = null;
    private KM200Device device = null;

    public KM200Comm(KM200Device device) {
        this.device = device;
    }

    /**
     * This function removes zero padding from a byte array.
     *
     */
    public static byte[] removeZeroPadding(byte[] bytes) {
        int i = bytes.length - 1;
        while (i >= 0 && bytes[i] == 0) {
            --i;
        }
        return Arrays.copyOf(bytes, i + 1);
    }

    /**
     * This function adds zero padding to a byte array.
     *
     */
    public static byte[] addZeroPadding(byte[] bdata, int bSize, String cSet) throws UnsupportedEncodingException {
        int encrypt_padchar = bSize - (bdata.length % bSize);
        byte[] padchars = new String(new char[encrypt_padchar]).getBytes(cSet);
        byte[] padded_data = new byte[bdata.length + padchars.length];
        System.arraycopy(bdata, 0, padded_data, 0, bdata.length);
        System.arraycopy(padchars, 0, padded_data, bdata.length, padchars.length);
        return padded_data;
    }

    /**
     * This function converts a hex string to a byte array
     *
     */
    public static byte[] hexToBytes(String str) {
        if (str == null) {
            return null;
        } else if (str.length() < 2) {
            return null;
        } else {
            int len = str.length() / 2;
            byte[] buffer = new byte[len];
            for (int i = 0; i < len; i++) {
                buffer[i] = (byte) Integer.parseInt(str.substring(i * 2, i * 2 + 2), 16);
            }
            return buffer;
        }
    }

    /**
     * This function converts a byte array to a hex string
     *
     */
    public static String bytesToHex(byte[] data) {
        if (data == null) {
            return null;
        }

        int len = data.length;
        String str = "";
        for (int i = 0; i < len; i++) {
            if ((data[i] & 0xFF) < 16) {
                str = str + "0" + Integer.toHexString(data[i] & 0xFF);
            } else {
                str = str + Integer.toHexString(data[i] & 0xFF);
            }
        }
        return str;
    }

    /**
     * This function does the GET http communication to the device
     *
     */
    public byte[] getDataFromService(String service) {
        byte[] responseBodyB64 = null;
        int maxNbrGets = 3;
        int statusCode = 0;
        // Create an instance of HttpClient.
        if (client == null) {
            client = new HttpClient();
        }
        synchronized (client) {

            // Create a method instance.
            GetMethod method = new GetMethod("http://" + device.getIP4Address() + service);

            // Provide custom retry handler is necessary
            method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
                    new DefaultHttpMethodRetryHandler(3, false));
            // Set the right header
            method.setRequestHeader("Accept", "application/json");
            method.addRequestHeader("User-Agent", "TeleHeater/2.2.3");

            try {
                for (int i = 0; i < maxNbrGets && statusCode != HttpStatus.SC_OK; i++) {
                    // Execute the method.
                    statusCode = client.executeMethod(method);
                    // Check the status
                    switch (statusCode) {
                    case HttpStatus.SC_OK:
                        break;
                    case HttpStatus.SC_INTERNAL_SERVER_ERROR:
                        /* Unknown problem with the device, wait and try again */
                        logger.warn("HTTP GET failed: 500, internal server error, repeating.. ");
                        Thread.sleep(2000L);
                        continue;
                    case HttpStatus.SC_FORBIDDEN:
                        /* Service is available but not readable */
                        byte[] test = new byte[1];
                        return test;
                    default:
                        logger.error("HTTP GET failed: {}", method.getStatusLine());
                        return null;
                    }
                }
                device.setCharSet(method.getResponseCharSet());
                // Read the response body.
                responseBodyB64 = ByteStreams.toByteArray(method.getResponseBodyAsStream());

            } catch (HttpException e) {
                logger.error("Fatal protocol violation: {}", e.getMessage());
            } catch (InterruptedException e) {
                logger.error("Sleep was interrupted: {}", e.getMessage());
            } catch (IOException e) {
                logger.error("Fatal transport error: {}", e.getMessage());
            } finally {
                // Release the connection.
                method.releaseConnection();
            }
            return responseBodyB64;
        }
    }

    /**
     * This function does the SEND http communication to the device
     *
     */
    public Integer sendDataToService(String service, byte[] data) {
        // Create an instance of HttpClient.
        Integer rCode = null;
        if (client == null) {
            client = new HttpClient();
        }
        synchronized (client) {

            // Create a method instance.
            PostMethod method = new PostMethod("http://" + device.getIP4Address() + service);

            // Provide custom retry handler is necessary
            method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
                    new DefaultHttpMethodRetryHandler(3, false));
            // Set the right header
            method.setRequestHeader("Accept", "application/json");
            method.addRequestHeader("User-Agent", "TeleHeater/2.2.3");
            method.setRequestEntity(new ByteArrayRequestEntity(data));

            try {
                rCode = client.executeMethod(method);

            } catch (Exception e) {
                logger.error("Failed to send data {}", e);

            } finally {
                // Release the connection.
                method.releaseConnection();
            }
            return rCode;
        }
    }

    /**
     * This function does the decoding for a new message from the device
     *
     */
    public String decodeMessage(byte[] encoded) {
        String retString = null;
        byte[] decodedB64 = null;

        try {
            decodedB64 = Base64.decodeBase64(encoded);
        } catch (Exception e) {
            logger.error("Message is not in valid Base64 scheme: {}", e.getMessage());
            return null;
        }
        try {
            /* Check whether the length of the decryptData is NOT multiplies of 16 */
            if ((decodedB64.length & 0xF) != 0) {
                /* Return the data */
                retString = new String(decodedB64, device.getCharSet());
                return retString;
            }
            // --- create cipher
            final Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(device.getCryptKeyPriv(), "AES"));
            final byte[] decryptedData = cipher.doFinal(decodedB64);
            byte[] decryptedDataWOZP = removeZeroPadding(decryptedData);
            retString = new String(decryptedDataWOZP, device.getCharSet());
            return retString;
        } catch (UnsupportedEncodingException | GeneralSecurityException e) {
            // failure to authenticate
            logger.error("Exception on encoding: {}", e);
            return null;
        }
    }

    /**
     * This function does the encoding for a new message to the device
     *
     */
    public byte[] encodeMessage(String data) {
        byte[] encryptedDataB64 = null;

        try {
            // --- create cipher
            byte[] bdata = data.getBytes(device.getCharSet());
            final Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(device.getCryptKeyPriv(), "AES"));
            logger.debug("Create padding..");
            int bsize = cipher.getBlockSize();
            logger.debug("Add Padding and Encrypt AES..");
            final byte[] encryptedData = cipher.doFinal(addZeroPadding(bdata, bsize, device.getCharSet()));
            logger.debug("Encrypt B64..");
            try {
                encryptedDataB64 = Base64.encodeBase64(encryptedData);
            } catch (Exception e) {
                logger.error("Base64encoding not possible: {}", e.getMessage());
            }
            return encryptedDataB64;
        } catch (UnsupportedEncodingException | GeneralSecurityException e) {
            // failure to authenticate
            logger.error("Exception on encoding: {}", e);
            return null;
        }
    }

    /**
     * This function checks the capabilities of a service on the device
     *
     */
    public void initObjects(String service) {
        String id = null, type = null, decodedData = null;
        Integer writeable = 0;
        Integer recordable = 0;
        JSONObject nodeRoot = null;
        KM200CommObject newObject = null;
        logger.debug("Init: {}", service);
        if (device.blacklistMap.contains(service)) {
            logger.debug("Service on blacklist: {}", service);
            return;
        }
        byte[] recData = getDataFromService(service.toString());
        try {
            if (recData == null) {
                throw new RuntimeException("Communication is not possible!");
            }
            if (recData.length == 0) {
                throw new RuntimeException("No reply from KM200!");
            }
            /* Look whether the communication was forbidden */
            if (recData.length == 1) {
                newObject = new KM200CommObject(service, "", 0, 0, 0);
                device.serviceMap.put(service, newObject);
                return;
            }
            decodedData = decodeMessage(recData);
            if (decodedData == null) {
                throw new RuntimeException("Decoding of the KM200 message is not possible!");
            }
            if (decodedData.length() > 0) {
                nodeRoot = new JSONObject(decodedData);
                type = nodeRoot.getString("type");
                id = nodeRoot.getString("id");
            } else {
                logger.warn("Get empty reply");
                return;
            }

            /* Check the service features and set the flags */
            if (nodeRoot.has("writeable")) {
                Integer val = nodeRoot.getInt("writeable");
                logger.debug(val.toString());
                writeable = val;
            }
            if (nodeRoot.has("recordable")) {
                Integer val = nodeRoot.getInt("recordable");
                logger.debug(val.toString());
                recordable = val;
            }
            logger.debug("Typ: {}", type);

            newObject = new KM200CommObject(id, type, writeable, recordable);
            newObject.setJSONData(decodedData);

            Object valObject = null;
            switch (type) {
            case "stringValue": /* Check whether the type is a single value containing a string value */
                logger.debug("initDevice: type string value: {}", decodedData);
                valObject = new String(nodeRoot.getString("value"));
                newObject.setValue(valObject);
                if (nodeRoot.has("allowedValues")) {
                    List<String> valParas = new ArrayList<String>();
                    JSONArray paras = nodeRoot.getJSONArray("allowedValues");
                    for (int i = 0; i < paras.length(); i++) {
                        String subJSON = (String) paras.get(i);
                        valParas.add(subJSON);
                    }
                    newObject.setValueParameter(valParas);
                }
                device.serviceMap.put(id, newObject);
                break;

            case "floatValue": /* Check whether the type is a single value containing a float value */
                logger.debug("initDevice: type float value: {}", decodedData);
                valObject = nodeRoot.getBigDecimal("value");
                newObject.setValue(valObject);
                if (nodeRoot.has("minValue") && nodeRoot.has("maxValue")) {
                    List<BigDecimal> valParas = new ArrayList<BigDecimal>();
                    valParas.add(nodeRoot.getBigDecimal("minValue"));
                    valParas.add(nodeRoot.getBigDecimal("maxValue"));
                    newObject.setValueParameter(valParas);
                }
                device.serviceMap.put(id, newObject);
                break;

            case "switchProgram": /* Check whether the type is a switchProgram */
                logger.debug("initDevice: type switchProgram {}", decodedData);
                KM200SwitchProgramService sPService = new KM200SwitchProgramService();
                sPService.setMaxNbOfSwitchPoints(nodeRoot.getInt("maxNbOfSwitchPoints"));
                sPService.setMaxNbOfSwitchPointsPerDay(nodeRoot.getInt("maxNbOfSwitchPointsPerDay"));
                sPService.setSwitchPointTimeRaster(nodeRoot.getInt("switchPointTimeRaster"));
                JSONObject propObject = nodeRoot.getJSONObject("setpointProperty");
                sPService.setSetpointProperty(propObject.getString("id"));
                sPService.updateSwitches(nodeRoot);
                newObject.setValueParameter(sPService);
                newObject.setJSONData(decodedData);
                device.serviceMap.put(id, newObject);
                device.virtualList.add(newObject);
                break;

            case "errorList": /* Check whether the type is a errorList */
                logger.debug("initDevice: type errorList: {}", decodedData);
                KM200ErrorService eService = new KM200ErrorService();
                eService.updateErrors(nodeRoot);
                newObject.setValueParameter(eService);
                newObject.setJSONData(decodedData);
                device.serviceMap.put(id, newObject);
                device.virtualList.add(newObject);
                break;

            case "refEnum": /* Check whether the type is a refEnum */
                logger.debug("initDevice: type refEnum: {}", decodedData);
                device.serviceMap.put(id, newObject);
                JSONArray refers = nodeRoot.getJSONArray("references");
                for (int i = 0; i < refers.length(); i++) {
                    JSONObject subJSON = refers.getJSONObject(i);
                    id = subJSON.getString("id");
                    initObjects(id);
                }
                break;

            case "moduleList": /* Check whether the type is a moduleList */
                logger.debug("initDevice: type moduleList: {}", decodedData);
                device.serviceMap.put(id, newObject);
                JSONArray vals = nodeRoot.getJSONArray("values");
                for (int i = 0; i < vals.length(); i++) {
                    JSONObject subJSON = vals.getJSONObject(i);
                    id = subJSON.getString("id");
                    initObjects(id);
                }
                break;

            case "yRecording": /* Check whether the type is a yRecording */
                logger.debug("initDevice: type yRecording: {}", decodedData);
                device.serviceMap.put(id, newObject);
                /* have to be completed */
                break;

            case "systeminfo": /* Check whether the type is a systeminfo */
                logger.debug("initDevice: type systeminfo: {}", decodedData);
                JSONArray sInfo = nodeRoot.getJSONArray("values");
                newObject.setValue(sInfo);
                device.serviceMap.put(id, newObject);
                /* have to be completed */
                break;
            case "arrayData":
                logger.debug("initDevice: type arrayData: {}", decodedData);
                newObject.setJSONData(decodedData);
                device.serviceMap.put(id, newObject);
                /* have to be completed */
                break;

            default: /* Unknown type */
                logger.info("initDevice: type unknown for service: {} Data: {}", service, decodedData);
                device.serviceMap.put(id, newObject);
            }
        } catch (

        JSONException e) {
            logger.error("Parsingexception in JSON: {} data: {}", e.getMessage(), decodedData);
        }
    }

    /**
     * This function creates the virtual services
     *
     */
    public void initVirtualObjects() {
        KM200CommObject newObject = null;
        for (KM200CommObject object : device.virtualList) {
            logger.debug(object.getFullServiceName());
            String id = object.getFullServiceName();
            String type = object.getServiceType();
            switch (type) {
            case "switchProgram":
                KM200SwitchProgramService sPService = ((KM200SwitchProgramService) object.getValueParameter());
                sPService.determineSwitchNames(device);
                newObject = new KM200CommObject(id + "/weekday", type, 1, 0, 1, id);
                device.serviceMap.put(id + "/weekday", newObject);
                newObject = new KM200CommObject(id + "/nbrCycles", type, 0, 0, 1, id);
                device.serviceMap.put(id + "/nbrCycles", newObject);
                newObject = new KM200CommObject(id + "/cycle", type, 1, 0, 1, id);
                device.serviceMap.put(id + "/cycle", newObject);
                logger.debug("On: {}  Of: {}", id + "/" + sPService.getPositiveSwitch(),
                        id + "/" + sPService.getNegativeSwitch());
                newObject = new KM200CommObject(id + "/" + sPService.getPositiveSwitch(), type,
                        object.getWriteable(), object.getRecordable(), 1, id);
                device.serviceMap.put(id + "/" + sPService.getPositiveSwitch(), newObject);
                newObject = new KM200CommObject(id + "/" + sPService.getNegativeSwitch(), type,
                        object.getWriteable(), object.getRecordable(), 1, id);
                device.serviceMap.put(id + "/" + sPService.getNegativeSwitch(), newObject);

                break;
            case "errorList":
                newObject = new KM200CommObject(id + "/nbrErrors", type, 0, 0, 1, id);
                device.serviceMap.put(id + "/nbrErrors", newObject);
                newObject = new KM200CommObject(id + "/error", type, 1, 0, 1, id);
                device.serviceMap.put(id + "/error", newObject);
                newObject = new KM200CommObject(id + "/errorString", type, 0, 0, 1, id);
                device.serviceMap.put(id + "/errorString", newObject);
                break;
            }
        }
    }

    /**
     * This function checks whether the service has a replacement parameter
     *
     */
    public String checkParameterReplacement(KM200BindingProvider provider, String item) {
        String service = provider.getService(item);
        if (provider.getParameter(item).containsKey("current")) {
            String currentService = provider.getParameter(item).get("current");
            if (device.serviceMap.containsKey(currentService)) {
                if (device.serviceMap.get(currentService).getServiceType().equals("stringValue")) {
                    String val = (String) device.serviceMap.get(currentService).getValue();
                    return (service.replace("__current__", val));
                }
            }
        }
        return service;
    }

    /**
     * This function checks the state of a service on the device
     *
     */
    public State getProvidersState(KM200BindingProvider provider, String item) {
        synchronized (device) {
            String decodedData = null;
            String type = null;
            byte[] recData = null;
            KM200CommObject object = null;
            String service = checkParameterReplacement(provider, item);

            Class<? extends Item> itemType = provider.getItemType(item);
            logger.debug("Check state of: {} type: {} item: {}", service, type, itemType.getName());
            if (device.blacklistMap.contains(service)) {
                logger.debug("Service on blacklist: {}", service);
                return null;
            }
            if (device.serviceMap.containsKey(service)) {
                object = device.serviceMap.get(service);
                if (object.getReadable() == 0) {
                    logger.warn("Service is listed as protected (reading is not possible): {}", service);
                    return null;
                }
                type = object.getServiceType();
            } else {
                logger.warn("Service is not in the determined device service list: {}", service);
                return null;
            }
            /* For using of virtual services only one receive on the parent service is needed */
            if (!object.getUpdated()
                    || (object.getVirtual() == 1 && !device.serviceMap.get(object.getParent()).getUpdated())) {

                if (object.getVirtual() == 1) {
                    /* If it's a virtual service then receive the data from parent service */
                    recData = getDataFromService(object.getParent());
                } else {
                    recData = getDataFromService(service);
                }

                if (recData == null) {
                    throw new RuntimeException("Communication is not possible!");
                }
                if (recData.length == 0) {
                    throw new RuntimeException("No reply from KM200!");
                }
                /* Look whether the communication was forbidden */
                if (recData.length == 1) {
                    logger.error("Service is listed as readable but communication is forbidden: {}", service);
                    return null;
                }
                decodedData = decodeMessage(recData);
                logger.debug("Check state of data: {}", decodedData);
                if (decodedData == null) {
                    throw new RuntimeException("Decoding of the KM200 message is not possible!");
                }
                if (object.getVirtual() == 1) {
                    device.serviceMap.get(object.getParent()).setJSONData(decodedData);
                    device.serviceMap.get(object.getParent()).setUpdated(true);
                } else {
                    object.setJSONData(decodedData);
                }
                object.setUpdated(true);

            } else {
                /* If already updated then use the saved data */
                if (object.getVirtual() == 1) {
                    decodedData = device.serviceMap.get(object.getParent()).getJSONData();
                } else {
                    decodedData = object.getJSONData();
                }
            }
            /* Data is received, now parsing it */
            return parseJSONData(decodedData, type, item, provider);
        }
    }

    /**
     * This function parses the receviced JSON Data and return the right state
     *
     */
    public State parseJSONData(String decodedData, String type, String item, KM200BindingProvider provider) {
        JSONObject nodeRoot = null;
        State state = null;
        Class<? extends Item> itemType = provider.getItemType(item);
        String service = checkParameterReplacement(provider, item);
        KM200CommObject object = device.serviceMap.get(service);
        logger.debug("parseJSONData service: {}, data: {}", service, decodedData);
        /* Now parsing of the JSON String depending on its type and the type of binding item */
        try {
            if (decodedData.length() > 0) {
                nodeRoot = new JSONObject(decodedData);
            } else {
                logger.warn("Get empty reply");
                return null;
            }

            switch (type) {
            case "stringValue": /* Check whether the type is a single value containing a string value */
                logger.debug("initDevice: type string value: {}", decodedData);
                String sVal = nodeRoot.getString("value");
                device.serviceMap.get(service).setValue(sVal);
                /* SwitchItem Binding */
                if (itemType.isAssignableFrom(SwitchItem.class)) {
                    if (provider.getParameter(item).containsKey("on")) {
                        if (sVal.equals(provider.getParameter(item).get("off"))) {
                            state = OnOffType.OFF;
                        } else if (sVal.equals(provider.getParameter(item).get("on"))) {
                            state = OnOffType.ON;
                        }
                    } else {
                        logger.warn("Switch-Item only on configured on/off string values: {}", decodedData);
                        return null;
                    }

                    /* NumberItem Binding */
                } else if (itemType.isAssignableFrom(NumberItem.class)) {
                    try {
                        state = new DecimalType(Float.parseFloat(sVal));
                    } catch (NumberFormatException e) {
                        logger.error(
                                "Conversion of the string value to Decimal wasn't possible, data: {} error: {}",
                                decodedData, e);
                        return null;
                    }
                    /* DateTimeItem Binding */
                } else if (itemType.isAssignableFrom(DateTimeItem.class)) {
                    try {
                        state = new DateTimeType(sVal);
                    } catch (IllegalArgumentException e) {
                        logger.error(
                                "Conversion of the string value to DateTime wasn't possible, data: {} error: {}",
                                decodedData, e);
                        return null;
                    }

                    /* StringItem Binding */
                } else if (itemType.isAssignableFrom(StringItem.class)) {
                    state = new StringType(sVal);

                } else {
                    logger.warn("Bindingtype not supported for string values: {}", itemType.getClass());
                    return null;
                }
                return state;

            case "floatValue": /* Check whether the type is a single value containing a float value */
                logger.debug("state of type float value: {}", decodedData);
                BigDecimal bdVal = nodeRoot.getBigDecimal("value");
                device.serviceMap.get(service).setValue(bdVal);
                /* NumberItem Binding */
                if (itemType.isAssignableFrom(NumberItem.class)) {
                    state = new DecimalType(bdVal.floatValue());

                    /* StringItem Binding */
                } else if (itemType.isAssignableFrom(StringItem.class)) {
                    state = new StringType(bdVal.toString());
                } else {
                    logger.warn("Bindingtype not supported for float values: {}", itemType.getClass());
                    return null;
                }
                return state;

            case "switchProgram": /* Check whether the type is a switchProgram */
                KM200SwitchProgramService sPService = null;
                logger.debug("state of type switchProgram: {}", decodedData);
                /* Get the KM200SwitchProgramService class object with all specific parameters */
                if (object.getVirtual() == 0) {
                    sPService = ((KM200SwitchProgramService) object.getValueParameter());
                } else {
                    sPService = ((KM200SwitchProgramService) device.serviceMap.get(object.getParent())
                            .getValueParameter());
                }
                /* Update the switches insode the KM200SwitchProgramService */
                sPService.updateSwitches(nodeRoot);

                /* the parsing of switch program-services have to be outside, using json in strings */
                if (object.getVirtual() == 1) {
                    return this.getVirtualState(object, itemType, service);
                } else {
                    /* if access to the parent non virtual service the return the switchPoints jsonarray */
                    if (itemType.isAssignableFrom(StringItem.class)) {
                        state = new StringType(nodeRoot.getJSONArray("switchPoints").toString());
                    } else {
                        logger.warn(
                                "Bindingtype not supported for switchProgram, only json over strings supported: {}",
                                itemType.getClass());
                        return null;
                    }
                    return state;
                }

            case "errorList": /* Check whether the type is a errorList */
                KM200ErrorService eService = null;
                logger.debug("state of type errorList: {}", decodedData);
                /* Get the KM200ErrorService class object with all specific parameters */
                if (object.getVirtual() == 0) {
                    eService = ((KM200ErrorService) object.getValueParameter());
                } else {
                    eService = ((KM200ErrorService) device.serviceMap.get(object.getParent()).getValueParameter());
                }
                /* Update the switches insode the KM200SwitchProgramService */
                eService.updateErrors(nodeRoot);

                /* the parsing of switch program-services have to be outside, using json in strings */
                if (object.getVirtual() == 1) {
                    return this.getVirtualState(object, itemType, service);
                } else {
                    /* if access to the parent non virtual service the return the switchPoints jsonarray */
                    if (itemType.isAssignableFrom(StringItem.class)) {
                        state = new StringType(nodeRoot.getJSONArray("values").toString());
                    } else {
                        logger.warn(
                                "Bindingtype not supported for error list, only json over strings is supported: {}",
                                itemType.getClass());
                        return null;
                    }
                    return state;
                }

            case "yRecording": /* Check whether the type is a yRecording */
                logger.info("state of: type yRecording is not supported yet: {}", decodedData);
                /* have to be completed */
                break;

            case "systeminfo": /* Check whether the type is a systeminfo */
                logger.info("state of: type systeminfo is not supported yet: {}", decodedData);
                /* have to be completed */
                break;

            case "arrayData": /* Check whether the type is a arrayData */
                logger.info("state of: type arrayData is not supported yet: {}", decodedData);
                /* have to be completed */
                break;
            }
        } catch (JSONException e) {
            logger.error("Parsingexception in JSON, data: {} error: {} ", decodedData, e.getMessage());
        }
        return null;
    }

    /**
     * This function checks the virtual state of a service
     *
     */
    public State getVirtualState(KM200CommObject object, Class<? extends Item> itemType, String service) {
        State state = null;
        String type = object.getServiceType();
        logger.debug("Check virtual state of: {} type: {} item: {}", service, type, itemType.getName());

        switch (type) {
        case "switchProgram":
            KM200SwitchProgramService sPService = ((KM200SwitchProgramService) device.serviceMap
                    .get(object.getParent()).getValueParameter());
            String[] servicePath = service.split("/");
            String virtService = servicePath[servicePath.length - 1];
            if (virtService.equals("weekday")) {
                if (itemType.isAssignableFrom(StringItem.class)) {
                    String val = sPService.getActiveDay();
                    if (val == null) {
                        return null;
                    }
                    state = new StringType(val);
                } else {
                    logger.warn("Bindingtype not supported for day service: {}", itemType.getClass());
                    return null;
                }
            } else if (virtService.equals("nbrCycles")) {
                if (itemType.isAssignableFrom(NumberItem.class)) {
                    Integer val = sPService.getNbrCycles();
                    if (val == null) {
                        return null;
                    }
                    state = new DecimalType(val);
                } else {
                    logger.warn("Bindingtype not supported for nbrCycles service: {}", itemType.getClass());
                    return null;
                }
            } else if (virtService.equals("cycle")) {
                if (itemType.isAssignableFrom(NumberItem.class)) {
                    Integer val = sPService.getActiveCycle();
                    if (val == null) {
                        return null;
                    }
                    state = new DecimalType(val);
                } else {
                    logger.warn("Bindingtype not supported for cycle service: {}", itemType.getClass());
                    return null;
                }
            } else if (virtService.equals(sPService.getPositiveSwitch())) {
                if (itemType.isAssignableFrom(NumberItem.class)) {
                    Integer val = sPService.getActivePositiveSwitch();
                    if (val == null) {
                        return null;
                    }
                    state = new DecimalType(val);
                } else if (itemType.isAssignableFrom(DateTimeItem.class)) {
                    Integer val = sPService.getActivePositiveSwitch();
                    if (val == null) {
                        return null;
                    }
                    Calendar rightNow = Calendar.getInstance();
                    Integer hour = val % 60;
                    Integer minute = val - (hour * 60);
                    rightNow.set(Calendar.HOUR_OF_DAY, hour);
                    rightNow.set(Calendar.MINUTE, minute);
                    state = new DateTimeType(rightNow);
                } else {
                    logger.warn("Bindingtype not supported for cycle service: {}", itemType.getClass());
                    return null;
                }
            } else if (virtService.equals(sPService.getNegativeSwitch())) {
                if (itemType.isAssignableFrom(NumberItem.class)) {
                    Integer val = sPService.getActiveNegativeSwitch();
                    if (val == null) {
                        return null;
                    }
                    state = new DecimalType(val);
                } else if (itemType.isAssignableFrom(DateTimeItem.class)) {
                    Integer val = sPService.getActiveNegativeSwitch();
                    if (val == null) {
                        return null;
                    }
                    Calendar rightNow = Calendar.getInstance();
                    Integer hour = val % 60;
                    Integer minute = val - (hour * 60);
                    rightNow.set(Calendar.HOUR_OF_DAY, hour);
                    rightNow.set(Calendar.MINUTE, minute);
                    state = new DateTimeType(rightNow);
                } else {
                    logger.warn("Bindingtype not supported for cycle service: {}", itemType.getClass());
                    return null;
                }
            }
            break;
        case "errorList":
            KM200ErrorService eService = ((KM200ErrorService) device.serviceMap.get(object.getParent())
                    .getValueParameter());
            String[] nServicePath = service.split("/");
            String nVirtService = nServicePath[nServicePath.length - 1];
            /* Go through the parameters and read the values */
            switch (nVirtService) {
            case "nbrErrors":
                if (itemType.isAssignableFrom(NumberItem.class)) {
                    Integer val = eService.getNbrErrors();
                    if (val == null) {
                        return null;
                    }
                    state = new DecimalType(val);
                } else {
                    logger.warn("Bindingtype not supported for error number service: {}", itemType.getClass());
                    return null;
                }
                break;
            case "error":
                if (itemType.isAssignableFrom(NumberItem.class)) {
                    Integer val = eService.getActiveError();
                    if (val == null) {
                        return null;
                    }
                    state = new DecimalType(val);
                } else {
                    logger.warn("Bindingtype not supported for error service: {}", itemType.getClass());
                    return null;
                }
                break;
            case "errorString":
                if (itemType.isAssignableFrom(StringItem.class)) {
                    String val = eService.getErrorString();
                    if (val == null) {
                        return null;
                    }
                    state = new StringType(val);
                } else {
                    logger.warn("Bindingtype not supported for error string service: {}", itemType.getClass());
                    return null;
                }
                break;
            }
            break;
        }
        return state;

    }

    /**
     * This function sets the state of a service on the device
     *
     */
    public byte[] sendProvidersState(KM200BindingProvider provider, String item, Command command) {
        synchronized (device) {
            String type = null;
            String dataToSend = null;
            KM200CommObject object = null;
            Class<? extends Item> itemType = provider.getItemType(item);
            String service = checkParameterReplacement(provider, item);

            logger.debug("Prepare item for send: {} type: {} item: {}", service, type, itemType.getName());
            if (device.blacklistMap.contains(service)) {
                logger.debug("Service on blacklist: {}", service);
                return null;
            }
            if (device.serviceMap.containsKey(service)) {
                if (device.serviceMap.get(service).getWriteable() == 0) {
                    logger.error("Service is listed as read-only: {}", service);
                    return null;
                }
                object = device.serviceMap.get(service);
                type = object.getServiceType();
            } else {
                logger.error("Service is not in the determined device service list: {}", service);
                return null;
            }
            /* The service is availible, set now the values depeding on the item and binding type */
            logger.debug("state of: {} type: {}", command, type);
            /* Binding is a NumberItem */
            if (itemType.isAssignableFrom(NumberItem.class)) {
                BigDecimal bdVal = ((DecimalType) command).toBigDecimal();
                /* Check the capabilities of this service */
                if (object.getValueParameter() != null) {
                    @SuppressWarnings("unchecked")
                    List<BigDecimal> valParas = (List<BigDecimal>) object.getValueParameter();
                    BigDecimal minVal = valParas.get(0);
                    BigDecimal maxVal = valParas.get(1);
                    if (bdVal.compareTo(minVal) < 0) {
                        bdVal = minVal;
                    }
                    if (bdVal.compareTo(maxVal) > 0) {
                        bdVal = maxVal;
                    }
                }
                if (type.equals("floatValue")) {
                    dataToSend = new JSONObject().put("value", bdVal).toString();
                } else if (type.equals("stringValue")) {
                    dataToSend = new JSONObject().put("value", bdVal.toString()).toString();
                } else if (type.equals("switchProgram") && object.getVirtual() == 1) {
                    /* A switchProgram as NumberItem is always virtual */
                    dataToSend = sendVirtualState(object, itemType, service, command);
                } else if (type.equals("errorList") && object.getVirtual() == 1) {
                    /* A errorList as NumberItem is always virtual */
                    dataToSend = sendVirtualState(object, itemType, service, command);
                } else {
                    logger.warn("Not supported type for numberItem: {}", type);
                }
                /* Binding is a StringItem */
            } else if (itemType.isAssignableFrom(StringItem.class)) {
                String val = ((StringType) command).toString();
                /* Check the capabilities of this service */
                if (object.getValueParameter() != null) {
                    @SuppressWarnings("unchecked")
                    List<String> valParas = (List<String>) object.getValueParameter();
                    if (!valParas.contains(val)) {
                        logger.warn("Parameter is not in the service parameterlist: {}", val);
                        return null;
                    }
                }
                if (type.equals("stringValue")) {
                    dataToSend = new JSONObject().put("value", val).toString();
                } else if (type.equals("floatValue")) {
                    dataToSend = new JSONObject().put("value", Float.parseFloat(val)).toString();
                } else if (type.equals("switchProgram")) {
                    if (object.getVirtual() == 1) {
                        dataToSend = sendVirtualState(object, itemType, service, command);
                    } else {
                        /* The JSONArray of switch items can be sended directly */
                        try {
                            /* Check whether ths input string is a valid JSONArray */
                            JSONArray userArray = new JSONArray(val);
                            dataToSend = userArray.toString();
                        } catch (Exception e) {
                            logger.warn("The input for the switchProgram is not a valid JSONArray : {}",
                                    e.getMessage());
                            return null;
                        }
                    }
                } else {
                    logger.warn("Not supported type for stringItem: {}", type);
                }

                /* Binding is a DateTimeItem */
            } else if (itemType.isAssignableFrom(DateTimeItem.class)) {
                String val = ((DateTimeType) command).toString();
                if (type.equals("stringValue")) {
                    dataToSend = new JSONObject().put("value", val).toString();
                } else if (type.equals("switchProgram")) {
                    dataToSend = sendVirtualState(object, itemType, service, command);
                } else {
                    logger.warn("Not supported type for dateTimeItem: {}", type);
                }

                /* Binding is a SwitchItem */
            } else if (itemType.isAssignableFrom(SwitchItem.class)) {
                String val = null;
                if (provider.getParameter(item).containsKey("on")) {
                    if (command == OnOffType.OFF) {
                        val = provider.getParameter(item).get("off");
                    } else if (command == OnOffType.ON) {
                        val = provider.getParameter(item).get("on");
                    }
                } else {
                    logger.warn("Switch-Item only on configured on/off string values {}", command);
                    return null;
                }
                if (type.equals("stringValue")) {
                    dataToSend = new JSONObject().put("value", val).toString();
                } else {
                    logger.warn("Not supported type for SwitchItem:{}", type);
                }

            } else {
                logger.warn("Bindingtype not supported: {}", itemType.getClass());
                return null;
            }
            /* If some data is availible then we have to send it to device */
            if (dataToSend != null) {
                /* base64 + encoding */
                logger.debug("Encoding: {}", dataToSend);
                byte[] encData = encodeMessage(dataToSend);
                if (encData == null) {
                    logger.error("Couldn't encrypt data");
                    return null;
                }
                return encData;
            } else {
                return null;
            }
        }
    }

    /**
     * This function sets the state of a virtual service
     *
     */
    public String sendVirtualState(KM200CommObject object, Class<? extends Item> itemType, String service,
            Command command) {
        String dataToSend = null;
        String type = null;
        logger.debug("Check virtual state of: {} type: {} item: {}", service, type, itemType.getName());
        object = device.serviceMap.get(service);
        KM200CommObject parObject = device.serviceMap.get(object.getParent());
        type = object.getServiceType();
        /* Binding is a StringItem */
        if (itemType.isAssignableFrom(StringItem.class)) {
            String val = ((StringType) command).toString();
            switch (type) {
            case "switchProgram":
                KM200SwitchProgramService sPService = ((KM200SwitchProgramService) parObject.getValueParameter());
                String[] servicePath = service.split("/");
                String virtService = servicePath[servicePath.length - 1];
                if (virtService.equals("weekday")) {
                    /* Only parameter changing without communication to device */
                    sPService.setActiveDay(val);
                }
                break;
            }
            /* Binding is a NumberItem */
        } else if (itemType.isAssignableFrom(NumberItem.class)) {
            Integer val = ((DecimalType) command).intValue();
            switch (type) {
            case "switchProgram":
                KM200SwitchProgramService sPService = ((KM200SwitchProgramService) parObject.getValueParameter());
                String[] servicePath = service.split("/");
                String virtService = servicePath[servicePath.length - 1];
                if (virtService.equals("cycle")) {
                    /* Only parameter changing without communication to device */
                    sPService.setActiveCycle(val);
                } else if (virtService.equals(sPService.getPositiveSwitch())) {
                    sPService.setActivePositiveSwitch(val);
                    /* Create a JSON Array from current switch configuration */
                    dataToSend = sPService.getUpdatedJSONData(parObject);
                } else if (virtService.equals(sPService.getNegativeSwitch())) {
                    sPService.setActiveNegativeSwitch(val);
                    /* Create a JSON Array from current switch configuration */
                    dataToSend = sPService.getUpdatedJSONData(parObject);
                }
                break;
            case "errorList":
                KM200ErrorService eService = ((KM200ErrorService) device.serviceMap.get(object.getParent())
                        .getValueParameter());
                String[] nServicePath = service.split("/");
                String nVirtService = nServicePath[nServicePath.length - 1];
                if (nVirtService.equals("error")) {
                    /* Only parameter changing without communication to device */
                    eService.setActiveError(val);
                }
                break;
            }
        } else if (itemType.isAssignableFrom(DateTimeItem.class)) {
            Calendar cal = ((DateTimeType) command).getCalendar();
            KM200SwitchProgramService sPService = ((KM200SwitchProgramService) parObject.getValueParameter());
            String[] servicePath = service.split("/");
            String virtService = servicePath[servicePath.length - 1];
            Integer minutes;
            if (virtService.equals(sPService.getPositiveSwitch())) {
                minutes = cal.get(Calendar.HOUR_OF_DAY) * 60 + cal.get(Calendar.MINUTE);
                minutes = (minutes % sPService.getSwitchPointTimeRaster()) * sPService.getSwitchPointTimeRaster();
                sPService.setActivePositiveSwitch(minutes);
                /* Create a JSON Array from current switch configuration */
                dataToSend = sPService.getUpdatedJSONData(parObject);
            }
            if (virtService.equals(sPService.getNegativeSwitch())) {
                minutes = cal.get(Calendar.HOUR_OF_DAY) * 60 + cal.get(Calendar.MINUTE);
                minutes = (minutes % sPService.getSwitchPointTimeRaster()) * sPService.getSwitchPointTimeRaster();
                sPService.setActiveNegativeSwitch(minutes);
                /* Create a JSON Array from current switch configuration */
                dataToSend = sPService.getUpdatedJSONData(parObject);
            }
        }
        return dataToSend;
    }
}