org.openhab.binding.cardio2e.internal.Cardio2eBinding.java Source code

Java tutorial

Introduction

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

import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Objects;

import org.openhab.binding.cardio2e.Cardio2eBindingProvider;
import org.openhab.binding.cardio2e.internal.Cardio2eGenericBindingProvider.Cardio2eBindingConfig;
import org.openhab.binding.cardio2e.internal.Cardio2eGenericBindingProvider.Cardio2eBindingConfigItem;
import org.openhab.binding.cardio2e.internal.code.Cardio2eCurtainTransaction;
import org.openhab.binding.cardio2e.internal.code.Cardio2eDateTimeTransaction;
import org.openhab.binding.cardio2e.internal.code.Cardio2eDecodedTransactionEvent;
import org.openhab.binding.cardio2e.internal.code.Cardio2eDecoder;
import org.openhab.binding.cardio2e.internal.code.Cardio2eDecoder.Cardio2eDecodedTransactionListener;
import org.openhab.binding.cardio2e.internal.code.Cardio2eHvacControlTransaction;
import org.openhab.binding.cardio2e.internal.code.Cardio2eHvacSystemModes;
import org.openhab.binding.cardio2e.internal.code.Cardio2eHvacTemperatureTransaction;
import org.openhab.binding.cardio2e.internal.code.Cardio2eLightingTransaction;
import org.openhab.binding.cardio2e.internal.code.Cardio2eLoginTransaction;
import org.openhab.binding.cardio2e.internal.code.Cardio2eObjectTypes;
import org.openhab.binding.cardio2e.internal.code.Cardio2eRelayTransaction;
import org.openhab.binding.cardio2e.internal.code.Cardio2eScenarioTransaction;
import org.openhab.binding.cardio2e.internal.code.Cardio2eSecurityTransaction;
import org.openhab.binding.cardio2e.internal.code.Cardio2eTransaction;
import org.openhab.binding.cardio2e.internal.code.Cardio2eTransactionTypes;
import org.openhab.binding.cardio2e.internal.code.Cardio2eZoneBypassStates;
import org.openhab.binding.cardio2e.internal.code.Cardio2eZoneStates;
import org.openhab.binding.cardio2e.internal.code.Cardio2eZonesBypassTransaction;
import org.openhab.binding.cardio2e.internal.code.Cardio2eZonesTransaction;
import org.openhab.binding.cardio2e.internal.com.Cardio2eCom;
import org.openhab.binding.cardio2e.internal.com.Cardio2eCom.Cardio2eComEventListener;
import org.openhab.binding.cardio2e.internal.com.Cardio2eConnectionEvent;
import org.openhab.binding.cardio2e.internal.com.Cardio2eReceivedDataEvent;
import org.apache.commons.lang.StringUtils;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.binding.BindingConfig;
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.UpDownType;
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;

/**
 * Implement this class if you are going create an actively polling service like
 * querying a Website/Device.
 * 
 * @author Manuel Alberto Guerrero Daz
 * @since 1.11.0
 */
public class Cardio2eBinding extends AbstractActiveBinding<Cardio2eBindingProvider> {

    private static final Logger logger = LoggerFactory.getLogger(Cardio2eBinding.class);
    private String port;
    private String programCode;
    private String securityCode;
    private boolean zoneStateDetection;
    private long zoneUnchangedMinRefreshDelay;
    private short datetimeMaxOffset;
    private boolean firstUpdateWillSetDatetime;
    private short allowedDatetimeUpdateHour;
    private boolean testMode;
    private Cardio2eCom com = null;
    private Cardio2eDecoder decoder = null;
    private ReceivedDataListener receivedDataListener = null;
    private DecodedTransactionListener decodedTransactionListener = null;
    private boolean loggedIn = false;
    private boolean executedDatetimeUpdate = false;
    private LiveDateTime pendingDateTimeUpdate = new LiveDateTime();
    private long lastDateTimeRequestTimestamp = 0;
    private static final long DATE_TIME_GET_MIN_DELAY = 30000;

    /**
     * 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.
     */
    @SuppressWarnings("unused")
    private BundleContext bundleContext;

    /**
     * The refresh interval that is used to periodically request date and time
     * values from Cardio2e (fixed value set to 30000ms)
     */
    private long refreshInterval = 30000;

    public Cardio2eBinding() {
    }

    /**
     * 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;

        logger.debug("activate");
        initializeCardio2eBinding(configuration);
    }

    /**
     * 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) {
        // Update the internal configuration accordingly
        logger.info(
                "Configuration was been modified. Cardio2e binding will be deactivated and reactivated to apply new configuration...");
        purgeCardio2eBinding();
        initializeCardio2eBinding(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;
        // deallocate resources here that are no longer needed and
        // should be reset when activating this binding again
        logger.debug("deactivate");
        purgeCardio2eBinding();
    }

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

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

    /**
     * @{inheritDoc
     */
    @Override
    protected void execute() {
        // The frequently executed code (polling) goes here ...
        logger.debug("execute() method is called!");
        if (loggedIn) {
            // Cardio 2 date and time cyclic ask code used for progressive
            // update only
            if ((datetimeMaxOffset >= 0) && (pendingDateTimeUpdate.getDateTime() != null)) {
                getCardioDateTime();
            }
        }
    }

