]> git.basschouten.com Git - openhab-addons.git/blob
01f4f35b1c714e3fa610bc79a0b7fa6e15109705
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.homematic.internal.communicator;
14
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.HashMap;
20 import java.util.Map;
21 import java.util.concurrent.TimeUnit;
22
23 import org.apache.commons.lang.ObjectUtils;
24 import org.apache.commons.lang.StringUtils;
25 import org.eclipse.jetty.client.HttpClient;
26 import org.eclipse.jetty.client.api.ContentResponse;
27 import org.eclipse.jetty.client.util.StringContentProvider;
28 import org.eclipse.jetty.http.HttpHeader;
29 import org.openhab.binding.homematic.internal.common.HomematicConfig;
30 import org.openhab.binding.homematic.internal.communicator.client.UnknownParameterSetException;
31 import org.openhab.binding.homematic.internal.communicator.client.UnknownRpcFailureException;
32 import org.openhab.binding.homematic.internal.communicator.parser.CcuLoadDeviceNamesParser;
33 import org.openhab.binding.homematic.internal.communicator.parser.CcuParamsetDescriptionParser;
34 import org.openhab.binding.homematic.internal.communicator.parser.CcuValueParser;
35 import org.openhab.binding.homematic.internal.communicator.parser.CcuVariablesAndScriptsParser;
36 import org.openhab.binding.homematic.internal.model.HmChannel;
37 import org.openhab.binding.homematic.internal.model.HmDatapoint;
38 import org.openhab.binding.homematic.internal.model.HmDevice;
39 import org.openhab.binding.homematic.internal.model.HmParamsetType;
40 import org.openhab.binding.homematic.internal.model.HmResult;
41 import org.openhab.binding.homematic.internal.model.TclScript;
42 import org.openhab.binding.homematic.internal.model.TclScriptDataList;
43 import org.openhab.binding.homematic.internal.model.TclScriptList;
44 import org.osgi.framework.Bundle;
45 import org.osgi.framework.FrameworkUtil;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 import com.thoughtworks.xstream.XStream;
50 import com.thoughtworks.xstream.io.xml.StaxDriver;
51
52 /**
53  * HomematicGateway implementation for a CCU.
54  *
55  * @author Gerhard Riegler - Initial contribution
56  */
57 public class CcuGateway extends AbstractHomematicGateway {
58     private final Logger logger = LoggerFactory.getLogger(CcuGateway.class);
59
60     private Map<String, String> tclregaScripts;
61     private XStream xStream = new XStream(new StaxDriver());
62
63     protected CcuGateway(String id, HomematicConfig config, HomematicGatewayAdapter gatewayAdapter,
64             HttpClient httpClient) {
65         super(id, config, gatewayAdapter, httpClient);
66
67         XStream.setupDefaultSecurity(xStream);
68         xStream.allowTypesByWildcard(new String[] { HmDevice.class.getPackageName() + ".**" });
69         xStream.setClassLoader(CcuGateway.class.getClassLoader());
70         xStream.autodetectAnnotations(true);
71         xStream.alias("scripts", TclScriptList.class);
72         xStream.alias("list", TclScriptDataList.class);
73         xStream.alias("result", HmResult.class);
74     }
75
76     @Override
77     protected void startClients() throws IOException {
78         super.startClients();
79
80         tclregaScripts = loadTclRegaScripts();
81     }
82
83     @Override
84     protected void stopClients() {
85         super.stopClients();
86         tclregaScripts = null;
87     }
88
89     @Override
90     protected void loadVariables(HmChannel channel) throws IOException {
91         TclScriptDataList resultList = sendScriptByName("getAllVariables", TclScriptDataList.class);
92         new CcuVariablesAndScriptsParser(channel).parse(resultList);
93     }
94
95     @Override
96     protected void loadScripts(HmChannel channel) throws IOException {
97         TclScriptDataList resultList = sendScriptByName("getAllPrograms", TclScriptDataList.class);
98         new CcuVariablesAndScriptsParser(channel).parse(resultList);
99     }
100
101     @Override
102     protected void loadDeviceNames(Collection<HmDevice> devices) throws IOException {
103         TclScriptDataList resultList = sendScriptByName("getAllDeviceNames", TclScriptDataList.class);
104         new CcuLoadDeviceNamesParser(devices).parse(resultList);
105     }
106
107     @Override
108     protected void setChannelDatapointValues(HmChannel channel, HmParamsetType paramsetType) throws IOException {
109         try {
110             super.setChannelDatapointValues(channel, paramsetType);
111         } catch (UnknownRpcFailureException ex) {
112             logger.debug(
113                     "RpcMessage unknown RPC failure (-1 Failure), fetching values with TclRega script for device {}, channel: {}, paramset: {}",
114                     channel.getDevice().getAddress(), channel.getNumber(), paramsetType);
115
116             Collection<String> dpNames = new ArrayList<>();
117             for (HmDatapoint dp : channel.getDatapoints()) {
118                 if (!dp.isVirtual() && dp.isReadable() && dp.getParamsetType() == HmParamsetType.VALUES) {
119                     dpNames.add(dp.getName());
120                 }
121             }
122             if (!dpNames.isEmpty()) {
123                 HmDevice device = channel.getDevice();
124                 String channelName = String.format("%s.%s:%s.", device.getHmInterface().getName(), device.getAddress(),
125                         channel.getNumber());
126                 String datapointNames = StringUtils.join(dpNames.toArray(), "\\t");
127                 TclScriptDataList resultList = sendScriptByName("getAllChannelValues", TclScriptDataList.class,
128                         new String[] { "channel_name", "datapoint_names" },
129                         new String[] { channelName, datapointNames });
130                 new CcuValueParser(channel).parse(resultList);
131                 channel.setInitialized(true);
132             }
133         }
134     }
135
136     @Override
137     protected void addChannelDatapoints(HmChannel channel, HmParamsetType paramsetType) throws IOException {
138         try {
139             getRpcClient(channel.getDevice().getHmInterface()).addChannelDatapoints(channel, paramsetType);
140         } catch (UnknownParameterSetException ex) {
141             logger.debug(
142                     "RpcMessage RPC failure (-3 Unknown paramset), fetching metadata with TclRega script for device: {}, channel: {}, paramset: {}",
143                     channel.getDevice().getAddress(), channel.getNumber(), paramsetType);
144
145             TclScriptDataList resultList = sendScriptByName("getParamsetDescription", TclScriptDataList.class,
146                     new String[] { "device_address", "channel_number" },
147                     new String[] { channel.getDevice().getAddress(), channel.getNumber().toString() });
148             new CcuParamsetDescriptionParser(channel, paramsetType).parse(resultList);
149         }
150     }
151
152     @Override
153     protected void setVariable(HmDatapoint dp, Object value) throws IOException {
154         String strValue = StringUtils.replace(ObjectUtils.toString(value), "\"", "\\\"");
155         if (dp.isStringType()) {
156             strValue = "\"" + strValue + "\"";
157         }
158         HmResult result = sendScriptByName("setVariable", HmResult.class,
159                 new String[] { "variable_name", "variable_state" }, new String[] { dp.getInfo(), strValue });
160         if (!result.isValid()) {
161             throw new IOException("Unable to set CCU variable " + dp.getInfo());
162         }
163     }
164
165     @Override
166     protected void executeScript(HmDatapoint dp) throws IOException {
167         HmResult result = sendScriptByName("executeProgram", HmResult.class, new String[] { "program_name" },
168                 new String[] { dp.getInfo() });
169         if (!result.isValid()) {
170             throw new IOException("Unable to start CCU program: " + dp.getInfo());
171         }
172     }
173
174     /**
175      * Sends a TclRega script to the CCU.
176      */
177     private <T> T sendScriptByName(String scriptName, Class<T> clazz) throws IOException {
178         return sendScriptByName(scriptName, clazz, new String[] {}, null);
179     }
180
181     /**
182      * Sends a TclRega script with the specified variables to the CCU.
183      */
184     private <T> T sendScriptByName(String scriptName, Class<T> clazz, String[] variableNames, String[] values)
185             throws IOException {
186         String script = tclregaScripts.get(scriptName);
187         for (int i = 0; i < variableNames.length; i++) {
188             script = StringUtils.replace(script, "{" + variableNames[i] + "}", values[i]);
189         }
190         return sendScript(script, clazz);
191     }
192
193     /**
194      * Main method for sending a TclRega script and parsing the XML result.
195      */
196     @SuppressWarnings("unchecked")
197     private synchronized <T> T sendScript(String script, Class<T> clazz) throws IOException {
198         try {
199             script = StringUtils.trim(script);
200             if (StringUtils.isEmpty(script)) {
201                 throw new RuntimeException("Homematic TclRegaScript is empty!");
202             }
203             if (logger.isTraceEnabled()) {
204                 logger.trace("TclRegaScript: {}", script);
205             }
206
207             StringContentProvider content = new StringContentProvider(script, config.getEncoding());
208             ContentResponse response = httpClient.POST(config.getTclRegaUrl()).content(content)
209                     .timeout(config.getTimeout(), TimeUnit.SECONDS)
210                     .header(HttpHeader.CONTENT_TYPE, "text/plain;charset=" + config.getEncoding()).send();
211
212             String result = new String(response.getContent(), config.getEncoding());
213             result = StringUtils.substringBeforeLast(result, "<xml><exec>");
214             if (logger.isTraceEnabled()) {
215                 logger.trace("Result TclRegaScript: {}", result);
216             }
217
218             return (T) xStream.fromXML(result);
219         } catch (Exception ex) {
220             throw new IOException(ex.getMessage(), ex);
221         }
222     }
223
224     /**
225      * Load predefined scripts from an XML file.
226      */
227     private Map<String, String> loadTclRegaScripts() throws IOException {
228         Bundle bundle = FrameworkUtil.getBundle(getClass());
229         try (InputStream stream = bundle.getResource("homematic/tclrega-scripts.xml").openStream()) {
230             TclScriptList scriptList = (TclScriptList) xStream.fromXML(stream);
231             Map<String, String> result = new HashMap<>();
232             if (scriptList.getScripts() != null) {
233                 for (TclScript script : scriptList.getScripts()) {
234                     result.put(script.name, StringUtils.trimToNull(script.data));
235                 }
236             }
237             return result;
238         } catch (IllegalStateException | IOException e) {
239             throw new IOException("The resource homematic/tclrega-scripts.xml could not be loaded!", e);
240         }
241     }
242 }