org.openhab.binding.wr3223.internal.WR3223Binding.java Source code

Java tutorial

Introduction

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

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.openhab.binding.wr3223.WR3223BindingProvider;
import org.openhab.binding.wr3223.WR3223CommandType;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.library.items.ContactItem;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class is an active binding to control the WR3223. To control the WR3223 over RS232/USB it must receive
 * at least every 20 second a message.
 *
 * @author Michael Fraefel
 * @since 1.10.0
 */
public class WR3223Binding extends AbstractActiveBinding<WR3223BindingProvider> {

    private static final WR3223CommandType[] READ_COMMANDS = { WR3223CommandType.TEMPERATURE_EVAPORATOR,
            WR3223CommandType.TEMPERATURE_CONDENSER, WR3223CommandType.TEMPERATURE_OUTSIDE,
            WR3223CommandType.TEMPERATURE_OUTGOING_AIR, WR3223CommandType.TEMPERATURE_AFTER_HEAT_EXCHANGER,
            WR3223CommandType.TEMPERATURE_SUPPLY_AIR, WR3223CommandType.TEMPERATURE_AFTER_BRINE_PREHEATING,
            WR3223CommandType.TEMPERATURE_AFTER_PREHEATING, WR3223CommandType.VENTILATION_LEVEL,
            WR3223CommandType.ROTATION_SPEED_SUPPLY_AIR_MOTOR, WR3223CommandType.ROTATION_SPEED_EXHAUST_AIR_MOTOR,
            WR3223CommandType.OPERATION_MODE, WR3223CommandType.TEMPERATURE_SUPPLY_AIR_TARGET,
            WR3223CommandType.HEAT_FEEDBACK_RATE, WR3223CommandType.SPEED_DEVIATION_MAX_LEVEL_1,
            WR3223CommandType.SPEED_DEVIATION_MAX_LEVEL_2, WR3223CommandType.SPEED_DEVIATION_MAX_LEVEL_3,
            WR3223CommandType.SPEED_INCREASE_EARTH_HEAT_EXCHANGER_LEVEL_1,
            WR3223CommandType.SPEED_INCREASE_EARTH_HEAT_EXCHANGER_LEVEL_2,
            WR3223CommandType.SPEED_INCREASE_EARTH_HEAT_EXCHANGER_LEVEL_3,
            WR3223CommandType.AIR_EXCHANGE_DECREASE_OUTSIDE_TEMPERATURE,
            WR3223CommandType.VENTILATION_SPEED_LEVEL_1, WR3223CommandType.VENTILATION_SPEED_LEVEL_2,
            WR3223CommandType.VENTILATION_SPEED_LEVEL_3,
            WR3223CommandType.SUMMER_EARTH_HEAT_EXCHANGER_ACTIVATION_TEMPERATURE,
            WR3223CommandType.WINTER_EARTH_HEAT_EXCHANGER_ACTIVATION_TEMPERATURE,
            WR3223CommandType.DEFROSTING_START_TEMPERATURE, WR3223CommandType.DEFROSTING_END_TEMPERATURE,
            WR3223CommandType.DEFROSTING_VENTILATION_LEVEL, WR3223CommandType.DEFROSTING_HOLD_OFF_TIME,
            WR3223CommandType.DEFROSTING_OVERTRAVEL_TIME, WR3223CommandType.DEFROSTING_HEAT_FEEDBACK_RATE,
            WR3223CommandType.SOLAR_MAX, WR3223CommandType.SOLAR_USAGE, WR3223CommandType.DELTA_T_OFF,
            WR3223CommandType.DELTA_T_ON, WR3223CommandType.TEMPERATURE_CONDENSER_MAX,
            WR3223CommandType.IDLE_TIME_PRESSURE_REDUCTION,
            WR3223CommandType.SUPPORT_FAN_LEVEL_1_EARTH_HEAT_EXCHANGER,
            WR3223CommandType.SUPPORT_FAN_LEVEL_2_EARTH_HEAT_EXCHANGER,
            WR3223CommandType.SUPPORT_FAN_LEVEL_3_EARTH_HEAT_EXCHANGER,
            WR3223CommandType.CONTROL_VOLTAGE_OUTGOING_AIR, WR3223CommandType.CONTROL_VOLTAGE_SUPPLY_AIR,
            WR3223CommandType.WARM_WATER_TARGET_TEMPERATURE, WR3223CommandType.HEAT_PUMP_OPEN,
            WR3223CommandType.ADDITIONAL_HEATER_OPEN };

