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