]> git.basschouten.com Git - openhab-addons.git/blob
ab70fdd45622aa57825800e129b567526115f3d3
[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.tr064.internal;
14
15 import static org.openhab.binding.tr064.internal.Tr064BindingConstants.THING_TYPE_SUBDEVICE;
16 import static org.openhab.binding.tr064.internal.Tr064BindingConstants.THING_TYPE_SUBDEVICE_LAN;
17
18 import java.util.HashMap;
19 import java.util.Map;
20 import java.util.Set;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.tr064.internal.config.Tr064ChannelConfig;
27 import org.openhab.binding.tr064.internal.config.Tr064SubConfiguration;
28 import org.openhab.binding.tr064.internal.dto.scpd.root.SCPDDeviceType;
29 import org.openhab.binding.tr064.internal.util.SCPDUtil;
30 import org.openhab.binding.tr064.internal.util.Util;
31 import org.openhab.core.cache.ExpiringCacheMap;
32 import org.openhab.core.thing.*;
33 import org.openhab.core.thing.binding.BaseThingHandler;
34 import org.openhab.core.thing.binding.builder.ThingBuilder;
35 import org.openhab.core.types.Command;
36 import org.openhab.core.types.RefreshType;
37 import org.openhab.core.types.State;
38 import org.openhab.core.types.UnDefType;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * The {@link Tr064SubHandler} is responsible for handling commands, which are
44  * sent to one of the channels.
45  *
46  * @author Jan N. Klug - Initial contribution
47  */
48 @NonNullByDefault
49 public class Tr064SubHandler extends BaseThingHandler {
50     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_SUBDEVICE,
51             THING_TYPE_SUBDEVICE_LAN);
52     private static final int RETRY_INTERVAL = 60;
53
54     private final Logger logger = LoggerFactory.getLogger(Tr064SubHandler.class);
55
56     private Tr064SubConfiguration config = new Tr064SubConfiguration();
57
58     private String deviceType = "";
59     private boolean isInitialized = false;
60
61     private final Map<ChannelUID, Tr064ChannelConfig> channels = new HashMap<>();
62     // caching is used to prevent excessive calls to the same action
63     private final ExpiringCacheMap<ChannelUID, State> stateCache = new ExpiringCacheMap<>(2000);
64
65     private @Nullable SOAPConnector soapConnector;
66     private @Nullable ScheduledFuture<?> connectFuture;
67     private @Nullable ScheduledFuture<?> pollFuture;
68
69     Tr064SubHandler(Thing thing) {
70         super(thing);
71     }
72
73     @Override
74     @SuppressWarnings("null")
75     public void handleCommand(ChannelUID channelUID, Command command) {
76         Tr064ChannelConfig channelConfig = channels.get(channelUID);
77         if (channelConfig == null) {
78             logger.trace("Channel {} not supported.", channelUID);
79             return;
80         }
81
82         if (command instanceof RefreshType) {
83             State state = stateCache.putIfAbsentAndGet(channelUID, () -> soapConnector == null ? UnDefType.UNDEF
84                     : soapConnector.getChannelStateFromDevice(channelConfig, channels, stateCache));
85             if (state != null) {
86                 updateState(channelUID, state);
87             }
88             return;
89         }
90
91         if (channelConfig.getChannelTypeDescription().getSetAction() == null) {
92             logger.debug("Discarding command {} to {}, read-only channel", command, channelUID);
93             return;
94         }
95         scheduler.execute(() -> {
96             if (soapConnector == null) {
97                 logger.warn("Could not send command because connector not available");
98             } else {
99                 soapConnector.sendChannelCommandToDevice(channelConfig, command);
100             }
101         });
102     }
103
104     @Override
105     public void initialize() {
106         config = getConfigAs(Tr064SubConfiguration.class);
107         if (!config.isValid()) {
108             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
109                     "One or more mandatory configuration fields are empty");
110             return;
111         }
112
113         final Bridge bridge = getBridge();
114         if (bridge != null && bridge.getStatus().equals(ThingStatus.ONLINE)) {
115             updateStatus(ThingStatus.UNKNOWN);
116             connectFuture = scheduler.scheduleWithFixedDelay(this::internalInitialize, 0, 30, TimeUnit.SECONDS);
117         } else {
118             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
119         }
120     }
121
122     private void internalInitialize() {
123         final Bridge bridge = getBridge();
124         if (bridge == null) {
125             return;
126         }
127         final Tr064RootHandler bridgeHandler = (Tr064RootHandler) bridge.getHandler();
128         if (bridgeHandler == null) {
129             logger.warn("Bridge-handler is null in thing {}", thing.getUID());
130             return;
131         }
132         final SCPDUtil scpdUtil = bridgeHandler.getSCPDUtil();
133         if (scpdUtil == null) {
134             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
135                     "Could not get device definitions");
136             return;
137         }
138
139         if (checkProperties(scpdUtil)) {
140             // properties set, check channels
141             ThingBuilder thingBuilder = editThing();
142             thingBuilder.withoutChannels(thing.getChannels());
143             Util.checkAvailableChannels(thing, thingBuilder, scpdUtil, config.uuid, deviceType, channels);
144             updateThing(thingBuilder.build());
145
146             // remove connect scheduler
147             removeConnectScheduler();
148             soapConnector = bridgeHandler.getSOAPConnector();
149
150             isInitialized = true;
151             installPolling();
152             updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
153         }
154     }
155
156     private void removeConnectScheduler() {
157         final ScheduledFuture<?> connectFuture = this.connectFuture;
158         if (connectFuture != null) {
159             connectFuture.cancel(true);
160             this.connectFuture = null;
161         }
162     }
163
164     @Override
165     public void dispose() {
166         removeConnectScheduler();
167         uninstallPolling();
168
169         stateCache.clear();
170         isInitialized = false;
171
172         super.dispose();
173     }
174
175     /**
176      * poll remote device for channel values
177      */
178     private void poll() {
179         SOAPConnector soapConnector = this.soapConnector;
180         channels.forEach((channelUID, channelConfig) -> {
181             if (isLinked(channelUID)) {
182                 State state = stateCache.putIfAbsentAndGet(channelUID, () -> soapConnector == null ? UnDefType.UNDEF
183                         : soapConnector.getChannelStateFromDevice(channelConfig, channels, stateCache));
184                 if (state != null) {
185                     updateState(channelUID, state);
186                 }
187             }
188         });
189     }
190
191     /**
192      * get device properties from remote device
193      *
194      * @param scpdUtil the SCPD util of this device
195      * @return true if successfull
196      */
197     private boolean checkProperties(SCPDUtil scpdUtil) {
198         try {
199             SCPDDeviceType device = scpdUtil.getDevice(config.uuid)
200                     .orElseThrow(() -> new SCPDException("Could not find device " + config.uuid));
201             String deviceType = device.getDeviceType();
202             if (deviceType == null) {
203                 throw new SCPDException("deviceType can't be null ");
204             }
205             this.deviceType = deviceType;
206
207             Map<String, String> properties = editProperties();
208             properties.put("deviceType", deviceType);
209             updateProperties(properties);
210
211             return true;
212         } catch (SCPDException e) {
213             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
214                     "Failed to update device properties: " + e.getMessage());
215
216             return false;
217         }
218     }
219
220     @Override
221     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
222         if (!bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
223             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
224             removeConnectScheduler();
225         } else {
226             if (isInitialized) {
227                 updateStatus(ThingStatus.ONLINE);
228             } else {
229                 updateStatus(ThingStatus.UNKNOWN);
230                 connectFuture = scheduler.scheduleWithFixedDelay(this::internalInitialize, 0, RETRY_INTERVAL,
231                         TimeUnit.SECONDS);
232             }
233         }
234     }
235
236     /**
237      * uninstall update polling
238      */
239     private void uninstallPolling() {
240         final ScheduledFuture<?> pollFuture = this.pollFuture;
241         if (pollFuture != null) {
242             pollFuture.cancel(true);
243             this.pollFuture = null;
244         }
245     }
246
247     /**
248      * install update polling
249      */
250     private void installPolling() {
251         uninstallPolling();
252         pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 0, config.refresh, TimeUnit.SECONDS);
253     }
254 }