    private static final WR3223CommandType[] WRITE_COMMANDS = { WR3223CommandType.OPERATION_MODE,
            WR3223CommandType.TEMPERATURE_SUPPLY_AIR_TARGET, WR3223CommandType.SPEED_DEVIATION_MAX_LEVEL_1,
            WR3223CommandType.SPEED_DEVIATION_MAX_LEVEL_2, WR3223CommandType.SPEED_DEVIATION_MAX_LEVEL_3,
            WR3223CommandType.SPEED_INCREASE_EARTH_HEAT_EXCHANGER_LEVEL_1,
            WR3223CommandType.SPEED_INCREASE_EARTH_HEAT_EXCHANGER_LEVEL_2,
            WR3223CommandType.SPEED_INCREASE_EARTH_HEAT_EXCHANGER_LEVEL_3,
            WR3223CommandType.AIR_EXCHANGE_DECREASE_OUTSIDE_TEMPERATURE,
            WR3223CommandType.VENTILATION_SPEED_LEVEL_1, WR3223CommandType.VENTILATION_SPEED_LEVEL_2,
            WR3223CommandType.VENTILATION_SPEED_LEVEL_3,
            WR3223CommandType.SUMMER_EARTH_HEAT_EXCHANGER_ACTIVATION_TEMPERATURE,
            WR3223CommandType.WINTER_EARTH_HEAT_EXCHANGER_ACTIVATION_TEMPERATURE,
            WR3223CommandType.DEFROSTING_START_TEMPERATURE, WR3223CommandType.DEFROSTING_END_TEMPERATURE,
            WR3223CommandType.DEFROSTING_VENTILATION_LEVEL, WR3223CommandType.DEFROSTING_HOLD_OFF_TIME,
            WR3223CommandType.DEFROSTING_OVERTRAVEL_TIME, WR3223CommandType.DEFROSTING_HEAT_FEEDBACK_RATE,
            WR3223CommandType.SOLAR_MAX, WR3223CommandType.DELTA_T_OFF, WR3223CommandType.DELTA_T_ON,
            WR3223CommandType.TEMPERATURE_CONDENSER_MAX, WR3223CommandType.IDLE_TIME_PRESSURE_REDUCTION,
            WR3223CommandType.SUPPORT_FAN_LEVEL_1_EARTH_HEAT_EXCHANGER,
            WR3223CommandType.SUPPORT_FAN_LEVEL_2_EARTH_HEAT_EXCHANGER,
            WR3223CommandType.SUPPORT_FAN_LEVEL_3_EARTH_HEAT_EXCHANGER,
            WR3223CommandType.WARM_WATER_TARGET_TEMPERATURE, WR3223CommandType.HEAT_PUMP_OPEN,
            WR3223CommandType.ADDITIONAL_HEATER_OPEN };

    private static final Logger logger = LoggerFactory.getLogger(WR3223Binding.class);

    /**
     * The BundleContext. This is only valid when the bundle is ACTIVE. It is set in the activate()
     * method and must not be accessed anymore once the deactivate() method was called or before activate()
     * was called.
     */
    private BundleContext bundleContext;

    /**
     * the refresh interval which is used to poll values from the WR3223
     * server (optional, defaults to 15000ms)
     */
    private long refreshInterval = 15000;

    /**
     * Host if connection over IP is used.
     */
    private String host;

    /**
     * Port if connection over IP is used.
     */
    private int port;

    /**
     * Serial port if connection over serial interface is used.
     */
    private String serialPort;

    /**
     * Controller address.
     */
    private byte controllerAddr = 1;

    /**
     * WR3223 Connector
     */
    private AbstractWR3223Connector connector;

    /**
     * Status of the WR3223
     */
    private StatusValueHolder statusHolder = new StatusValueHolder();

    /**
     * Store the value to update
     */
    private Map<WR3223CommandType, Integer> updateMap = new HashMap<WR3223CommandType, Integer>();

    /**
     * Not all controller supports all commands. The binding automatically disable not supported commands.
     */
    private Set<WR3223Commands> disabledCommands = new HashSet<>();

    /**
     * Called by the SCR to activate the component with its configuration read from CAS
     *
     * @param bundleContext BundleContext of the Bundle that defines this component
     * @param configuration Configuration properties for this component obtained from the ConfigAdmin service
     */
    public void activate(final BundleContext bundleContext, final Map<String, Object> configuration) {
        this.bundleContext = bundleContext;
        configure(configuration);

        // Default start values
        updateMap.put(WR3223CommandType.OPERATION_MODE, 3);
    }

    /**
     * Called by the SCR when the configuration of a binding has been changed through the ConfigAdmin service.
     *
     * @param configuration Updated configuration properties
     */
    public void modified(final Map<String, Object> configuration) {
        configure(configuration);
    }

    /**
     * Called by the SCR to deactivate the component when either the configuration is removed or
     * mandatory references are no longer satisfied or the component has simply been stopped.
     *
     * @param reason Reason code for the deactivation:<br>
     *            <ul>
     *            <li>0  Unspecified
     *            <li>1  The component was disabled
     *            <li>2  A reference became unsatisfied
     *            <li>3  A configuration was changed
     *            <li>4  A configuration was deleted
     *            <li>5  The component was disposed
     *            <li>6  The bundle was stopped
     *            </ul>
     */
    public void deactivate(final int reason) {
        this.bundleContext = null;
        if (connector != null) {
            try {
                connector.close();
            } catch (IOException ex) {
                logger.error("Error by closing connector.", ex);
            }
        }
    }

