Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[upnpcontrol] Send periodic keep alive #17976

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public void unregisterHandler(Thing thing) {
}

private UpnpServerHandler addServer(Thing thing) {
UpnpServerHandler handler = new UpnpServerHandler(thing, upnpIOService, upnpRenderers,
UpnpServerHandler handler = new UpnpServerHandler(thing, upnpIOService, upnpService, upnpRenderers,
upnpStateDescriptionProvider, upnpCommandDescriptionProvider, configuration);
String key = thing.getUID().toString();
upnpServers.put(key, handler);
Expand All @@ -162,8 +162,8 @@ private UpnpServerHandler addServer(Thing thing) {

private UpnpRendererHandler addRenderer(Thing thing) {
callbackUrl = createCallbackUrl();
UpnpRendererHandler handler = new UpnpRendererHandler(thing, upnpIOService, this, upnpStateDescriptionProvider,
upnpCommandDescriptionProvider, configuration);
UpnpRendererHandler handler = new UpnpRendererHandler(thing, upnpIOService, upnpService, this,
upnpStateDescriptionProvider, upnpCommandDescriptionProvider, configuration);
String key = thing.getUID().toString();
upnpRenderers.put(key, handler);
upnpServers.forEach((thingId, value) -> value.addRendererOption(key));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.jupnp.UpnpService;
import org.jupnp.model.message.discovery.OutgoingSearchRequest;
import org.jupnp.model.message.header.UDNHeader;
import org.jupnp.model.message.header.UpnpHeader;
import org.jupnp.model.meta.RemoteDevice;
import org.jupnp.model.types.UDN;
import org.jupnp.transport.RouterException;
import org.openhab.binding.upnpcontrol.internal.UpnpChannelName;
import org.openhab.binding.upnpcontrol.internal.UpnpDynamicCommandDescriptionProvider;
import org.openhab.binding.upnpcontrol.internal.UpnpDynamicStateDescriptionProvider;
Expand Down Expand Up @@ -77,6 +83,7 @@ public abstract class UpnpHandler extends BaseThingHandler implements UpnpIOPart
static final Pattern PROTOCOL_PATTERN = Pattern.compile("(?:.*):(?:.*):(.*):(?:.*)");

protected UpnpIOService upnpIOService;
protected UpnpService upnpService;

protected volatile @Nullable RemoteDevice device;

Expand Down Expand Up @@ -117,12 +124,14 @@ public abstract class UpnpHandler extends BaseThingHandler implements UpnpIOPart
protected UpnpDynamicStateDescriptionProvider upnpStateDescriptionProvider;
protected UpnpDynamicCommandDescriptionProvider upnpCommandDescriptionProvider;

public UpnpHandler(Thing thing, UpnpIOService upnpIOService, UpnpControlBindingConfiguration configuration,
public UpnpHandler(Thing thing, UpnpIOService upnpIOService, UpnpService upnpService,
UpnpControlBindingConfiguration configuration,
UpnpDynamicStateDescriptionProvider upnpStateDescriptionProvider,
UpnpDynamicCommandDescriptionProvider upnpCommandDescriptionProvider) {
super(thing);

this.upnpIOService = upnpIOService;
this.upnpService = upnpService;

this.bindingConfig = configuration;

Expand Down Expand Up @@ -652,4 +661,21 @@ private void cancelSubscriptionRefreshJob() {
protected @Nullable RemoteDevice getDevice() {
return device;
}

/**
* Send a device search request to the UPnP remote device.
*
* Some devices, such as LinkPlay based systems (WiiM, Arylic, etc.) loose their registrations over time. Sending a
* periodic search request will help keep the device registered.
*/
protected void sendDeviceSearchRequest() {
try {
UpnpHeader<UDN> searchTarget = new UDNHeader(new UDN(getUDN()));
OutgoingSearchRequest searchRequest = new OutgoingSearchRequest(searchTarget, 5);
upnpService.getRouter().send(searchRequest);
logger.debug("M-SEARCH query sent for device UDN: {}", searchTarget.getValue());
} catch (RouterException e) {
logger.debug("Failed to send M-SEARCH", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.jupnp.UpnpService;
import org.jupnp.model.meta.RemoteDevice;
import org.openhab.binding.upnpcontrol.internal.UpnpChannelName;
import org.openhab.binding.upnpcontrol.internal.UpnpDynamicCommandDescriptionProvider;
Expand Down Expand Up @@ -160,11 +161,12 @@ public class UpnpRendererHandler extends UpnpHandler {
private volatile @Nullable ScheduledFuture<?> trackPositionRefresh;
private volatile int posAtNotificationStart = 0;

public UpnpRendererHandler(Thing thing, UpnpIOService upnpIOService, UpnpAudioSinkReg audioSinkReg,
UpnpDynamicStateDescriptionProvider upnpStateDescriptionProvider,
public UpnpRendererHandler(Thing thing, UpnpIOService upnpIOService, UpnpService upnpService,
UpnpAudioSinkReg audioSinkReg, UpnpDynamicStateDescriptionProvider upnpStateDescriptionProvider,
UpnpDynamicCommandDescriptionProvider upnpCommandDescriptionProvider,
UpnpControlBindingConfiguration configuration) {
super(thing, upnpIOService, configuration, upnpStateDescriptionProvider, upnpCommandDescriptionProvider);
super(thing, upnpIOService, upnpService, configuration, upnpStateDescriptionProvider,
upnpCommandDescriptionProvider);

serviceSubscriptions.add(AV_TRANSPORT);
serviceSubscriptions.add(RENDERING_CONTROL);
Expand Down Expand Up @@ -218,6 +220,9 @@ public void dispose() {
@Override
protected void initJob() {
synchronized (jobLock) {

digitaldan marked this conversation as resolved.
Show resolved Hide resolved
sendDeviceSearchRequest();

if (!upnpIOService.isRegistered(this)) {
String msg = String.format("@text/offline.device-not-registered [ \"%s\" ]", getUDN());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, msg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.jupnp.UpnpService;
import org.openhab.binding.upnpcontrol.internal.UpnpDynamicCommandDescriptionProvider;
import org.openhab.binding.upnpcontrol.internal.UpnpDynamicStateDescriptionProvider;
import org.openhab.binding.upnpcontrol.internal.config.UpnpControlBindingConfiguration;
Expand Down Expand Up @@ -100,12 +101,13 @@ public class UpnpServerHandler extends UpnpHandler {

protected @NonNullByDefault({}) UpnpControlServerConfiguration config;

public UpnpServerHandler(Thing thing, UpnpIOService upnpIOService,
public UpnpServerHandler(Thing thing, UpnpIOService upnpIOService, UpnpService upnpService,
ConcurrentMap<String, UpnpRendererHandler> upnpRenderers,
UpnpDynamicStateDescriptionProvider upnpStateDescriptionProvider,
UpnpDynamicCommandDescriptionProvider upnpCommandDescriptionProvider,
UpnpControlBindingConfiguration configuration) {
super(thing, upnpIOService, configuration, upnpStateDescriptionProvider, upnpCommandDescriptionProvider);
super(thing, upnpIOService, upnpService, configuration, upnpStateDescriptionProvider,
upnpCommandDescriptionProvider);
this.upnpRenderers = upnpRenderers;

// put root as highest level in parent map
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.jupnp.UpnpService;
import org.jupnp.model.message.discovery.OutgoingSearchRequest;
import org.jupnp.transport.Router;
import org.jupnp.transport.RouterException;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
Expand Down Expand Up @@ -67,6 +71,9 @@ public class UpnpHandlerTest {
@Mock
protected @Nullable UpnpIOService upnpIOService;

@Mock
protected @Nullable UpnpService upnpService;

@Mock
protected @Nullable UpnpDynamicStateDescriptionProvider upnpStateDescriptionProvider;

Expand Down Expand Up @@ -115,6 +122,16 @@ public void setUp() {

// stub config for initialize
when(config.as(UpnpControlConfiguration.class)).thenReturn(new UpnpControlConfiguration());

upnpService = mock(UpnpService.class);
Router router = mock(Router.class);
when(upnpService.getRouter()).thenReturn(router);
try {
doNothing().when(router).send(any(OutgoingSearchRequest.class));
} catch (RouterException e) {
// This will never happen in the test since doNothing doesn't trigger behavior
throw new RuntimeException("Unexpected exception in test setup", e);
}
}

protected void initHandler(UpnpHandler handler) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ public void setUp() {
upnpEntryQueue = new UpnpEntryQueue(entries, "54321");

handler = spy(new UpnpRendererHandler(requireNonNull(thing), requireNonNull(upnpIOService),
requireNonNull(audioSinkReg), requireNonNull(upnpStateDescriptionProvider),
requireNonNull(upnpService), requireNonNull(audioSinkReg), requireNonNull(upnpStateDescriptionProvider),
requireNonNull(upnpCommandDescriptionProvider), configuration));

initHandler(requireNonNull(handler));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,10 @@ public void setUp() {
// stub config for initialize
when(config.as(UpnpControlServerConfiguration.class)).thenReturn(new UpnpControlServerConfiguration());

handler = spy(new UpnpServerHandler(requireNonNull(thing), requireNonNull(upnpIOService),
requireNonNull(upnpRenderers), requireNonNull(upnpStateDescriptionProvider),
requireNonNull(upnpCommandDescriptionProvider), configuration));
handler = spy(
new UpnpServerHandler(requireNonNull(thing), requireNonNull(upnpIOService), requireNonNull(upnpService),
requireNonNull(upnpRenderers), requireNonNull(upnpStateDescriptionProvider),
requireNonNull(upnpCommandDescriptionProvider), configuration));

initHandler(requireNonNull(handler));

Expand Down