Initialize connection to devices asynchronously (#9228)

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
pull/10456/head
Christoph Weitkamp 2021-04-09 21:55:00 +02:00 committed by GitHub
parent 13a58b9458
commit 1822f77b07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 57 additions and 16 deletions

View File

@ -13,12 +13,11 @@
package org.openhab.binding.chromecast.internal.handler; package org.openhab.binding.chromecast.internal.handler;
import java.io.IOException; import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -60,12 +59,11 @@ import su.litvak.chromecast.api.v2.ChromeCast;
*/ */
@NonNullByDefault @NonNullByDefault
public class ChromecastHandler extends BaseThingHandler implements AudioSink { public class ChromecastHandler extends BaseThingHandler implements AudioSink {
private static final Set<AudioFormat> SUPPORTED_FORMATS = Collections
.unmodifiableSet(Stream.of(AudioFormat.MP3, AudioFormat.WAV).collect(Collectors.toSet()));
private static final Set<Class<? extends AudioStream>> SUPPORTED_STREAMS = Collections.singleton(AudioStream.class);
private final Logger logger = LoggerFactory.getLogger(ChromecastHandler.class); private final Logger logger = LoggerFactory.getLogger(ChromecastHandler.class);
private static final Set<AudioFormat> SUPPORTED_FORMATS = Set.of(AudioFormat.MP3, AudioFormat.WAV);
private static final Set<Class<? extends AudioStream>> SUPPORTED_STREAMS = Set.of(AudioStream.class);
private final AudioHTTPServer audioHTTPServer; private final AudioHTTPServer audioHTTPServer;
private final @Nullable String callbackUrl; private final @Nullable String callbackUrl;
@ -92,12 +90,14 @@ public class ChromecastHandler extends BaseThingHandler implements AudioSink {
ChromecastConfig config = getConfigAs(ChromecastConfig.class); ChromecastConfig config = getConfigAs(ChromecastConfig.class);
final String ipAddress = config.ipAddress; final String ipAddress = config.ipAddress;
if (ipAddress == null || ipAddress.isEmpty()) { if (ipAddress == null || ipAddress.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"Cannot connect to Chromecast. IP address is not valid or missing."); "Cannot connect to Chromecast. IP address is not valid or missing.");
return; return;
} }
updateStatus(ThingStatus.UNKNOWN);
Coordinator localCoordinator = coordinator; Coordinator localCoordinator = coordinator;
if (localCoordinator != null && (!localCoordinator.chromeCast.getAddress().equals(ipAddress) if (localCoordinator != null && (!localCoordinator.chromeCast.getAddress().equals(ipAddress)
|| (localCoordinator.chromeCast.getPort() != config.port))) { || (localCoordinator.chromeCast.getPort() != config.port))) {
@ -109,8 +109,14 @@ public class ChromecastHandler extends BaseThingHandler implements AudioSink {
ChromeCast chromecast = new ChromeCast(ipAddress, config.port); ChromeCast chromecast = new ChromeCast(ipAddress, config.port);
localCoordinator = new Coordinator(this, thing, chromecast, config.refreshRate, audioHTTPServer, localCoordinator = new Coordinator(this, thing, chromecast, config.refreshRate, audioHTTPServer,
callbackUrl); callbackUrl);
localCoordinator.initialize();
coordinator = localCoordinator; coordinator = localCoordinator;
scheduler.submit(() -> {
Coordinator c = coordinator;
if (c != null) {
c.initialize();
}
});
} }
} }
@ -211,7 +217,7 @@ public class ChromecastHandler extends BaseThingHandler implements AudioSink {
@Override @Override
public Collection<Class<? extends ThingHandlerService>> getServices() { public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singletonList(ChromecastActions.class); return List.of(ChromecastActions.class);
} }
public boolean playURL(String url, @Nullable String mediaType) { public boolean playURL(String url, @Nullable String mediaType) {
@ -235,6 +241,19 @@ public class ChromecastHandler extends BaseThingHandler implements AudioSink {
private final ChromecastStatusUpdater statusUpdater; private final ChromecastStatusUpdater statusUpdater;
private final ChromecastScheduler scheduler; private final ChromecastScheduler scheduler;
/**
* used internally to represent the connection state
*/
private enum ConnectionState {
UNKNOWN,
CONNECTING,
CONNECTED,
DISCONNECTING,
DISCONNECTED
}
private ConnectionState connectionState = ConnectionState.UNKNOWN;
private Coordinator(ChromecastHandler handler, Thing thing, ChromeCast chromeCast, long refreshRate, private Coordinator(ChromecastHandler handler, Thing thing, ChromeCast chromeCast, long refreshRate,
AudioHTTPServer audioHttpServer, @Nullable String callbackURL) { AudioHTTPServer audioHttpServer, @Nullable String callbackURL) {
this.chromeCast = chromeCast; this.chromeCast = chromeCast;
@ -249,30 +268,52 @@ public class ChromecastHandler extends BaseThingHandler implements AudioSink {
} }
void initialize() { void initialize() {
if (connectionState == ConnectionState.CONNECTED) {
logger.debug("Already connected");
return;
} else if (connectionState == ConnectionState.CONNECTING) {
logger.debug("Already connecting");
return;
} else if (connectionState == ConnectionState.DISCONNECTING) {
logger.warn("Trying to re-connect while still disconnecting");
return;
}
connectionState = ConnectionState.CONNECTING;
chromeCast.registerListener(eventReceiver); chromeCast.registerListener(eventReceiver);
chromeCast.registerConnectionListener(eventReceiver); chromeCast.registerConnectionListener(eventReceiver);
this.connect(); connect();
} }
void destroy() { void destroy() {
connectionState = ConnectionState.DISCONNECTING;
chromeCast.unregisterConnectionListener(eventReceiver); chromeCast.unregisterConnectionListener(eventReceiver);
chromeCast.unregisterListener(eventReceiver); chromeCast.unregisterListener(eventReceiver);
try {
scheduler.destroy(); scheduler.destroy();
try {
chromeCast.disconnect(); chromeCast.disconnect();
} catch (final IOException ex) {
logger.debug("Disconnect failed: {}", ex.getMessage()); connectionState = ConnectionState.DISCONNECTED;
} catch (final IOException e) {
logger.debug("Disconnect failed: {}", e.getMessage());
connectionState = ConnectionState.UNKNOWN;
} }
} }
private void connect() { private void connect() {
try { try {
chromeCast.connect(); chromeCast.connect();
statusUpdater.updateMediaStatus(null); statusUpdater.updateMediaStatus(null);
statusUpdater.updateStatus(ThingStatus.ONLINE); statusUpdater.updateStatus(ThingStatus.ONLINE);
} catch (final Exception e) {
connectionState = ConnectionState.CONNECTED;
} catch (final IOException | GeneralSecurityException e) {
logger.debug("Connect failed, trying to reconnect: {}", e.getMessage());
statusUpdater.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, statusUpdater.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
e.getMessage()); e.getMessage());
scheduler.scheduleConnect(); scheduler.scheduleConnect();