    /**
     * Configure binding.
     *
     * @param configuration
     */
    private void configure(final Map<String, Object> configuration) {
        // Configure refresh
        String refreshIntervalString = (String) configuration.get("refresh");
        if (StringUtils.isNotBlank(refreshIntervalString)) {
            refreshInterval = Long.parseLong(refreshIntervalString);
            if (refreshInterval > 20000) {
                logger.warn("Refresh interval should not be over 20 seconds.");
            }
        }

        // Controller
        String controllerAddrString = (String) configuration.get("controllerAddr");
        if (StringUtils.isNotBlank(controllerAddrString)) {
            controllerAddr = Byte.parseByte(controllerAddrString);
        }

        // Configure connection
        String hostString = (String) configuration.get("host");
        String portString = (String) configuration.get("port");
        String serialPortString = (String) configuration.get("serialPort");
        if (StringUtils.isNotBlank(hostString) && StringUtils.isNotBlank(portString)) {
            host = hostString;
            port = Integer.parseInt(portString);
            serialPort = null;
            logger.info("Connect over IP to host {}:{}", host, port);
            setProperlyConfigured(true);
        } else if (StringUtils.isNotBlank(serialPortString)) {
            serialPort = serialPortString;
            host = null;
            logger.info("Connect over serial port {}", serialPort);
            setProperlyConfigured(true);
        } else {
            setProperlyConfigured(false);
            logger.error("No connection configured");
        }

        // Close the connector if already one is open.
        if (connector != null) {
            try {
                connector.close();
            } catch (IOException ex) {
                logger.error("Error by closing connector.", ex);
            }
        }

    }

    /**
     * @{inheritDoc}
     */
    @Override
    protected long getRefreshInterval() {
        return refreshInterval;
    }

    /**
     * @{inheritDoc}
     */
    @Override
    protected String getName() {
        return "WR3223 Refresh Service";
    }

    /**
     * @{inheritDoc}
     */
    @Override
    protected void execute() {

        // Connector if not already connected.
        try {
            if (connector == null) {
                if (host != null) {
                    TcpWR3223Connector tcpConnector = new TcpWR3223Connector();
                    tcpConnector.connect(host, port);
                    connector = tcpConnector;
                    logger.info("Connected to WR3223 over tcp to host {}:{}.", host, port);
                } else if (serialPort != null) {
                    SerialWR3223Connector serialConnector = new SerialWR3223Connector();
                    serialConnector.connect(serialPort, 9600);
                    connector = serialConnector;
                    logger.info("Connected to WR3223 over serial port {}.", serialPort);
                }
            }
        } catch (IOException ex) {
            logger.error("Couldn't establish connection to WR3223.", ex);
            connector = null;
            return;
        }

        try {

            // Read status values (Ta)
            if (checkIfCommandIsAvailable(WR3223Commands.Ta)) {
                statusHolder.valueOf(connector.read(controllerAddr, WR3223Commands.Ta));
                publishValueToBoundItems(WR3223CommandType.MALFUNCTION, statusHolder.isMalfunction());
                publishValueToBoundItems(WR3223CommandType.HEAT_PUMP_STATUS, statusHolder.getHeatPumpOnStatus());
                publishValueToBoundItems(WR3223CommandType.ADDITIONAL_HEATER_STATUS,
                        statusHolder.getAdditionalHeatingOnStatus());
            }

            // EVU Blockade handling (Tf)
            if (checkIfCommandIsAvailable(WR3223Commands.Tf)) {
                EvuBlockadeHandler handler = EvuBlockadeHandler
                        .valueOf(connector.read(controllerAddr, WR3223Commands.Tf));
                publishValueToBoundItems(WR3223CommandType.EVU_BLOCKADE, handler.isBlockade());
            }

            // Read relais
            RelaisValueDecoder relais = readAndPublishRelaisValues();

            // Read errors
            readAndPublishErrorValues();

            // Write values if no control device connected
            if (!relais.isControlDeviceActive()) {
                if (connector.write(controllerAddr, WR3223Commands.SW, statusHolder.getStatusValue())) {

                    // Commit value updates to WR3223
                    Iterator<Entry<WR3223CommandType, Integer>> it = updateMap.entrySet().iterator();
                    while (it.hasNext()) {
                        Entry<WR3223CommandType, Integer> update = it.next();
                        if (connector.write(controllerAddr, update.getKey().getWr3223Command(),
                                String.valueOf(update.getValue()))) {
                            it.remove();
                        }
                    }
                } else {
                    logger.error("Couldn't send keep alive message to WR3223.");
                }
            } else {
                logger.warn(
                        "The control device is active! Openhab can only control the WR3223, when the control device is removed. (Bedienteil)");
            }

            // Read and publish other values from WR3223
            for (WR3223CommandType readCommand : READ_COMMANDS) {
                if (checkIfCommandIsAvailable(readCommand.getWr3223Command())) {
                    if (!updateMap.containsKey(readCommand)) {
                        readAndPublishValue(readCommand);
                    } else {
                        logger.info(
                                "Skip reading values for command {} from WR3223, because an updated value must first be sent to WR3223.",
                                readCommand.getCommand());
                    }
                }
            }
        } catch (IOException e) {
            logger.error("Communication error to WR3223.", e);
            if (connector != null) {
                try {
                    connector.close();
                } catch (IOException e1) {
                    logger.error("Couldn't close communication to WR3223.", e1);
                }
            }
            connector = null;
        }

    }

    /**
     * Check if a command is available.
     *
     * @param command
     * @return
     * @throws IOException
     */
    private boolean checkIfCommandIsAvailable(WR3223Commands command) throws IOException {
        if (disabledCommands.contains(command)) {
            return false;
        }
        String value = connector.read(controllerAddr, command);
        if (value == null || value.contains("???")) {
            disabledCommands.add(command);
            logger.warn("Command {} is not supported by the controller.", command);
            return false;
        }
        return true;
    }

