]> git.basschouten.com Git - openhab-addons.git/blob
4f2fab2c823c902fe493a3ed9dc0a11e2f538ccb
[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.client;
14
15 import static org.openhab.binding.homematic.internal.HomematicBindingConstants.*;
16
17 import java.io.IOException;
18 import java.util.Collection;
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Map;
22
23 import org.openhab.binding.homematic.internal.HomematicBindingConstants;
24 import org.openhab.binding.homematic.internal.common.HomematicConfig;
25 import org.openhab.binding.homematic.internal.communicator.message.RpcRequest;
26 import org.openhab.binding.homematic.internal.communicator.parser.GetAllScriptsParser;
27 import org.openhab.binding.homematic.internal.communicator.parser.GetAllSystemVariablesParser;
28 import org.openhab.binding.homematic.internal.communicator.parser.GetDeviceDescriptionParser;
29 import org.openhab.binding.homematic.internal.communicator.parser.GetParamsetDescriptionParser;
30 import org.openhab.binding.homematic.internal.communicator.parser.GetParamsetParser;
31 import org.openhab.binding.homematic.internal.communicator.parser.GetValueParser;
32 import org.openhab.binding.homematic.internal.communicator.parser.HomegearLoadDeviceNamesParser;
33 import org.openhab.binding.homematic.internal.communicator.parser.ListBidcosInterfacesParser;
34 import org.openhab.binding.homematic.internal.communicator.parser.ListDevicesParser;
35 import org.openhab.binding.homematic.internal.communicator.parser.RssiInfoParser;
36 import org.openhab.binding.homematic.internal.misc.MiscUtils;
37 import org.openhab.binding.homematic.internal.model.HmChannel;
38 import org.openhab.binding.homematic.internal.model.HmDatapoint;
39 import org.openhab.binding.homematic.internal.model.HmDevice;
40 import org.openhab.binding.homematic.internal.model.HmGatewayInfo;
41 import org.openhab.binding.homematic.internal.model.HmInterface;
42 import org.openhab.binding.homematic.internal.model.HmParamsetType;
43 import org.openhab.binding.homematic.internal.model.HmRssiInfo;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48  * Client implementation for sending messages via BIN-RPC to a Homematic gateway.
49  *
50  * @author Gerhard Riegler - Initial contribution
51  */
52 public abstract class RpcClient<T> {
53     private final Logger logger = LoggerFactory.getLogger(RpcClient.class);
54     protected static final int MAX_RPC_RETRY = 3;
55     protected static final int RESP_BUFFER_SIZE = 8192;
56
57     protected HomematicConfig config;
58
59     public RpcClient(HomematicConfig config) {
60         this.config = config;
61     }
62
63     /**
64      * Disposes the client.
65      */
66     public abstract void dispose();
67
68     /**
69      * Returns a RpcRequest for this client.
70      */
71     protected abstract RpcRequest<T> createRpcRequest(String methodName);
72
73     /**
74      * Returns the callback url for this client.
75      */
76     protected abstract String getRpcCallbackUrl();
77
78     /**
79      * Sends the RPC message to the gateway.
80      */
81     protected abstract Object[] sendMessage(int port, RpcRequest<T> request) throws IOException;
82
83     /**
84      * Register a callback for the specified interface where the Homematic gateway can send its events.
85      */
86     public void init(HmInterface hmInterface, String clientId) throws IOException {
87         RpcRequest<T> request = createRpcRequest("init");
88         request.addArg(getRpcCallbackUrl());
89         request.addArg(clientId);
90         if (config.getGatewayInfo().isHomegear()) {
91             request.addArg(Integer.valueOf(0x22));
92         }
93         sendMessage(config.getRpcPort(hmInterface), request);
94     }
95
96     /**
97      * Release a callback for the specified interface.
98      */
99     public void release(HmInterface hmInterface) throws IOException {
100         RpcRequest<T> request = createRpcRequest("init");
101         request.addArg(getRpcCallbackUrl());
102         sendMessage(config.getRpcPort(hmInterface), request);
103     }
104
105     /**
106      * Sends a ping to the specified interface.
107      */
108     public void ping(HmInterface hmInterface, String callerId) throws IOException {
109         RpcRequest<T> request = createRpcRequest("ping");
110         request.addArg(callerId);
111         sendMessage(config.getRpcPort(hmInterface), request);
112     }
113
114     /**
115      * Returns the info of all BidCos interfaces available on the gateway.
116      */
117     public ListBidcosInterfacesParser listBidcosInterfaces(HmInterface hmInterface) throws IOException {
118         RpcRequest<T> request = createRpcRequest("listBidcosInterfaces");
119         return new ListBidcosInterfacesParser().parse(sendMessage(config.getRpcPort(hmInterface), request));
120     }
121
122     /**
123      * Returns some infos of the gateway.
124      */
125     private GetDeviceDescriptionParser getDeviceDescription(HmInterface hmInterface) throws IOException {
126         RpcRequest<T> request = createRpcRequest("getDeviceDescription");
127         request.addArg("BidCoS-RF");
128         return new GetDeviceDescriptionParser().parse(sendMessage(config.getRpcPort(hmInterface), request));
129     }
130
131     /**
132      * Returns all variable metadata and values from a Homegear gateway.
133      */
134     public void getAllSystemVariables(HmChannel channel) throws IOException {
135         RpcRequest<T> request = createRpcRequest("getAllSystemVariables");
136         new GetAllSystemVariablesParser(channel).parse(sendMessage(config.getRpcPort(channel), request));
137     }
138
139     /**
140      * Loads all device names from a Homegear gateway.
141      */
142     public void loadDeviceNames(HmInterface hmInterface, Collection<HmDevice> devices) throws IOException {
143         RpcRequest<T> request = createRpcRequest("getDeviceInfo");
144         new HomegearLoadDeviceNamesParser(devices).parse(sendMessage(config.getRpcPort(hmInterface), request));
145     }
146
147     /**
148      * Returns true, if the interface is available on the gateway.
149      */
150     public void checkInterface(HmInterface hmInterface) throws IOException {
151         RpcRequest<T> request = createRpcRequest("init");
152         request.addArg("http://openhab.validation:1000");
153         sendMessage(config.getRpcPort(hmInterface), request);
154     }
155
156     /**
157      * Returns all script metadata from a Homegear gateway.
158      */
159     public void getAllScripts(HmChannel channel) throws IOException {
160         RpcRequest<T> request = createRpcRequest("getAllScripts");
161         new GetAllScriptsParser(channel).parse(sendMessage(config.getRpcPort(channel), request));
162     }
163
164     /**
165      * Returns all device and channel metadata.
166      */
167     public Collection<HmDevice> listDevices(HmInterface hmInterface) throws IOException {
168         RpcRequest<T> request = createRpcRequest("listDevices");
169         return new ListDevicesParser(hmInterface, config).parse(sendMessage(config.getRpcPort(hmInterface), request));
170     }
171
172     /**
173      * Loads all datapoint metadata into the given channel.
174      */
175     public void addChannelDatapoints(HmChannel channel, HmParamsetType paramsetType) throws IOException {
176         if (isConfigurationChannel(channel) && paramsetType != HmParamsetType.MASTER) {
177             // The configuration channel only has a MASTER Paramset, so there is nothing to load
178             return;
179         }
180         RpcRequest<T> request = createRpcRequest("getParamsetDescription");
181         request.addArg(getRpcAddress(channel.getDevice().getAddress()) + getChannelSuffix(channel));
182         request.addArg(paramsetType.toString());
183         new GetParamsetDescriptionParser(channel, paramsetType).parse(sendMessage(config.getRpcPort(channel), request));
184     }
185
186     /**
187      * Sets all datapoint values for the given channel.
188      */
189     public void setChannelDatapointValues(HmChannel channel, HmParamsetType paramsetType) throws IOException {
190         if (isConfigurationChannel(channel) && paramsetType != HmParamsetType.MASTER) {
191             // The configuration channel only has a MASTER Paramset, so there is nothing to load
192             return;
193         }
194
195         RpcRequest<T> request = createRpcRequest("getParamset");
196         request.addArg(getRpcAddress(channel.getDevice().getAddress()) + getChannelSuffix(channel));
197         request.addArg(paramsetType.toString());
198         if (channel.getDevice().getHmInterface() == HmInterface.CUXD && paramsetType == HmParamsetType.VALUES) {
199             setChannelDatapointValues(channel);
200         } else {
201             try {
202                 new GetParamsetParser(channel, paramsetType).parse(sendMessage(config.getRpcPort(channel), request));
203             } catch (UnknownRpcFailureException ex) {
204                 if (paramsetType == HmParamsetType.VALUES) {
205                     logger.debug(
206                             "RpcResponse unknown RPC failure (-1 Failure), fetching values with another API method for device: {}, channel: {}, paramset: {}",
207                             channel.getDevice().getAddress(), channel.getNumber(), paramsetType);
208                     setChannelDatapointValues(channel);
209                 } else {
210                     throw ex;
211                 }
212             }
213         }
214     }
215
216     /**
217      * Reads all VALUES datapoints individually, fallback method if setChannelDatapointValues throws a -1 Failure
218      * exception.
219      */
220     private void setChannelDatapointValues(HmChannel channel) throws IOException {
221         for (HmDatapoint dp : channel.getDatapoints()) {
222             getDatapointValue(dp);
223         }
224     }
225
226     /**
227      * Tries to identify the gateway and returns the GatewayInfo.
228      */
229     public HmGatewayInfo getGatewayInfo(String id) throws IOException {
230         boolean isHomegear = false;
231         GetDeviceDescriptionParser ddParser;
232         ListBidcosInterfacesParser biParser;
233
234         try {
235             ddParser = getDeviceDescription(HmInterface.RF);
236             isHomegear = "Homegear".equalsIgnoreCase(ddParser.getType());
237         } catch (IOException ex) {
238             // can't load gateway infos via RF interface
239             ddParser = new GetDeviceDescriptionParser();
240         }
241
242         try {
243             biParser = listBidcosInterfaces(HmInterface.RF);
244         } catch (IOException ex) {
245             biParser = listBidcosInterfaces(HmInterface.HMIP);
246         }
247
248         HmGatewayInfo gatewayInfo = new HmGatewayInfo();
249         gatewayInfo.setAddress(biParser.getGatewayAddress());
250         String gwType = biParser.getType();
251         if (isHomegear) {
252             gatewayInfo.setId(HmGatewayInfo.ID_HOMEGEAR);
253             gatewayInfo.setType(ddParser.getType());
254             gatewayInfo.setFirmware(ddParser.getFirmware());
255         } else if ((MiscUtils.strStartsWithIgnoreCase(gwType, "CCU")
256                 || MiscUtils.strStartsWithIgnoreCase(gwType, "HMIP_CCU")
257                 || MiscUtils.strStartsWithIgnoreCase(ddParser.getType(), "HM-RCV-50") || config.isCCUType())
258                 && !config.isNoCCUType()) {
259             gatewayInfo.setId(HmGatewayInfo.ID_CCU);
260             String type = gwType.isBlank() ? "CCU" : gwType;
261             gatewayInfo.setType(type);
262             gatewayInfo
263                     .setFirmware(!ddParser.getFirmware().isEmpty() ? ddParser.getFirmware() : biParser.getFirmware());
264         } else {
265             gatewayInfo.setId(HmGatewayInfo.ID_DEFAULT);
266             gatewayInfo.setType(gwType);
267             gatewayInfo.setFirmware(biParser.getFirmware());
268         }
269
270         if (gatewayInfo.isCCU() || config.hasRfPort()) {
271             gatewayInfo.setRfInterface(hasInterface(HmInterface.RF, id));
272         }
273
274         if (gatewayInfo.isCCU() || config.hasWiredPort()) {
275             gatewayInfo.setWiredInterface(hasInterface(HmInterface.WIRED, id));
276         }
277
278         if (gatewayInfo.isCCU() || config.hasHmIpPort()) {
279             gatewayInfo.setHmipInterface(hasInterface(HmInterface.HMIP, id));
280         }
281
282         if (gatewayInfo.isCCU() || config.hasCuxdPort()) {
283             gatewayInfo.setCuxdInterface(hasInterface(HmInterface.CUXD, id));
284         }
285
286         if (gatewayInfo.isCCU() || config.hasGroupPort()) {
287             gatewayInfo.setGroupInterface(hasInterface(HmInterface.GROUP, id));
288         }
289
290         return gatewayInfo;
291     }
292
293     /**
294      * Returns true, if a connection is possible with the given interface.
295      */
296     private boolean hasInterface(HmInterface hmInterface, String id) throws IOException {
297         try {
298             checkInterface(hmInterface);
299             return true;
300         } catch (IOException ex) {
301             logger.info("Interface '{}' on gateway '{}' not available, disabling support", hmInterface, id);
302             return false;
303         }
304     }
305
306     /**
307      * Sets the value of the datapoint using the provided rx transmission mode.
308      *
309      * @param dp The datapoint to set
310      * @param value The new value to set on the datapoint
311      * @param rxMode The rx mode to use for the transmission of the datapoint value
312      *            ({@link HomematicBindingConstants#RX_BURST_MODE "BURST"} for burst mode,
313      *            {@link HomematicBindingConstants#RX_WAKEUP_MODE "WAKEUP"} for wakeup mode, or null for the default
314      *            mode)
315      */
316     public void setDatapointValue(HmDatapoint dp, Object value, String rxMode) throws IOException {
317         if (dp.isIntegerType() && value instanceof Double) {
318             value = ((Number) value).intValue();
319         }
320
321         RpcRequest<T> request;
322         if (HmParamsetType.VALUES == dp.getParamsetType()) {
323             request = createRpcRequest("setValue");
324             request.addArg(getRpcAddress(dp.getChannel().getDevice().getAddress()) + getChannelSuffix(dp.getChannel()));
325             request.addArg(dp.getName());
326             request.addArg(value);
327             configureRxMode(request, rxMode);
328         } else {
329             request = createRpcRequest("putParamset");
330             request.addArg(getRpcAddress(dp.getChannel().getDevice().getAddress()) + getChannelSuffix(dp.getChannel()));
331             request.addArg(HmParamsetType.MASTER.toString());
332             Map<String, Object> paramSet = new HashMap<>();
333             paramSet.put(dp.getName(), value);
334             request.addArg(paramSet);
335             configureRxMode(request, rxMode);
336         }
337         sendMessage(config.getRpcPort(dp.getChannel()), request);
338     }
339
340     protected void configureRxMode(RpcRequest<T> request, String rxMode) {
341         if (rxMode != null) {
342             if (RX_BURST_MODE.equals(rxMode) || RX_WAKEUP_MODE.equals(rxMode)) {
343                 request.addArg(rxMode);
344             }
345         }
346     }
347
348     /**
349      * Retrieves the value of a single {@link HmDatapoint} from the device. Can only be used for the paramset "VALUES".
350      *
351      * @param dp The HmDatapoint that shall be loaded
352      * @throws IOException If there is a problem while communicating to the gateway
353      */
354     public void getDatapointValue(HmDatapoint dp) throws IOException {
355         if (dp.isReadable() && !dp.isVirtual() && dp.getParamsetType() == HmParamsetType.VALUES) {
356             RpcRequest<T> request = createRpcRequest("getValue");
357             request.addArg(getRpcAddress(dp.getChannel().getDevice().getAddress()) + getChannelSuffix(dp.getChannel()));
358             request.addArg(dp.getName());
359             new GetValueParser(dp).parse(sendMessage(config.getRpcPort(dp.getChannel()), request));
360         }
361     }
362
363     /**
364      * Sets the value of a system variable on a Homegear gateway.
365      */
366     public void setSystemVariable(HmDatapoint dp, Object value) throws IOException {
367         RpcRequest<T> request = createRpcRequest("setSystemVariable");
368         request.addArg(dp.getInfo());
369         request.addArg(value);
370         sendMessage(config.getRpcPort(dp.getChannel()), request);
371     }
372
373     /**
374      * Executes a script on the Homegear gateway.
375      */
376     public void executeScript(HmDatapoint dp) throws IOException {
377         RpcRequest<T> request = createRpcRequest("runScript");
378         request.addArg(dp.getInfo());
379         sendMessage(config.getRpcPort(dp.getChannel()), request);
380     }
381
382     /**
383      * Enables/disables the install mode for given seconds.
384      *
385      * @param hmInterface specifies the interface to enable / disable install mode on
386      * @param enable if <i>true</i> it will be enabled, otherwise disabled
387      * @param seconds desired duration of install mode
388      * @throws IOException if RpcClient fails to propagate command
389      */
390     public void setInstallMode(HmInterface hmInterface, boolean enable, int seconds) throws IOException {
391         RpcRequest<T> request = createRpcRequest("setInstallMode");
392         request.addArg(enable);
393         request.addArg(seconds);
394         request.addArg(INSTALL_MODE_NORMAL);
395         logger.debug("Submitting setInstallMode(on={}, time={}, mode={}) ", enable, seconds, INSTALL_MODE_NORMAL);
396         sendMessage(config.getRpcPort(hmInterface), request);
397     }
398
399     /**
400      * Returns the remaining time of <i>install_mode==true</i>
401      *
402      * @param hmInterface specifies the interface on which install mode status is requested
403      * @return current duration in seconds that the controller will remain in install mode,
404      *         value of 0 means that the install mode is disabled
405      * @throws IOException if RpcClient fails to propagate command
406      */
407     public int getInstallMode(HmInterface hmInterface) throws IOException {
408         RpcRequest<T> request = createRpcRequest("getInstallMode");
409         Object[] result = sendMessage(config.getRpcPort(hmInterface), request);
410         if (logger.isTraceEnabled()) {
411             logger.trace(
412                     "Checking InstallMode: getInstallMode() request returned {} (remaining seconds in InstallMode=true)",
413                     result);
414         }
415         try {
416             return (int) result[0];
417         } catch (Exception cause) {
418             IOException wrappedException = new IOException(
419                     "Failed to request install mode from interface " + hmInterface);
420             wrappedException.initCause(cause);
421             throw wrappedException;
422         }
423     }
424
425     /**
426      * Deletes the device from the gateway.
427      */
428     public void deleteDevice(HmDevice device, int flags) throws IOException {
429         RpcRequest<T> request = createRpcRequest("deleteDevice");
430         request.addArg(device.getAddress());
431         request.addArg(flags);
432         sendMessage(config.getRpcPort(device.getHmInterface()), request);
433     }
434
435     /**
436      * Returns the rpc address from a device address, correctly handling groups.
437      */
438     private String getRpcAddress(String address) {
439         if (address != null && address.startsWith("T-")) {
440             address = "*" + address.substring(2);
441         }
442         return address;
443     }
444
445     /**
446      * Returns the rssi values for all devices.
447      */
448     public List<HmRssiInfo> loadRssiInfo(HmInterface hmInterface) throws IOException {
449         RpcRequest<T> request = createRpcRequest("rssiInfo");
450         return new RssiInfoParser(config).parse(sendMessage(config.getRpcPort(hmInterface), request));
451     }
452
453     /**
454      * Returns the address suffix that specifies the channel for a given HmChannel. This is either a colon ":" followed
455      * by the channel number, or the empty string for a configuration channel.
456      */
457     private String getChannelSuffix(HmChannel channel) {
458         return isConfigurationChannel(channel) ? "" : ":" + channel.getNumber();
459     }
460
461     /**
462      * Checks whether a channel is a configuration channel. The configuration channel of a device encapsulates the
463      * MASTER Paramset that does not belong to one of its actual channels.
464      */
465     private boolean isConfigurationChannel(HmChannel channel) {
466         return channel.getNumber() == CONFIGURATION_CHANNEL_NUMBER;
467     }
468 }