/** * 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.connection; import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; import org.openhab.binding.ebus.EBusBindingProvider; import org.openhab.binding.ebus.internal.configuration.TelegramConfiguration; import org.openhab.binding.ebus.internal.configuration.TelegramValue; import org.openhab.binding.ebus.internal.parser.EBusConfigurationProvider; import org.openhab.binding.ebus.internal.utils.EBusCodecUtils; import org.openhab.binding.ebus.internal.utils.EBusUtils; import org.openhab.binding.ebus.internal.utils.NumberUtils; import org.openhab.binding.ebus.internal.utils.StateUtils; import org.openhab.core.binding.BindingChangeListener; import org.openhab.core.binding.BindingProvider; import org.openhab.core.types.Command; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Christian Sowada * @since 1.7.0 */ public class EBusCommandProcessor implements BindingChangeListener { private static final Logger logger = LoggerFactory.getLogger(EBusCommandProcessor.class); private Map<String, ScheduledFuture<?>> futureMap = new HashMap<String, ScheduledFuture<?>>(); private ScheduledExecutorService scheduler; private AbstractEBusWriteConnector connector; private EBusConfigurationProvider configurationProvider; /** * @param connector */ public void setConnector(AbstractEBusWriteConnector connector) { this.connector = connector; } /** * */ public void deactivate() { if (scheduler != null) { scheduler.shutdown(); } } /* * (non-Javadoc) * * @see org.openhab.core.binding.BindingChangeListener#allBindingsChanged(org.openhab.core.binding.BindingProvider) */ @Override public void allBindingsChanged(BindingProvider provider) { logger.debug("Remove all polling items for this provider from scheduler ..."); for (String itemName : provider.getItemNames()) { if (futureMap.containsKey(itemName)) { futureMap.get(itemName).cancel(true); } } for (String itemName : provider.getItemNames()) { bindingChanged(provider, itemName); } } /* * (non-Javadoc) * * @see org.openhab.core.binding.BindingChangeListener#bindingChanged(org.openhab.core.binding.BindingProvider, * java.lang.String) */ @Override public void bindingChanged(BindingProvider provider, final String itemName) { logger.debug("Binding changed for item {}", itemName); final EBusBindingProvider eBusProvider = (EBusBindingProvider) provider; int refreshRate = eBusProvider.getRefreshRate(itemName); if (refreshRate > 0) { final Runnable r = new Runnable() { @Override public void run() { try { byte[] data = composeSendData(eBusProvider, itemName, null); if (data != null && data.length > 0) { if (connector == null) { logger.warn("eBus connector not ready, can't send data yet!"); } else { connector.addToSendQueue(data); } } else { if (configurationProvider != null && !configurationProvider.isEmpty()) { logger.warn("No data to send for item {}! Check your item configuration.", itemName); } } } catch (Exception e) { logger.error("Error while running runnable ...", e); } } }; if (futureMap.containsKey(itemName)) { logger.debug("Stopped old polling item {} ...", itemName); futureMap.remove(itemName).cancel(true); } if (scheduler == null) { scheduler = Executors.newScheduledThreadPool(2, new WorkerThreadFactory("ebus-scheduler")); } logger.debug("Add polling item {} with refresh rate {} to scheduler ...", itemName, refreshRate); // do not start all pollings at the same time int randomInitDelay = (int) (Math.random() * (30 - 4) + 4); futureMap.put(itemName, scheduler.scheduleWithFixedDelay(r, randomInitDelay, refreshRate, TimeUnit.SECONDS)); } else if (futureMap.containsKey(itemName)) { logger.debug("Remove scheduled refresh for item {}", itemName); futureMap.get(itemName).cancel(true); futureMap.remove(itemName); } } /** * @param binding */ public void setConfigurationProvider(EBusConfigurationProvider configurationProvider) { this.configurationProvider = configurationProvider; } /** * @param commandId * @param commandClass * @param dst * @param src * @param values * @return */ private byte[] composeEBusTelegram(String commandId, Byte dst, Byte src, Map<String, Object> values) { if (configurationProvider == null || configurationProvider.isEmpty()) { logger.debug("eBUS configuration provider not ready, can't get send data yet."); return null; } byte[] buffer = null; TelegramConfiguration commandCfg = configurationProvider.getCommandById(commandId); if (commandCfg != null) { if (dst == null && StringUtils.isNotEmpty(commandCfg.getDst())) { dst = EBusUtils.toByte(commandCfg.getDst()); } if (dst == null) { logger.error("Unable to send command, destination address is missing. Set \"dst\" in item.cfg ..."); return null; } byte[] bytesData = EBusUtils.toByteArray(commandCfg.getData()); byte[] bytesCmd = EBusUtils.toByteArray(commandCfg.getCommand()); if (values == null || values.isEmpty()) { logger.trace("No setter-values for eBUS telegram, used default data ..."); buffer = new byte[bytesData.length + 6]; buffer[0] = src; buffer[1] = dst; buffer[4] = (byte) bytesData.length; System.arraycopy(bytesCmd, 0, buffer, 2, bytesCmd.length); System.arraycopy(bytesData, 0, buffer, 5, bytesData.length); return buffer; } Map<String, TelegramValue> valuesConfig = commandCfg.getValues(); if (valuesConfig == null || valuesConfig.isEmpty()) { logger.warn("No values configurated in json cfg ..."); return null; } for (Entry<String, Object> entry : values.entrySet()) { TelegramValue valueEntry = valuesConfig.get(entry.getKey()); if (valueEntry == null) { logger.warn("Unable to set value key \"{}\" in command \"{}.{}\", can't compose telegram ...", entry.getKey(), commandId); return null; } String type = valueEntry.getType(); int pos = valueEntry.getPos() - 1; BigDecimal value = NumberUtils.toBigDecimal(entry.getValue()); if (valueEntry.getMax() != null && value.compareTo(valueEntry.getMax()) == 1) { throw new RuntimeException("Value larger than allowed!"); } if (valueEntry.getMin() != null && value.compareTo(valueEntry.getMin()) == -1) { throw new RuntimeException("Value smaller than allowed!"); } if (value != null && valueEntry.getFactor() != null) { value = value.divide(valueEntry.getFactor()); } byte[] encode = EBusCodecUtils.encode(type, value); if (encode.length == 0) { logger.warn("eBUS codec encoder returns empty buffer ..."); return null; } // add computed single value to data buffer System.arraycopy(encode, 0, bytesData, pos - 5, encode.length); } for (Entry<String, TelegramValue> value : valuesConfig.entrySet()) { // check if the special value type for kromsch�der/wolf crc is availabel if (StringUtils.equals(value.getValue().getType(), "crc-kw")) { byte b = 0; int pos = value.getValue().getPos() - 6; for (int i = 0; i < bytesData.length; i++) { // exclude crc pos if (i != pos) { b = EBusUtils.crc8(bytesData[i], b, (byte) 0x5C); } } // set crc to specified position bytesData[pos] = b; } } bytesData = EBusUtils.encodeEBusData(bytesData); buffer = new byte[bytesData.length + 6]; buffer[0] = src; buffer[1] = dst; buffer[4] = (byte) bytesData.length; System.arraycopy(bytesCmd, 0, buffer, 2, bytesCmd.length); System.arraycopy(bytesData, 0, buffer, 5, bytesData.length); return buffer; } return null; } /** * @param provider * @param itemName * @param type * @return */ public byte[] composeSendData(EBusBindingProvider provider, String itemName, Command command) { if (configurationProvider == null || configurationProvider.isEmpty()) { logger.debug("eBus configuration provider not ready, can't get send data yet."); return null; } byte[] data = null; String cmd = provider.getCommand(itemName); Byte dst = provider.getTelegramDestination(itemName); Byte src = provider.getTelegramSource(itemName); HashMap<String, Object> values = null; if (StringUtils.isEmpty(cmd)) { // use id instead String id = provider.getId(itemName); if (!StringUtils.isEmpty(id)) { String[] split = StringUtils.split(id, "."); if (split.length > 1) { cmd = split[0] + "." + split[1]; } } } if (src == null) { src = connector.getSenderId(); } if (command == null) { // polling data = composeEBusTelegram(cmd, dst, src, values); } else { String setValue = provider.getSet(itemName); int index = StringUtils.lastIndexOf(setValue, "."); String cmdId = StringUtils.left(setValue, index); String valueName = StringUtils.substring(setValue, index + 1); // try to convert command to a supported value Object value = StateUtils.convertFromState(command); if (value != null) { values = new HashMap<String, Object>(); values.put(valueName, value); } data = composeEBusTelegram(cmdId, dst, src, values); } // first try, data-ON, data-OFF, etc. if (data == null) { String type = command != null ? command.toString().toLowerCase() : null; if (StringUtils.isNotEmpty(type)) { data = provider.getTelegramData(itemName, type); } } if (data == null) { // ok, try data param data = provider.getTelegramData(itemName); } return data; } }