    /**
     * @return
     * @throws IOException
     */
    private RelaisValueDecoder readAndPublishRelaisValues() throws IOException {
        RelaisValueDecoder relais = RelaisValueDecoder.valueOf(connector.read(controllerAddr, WR3223Commands.RL));
        publishValueToBoundItems(WR3223CommandType.COMPRESSOR, relais.isCompressor());
        publishValueToBoundItems(WR3223CommandType.ADDITIONAL_HEATER_RELAIS, relais.isAdditionalHeater());
        publishValueToBoundItems(WR3223CommandType.PREHEATING_RADIATOR_ACTIVE, relais.isPreHeaterRadiatorActive());
        publishValueToBoundItems(WR3223CommandType.BYPASS, !relais.isBypass());
        publishValueToBoundItems(WR3223CommandType.BYPASS_RELAY, relais.isBypassRelay());
        publishValueToBoundItems(WR3223CommandType.CONTROL_DEVICE_ACTIVE, relais.isControlDeviceActive());
        publishValueToBoundItems(WR3223CommandType.EARTH_HEAT_EXCHANGER, relais.isEarthHeatExchanger());
        publishValueToBoundItems(WR3223CommandType.MAGNET_VALVE, relais.isMagnetValve());
        publishValueToBoundItems(WR3223CommandType.OPENHAB_INTERFACE_ACTIVE, relais.isOpenhabInterfaceActive());
        publishValueToBoundItems(WR3223CommandType.PREHEATING_RADIATOR, relais.isPreheatingRadiator());
        publishValueToBoundItems(WR3223CommandType.VENTILATION_LEVEL_AVAILABLE,
                relais.isVentilationLevelAvailable());
        publishValueToBoundItems(WR3223CommandType.WARM_WATER_POST_HEATER, relais.isWarmWaterPostHeater());
        return relais;
    }

    /**
     * @throws IOException
     */
    private void readAndPublishErrorValues() throws IOException {
        if (checkIfCommandIsAvailable(WR3223Commands.ER)) {
            ErrorValueDecoder errors = ErrorValueDecoder.valueOf(connector.read(controllerAddr, WR3223Commands.ER));
            publishValueToBoundItems(WR3223CommandType.ERROR_TEMP_SENSOR_SHORT, errors.isError_temp_sensor_short());
            publishValueToBoundItems(WR3223CommandType.ERROR_OFFSET, errors.isError_offset());
            publishValueToBoundItems(WR3223CommandType.ERROR_TEMP_SENSOR_INTERUPT,
                    errors.isError_temp_sensor_interupt());
            publishValueToBoundItems(WR3223CommandType.ERROR_HIGH_PRESSURE, errors.isError_high_pressure());
            publishValueToBoundItems(WR3223CommandType.ERROR_SYS_RAM, errors.isError_sys_ram());
            publishValueToBoundItems(WR3223CommandType.ERROR_SYS_ROM, errors.isError_sys_rom());
            publishValueToBoundItems(WR3223CommandType.ERROR_SYS_EE, errors.isError_sys_ee());
            publishValueToBoundItems(WR3223CommandType.ERROR_SYS_IO, errors.isError_sys_io());
            publishValueToBoundItems(WR3223CommandType.ERROR_SYS_67_AD, errors.isError_sys_67_ad());
            publishValueToBoundItems(WR3223CommandType.ERROR_SUPPLY_AIR, errors.isError_supply_air());
            publishValueToBoundItems(WR3223CommandType.ERROR_OUTGOING_AIR, errors.isError_outgoing_air());
            publishValueToBoundItems(WR3223CommandType.ERROR_CONDENSER, errors.isError_condenser());
            publishValueToBoundItems(WR3223CommandType.ERROR_PREHEATING, errors.isError_preheating());
        }
    }

    /**
     * Read value of given command and publish it to the bus.
     *
     * @param wr3223CommandType
     * @throws IOException
     */
    private void readAndPublishValue(WR3223CommandType wr3223CommandType) throws IOException {
        List<String> itemNames = getBoundItemsForType(wr3223CommandType);
        if (itemNames.size() > 0) {
            String value = connector.read(controllerAddr, wr3223CommandType.getWr3223Command());
            publishValueToItems(itemNames, wr3223CommandType, value);
        }
    }

    /**
     * Publish the value to all bound items.
     *
     * @param wr3223CommandType
     * @param value
     */
    private void publishValueToBoundItems(WR3223CommandType wr3223CommandType, Object value) {
        List<String> itemNames = getBoundItemsForType(wr3223CommandType);
        if (itemNames.size() > 0) {
            publishValueToItems(itemNames, wr3223CommandType, value);
        }
    }

