/** * 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.ebus.internal; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import java.util.Dictionary; import java.util.Map; import java.util.Map.Entry; import org.apache.commons.lang.StringUtils; import org.openhab.binding.ebus.EBusBindingProvider; import org.openhab.binding.ebus.internal.connection.AbstractEBusWriteConnector; import org.openhab.binding.ebus.internal.connection.EBusCommandProcessor; import org.openhab.binding.ebus.internal.connection.EBusConnectorEventListener; import org.openhab.binding.ebus.internal.connection.EBusTCPConnector; import org.openhab.binding.ebus.internal.parser.EBusConfigurationProvider; import org.openhab.binding.ebus.internal.parser.EBusTelegramCSVWriter; import org.openhab.binding.ebus.internal.parser.EBusTelegramParser; import org.openhab.binding.ebus.internal.utils.EBusUtils; import org.openhab.binding.ebus.internal.utils.StateUtils; import org.openhab.core.binding.AbstractBinding; import org.openhab.core.binding.BindingProvider; import org.openhab.core.types.Command; import org.openhab.core.types.State; import org.osgi.framework.Bundle; import org.osgi.framework.FrameworkUtil; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * eBus binding implementation. * * @author Christian Sowada * @since 1.7.0 */ public class EBusBinding extends AbstractBinding<EBusBindingProvider> implements ManagedService, EBusConnectorEventListener { private static final Logger logger = LoggerFactory.getLogger(EBusBinding.class); // Used to process the commands incl. polling private EBusCommandProcessor commandProcessor; // The connector to serial or ethernet private AbstractEBusWriteConnector connector; // The parser to converts received bytes to key/value maps private EBusTelegramParser parser; private EBusConfigurationProvider configurationProvider; private EBusTelegramCSVWriter debugWriter; /* * (non-Javadoc) * * @see org.openhab.core.binding.AbstractBinding#internalReceiveCommand(java.lang.String, * org.openhab.core.types.Command) */ @Override protected void internalReceiveCommand(String itemName, Command command) { for (EBusBindingProvider provider : providers) { byte[] data = commandProcessor.composeSendData(provider, itemName, command); connector.addToSendQueue(data); } } /** * Check and initialize the configuration provider if needed */ private void checkConfigurationProvider() { if (configurationProvider == null) { configurationProvider = new EBusConfigurationProvider(); } } /** * Check and initialize the command processor if needed */ private void checkCommandProcessor() { if (commandProcessor == null) { checkConfigurationProvider(); commandProcessor = new EBusCommandProcessor(); commandProcessor.setConfigurationProvider(configurationProvider); } } private void stopConnector() { if (debugWriter != null) { try { debugWriter.close(); debugWriter = null; } catch (IOException e) { logger.error("error!", e); } } // stop last thread if active if (connector != null && connector.isAlive()) { logger.info("Shutdown old eBus connector thread ..."); connector.interrupt(); try { // wait up to 20sec. for shutdown logger.debug("Waiting for connector thread shutdown ..."); connector.join(20000); if (connector.isAlive()) { logger.debug("Unable to stop eBUS connection thread!"); } else { logger.debug("Connector thread successfully shutdown ..."); } } catch (InterruptedException e) { logger.error("Interrupted while shutdown old connector thread!", e); } } } protected void addBindingProvider(EBusBindingProvider bindingProvider) { super.addBindingProvider(bindingProvider); } protected void removeBindingProvider(EBusBindingProvider bindingProvider) { super.removeBindingProvider(bindingProvider); } /* * (non-Javadoc) * * @see org.osgi.service.cm.ManagedService#updated(java.util.Dictionary) */ @Override public void updated(Dictionary<String, ?> properties) throws ConfigurationException { logger.info("Update eBus Binding configuration ..."); if (properties == null || properties.isEmpty()) { throw new RuntimeException("No properties in openhab.cfg set!"); } try { // stop last connector-thread if active stopConnector(); // check to ensure that it is available checkConfigurationProvider(); // clear current configuration configurationProvider.clear(); // load parser from default url parser = new EBusTelegramParser(configurationProvider); URL configurationUrl = null; String parsers = (String) properties.get("parsers"); if (StringUtils.isEmpty(parsers)) { // set to current stable configurations as default parsers = "common"; } for (String elem : parsers.split(",")) { configurationUrl = null; // check for keyword custom to load custom configuration if (elem.trim().equals("custom")) { String parserUrl = (String) properties.get("parserUrl"); if (parserUrl != null) { logger.debug("Load custom eBus Parser with url {}", parserUrl); configurationUrl = new URL(parserUrl); } } else { logger.debug("Load eBus Parser Configuration \"{}\" ...", elem.trim()); String filename = "src/main/resources/" + elem.trim() + "-configuration.json"; Bundle bundle = FrameworkUtil.getBundle(EBusBinding.class); configurationUrl = bundle.getResource(filename); if (configurationUrl == null) { logger.error("Unable to load file {} ...", elem.trim() + "-configuration.json"); } } if (configurationUrl != null) { configurationProvider.loadConfigurationFile(configurationUrl); } } // check minimal config if (properties.get("serialPort") != null && properties.get("hostname") != null) { throw new ConfigurationException("hostname", "Set property serialPort or hostname, not both!"); } // use the serial connector if (StringUtils.isNotEmpty((String) properties.get("serialPort"))) { try { // load class by reflection to keep gnu.io (serial) optional. Declarative Services causes an // class not found exception, also if serial is not used! // FIXME: Is there a better way to avoid that a class not found exception? @SuppressWarnings("unchecked") Class<AbstractEBusWriteConnector> _tempClass = (Class<AbstractEBusWriteConnector>) EBusBinding.class .getClassLoader() .loadClass("org.openhab.binding.ebus.internal.connection.EBusSerialConnector"); Constructor<AbstractEBusWriteConnector> constructor = _tempClass .getDeclaredConstructor(String.class); connector = constructor.newInstance((String) properties.get("serialPort")); } catch (ClassNotFoundException e) { logger.error(e.toString(), e); } catch (NoSuchMethodException e) { logger.error(e.toString(), e); } catch (SecurityException e) { logger.error(e.toString(), e); } catch (InstantiationException e) { logger.error(e.toString(), e); } catch (IllegalAccessException e) { logger.error(e.toString(), e); } catch (IllegalArgumentException e) { logger.error(e.toString(), e); } catch (InvocationTargetException e) { logger.error(e.toString(), e); } } else if (StringUtils.isNotEmpty((String) properties.get("hostname"))) { // use the tcp-ip connector connector = new EBusTCPConnector((String) properties.get("hostname"), Integer.parseInt((String) properties.get("port"))); } // Set eBus sender id or default 0xFF if (StringUtils.isNotEmpty((String) properties.get("senderId"))) { connector.setSenderId(EBusUtils.toByte((String) properties.get("senderId"))); } if (properties.get("record") != null) { String debugWriterMode = (String) properties.get("record"); logger.info("Enable CSV writer for eBUS {}", debugWriterMode); debugWriter = new EBusTelegramCSVWriter(); debugWriter.openInUserData("ebus-" + debugWriterMode + ".csv"); parser.setDebugCSVWriter(debugWriter, debugWriterMode); } // add event listener connector.addEBusEventListener(this); // start thread connector.start(); // set the new connector commandProcessor.setConnector(connector); commandProcessor.setConfigurationProvider(configurationProvider); } catch (MalformedURLException e) { logger.error(e.toString(), e); } catch (IOException e) { throw new ConfigurationException("general", e.toString(), e); } } @Override public void allBindingsChanged(BindingProvider provider) { super.allBindingsChanged(provider); checkCommandProcessor(); commandProcessor.allBindingsChanged(provider); } @Override public void bindingChanged(BindingProvider provider, String itemName) { super.bindingChanged(provider, itemName); checkCommandProcessor(); commandProcessor.bindingChanged(provider, itemName); } /* * (non-Javadoc) * * @see org.openhab.core.binding.AbstractBinding#activate() */ @Override public void activate() { super.activate(); logger.debug("eBus binding has been started."); // check to ensure that it is available checkConfigurationProvider(); } /* * (non-Javadoc) * * @see org.openhab.core.binding.AbstractBinding#deactivate() */ @Override public void deactivate() { super.deactivate(); // stop last connector-thread if active stopConnector(); if (commandProcessor != null) { commandProcessor.deactivate(); commandProcessor = null; } if (debugWriter != null) { try { debugWriter.close(); } catch (IOException e) { logger.error("io error", e); } debugWriter = null; } logger.debug("eBus binding has been stopped."); } /* * (non-Javadoc) * * @see org.openhab.binding.ebus.connection.EBusConnectorEventListener#onTelegramReceived(org.openhab.binding.ebus. * EbusTelegram) */ @Override public void onTelegramReceived(EBusTelegram telegram) { // parse the raw telegram to a key/value map final Map<String, Object> results = parser.parse(telegram); if (results == null) { logger.trace("No valid parser result for raw telegram!"); return; } for (Entry<String, Object> entry : results.entrySet()) { State state = StateUtils.convertToState(entry.getValue()); // process if the state is set if (state != null) { // loop over all items to update the state for (EBusBindingProvider provider : providers) { for (String itemName : provider.getItemNames(entry.getKey())) { Byte telegramSource = provider.getTelegramSource(itemName); Byte telegramDestination = provider.getTelegramDestination(itemName); // check if this item has a src or dst defined if (telegramSource == null || telegram.getSource() == telegramSource) { if (telegramDestination == null || telegram.getDestination() == telegramDestination) { eventPublisher.postUpdate(itemName, state); } } } } // for } // if } } }