]> git.basschouten.com Git - openhab-addons.git/blob
8565418c2ae3a57cbdecc5772077052d7fe5c04e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.wemo.internal.handler;
14
15 import java.net.URL;
16 import java.time.Instant;
17 import java.util.ArrayList;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Map.Entry;
21 import java.util.concurrent.ConcurrentHashMap;
22 import java.util.stream.Collectors;
23 import java.util.stream.IntStream;
24 import java.util.stream.Stream;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.wemo.internal.WemoBindingConstants;
29 import org.openhab.binding.wemo.internal.http.WemoHttpCall;
30 import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
31 import org.openhab.core.io.transport.upnp.UpnpIOService;
32 import org.openhab.core.thing.ChannelUID;
33 import org.openhab.core.thing.Thing;
34 import org.openhab.core.thing.ThingStatus;
35 import org.openhab.core.thing.ThingStatusDetail;
36 import org.openhab.core.thing.binding.BaseThingHandler;
37 import org.openhab.core.types.Command;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
42  * {@link WemoBaseThingHandler} provides a base implementation for the
43  * concrete WeMo handlers.
44  *
45  * @author Jacob Laursen - Initial contribution
46  */
47 @NonNullByDefault
48 public abstract class WemoBaseThingHandler extends BaseThingHandler implements UpnpIOParticipant {
49
50     private static final int PORT_RANGE_START = 49151;
51     private static final int PORT_RANGE_END = 49157;
52
53     private final Logger logger = LoggerFactory.getLogger(WemoBaseThingHandler.class);
54     private final UpnpIOService service;
55     private final Object upnpLock = new Object();
56
57     protected WemoHttpCall wemoHttpCaller;
58
59     private @Nullable String host;
60     private Map<String, Instant> subscriptions = new ConcurrentHashMap<String, Instant>();
61
62     public WemoBaseThingHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
63         super(thing);
64         this.service = upnpIOService;
65         this.wemoHttpCaller = wemoHttpCaller;
66     }
67
68     @Override
69     public void initialize() {
70         logger.debug("Registering UPnP participant for {}", getThing().getUID());
71         service.registerParticipant(this);
72         initializeHost();
73     }
74
75     @Override
76     public void dispose() {
77         removeSubscriptions();
78         logger.debug("Unregistering UPnP participant for {}", getThing().getUID());
79         service.unregisterParticipant(this);
80     }
81
82     @Override
83     public void handleCommand(ChannelUID channelUID, Command command) {
84         // can be overridden by subclasses
85     }
86
87     @Override
88     public void onStatusChanged(boolean status) {
89         if (status) {
90             logger.debug("UPnP device {} for {} is present", getUDN(), getThing().getUID());
91         } else {
92             logger.info("UPnP device {} for {} is absent", getUDN(), getThing().getUID());
93             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR);
94             // Expire subscriptions.
95             synchronized (upnpLock) {
96                 for (Entry<String, Instant> subscription : subscriptions.entrySet()) {
97                     subscription.setValue(Instant.MIN);
98                 }
99             }
100         }
101     }
102
103     @Override
104     public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
105         // can be overridden by subclasses
106     }
107
108     @Override
109     public void onServiceSubscribed(@Nullable String service, boolean succeeded) {
110         if (service == null) {
111             return;
112         }
113         logger.debug("Subscription to service {} for {} {}", service, getUDN(), succeeded ? "succeeded" : "failed");
114         if (succeeded) {
115             synchronized (upnpLock) {
116                 subscriptions.put(service, Instant.now());
117             }
118         }
119     }
120
121     @Override
122     public @Nullable String getUDN() {
123         return (String) this.getConfig().get(WemoBindingConstants.UDN);
124     }
125
126     protected boolean isUpnpDeviceRegistered() {
127         return service.isRegistered(this);
128     }
129
130     protected void addSubscription(String serviceId) {
131         synchronized (upnpLock) {
132             if (subscriptions.containsKey(serviceId)) {
133                 logger.debug("{} already subscribed to {}", getUDN(), serviceId);
134                 return;
135             }
136             subscriptions.put(serviceId, Instant.MIN);
137             logger.debug("Adding GENA subscription {} for {}, participant is {}", serviceId, getUDN(),
138                     service.isRegistered(this) ? "registered" : "not registered");
139         }
140         service.addSubscription(this, serviceId, WemoBindingConstants.SUBSCRIPTION_DURATION_SECONDS);
141     }
142
143     private void removeSubscriptions() {
144         logger.debug("Removing GENA subscriptions for {}, participant is {}", getUDN(),
145                 service.isRegistered(this) ? "registered" : "not registered");
146         synchronized (upnpLock) {
147             subscriptions.forEach((serviceId, lastRenewed) -> {
148                 logger.debug("Removing subscription for service {}", serviceId);
149                 service.removeSubscription(this, serviceId);
150             });
151             subscriptions.clear();
152         }
153     }
154
155     public @Nullable String getWemoURL(String actionService) {
156         String host = getHost();
157         if (host == null) {
158             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
159                     "@text/config-status.error.missing-ip");
160             return null;
161         }
162         int port = scanForPort(host);
163         if (port == 0) {
164             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
165                     "@text/config-status.error.missing-url");
166             return null;
167         }
168         return "http://" + host + ":" + port + "/upnp/control/" + actionService + "1";
169     }
170
171     private @Nullable String getHost() {
172         if (host != null) {
173             return host;
174         }
175         initializeHost();
176         return host;
177     }
178
179     private void initializeHost() {
180         host = getHostFromService();
181     }
182
183     private int scanForPort(String host) {
184         Integer portFromService = getPortFromService();
185         List<Integer> portsToCheck = new ArrayList<Integer>(PORT_RANGE_END - PORT_RANGE_START + 1);
186         Stream<Integer> portRange = IntStream.rangeClosed(PORT_RANGE_START, PORT_RANGE_END).boxed();
187         if (portFromService != null) {
188             portsToCheck.add(portFromService);
189             portRange = portRange.filter(p -> p.intValue() != portFromService);
190         }
191         portsToCheck.addAll(portRange.collect(Collectors.toList()));
192         int port = 0;
193         for (Integer portCheck : portsToCheck) {
194             String urlProbe = "http://" + host + ":" + portCheck;
195             logger.trace("Probing {} to find port", urlProbe);
196             if (!wemoHttpCaller.probeURL(urlProbe)) {
197                 continue;
198             }
199             port = portCheck;
200             logger.trace("Successfully detected port {}", port);
201             break;
202         }
203         return port;
204     }
205
206     private @Nullable String getHostFromService() {
207         URL descriptorURL = service.getDescriptorURL(this);
208         if (descriptorURL != null) {
209             return descriptorURL.getHost();
210         }
211         return null;
212     }
213
214     private @Nullable Integer getPortFromService() {
215         URL descriptorURL = service.getDescriptorURL(this);
216         if (descriptorURL != null) {
217             int port = descriptorURL.getPort();
218             if (port != -1) {
219                 return port;
220             }
221         }
222         return null;
223     }
224 }