    /**
     * Publish the value to the given items.
     *
     * @param itemNames
     * @param wr3223CommandType
     * @param value
     */
    private void publishValueToItems(List<String> itemNames, WR3223CommandType wr3223CommandType, Object value) {
        if (value == null) {
            logger.error("Can't set NULL value to item type {}.", wr3223CommandType.getCommand());
            return;
        }

        State state = null;
        if (wr3223CommandType.getItemClass() == NumberItem.class) {
            try {
                if (logger.isDebugEnabled()) {
                    logger.debug("WR3223Binding.publishValueToItems: publish command {} with number {} ",
                            wr3223CommandType.getCommand(), value.toString().trim());
                }
                state = DecimalType.valueOf(value.toString().trim());
            } catch (NumberFormatException nfe) {
                logger.error("Can't set value {} to item type {} because it's not a decimal number.", value,
                        wr3223CommandType.getCommand());
            }
        } else if (wr3223CommandType.getItemClass() == SwitchItem.class) {
            state = parseBooleanValue(value);
            if (logger.isDebugEnabled()) {
                logger.debug("WR3223 publish switch item {} to {}.", wr3223CommandType.getCommand(), state);
            }
        } else if (wr3223CommandType.getItemClass() == ContactItem.class) {
            state = parseBooleanValue(value) == OnOffType.ON ? OpenClosedType.CLOSED : OpenClosedType.OPEN;
            if (logger.isDebugEnabled()) {
                logger.debug("WR3223 publish contact item {} to {}.", wr3223CommandType.getCommand(), state);
            }
        } else {
            logger.error("Can't set value {} to item type {}.", value, wr3223CommandType.getCommand());
        }
        if (state != null) {
            for (String itemName : itemNames) {
                eventPublisher.postUpdate(itemName, state);
            }
        }
    }

    /**
     * Try to read the On/Off state.
     *
     * @param value
     * @return state of a boolean value
     */
    private State parseBooleanValue(Object value) {
        State state;
        String valStr = value.toString().trim();
        state = (valStr.equalsIgnoreCase("true") || valStr.equals("1") || valStr.equals("1.")) ? OnOffType.ON
                : OnOffType.OFF;
        if (logger.isDebugEnabled()) {
            logger.debug("W3223Binding.parseBooleanValue: parsed value {} to state {}", valStr, state);
        }
        return state;
    }

    private List<String> getBoundItemsForType(WR3223CommandType wr3223CommandType) {
        List<String> itemNames = new ArrayList<String>();
        for (WR3223BindingProvider provider : providers) {
            for (String itemName : provider.getItemNamesForType(wr3223CommandType)) {
                itemNames.add(itemName);
            }
        }
        return itemNames;
    }

    private WR3223CommandType getWR3223BindingConfig(String itemName) {
        WR3223CommandType type = null;
        Iterator<WR3223BindingProvider> providerIt = providers.iterator();
        while (providerIt.hasNext() && type == null) {
            type = providerIt.next().getWR3223CommandTypeForItemName(itemName);
        }
        return type;
    }

    /**
     * @{inheritDoc}
     */
    @Override
    protected void internalReceiveCommand(String itemName, Command command) {
        if (logger.isDebugEnabled()) {
            logger.debug("internalReceiveCommand({},{}) is called!", itemName, command);
        }
        WR3223CommandType type = getWR3223BindingConfig(itemName);
        if (type == null) {
            logger.error("Item {} is not bound to WR3223 binding.", itemName);
        } else {
            if (type == WR3223CommandType.VENTILATION_LEVEL) {
                if (command instanceof DecimalType) {
                    int value = ((DecimalType) command).intValue();
                    if (value >= 0 && value <= 3) {
                        statusHolder.setVentilationLevel(value);
                    } else {
                        logger.warn("WR3223 value {} of item {} out of range.", value, itemName);
                    }
                } else {
                    logger.warn("WR3223 item {} must be from type:{}.", itemName,
                            DecimalType.class.getSimpleName());
                }
            } else if (type == WR3223CommandType.ADDITIONAL_HEATER_ACTIVATE) {
                if (command instanceof OnOffType) {
                    if (command == OnOffType.ON) {
                        statusHolder.setAdditionalHeatingOn(true);
                    } else {
                        statusHolder.setAdditionalHeatingOn(false);
                    }
                } else {
                    logger.warn("WR3223 item {} must be from type:{}.", itemName, OnOffType.class.getSimpleName());
                }
            } else if (type == WR3223CommandType.HEAT_PUMP_ACTIVATE) {
                if (command instanceof OnOffType) {
                    if (command == OnOffType.ON) {
                        statusHolder.setHeatPumpOn(true);
                    } else {
                        statusHolder.setHeatPumpOn(false);
                    }
                } else {
                    logger.warn("WR3223 item {} must be from type:{}.", itemName, OnOffType.class.getSimpleName());
                }
            } else if (type == WR3223CommandType.COOLING_MODE_ACTIVATE) {
                if (command instanceof OnOffType) {
                    if (command == OnOffType.ON) {
                        statusHolder.setCoolingOn(true);
                    } else {
                        statusHolder.setCoolingOn(false);
                    }
                } else {
                    logger.warn("WR3223 item {} must be from type:{}.", itemName, OnOffType.class.getSimpleName());
                }
            } else {
                for (WR3223CommandType t : WRITE_COMMANDS) {
                    if (type == t) {
                        if (type.getItemClass() == NumberItem.class && command instanceof DecimalType) {
                            int value = ((DecimalType) command).intValue();
                            if (type.getMinValue() != null && type.getMinValue() > value) {
                                logger.warn("Value of WR3223 command {} must be bigger or equals {}.",
                                        type.getCommand(), type.getMinValue());
                            } else if (type.getMaxValue() != null && type.getMaxValue() < value) {
                                logger.warn("Value of WR3223 command {} must be lower or equals {}.",
                                        type.getCommand(), type.getMaxValue());
                            } else {
                                updateMap.put(type, value);
                            }
                        } else if (type.getItemClass() == SwitchItem.class && command instanceof OnOffType) {
                            logger.debug("SwitchCommand({},{}) is called!", itemName, command);
                            if (command == OnOffType.ON) {
                                updateMap.put(type, 1);
                            } else if (command == OnOffType.OFF) {
                                updateMap.put(type, 0);
                            } else {
                                logger.warn("WR3223 command {} should be of type {}.", type.getCommand(),
                                        type.getItemClass());
                            }

                        } else {
                            logger.warn("WR3223 command {} should be of type {}.", type.getCommand(),
                                    type.getItemClass());
                        }
                        break;
                    }
                }
            }
        }
    }