    public void initializeCardio2eBinding(final Map<String, Object> configuration) {
        int minDelayBetweenReceivingAndSending = 0;
        int minDelayBetweenSendings = 0;
        // Set default values
        port = null;
        programCode = "00000";
        securityCode = null;
        zoneStateDetection = false;
        zoneUnchangedMinRefreshDelay = 600000;
        datetimeMaxOffset = 15;
        firstUpdateWillSetDatetime = false;
        allowedDatetimeUpdateHour = -1;
        testMode = false;
        Cardio2eTransactionParser.filterUnnecessaryCommand = false;
        Cardio2eTransactionParser.filterUnnecessaryReverseModeUpdate = true;
        Cardio2eLightingTransaction.setSmartSendingEnabledClass(false);
        Cardio2eRelayTransaction.setSmartSendingEnabledClass(false);
        Cardio2eHvacControlTransaction.setSmartSendingEnabledClass(false);
        Cardio2eDateTimeTransaction.setSmartSendingEnabledClass(false);
        Cardio2eScenarioTransaction.setSmartSendingEnabledClass(false);
        Cardio2eSecurityTransaction.setSmartSendingEnabledClass(false);
        Cardio2eZonesBypassTransaction.setSmartSendingEnabledClass(false);
        Cardio2eCurtainTransaction.setSmartSendingEnabledClass(false);
        // Load config values
        String portString = Objects.toString(configuration.get("port"), null);
        if (StringUtils.isNotBlank(portString)) {
            port = portString;
            logger.info("Serial port set from config file: {}", port);
        }
        if (port != null) {
            String programcodeString = Objects.toString(configuration.get("programcode"), null);
            if (StringUtils.isNotBlank(programcodeString)) {
                programCode = programcodeString;
                logger.info("Program code updated from config file");
            }

            String securitycodeString = Objects.toString(configuration.get("securitycode"), null);
            if (StringUtils.isNotBlank(securitycodeString)) {
                securityCode = securitycodeString;
                logger.info("Security code updated from config file");
            }

            String zonesString = Objects.toString(configuration.get("zones"), null);
            if (StringUtils.isNotBlank(zonesString)) {
                zoneStateDetection = Boolean.parseBoolean(zonesString);
                logger.info("Zone state detection {} in config file",
                        (zoneStateDetection ? "enabled" : "disabled"));
            }

            String zoneUnchangedMinRefreshDelayString = Objects
                    .toString(configuration.get("zoneUnchangedMinRefreshDelay"), null);
            if (StringUtils.isNotBlank(zoneUnchangedMinRefreshDelayString)) {
                zoneUnchangedMinRefreshDelay = Long.parseLong(zoneUnchangedMinRefreshDelayString);
                logger.info(
                        "Zone state unchanged minimum refresh delay updated to {} milliseconds from config file",
                        zoneUnchangedMinRefreshDelayString);
            }

            String datetimeMaxOffsetString = Objects.toString(configuration.get("datetimeMaxOffset"), null);
            if (StringUtils.isNotBlank(datetimeMaxOffsetString)) {
                datetimeMaxOffset = Short.parseShort(datetimeMaxOffsetString);
                if (datetimeMaxOffset < -1) {
                    logger.info(
                            "Date and time direct update selected from config file (always sends updates, no filters, even if current date and time of Cardio 2 matches the update)");
                } else {
                    if (datetimeMaxOffset > 0) {
                        logger.info("Date and time maximum offset updated to {} minutes from config file",
                                datetimeMaxOffsetString);
                    } else {
                        if (datetimeMaxOffset == 0)
                            logger.info("Date and time maximum offset disabled from config file");
                        if (datetimeMaxOffset == -1)
                            logger.info(
                                    "Date and time both, progressive update and maximum offset, disabled from config file");
                    }
                }
            }

            String firstUpdateWillSetDatetimeString = Objects
                    .toString(configuration.get("firstUpdateWillSetDatetime"), null);
            if (StringUtils.isNotBlank(firstUpdateWillSetDatetimeString)) {
                firstUpdateWillSetDatetime = Boolean.parseBoolean(firstUpdateWillSetDatetimeString);
                logger.info("In the configuration file was {} that the first update always sets the date and time",
                        (firstUpdateWillSetDatetime ? "enabled" : "disabled"));
            }

            String allowedDatetimeUpdateHourString = Objects
                    .toString(configuration.get("allowedDatetimeUpdateHour"), null);
            if (StringUtils.isNotBlank(allowedDatetimeUpdateHourString)) {
                allowedDatetimeUpdateHour = Short.parseShort(allowedDatetimeUpdateHourString);
                if ((allowedDatetimeUpdateHour >= 0) && (allowedDatetimeUpdateHour <= 23)) {
                    logger.info("Allowed date and time update hour set to '{}' in config file",
                            allowedDatetimeUpdateHour);
                } else {
                    allowedDatetimeUpdateHour = -1;
                    logger.info("Allowed date and time update hour limit disabled in config file");
                }
            }

            String testmodeString = Objects.toString(configuration.get("testmode"), null);
            if (StringUtils.isNotBlank(testmodeString)) {
                testMode = Boolean.parseBoolean(testmodeString);
                logger.info("Test mode {} in config file", (testMode ? "enabled" : "disabled"));
            }

            String minDelayBetweenReceivingAndSendingString = Objects
                    .toString(configuration.get("minDelayBetweenReceivingAndSending"), null);
            if (StringUtils.isNotBlank(minDelayBetweenReceivingAndSendingString)) {
                minDelayBetweenReceivingAndSending = Integer.parseInt(minDelayBetweenReceivingAndSendingString);
                if (minDelayBetweenReceivingAndSending > 0)
                    logger.info("Minimum delay between receiving and sending updated to {} ms. from config file",
                            minDelayBetweenReceivingAndSending);
            }

            String minDelayBetweenSendingsString = Objects.toString(configuration.get("minDelayBetweenSendings"),
                    null);
            if (StringUtils.isNotBlank(minDelayBetweenSendingsString)) {
                minDelayBetweenSendings = Integer.parseInt(minDelayBetweenSendingsString);
                if (minDelayBetweenSendings > 0)
                    logger.info("Minimum delay between sendings updated to {} ms. from config file",
                            minDelayBetweenSendings);
            }

            String filterUnnecessaryCommandString = Objects.toString(configuration.get("filterUnnecessaryCommand"),
                    null);
            if (StringUtils.isNotBlank(filterUnnecessaryCommandString)) {
                Cardio2eTransactionParser.filterUnnecessaryCommand = Boolean
                        .parseBoolean(filterUnnecessaryCommandString);
                logger.info("Filter unnecessary command {} in config file",
                        (Cardio2eTransactionParser.filterUnnecessaryCommand ? "enabled" : "disabled"));
            }

            String filterUnnecessaryReverseModeUpdateFilterString = Objects
                    .toString(configuration.get("filterUnnecessaryReverseModeUpdate"), null);
            if (StringUtils.isNotBlank(filterUnnecessaryReverseModeUpdateFilterString)) {
                Cardio2eTransactionParser.filterUnnecessaryReverseModeUpdate = Boolean
                        .parseBoolean(filterUnnecessaryReverseModeUpdateFilterString);
                logger.info("Filter unnecessary reverse mode update {} in config file",
                        (Cardio2eTransactionParser.filterUnnecessaryReverseModeUpdate ? "enabled" : "disabled"));
            }

            String smartSendingEnabledObjectTypesString = Objects
                    .toString(configuration.get("smartSendingEnabledObjectTypes"), null);
            if (StringUtils.isNotBlank(smartSendingEnabledObjectTypesString)) {
                smartSendingEnabledObjectTypesString = smartSendingEnabledObjectTypesString.toUpperCase();
                if (smartSendingEnabledObjectTypesString.contains(Cardio2eObjectTypes.LIGHTING.name())) {
                    Cardio2eLightingTransaction.setSmartSendingEnabledClass(true);
                    logger.info("Smart sending enabled for LIGHTING object type in config file");
                }
                if (smartSendingEnabledObjectTypesString.contains(Cardio2eObjectTypes.RELAY.name())) {
                    Cardio2eRelayTransaction.setSmartSendingEnabledClass(true);
                    logger.info("Smart sending enabled for RELAY object type in config file");
                }
                if (smartSendingEnabledObjectTypesString.contains(Cardio2eObjectTypes.HVAC_CONTROL.name())) {
                    Cardio2eHvacControlTransaction.setSmartSendingEnabledClass(true);
                    logger.info("Smart sending enabled for HVAC_CONTROL object type in config file");
                }
                if (smartSendingEnabledObjectTypesString.contains(Cardio2eObjectTypes.DATE_AND_TIME.name())) {
                    Cardio2eDateTimeTransaction.setSmartSendingEnabledClass(true);
                    logger.info("Smart sending enabled for DATE_AND_TIME object type in config file");
                }
                if (smartSendingEnabledObjectTypesString.contains(Cardio2eObjectTypes.SCENARIO.name())) {
                    Cardio2eScenarioTransaction.setSmartSendingEnabledClass(true);
                    logger.info("Smart sending enabled for SCENARIO object type in config file");
                }
                if (smartSendingEnabledObjectTypesString.contains(Cardio2eObjectTypes.SECURITY.name())) {
                    Cardio2eSecurityTransaction.setSmartSendingEnabledClass(true);
                    logger.info("Smart sending enabled for SECURITY object type in config file");
                }
                if (smartSendingEnabledObjectTypesString.contains(Cardio2eObjectTypes.ZONES_BYPASS.name())) {
                    Cardio2eZonesBypassTransaction.setSmartSendingEnabledClass(true);
                    logger.info("Smart sending enabled for ZONES_BYPASS object type in config file");
                }
                if (smartSendingEnabledObjectTypesString.contains(Cardio2eObjectTypes.CURTAIN.name())) {
                    Cardio2eCurtainTransaction.setSmartSendingEnabledClass(true);
                    logger.info("Smart sending enabled for CURTAIN object type in config file");
                }
            }

            // Read further config parameters here ...

            com = new Cardio2eCom();
            decoder = com.decoder;
            receivedDataListener = new ReceivedDataListener();
            decodedTransactionListener = new DecodedTransactionListener();
            com.addReceivedDataListener(receivedDataListener);
            decoder.addDecodedTransactionListener(decodedTransactionListener);
            com.testMode = testMode;
            decoder.decodeZonesStateTransaction = zoneStateDetection;
            com.setSerialPort(port);
            if (minDelayBetweenReceivingAndSending > 0)
                com.setMinDelayBetweenReceivingAndSending(minDelayBetweenReceivingAndSending);
            if (minDelayBetweenSendings > 0)
                com.setMinDelayBetweenSendings(minDelayBetweenSendings);

            try {
                com.sendTransaction(new Cardio2eLoginTransaction(programCode)); // Login
                // request
            } catch (Exception ex) {
                logger.warn("Failed to send login request: '{}'", ex.toString());
            }

            if (testMode)
                loggedIn = true;

            setProperlyConfigured(true);

            logger.debug("Cardio2e binding activated");
        } else
            logger.warn("Cardio2e binding cannot be activated because no serial port is set in config file");
    }

