Skip to content

Commit

Permalink
[discovery.upnp] Devices may apply a grace period for removal from th…
Browse files Browse the repository at this point in the history
…e Inbox (#2144)

* [discovery.upnp] delay removing devices from Inbox

Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
  • Loading branch information
andrewfg authored Feb 6, 2021
1 parent 023f373 commit b99aa44
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,21 @@ public interface UpnpDiscoveryParticipant {
*/
@Nullable
ThingUID getThingUID(RemoteDevice device);

/**
* The JUPnP library strictly follows the UPnP specification insofar as if a device fails to send its next
* 'ssdp:alive' notification within its declared 'maxAge' period, it is immediately considered to be gone. But
* unfortunately some openHAB bindings handle devices that can sometimes be a bit late in sending their 'ssdp:alive'
* notifications even though they have not really gone offline, which means that such devices are repeatedly removed
* from, and (re)added to, the Inbox.
*
* To prevent this, a binding that implements a UpnpDiscoveryParticipant may OPTIONALLY implement this method to
* specify an additional delay period (grace period) to wait before the device is removed from the Inbox.
*
* @param device the UPnP device on the network
* @return the additional grace period delay in seconds before the device will be removed from the Inbox
*/
default long getRemovalGracePeriodSeconds(RemoteDevice device) {
return 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import org.jupnp.UpnpService;
import org.jupnp.model.meta.LocalDevice;
import org.jupnp.model.meta.RemoteDevice;
import org.jupnp.model.types.UDN;
import org.jupnp.registry.Registry;
import org.jupnp.registry.RegistryListener;
import org.jupnp.transport.RouterException;
Expand Down Expand Up @@ -65,6 +69,11 @@ public UpnpDiscoveryService() {

private UpnpService upnpService;

/*
* Map of scheduled tasks to remove devices from the Inbox
*/
private Map<UDN, Future<?>> deviceRemovalTasks = new ConcurrentHashMap<>();

@Override
protected void activate(Map<String, Object> configProperties) {
super.activate(configProperties);
Expand Down Expand Up @@ -157,6 +166,9 @@ public void remoteDeviceAdded(Registry registry, RemoteDevice device) {
try {
DiscoveryResult result = participant.createResult(device);
if (result != null) {
if (participant.getRemovalGracePeriodSeconds(device) > 0) {
cancelRemovalTask(device.getIdentity().getUdn());
}
thingDiscovered(result);
}
} catch (Exception e) {
Expand All @@ -165,13 +177,33 @@ public void remoteDeviceAdded(Registry registry, RemoteDevice device) {
}
}

/*
* If the device has been scheduled to be removed, cancel its respective removal task
*/
private void cancelRemovalTask(UDN udn) {
Future<?> deviceRemovalTask = deviceRemovalTasks.remove(udn);
if (deviceRemovalTask != null) {
deviceRemovalTask.cancel(false);
}
}

@Override
public void remoteDeviceRemoved(Registry registry, RemoteDevice device) {
for (UpnpDiscoveryParticipant participant : participants) {
try {
ThingUID thingUID = participant.getThingUID(device);
if (thingUID != null) {
thingRemoved(thingUID);
long gracePeriod = participant.getRemovalGracePeriodSeconds(device);
if (gracePeriod <= 0) {
thingRemoved(thingUID);
} else {
UDN udn = device.getIdentity().getUdn();
cancelRemovalTask(udn);
deviceRemovalTasks.put(udn, scheduler.schedule(() -> {
thingRemoved(thingUID);
cancelRemovalTask(udn);
}, gracePeriod, TimeUnit.SECONDS));
}
}
} catch (Exception e) {
logger.error("Participant '{}' threw an exception", participant.getClass().getName(), e);
Expand Down

0 comments on commit b99aa44

Please sign in to comment.