    /**
     * Hold the values which must be send every 20 seconds to the WR3223. The values received from the bus are stored in
     * this class.
     *
     * @author Michael Fraefel
     *
     */
    private static final class StatusValueHolder {

        private boolean heatPumpOn = false;
        private int ventilationLevel = 2;
        private boolean additionalHeatingOn = false;
        private boolean coolingOn = false;

        private boolean malfunction = false;
        private final int FLAG_MALFUNCTION = 16;

        private boolean heatPumpOnStatus = false;
        private final int FLAG_HEAT_PUMP_STATUS = 32;

        private boolean additionalHeatingOnStatus = false;
        private final int FLAG_ADDITIONAL_HEATER_STATUS = 64;

        private final int STATUS_MASK = 112;

        /**
         * @param "Wrmepumpe ein (bei Anlagen mit Wrmepumpe)"
         */
        public void setHeatPumpOn(boolean heatPumpOn) {
            this.heatPumpOn = heatPumpOn;
        }

        /**
         * @param "Lftungsstufe 0-3, 0=Aus"
         */
        public void setVentilationLevel(int ventilationLevel) {
            this.ventilationLevel = ventilationLevel;
        }

        /**
         * @param "Zusatzheizung ein"
         */
        public void setAdditionalHeatingOn(boolean additionalHeatingOn) {
            this.additionalHeatingOn = additionalHeatingOn;
        }

        /**
         * @param "khlen (bei Anlagen mit Wrmepumpe)"
         */
        public void setCoolingOn(boolean coolingOn) {
            this.coolingOn = coolingOn;
        }

        /**
         * @return Strung
         */
        public boolean isMalfunction() {
            return malfunction;
        }

        /**
         * @return Zusatzheizung An/Aus
         */
        public boolean getAdditionalHeatingOnStatus() {
            return additionalHeatingOnStatus;
        }

        /**
         * @return Wrmepumpe An/Aus
         */
        public boolean getHeatPumpOnStatus() {
            return heatPumpOnStatus;
        }

        public String getStatusValue() {

            int data = 0;
            if (!heatPumpOn) {
                data += 1;
            }
            if (ventilationLevel == 2 || ventilationLevel == 1) {
                data += 2;
            }
            if (ventilationLevel == 3 || ventilationLevel == 1) {
                data += 4;
            }
            if (!additionalHeatingOn) {
                data += 8;
            }
            if (ventilationLevel == 0) {
                data += 16;
            }
            if (!coolingOn) {
                data += 32;
            }

            return String.valueOf(data);
        }

        public void valueOf(String read) {

            read = read.trim();
            int decPoint = read.indexOf(".");
            if (decPoint > 0) {
                read = read.substring(0, decPoint);
            }
            int value = Integer.valueOf(read.trim()) * -1;

            // Mask value as only bits 4, 5 and 6 are necessary
            value &= STATUS_MASK;

            malfunction = (value & FLAG_MALFUNCTION) == FLAG_MALFUNCTION ? true : false;
            heatPumpOnStatus = (value & FLAG_HEAT_PUMP_STATUS) == FLAG_HEAT_PUMP_STATUS ? true : false;
            additionalHeatingOnStatus = (value & FLAG_ADDITIONAL_HEATER_STATUS) == FLAG_ADDITIONAL_HEATER_STATUS
                    ? true
                    : false;

        }
    }

    /**
     * Coding of the RL command.
     *
     * @author Michael Fraefel
     *
     */
    private static final class RelaisValueDecoder {

        private static final int FLAG_COMPRESSOR = 1;
        private static final int FLAG_ADDITIONAL_HEATER = 2;
        private static final int FLAG_EARTH_HEAT_EXCHANGER = 4;
        private static final int FLAG_BYPASS = 8;
        private static final int FLAG_PREHEATING_RADIATOR = 16;
        private static final int FLAG_BYPASS_RELAY = 32;
        private static final int FLAG_CONTROL_DEVICE_ACTIVE = 64;
        private static final int FLAG_OPENHAB_INTERFACE_ACTIVE = 128;
        private static final int FLAG_VENTILATION_LEVEL_AVAILABLE = 256;
        private static final int FLAG_WARM_WATER_POST_HEATER = 512;
        private static final int FLAG_MAGNET_VALVE = 2048;
        private static final int FLAG_PRE_HEATER_RADIATOR_ACTIVE = 4096;

        private boolean compressor;
        private boolean additionalHeater;
        private boolean earthHeatExchanger;
        private boolean bypass;
        private boolean bypassRelay;
        private boolean preheatingRadiator;
        private boolean controlDeviceActive;
        private boolean openhabInterfaceActive;
        private boolean ventilationLevelAvailable;
        private boolean warmWaterPostHeater;
        private boolean magnetValve;
        private boolean preHeaterRadiatorActive;

