org.openhab.binding.zwave.internal.ZWaveActiveBinding.java Source code

Java tutorial

Introduction

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

import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.openhab.binding.zwave.ZWaveBindingConfig;
import org.openhab.binding.zwave.ZWaveBindingProvider;
import org.openhab.binding.zwave.internal.config.ZWaveConfiguration;
import org.openhab.binding.zwave.internal.converter.ZWaveConverterHandler;
import org.openhab.binding.zwave.internal.protocol.SerialInterfaceException;
import org.openhab.binding.zwave.internal.protocol.ZWaveController;
import org.openhab.binding.zwave.internal.protocol.ZWaveEventListener;
import org.openhab.binding.zwave.internal.protocol.ZWaveNode;
import org.openhab.binding.zwave.internal.protocol.event.ZWaveCommandClassValueEvent;
import org.openhab.binding.zwave.internal.protocol.event.ZWaveEvent;
import org.openhab.binding.zwave.internal.protocol.event.ZWaveInitializationCompletedEvent;
import org.openhab.binding.zwave.internal.protocol.initialization.ZWaveNodeInitStage;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.binding.BindingProvider;
import org.openhab.core.types.Command;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * ZWaveActiveBinding Class. Polls Z-Wave nodes frequently,
 * responds to item commands, and also handles events coming 
 * from the Z-Wave controller.
 * @author Victor Belov
 * @author Brian Crosby
 * @author Jan-Willem Spuij
 * @author Chris Jackson
 * @since 1.3.0
 */