    public void purgeCardio2eBinding() {
        if (com != null) {
            com.disconnect();
            loggedIn = false;
            executedDatetimeUpdate = false;
            pendingDateTimeUpdate.setDateTime(null);
            com.removeReceivedDataListener(receivedDataListener);
            decoder.removeDecodedTransactionListener(decodedTransactionListener);
            decodedTransactionListener = null;
            receivedDataListener = null;
            decoder = null;
            com = null;
            port = null;
            logger.debug("Cardio2e binding deactivated");
        }
    }

    /**
     * @{inheritDoc
     */
    @Override
    protected void internalReceiveCommand(String itemName, Command command) {
        // The code being executed when a command was sent on the openHAB
        // event bus goes here. This method is only called if one of the
        // BindingProviders provide a binding for the given 'itemName'.
        logger.debug("internalReceiveCommand() is called, with itemName '{}' and Command '{}'!", itemName, command);
        if (loggedIn) {
            Cardio2eTransaction transaction;
            Cardio2eBindingConfig config = null;
            boolean reverseOrder;
            Cardio2eBindingProvider provider = null;
            if (!providers.isEmpty()) {
                provider = providers.iterator().next(); // Multiple providers
                // are not supported, so
                // will use first
                // available provider
                logger.debug("Checking provider with names {}", provider.getItemNames());
                transaction = null;
                reverseOrder = false;
                if (command instanceof UpDownType) { // Rollershutter hardware
                    // security patch for
                    // pair of relay control
                    // (reverse config order
                    // to do stop down
                    // before up)
                    if ((UpDownType) command == UpDownType.UP) {
                        reverseOrder = true;
                    }
                }
                if (reverseOrder) { // Get config in reverse or normal order
                    config = provider.getReverseOrderConfig(itemName);
                } else {
                    config = provider.getConfig(itemName);
                }
                for (Cardio2eBindingConfigItem configItem : config) {
                    try {
                        synchronized (configItem.transaction) {
                            transaction = configItem.transaction.deepClone();
                        }
                        if (!(configItem.reverseMode)) {
                            switch (transaction.getObjectType()) {
                            case SECURITY:
                            case SCENARIO:
                                Cardio2eTransactionParser.commandToTransaction(command, transaction, securityCode);
                                break;
                            case DATE_AND_TIME:
                                pendingDateTimeUpdate.setDateTime(null);
                                logger.debug("Cancelled any date and time update task");
                            default:
                                Cardio2eTransactionParser.commandToTransaction(command, transaction, null);
                            }
                            if (transaction.mustBeSent) {
                                transaction.primitiveStringTransaction = transaction.toString();
                                logger.debug("Adding '{}' to Cardio 2 RS-232 send queue",
                                        transaction.primitiveStringTransaction.substring(0,
                                                transaction.primitiveStringTransaction.length() - 1));
                                com.sendTransaction(transaction);
                            } else {
                                logger.debug(
                                        "No command sent to Cardio 2 because it is not necessary to send current object value again");
                            }
                        } else {
                            logger.debug(
                                    "No command sent to Cardio 2 because item is configured as 'reverse mode'");
                        }
                    } catch (Exception ex) {
                        logger.warn("Error in processing command: '{}'", ex.toString());
                    }
                }
            } else {
                logger.warn("No command will be sent to Cardio 2 because no provider is available");
            }
        } else {
            logger.warn("Cannot send command to Cardio 2 because we are not logged in");
        }
    }

