/** * 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.tellstick.internal; import java.math.BigDecimal; import java.util.Calendar; import java.util.Collections; import java.util.Dictionary; import java.util.HashMap; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import org.openhab.binding.tellstick.TellstickBindingConfig; import org.openhab.binding.tellstick.TellstickBindingProvider; import org.openhab.binding.tellstick.TellstickValueSelector; import org.openhab.binding.tellstick.internal.JNA.DataType; import org.openhab.binding.tellstick.internal.JNA.Method; import org.openhab.binding.tellstick.internal.device.SupportedMethodsException; import org.openhab.binding.tellstick.internal.device.TellstickDevice; import org.openhab.binding.tellstick.internal.device.TellstickDeviceEvent; import org.openhab.binding.tellstick.internal.device.TellstickSensorEvent; import org.openhab.binding.tellstick.internal.device.iface.DeviceChangeListener; import org.openhab.binding.tellstick.internal.device.iface.SensorListener; import org.openhab.core.binding.AbstractActiveBinding; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PercentType; 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; import com.sun.jna.Platform; /** * This class coordinates between the events in openHAB and the Tellstick * device. It uses a JNA bridge to talk to the C api of the tellstick. * * @author jarlebh * @author Elias Gabrielsson * @since 1.5.0 */ public class TellstickBinding extends AbstractActiveBinding<TellstickBindingProvider>implements ManagedService { private static final Logger logger = LoggerFactory.getLogger(TellstickBinding.class); /** * Max time without receiving any events. */ private static final int MAX_IDLE_BEFORE_RESTART = 600000; private static BigDecimal HUNDRED = new BigDecimal("100"); private int restartTimeout; private long lastRefresh = 0; /** * the refresh interval which is used to poll values from the Tellstick * server (optional, defaults to 60000ms) */ private long refreshInterval = 60000; private TellstickController controller; private Thread controllerThread; private SortedMap<TellstickDevice, TellstickSendEvent> messageQue; public TellstickBinding() { messageQue = Collections.synchronizedSortedMap(new TreeMap<TellstickDevice, TellstickSendEvent>()); controller = new TellstickController(messageQue); controllerThread = new Thread(controller); } @Override public void activate() { logger.info("Activate " + Thread.currentThread()); } @Override public void deactivate() { logger.info("Deactivate " + this); try { deRegisterListeners(); controllerThread.interrupt(); } catch (Exception e) { logger.error("Failed to deactivate", e); } } private void registerListeners() { for (TellstickBindingProvider prov : providers) { prov.addListener(new TellstickDeviceEventHandler()); prov.addListener(new TellstickSensorEventHandler()); } } private void deRegisterListeners() throws SupportedMethodsException { for (TellstickBindingProvider prov : providers) { prov.removeTellstickListener(); } } private void resetTelldusProvider() throws SupportedMethodsException { for (TellstickBindingProvider prov : providers) { prov.resetTellstickListener(); } } /** * @{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! for " + itemName + " with " + command); TellstickBindingConfig config = findTellstickBindingConfig(itemName); if (config != null) { if(!controllerThread.isAlive()){ controllerThread.start(); } TellstickDevice dev = findDevice(config); Long eventTime = System.currentTimeMillis(); synchronized (messageQue) { messageQue.put(dev, new TellstickSendEvent(config, dev, command, eventTime)); messageQue.notify(); } } } private TellstickDevice findDevice(TellstickBindingConfig config) { TellstickDevice dev = null; for (TellstickBindingProvider prov : providers) { dev = prov.getDevice(config.getItemName()); if (dev != null) { break; } } return dev; } protected void addBindingProvider(TellstickBindingProvider bindingProvider) { super.addBindingProvider(bindingProvider); } protected void removeBindingProvider(TellstickBindingProvider bindingProvider) { super.removeBindingProvider(bindingProvider); } /** * {@inheritDoc} */ @Override public void updated(Dictionary<String, ?> config) throws ConfigurationException { this.restartTimeout = MAX_IDLE_BEFORE_RESTART; String libraryPath = null; if (Platform.isWindows()) { libraryPath = "C:/Program Files/Telldus/;C:/Program Files (x86)/Telldus/"; } logger.info("Called with config " + config); if (config != null) { String maxIdle = (String) config.get("max_idle"); String confLibraryPath = (String) config.get("library_path"); if (maxIdle != null) { this.restartTimeout = Integer.valueOf(maxIdle); } if (confLibraryPath != null) { libraryPath = confLibraryPath; } } if (libraryPath != null) { logger.info("Loading " + JNA.library + " from " + libraryPath); System.setProperty("jna.library.path", libraryPath); } else { logger.info("Loading " + JNA.library + " from system default paths"); } resetTellstick(); setProperlyConfigured(true); } private TellstickBindingConfig findTellstickBindingConfig(int itemId, TellstickValueSelector valueSel, String protocol) { TellstickBindingConfig matchingConfig = null; for (TellstickBindingProvider provider : this.providers) { TellstickBindingConfig config = provider.getTellstickBindingConfig(itemId, valueSel, protocol); if (config != null) { matchingConfig = config; break; } } return matchingConfig; } private TellstickBindingConfig findTellstickBindingConfig(String itemName) { TellstickBindingConfig matchingConfig = null; for (TellstickBindingProvider provider : this.providers) { TellstickBindingConfig config = provider.getTellstickBindingConfig(itemName); if (config != null) { matchingConfig = config; break; } } return matchingConfig; } class TellstickDeviceEventHandler implements DeviceChangeListener { @Override public void onRequest(TellstickDeviceEvent newDevices) { handleDeviceEvent(newDevices); } } private void handleDeviceEvent(TellstickDeviceEvent event) { TellstickDevice device = event.getDevice(); controller.setLastSend(System.currentTimeMillis()); logger.debug("Got deviceEvent for " + device + " name:" + device + " method " + event.getMethod()); if (device != null) { State cmd = resolveCommand(event.getMethod(), event.getData()); TellstickBindingConfig conf = findTellstickBindingConfig(device.getId(), null, null); if (conf != null) { sendToOpenHab(conf.getItemName(), cmd); } else { logger.info("Could not find config for " + device); } } } private State resolveCommand(Method method, String data) { State cmd = null; if (method == Method.TURNON) { cmd = OnOffType.ON; } else if (method == Method.TURNOFF) { cmd = OnOffType.OFF; } else if (method == Method.DIM) { double value = ((Double.valueOf(data) * 100) / 255); cmd = new PercentType((int) Math.round(value)); } return cmd; } private void sendToOpenHab(String itemName, State cmd) { eventPublisher.postUpdate(itemName, cmd); } class TellstickSensorEventHandler implements SensorListener { private Map<DataType, String> prevMessages = new HashMap<DataType, String>(); private void handleSensorEvent(TellstickSensorEvent event, TellstickBindingConfig device) { BigDecimal dValue = new BigDecimal(String.valueOf(event.getData())); dValue.setScale(1, BigDecimal.ROUND_HALF_UP); TellstickValueSelector selector = device.getUsageSelector(); if (selector == null) { selector = device.getValueSelector(); } State cmd = getCommand(event, dValue, selector); sendToOpenHab(device.getItemName(), cmd); } private State getCommand(TellstickSensorEvent event, BigDecimal dValue, TellstickValueSelector selector) { State cmd = null; switch (event.getDataType()) { case TEMPERATURE: switch (selector) { case MOTION: cmd = OnOffType.ON; break; default: cmd = new DecimalType(dValue); } break; case HUMIDITY: switch (selector) { case BATTERY_LEVEL: cmd = new DecimalType(dValue); break; case HUMIDITY: default: cmd = new PercentType(HUNDRED.min(dValue)); } break; case WINDAVERAGE: case WINDDIRECTION: case WINDGUST: case RAINRATE: case RAINTOTAL: cmd = new DecimalType(dValue); break; default: logger.warn("Event of type " + event.getDataType() + " does not have a mapping"); } return cmd; } private TellstickValueSelector getSensorBindingType(DataType dataType) { TellstickValueSelector result = null; switch (dataType) { case TEMPERATURE: result = TellstickValueSelector.TEMPERATURE; break; case HUMIDITY: result = TellstickValueSelector.HUMIDITY; break; case WINDAVERAGE: result = TellstickValueSelector.WIND_AVG; break; case WINDDIRECTION: result = TellstickValueSelector.WIND_DIRECTION; break; case WINDGUST: result = TellstickValueSelector.WIND_GUST; break; case RAINRATE: result = TellstickValueSelector.RAIN_RATE; break; case RAINTOTAL: result = TellstickValueSelector.RAIN_TOTAL; break; default: logger.warn("Sensor of type " + dataType + " not supported"); } return result; } @Override public void onRequest(TellstickSensorEvent sensorEvent) { controller.setLastSend(System.currentTimeMillis()); Calendar cal = Calendar.getInstance(); String thisMsg = cal.get(Calendar.MINUTE) + sensorEvent.getProtocol() + sensorEvent.getModel() + sensorEvent.getSensorId() + sensorEvent.getData(); String prevMessage = prevMessages.get(sensorEvent.getDataType()); if (!thisMsg.equals(prevMessage)) { prevMessages.put(sensorEvent.getDataType(), thisMsg); TellstickValueSelector sensorBindingType = getSensorBindingType(sensorEvent.getDataType()); TellstickBindingConfig device = findTellstickBindingConfig(sensorEvent.getSensorId(), sensorBindingType, sensorEvent.getProtocol()); logger.debug( "Got sensorEvent for " + sensorEvent.getSensorId() + " type " + sensorBindingType + " proto " + sensorEvent.getProtocol() + " name:" + device + " value:" + sensorEvent.getData()); if (device != null) { handleSensorEvent(sensorEvent, device); } } else { logger.debug("Ignored duplicate message for " + sensorEvent.getSensorId() + " value:" + sensorEvent.getData()); } } } @Override protected void execute() { long lastSend = controller.getLastSend(); logger.trace("Check thread current idle ms " + (System.currentTimeMillis() - lastSend)); if ((System.currentTimeMillis() - lastSend) > restartTimeout) { // RE-INIT resetTellstick(); } if (lastRefresh <= 0) { // Only read from tellstick once, the status is sometimes wrong. refreshFromTellstick(); } } private void refreshFromTellstick() { logger.trace("Update with telldus state"); for (TellstickBindingProvider prov : providers) { for (String name : prov.getItemNames()) { TellstickDevice dev = prov.getDevice(name); if (dev != null) { Method method = Method.getMethodById(dev.getStatus()); sendToOpenHab(name, resolveCommand(method, dev.getData())); } } } lastRefresh = System.currentTimeMillis(); } private void resetTellstick() { logger.warn("Will do a reinit of listeners, no message received for " + restartTimeout / 1000 + " seconds"); try { deRegisterListeners(); logger.info("Listeners removed"); resetTelldusProvider(); logger.info("Telldus reset"); registerListeners(); logger.info("Listeners restarted"); controller.setLastSend(System.currentTimeMillis()); } catch (Exception e) { logger.error("Failed to reset listener", e); } } @Override protected long getRefreshInterval() { return refreshInterval; } @Override protected String getName() { return "Telldus Sync Service"; } }