2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.wemo.internal.handler;
16 import java.time.Instant;
17 import java.util.ArrayList;
18 import java.util.List;
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;
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;
42 * {@link WemoBaseThingHandler} provides a base implementation for the
43 * concrete WeMo handlers.
45 * @author Jacob Laursen - Initial contribution
48 public abstract class WemoBaseThingHandler extends BaseThingHandler implements UpnpIOParticipant {
50 private static final int PORT_RANGE_START = 49151;
51 private static final int PORT_RANGE_END = 49157;
53 private final Logger logger = LoggerFactory.getLogger(WemoBaseThingHandler.class);
54 private final UpnpIOService service;
55 private final Object upnpLock = new Object();
57 protected WemoHttpCall wemoHttpCaller;
59 private @Nullable String host;
60 private Map<String, Instant> subscriptions = new ConcurrentHashMap<String, Instant>();
62 public WemoBaseThingHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
64 this.service = upnpIOService;
65 this.wemoHttpCaller = wemoHttpCaller;
69 public void initialize() {
70 logger.debug("Registering UPnP participant for {}", getThing().getUID());
71 service.registerParticipant(this);
76 public void dispose() {
77 removeSubscriptions();
78 logger.debug("Unregistering UPnP participant for {}", getThing().getUID());
79 service.unregisterParticipant(this);
83 public void handleCommand(ChannelUID channelUID, Command command) {
84 // can be overridden by subclasses
88 public void onStatusChanged(boolean status) {
90 logger.debug("UPnP device {} for {} is present", getUDN(), getThing().getUID());
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);
104 public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
105 // can be overridden by subclasses
109 public void onServiceSubscribed(@Nullable String service, boolean succeeded) {
110 if (service == null) {
113 logger.debug("Subscription to service {} for {} {}", service, getUDN(), succeeded ? "succeeded" : "failed");
115 synchronized (upnpLock) {
116 subscriptions.put(service, Instant.now());
122 public @Nullable String getUDN() {
123 return (String) this.getConfig().get(WemoBindingConstants.UDN);
126 protected boolean isUpnpDeviceRegistered() {
127 return service.isRegistered(this);
130 protected void addSubscription(String serviceId) {
131 synchronized (upnpLock) {
132 if (subscriptions.containsKey(serviceId)) {
133 logger.debug("{} already subscribed to {}", getUDN(), serviceId);
136 subscriptions.put(serviceId, Instant.MIN);
137 logger.debug("Adding GENA subscription {} for {}, participant is {}", serviceId, getUDN(),
138 service.isRegistered(this) ? "registered" : "not registered");
140 service.addSubscription(this, serviceId, WemoBindingConstants.SUBSCRIPTION_DURATION_SECONDS);
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);
151 subscriptions.clear();
155 public @Nullable String getWemoURL(String actionService) {
156 String host = getHost();
158 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
159 "@text/config-status.error.missing-ip");
162 int port = scanForPort(host);
164 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
165 "@text/config-status.error.missing-url");
168 return "http://" + host + ":" + port + "/upnp/control/" + actionService + "1";
171 private @Nullable String getHost() {
179 private void initializeHost() {
180 host = getHostFromService();
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);
191 portsToCheck.addAll(portRange.collect(Collectors.toList()));
193 for (Integer portCheck : portsToCheck) {
194 String urlProbe = "http://" + host + ":" + portCheck;
195 logger.trace("Probing {} to find port", urlProbe);
196 if (!wemoHttpCaller.probeURL(urlProbe)) {
200 logger.trace("Successfully detected port {}", port);
206 private @Nullable String getHostFromService() {
207 URL descriptorURL = service.getDescriptorURL(this);
208 if (descriptorURL != null) {
209 return descriptorURL.getHost();
214 private @Nullable Integer getPortFromService() {
215 URL descriptorURL = service.getDescriptorURL(this);
216 if (descriptorURL != null) {
217 int port = descriptorURL.getPort();