    /**
     * @{inheritDoc
     */
    @Override
    protected void internalReceiveUpdate(String itemName, State newState) {
        // The code being executed when a state was sent on the openHAB
        // event bus goes here. This method is only called if one of the
        // BindingProviders provide a binding for the given 'itemName'.
        logger.debug("internalReceiveUpdate() is called, with itemName '{}' and State '{}'!", itemName, newState);
        if (loggedIn) {
            Cardio2eTransaction transaction;
            Cardio2eBindingProvider provider = null;
            if (!providers.isEmpty()) {
                provider = providers.iterator().next(); // Multiple providers
                // are not supported, so
                // will use first
                // available provider
                logger.debug("Checking provider with names '{}'", provider.getItemNames());
                transaction = null;
                for (Cardio2eBindingConfigItem configItem : provider.getConfig(itemName)) {
                    try {
                        if (configItem.reverseMode) {
                            synchronized (configItem) {
                                transaction = configItem.transaction.deepClone();
                                Cardio2eTransactionParser.stateToTransaction(newState, transaction);
                                if (transaction.mustBeSent) {
                                    configItem.setPendingUpdates(configItem.getPendingUpdates() + 1);
                                    transaction.primitiveStringTransaction = transaction.toString();
                                    logger.debug("Adding '{}' to Cardio 2 RS-232 send queue",
                                            transaction.primitiveStringTransaction.substring(0,
                                                    transaction.primitiveStringTransaction.length() - 1));
                                    com.sendTransaction(transaction);
                                } else {
                                    logger.debug(
                                            "No state sent to Cardio 2 because it is not necessary to send current object value again");
                                }
                            }
                        } else {
                            if (configItem.transaction.getObjectType() == Cardio2eObjectTypes.DATE_AND_TIME) {
                                DateTimeType newDateTimeState = (DateTimeType) newState;
                                if ((allowedDatetimeUpdateHour == -1)
                                        || (newDateTimeState.getCalendar()
                                                .get(Calendar.HOUR_OF_DAY) == allowedDatetimeUpdateHour)
                                        || ((firstUpdateWillSetDatetime) && (!executedDatetimeUpdate))) {
                                    // Allow date and time update if
                                    // allowedDatetimeUpdateHour is -1 or
                                    // allowedDatetimeUpdateHour is newState
                                    // hour (or first update when
                                    // firstUpdateWillSetDatetime)
                                    if ((datetimeMaxOffset <= -2)
                                            || ((firstUpdateWillSetDatetime) && (!executedDatetimeUpdate))) {
                                        // Direct update
                                        executedDatetimeUpdate = true;
                                        pendingDateTimeUpdate.setDateTime(null);
                                        transaction = new Cardio2eDateTimeTransaction(Cardio2eTransactionTypes.SET);
                                        Cardio2eTransactionParser.stateToTransaction(newDateTimeState, transaction);
                                        transaction.primitiveStringTransaction = transaction.toString();
                                        logger.debug("Adding '{}' to Cardio 2 RS-232 send queue",
                                                transaction.primitiveStringTransaction.substring(0,
                                                        transaction.primitiveStringTransaction.length() - 1));
                                        com.sendTransaction(transaction);
                                    } else {
                                        // Will ask Cardio 2 for current date
                                        // and time (GET transaction). When
                                        // Cardio answers, will send new value
                                        // (SET) if offset is less than
                                        // datetimeMaxOffset.
                                        pendingDateTimeUpdate.setDateTime((DateTimeType) newDateTimeState);
                                        logger.debug(
                                                "Executing date and time update task. Now will ask Cardio for current date and time...");
                                        getCardioDateTime();
                                    }
                                } else {
                                    logger.debug(
                                            "Date and time update ignored because time is not betwen {}:00 and {}:59",
                                            allowedDatetimeUpdateHour, allowedDatetimeUpdateHour);
                                }
                            } else {
                                logger.debug(
                                        "No state sent to Cardio 2 because item is not configured as 'reverse mode'");
                            }
                        }
                    } catch (Exception ex) {
                        logger.warn("Error in processing state: '{}'", ex.toString());
                    }
                }
            } else {
                logger.warn("No state will be sent to Cardio 2 because no provider is available");
            }
        } else {
            logger.warn("Cannot send state to Cardio 2 because we are not logged in");
        }
    }

    private void getCardioDateTime() {
        if ((datetimeMaxOffset < 0)
                || ((System.currentTimeMillis() - lastDateTimeRequestTimestamp) >= DATE_TIME_GET_MIN_DELAY)) {
            lastDateTimeRequestTimestamp = System.currentTimeMillis();
            try {
                logger.debug("Date and time update in course (adding GET request to Cardio 2 RS-232 send queue)");
                com.sendTransaction(new Cardio2eDateTimeTransaction(Cardio2eTransactionTypes.GET));
            } catch (Exception ex) {
                logger.warn("Failed to send GET date and time request: '{}'", ex.toString());
            }
        }
    }

    private class LiveDateTime {
        private DateTimeType dateTime = null;
        private long timestamp;

        public LiveDateTime() {
        }

        public void setDateTime(DateTimeType dateTime) {
            synchronized (this) {
                timestamp = System.currentTimeMillis();
                this.dateTime = dateTime;
            }
        }