        public static RelaisValueDecoder valueOf(String read) {
            RelaisValueDecoder relaisValue = new RelaisValueDecoder();
            read = read.trim();
            int decPoint = read.indexOf(".");
            if (decPoint > 0) {
                read = read.substring(0, decPoint);
            }
            logger.debug("WR3223.RelaisValueDecoder read string={}.", read);
            int value = Integer.valueOf(read.trim());
            if ((value & FLAG_COMPRESSOR) == FLAG_COMPRESSOR) {
                relaisValue.compressor = true;
            }
            if ((value & FLAG_ADDITIONAL_HEATER) == FLAG_ADDITIONAL_HEATER) {
                relaisValue.additionalHeater = true;
            }
            if ((value & FLAG_EARTH_HEAT_EXCHANGER) == FLAG_EARTH_HEAT_EXCHANGER) {
                relaisValue.earthHeatExchanger = true;
            }
            if ((value & FLAG_BYPASS) == FLAG_BYPASS) {
                relaisValue.bypass = true;
            }
            if ((value & FLAG_PREHEATING_RADIATOR) == FLAG_PREHEATING_RADIATOR) {
                relaisValue.preheatingRadiator = true;
            }
            if ((value & FLAG_BYPASS_RELAY) == FLAG_BYPASS_RELAY) {
                relaisValue.bypassRelay = true;
            }
            if ((value & FLAG_CONTROL_DEVICE_ACTIVE) == FLAG_CONTROL_DEVICE_ACTIVE) {
                relaisValue.controlDeviceActive = true;
            }
            if ((value & FLAG_OPENHAB_INTERFACE_ACTIVE) == FLAG_OPENHAB_INTERFACE_ACTIVE) {
                relaisValue.openhabInterfaceActive = true;
            }
            if ((value & FLAG_VENTILATION_LEVEL_AVAILABLE) == FLAG_VENTILATION_LEVEL_AVAILABLE) {
                relaisValue.ventilationLevelAvailable = true;
            }
            if ((value & FLAG_WARM_WATER_POST_HEATER) == FLAG_WARM_WATER_POST_HEATER) {
                relaisValue.warmWaterPostHeater = true;
            }
            if ((value & FLAG_MAGNET_VALVE) == FLAG_MAGNET_VALVE) {
                relaisValue.magnetValve = true;
            }
            if ((value & FLAG_PRE_HEATER_RADIATOR_ACTIVE) == FLAG_PRE_HEATER_RADIATOR_ACTIVE) {
                relaisValue.preHeaterRadiatorActive = true;
            }
            return relaisValue;
        }

        /**
         * @return "Kompressor Relais"
         */
        public boolean isCompressor() {
            return compressor;
        }

        /**
         * @return "Zusatzheizung Relais"
         */
        public boolean isAdditionalHeater() {
            return additionalHeater;
        }

        /**
         * @return "Erdwrmetauscher"
         */
        public boolean isEarthHeatExchanger() {
            return earthHeatExchanger;
        }

        /**
         * @return "Bypass"
         */
        public boolean isBypass() {
            return bypass;
        }

        /**
         * "Netzrelais Bypass"
         *
         * @return
         */
        public boolean isBypassRelay() {
            return bypassRelay;
        }

        /**
         * @return "Vorheizregister"
         */
        public boolean isPreheatingRadiator() {
            return preheatingRadiator;
        }

        /**
         * @return "Bedienteil aktiv"
         */
        public boolean isControlDeviceActive() {
            return controlDeviceActive;
        }

        /**
         * @return "Bedienung ber RS Schnittstelle"
         */
        public boolean isOpenhabInterfaceActive() {
            return openhabInterfaceActive;
        }

        /**
         * @return "Luftstufe vorhanden"
         */
        public boolean isVentilationLevelAvailable() {
            return ventilationLevelAvailable;
        }

        /**
         * @return "WW_Nachheizrgister"
         */
        public boolean isWarmWaterPostHeater() {
            return warmWaterPostHeater;
        }

        /**
         * @return "Magnetventil"
         */
        public boolean isMagnetValve() {
            return magnetValve;
        }

        /**
         * @return "Vorheizen aktiv"
         */
        public boolean isPreHeaterRadiatorActive() {
            return preHeaterRadiatorActive;
        }

    }

    /**
     * Check the different possible errors codes
     *
     * @author Christian Spiegel
     *
     */
    private static final class ErrorValueDecoder {

        private static final int ERROR_TEMP_SENSOR_SHORT = 1;
        private static final int ERROR_OFFSET = 2;
        private static final int ERROR_TEMP_SENSOR_INTERUPT = 3;
        private static final int ERROR_HIGH_PRESSURE = 4;
        private static final int ERROR_SYS_RAM = 61;
        private static final int ERROR_SYS_ROM = 62;
        private static final int ERROR_SYS_EE = 65;
        private static final int ERROR_SYS_IO = 66;
        private static final int ERROR_SYS_67_AD = 67;
        private static final int ERROR_SUPPLY_AIR = 128;
        private static final int ERROR_OUTGOING_AIR = 132;
        private static final int ERROR_CONDENSER = 130;
        private static final int ERROR_PREHEATING = 133;

