/**
* 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.km200.internal;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.km200.KM200BindingProvider;
import org.openhab.core.binding.AbstractActiveBinding;
import org.openhab.core.events.EventPublisher;
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.google.common.net.InetAddresses;
/**
* The KM200 binding connects to a Buderus Gateway Logamatic web KM50/100/200.
*
* @author Markus Eckhardt
*
* @since 1.9.0
*/
public class KM200Binding extends AbstractActiveBinding<KM200BindingProvider> implements ManagedService {
private static final Logger logger = LoggerFactory.getLogger(KM200Binding.class);
private Map<String, byte[]> sendMap = Collections.synchronizedMap(new LinkedHashMap<String, byte[]>());
ExecutorService threadPool = Executors.newSingleThreadExecutor();
private KM200Device device = null;
private KM200Comm comm = null;
private SendKM200Thread sThread = null;
public KM200Binding() {
if (device == null) {
device = new KM200Device();
}
if (comm == null) {
comm = new KM200Comm(device);
}
}
@Override
public void activate() {
if (device != null) {
logger.debug("Starting send thread");
sThread = new SendKM200Thread(sendMap, device, comm, providers, eventPublisher);
sThread.start();
}
super.activate();
logger.info("Activated");
}
@Override
public void deactivate() {
if (sThread != null) {
logger.debug("Interrupt send thread");
sThread.interrupt();
}
super.deactivate();
logger.info("Deactivated");
}
protected void addBindingProvider(KM200BindingProvider bindingProvider) {
super.addBindingProvider(bindingProvider);
}
protected void removeBindingProvider(KM200BindingProvider bindingProvider) {
super.removeBindingProvider(bindingProvider);
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("rawtypes")
public void updated(Dictionary config) throws ConfigurationException {
if (config == null) {
return;
} else {
if (config.isEmpty()) {
return;
}
logger.info("Update KM200 Binding configuration, it takes a minute....");
String ip = Objects.toString(config.get("ip4_address"), null);
if (StringUtils.isNotBlank(ip)) {
try {
InetAddresses.forString(ip);
} catch (IllegalArgumentException e) {
logger.error("IP4_address in openhab.cfg is not valid!");
throw new ConfigurationException("ip4_address", "ip4_address in openhab.cfg is not valid!");
}
device.setIP4Address(ip);
} else {
logger.error("No ip4_address in openhab.cfg set!");
throw new ConfigurationException("ip4_address", "No ip4_address in openhab.cfg set!");
}
/* There a two possibilities of configuratiom */
/* 1. With a private key */
String PrivKey = Objects.toString(config.get("PrivKey"), null);
if (StringUtils.isNotBlank(PrivKey)) {
device.setCryptKeyPriv(PrivKey);
} else { /* 2. With the MD5Salt, the device and user private password */
String MD5Salt = Objects.toString(config.get("MD5Salt"), null);
if (StringUtils.isNotBlank(MD5Salt)) {
device.setMD5Salt(MD5Salt);
} else {
logger.error("No MD5Salt in openhab.cfg set!");
throw new ConfigurationException("MD5Salt", "No MD5Salt in openhab.cfg set!");
}
String gpassword = Objects.toString(config.get("GatewayPassword"), null);
if (StringUtils.isNotBlank(gpassword)) {
device.setGatewayPassword(gpassword);
} else {
logger.error("No GatewayPassword in openhab.cfg set!");
throw new ConfigurationException("GatewayPassword", "No GatewayPassword in openhab.cfg set!");
}
String ppassword = Objects.toString(config.get("PrivatePassword"), null);
if (StringUtils.isNotBlank(ppassword)) {
device.setPrivatePassword(ppassword);
} else {
logger.error("No PrivatePassword in openhab.cfg set!");
throw new ConfigurationException("PrivatePassword", "No PrivatePassword in openhab.cfg set!");
}
}
logger.info("Starting communication test..");
/* Get HTTP Data from device */
byte[] recData = comm.getDataFromService("/gateway/DateTime");
if (recData == null) {
throw new RuntimeException("Communication is not possible!");
}
if (recData.length == 0) {
throw new RuntimeException("No reply from KM200!");
}
logger.info("Received data..");
/* Derypt the message */
String decodedData = comm.decodeMessage(recData);
if (decodedData == null) {
throw new RuntimeException("Decoding of the KM200 message is not possible!");
}
if (decodedData == "SERVICE NOT AVAILABLE") {
logger.error("/gateway/DateTime: SERVICE NOT AVAILABLE");
} else {
logger.info("Test of the communication to the gateway was successful..");
}
logger.info("Init services..");
/* communication is working */
/* Checking of the devicespecific services and creating of a service list */
for (KM200ServiceTypes service : KM200ServiceTypes.values()) {
try {
logger.debug(service.getDescription());
comm.initObjects(service.getDescription());
} catch (Exception e) {
logger.error("Couldn't init service: {} error: {}", service, e.getMessage());
}
}
/* Now init the virtual services */
logger.debug("init Virtual Objects");
try {
comm.initVirtualObjects();
} catch (Exception e) {
logger.error("Couldn't init virtual services: {}", e.getMessage());
}
/* Output all availible services in the log file */
/* Now init the virtual services */
logger.debug("list All Services");
device.listAllServices();
logger.info("... Update of the KM200 Binding configuration completed");
device.setInited(true);
setProperlyConfigured(true);
}
}
@Override
protected void execute() {
logger.debug("KM200 execute");
if (device == null) {
return;
} else if (!device.isConfigured() || !device.getInited()) {
logger.error("Device is not configured, did you set the configuration?");
return;
}
threadPool.submit(new GetKM200Runnable(device, comm, providers, eventPublisher));
}
@SuppressWarnings("null")
@Override
public void internalReceiveCommand(String item, Command command) {
logger.debug("internalReceiveCommand");
byte[] sendData = null;
if (device != null) {
String type = null;
String service = null;
KM200BindingProvider provider = null;
for (KM200BindingProvider tmpProvider : providers) {
type = tmpProvider.getType(item);
if (type != null) {
provider = tmpProvider;
break;
}
}
if (type == null) {
return;
}
logger.debug("KM200 type: {} {}", type, provider.getService(item));
try {
sendData = comm.sendProvidersState(provider, item, command);
} catch (Exception e) {
logger.error("Could not send item state {}", e);
}
synchronized (device) {
service = comm.checkParameterReplacement(provider, item);
if (sendData != null) {
sendMap.put(item, sendData);
} else if (device.serviceMap.get(service).getVirtual() == 1) {
String parent = device.serviceMap.get(service).getParent();
for (KM200BindingProvider tmpProvider : providers) {
for (String tmpItem : tmpProvider.getItemNames()) {
service = comm.checkParameterReplacement(tmpProvider, tmpItem);
if (parent.equals(device.serviceMap.get(service).getParent())) {
try {
State state = comm.getProvidersState(tmpProvider, tmpItem);
if (state != null) {
eventPublisher.postUpdate(tmpItem, state);
}
} catch (Exception e) {
logger.error("Could not get updated item state, Error: {}", e);
}
}
}
}
}
}
}
}
/**
* The GetKM200Runnable class get the data from device to the items.
*
* @author Markus Eckhardt
*
* @since 1.9.0
*/
private static class GetKM200Runnable implements Runnable {
public GetKM200Runnable(KM200Device device, KM200Comm comm, Collection<KM200BindingProvider> providers,
EventPublisher eventPublisher) {
super();
this.device = device;
this.providers = providers;
this.eventPublisher = eventPublisher;
this.comm = comm;
}
private Collection<KM200BindingProvider> providers;
private KM200Device device;
private EventPublisher eventPublisher;
private KM200Comm comm;
@Override
public void run() {
try {
logger.debug("GetKM200Runnable");
org.openhab.core.types.State state = null;
synchronized (device) {
device.resetAllUpdates();
for (KM200BindingProvider provider : providers) {
for (String item : provider.getItemNames()) {
try {
state = comm.getProvidersState(provider, item);
if (state != null) {
eventPublisher.postUpdate(item, state);
}
} catch (Exception e) {
logger.error("Could not get item state, Error: {}", e);
}
}
}
}
} catch (
Exception e) {
logger.warn("Error processing command", e);
}
}
}
/**
* The sendKM200Thread class sends the data to the device.
*
* @author Markus Eckhardt
*
* @since 1.9.0
*/
private static class SendKM200Thread extends Thread {
public SendKM200Thread(Map<String, byte[]> sendMap, KM200Device device, KM200Comm comm,
Collection<KM200BindingProvider> providers, EventPublisher eventPublisher) {
super();
this.sendMap = sendMap;
this.device = device;
this.providers = providers;
this.eventPublisher = eventPublisher;
this.comm = comm;
}
private Map<String, byte[]> sendMap = null;
private Collection<KM200BindingProvider> providers;
private KM200Device device;
private EventPublisher eventPublisher;
private KM200Comm comm;
@Override
public void run() {
try {
logger.debug("Send-Thread started");
while (!isInterrupted()) {
Map.Entry<String, byte[]> nextEntry = null;
{
/* Check whether a new entry is availible, if yes then take and remove it */
synchronized (sendMap) {
Iterator<Entry<String, byte[]>> i = sendMap.entrySet().iterator();
if (i.hasNext()) {
logger.debug("Send-Thread, new entry");
nextEntry = i.next();
i.remove();
}
}
}
if (nextEntry != null) {
/* Now send the data to the device */
Integer rCode;
org.openhab.core.types.State state = null;
String item = nextEntry.getKey();
KM200BindingProvider provider = null;
byte[] encData = nextEntry.getValue();
for (KM200BindingProvider tmpProvider : providers) {
String type = tmpProvider.getType(item);
if (type != null) {
provider = tmpProvider;
break;
}
}
if (provider == null) {
continue;
}
String service = comm.checkParameterReplacement(provider, item);
KM200CommObject object = device.serviceMap.get(service);
logger.debug("Sending: {}", provider.getService(item));
if (object.getVirtual() == 1) {
rCode = comm.sendDataToService(object.getParent(), encData);
} else {
rCode = comm.sendDataToService(service, encData);
}
logger.debug("Returncode: {}", rCode);
/* set all update flags to zero */
logger.debug("Data sended, reset und updated providers");
/* Now update the set values and for all virtual values depending on same parent */
if (object.getVirtual() == 1) {
String parent = object.getParent();
device.serviceMap.get(parent).setUpdated(false);
for (KM200BindingProvider tmpProvider : providers) {
for (String tmpItem : tmpProvider.getItemNames()) {
String tmpService = comm.checkParameterReplacement(tmpProvider, tmpItem);
if (parent.equals(device.serviceMap.get(tmpService).getParent())) {
try {
state = comm.getProvidersState(tmpProvider, tmpItem);
if (state != null) {
eventPublisher.postUpdate(tmpItem, state);
}
} catch (Exception e) {
logger.error("Could not get updated item state, Error: {}", e);
}
}
}
}
} else {
try {
object.setUpdated(false);
state = comm.getProvidersState(provider, item);
if (state != null) {
eventPublisher.postUpdate(item, state);
}
/* Check whether the service is used as a parameter replacement */
for (KM200BindingProvider tmpProvider : providers) {
for (String tmpItem : tmpProvider.getItemNames()) {
if (tmpProvider.getParameter(tmpItem).containsKey("current")) {
if (service.equals(tmpProvider.getParameter(tmpItem).get("current"))) {
try {
state = comm.getProvidersState(tmpProvider, tmpItem);
if (state != null) {
eventPublisher.postUpdate(tmpItem, state);
}
} catch (Exception e) {
logger.error("Could not get updated item state, Error: {}", e);
}
}
}
}
}
} catch (Exception e) {
logger.error("Could not get item state, Error: {}", e);
}
}
}
/*
* We have time, all changes on same item in this time are overwritten in memory and we need send
* only the last state
*/
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
interrupt();
}
}
} catch (
Exception e) {
logger.warn("Error processing command", e);
}
}
}
@Override
protected long getRefreshInterval() {
return 60000L;
}
@Override
protected String getName() {
return "KM200 Binding";
}
}