/**
* 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.homematic.internal.communicator.client;
import java.io.InputStream;
import java.io.StringReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.SimpleHttpConnectionManager;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.homematic.internal.communicator.client.interfaces.RpcClient;
import org.openhab.binding.homematic.internal.config.binding.DatapointConfig;
import org.openhab.binding.homematic.internal.config.binding.VariableConfig;
import org.openhab.binding.homematic.internal.model.CommonUnmarshallerListener;
import org.openhab.binding.homematic.internal.model.HmChannel;
import org.openhab.binding.homematic.internal.model.HmDatapoint;
import org.openhab.binding.homematic.internal.model.HmDevice;
import org.openhab.binding.homematic.internal.model.HmDeviceList;
import org.openhab.binding.homematic.internal.model.HmInterface;
import org.openhab.binding.homematic.internal.model.HmResult;
import org.openhab.binding.homematic.internal.model.HmRssiInfo;
import org.openhab.binding.homematic.internal.model.HmValueItem;
import org.openhab.binding.homematic.internal.model.HmVariable;
import org.openhab.binding.homematic.internal.model.HmVariableList;
import org.openhab.binding.homematic.internal.model.TclScript;
import org.openhab.binding.homematic.internal.model.TclScripts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* HomematicClient implementation for a CCU including TclRega script executer.
*
* @author Gerhard Riegler
* @since 1.6.0
*/
public class CcuClient extends BaseHomematicClient {
private static final Logger logger = LoggerFactory.getLogger(CcuClient.class);
private static final boolean TRACE_ENABLED = logger.isTraceEnabled();
private Map<String, String> tclregaScripts;
private HttpClient httpClient;
public CcuClient(RpcClient rpcClient) {
super(rpcClient);
}
/**
* {@inheritDoc}
*/
@Override
public HmInterface getDefaultInterface() {
return HmInterface.RF;
}
/**
* {@inheritDoc}
*/
@Override
public void start() throws HomematicClientException {
logger.info("Starting {}", CcuClient.class.getSimpleName());
super.start();
tclregaScripts = loadTclRegaScripts();
httpClient = new HttpClient(new SimpleHttpConnectionManager(true));
HttpClientParams params = httpClient.getParams();
Long timeout = context.getConfig().getTimeout() * 1000L;
params.setConnectionManagerTimeout(timeout);
params.setSoTimeout(timeout.intValue());
params.setContentCharset("ISO-8859-1");
}
/**
* {@inheritDoc}
*/
@Override
public void shutdown() throws HomematicClientException {
super.shutdown();
tclregaScripts = null;
httpClient = null;
}
/**
* {@inheritDoc}
*/
@Override
public void registerCallback() throws HomematicClientException {
rpcClient.init(getDefaultInterface());
rpcClient.init(HmInterface.WIRED);
rpcClient.init(HmInterface.CUXD);
}
/**
* {@inheritDoc}
*/
@Override
public void releaseCallback() throws HomematicClientException {
rpcClient.release(getDefaultInterface());
rpcClient.release(HmInterface.WIRED);
rpcClient.release(HmInterface.CUXD);
}
/**
* {@inheritDoc}
*/
@Override
public void iterateAllDatapoints(HmValueItemIteratorCallback callback) throws HomematicClientException {
List<HmDevice> devices = sendScriptByName("getAllDevices", HmDeviceList.class).getDevices();
Map<String, HmRssiInfo> rssiList = rpcClient.getRssiInfo(HmInterface.RF);
for (HmDevice device : devices) {
addBatteryInfo(device);
boolean deviceHasRssiDatapoint = false;
for (HmChannel channel : device.getChannels()) {
boolean isChannelZero = "0".equals(channel.getNumber());
for (HmDatapoint dp : channel.getDatapoints()) {
DatapointConfig bindingConfig = new DatapointConfig(device.getAddress(), channel.getNumber(),
dp.getName());
HmRssiInfo rssiInfo = rssiList.get(bindingConfig.getAddress());
if (rssiInfo != null) {
if ("RSSI_DEVICE".equals(bindingConfig.getParameter())) {
dp.setValue(rssiInfo.getDevice());
deviceHasRssiDatapoint = true;
} else if ("RSSI_PEER".equals(bindingConfig.getParameter())) {
dp.setValue(rssiInfo.getPeer());
deviceHasRssiDatapoint = true;
}
}
callback.iterate(bindingConfig, dp);
}
if (isChannelZero && !deviceHasRssiDatapoint) {
HmRssiInfo rssiInfo = rssiList.get(device.getAddress());
if (rssiInfo != null) {
logger.debug("Adding missing RSSI datapoints to device {} with address {}", device.getType(),
device.getAddress());
addRssiDatapoint(channel, "RSSI_DEVICE", rssiInfo.getDevice(), callback);
addRssiDatapoint(channel, "RSSI_PEER", rssiInfo.getPeer(), callback);
}
}
}
}
}
/**
* Generates a missing RSSI datapoint, workaround for a CCU bug.
*/
private void addRssiDatapoint(HmChannel channel, String name, Object value, HmValueItemIteratorCallback callback) {
HmDatapoint dp = new HmDatapoint();
dp.setName(name);
dp.setValueType(8);
dp.setWriteable(false);
dp.setValue(value);
channel.addDatapoint(dp);
DatapointConfig bindingConfig = new DatapointConfig(channel.getDevice().getAddress(), channel.getNumber(),
dp.getName());
callback.iterate(bindingConfig, dp);
}
/**
* {@inheritDoc}
*/
@Override
public Map<String, HmRssiInfo> getRssiInfo() throws HomematicClientException {
return rpcClient.getRssiInfo(HmInterface.RF);
}
/**
* {@inheritDoc}
*/
@Override
public void iterateAllVariables(HmValueItemIteratorCallback callback) throws HomematicClientException {
List<HmVariable> variables = sendScriptByName("getAllVariables", HmVariableList.class).getVariables();
for (HmVariable variable : variables) {
VariableConfig bindingConfig = new VariableConfig(variable.getName());
callback.iterate(bindingConfig, variable);
}
}
/**
* {@inheritDoc}
*/
@Override
public void executeProgram(String programName) throws HomematicClientException {
logger.debug("Executing program on CCU: {}", programName);
HmResult result = sendScriptByName("executeProgram", HmResult.class, new String[] { "program_name" },
new String[] { programName });
if (!result.isValid()) {
throw new HomematicClientException("Unable to start CCU program " + programName);
}
}
@Override
public void setDatapointValue(HmDatapoint dp, String datapointName, Object value) throws HomematicClientException {
HmInterface hmInterface = dp.getChannel().getDevice().getHmInterface();
if (hmInterface == HmInterface.VIRTUALDEVICES) {
String groupName = HmInterface.VIRTUALDEVICES + "." + dp.getChannel().getAddress() + "." + datapointName;
if (dp.isIntegerValue() && value instanceof Double) {
value = ((Number) value).intValue();
}
String strValue = ObjectUtils.toString(value);
if (dp.isStringValue()) {
strValue = "\"" + strValue + "\"";
}
HmResult result = sendScriptByName("setVirtualGroup", HmResult.class,
new String[] { "group_name", "group_state" }, new String[] { groupName, strValue });
if (!result.isValid()) {
throw new HomematicClientException("Unable to set CCU group " + groupName);
}
} else {
super.setDatapointValue(dp, datapointName, value);
}
}
/**
* {@inheritDoc}
*/
@Override
public void setVariable(HmValueItem hmValueItem, Object value) throws HomematicClientException {
String strValue = ObjectUtils.toString(value);
if (hmValueItem.isStringValue()) {
strValue = "\"" + strValue + "\"";
}
logger.debug("Sending {} with value '{}' to CCU", hmValueItem.getName(), strValue);
HmResult result = sendScriptByName("setVariable", HmResult.class,
new String[] { "variable_name", "variable_state" }, new String[] { hmValueItem.getName(), strValue });
if (!result.isValid()) {
throw new HomematicClientException("Unable to set CCU variable " + hmValueItem.getName());
}
}
/**
* Sends a TclRega script to the CCU.
*/
private <T> T sendScriptByName(String scriptName, Class<T> clazz) throws HomematicClientException {
return sendScript(getTclRegaScript(scriptName), clazz);
}
/**
* Sends a TclRega script with the specified variables to the CCU.
*/
private <T> T sendScriptByName(String scriptName, Class<T> clazz, String[] variableNames, String[] values)
throws HomematicClientException {
String script = getTclRegaScript(scriptName);
for (int i = 0; i < variableNames.length; i++) {
script = StringUtils.replace(script, "{" + variableNames[i] + "}", values[i]);
}
return sendScript(script, clazz);
}
private String getTclRegaScript(String scriptName) throws HomematicClientException {
if (!isStarted()) {
throw new HomematicClientException(CcuClient.class.getSimpleName() + " is not configured!");
}
return tclregaScripts.get(scriptName);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isStarted() {
return tclregaScripts != null;
}
/**
* {@inheritDoc}
*/
@Override
public boolean supportsVariables() {
return true;
}
/**
* Main method for sending a TclRega script and parsing the XML result.
*/
@SuppressWarnings("unchecked")
private synchronized <T> T sendScript(String script, Class<T> clazz) throws HomematicClientException {
PostMethod post = null;
try {
script = StringUtils.trim(script);
if (StringUtils.isEmpty(script)) {
throw new RuntimeException("Homematic TclRegaScript is empty!");
}
if (TRACE_ENABLED) {
logger.trace("TclRegaScript: {}", script);
}
post = new PostMethod(context.getConfig().getTclRegaUrl());
RequestEntity re = new ByteArrayRequestEntity(script.getBytes("ISO-8859-1"));
post.setRequestEntity(re);
httpClient.executeMethod(post);
String result = post.getResponseBodyAsString();
result = StringUtils.substringBeforeLast(result, "<xml><exec>");
if (TRACE_ENABLED) {
logger.trace("Result TclRegaScript: {}", result);
}
Unmarshaller um = JAXBContext.newInstance(clazz).createUnmarshaller();
um.setListener(new CommonUnmarshallerListener());
return (T) um.unmarshal(new StringReader(result));
} catch (Exception ex) {
throw new HomematicClientException(ex.getMessage(), ex);
} finally {
if (post != null) {
post.releaseConnection();
}
}
}
/**
* Load predefined scripts from an XML file.
*/
private Map<String, String> loadTclRegaScripts() throws HomematicClientException {
try {
Unmarshaller um = JAXBContext.newInstance(TclScripts.class).createUnmarshaller();
InputStream stream = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("homematic/tclrega-scripts.xml");
TclScripts scripts = (TclScripts) um.unmarshal(stream);
Map<String, String> result = new HashMap<String, String>();
for (TclScript script : scripts.getScripts()) {
result.put(script.getName(), script.getData());
}
return result;
} catch (JAXBException ex) {
throw new HomematicClientException(ex.getMessage(), ex);
}
}
}