        private boolean error_temp_sensor_short;
        private boolean error_offset;
        private boolean error_temp_sensor_interupt;
        private boolean error_high_pressure;
        private boolean error_sys_ram;
        private boolean error_sys_rom;
        private boolean error_sys_ee;
        private boolean error_sys_io;
        private boolean error_sys_67_ad;
        private boolean error_supply_air;
        private boolean error_outgoing_air;
        private boolean error_condenser;
        private boolean error_preheating;

        public static ErrorValueDecoder valueOf(String read) {

            ErrorValueDecoder errorValue = new ErrorValueDecoder();
            read = read.trim();
            int decPoint = read.indexOf(".");
            if (decPoint > 0) {
                read = read.substring(0, decPoint);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("WR3223.ErrorValueDecoder read string={}.", read);
            }
            int value = Integer.valueOf(read.trim());

            if ((value & ERROR_TEMP_SENSOR_SHORT) == ERROR_TEMP_SENSOR_SHORT) {
                errorValue.error_temp_sensor_short = true;
            }
            if ((value & ERROR_OFFSET) == ERROR_OFFSET) {
                errorValue.error_offset = true;
            }
            if ((value & ERROR_TEMP_SENSOR_INTERUPT) == ERROR_TEMP_SENSOR_INTERUPT) {
                errorValue.error_temp_sensor_interupt = true;
            }
            if ((value & ERROR_HIGH_PRESSURE) == ERROR_HIGH_PRESSURE) {
                errorValue.error_high_pressure = true;
            }
            if ((value & ERROR_SYS_RAM) == ERROR_SYS_RAM) {
                errorValue.error_sys_ram = true;
            }
            if ((value & ERROR_SYS_ROM) == ERROR_SYS_ROM) {
                errorValue.error_sys_rom = true;
            }
            if ((value & ERROR_SYS_EE) == ERROR_SYS_EE) {
                errorValue.error_sys_ee = true;
            }
            if ((value & ERROR_SYS_IO) == ERROR_SYS_IO) {
                errorValue.error_sys_io = true;
            }
            if ((value & ERROR_SYS_67_AD) == ERROR_SYS_67_AD) {
                errorValue.error_sys_67_ad = true;
            }
            if ((value & ERROR_SUPPLY_AIR) == ERROR_SUPPLY_AIR) {
                errorValue.error_supply_air = true;
            }
            if ((value & ERROR_OUTGOING_AIR) == ERROR_OUTGOING_AIR) {
                errorValue.error_outgoing_air = true;
            }
            if ((value & ERROR_CONDENSER) == ERROR_CONDENSER) {
                errorValue.error_condenser = true;
            }
            if ((value & ERROR_PREHEATING) == ERROR_PREHEATING) {
                errorValue.error_preheating = true;
            }
            return errorValue;
        }

        /**
         * @return the error_temp_sensor_short
         */
        public boolean isError_temp_sensor_short() {
            return error_temp_sensor_short;
        }

        /**
         * @return the error_offset
         */
        public boolean isError_offset() {
            return error_offset;
        }

        /**
         * @return the error_temp_sensor_interupt
         */
        public boolean isError_temp_sensor_interupt() {
            return error_temp_sensor_interupt;
        }

        /**
         * @return the error_high_pressure
         */
        public boolean isError_high_pressure() {
            return error_high_pressure;
        }

        /**
         * @return the error_sys_ram
         */
        public boolean isError_sys_ram() {
            return error_sys_ram;
        }

        /**
         * @return the error_sys_rom
         */
        public boolean isError_sys_rom() {
            return error_sys_rom;
        }

        /**
         * @return the error_sys_ee
         */
        public boolean isError_sys_ee() {
            return error_sys_ee;
        }

        /**
         * @return the error_sys_io
         */
        public boolean isError_sys_io() {
            return error_sys_io;
        }

        /**
         * @return the error_sys_67_ad
         */
        public boolean isError_sys_67_ad() {
            return error_sys_67_ad;
        }

        /**
         * @return the error_supply_air
         */
        public boolean isError_supply_air() {
            return error_supply_air;
        }

        /**
         * @return the error_outgoing_air
         */
        public boolean isError_outgoing_air() {
            return error_outgoing_air;
        }

        /**
         * @return the error_condenser
         */
        public boolean isError_condenser() {
            return error_condenser;
        }

        /**
         * @return the error_preheating
         */
        public boolean isError_preheating() {
            return error_preheating;
        }
    }

    /**
     * Check if EVU blockade is on/off.
     *
     * @author Christian Spiegel
     *
     */
    private static final class EvuBlockadeHandler {

        private static final int FLAG_BLOCKADE = 1;

        private boolean blockade = false;

        public static EvuBlockadeHandler valueOf(String read) {
            EvuBlockadeHandler handler = new EvuBlockadeHandler();
            read = read.trim();
            int decPoint = read.indexOf(".");
            if (decPoint > 0) {
                read = read.substring(0, decPoint);
            }

            int value = Integer.valueOf(read.trim()) * -1;
            if (logger.isDebugEnabled()) {
                logger.debug("WR3223.EvuBlockadeHandler value={}.", value);
            }

            if ((value & FLAG_BLOCKADE) == FLAG_BLOCKADE) {
                handler.blockade = true;
                // TODO Anlage ausschalten bei EVU Sperre?
            }

            return handler;

        }

        /**
         * @return the blockade
         */
        public boolean isBlockade() {
            return blockade;
        }
    }
}