From 703796df19511d6b27d46bc293d09810a1494e9f Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Sun, 9 Mar 2025 18:51:12 +0100 Subject: [PATCH] [squeezebox] Fix properties for manually configured Things (#18347) * Refactor discovery Add null annotations and fix warnings Signed-off-by: Jacob Laursen * Fix property initialization for manually configured player Things Signed-off-by: Jacob Laursen --------- Signed-off-by: Jacob Laursen --- .../internal/SqueezeBoxAudioSink.java | 14 ++- .../internal/SqueezeBoxBindingConstants.java | 5 + .../internal/SqueezeBoxHandlerFactory.java | 96 ++++------------ .../config/SqueezeBoxPlayerConfig.java | 8 +- ... => SqueezeBoxPlayerDiscoveryService.java} | 82 ++++++++++---- .../SqueezeBoxServerDiscoveryParticipant.java | 7 +- .../SqueezeBoxNotificationListener.java | 5 +- .../handler/SqueezeBoxNotificationPlayer.java | 6 +- .../SqueezeBoxPlayerEventListener.java | 5 +- .../handler/SqueezeBoxPlayerHandler.java | 107 ++++++++++++------ .../handler/SqueezeBoxPlayerPlayState.java | 3 + .../handler/SqueezeBoxPlayerState.java | 2 + .../handler/SqueezeBoxServerHandler.java | 100 +++++++++------- .../squeezebox/internal/model/Favorite.java | 5 +- .../squeezebox/internal/utils/HttpUtils.java | 2 + .../SqueezeBoxCommunicationException.java | 3 + .../SqueezeBoxNotAuthorizedException.java | 3 + .../utils/SqueezeBoxTimeoutException.java | 3 + .../OH-INF/i18n/squeezebox.properties | 1 + .../resources/OH-INF/thing/thing-types.xml | 4 - 20 files changed, 267 insertions(+), 194 deletions(-) rename bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/discovery/{SqueezeBoxPlayerDiscoveryParticipant.java => SqueezeBoxPlayerDiscoveryService.java} (70%) diff --git a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/SqueezeBoxAudioSink.java b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/SqueezeBoxAudioSink.java index 41b735f366..ed6510f6f9 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/SqueezeBoxAudioSink.java +++ b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/SqueezeBoxAudioSink.java @@ -17,11 +17,11 @@ import java.io.InputStream; import java.util.Locale; import java.util.Set; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.squeezebox.internal.handler.SqueezeBoxPlayerHandler; import org.openhab.core.audio.AudioFormat; import org.openhab.core.audio.AudioHTTPServer; -import org.openhab.core.audio.AudioSink; import org.openhab.core.audio.AudioSinkSync; import org.openhab.core.audio.AudioStream; import org.openhab.core.audio.FileAudioStream; @@ -36,11 +36,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * This makes a SqueezeBox Player serve as an {@link AudioSink}- + * This makes a SqueezeBox Player serve as an {@link org.openhab.core.audio.AudioSink} * * @author Mark Hilbush - Initial contribution * @author Mark Hilbush - Add callbackUrl */ +@NonNullByDefault public class SqueezeBoxAudioSink extends AudioSinkSync { private final Logger logger = LoggerFactory.getLogger(SqueezeBoxAudioSink.class); @@ -50,13 +51,13 @@ public class SqueezeBoxAudioSink extends AudioSinkSync { // Needed because Squeezebox does multiple requests for the stream private static final int STREAM_TIMEOUT = 10; - private String callbackUrl; + private @Nullable String callbackUrl; private AudioHTTPServer audioHTTPServer; private SqueezeBoxPlayerHandler playerHandler; public SqueezeBoxAudioSink(SqueezeBoxPlayerHandler playerHandler, AudioHTTPServer audioHTTPServer, - String callbackUrl) { + @Nullable String callbackUrl) { this.playerHandler = playerHandler; this.audioHTTPServer = audioHTTPServer; this.callbackUrl = callbackUrl; @@ -71,12 +72,12 @@ public class SqueezeBoxAudioSink extends AudioSinkSync { } @Override - public String getLabel(Locale locale) { + public @Nullable String getLabel(@Nullable Locale locale) { return playerHandler.getThing().getLabel(); } @Override - public void processSynchronously(AudioStream audioStream) + public void processSynchronously(@Nullable AudioStream audioStream) throws UnsupportedAudioFormatException, UnsupportedAudioStreamException { if (audioStream == null) { return; @@ -105,6 +106,7 @@ public class SqueezeBoxAudioSink extends AudioSinkSync { // Form the URL for streaming the notification from the OH web server // Use the callback URL if it is set in the binding configuration + String callbackUrl = this.callbackUrl; String host = callbackUrl == null || callbackUrl.isEmpty() ? playerHandler.getHostAndPort() : callbackUrl; if (host == null) { diff --git a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/SqueezeBoxBindingConstants.java b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/SqueezeBoxBindingConstants.java index 048347e50b..1c57646aae 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/SqueezeBoxBindingConstants.java +++ b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/SqueezeBoxBindingConstants.java @@ -74,4 +74,9 @@ public class SqueezeBoxBindingConstants { public static final String CHANNEL_FAVORITES_PLAY = "playFavorite"; public static final String CHANNEL_RATE = "rate"; public static final String CHANNEL_SLEEP = "sleep"; + + // List of all properties + public static final String PROPERTY_NAME = "name"; + public static final String PROPERTY_UID = "uid"; + public static final String PROPERTY_IP = "ip"; } diff --git a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/SqueezeBoxHandlerFactory.java b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/SqueezeBoxHandlerFactory.java index 759687c2b2..de41256ac5 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/SqueezeBoxHandlerFactory.java +++ b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/SqueezeBoxHandlerFactory.java @@ -15,7 +15,6 @@ package org.openhab.binding.squeezebox.internal; import static org.openhab.binding.squeezebox.internal.SqueezeBoxBindingConstants.*; import java.util.Dictionary; -import java.util.HashMap; import java.util.Hashtable; import java.util.Map; import java.util.Set; @@ -23,19 +22,17 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.openhab.binding.squeezebox.internal.discovery.SqueezeBoxPlayerDiscoveryParticipant; -import org.openhab.binding.squeezebox.internal.handler.SqueezeBoxPlayerEventListener; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.squeezebox.internal.handler.SqueezeBoxPlayerHandler; import org.openhab.binding.squeezebox.internal.handler.SqueezeBoxServerHandler; import org.openhab.core.audio.AudioHTTPServer; import org.openhab.core.audio.AudioSink; -import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.net.HttpServiceUtil; import org.openhab.core.net.NetworkAddressService; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.binding.BaseThingHandlerFactory; import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandlerFactory; @@ -56,6 +53,7 @@ import org.slf4j.LoggerFactory; * @author Mark Hilbush - Add callbackUrl */ @Component(service = ThingHandlerFactory.class, configurationPid = "binding.squeezebox") +@NonNullByDefault public class SqueezeBoxHandlerFactory extends BaseThingHandlerFactory { private final Logger logger = LoggerFactory.getLogger(SqueezeBoxHandlerFactory.class); @@ -64,8 +62,6 @@ public class SqueezeBoxHandlerFactory extends BaseThingHandlerFactory { SqueezeBoxPlayerHandler.SUPPORTED_THING_TYPES_UIDS.stream()) .collect(Collectors.toSet()); - private Map> discoveryServiceRegs = new HashMap<>(); - private final AudioHTTPServer audioHTTPServer; private final NetworkAddressService networkAddressService; private final SqueezeBoxStateDescriptionOptionsProvider stateDescriptionProvider; @@ -73,12 +69,12 @@ public class SqueezeBoxHandlerFactory extends BaseThingHandlerFactory { private Map> audioSinkRegistrations = new ConcurrentHashMap<>(); // Callback url (scheme+server+port) to use for playing notification sounds - private String callbackUrl = null; + private @Nullable String callbackUrl = null; @Activate - public SqueezeBoxHandlerFactory(@Reference AudioHTTPServer audioHTTPServer, - @Reference NetworkAddressService networkAddressService, - @Reference SqueezeBoxStateDescriptionOptionsProvider stateDescriptionProvider) { + public SqueezeBoxHandlerFactory(final @Reference AudioHTTPServer audioHTTPServer, + final @Reference NetworkAddressService networkAddressService, + final @Reference SqueezeBoxStateDescriptionOptionsProvider stateDescriptionProvider) { this.audioHTTPServer = audioHTTPServer; this.networkAddressService = networkAddressService; this.stateDescriptionProvider = stateDescriptionProvider; @@ -97,28 +93,25 @@ public class SqueezeBoxHandlerFactory extends BaseThingHandlerFactory { } @Override - protected ThingHandler createHandler(Thing thing) { + protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - if (thingTypeUID.equals(SQUEEZEBOXSERVER_THING_TYPE)) { - logger.trace("creating handler for bridge thing {}", thing); - SqueezeBoxServerHandler bridge = new SqueezeBoxServerHandler((Bridge) thing); - registerSqueezeBoxPlayerDiscoveryService(bridge); - return bridge; + if (SQUEEZEBOXSERVER_THING_TYPE.equals(thingTypeUID)) { + logger.trace("Creating handler for bridge thing {}", thing); + return new SqueezeBoxServerHandler((Bridge) thing); } - if (thingTypeUID.equals(SQUEEZEBOXPLAYER_THING_TYPE)) { - logger.trace("creating handler for player thing {}", thing); + if (SQUEEZEBOXPLAYER_THING_TYPE.equals(thingTypeUID)) { + logger.trace("Creating handler for player thing {}", thing); SqueezeBoxPlayerHandler playerHandler = new SqueezeBoxPlayerHandler(thing, createCallbackUrl(), stateDescriptionProvider); // Register the player as an audio sink logger.trace("Registering an audio sink for player thing {}", thing.getUID()); SqueezeBoxAudioSink audioSink = new SqueezeBoxAudioSink(playerHandler, audioHTTPServer, callbackUrl); - @SuppressWarnings("unchecked") - ServiceRegistration reg = (ServiceRegistration) bundleContext - .registerService(AudioSink.class.getName(), audioSink, new Hashtable<>()); - audioSinkRegistrations.put(thing.getUID().toString(), reg); + ServiceRegistration audioSinkRegistration = bundleContext.registerService(AudioSink.class, + audioSink, new Hashtable<>()); + audioSinkRegistrations.put(thing.getUID().toString(), audioSinkRegistration); return playerHandler; } @@ -126,72 +119,27 @@ public class SqueezeBoxHandlerFactory extends BaseThingHandlerFactory { return null; } - /** - * Adds SqueezeBoxServerHandlers to the discovery service to find SqueezeBox - * Players - * - * @param squeezeBoxServerHandler - */ - private synchronized void registerSqueezeBoxPlayerDiscoveryService( - SqueezeBoxServerHandler squeezeBoxServerHandler) { - logger.trace("registering player discovery service"); - - SqueezeBoxPlayerDiscoveryParticipant discoveryService = new SqueezeBoxPlayerDiscoveryParticipant( - squeezeBoxServerHandler); - - // Register the PlayerListener with the SqueezeBoxServerHandler - squeezeBoxServerHandler.registerSqueezeBoxPlayerListener(discoveryService); - - // Register the service, then add the service to the ServiceRegistration map - discoveryServiceRegs.put(squeezeBoxServerHandler.getThing().getUID(), - bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>())); - } - @Override protected synchronized void removeHandler(ThingHandler thingHandler) { - if (thingHandler instanceof SqueezeBoxServerHandler serverHandler) { - logger.trace("removing handler for bridge thing {}", thingHandler.getThing()); - - ServiceRegistration serviceReg = this.discoveryServiceRegs.get(thingHandler.getThing().getUID()); - if (serviceReg != null) { - logger.trace("unregistering player discovery service"); - - // Get the discovery service object and use it to cancel the RequestPlayerJob - SqueezeBoxPlayerDiscoveryParticipant discoveryService = (SqueezeBoxPlayerDiscoveryParticipant) bundleContext - .getService(serviceReg.getReference()); - discoveryService.cancelRequestPlayerJob(); - - // Unregister the PlayerListener from the SqueezeBoxServerHandler - serverHandler.unregisterSqueezeBoxPlayerListener( - (SqueezeBoxPlayerEventListener) bundleContext.getService(serviceReg.getReference())); - - // Unregister the PlayerListener service - serviceReg.unregister(); - - // Remove the service from the ServiceRegistration map - discoveryServiceRegs.remove(thingHandler.getThing().getUID()); - } - } - if (thingHandler instanceof SqueezeBoxPlayerHandler playerHandler) { SqueezeBoxServerHandler bridge = playerHandler.getSqueezeBoxServerHandler(); if (bridge != null) { // Unregister the player's audio sink logger.trace("Unregistering the audio sync service for player thing {}", thingHandler.getThing().getUID()); - ServiceRegistration reg = audioSinkRegistrations - .get(thingHandler.getThing().getUID().toString()); - if (reg != null) { - reg.unregister(); + ServiceRegistration audioSinkRegistration = audioSinkRegistrations + .remove(thingHandler.getThing().getUID().toString()); + if (audioSinkRegistration != null) { + audioSinkRegistration.unregister(); } - logger.trace("removing handler for player thing {}", thingHandler.getThing()); + logger.trace("Removing handler for player thing {}", thingHandler.getThing()); bridge.removePlayerCache(playerHandler.getMac()); } } } - private String createCallbackUrl() { + private @Nullable String createCallbackUrl() { final String ipAddress = networkAddressService.getPrimaryIpv4HostAddress(); if (ipAddress == null) { logger.warn("No network interface could be found."); diff --git a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/config/SqueezeBoxPlayerConfig.java b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/config/SqueezeBoxPlayerConfig.java index 5559ad6e0a..ee6f084a06 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/config/SqueezeBoxPlayerConfig.java +++ b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/config/SqueezeBoxPlayerConfig.java @@ -12,6 +12,9 @@ */ package org.openhab.binding.squeezebox.internal.config; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + /** * Configuration for a player * @@ -19,11 +22,12 @@ package org.openhab.binding.squeezebox.internal.config; * @author Mark Hilbush - Convert sound notification volume from channel to config parameter * */ +@NonNullByDefault public class SqueezeBoxPlayerConfig { /** * MAC address of player */ - public String mac; + public String mac = ""; /** * Number of seconds to wait to time out a notification @@ -33,5 +37,5 @@ public class SqueezeBoxPlayerConfig { /** * Volume used for playing notifications */ - public Integer notificationVolume; + public @Nullable Integer notificationVolume; } diff --git a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/discovery/SqueezeBoxPlayerDiscoveryParticipant.java b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/discovery/SqueezeBoxPlayerDiscoveryService.java similarity index 70% rename from bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/discovery/SqueezeBoxPlayerDiscoveryParticipant.java rename to bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/discovery/SqueezeBoxPlayerDiscoveryService.java index 10199665ba..d635de9507 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/discovery/SqueezeBoxPlayerDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/discovery/SqueezeBoxPlayerDiscoveryService.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.squeezebox.internal.discovery; -import static org.openhab.binding.squeezebox.internal.SqueezeBoxBindingConstants.SQUEEZEBOXPLAYER_THING_TYPE; +import static org.openhab.binding.squeezebox.internal.SqueezeBoxBindingConstants.*; import java.util.HashMap; import java.util.List; @@ -20,15 +20,20 @@ import java.util.Map; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.squeezebox.internal.handler.SqueezeBoxPlayer; import org.openhab.binding.squeezebox.internal.handler.SqueezeBoxPlayerEventListener; import org.openhab.binding.squeezebox.internal.handler.SqueezeBoxPlayerHandler; import org.openhab.binding.squeezebox.internal.handler.SqueezeBoxServerHandler; import org.openhab.binding.squeezebox.internal.model.Favorite; -import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingUID; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ServiceScope; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,63 +47,92 @@ import org.slf4j.LoggerFactory; * @author Mark Hilbush - Added event to update favorites list * */ -public class SqueezeBoxPlayerDiscoveryParticipant extends AbstractDiscoveryService +@Component(scope = ServiceScope.PROTOTYPE, service = SqueezeBoxPlayerDiscoveryService.class) +@NonNullByDefault +public class SqueezeBoxPlayerDiscoveryService extends AbstractThingHandlerDiscoveryService implements SqueezeBoxPlayerEventListener { - private final Logger logger = LoggerFactory.getLogger(SqueezeBoxPlayerDiscoveryParticipant.class); + private final Logger logger = LoggerFactory.getLogger(SqueezeBoxPlayerDiscoveryService.class); private static final int TIMEOUT = 60; private static final int TTL = 60; - private SqueezeBoxServerHandler squeezeBoxServerHandler; - private ScheduledFuture requestPlayerJob; + private @Nullable ScheduledFuture requestPlayerJob; /** * Discovers SqueezeBox Players attached to a SqueezeBox Server - * - * @param squeezeBoxServerHandler */ - public SqueezeBoxPlayerDiscoveryParticipant(SqueezeBoxServerHandler squeezeBoxServerHandler) { - super(SqueezeBoxPlayerHandler.SUPPORTED_THING_TYPES_UIDS, TIMEOUT, true); - this.squeezeBoxServerHandler = squeezeBoxServerHandler; + public SqueezeBoxPlayerDiscoveryService() { + super(SqueezeBoxServerHandler.class, SqueezeBoxPlayerHandler.SUPPORTED_THING_TYPES_UIDS, TIMEOUT, true); setupRequestPlayerJob(); } + @Override + public void initialize() { + super.initialize(); + + // Register the PlayerListener with the SqueezeBoxServerHandler + thingHandler.registerSqueezeBoxPlayerListener(this); + } + + @Override + public void dispose() { + // Unregister the PlayerListener from the SqueezeBoxServerHandler + thingHandler.unregisterSqueezeBoxPlayerListener(this); + + cancelRequestPlayerJob(); + + super.dispose(); + } + @Override protected void startScan() { logger.debug("startScan invoked in SqueezeBoxPlayerDiscoveryParticipant"); - this.squeezeBoxServerHandler.requestPlayers(); - this.squeezeBoxServerHandler.requestFavorites(); + thingHandler.requestPlayers(); + thingHandler.requestFavorites(); } /* * Allows request player job to be canceled when server handler is removed */ - public void cancelRequestPlayerJob() { + private void cancelRequestPlayerJob() { logger.debug("canceling RequestPlayerJob"); + ScheduledFuture requestPlayerJob = this.requestPlayerJob; if (requestPlayerJob != null) { requestPlayerJob.cancel(true); - requestPlayerJob = null; + this.requestPlayerJob = null; } } @Override public void playerAdded(SqueezeBoxPlayer player) { - ThingUID bridgeUID = squeezeBoxServerHandler.getThing().getUID(); + ThingUID bridgeUID = thingHandler.getThing().getUID(); ThingUID thingUID = new ThingUID(SQUEEZEBOXPLAYER_THING_TYPE, bridgeUID, player.macAddress.replace(":", "")); if (!playerThingExists(thingUID)) { - logger.debug("player added {} : {} ", player.macAddress, player.name); + logger.debug("Player added {}: {} ", player.macAddress, player.name); Map properties = new HashMap<>(1); String representationPropertyName = "mac"; properties.put(representationPropertyName, player.macAddress); // Added other properties - properties.put("modelId", player.model); - properties.put("name", player.name); - properties.put("uid", player.uuid); - properties.put("ip", player.ipAddr); + String model = player.model; + if (model != null) { + properties.put(Thing.PROPERTY_MODEL_ID, model); + } + String name = player.name; + if (name != null) { + properties.put(PROPERTY_NAME, name); + } + String uuid = player.uuid; + if (uuid != null) { + properties.put(PROPERTY_UID, uuid); + } + String ipAddr = player.ipAddr; + if (ipAddr != null) { + properties.put(PROPERTY_IP, ipAddr); + } DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties) .withRepresentationProperty(representationPropertyName).withBridge(bridgeUID).withLabel(player.name) @@ -109,7 +143,7 @@ public class SqueezeBoxPlayerDiscoveryParticipant extends AbstractDiscoveryServi } private boolean playerThingExists(ThingUID newThingUID) { - return squeezeBoxServerHandler.getThing().getThing(newThingUID) != null; + return thingHandler.getThing().getThing(newThingUID) != null; } /** @@ -118,7 +152,7 @@ public class SqueezeBoxPlayerDiscoveryParticipant extends AbstractDiscoveryServi private void setupRequestPlayerJob() { logger.debug("Request player job scheduled to run every {} seconds", TTL); requestPlayerJob = scheduler.scheduleWithFixedDelay(() -> { - squeezeBoxServerHandler.requestPlayers(); + thingHandler.requestPlayers(); }, 10, TTL, TimeUnit.SECONDS); } @@ -228,7 +262,7 @@ public class SqueezeBoxPlayerDiscoveryParticipant extends AbstractDiscoveryServi } @Override - public void buttonsChangeEvent(String mac, String likeCommand, String unlikeCommand) { + public void buttonsChangeEvent(String mac, @Nullable String likeCommand, @Nullable String unlikeCommand) { } @Override diff --git a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/discovery/SqueezeBoxServerDiscoveryParticipant.java b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/discovery/SqueezeBoxServerDiscoveryParticipant.java index 0b42110685..b88485133e 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/discovery/SqueezeBoxServerDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/discovery/SqueezeBoxServerDiscoveryParticipant.java @@ -19,6 +19,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.jupnp.model.meta.RemoteDevice; import org.openhab.binding.squeezebox.internal.utils.HttpUtils; import org.openhab.binding.squeezebox.internal.utils.SqueezeBoxCommunicationException; @@ -40,6 +42,7 @@ import org.slf4j.LoggerFactory; * */ @Component +@NonNullByDefault public class SqueezeBoxServerDiscoveryParticipant implements UpnpDiscoveryParticipant { private final Logger logger = LoggerFactory.getLogger(SqueezeBoxServerDiscoveryParticipant.class); @@ -54,7 +57,7 @@ public class SqueezeBoxServerDiscoveryParticipant implements UpnpDiscoveryPartic } @Override - public DiscoveryResult createResult(RemoteDevice device) { + public @Nullable DiscoveryResult createResult(RemoteDevice device) { ThingUID uid = getThingUID(device); if (uid != null) { Map properties = new HashMap<>(3); @@ -98,7 +101,7 @@ public class SqueezeBoxServerDiscoveryParticipant implements UpnpDiscoveryPartic } @Override - public ThingUID getThingUID(RemoteDevice device) { + public @Nullable ThingUID getThingUID(RemoteDevice device) { if (device.getDetails().getFriendlyName() != null) { if (device.getDetails().getModelDetails().getModelName().contains(MODEL_NAME)) { logger.debug("Discovered a {} thing with UDN '{}'", device.getDetails().getFriendlyName(), diff --git a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxNotificationListener.java b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxNotificationListener.java index 7e118351d0..02947feb91 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxNotificationListener.java +++ b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxNotificationListener.java @@ -16,6 +16,8 @@ import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.squeezebox.internal.model.Favorite; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,6 +29,7 @@ import org.slf4j.LoggerFactory; * @author Mark Hilbush - Initial contribution * @author Mark Hilbush - Added event to update favorites list */ +@NonNullByDefault public final class SqueezeBoxNotificationListener implements SqueezeBoxPlayerEventListener { private final Logger logger = LoggerFactory.getLogger(SqueezeBoxNotificationListener.class); @@ -238,7 +241,7 @@ public final class SqueezeBoxNotificationListener implements SqueezeBoxPlayerEve } @Override - public void buttonsChangeEvent(String mac, String likeCommand, String unlikeCommand) { + public void buttonsChangeEvent(String mac, @Nullable String likeCommand, @Nullable String unlikeCommand) { } @Override diff --git a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxNotificationPlayer.java b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxNotificationPlayer.java index 24653902ae..f4bdc5da59 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxNotificationPlayer.java +++ b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxNotificationPlayer.java @@ -14,6 +14,7 @@ package org.openhab.binding.squeezebox.internal.handler; import java.io.Closeable; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.squeezebox.internal.utils.SqueezeBoxTimeoutException; import org.openhab.core.library.types.StringType; import org.slf4j.Logger; @@ -29,6 +30,7 @@ import org.slf4j.LoggerFactory; * @author Mark Hilbush - Convert sound notification volume from channel to config parameter * */ +@NonNullByDefault class SqueezeBoxNotificationPlayer implements Closeable { private final Logger logger = LoggerFactory.getLogger(SqueezeBoxNotificationPlayer.class); @@ -61,10 +63,6 @@ class SqueezeBoxNotificationPlayer implements Closeable { } void play() throws InterruptedException, SqueezeBoxTimeoutException { - if (squeezeBoxServerHandler == null) { - logger.warn("Server handler is null"); - return; - } setupPlayerForNotification(); addNotificationMessageToPlaylist(); playNotification(); diff --git a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxPlayerEventListener.java b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxPlayerEventListener.java index 00a96308a7..888f5eb86c 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxPlayerEventListener.java +++ b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxPlayerEventListener.java @@ -14,6 +14,8 @@ package org.openhab.binding.squeezebox.internal.handler; import java.util.List; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.squeezebox.internal.model.Favorite; /** @@ -23,6 +25,7 @@ import org.openhab.binding.squeezebox.internal.model.Favorite; * @author Mark Hilbush - Added durationEvent * @author Mark Hilbush - Added event to update favorites list */ +@NonNullByDefault public interface SqueezeBoxPlayerEventListener { void playerAdded(SqueezeBoxPlayer player); @@ -91,7 +94,7 @@ public interface SqueezeBoxPlayerEventListener { void sourceChangeEvent(String mac, String source); - void buttonsChangeEvent(String mac, String likeCommand, String unlikeCommand); + void buttonsChangeEvent(String mac, @Nullable String likeCommand, @Nullable String unlikeCommand); void connectedStateChangeEvent(String mac, boolean connected); } diff --git a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxPlayerHandler.java b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxPlayerHandler.java index b192b0f74f..8db0cfbd1d 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxPlayerHandler.java +++ b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxPlayerHandler.java @@ -26,7 +26,8 @@ import java.util.Set; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.squeezebox.internal.SqueezeBoxStateDescriptionOptionsProvider; import org.openhab.binding.squeezebox.internal.config.SqueezeBoxPlayerConfig; import org.openhab.binding.squeezebox.internal.model.Favorite; @@ -72,6 +73,7 @@ import org.slf4j.LoggerFactory; * @author Mark Hilbush - Convert sound notification volume from channel to config parameter * @author Mark Hilbush - Add like/unlike functionality */ +@NonNullByDefault public class SqueezeBoxPlayerHandler extends BaseThingHandler implements SqueezeBoxPlayerEventListener { private final Logger logger = LoggerFactory.getLogger(SqueezeBoxPlayerHandler.class); @@ -86,17 +88,17 @@ public class SqueezeBoxPlayerHandler extends BaseThingHandler implements Squeeze /** * Keeps current track time */ - private ScheduledFuture timeCounterJob; + private @Nullable ScheduledFuture timeCounterJob; /** * Local reference to our bridge */ - private SqueezeBoxServerHandler squeezeBoxServerHandler; + private @Nullable SqueezeBoxServerHandler squeezeBoxServerHandler; /** * Our mac address, needed everywhere */ - private String mac; + private String mac = ""; /** * The server sends us the current time on play/pause/stop events, we @@ -113,26 +115,27 @@ public class SqueezeBoxPlayerHandler extends BaseThingHandler implements Squeeze /** * Separate volume level for notifications */ - private Integer notificationSoundVolume = null; + private @Nullable Integer notificationSoundVolume = null; - private String callbackUrl; + private @Nullable String callbackUrl; private SqueezeBoxStateDescriptionOptionsProvider stateDescriptionProvider; - private static final ExpiringCacheMap IMAGE_CACHE = new ExpiringCacheMap<>( + private static final ExpiringCacheMap IMAGE_CACHE = new ExpiringCacheMap<>( TimeUnit.MINUTES.toMillis(15)); // 15min - private String likeCommand; - private String unlikeCommand; + private @Nullable String likeCommand; + private @Nullable String unlikeCommand; private boolean connected = false; /** * Creates SqueezeBox Player Handler * * @param thing + * @param callbackUrl * @param stateDescriptionProvider */ - public SqueezeBoxPlayerHandler(@NonNull Thing thing, String callbackUrl, + public SqueezeBoxPlayerHandler(Thing thing, @Nullable String callbackUrl, SqueezeBoxStateDescriptionOptionsProvider stateDescriptionProvider) { super(thing); this.callbackUrl = callbackUrl; @@ -142,9 +145,15 @@ public class SqueezeBoxPlayerHandler extends BaseThingHandler implements Squeeze @Override public void initialize() { mac = getConfig().as(SqueezeBoxPlayerConfig.class).mac; + if (mac.isBlank()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/offline.conf-error.mac-not-set"); + return; + } timeCounter(); updateThingStatus(); - logger.debug("player thing {} initialized with mac {}", getThing().getUID(), mac); + logger.debug("Player thing {} initialized with mac {}", getThing().getUID(), mac); + SqueezeBoxServerHandler squeezeBoxServerHandler = this.squeezeBoxServerHandler; if (squeezeBoxServerHandler != null) { // ensure we get an up-to-date connection state squeezeBoxServerHandler.requestPlayers(); @@ -178,20 +187,23 @@ public class SqueezeBoxPlayerHandler extends BaseThingHandler implements Squeeze @Override public void dispose() { // stop our duration counter + ScheduledFuture timeCounterJob = this.timeCounterJob; if (timeCounterJob != null && !timeCounterJob.isCancelled()) { timeCounterJob.cancel(true); - timeCounterJob = null; + this.timeCounterJob = null; } + SqueezeBoxServerHandler squeezeBoxServerHandler = this.squeezeBoxServerHandler; if (squeezeBoxServerHandler != null) { squeezeBoxServerHandler.removePlayerCache(mac); } - logger.debug("player thing {} disposed for mac {}", getThing().getUID(), mac); + logger.debug("Player thing {} disposed for mac {}", getThing().getUID(), mac); super.dispose(); } @Override public void handleCommand(ChannelUID channelUID, Command command) { + SqueezeBoxServerHandler squeezeBoxServerHandler = this.squeezeBoxServerHandler; if (squeezeBoxServerHandler == null) { logger.debug("Player {} has no server configured, ignoring command: {}", getThing().getUID(), command); return; @@ -319,9 +331,11 @@ public class SqueezeBoxPlayerHandler extends BaseThingHandler implements Squeeze squeezeBoxServerHandler.playFavorite(mac, command.toString()); break; case CHANNEL_RATE: - if (command.equals(OnOffType.ON)) { + String likeCommand = this.likeCommand; + String unlikeCommand = this.unlikeCommand; + if (command.equals(OnOffType.ON) && likeCommand != null) { squeezeBoxServerHandler.rate(mac, likeCommand); - } else if (command.equals(OnOffType.OFF)) { + } else if (command.equals(OnOffType.OFF) && unlikeCommand != null) { squeezeBoxServerHandler.rate(mac, unlikeCommand); } break; @@ -342,7 +356,30 @@ public class SqueezeBoxPlayerHandler extends BaseThingHandler implements Squeeze @Override public void playerAdded(SqueezeBoxPlayer player) { - // Player properties are saved in SqueezeBoxPlayerDiscoveryParticipant + if (!isMe(player.macAddress)) { + return; + } + + Map properties = editProperties(); + + String model = player.model; + if (model != null) { + properties.put(Thing.PROPERTY_MODEL_ID, model); + } + String name = player.name; + if (name != null) { + properties.put(PROPERTY_NAME, name); + } + String uuid = player.uuid; + if (uuid != null) { + properties.put(PROPERTY_UID, uuid); + } + String ipAddr = player.ipAddr; + if (ipAddr != null) { + properties.put(PROPERTY_IP, ipAddr); + } + + updateProperties(properties); } @Override @@ -482,10 +519,10 @@ public class SqueezeBoxPlayerHandler extends BaseThingHandler implements Squeeze * @return A RawType object containing the image, null if the content type could not be found or the content type is * not an image. */ - private RawType downloadImage(String mac, String url) { + private @Nullable RawType downloadImage(String mac, String url) { // Only get the image if this is my PlayerHandler instance if (isMe(mac)) { - if (url != null && !url.isEmpty()) { + if (!url.isEmpty()) { RawType image = IMAGE_CACHE.putIfAbsentAndGet(url, () -> { if (logger.isDebugEnabled()) { logger.debug("Trying to download the content of URL {}", sanitizeUrl(url)); @@ -500,14 +537,10 @@ public class SqueezeBoxPlayerHandler extends BaseThingHandler implements Squeeze return null; } }); - if (image == null) { - if (logger.isDebugEnabled()) { - logger.debug("Failed to download the content of URL {}", sanitizeUrl(url)); - } - return null; - } else { - return image; + if (image == null && logger.isDebugEnabled()) { + logger.debug("Failed to download the content of URL {}", sanitizeUrl(url)); } + return image; } } return null; @@ -536,7 +569,7 @@ public class SqueezeBoxPlayerHandler extends BaseThingHandler implements Squeeze /** * Wrap the given RawType and return it as {@link State} or return {@link UnDefType#UNDEF} if the RawType is null. */ - private State createImage(RawType image) { + private State createImage(@Nullable RawType image) { if (image == null) { return UnDefType.UNDEF; } else { @@ -567,7 +600,7 @@ public class SqueezeBoxPlayerHandler extends BaseThingHandler implements Squeeze } @Override - public void buttonsChangeEvent(String mac, String likeCommand, String unlikeCommand) { + public void buttonsChangeEvent(String mac, @Nullable String likeCommand, @Nullable String unlikeCommand) { if (isMe(mac)) { this.likeCommand = likeCommand; this.unlikeCommand = unlikeCommand; @@ -656,7 +689,7 @@ public class SqueezeBoxPlayerHandler extends BaseThingHandler implements Squeeze return cachedStateAsInt(CHANNEL_CURRENT_PLAYLIST_REPEAT); } - private boolean cachedStateAsBoolean(String key, @NonNull State activeState) { + private boolean cachedStateAsBoolean(String key, State activeState) { return activeState.equals(stateMap.get(key)); } @@ -693,8 +726,8 @@ public class SqueezeBoxPlayerHandler extends BaseThingHandler implements Squeeze * * @return */ - public SqueezeBoxServerHandler getSqueezeBoxServerHandler() { - return this.squeezeBoxServerHandler; + public @Nullable SqueezeBoxServerHandler getSqueezeBoxServerHandler() { + return squeezeBoxServerHandler; } /** @@ -703,7 +736,7 @@ public class SqueezeBoxPlayerHandler extends BaseThingHandler implements Squeeze * @return */ public String getMac() { - return this.mac; + return mac; } /* @@ -723,6 +756,7 @@ public class SqueezeBoxPlayerHandler extends BaseThingHandler implements Squeeze public PercentType getNotificationSoundVolume() { // Get the notification sound volume from this player thing's configuration Integer configNotificationSoundVolume = getConfigAs(SqueezeBoxPlayerConfig.class).notificationVolume; + Integer notificationSoundVolume = this.notificationSoundVolume; // Determine which volume to use Integer currentNotificationSoundVolume; @@ -740,9 +774,7 @@ public class SqueezeBoxPlayerHandler extends BaseThingHandler implements Squeeze * Used by the AudioSink to set the volume level that should be used to play the notification */ public void setNotificationSoundVolume(PercentType newNotificationSoundVolume) { - if (newNotificationSoundVolume != null) { - notificationSoundVolume = Integer.valueOf(newNotificationSoundVolume.intValue()); - } + notificationSoundVolume = Integer.valueOf(newNotificationSoundVolume.intValue()); } /* @@ -751,6 +783,11 @@ public class SqueezeBoxPlayerHandler extends BaseThingHandler implements Squeeze public void playNotificationSoundURI(StringType uri) { logger.debug("Play notification sound on player {} at URI {}", mac, uri); + SqueezeBoxServerHandler squeezeBoxServerHandler = this.squeezeBoxServerHandler; + if (squeezeBoxServerHandler == null) { + logger.warn("Server handler is null"); + return; + } try (SqueezeBoxNotificationPlayer notificationPlayer = new SqueezeBoxNotificationPlayer(this, squeezeBoxServerHandler, uri)) { notificationPlayer.play(); @@ -766,7 +803,7 @@ public class SqueezeBoxPlayerHandler extends BaseThingHandler implements Squeeze /* * Return the IP and port of the OH2 web server */ - public String getHostAndPort() { + public @Nullable String getHostAndPort() { return callbackUrl; } } diff --git a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxPlayerPlayState.java b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxPlayerPlayState.java index 92ad5a90ea..4391fbae03 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxPlayerPlayState.java +++ b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxPlayerPlayState.java @@ -12,12 +12,15 @@ */ package org.openhab.binding.squeezebox.internal.handler; +import org.eclipse.jdt.annotation.NonNullByDefault; + /*** * Enumeration of the play states of a player. * * @author Patrik Gfeller - Initial contribution * */ +@NonNullByDefault enum SqueezeBoxPlayerPlayState { STOP, PLAY, diff --git a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxPlayerState.java b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxPlayerState.java index 9994ddb90f..8b87d2ed16 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxPlayerState.java +++ b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxPlayerState.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.squeezebox.internal.handler; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,6 +22,7 @@ import org.slf4j.LoggerFactory; * @author Mark Hilbush - Initial contribution * @author Patrik Gfeller - Moved class to its own file. */ +@NonNullByDefault class SqueezeBoxPlayerState { private final Logger logger = LoggerFactory.getLogger(SqueezeBoxPlayerState.class); diff --git a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxServerHandler.java b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxServerHandler.java index e9a4136fc4..86d6414914 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxServerHandler.java +++ b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/handler/SqueezeBoxServerHandler.java @@ -27,6 +27,7 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -39,8 +40,10 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.squeezebox.internal.SqueezeBoxBindingConstants; import org.openhab.binding.squeezebox.internal.config.SqueezeBoxServerConfig; +import org.openhab.binding.squeezebox.internal.discovery.SqueezeBoxPlayerDiscoveryService; import org.openhab.binding.squeezebox.internal.dto.ButtonDTO; import org.openhab.binding.squeezebox.internal.dto.ButtonDTODeserializer; import org.openhab.binding.squeezebox.internal.dto.ButtonsDTO; @@ -57,6 +60,7 @@ import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.binding.BaseBridgeHandler; import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; import org.openhab.core.types.UnDefType; import org.slf4j.Logger; @@ -82,6 +86,7 @@ import com.google.gson.JsonSyntaxException; * @author Mark Hilbush - Get favorites from LMS; update channel and send to players * @author Mark Hilbush - Add like/unlike functionality */ +@NonNullByDefault public class SqueezeBoxServerHandler extends BaseBridgeHandler { private final Logger logger = LoggerFactory.getLogger(SqueezeBoxServerHandler.class); @@ -93,7 +98,7 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { // the value by which the volume is changed by each INCREASE or // DECREASE-Event private static final int VOLUME_CHANGE_SIZE = 5; - private static final String NEW_LINE = System.getProperty("line.separator"); + private static final @Nullable String NEW_LINE = System.getProperty("line.separator"); private static final String CHANNEL_CONFIG_QUOTE_LIST = "quoteList"; @@ -105,24 +110,24 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { private Map players = Collections.synchronizedMap(new HashMap<>()); // client socket and listener thread - private Socket clientSocket; - private SqueezeServerListener listener; - private Future reconnectFuture; + private @Nullable Socket clientSocket; + private @Nullable SqueezeServerListener listener; + private @Nullable Future reconnectFuture; - private String host; + private String host = ""; private int cliport; private int webport; - private String userId; + private String userId = ""; - private String password; + private String password = ""; private final Gson gson = new GsonBuilder().registerTypeAdapter(ButtonDTO.class, new ButtonDTODeserializer()) .create(); - private String jsonRpcUrl; - private String basicAuthorization; + private @Nullable String jsonRpcUrl; + private @Nullable String basicAuthorization; public SqueezeBoxServerHandler(Bridge bridge) { super(bridge); @@ -145,12 +150,18 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { public void handleCommand(ChannelUID channelUID, Command command) { } + @Override + public Collection> getServices() { + return Set.of(SqueezeBoxPlayerDiscoveryService.class); + } + /** * Checks if we have a connection to the Server * * @return */ public synchronized boolean isConnected() { + Socket clientSocket = this.clientSocket; if (clientSocket == null) { return false; } @@ -229,7 +240,7 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { addPlaylistItem(mac, url, null); } - public void addPlaylistItem(String mac, String url, String title) { + public void addPlaylistItem(String mac, String url, @Nullable String title) { StringBuilder playlistCommand = new StringBuilder(); playlistCommand.append(mac).append(" playlist add ").append(url); if (title != null) { @@ -294,9 +305,7 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { } public void rate(String mac, String rateCommand) { - if (rateCommand != null) { - sendCommand(mac + " " + rateCommand); - } + sendCommand(mac + " " + rateCommand); } public void sleep(String mac, Duration sleepDuration) { @@ -349,8 +358,9 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { return; } - if (!isConnected()) { - logger.debug("no connection to squeeze server when trying to send command, returning..."); + Socket clientSocket = this.clientSocket; + if (clientSocket == null || !isConnected()) { + logger.debug("No connection to squeeze server when trying to send command, returning..."); return; } @@ -367,7 +377,7 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { /* * Remove password from login command to prevent it from being logged */ - String sanitizeCommand(String command) { + private String sanitizeCommand(String command) { String sanitizedCommand = command; if (command.startsWith("login")) { sanitizedCommand = command.replace(password, "**********"); @@ -406,7 +416,7 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { } try { - listener = new SqueezeServerListener(); + SqueezeServerListener listener = this.listener = new SqueezeServerListener(); listener.start(); logger.debug("listener connection started to server {}:{}", host, cliport); } catch (IllegalThreadStateException e) { @@ -420,6 +430,8 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { * Disconnects from a SqueezeBox Server */ private void disconnect() { + SqueezeServerListener listener = this.listener; + Socket clientSocket = this.clientSocket; try { if (listener != null) { listener.terminate(); @@ -431,8 +443,8 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { logger.trace("Error attempting to disconnect from Squeeze Server", e); return; } finally { - clientSocket = null; - listener = null; + this.clientSocket = null; + this.listener = null; } players.clear(); logger.trace("Squeeze Server connection stopped."); @@ -457,6 +469,10 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { ScheduledFuture requestFavoritesJob = null; try { + Socket clientSocket = SqueezeBoxServerHandler.this.clientSocket; + if (clientSocket == null) { + throw new IllegalStateException("Client socket is null"); + } reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); login(); updateStatus(ThingStatus.ONLINE); @@ -522,7 +538,6 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { return URLEncoder.encode(raw, StandardCharsets.UTF_8); } - @NonNullByDefault private class KeyValue { final String key; final String value; @@ -535,15 +550,13 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { private List decodeKeyValueResponse(String[] response) { final List keysAndValues = new ArrayList<>(); - if (response != null) { - for (String line : response) { - final String decoded = decode(line); - int colonPos = decoded.indexOf(":"); - if (colonPos < 0) { - continue; - } - keysAndValues.add(new KeyValue(decoded.substring(0, colonPos), decoded.substring(colonPos + 1))); + for (String line : response) { + final String decoded = decode(line); + int colonPos = decoded.indexOf(":"); + if (colonPos < 0) { + continue; } + keysAndValues.add(new KeyValue(decoded.substring(0, colonPos), decoded.substring(colonPos + 1))); } return keysAndValues; } @@ -555,7 +568,8 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { String[] playersList = message.split("playerindex\\S*\\s"); for (String playerParams : playersList) { // For each player, split out parameters and decode parameter - final Map keysAndValues = decodeKeyValueResponse(playerParams.split("\\s")).stream() + String[] playParamsArray = playerParams.split("\\s"); + final Map keysAndValues = decodeKeyValueResponse(playParamsArray).stream() .collect(Collectors.toMap(kv -> kv.key, kv -> kv.value)); final String macAddress = keysAndValues.get("playerid"); @@ -820,7 +834,8 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { }); } - private String constructCoverArtUrl(String mac, boolean coverart, String coverid, String artwork_url) { + private String constructCoverArtUrl(String mac, boolean coverart, @Nullable String coverid, + @Nullable String artworkUrl) { String hostAndPort; if (!userId.isEmpty()) { hostAndPort = "http://" + encode(userId) + ":" + encode(password) + "@" + host + ":" + webport; @@ -837,16 +852,16 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { // Typically is used to access cover art of local music files url = hostAndPort + "/music/" + coverid + "/cover.jpg"; } - } else if (artwork_url != null) { - if (artwork_url.startsWith("http")) { + } else if (artworkUrl != null) { + if (artworkUrl.startsWith("http")) { // Typically indicates that cover art is not local to LMS - url = artwork_url; - } else if (artwork_url.startsWith("/")) { + url = artworkUrl; + } else if (artworkUrl.startsWith("/")) { // Typically used for default coverart for plugins (e.g. Pandora, etc.) - url = hostAndPort + artwork_url; + url = hostAndPort + artworkUrl; } else { // Another variation of default coverart for plugins (e.g. Pandora, etc.) - url = hostAndPort + "/" + artwork_url; + url = hostAndPort + "/" + artworkUrl; } } return url; @@ -933,7 +948,7 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { isTypePlaylist = false; } // Favorite name - else if ("name".equals(entry.key)) { + else if ("name".equals(entry.key) && f != null) { f.name = entry.value; } else if ("type".equals(entry.key) && "playlist".equals(entry.value)) { isTypePlaylist = true; @@ -992,6 +1007,10 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { } private void updateCustomButtons(final String mac) { + String jsonRpcUrl = SqueezeBoxServerHandler.this.jsonRpcUrl; + if (jsonRpcUrl == null) { + throw new IllegalStateException("JSON-RPC URL is not initialized"); + } String response = executePost(jsonRpcUrl, JSONRPC_STATUS_REQUEST.replace("@@MAC@@", mac)); if (response != null) { logger.trace("Status response: {}", response); @@ -1018,7 +1037,7 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { } } - private String executePost(String url, String content) { + private @Nullable String executePost(String url, String content) { // @formatter:off HttpRequestBuilder builder = HttpRequestBuilder.postTo(url) .withTimeout(Duration.ofSeconds(5)) @@ -1068,7 +1087,7 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { for (Thing thing : things) { ThingHandler handler = thing.getHandler(); if (handler instanceof SqueezeBoxPlayerEventListener playerEventListener - && !squeezeBoxPlayerListeners.contains(handler)) { + && !squeezeBoxPlayerListeners.contains(playerEventListener)) { event.updateListener(playerEventListener); } } @@ -1110,7 +1129,7 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { * Schedule the server to try and reconnect */ private void scheduleReconnect() { - logger.debug("scheduling squeeze server reconnect in {} seconds", RECONNECT_TIME); + logger.debug("Scheduling squeeze server reconnect in {} seconds", RECONNECT_TIME); cancelReconnect(); reconnectFuture = scheduler.schedule(this::connect, RECONNECT_TIME, TimeUnit.SECONDS); } @@ -1119,6 +1138,7 @@ public class SqueezeBoxServerHandler extends BaseBridgeHandler { * Clears our reconnect job if exists */ private void cancelReconnect() { + Future reconnectFuture = this.reconnectFuture; if (reconnectFuture != null) { reconnectFuture.cancel(true); } diff --git a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/model/Favorite.java b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/model/Favorite.java index baceab5cf2..642837c4c8 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/model/Favorite.java +++ b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/model/Favorite.java @@ -12,12 +12,15 @@ */ package org.openhab.binding.squeezebox.internal.model; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Attributes of a Squeezebox Server favorite * * @author Mark Hilbush - Initial contribution * */ +@NonNullByDefault public class Favorite { /** * Favorite id is of form xxxxxxxx.nn @@ -32,7 +35,7 @@ public class Favorite { /** * The name given to the favorite in the Squeezebox Server. */ - public String name; + public String name = ""; /** * Creates a preset from the given favorite id diff --git a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/utils/HttpUtils.java b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/utils/HttpUtils.java index a8dc9e7aa7..114f8458b7 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/utils/HttpUtils.java +++ b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/utils/HttpUtils.java @@ -16,6 +16,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.util.StringContentProvider; @@ -35,6 +36,7 @@ import com.google.gson.JsonParser; * @author Mark Hilbush - Add support for LMS authentication * @author Mark Hilbush - Rework exception handling */ +@NonNullByDefault public class HttpUtils { private static Logger logger = LoggerFactory.getLogger(HttpUtils.class); diff --git a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/utils/SqueezeBoxCommunicationException.java b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/utils/SqueezeBoxCommunicationException.java index 848f06963a..3648d8ed92 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/utils/SqueezeBoxCommunicationException.java +++ b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/utils/SqueezeBoxCommunicationException.java @@ -12,11 +12,14 @@ */ package org.openhab.binding.squeezebox.internal.utils; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Exception thrown when unable to communicate with LMS server. * * @author Mark Hilbush - Initial contribution */ +@NonNullByDefault public class SqueezeBoxCommunicationException extends Exception { private static final long serialVersionUID = 1540489268747099161L; diff --git a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/utils/SqueezeBoxNotAuthorizedException.java b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/utils/SqueezeBoxNotAuthorizedException.java index 6c5d734b08..8f64e8815c 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/utils/SqueezeBoxNotAuthorizedException.java +++ b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/utils/SqueezeBoxNotAuthorizedException.java @@ -12,12 +12,15 @@ */ package org.openhab.binding.squeezebox.internal.utils; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Exception thrown when calling LMS command line interface, and * the LMS is set up to require authentication. * * @author Mark Hilbush - Initial contribution */ +@NonNullByDefault public class SqueezeBoxNotAuthorizedException extends Exception { private static final long serialVersionUID = -5190671725971757821L; diff --git a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/utils/SqueezeBoxTimeoutException.java b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/utils/SqueezeBoxTimeoutException.java index f145086090..07b4850fb0 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/utils/SqueezeBoxTimeoutException.java +++ b/bundles/org.openhab.binding.squeezebox/src/main/java/org/openhab/binding/squeezebox/internal/utils/SqueezeBoxTimeoutException.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.squeezebox.internal.utils; +import org.eclipse.jdt.annotation.NonNullByDefault; + /*** * * Exception class to indicate a timeout during comminication with @@ -20,6 +22,7 @@ package org.openhab.binding.squeezebox.internal.utils; * @author Patrik Gfeller - Initial contribution * */ +@NonNullByDefault public class SqueezeBoxTimeoutException extends Exception { private static final long serialVersionUID = 4542388088266882905L; diff --git a/bundles/org.openhab.binding.squeezebox/src/main/resources/OH-INF/i18n/squeezebox.properties b/bundles/org.openhab.binding.squeezebox/src/main/resources/OH-INF/i18n/squeezebox.properties index 14f5704fef..647b51b6e3 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/resources/OH-INF/i18n/squeezebox.properties +++ b/bundles/org.openhab.binding.squeezebox/src/main/resources/OH-INF/i18n/squeezebox.properties @@ -116,4 +116,5 @@ channel-type.config.squeezebox.favoritesList.quoteList.description = Wrap the ri offline.conf-error.bridge-not-found = Bridge not found offline.conf-error.host-not-set = host is not set +offline.conf-error.mac-not-set = MAC address is not set offline.comm-error.end-of-stream = end of stream on socket read diff --git a/bundles/org.openhab.binding.squeezebox/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.squeezebox/src/main/resources/OH-INF/thing/thing-types.xml index 3d6824747b..e83a98a072 100644 --- a/bundles/org.openhab.binding.squeezebox/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.squeezebox/src/main/resources/OH-INF/thing/thing-types.xml @@ -94,10 +94,6 @@ Logitech - - - - 1