/** * 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.satel.internal; import java.nio.charset.Charset; import java.util.Dictionary; import java.util.LinkedList; import java.util.List; import org.apache.commons.lang.StringUtils; import org.openhab.binding.satel.SatelBindingConfig; import org.openhab.binding.satel.SatelBindingProvider; import org.openhab.binding.satel.SatelCommModule; import org.openhab.binding.satel.command.IntegraStatusCommand; import org.openhab.binding.satel.command.NewStatesCommand; import org.openhab.binding.satel.command.SatelCommand; import org.openhab.binding.satel.internal.event.ConnectionStatusEvent; import org.openhab.binding.satel.internal.event.NewStatesEvent; import org.openhab.binding.satel.internal.event.SatelEvent; import org.openhab.binding.satel.internal.event.SatelEventListener; import org.openhab.binding.satel.internal.protocol.Ethm1Module; import org.openhab.binding.satel.internal.protocol.IntRSModule; import org.openhab.binding.satel.internal.protocol.SatelModule; import org.openhab.binding.satel.internal.types.IntegraType; import org.openhab.core.binding.AbstractActiveBinding; import org.openhab.core.items.Item; import org.openhab.core.types.Command; import org.openhab.core.types.State; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This is main service class that helps exchanging data between openHAB and * Satel module in both directions. Implements regular openHAB binding service. * * @author Krzysztof Goworek * @since 1.7.0 */ public class SatelBinding extends AbstractActiveBinding<SatelBindingProvider> implements ManagedService, SatelEventListener, SatelCommModule { private static final Logger logger = LoggerFactory.getLogger(SatelBinding.class); private long refreshInterval = 10000; private String userCode; private SatelModule satelModule = null; private boolean forceRefresh = false; private String textEncoding; private String userCodeOverride = null; /** * {@inheritDoc} */ @Override protected String getName() { return "Satel Refresh Service"; } /** * {@inheritDoc} */ @Override protected long getRefreshInterval() { return refreshInterval; } /** * {@inheritDoc} */ @Override public void execute() { if (!this.satelModule.isInitialized()) { logger.debug("Module not initialized yet, skipping refresh"); return; } // get list of states that have changed logger.trace("Sending 'get new states' command"); this.satelModule.sendCommand(new NewStatesCommand(this.satelModule.getIntegraType() == IntegraType.I256_PLUS)); } /** * {@inheritDoc} */ @Override public void updated(Dictionary<String, ?> config) throws ConfigurationException { logger.trace("Binding configuration updated"); if (config == null) { return; } ConfigurationDictionary configuration = new ConfigurationDictionary(config); this.refreshInterval = configuration.getLong("refresh", 10000); this.userCode = configuration.getString("user_code"); this.textEncoding = configuration.getString("encoding", "windows-1250"); // validate charset try { Charset.forName(this.textEncoding); } catch (Exception e) { throw new ConfigurationException("encoding", "Specified character set is either incorrect or not supported: " + this.textEncoding); } int timeout = configuration.getInt("timeout", 5000); String host = configuration.getString("host"); if (StringUtils.isNotBlank(host)) { this.satelModule = new Ethm1Module(host, configuration.getInt("port", 7094), timeout, configuration.getString("encryption_key")); } else { this.satelModule = new IntRSModule(configuration.getString("port"), timeout); } this.satelModule.addEventListener(this); this.satelModule.open(); setProperlyConfigured(true); logger.trace("Binding properly configured"); } /** * {@inheritDoc} */ @Override protected void internalReceiveCommand(String itemName, Command command) { if (!isProperlyConfigured()) { logger.warn("Binding not properly configured, exiting"); return; } if (!this.satelModule.isInitialized()) { logger.debug("Module not initialized yet, ignoring command"); return; } for (SatelBindingProvider provider : providers) { SatelBindingConfig itemConfig = provider.getItemConfig(itemName); if (itemConfig != null) { logger.trace("Sending internal command for item {}: {}", itemName, command); SatelCommand satelCmd = itemConfig.convertCommand(command, this.satelModule.getIntegraType(), getUserCode()); if (satelCmd != null) { this.satelModule.sendCommand(satelCmd); } break; } } } private String getUserCode() { if (StringUtils.isNotEmpty(this.userCodeOverride)) { return this.userCodeOverride; } else { return this.userCode; } } /** * {@inheritDoc} */ @Override public void incomingEvent(SatelEvent event) { logger.trace("Handling incoming event: {}", event); // refresh all states that have changed if (event instanceof NewStatesEvent) { List<SatelCommand> commands = getRefreshCommands((NewStatesEvent) event); for (SatelCommand command : commands) { this.satelModule.sendCommand(command); } } // if just connected, force refreshing if (event instanceof ConnectionStatusEvent) { ConnectionStatusEvent statusEvent = (ConnectionStatusEvent) event; if (statusEvent.isConnected()) { switchForceRefresh(true); } } // update items for (SatelBindingProvider provider : providers) { for (String itemName : provider.getItemNames()) { SatelBindingConfig itemConfig = provider.getItemConfig(itemName); Item item = provider.getItem(itemName); State newState = itemConfig.convertEventToState(item, event); if (newState != null) { logger.debug("Updating item state: item = {}, state = {}, event = {}", itemName, newState, event); eventPublisher.postUpdate(itemName, newState); itemConfig.setItemInitialized(); } } } } /** * Deactivates the binding by closing connected module. */ @Override public void deactivate() { if (this.satelModule != null) { this.satelModule.close(); this.satelModule = null; } } private List<SatelCommand> getRefreshCommands(NewStatesEvent nse) { logger.trace("Gathering refresh commands from all items"); boolean forceRefresh = switchForceRefresh(false); List<SatelCommand> commands = new LinkedList<SatelCommand>(); for (SatelBindingProvider provider : providers) { for (String itemName : provider.getItemNames()) { logger.trace("Getting refresh command from item: {}", itemName); SatelBindingConfig itemConfig = provider.getItemConfig(itemName); SatelCommand command = itemConfig.buildRefreshCommand(this.satelModule.getIntegraType()); if (command == null || commands.contains(command)) { continue; } // either state has changed or this is status command (so likely RTC has changed) // also if item hasn't received any update yet or refresh is forced // get the latest value from the module byte commandCode = command.getRequest().getCommand(); if (forceRefresh || !itemConfig.isItemInitialized() || (nse != null && nse.isNew(commandCode)) || commandCode == IntegraStatusCommand.COMMAND_CODE) { commands.add(command); } } } return commands; } /** * Changes <code>forceRefresh</code> flag atomically returning previous * value. * * @param forceRefresh * new value for the flag * @return previous value of the flag */ private synchronized boolean switchForceRefresh(boolean forceRefresh) { boolean oldValue = this.forceRefresh; this.forceRefresh = forceRefresh; return oldValue; } /** * {@inheritDoc} */ @Override public boolean isConnected() { logger.debug("[SatelCommModule] isConnected"); return this.satelModule != null && this.satelModule.isConnected(); } /** * {@inheritDoc} */ @Override public boolean sendCommand(SatelCommand command) { logger.debug("[SatelCommModule] sendCommand"); if (this.satelModule == null) { return false; } if (!this.satelModule.sendCommand(command, true)) { return false; } boolean interrupted = false; while (!interrupted) { // wait for command state change try { synchronized (command) { command.wait(this.satelModule.getTimeout()); } } catch (InterruptedException e) { // ignore, we will leave the loop on next interruption state check interrupted = true; } // check current state switch (command.getState()) { case SUCCEEDED: return true; case FAILED: return false; default: // wait for next change unless interrupted } } return false; } /** * {@inheritDoc} */ @Override public String getTextEncoding() { return textEncoding; } /** * {@inheritDoc} */ @Override public String getFirmwareVersion() { if (this.satelModule == null) { return null; } return this.satelModule.getIntegraVersion(); } /** * {@inheritDoc} */ @Override public void setUserCode(String userCode) { this.userCodeOverride = userCode; } /** * {@inheritDoc} */ @Override public void resetUserCode() { this.userCodeOverride = null; } }