org.openhab.binding.maxcul.internal.MaxCulBinding.java Source code

Java tutorial

Introduction

Here is the source code for org.openhab.binding.maxcul.internal.MaxCulBinding.java

Source

/**
 * Copyright (c) 2010-2015, openHAB.org and others.
 *
 * 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.maxcul.internal;

import java.util.Collection;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.commons.lang.StringUtils;
import org.openhab.binding.maxcul.MaxCulBindingProvider;
import org.openhab.binding.maxcul.internal.message.sequencers.PairingInitialisationSequence;
import org.openhab.binding.maxcul.internal.message.sequencers.TimeUpdateRequestSequence;
import org.openhab.binding.maxcul.internal.messages.BaseMsg;
import org.openhab.binding.maxcul.internal.messages.MaxCulBindingMessageProcessor;
import org.openhab.binding.maxcul.internal.messages.MaxCulMsgType;
import org.openhab.binding.maxcul.internal.messages.PairPingMsg;
import org.openhab.binding.maxcul.internal.messages.PushButtonMsg;
import org.openhab.binding.maxcul.internal.messages.PushButtonMsg.PushButtonMode;
import org.openhab.binding.maxcul.internal.messages.SetTemperatureMsg;
import org.openhab.binding.maxcul.internal.messages.ThermostatStateMsg;
import org.openhab.binding.maxcul.internal.messages.TimeInfoMsg;
import org.openhab.binding.maxcul.internal.messages.WallThermostatControlMsg;
import org.openhab.binding.maxcul.internal.messages.WallThermostatStateMsg;
import org.openhab.core.binding.AbstractBinding;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.types.Command;
import org.openhab.io.transport.cul.CULDeviceException;
import org.openhab.io.transport.cul.CULHandler;
import org.openhab.io.transport.cul.CULManager;
import org.openhab.io.transport.cul.CULMode;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This binding allows integration of the MAX! devices via the CUL device - so
 * without the need for the Max!Cube device.
 * 
 * @author Paul Hampson (cyclingengineer)
 * @since 1.6.0
 */