        public DateTimeType getDateTime() {
            synchronized (this) {
                return this.dateTime;
            }
        }

        public DateTimeType getUpdatedDateTime() {
            synchronized (this) {
                DateTimeType dateTimeUpdate = null;
                if (this.dateTime != null) {
                    dateTimeUpdate = new DateTimeType();
                    dateTimeUpdate.getCalendar().setTimeInMillis(this.dateTime.getCalendar().getTimeInMillis()
                            + (System.currentTimeMillis() - timestamp));
                }
                return dateTimeUpdate;
            }
        }
    }

    private class ReceivedDataListener implements Cardio2eComEventListener {
        public ReceivedDataListener() {
        }

        public void receivedData(Cardio2eReceivedDataEvent e) {
        }

        public void isConnected(Cardio2eConnectionEvent e) {
            logger.info("Cardio is {}.", (e.getIsConnected() ? "CONNECTED" : "DISCONNECTED"));
        }
    }

    private class DecodedTransactionListener implements Cardio2eDecodedTransactionListener {
        public DecodedTransactionListener() {
        }

        public void decodedTransaction(Cardio2eDecodedTransactionEvent e) {
            Cardio2eTransaction transaction = e.getDecodedTransaction();
            Cardio2eBindingConfig config;
            Command command;
            State newState;
            switch (transaction.getTransactionType()) {
            case INFORMATION:
                switch (transaction.getObjectType()) {
                case LOGIN: // Decoded login confirmation "@I P E"
                    loggedIn = true;
                    logger.info("Cardio login succeed.");
                    break;
                case LIGHTING:
                    Cardio2eLightingTransaction receivedLightingTransaction;
                    Cardio2eLightingTransaction configLightingTransaction;
                    for (Cardio2eBindingProvider provider : providers) {
                        Map<String, BindingConfig> matchBindingConfigs = provider
                                .getMatchBindingConfigs(transaction);
                        for (String itemName : matchBindingConfigs.keySet()) {
                            try {
                                config = (Cardio2eBindingConfig) matchBindingConfigs.get(itemName);
                                for (Cardio2eBindingConfigItem configItem : config) {
                                    receivedLightingTransaction = (Cardio2eLightingTransaction) transaction;
                                    configLightingTransaction = (Cardio2eLightingTransaction) configItem.transaction;
                                    configLightingTransaction
                                            .setLightIntensity(receivedLightingTransaction.getLightIntensity()); // Stores
                                    // last
                                    // received
                                    // light
                                    // intensity
                                    // (example:
                                    // for
                                    // increase
                                    // /
                                    // decrease
                                    // commands)
                                    if (configItem.reverseMode) {
                                        int pendingUpdates = configItem.getPendingUpdates();
                                        if (pendingUpdates > 0) {
                                            configItem.setPendingUpdates(pendingUpdates - 1);
                                        } else if (loggedIn) {
                                            command = Cardio2eTransactionParser.transactionToCommand(
                                                    configLightingTransaction, provider.getItem(itemName));
                                            logger.debug("Sending LIGHT #{} {} command to the bus.",
                                                    configLightingTransaction.getObjectNumber(), command);
                                            eventPublisher.postCommand(itemName, command);
                                        }
                                    } else {
                                        newState = Cardio2eTransactionParser.transactionToState(
                                                configLightingTransaction, provider.getItem(itemName));
                                        logger.debug("Sending LIGHT #{} {} state to the bus.",
                                                configLightingTransaction.getObjectNumber(), newState);
                                        eventPublisher.postUpdate(itemName, newState);
                                    }
                                }
                            } catch (Exception ex) {
                                logger.warn("Error in processing decoded transaction: '{}'", ex);
                            }
                        }
                    }
                    break;
                case RELAY:
                    Cardio2eRelayTransaction receivedRelayTransaction;
                    Cardio2eRelayTransaction configRelayTransaction;
                    for (Cardio2eBindingProvider provider : providers) {
                        Map<String, BindingConfig> matchBindingConfigs = provider
                                .getMatchBindingConfigs(transaction);
                        for (String itemName : matchBindingConfigs.keySet()) {
                            try {
                                config = (Cardio2eBindingConfig) matchBindingConfigs.get(itemName);
                                for (Cardio2eBindingConfigItem configItem : config) {
                                    configRelayTransaction = (Cardio2eRelayTransaction) configItem.transaction;
                                    receivedRelayTransaction = (Cardio2eRelayTransaction) transaction;
                                    configRelayTransaction.setRelayState(receivedRelayTransaction.getRelayState());
                                    if (configItem.reverseMode) {
                                        if (configItem.getPendingUpdates() > 0) {
                                            configItem.setPendingUpdates(configItem.getPendingUpdates() - 1);
                                        } else if (loggedIn) {
                                            command = Cardio2eTransactionParser.transactionToCommand(
                                                    configRelayTransaction, provider.getItem(itemName));
                                            logger.debug("Sending RELAY #{} {} command to the bus.",
                                                    configRelayTransaction.getObjectNumber(), command);
                                            eventPublisher.postCommand(itemName, command);
                                        }
                                    } else if ((configRelayTransaction.getRelayON())
                                            || (!configRelayTransaction.getBlindMode())) { // Relay
                                        // blind
                                        // mode
                                        // do
                                        // not
                                        // report
                                        // OFF
                                        // states
                                        newState = Cardio2eTransactionParser.transactionToState(
                                                configRelayTransaction, provider.getItem(itemName));
                                        logger.debug("Sending RELAY #{} {} state to the bus.",
                                                configRelayTransaction.getObjectNumber(), newState);
                                        eventPublisher.postUpdate(itemName, newState);
                                    }
                                }
                            } catch (Exception ex) {
                                logger.warn("Error in processing decoded transaction: '{}'", ex);
                            }
                        }
                    }
                    break;
                case HVAC_TEMPERATURE:
                    Cardio2eHvacTemperatureTransaction receivedHvacTemperatureTransaction;
                    for (Cardio2eBindingProvider provider : providers) {
                        Map<String, BindingConfig> matchBindingConfigs = provider
                                .getMatchBindingConfigs(transaction);
                        for (String itemName : matchBindingConfigs.keySet()) {
                            try {
                                receivedHvacTemperatureTransaction = (Cardio2eHvacTemperatureTransaction) transaction;
                                newState = Cardio2eTransactionParser.transactionToState(
                                        receivedHvacTemperatureTransaction, provider.getItem(itemName));
                                logger.debug("Sending HVAC_TEMPERATURE #{} {} state to the bus.",
                                        receivedHvacTemperatureTransaction.getObjectNumber(), newState);
                                eventPublisher.postUpdate(itemName, newState);
                            } catch (Exception ex) {
                                logger.warn("Error in processing decoded transaction: '{}'", ex);
                            }
                        }
                    }
                    break;
                case HVAC_CONTROL:
                    Cardio2eHvacControlTransaction receivedHvacControlTransaction;
                    Cardio2eHvacControlTransaction configHvacControlTransaction;
                    for (Cardio2eBindingProvider provider : providers) {
                        Map<String, BindingConfig> matchBindingConfigs = provider
                                .getMatchBindingConfigs(transaction);
                        for (String itemName : matchBindingConfigs.keySet()) {
                            try {
                                config = (Cardio2eBindingConfig) matchBindingConfigs.get(itemName);
                                for (Cardio2eBindingConfigItem configItem : config) {
                                    receivedHvacControlTransaction = (Cardio2eHvacControlTransaction) transaction;
                                    synchronized (configItem.transaction) {
                                        configHvacControlTransaction = (Cardio2eHvacControlTransaction) configItem.transaction;
                                        configHvacControlTransaction.setHvacHeatingSetPoint(
                                                receivedHvacControlTransaction.getHvacHeatingSetPoint());
                                        configHvacControlTransaction.setHvacCoolingSetPoint(
                                                receivedHvacControlTransaction.getHvacCoolingSetPoint());
                                        configHvacControlTransaction
                                                .setHvacFanState(receivedHvacControlTransaction.getHvacFanState());
                                        configHvacControlTransaction.setHvacSystemMode(
                                                receivedHvacControlTransaction.getHvacSystemMode());
                                    }
                                    newState = Cardio2eTransactionParser.transactionToState(
                                            configHvacControlTransaction, provider.getItem(itemName));
                                    if (configHvacControlTransaction.singleHvacSystemMode == null) {
                                        logger.debug("Sending HVAC #{} {} state to the bus.",
                                                configHvacControlTransaction.getObjectNumber(), newState);
                                    } else {
                                        logger.debug("Sending HVAC #{} {} {} state to the bus.",
                                                configHvacControlTransaction.getObjectNumber(),
                                                (configHvacControlTransaction.singleHvacSystemMode == Cardio2eHvacSystemModes.OFF
                                                        ? "FAN"
                                                        : configHvacControlTransaction.singleHvacSystemMode),
                                                newState);
                                    }
                                    eventPublisher.postUpdate(itemName, newState);
                                }
                            } catch (Exception ex) {
                                logger.warn("Error in processing decoded transaction: '{}'", ex);
                            }
                        }
                    }
                    break;
                case ZONES:
                    Cardio2eZonesTransaction receivedZonesTransaction;
                    Cardio2eZonesTransaction configZonesTransaction;
                    for (Cardio2eBindingProvider provider : providers) {
                        Map<String, BindingConfig> matchBindingConfigs = provider
                                .getMatchBindingConfigs(transaction);
                        for (String itemName : matchBindingConfigs.keySet()) {
                            try {
                                config = (Cardio2eBindingConfig) matchBindingConfigs.get(itemName);
                                for (Cardio2eBindingConfigItem configItem : config) {
                                    configZonesTransaction = (Cardio2eZonesTransaction) configItem.transaction;
                                    receivedZonesTransaction = (Cardio2eZonesTransaction) transaction;
                                    short configZoneNumber = configZonesTransaction.getObjectNumber();
                                    short receivedFirstZoneNumber = receivedZonesTransaction.getObjectNumber();
                                    if (configZoneNumber >= receivedFirstZoneNumber) {
                                        Cardio2eZoneStates[] receivedZoneStates = receivedZonesTransaction
                                                .getZoneStates();
                                        int receivedZoneNumberOffset = (configZoneNumber - receivedFirstZoneNumber);
                                        if (receivedZoneStates.length > receivedZoneNumberOffset) {
                                            Cardio2eZoneStates receivedZoneState = receivedZoneStates[(configZoneNumber
                                                    - receivedFirstZoneNumber)];
                                            long timestamp = System.currentTimeMillis();
                                            if ((configZonesTransaction.getZoneState() != receivedZoneState)
                                                    || ((timestamp
                                                            - configItem.lastStateSentTimestamp) >= zoneUnchangedMinRefreshDelay)) {
                                                configZonesTransaction.setZoneState(receivedZoneState);
                                                newState = Cardio2eTransactionParser.transactionToState(
                                                        configZonesTransaction, provider.getItem(itemName));
                                                logger.debug("Sending ZONE #{} {} state to the bus.",
                                                        configZonesTransaction.getObjectNumber(), newState);
                                                eventPublisher.postUpdate(itemName, newState);
                                                configItem.lastStateSentTimestamp = timestamp;
                                            }
                                        }
                                    }
                                }
                            } catch (Exception ex) {
                                logger.warn("Error in processing decoded transaction: '{}'", ex);
                            }
                        }
                    }
                    break;
                case ZONES_BYPASS:
                    Cardio2eZonesBypassTransaction receivedZonesBypassTransaction;
                    Cardio2eZonesBypassTransaction configZonesBypassTransaction;
                    for (Cardio2eBindingProvider provider : providers) {
                        Map<String, BindingConfig> matchBindingConfigs = provider
                                .getMatchBindingConfigs(transaction);
                        for (String itemName : matchBindingConfigs.keySet()) {
                            try {
                                config = (Cardio2eBindingConfig) matchBindingConfigs.get(itemName);
                                for (Cardio2eBindingConfigItem configItem : config) {
                                    configZonesBypassTransaction = (Cardio2eZonesBypassTransaction) configItem.transaction;
                                    receivedZonesBypassTransaction = (Cardio2eZonesBypassTransaction) transaction;
                                    short configZoneBypassNumber = configZonesBypassTransaction.getObjectNumber();
                                    short receivedFirstZoneBypassNumber = receivedZonesBypassTransaction
                                            .getObjectNumber();
                                    if (configZoneBypassNumber >= receivedFirstZoneBypassNumber) {
                                        Cardio2eZoneBypassStates[] receivedZoneBypassStates = receivedZonesBypassTransaction
                                                .getZoneBypassStates();
                                        int receivedZoneBypassNumberOffset = (configZoneBypassNumber
                                                - receivedFirstZoneBypassNumber);
                                        if (receivedZoneBypassStates.length > receivedZoneBypassNumberOffset) {
                                            Cardio2eZoneBypassStates receivedZoneBypassState = receivedZoneBypassStates[(configZoneBypassNumber
                                                    - receivedFirstZoneBypassNumber)];
                                            long timestamp = System.currentTimeMillis();
                                            if ((configZonesBypassTransaction
                                                    .getZoneBypassState() != receivedZoneBypassState)
                                                    || ((timestamp
                                                            - configItem.lastStateSentTimestamp) >= zoneUnchangedMinRefreshDelay)) {
                                                configZonesBypassTransaction
                                                        .setZoneBypassState(receivedZoneBypassState);
                                                newState = Cardio2eTransactionParser.transactionToState(
                                                        configZonesBypassTransaction, provider.getItem(itemName));
                                                logger.debug("Sending ZONE #{} {} state to the bus.",
                                                        configZonesBypassTransaction.getObjectNumber(), newState);
                                                eventPublisher.postUpdate(itemName, newState);
                                                configItem.lastStateSentTimestamp = timestamp;
                                            }
                                        }
                                    }
                                }
                            } catch (Exception ex) {
                                logger.warn("Error in processing decoded transaction: '{}'", ex);
                            }
                        }
                    }
                    break;
                case SECURITY:
                    Cardio2eSecurityTransaction receivedSecurityTransaction;
                    for (Cardio2eBindingProvider provider : providers) {
                        Map<String, BindingConfig> matchBindingConfigs = provider
                                .getMatchBindingConfigs(transaction);
                        for (String itemName : matchBindingConfigs.keySet()) {
                            if (provider.getItem(itemName).getAcceptedDataTypes().contains(OnOffType.class)) { // Only sends
                                // states to
                                // Switch
                                // Items
                                try {
                                    receivedSecurityTransaction = (Cardio2eSecurityTransaction) transaction;
                                    newState = Cardio2eTransactionParser.transactionToState(
                                            receivedSecurityTransaction, provider.getItem(itemName));
                                    logger.debug("Sending SECURITY armed {} state to the bus.", newState);
                                    eventPublisher.postUpdate(itemName, newState);
                                } catch (Exception ex) {
                                    logger.warn("Error in processing decoded transaction: '{}'", ex.toString());
                                }
                            }
                        }
                    }
                    break;
                case DATE_AND_TIME:
                    // No updates will be sent to the openhab bus.
                    boolean canCleanpendingDateTimeUpdate = true;
                    synchronized (pendingDateTimeUpdate) {
                        try {
                            DateTimeType storedUpdate = pendingDateTimeUpdate.getDateTime();
                            if (storedUpdate != null) {
                                DateTimeType pendingUpdate = pendingDateTimeUpdate.getUpdatedDateTime();
                                Cardio2eDateTimeTransaction receivedDateTimeTransaction = (Cardio2eDateTimeTransaction) transaction;
                                Cardio2eDateTimeTransaction newDateTimeTransaction = new Cardio2eDateTimeTransaction();
                                Cardio2eTransactionParser.stateToTransaction(pendingUpdate, newDateTimeTransaction);
                                logger.debug("Received date and time from Cardio 2 : '{}'",
                                        newDateTimeTransaction.getDateTime().toString());
                                logger.debug("Received date and time state from bus : '{}'",
                                        storedUpdate.toString());
                                logger.debug("Updated target value would should be  : '{}'",
                                        pendingUpdate.toString());
                                if (receivedDateTimeTransaction.getDateTime().toDate()
                                        .equals(newDateTimeTransaction.getDateTime().toDate())) {
                                    // New and received transactions dates are
                                    // equals, will not send new datetime
                                    logger.debug(
                                            "Cardio 2 current date and time match new updated state, so we will not send it");
                                    pendingDateTimeUpdate.setDateTime(null);
                                } else {
                                    // There is a different
                                    // pendingDateTimeUpdate and offset is not
                                    // greater than datetimeMaxOffset (or
                                    // datetimeMaxOffset<=0) will update Cardio
                                    // 2 date and time
                                    long dateTimeOffset = 0;
                                    if (datetimeMaxOffset > 0)
                                        dateTimeOffset = Math
                                                .abs(receivedDateTimeTransaction.getDateTime().toDate().getTime()
                                                        - newDateTimeTransaction.getDateTime().toDate().getTime())
                                                / 60000;
                                    if ((datetimeMaxOffset <= 0) || (dateTimeOffset <= datetimeMaxOffset)) {
                                        newDateTimeTransaction.smartSendingEnabledTransaction = true;
                                        newDateTimeTransaction.smartSendingEnqueueFirst = true;
                                        if (datetimeMaxOffset > 0)
                                            logger.debug(
                                                    "Date and time {} minutes offset is OK (maximum is {} minutes)",
                                                    dateTimeOffset, datetimeMaxOffset);
                                        if ((datetimeMaxOffset >= 0) && (receivedDateTimeTransaction.getDateTime()
                                                .toDate().before(newDateTimeTransaction.getDateTime().toDate()))) {
                                            // Progressive update is enabled,
                                            // and received date is before than
                                            // new one: will do a progressive
                                            // update, minute by minute
                                            Date receivedDatePlusMinute = new Date();
                                            receivedDatePlusMinute.setTime(
                                                    (receivedDateTimeTransaction.getDateTime().toDate().getTime()
                                                            + 60000));
                                            newDateTimeTransaction.getDateTime().fromDate(receivedDatePlusMinute);
                                            canCleanpendingDateTimeUpdate = false;
                                            logger.debug("Doing progressive aproach: will advance a minute");
                                        }
                                        newDateTimeTransaction.primitiveStringTransaction = newDateTimeTransaction
                                                .toString();
                                        logger.debug("Sending '{}'",
                                                newDateTimeTransaction.primitiveStringTransaction.substring(0,
                                                        newDateTimeTransaction.primitiveStringTransaction.length()
                                                                - 1));
                                        com.sendTransaction(newDateTimeTransaction);
                                    } else {
                                        logger.debug(
                                                "Date and time {} minutes offset is too much (maximum is {} minutes). New date and time will be discarded",
                                                dateTimeOffset, datetimeMaxOffset);
                                    }
                                }
                            }
                        } catch (Exception ex) {
                            logger.warn("Error in processing decoded transaction: '{}'", ex);
                        } finally {
                            if (canCleanpendingDateTimeUpdate) {
                                pendingDateTimeUpdate.setDateTime(null);
                                logger.debug("Finished date and time update task");
                            }
                        }
                    }
                    break;
                case CURTAIN:
                    Cardio2eCurtainTransaction receivedCurtainTransaction;
                    Cardio2eCurtainTransaction configCurtainTransaction;
                    for (Cardio2eBindingProvider provider : providers) {
                        Map<String, BindingConfig> matchBindingConfigs = provider
                                .getMatchBindingConfigs(transaction);
                        for (String itemName : matchBindingConfigs.keySet()) {
                            try {
                                config = (Cardio2eBindingConfig) matchBindingConfigs.get(itemName);
                                for (Cardio2eBindingConfigItem configItem : config) {
                                    receivedCurtainTransaction = (Cardio2eCurtainTransaction) transaction;
                                    configCurtainTransaction = (Cardio2eCurtainTransaction) configItem.transaction;
                                    configCurtainTransaction.setClosingPercentage(
                                            receivedCurtainTransaction.getClosingPercentage()); // Stores
                                    // last
                                    // received
                                    // closing
                                    // percentage
                                    if (configItem.reverseMode) {
                                        int pendingUpdates = configItem.getPendingUpdates();
                                        if (pendingUpdates > 0) {
                                            configItem.setPendingUpdates(pendingUpdates - 1);
                                        } else if (loggedIn) {
                                            command = Cardio2eTransactionParser.transactionToCommand(
                                                    configCurtainTransaction, provider.getItem(itemName));
                                            logger.debug("Sending CURTAIN #{} {} command to the bus.",
                                                    configCurtainTransaction.getObjectNumber(), command);
                                            eventPublisher.postCommand(itemName, command);
                                        }
                                    } else {
                                        newState = Cardio2eTransactionParser.transactionToState(
                                                configCurtainTransaction, provider.getItem(itemName));
                                        logger.debug("Sending CURTAIN #{} {} state to the bus.",
                                                configCurtainTransaction.getObjectNumber(), newState);
                                        eventPublisher.postUpdate(itemName, newState);
                                    }
                                }
                            } catch (Exception ex) {
                                logger.warn("Error in processing decoded transaction: '{}'", ex);
                            }
                        }
                    }
                    break;
                default:
                    logger.debug("No actions defined for received {} information type transactions",
                            transaction.getObjectType());
                }
                break;
            case ACK:
                if (transaction.getObjectType() == Cardio2eObjectTypes.SECURITY) { // Reports
                    // NO
                    // error
                    // code
                    // for
                    // security
                    // object
                    // NACK
                    for (Cardio2eBindingProvider provider : providers) {
                        Map<String, BindingConfig> matchBindingConfigs = provider
                                .getMatchBindingConfigs(transaction);
                        for (String itemName : matchBindingConfigs.keySet()) {
                            if (provider.getItem(itemName).getAcceptedDataTypes().contains(DecimalType.class)) { // Only
                                // sends
                                // NACK NO
                                // error
                                // value to
                                // Number
                                // Items
                                try {
                                    logger.debug("Sending SECURITY NO error code (ZERO) state to the bus.");
                                    eventPublisher.postUpdate(itemName, DecimalType.ZERO);
                                } catch (Exception ex) {
                                    logger.warn(
                                            "Error in reporting no error code for SECURITY ACK transaction: '{}'",
                                            ex);
                                }
                            }
                        }
                    }
                }
                break;
            case NACK:
                switch (transaction.getObjectType()) {
                case LOGIN:
                case DATE_AND_TIME:
                case SCENARIO:
                    logger.debug("NACK: {} transaction NOT ACCEPTED. {}.", transaction.getObjectType(),
                            transaction.getErrorCodeDescription());
                    break;
                default:
                    if (transaction.getObjectType() == Cardio2eObjectTypes.SECURITY) { // Reports
                        // error
                        // code
                        // for
                        // security
                        // object
                        // NACK
                        Cardio2eSecurityTransaction receivedSecurityTransaction;
                        for (Cardio2eBindingProvider provider : providers) {
                            Map<String, BindingConfig> matchBindingConfigs = provider
                                    .getMatchBindingConfigs(transaction);
                            for (String itemName : matchBindingConfigs.keySet()) {
                                if (provider.getItem(itemName).getAcceptedDataTypes().contains(DecimalType.class)) { // Only
                                    // sends
                                    // NACK
                                    // error
                                    // values
                                    // to
                                    // Number
                                    // Items
                                    try {
                                        receivedSecurityTransaction = (Cardio2eSecurityTransaction) transaction;
                                        newState = Cardio2eTransactionParser.transactionToState(
                                                receivedSecurityTransaction, provider.getItem(itemName));
                                        logger.debug("Sending SECURITY NACK error code {} to the bus.", newState);
                                        eventPublisher.postUpdate(itemName, newState);
                                    } catch (Exception ex) {
                                        logger.warn(
                                                "Error in reporting error code for SECURITY NACK transaction: '{}'",
                                                ex);
                                    }
                                }
                            }
                        }
                    }
                    try { // Ask current state to Cardio 2
                        if (transaction.getObjectNumber() == -1) {
                            logger.debug(
                                    "NACK: {} transaction NOT ACCEPTED. {}. Will not ask for current state because no object information received.",
                                    transaction.getObjectType(), transaction.getErrorCodeDescription());
                        } else {
                            logger.debug("NACK: {} transaction NOT ACCEPTED. {}. Will ask for current state.",
                                    transaction.getObjectType(), transaction.getErrorCodeDescription());
                            transaction.setTransactionType(Cardio2eTransactionTypes.GET);
                            com.sendTransaction(transaction);
                        }
                    } catch (Exception ex) {
                        logger.warn("Error in requesting current state: '{}'", ex.toString());
                    }
                }
                break;
            default:
                logger.debug("No actions defined for received {} type transactions",
                        transaction.getTransactionType());
            }
        }
    }

}