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