public class MaxCulBinding extends AbstractBinding<MaxCulBindingProvider>
        implements ManagedService, MaxCulBindingMessageProcessor {

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

    /**
     * The device that is used to access the CUL hardware
     */
    private String accessDevice;

    /**
     * This provides access to the CULFW device (e.g. USB stick)
     */
    private CULHandler cul;

    /**
     * This sets the address of the controller i.e. us!
     */
    private final String srcAddr = "010203";

    /**
     * Set default group ID
     */
    private final byte DEFAULT_GROUP_ID = 0x1;

    /**
     * Flag to indicate if we are in pairing mode. Default timeout is 60
     * seconds.
     */
    private boolean pairMode = false;
    private int pairModeTimeout = 60000;
    private int PACED_TRANSMIT_TIME = 10000;

    private Map<String, Timer> timers = new HashMap<String, Timer>();
    private Map<MaxCulBindingConfig, Timer> pacedBindingTransmitTimers = new HashMap<MaxCulBindingConfig, Timer>();

    MaxCulMsgHandler messageHandler;

    private String tzStr;

    public MaxCulBinding() {
    }

    public void activate() {
        super.activate();
        logger.debug("Activating MaxCul binding");
    }

    public void deactivate() {
        logger.debug("De-Activating MaxCul binding");
        if (cul != null) {
            cul.unregisterListener(messageHandler);
            CULManager.close(cul);
            logger.debug("CUL IO should now be closed");
        }
    }

    /**
     * @{inheritDoc
     */
    @Override
    protected void internalReceiveCommand(final String itemName, Command command) {
        Timer pairModeTimer = null;

        MaxCulBindingConfig bindingConfig = null;
        for (MaxCulBindingProvider provider : super.providers) {
            bindingConfig = provider.getConfigForItemName(itemName);
            if (bindingConfig != null) {
                break;
            }
        }
        logger.debug("Received command " + command.toString() + " for item " + itemName);
        if (bindingConfig != null) {
            logger.debug("Found config for " + itemName);

            switch (bindingConfig.getDeviceType()) {
            case PAIR_MODE:
                if ((command instanceof OnOffType)) {
                    switch ((OnOffType) command) {
                    case ON:
                        /*
                         * turn on pair mode and schedule disabling of pairing
                         * mode
                         */
                        pairMode = true;
                        TimerTask task = new TimerTask() {
                            public void run() {
                                logger.debug(itemName + " pairMode time out executed");
                                pairMode = false;
                                eventPublisher.postUpdate(itemName, OnOffType.OFF);
                            }
                        };
                        pairModeTimer = timers.get(itemName);
                        if (pairModeTimer != null) {
                            pairModeTimer.cancel();
                            timers.remove(itemName);
                        }
                        pairModeTimer = new Timer();
                        timers.put(itemName, pairModeTimer);
                        pairModeTimer.schedule(task, pairModeTimeout);
                        logger.debug(itemName + " pairMode enabled & timeout scheduled");
                        break;
                    case OFF:
                        /*
                         * we are manually disabling, so clear the timer and the
                         * flag
                         */
                        pairMode = false;
                        pairModeTimer = timers.get(itemName);
                        if (pairModeTimer != null) {
                            logger.debug(itemName + " pairMode timer cancelled");
                            pairModeTimer.cancel();
                            timers.remove(itemName);
                        }
                        logger.debug(itemName + " pairMode cleared");
                        break;
                    }
                } else
                    logger.warn(
                            "Command not handled for " + bindingConfig.getDeviceType() + " that is not OnOffType");
                break;
            case LISTEN_MODE:
                if (command instanceof OnOffType) {
                    this.messageHandler.setListenMode(((OnOffType) command == OnOffType.ON));
                } else
                    logger.warn(
                            "Command not handled for " + bindingConfig.getDeviceType() + " that is not OnOffType");
                break;
            case RADIATOR_THERMOSTAT:
            case RADIATOR_THERMOSTAT_PLUS:
            case WALL_THERMOSTAT:
                if (bindingConfig.getFeature() == MaxCulFeature.THERMOSTAT) {
                    /* clear out old pacing timer */
                    if (pacedBindingTransmitTimers.containsKey(bindingConfig)) {
                        pacedBindingTransmitTimers.get(bindingConfig).cancel();
                        pacedBindingTransmitTimers.remove(bindingConfig);
                    }
                    /* schedule new timer */
                    Timer pacingTimer = new Timer();
                    pacedBindingTransmitTimers.put(bindingConfig, pacingTimer);
                    pacingTimer.schedule(new MaxCulPacedThermostatTransmitTask(command, bindingConfig,
                            messageHandler, super.providers), PACED_TRANSMIT_TIME);
                } else if (bindingConfig.getFeature() == MaxCulFeature.RESET) {
                    messageHandler.sendReset(bindingConfig.getDevAddr());
                } else {
                    logger.warn("Command not handled for " + bindingConfig.getDeviceType()
                            + " that is not OnOffType or DecimalType");
                }
                break;
            default:
                logger.warn("Command not handled for " + bindingConfig.getDeviceType());
                break;
            }
        }
        updateCreditMonitors();
    }

    /**
     * @{inheritDoc
     */
    @Override
    public void updated(Dictionary<String, ?> config) throws ConfigurationException {
        logger.debug("MaxCUL Reading config");
        if (config != null) {

            // handle timezone configuration
            // maxcul:timezone=Europe/London
            String timezoneString = (String) config.get("timezone");
            if (StringUtils.isNotBlank(timezoneString)) {
                this.tzStr = timezoneString;
            } else {
                this.tzStr = "Europe/London";
            }

            // handle device config
            // maxcul:device=/dev/cul
            String deviceString = (String) config.get("device");
            if (StringUtils.isNotBlank(deviceString)) {
                logger.debug("Setting up device " + deviceString);
                setupDevice(deviceString, config);
                if (cul == null)
                    throw new ConfigurationException("device",
                            "Configuration failed. Unable to access CUL device " + deviceString);
            } else {
                throw new ConfigurationException("device", "No device set - please set one");
            }
        }
    }

    private void setupDevice(String device, Dictionary<String, ?> config) {
        if (cul != null) {
            CULManager.close(cul);
        }
        try {
            accessDevice = device;
            logger.debug("Opening CUL device on " + accessDevice);
            cul = CULManager.getOpenCULHandler(accessDevice, CULMode.MAX, convertDictionaryToMap(config));
            messageHandler = new MaxCulMsgHandler(this.srcAddr, cul, super.providers);
            messageHandler.registerMaxCulBindingMessageProcessor(this);
        } catch (CULDeviceException e) {
            logger.error("Cannot open CUL device", e);
            cul = null;
            accessDevice = null;
        }
    }

    private Map<String, Object> convertDictionaryToMap(Dictionary<String, ?> config) {

        Map<String, Object> myMap = new HashMap<String, Object>();

        if (config == null) {
            return null;
        }
        if (config.size() == 0) {
            return myMap;
        }

        Enumeration<String> allKeys = config.keys();
        while (allKeys.hasMoreElements()) {
            String key = allKeys.nextElement();
            myMap.put(key, config.get(key));
        }
        return myMap;
    }

    private Collection<MaxCulBindingConfig> getBindingsBySerial(String serial) {
        Collection<MaxCulBindingConfig> bindingConfigs = null;
        for (MaxCulBindingProvider provider : super.providers) {
            bindingConfigs = provider.getConfigsForSerialNumber(serial);
            if (bindingConfigs != null)
                break;
        }
        if (bindingConfigs == null) {
            logger.error("Unable to find configuration for serial " + serial + ". Do you have a binding for it?");
            return null;
        }
        return bindingConfigs;
    }

    private void updateCreditMonitors() {
        /* find and update credit monitor binding if it exists */
        int credit10ms = messageHandler.getCreditStatus();
        for (MaxCulBindingProvider provider : super.providers) {
            Collection<MaxCulBindingConfig> bindingConfigs = provider.getCreditMonitorBindings();
            for (MaxCulBindingConfig bc : bindingConfigs) {
                String itemName = provider.getItemNameForConfig(bc);
                eventPublisher.postUpdate(itemName, new DecimalType(credit10ms));
            }
        }
    }

    @Override
    public void maxCulMsgReceived(String data, boolean isBroadcast) {
        logger.debug("Received data from CUL: " + data);

        MaxCulMsgType msgType = BaseMsg.getMsgType(data);
        /*
         * Check if it's broadcast and we're in pair mode or a PAIR_PING message
         * directly for us
         */
        if (((pairMode && isBroadcast) || !isBroadcast) && msgType == MaxCulMsgType.PAIR_PING) {
            logger.debug("Got PAIR_PING message");
            MaxCulBindingConfig configWithTempsConfig = null;
            /* process packet */
            PairPingMsg pkt = new PairPingMsg(data);

            /* Match serial number to binding configuration */
            Collection<MaxCulBindingConfig> bindingConfigs = getBindingsBySerial(pkt.serial);

            /*
             * only respond and set pairing info if we found at least one
             * binding config
             */
            if (bindingConfigs != null) {
                logger.debug("Found " + bindingConfigs.size() + " configs for " + pkt.serial);
                for (MaxCulBindingConfig bc : bindingConfigs) {
                    /* Set pairing information */
                    bc.setPairedInfo(pkt.srcAddrStr); /*
                                                      * where it came from gives
                                                      * the addr of the device
                                                      */
                    if (bc.isTemperatureConfigSet() && configWithTempsConfig == null) {
                        configWithTempsConfig = bc;
                    }
                }

                /* if none have values set then send default from first config */
                if (configWithTempsConfig == null) {
                    logger.debug("Using default temperature configuration from config 0");
                    configWithTempsConfig = (MaxCulBindingConfig) bindingConfigs.toArray()[0];
                }

                /* get device associations */
                HashSet<MaxCulBindingConfig> associations = null;
                for (MaxCulBindingProvider provider : super.providers) {
                    associations = provider.getAssociations(configWithTempsConfig.getSerialNumber());
                    if (associations != null && associations.isEmpty() == false) {
                        logger.debug("Found associations");
                        break;
                    }
                }

                /* start the initialisation sequence */
                logger.debug("Creating pairing sequencer");
                PairingInitialisationSequence ps = new PairingInitialisationSequence(this.DEFAULT_GROUP_ID,
                        messageHandler, configWithTempsConfig, associations);
                messageHandler.startSequence(ps, pkt);
            } else {
                logger.error("Pairing failed: Unable to find binding config for device " + pkt.serial);
            }
        } else {
            switch (msgType) {
            /*
             * TODO there are other incoming messages that aren't handled that
             * could be
             */
            case WALL_THERMOSTAT_CONTROL:
                WallThermostatControlMsg wallThermCtrlMsg = new WallThermostatControlMsg(data);
                wallThermCtrlMsg.printMessage();
                for (MaxCulBindingProvider provider : super.providers) {
                    Collection<MaxCulBindingConfig> bindingConfigs = provider
                            .getConfigsForRadioAddr(wallThermCtrlMsg.srcAddrStr);
                    for (MaxCulBindingConfig bc : bindingConfigs) {
                        if (bc.getFeature() == MaxCulFeature.THERMOSTAT
                                && wallThermCtrlMsg.getDesiredTemperature() != null) {
                            String itemName = provider.getItemNameForConfig(bc);
                            eventPublisher.postUpdate(itemName,
                                    new DecimalType(wallThermCtrlMsg.getDesiredTemperature()));
                        } else if (bc.getFeature() == MaxCulFeature.TEMPERATURE
                                && wallThermCtrlMsg.getMeasuredTemperature() != null) {
                            String itemName = provider.getItemNameForConfig(bc);
                            eventPublisher.postUpdate(itemName,
                                    new DecimalType(wallThermCtrlMsg.getMeasuredTemperature()));
                        }
                        // TODO switch mode between manual/automatic?
                    }
                }

                /* reply only if not broadcast */
                if (isBroadcast == false)
                    this.messageHandler.sendAck(wallThermCtrlMsg);
                break;
            case SET_TEMPERATURE:
                SetTemperatureMsg setTempMsg = new SetTemperatureMsg(data);
                setTempMsg.printMessage();
                for (MaxCulBindingProvider provider : super.providers) {
                    Collection<MaxCulBindingConfig> bindingConfigs = provider
                            .getConfigsForRadioAddr(setTempMsg.srcAddrStr);
                    for (MaxCulBindingConfig bc : bindingConfigs) {
                        if (bc.getFeature() == MaxCulFeature.THERMOSTAT) {
                            String itemName = provider.getItemNameForConfig(bc);
                            eventPublisher.postUpdate(itemName,
                                    new DecimalType(setTempMsg.getDesiredTemperature()));
                        }
                        // TODO switch mode between manual/automatic?
                    }
                }
                /* respond to device */
                if (isBroadcast == false)
                    this.messageHandler.sendAck(setTempMsg);
                break;
            case THERMOSTAT_STATE:
                ThermostatStateMsg thermStateMsg = new ThermostatStateMsg(data);
                thermStateMsg.printMessage();
                for (MaxCulBindingProvider provider : super.providers) {
                    Collection<MaxCulBindingConfig> bindingConfigs = provider
                            .getConfigsForRadioAddr(thermStateMsg.srcAddrStr);
                    for (MaxCulBindingConfig bc : bindingConfigs) {
                        String itemName = provider.getItemNameForConfig(bc);
                        if (bc.getFeature() == MaxCulFeature.THERMOSTAT
                                && thermStateMsg.getDesiredTemperature() != null) {
                            eventPublisher.postUpdate(itemName,
                                    new DecimalType(thermStateMsg.getDesiredTemperature()));
                        } else if (bc.getFeature() == MaxCulFeature.TEMPERATURE
                                && thermStateMsg.getMeasuredTemperature() != null) {
                            eventPublisher.postUpdate(itemName,
                                    new DecimalType(thermStateMsg.getMeasuredTemperature()));
                        } else if (bc.getFeature() == MaxCulFeature.BATTERY) {
                            eventPublisher.postUpdate(itemName,
                                    thermStateMsg.getBatteryLow() ? OnOffType.ON : OnOffType.OFF);
                        } else if (bc.getFeature() == MaxCulFeature.MODE) {
                            eventPublisher.postUpdate(itemName,
                                    new DecimalType(thermStateMsg.getControlMode().toInt()));
                        } else if (bc.getFeature() == MaxCulFeature.VALVE_POS) {
                            eventPublisher.postUpdate(itemName, new DecimalType(thermStateMsg.getValvePos()));
                        }
                        // TODO switch mode between manual/automatic?
                    }
                }
                /* respond to device */
                if (isBroadcast == false)
                    this.messageHandler.sendAck(thermStateMsg);
                break;
            case WALL_THERMOSTAT_STATE:
                WallThermostatStateMsg wallThermStateMsg = new WallThermostatStateMsg(data);
                wallThermStateMsg.printMessage();
                for (MaxCulBindingProvider provider : super.providers) {
                    Collection<MaxCulBindingConfig> bindingConfigs = provider
                            .getConfigsForRadioAddr(wallThermStateMsg.srcAddrStr);
                    for (MaxCulBindingConfig bc : bindingConfigs) {
                        String itemName = provider.getItemNameForConfig(bc);
                        if (bc.getFeature() == MaxCulFeature.THERMOSTAT
                                && wallThermStateMsg.getDesiredTemperature() != null) {
                            eventPublisher.postUpdate(itemName,
                                    new DecimalType(wallThermStateMsg.getDesiredTemperature()));
                        } else if (bc.getFeature() == MaxCulFeature.TEMPERATURE
                                && wallThermStateMsg.getMeasuredTemperature() != null) {
                            eventPublisher.postUpdate(itemName,
                                    new DecimalType(wallThermStateMsg.getMeasuredTemperature()));
                        } else if (bc.getFeature() == MaxCulFeature.BATTERY) {
                            eventPublisher.postUpdate(itemName,
                                    wallThermStateMsg.getBatteryLow() ? OnOffType.ON : OnOffType.OFF);
                        } else if (bc.getFeature() == MaxCulFeature.MODE) {
                            eventPublisher.postUpdate(itemName,
                                    new DecimalType(wallThermStateMsg.getControlMode().toInt()));
                        }
                    }
                }
                /* respond to device */
                if (isBroadcast == false)
                    this.messageHandler.sendAck(wallThermStateMsg);
                break;
            case TIME_INFO:
                TimeInfoMsg timeMsg = new TimeInfoMsg(data);
                timeMsg.printMessage();
                TimeUpdateRequestSequence timeSeq = new TimeUpdateRequestSequence(this.tzStr, messageHandler);
                messageHandler.startSequence(timeSeq, timeMsg);
                break;
            case PUSH_BUTTON_STATE:
                PushButtonMsg pbMsg = new PushButtonMsg(data);
                pbMsg.printMessage();
                for (MaxCulBindingProvider provider : super.providers) {
                    Collection<MaxCulBindingConfig> bindingConfigs = provider
                            .getConfigsForRadioAddr(pbMsg.srcAddrStr);
                    for (MaxCulBindingConfig bc : bindingConfigs) {
                        String itemName = provider.getItemNameForConfig(bc);
                        if (bc.getFeature() == MaxCulFeature.SWITCH) {
                            // ON maps to 'AUTO'
                            if (pbMsg.getMode() == PushButtonMode.AUTO)
                                eventPublisher.postUpdate(itemName, OnOffType.ON);
                            // OFF maps to 'ECO'
                            else if (pbMsg.getMode() == PushButtonMode.ECO)
                                eventPublisher.postUpdate(itemName, OnOffType.OFF);
                        }
                    }
                }
                if (isBroadcast == false)
                    this.messageHandler.sendAck(pbMsg);
                break;
            default:
                logger.debug("Unhandled message type " + msgType.toString());
                break;

            }
        }
        updateCreditMonitors();
    }
}