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