/** * Copyright (c) 2010-2016 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.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.commandclass.ZWaveSecurityCommandClass; 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} */ @Override 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); } } protected void addBindingProvider(ZWaveBindingProvider bindingProvider) { super.addBindingProvider(bindingProvider); } protected void removeBindingProvider(ZWaveBindingProvider bindingProvider) { super.removeBindingProvider(bindingProvider); } /** * {@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."); } } if (StringUtils.isNotBlank((String) config.get("networkKey"))) { String keyString = (String) config.get("networkKey"); // All errors will be caught and logged by ZWaveSecurityCommandClass.setRealNetworkKey(keyString); } // 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; } }