2 * Copyright (c) 2010-2022 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;
18 import java.util.concurrent.ConcurrentHashMap;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.wemo.internal.WemoBindingConstants;
25 import org.openhab.binding.wemo.internal.http.WemoHttpCall;
26 import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
27 import org.openhab.core.io.transport.upnp.UpnpIOService;
28 import org.openhab.core.thing.ChannelUID;
29 import org.openhab.core.thing.Thing;
30 import org.openhab.core.thing.binding.BaseThingHandler;
31 import org.openhab.core.types.Command;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
36 * {@link WemoBaseThingHandler} provides a base implementation for the
37 * concrete WeMo handlers.
39 * @author Jacob Laursen - Initial contribution
42 public abstract class WemoBaseThingHandler extends BaseThingHandler implements UpnpIOParticipant {
44 private static final int SUBSCRIPTION_RENEWAL_INITIAL_DELAY_SECONDS = 15;
45 private static final int SUBSCRIPTION_RENEWAL_INTERVAL_SECONDS = 60;
47 private final Logger logger = LoggerFactory.getLogger(WemoBaseThingHandler.class);
49 protected @Nullable UpnpIOService service;
50 protected WemoHttpCall wemoHttpCaller;
51 protected String host = "";
53 private Map<String, Instant> subscriptions = new ConcurrentHashMap<String, Instant>();
54 private @Nullable ScheduledFuture<?> subscriptionRenewalJob;
56 public WemoBaseThingHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpCaller) {
58 this.service = upnpIOService;
59 this.wemoHttpCaller = wemoHttpCaller;
63 public void initialize() {
64 UpnpIOService service = this.service;
65 if (service != null) {
66 logger.debug("Registering UPnP participant for {}", getThing().getUID());
67 service.registerParticipant(this);
72 public void dispose() {
73 removeSubscriptions();
74 UpnpIOService service = this.service;
75 if (service != null) {
76 logger.debug("Unregistering UPnP participant for {}", getThing().getUID());
77 service.unregisterParticipant(this);
79 cancelSubscriptionRenewalJob();
83 public void handleCommand(ChannelUID channelUID, Command command) {
84 // can be overridden by subclasses
88 public void onStatusChanged(boolean status) {
89 // can be overridden by subclasses
93 public void onValueReceived(@Nullable String variable, @Nullable String value, @Nullable String service) {
94 // can be overridden by subclasses
98 public void onServiceSubscribed(@Nullable String service, boolean succeeded) {
99 if (service == null) {
102 logger.debug("Subscription to service {} for {} {}", service, getUDN(), succeeded ? "succeeded" : "failed");
104 subscriptions.put(service, Instant.now());
109 public @Nullable String getUDN() {
110 return (String) this.getThing().getConfiguration().get(WemoBindingConstants.UDN);
113 protected boolean isUpnpDeviceRegistered() {
114 UpnpIOService service = this.service;
115 return service != null && service.isRegistered(this);
118 protected void addSubscription(String serviceId) {
119 if (subscriptions.containsKey(serviceId)) {
120 logger.debug("{} already subscribed to {}", getUDN(), serviceId);
123 if (subscriptions.isEmpty()) {
124 logger.debug("Adding first GENA subscription for {}, scheduling renewal job", getUDN());
125 scheduleSubscriptionRenewalJob();
127 subscriptions.put(serviceId, Instant.ofEpochSecond(0));
128 UpnpIOService service = this.service;
129 if (service == null) {
132 if (!service.isRegistered(this)) {
133 logger.debug("Registering UPnP participant for {}", getUDN());
134 service.registerParticipant(this);
136 if (!service.isRegistered(this)) {
137 logger.debug("Trying to add GENA subscription {} for {}, but service is not registered", serviceId,
141 logger.debug("Adding GENA subscription {} for {}", serviceId, getUDN());
142 service.addSubscription(this, serviceId, WemoBindingConstants.SUBSCRIPTION_DURATION_SECONDS);
145 protected void removeSubscription(String serviceId) {
146 UpnpIOService service = this.service;
147 if (service == null) {
150 subscriptions.remove(serviceId);
151 if (subscriptions.isEmpty()) {
152 logger.debug("Removing last GENA subscription for {}, cancelling renewal job", getUDN());
153 cancelSubscriptionRenewalJob();
155 if (!service.isRegistered(this)) {
156 logger.debug("Trying to remove GENA subscription {} for {}, but service is not registered", serviceId,
160 logger.debug("Unsubscribing {} from service {}", getUDN(), serviceId);
161 service.removeSubscription(this, serviceId);
164 private void scheduleSubscriptionRenewalJob() {
165 cancelSubscriptionRenewalJob();
166 this.subscriptionRenewalJob = scheduler.scheduleWithFixedDelay(this::renewSubscriptions,
167 SUBSCRIPTION_RENEWAL_INITIAL_DELAY_SECONDS, SUBSCRIPTION_RENEWAL_INTERVAL_SECONDS, TimeUnit.SECONDS);
170 private void cancelSubscriptionRenewalJob() {
171 ScheduledFuture<?> subscriptionRenewalJob = this.subscriptionRenewalJob;
172 if (subscriptionRenewalJob != null) {
173 subscriptionRenewalJob.cancel(true);
175 this.subscriptionRenewalJob = null;
178 private void renewSubscriptions() {
179 if (subscriptions.isEmpty()) {
182 UpnpIOService service = this.service;
183 if (service == null) {
186 if (!service.isRegistered(this)) {
187 service.registerParticipant(this);
189 if (!service.isRegistered(this)) {
190 logger.debug("Trying to renew GENA subscriptions for {}, but service is not registered", getUDN());
193 logger.debug("Renewing GENA subscriptions for {}", getUDN());
194 subscriptions.forEach((serviceId, lastRenewed) -> {
195 if (lastRenewed.isBefore(Instant.now().minusSeconds(
196 WemoBindingConstants.SUBSCRIPTION_DURATION_SECONDS - SUBSCRIPTION_RENEWAL_INTERVAL_SECONDS))) {
197 logger.debug("Subscription for service {} with timestamp {} has expired, renewing", serviceId,
199 service.removeSubscription(this, serviceId);
200 service.addSubscription(this, serviceId, WemoBindingConstants.SUBSCRIPTION_DURATION_SECONDS);
205 private void removeSubscriptions() {
206 if (subscriptions.isEmpty()) {
209 UpnpIOService service = this.service;
210 if (service == null) {
213 if (!service.isRegistered(this)) {
214 logger.debug("Trying to remove GENA subscriptions for {}, but service is not registered",
215 getThing().getUID());
218 logger.debug("Removing GENA subscriptions for {}", getUDN());
219 subscriptions.forEach((serviceId, lastRenewed) -> {
220 logger.debug("Removing subscription for service {}", serviceId);
221 service.removeSubscription(this, serviceId);
223 subscriptions.clear();
226 protected String getHost() {
227 String localHost = host;
228 if (!localHost.isEmpty()) {
231 UpnpIOService localService = service;
232 if (localService != null) {
233 URL descriptorURL = localService.getDescriptorURL(this);
234 if (descriptorURL != null) {
235 return descriptorURL.getHost();