/**
* 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;
}
}