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