public class ZWaveActiveBinding extends AbstractActiveBinding<ZWaveBindingProvider>
        implements ManagedService, ZWaveEventListener {
    /**
     * The refresh interval which is used to poll values from the ZWave binding. 
     */
    private long refreshInterval = 5000;

    private int pollingQueue = 1;

    private static final Logger logger = LoggerFactory.getLogger(ZWaveActiveBinding.class);
    private String port;
    private boolean isSUC = false;
    private boolean softReset = false;
    private boolean masterController = true;
    private Integer healtime = null;
    private Integer aliveCheckPeriod = null;
    private Integer timeout = null;
    private volatile ZWaveController zController;
    private volatile ZWaveConverterHandler converterHandler;

    private Iterator<ZWavePollItem> pollingIterator = null;
    private List<ZWavePollItem> pollingList = new ArrayList<ZWavePollItem>();

    // Configuration Service
    ZWaveConfiguration zConfigurationService;

    // Network monitoring class
    ZWaveNetworkMonitor networkMonitor;

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

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

    /**
     * Working method that executes refreshing of the bound items. The method is executed
     * at every refresh interval. The nodes are polled only every 6 refreshes.
     */
    @Override
    protected void execute() {
        // Call the network monitor
        if (networkMonitor != null) {
            networkMonitor.execute();
        }

        // If we're not currently in a poll cycle, restart the polling table
        if (pollingIterator == null) {
            pollingIterator = pollingList.iterator();
        }

        // Loop through the polling list. We only allow a certain number of messages
        // into the send queue at a time to avoid congestion within the system.
        // Basically, we don't want the polling to slow down 'important' stuff.
        // The queue ensures all nodes get a chance - if we always started at the top
        // then the last items might never get polled.
        while (pollingIterator.hasNext()) {
            if (zController.getSendQueueLength() >= pollingQueue) {
                logger.trace("Polling queue full!");
                break;
            }
            ZWavePollItem poll = pollingIterator.next();
            converterHandler.executeRefresh(poll.provider, poll.item, false);
        }
        if (pollingIterator.hasNext() == false) {
            pollingIterator = null;
        }
    }

    /**
     * Called, if a single binding has changed. The given item could have been
     * added or removed. We refresh the binding in case it's in the done stage.
     * 
     * @param provider the binding provider where the binding has changed
     * @param itemName the item name for which the binding has changed
     */
    @Override
    public void bindingChanged(BindingProvider provider, String itemName) {
        logger.trace("bindingChanged {}", itemName);

        ZWaveBindingProvider zProvider = (ZWaveBindingProvider) provider;

        if (zProvider != null) {
            ZWaveBindingConfig bindingConfig = zProvider.getZwaveBindingConfig(itemName);

            if (bindingConfig != null && converterHandler != null) {
                converterHandler.executeRefresh(zProvider, itemName, true);
            }
        }

        // Bindings have changed - rebuild the polling table
        rebuildPollingTable();

        super.bindingChanged(provider, itemName);
    }

    /**
     * {@inheritDoc}
     */
    public void allBindingsChanged(BindingProvider provider) {
        logger.trace("allBindingsChanged");
        super.allBindingsChanged(provider);

        // Bindings have changed - rebuild the polling table
        rebuildPollingTable();
    }

    /**
     * This method rebuilds the polling table. The polling table is a list of items that have
     * polling enabled (ie a refresh interval is set). This list is then checked periodically
     * and any item that has passed its polling interval will be polled.
     */
    private void rebuildPollingTable() {
        // Rebuild the polling table
        pollingList.clear();

        if (converterHandler == null) {
            logger.debug("ConverterHandler not initialised. Polling disabled.");

            return;
        }

        // Loop all binding providers for the Z-wave binding.
        for (ZWaveBindingProvider eachProvider : providers) {
            // Loop all bound items for this provider
            for (String name : eachProvider.getItemNames()) {
                // Find the node and check if it's completed initialisation.
                ZWaveBindingConfig cfg = eachProvider.getZwaveBindingConfig(name);
                ZWaveNode node = this.zController.getNode(cfg.getNodeId());
                if (node == null) {
                    logger.debug("NODE {}: Polling list: can't get node for item {}", cfg.getNodeId(), name);
                    continue;
                }
                if (node.getNodeInitializationStage() != ZWaveNodeInitStage.DONE) {
                    logger.debug("NODE {}: Polling list: item {} is not completed initialisation", cfg.getNodeId(),
                            name);
                    continue;
                }

                logger.trace("Polling list: Checking {} == {}", name,
                        converterHandler.getRefreshInterval(eachProvider, name));

                // If this binding is configured to poll - add it to the list
                if (converterHandler.getRefreshInterval(eachProvider, name) > 0) {
                    ZWavePollItem item = new ZWavePollItem();
                    item.item = name;
                    item.provider = eachProvider;
                    pollingList.add(item);
                    logger.trace("Polling list added {}", name);
                }
            }
        }
        pollingIterator = null;
    }

    /**
     * Handles a command update by sending the appropriate Z-Wave instructions
     * to the controller.
     * {@inheritDoc}
     */
    @Override
    protected void internalReceiveCommand(String itemName, Command command) {
        boolean handled = false;

        // if we are not yet initialized, don't waste time and return
        if (this.isProperlyConfigured() == false) {
            logger.debug("internalReceiveCommand Called, But Not Properly Configure yet, returning.");
            return;
        }

        logger.trace("internalReceiveCommand(itemname = {}, Command = {})", itemName, command.toString());
        for (ZWaveBindingProvider provider : providers) {

            if (!provider.providesBindingFor(itemName)) {
                continue;
            }

            converterHandler.receiveCommand(provider, itemName, command);
            handled = true;
        }

        if (!handled) {
            logger.warn("No converter found for item = {}, command = {}, ignoring.", itemName, command.toString());
        }
    }

    /**
     * Activates the binding. Actually does nothing, because on activation
     * OpenHAB always calls updated to indicate that the config is updated.
     * Activation is done there.
     */
    @Override
    public void activate() {

    }

    /**
     * Deactivates the binding. The Controller is stopped and the serial interface
     * is closed as well.
     */
    @Override
    public void deactivate() {
        if (this.converterHandler != null) {
            this.converterHandler = null;
        }

        if (this.zConfigurationService != null) {
            this.zController.removeEventListener(this.zConfigurationService);
            this.zConfigurationService = null;
        }

        ZWaveController controller = this.zController;
        if (controller != null) {
            this.zController = null;
            controller.close();
            controller.removeEventListener(this);
        }
    }

    /**
     * Initialises the binding. This is called after the 'updated' method
     * has been called and all configuration has been passed.
     * @throws ConfigurationException 
     */
    private void initialise() throws ConfigurationException {
        try {
            logger.debug("Initialising zwave binding");
            this.setProperlyConfigured(true);
            this.deactivate();
            this.zController = new ZWaveController(masterController, isSUC, port, timeout, softReset);
            this.converterHandler = new ZWaveConverterHandler(this.zController, this.eventPublisher);
            zController.addEventListener(this);

            // The network monitor service needs to know the controller...
            this.networkMonitor = new ZWaveNetworkMonitor(this.zController);
            if (healtime != null) {
                this.networkMonitor.setHealTime(healtime);
            }
            if (aliveCheckPeriod != null) {
                this.networkMonitor.setPollPeriod(aliveCheckPeriod);
            }
            if (softReset != false) {
                this.networkMonitor.resetOnError(softReset);
            }

            // The config service needs to know the controller and the network monitor...
            this.zConfigurationService = new ZWaveConfiguration(this.zController, this.networkMonitor);
            zController.addEventListener(this.zConfigurationService);
            return;
        } catch (SerialInterfaceException ex) {
            this.setProperlyConfigured(false);
            throw new ConfigurationException("port", ex.getLocalizedMessage(), ex);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void updated(Dictionary<String, ?> config) throws ConfigurationException {
        if (config == null) {
            logger.info("ZWave 'updated' with null config");
            return;
        }

        // Check the serial port configuration value.
        // This value is mandatory.
        if (StringUtils.isNotBlank((String) config.get("port"))) {
            port = (String) config.get("port");
            logger.info("Update config, port = {}", port);
        }
        if (StringUtils.isNotBlank((String) config.get("healtime"))) {
            try {
                healtime = Integer.parseInt((String) config.get("healtime"));
                logger.info("Update config, healtime = {}", healtime);
            } catch (NumberFormatException e) {
                healtime = null;
                logger.error(
                        "Error parsing 'healtime'. This must be a single number to set the hour to perform the heal.");
            }
        }
        if (StringUtils.isNotBlank((String) config.get("refreshInterval"))) {
            try {
                refreshInterval = Integer.parseInt((String) config.get("refreshInterval"));
                logger.info("Update config, refreshInterval = {}", refreshInterval);
            } catch (NumberFormatException e) {
                refreshInterval = 10000;
                logger.error("Error parsing 'refreshInterval'. This must be a single number time in milliseconds.");
            }
        }
        if (StringUtils.isNotBlank((String) config.get("pollingQueue"))) {
            try {
                pollingQueue = Integer.parseInt((String) config.get("pollingQueue"));
                logger.info("Update config, pollingQueue = {}", pollingQueue);
            } catch (NumberFormatException e) {
                pollingQueue = 2;
                logger.error("Error parsing 'pollingQueue'. This must be a single number time in milliseconds.");
            }
        }
        if (StringUtils.isNotBlank((String) config.get("aliveCheckPeriod"))) {
            try {
                aliveCheckPeriod = Integer.parseInt((String) config.get("aliveCheckPeriod"));
                logger.info("Update config, aliveCheckPeriod = {}", aliveCheckPeriod);
            } catch (NumberFormatException e) {
                aliveCheckPeriod = null;
                logger.error("Error parsing 'aliveCheckPeriod'. This must be an Integer.");
            }
        }
        if (StringUtils.isNotBlank((String) config.get("timeout"))) {
            try {
                timeout = Integer.parseInt((String) config.get("timeout"));
                logger.info("Update config, timeout = {}", timeout);
            } catch (NumberFormatException e) {
                timeout = null;
                logger.error("Error parsing 'timeout'. This must be an Integer.");
            }
        }
        if (StringUtils.isNotBlank((String) config.get("setSUC"))) {
            try {
                isSUC = Boolean.parseBoolean((String) config.get("setSUC"));
                logger.info("Update config, setSUC = {}", isSUC);
            } catch (NumberFormatException e) {
                isSUC = false;
                logger.error("Error parsing 'setSUC'. This must be boolean.");
            }
        }
        if (StringUtils.isNotBlank((String) config.get("softReset"))) {
            try {
                softReset = Boolean.parseBoolean((String) config.get("softReset"));
                logger.info("Update config, softReset = {}", softReset);
            } catch (NumberFormatException e) {
                softReset = false;
                logger.error("Error parsing 'softReset'. This must be boolean.");
            }
        }
        if (StringUtils.isNotBlank((String) config.get("masterController"))) {
            try {
                masterController = Boolean.parseBoolean((String) config.get("masterController"));
                logger.info("Update config, masterController = {}", masterController);
            } catch (NumberFormatException e) {
                masterController = true;
                logger.error("Error parsing 'masterController'. This must be boolean.");
            }
        }

        // Now that we've read ALL the configuration, initialise the binding.
        initialise();
    }

    /**
     * Returns the port value.
     * @return
     */
    public String getPort() {
        return port;
    }

    /**
     * Event handler method for incoming Z-Wave events.
     * @param event the incoming Z-Wave event.
     */
    @Override
    public void ZWaveIncomingEvent(ZWaveEvent event) {

        // If we are not yet initialized, don't waste time and return
        if (!this.isProperlyConfigured()) {
            return;
        }

        if (event instanceof ZWaveInitializationCompletedEvent) {
            logger.debug("NODE {}: ZWaveIncomingEvent Called, Network Event, Init Done. Setting device ready.",
                    event.getNodeId());

            // Initialise the polling table
            rebuildPollingTable();

            return;
        }

        logger.debug("ZwaveIncomingEvent");

        // handle command class value events.
        if (event instanceof ZWaveCommandClassValueEvent) {
            handleZWaveCommandClassValueEvent((ZWaveCommandClassValueEvent) event);
            return;
        }
    }

    /**
     * Handle an incoming Command class value event
     * @param event the incoming Z-Wave event.
     */
    private void handleZWaveCommandClassValueEvent(ZWaveCommandClassValueEvent event) {
        boolean handled = false;

        logger.debug(
                "NODE {}: Got a value event from Z-Wave network, endpoint = {}, command class = {}, value = {}",
                new Object[] { event.getNodeId(), event.getEndpoint(), event.getCommandClass().getLabel(),
                        event.getValue() });

        for (ZWaveBindingProvider provider : providers) {
            for (String itemName : provider.getItemNames()) {
                ZWaveBindingConfig bindingConfig = provider.getZwaveBindingConfig(itemName);

                if (bindingConfig.getNodeId() != event.getNodeId()
                        || bindingConfig.getEndpoint() != event.getEndpoint()) {
                    continue;
                }

                converterHandler.handleEvent(provider, itemName, event);
                handled = true;
            }
        }

        if (!handled) {
            logger.warn(
                    "NODE {}: No item bound for event, endpoint = {}, command class = {}, value = {}, ignoring.",
                    new Object[] { event.getNodeId(), event.getEndpoint(), event.getCommandClass().getLabel(),
                            event.getValue() });
        }
    }

    class ZWavePollItem {
        ZWaveBindingProvider provider;
        String item;
    }
}