]> git.basschouten.com Git - openhab-addons.git/commitdiff
[pulseaudio] Fix exception handling when connecting (#12423)
authorlolodomo <lg.hc@free.fr>
Mon, 7 Mar 2022 23:01:54 +0000 (00:01 +0100)
committerGitHub <noreply@github.com>
Mon, 7 Mar 2022 23:01:54 +0000 (00:01 +0100)
Fix bridge/thing status update
Also update log levels

Fix #12419
Fix #12424

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseaudioClient.java
bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/handler/PulseaudioBridgeHandler.java
bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/handler/PulseaudioHandler.java

index 94bf0d28e61829391c3e6dffa166de77274a3ef7..4f7d6dc2c2be8fbb894f11734a74425a622d2d96 100644 (file)
@@ -18,7 +18,6 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintStream;
 import java.math.BigDecimal;
-import java.net.NoRouteToHostException;
 import java.net.Socket;
 import java.net.SocketException;
 import java.net.SocketTimeoutException;
@@ -129,20 +128,18 @@ public class PulseaudioClient {
      */
     private static final String MODULE_COMBINE_SINK = "module-combine-sink";
 
-    public PulseaudioClient(String host, int port, PulseAudioBindingConfiguration configuration) throws IOException {
+    public PulseaudioClient(String host, int port, PulseAudioBindingConfiguration configuration) {
         this.host = host;
         this.port = port;
         this.configuration = configuration;
 
         items = new ArrayList<>();
         modules = new ArrayList<>();
-
-        connect();
-        update();
     }
 
     public boolean isConnected() {
-        return client != null ? client.isConnected() : false;
+        Socket clientSocket = client;
+        return clientSocket != null ? clientSocket.isConnected() : false;
     }
 
     /**
@@ -378,9 +375,6 @@ public class PulseaudioClient {
      *            0 - 65536)
      */
     public void setVolume(AbstractAudioDeviceConfig item, int vol) {
-        if (item == null) {
-            return;
-        }
         String itemCommandName = getItemCommandName(item);
         if (itemCommandName == null) {
             return;
@@ -485,7 +479,7 @@ public class PulseaudioClient {
                 .map(portS -> Integer.parseInt(portS));
     }
 
-    private @NonNull Optional<@NonNull String> extractArgumentFromLine(String argumentWanted, String argumentLine) {
+    private Optional<@NonNull String> extractArgumentFromLine(String argumentWanted, String argumentLine) {
         String argument = null;
         int startPortIndex = argumentLine.indexOf(argumentWanted + "=");
         if (startPortIndex != -1) {
@@ -525,11 +519,8 @@ public class PulseaudioClient {
      * @param vol the new volume percent value the {@link AbstractAudioDeviceConfig} should be changed to (possible
      *            values from 0 - 100)
      */
-    public void setVolumePercent(@Nullable AbstractAudioDeviceConfig item, int vol) {
+    public void setVolumePercent(AbstractAudioDeviceConfig item, int vol) {
         int volumeToSet = vol;
-        if (item == null) {
-            return;
-        }
         if (volumeToSet <= 100) {
             volumeToSet = toAbsoluteVolume(volumeToSet);
         }
@@ -662,15 +653,16 @@ public class PulseaudioClient {
 
     private synchronized void sendRawCommand(String command) {
         checkConnection();
-        if (client != null && client.isConnected()) {
+        Socket clientSocket = client;
+        if (clientSocket != null && clientSocket.isConnected()) {
             try {
-                PrintStream out = new PrintStream(client.getOutputStream(), true);
+                PrintStream out = new PrintStream(clientSocket.getOutputStream(), true);
                 logger.trace("sending command {} to pa-server {}", command, host);
                 out.print(command + "\r\n");
                 out.close();
-                client.close();
+                clientSocket.close();
             } catch (IOException e) {
-                logger.error("{}", e.getLocalizedMessage(), e);
+                logger.warn("{}", e.getMessage(), e);
             }
         }
     }
@@ -679,12 +671,13 @@ public class PulseaudioClient {
         logger.trace("_sendRawRequest({})", command);
         checkConnection();
         String result = "";
-        if (client != null && client.isConnected()) {
+        Socket clientSocket = client;
+        if (clientSocket != null && clientSocket.isConnected()) {
             try {
-                PrintStream out = new PrintStream(client.getOutputStream(), true);
+                PrintStream out = new PrintStream(clientSocket.getOutputStream(), true);
                 out.print(command + "\r\n");
 
-                InputStream instr = client.getInputStream();
+                InputStream instr = clientSocket.getInputStream();
 
                 try {
                     byte[] buff = new byte[1024];
@@ -709,42 +702,52 @@ public class PulseaudioClient {
                 } catch (SocketException e) {
                     logger.warn("Socket exception while sending pulseaudio command: {}", e.getMessage());
                 } catch (IOException e) {
-                    logger.error("Exception while reading socket: {}", e.getMessage());
+                    logger.warn("Exception while reading socket: {}", e.getMessage());
                 }
                 instr.close();
                 out.close();
-                client.close();
+                clientSocket.close();
                 return result;
             } catch (IOException e) {
-                logger.error("{}", e.getLocalizedMessage(), e);
+                logger.warn("{}", e.getMessage(), e);
             }
         }
         return result;
     }
 
     private void checkConnection() {
-        if (client == null || client.isClosed() || !client.isConnected()) {
-            try {
-                connect();
-            } catch (IOException e) {
-                logger.error("{}", e.getLocalizedMessage(), e);
-            }
+        try {
+            connect();
+        } catch (IOException e) {
+            logger.debug("{}", e.getMessage(), e);
         }
     }
 
     /**
      * Connects to the pulseaudio server (timeout 500ms)
      */
-    private void connect() throws IOException {
-        try {
-            client = new Socket(host, port);
-            client.setSoTimeout(500);
-        } catch (UnknownHostException e) {
-            logger.error("unknown socket host {}", host);
-        } catch (NoRouteToHostException e) {
-            logger.error("no route to host {}", host);
-        } catch (SocketException e) {
-            logger.error("cannot connect to host {} : {}", host, e.getMessage());
+    public void connect() throws IOException {
+        Socket clientSocket = client;
+        if (clientSocket == null || clientSocket.isClosed() || !clientSocket.isConnected()) {
+            logger.trace("Try to connect...");
+            try {
+                client = new Socket(host, port);
+                client.setSoTimeout(500);
+                logger.trace("connected");
+            } catch (UnknownHostException e) {
+                client = null;
+                throw new IOException("Unknown host", e);
+            } catch (IllegalArgumentException e) {
+                client = null;
+                throw new IOException("Invalid port", e);
+            } catch (SecurityException | SocketException e) {
+                client = null;
+                throw new IOException(
+                        String.format("Cannot connect socket: %s", e.getMessage() != null ? e.getMessage() : ""), e);
+            } catch (IOException e) {
+                client = null;
+                throw e;
+            }
         }
     }
 
@@ -752,11 +755,12 @@ public class PulseaudioClient {
      * Disconnects from the pulseaudio server
      */
     public void disconnect() {
-        if (client != null) {
+        Socket clientSocket = client;
+        if (clientSocket != null) {
             try {
-                client.close();
+                clientSocket.close();
             } catch (IOException e) {
-                logger.error("{}", e.getLocalizedMessage(), e);
+                logger.debug("{}", e.getMessage(), e);
             }
         }
     }
index 7a90d94a4e1a137ea7b92871c2ce9c7fd5cdced9..133669d743de86cc8f1b4d9fdc080a61a473ab30 100644 (file)
@@ -34,6 +34,7 @@ import org.openhab.core.config.core.Configuration;
 import org.openhab.core.thing.Bridge;
 import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
 import org.openhab.core.thing.ThingTypeUID;
 import org.openhab.core.thing.binding.BaseBridgeHandler;
 import org.openhab.core.types.Command;
@@ -67,11 +68,22 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA
     private HashSet<String> lastActiveDevices = new HashSet<>();
 
     private ScheduledFuture<?> pollingJob;
-    private Runnable pollingRunnable = () -> {
-        update();
-    };
 
     private synchronized void update() {
+        try {
+            client.connect();
+            if (getThing().getStatus() != ThingStatus.ONLINE) {
+                updateStatus(ThingStatus.ONLINE);
+                logger.debug("Established connection to Pulseaudio server on Host '{}':'{}'.", host, port);
+            }
+        } catch (IOException e) {
+            logger.debug("{}", e.getMessage(), e);
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                    String.format("Couldn't connect to Pulsaudio server [Host '%s':'%d']: %s", host, port,
+                            e.getMessage() != null ? e.getMessage() : ""));
+            return;
+        }
+
         client.update();
         for (AbstractAudioDeviceConfig device : client.getItems()) {
             if (lastActiveDevices != null && lastActiveDevices.contains(device.getPaName())) {
@@ -79,7 +91,7 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA
                     try {
                         deviceStatusListener.onDeviceStateChanged(getThing().getUID(), device);
                     } catch (Exception e) {
-                        logger.error("An exception occurred while calling the DeviceStatusListener", e);
+                        logger.warn("An exception occurred while calling the DeviceStatusListener", e);
                     }
                 }
             } else {
@@ -88,7 +100,7 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA
                         deviceStatusListener.onDeviceAdded(getThing(), device);
                         deviceStatusListener.onDeviceStateChanged(getThing().getUID(), device);
                     } catch (Exception e) {
-                        logger.error("An exception occurred while calling the DeviceStatusListener", e);
+                        logger.warn("An exception occurred while calling the DeviceStatusListener", e);
                     }
                     lastActiveDevices.add(device.getPaName());
                 }
@@ -106,13 +118,7 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA
         if (command instanceof RefreshType) {
             client.update();
         } else {
-            logger.warn("received invalid command for pulseaudio bridge '{}'.", host);
-        }
-    }
-
-    private synchronized void startAutomaticRefresh() {
-        if (pollingJob == null || pollingJob.isCancelled()) {
-            pollingJob = scheduler.scheduleWithFixedDelay(pollingRunnable, 0, refreshInterval, TimeUnit.MILLISECONDS);
+            logger.debug("received unexpected command for pulseaudio bridge '{}'.", host);
         }
     }
 
@@ -140,26 +146,15 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA
         }
 
         if (host != null && !host.isEmpty()) {
-            Runnable connectRunnable = () -> {
-                try {
-                    client = new PulseaudioClient(host, port, configuration);
-                    if (client.isConnected()) {
-                        updateStatus(ThingStatus.ONLINE);
-                        logger.info("Established connection to Pulseaudio server on Host '{}':'{}'.", host, port);
-                        startAutomaticRefresh();
-                    }
-                } catch (IOException e) {
-                    logger.error("Couldn't connect to Pulsaudio server [Host '{}':'{}']: {}", host, port,
-                            e.getLocalizedMessage());
-                    updateStatus(ThingStatus.OFFLINE);
-                }
-            };
-            scheduler.schedule(connectRunnable, 0, TimeUnit.SECONDS);
+            client = new PulseaudioClient(host, port, configuration);
+            updateStatus(ThingStatus.UNKNOWN);
+            if (pollingJob == null || pollingJob.isCancelled()) {
+                pollingJob = scheduler.scheduleWithFixedDelay(this::update, 0, refreshInterval, TimeUnit.MILLISECONDS);
+            }
         } else {
-            logger.warn(
-                    "Couldn't connect to Pulseaudio server because of missing connection parameters [Host '{}':'{}'].",
-                    host, port);
-            updateStatus(ThingStatus.OFFLINE);
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, String.format(
+                    "Couldn't connect to Pulseaudio server because of missing connection parameters [Host '%s':'%d']",
+                    host, port));
         }
 
         this.configuration.addPulseAudioBindingConfigurationListener(this);
@@ -168,8 +163,10 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA
     @Override
     public void dispose() {
         this.configuration.removePulseAudioBindingConfigurationListener(this);
-        if (pollingJob != null) {
-            pollingJob.cancel(true);
+        ScheduledFuture<?> job = pollingJob;
+        if (job != null) {
+            job.cancel(true);
+            pollingJob = null;
         }
         if (client != null) {
             client.disconnect();
index 2a25df118a0a4879837cddb451d2a3ddbecc0ae6..a357b6631886d0c64ab7c9efc5d98e5a8f20165d 100644 (file)
@@ -50,6 +50,8 @@ import org.openhab.core.thing.Bridge;
 import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingStatusInfo;
 import org.openhab.core.thing.ThingTypeUID;
 import org.openhab.core.thing.ThingUID;
 import org.openhab.core.thing.binding.BaseThingHandler;
@@ -101,15 +103,13 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
         Configuration config = getThing().getConfiguration();
         name = (String) config.get(DEVICE_PARAMETER_NAME);
 
-        // until we get an update put the Thing offline
-        updateStatus(ThingStatus.OFFLINE);
+        updateStatus(ThingStatus.UNKNOWN);
         deviceOnlineWatchdog();
 
         // if it's a SINK thing, then maybe we have to activate the audio sink
-        if (PulseaudioBindingConstants.SINK_THING_TYPE.equals(thing.getThingTypeUID())) {
+        if (SINK_THING_TYPE.equals(thing.getThingTypeUID())) {
             // check the property to see if we it's enabled :
-            Boolean sinkActivated = (Boolean) thing.getConfiguration()
-                    .get(PulseaudioBindingConstants.DEVICE_PARAMETER_AUDIO_SINK_ACTIVATION);
+            Boolean sinkActivated = (Boolean) thing.getConfiguration().get(DEVICE_PARAMETER_AUDIO_SINK_ACTIVATION);
             if (sinkActivated != null && sinkActivated) {
                 audioSinkSetup();
             }
@@ -182,22 +182,25 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
 
     @Override
     public void dispose() {
-        if (refreshJob != null && !refreshJob.isCancelled()) {
-            refreshJob.cancel(true);
+        ScheduledFuture<?> job = refreshJob;
+        if (job != null && !job.isCancelled()) {
+            job.cancel(true);
             refreshJob = null;
         }
-        updateStatus(ThingStatus.OFFLINE);
-        if (bridgeHandler != null) {
-            bridgeHandler.unregisterDeviceStatusListener(this);
+        PulseaudioBridgeHandler briHandler = bridgeHandler;
+        if (briHandler != null) {
+            briHandler.unregisterDeviceStatusListener(this);
             bridgeHandler = null;
         }
         logger.trace("Thing {} {} disposed.", getThing().getUID(), name);
         super.dispose();
-        if (audioSink != null) {
-            audioSink.disconnect();
+        PulseAudioAudioSink sink = audioSink;
+        if (sink != null) {
+            sink.disconnect();
         }
-        if (audioSource != null) {
-            audioSource.disconnect();
+        PulseAudioAudioSource source = audioSource;
+        if (source != null) {
+            source.disconnect();
         }
         // Unregister the potential pulse audio sink's audio sink
         ServiceRegistration<AudioSink> sinkReg = audioSinkRegistrations.remove(getThing().getUID().toString());
@@ -213,20 +216,42 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
         }
     }
 
+    @Override
+    public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
+        if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE
+                && getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
+            // Bridge is now ONLINE, restart the refresh job to get an update of the thing status without waiting
+            // its next planned run
+            ScheduledFuture<?> job = refreshJob;
+            if (job != null && !job.isCancelled()) {
+                job.cancel(true);
+                refreshJob = null;
+            }
+            deviceOnlineWatchdog();
+        } else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE
+                || bridgeStatusInfo.getStatus() == ThingStatus.UNKNOWN) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
+        }
+    }
+
     private void deviceOnlineWatchdog() {
         Runnable runnable = () -> {
             try {
                 PulseaudioBridgeHandler bridgeHandler = getPulseaudioBridgeHandler();
                 if (bridgeHandler != null) {
-                    if (bridgeHandler.getDevice(name) == null) {
-                        updateStatus(ThingStatus.OFFLINE);
-                        this.bridgeHandler = null;
+                    if (bridgeHandler.getThing().getStatus() == ThingStatus.ONLINE) {
+                        if (bridgeHandler.getDevice(name) == null) {
+                            updateStatus(ThingStatus.OFFLINE);
+                            this.bridgeHandler = null;
+                        } else {
+                            updateStatus(ThingStatus.ONLINE);
+                        }
                     } else {
-                        updateStatus(ThingStatus.ONLINE);
+                        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
                     }
                 } else {
                     logger.debug("Bridge for pulseaudio device {} not found.", name);
-                    updateStatus(ThingStatus.OFFLINE);
+                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
                 }
             } catch (Exception e) {
                 logger.debug("Exception occurred during execution: {}", e.getMessage(), e);
@@ -258,17 +283,17 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
 
     @Override
     public void handleCommand(ChannelUID channelUID, Command command) {
-        PulseaudioBridgeHandler bridge = getPulseaudioBridgeHandler();
-        if (bridge == null) {
-            logger.warn("pulseaudio server bridge handler not found. Cannot handle command without bridge.");
+        PulseaudioBridgeHandler briHandler = getPulseaudioBridgeHandler();
+        if (briHandler == null) {
+            logger.debug("pulseaudio server bridge handler not found. Cannot handle command without bridge.");
             return;
         }
         if (command instanceof RefreshType) {
-            bridge.handleCommand(channelUID, command);
+            briHandler.handleCommand(channelUID, command);
             return;
         }
 
-        AbstractAudioDeviceConfig device = bridge.getDevice(name);
+        AbstractAudioDeviceConfig device = briHandler.getDevice(name);
         if (device == null) {
             logger.warn("device {} not found", name);
             updateStatus(ThingStatus.OFFLINE);
@@ -279,8 +304,8 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
             if (channelUID.getId().equals(VOLUME_CHANNEL)) {
                 if (command instanceof IncreaseDecreaseType) {
                     // refresh to get the current volume level
-                    bridge.getClient().update();
-                    device = bridge.getDevice(name);
+                    briHandler.getClient().update();
+                    device = briHandler.getDevice(name);
                     if (device == null) {
                         logger.warn("missing device info, aborting");
                         return;
@@ -293,24 +318,24 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
                     if (command.equals(IncreaseDecreaseType.DECREASE)) {
                         newVolume = Math.max(0, oldVolume - 5);
                     }
-                    bridge.getClient().setVolumePercent(device, newVolume);
+                    briHandler.getClient().setVolumePercent(device, newVolume);
                     updateState = new PercentType(newVolume);
                     savedVolume = newVolume;
                 } else if (command instanceof PercentType) {
                     DecimalType volume = (DecimalType) command;
-                    bridge.getClient().setVolumePercent(device, volume.intValue());
+                    briHandler.getClient().setVolumePercent(device, volume.intValue());
                     updateState = (PercentType) command;
                     savedVolume = volume.intValue();
                 } else if (command instanceof DecimalType) {
                     // set volume
                     DecimalType volume = (DecimalType) command;
-                    bridge.getClient().setVolume(device, volume.intValue());
+                    briHandler.getClient().setVolume(device, volume.intValue());
                     updateState = (DecimalType) command;
                     savedVolume = volume.intValue();
                 }
             } else if (channelUID.getId().equals(MUTE_CHANNEL)) {
                 if (command instanceof OnOffType) {
-                    bridge.getClient().setMute(device, OnOffType.ON.equals(command));
+                    briHandler.getClient().setMute(device, OnOffType.ON.equals(command));
                     updateState = (OnOffType) command;
                 }
             } else if (channelUID.getId().equals(SLAVES_CHANNEL)) {
@@ -318,32 +343,32 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
                     if (command instanceof StringType) {
                         List<Sink> slaves = new ArrayList<>();
                         for (String slaveName : command.toString().split(",")) {
-                            Sink slave = bridge.getClient().getSink(slaveName.trim());
+                            Sink slave = briHandler.getClient().getSink(slaveName.trim());
                             if (slave != null) {
                                 slaves.add(slave);
                             }
                         }
                         if (!slaves.isEmpty()) {
-                            bridge.getClient().setCombinedSinkSlaves(((Sink) device), slaves);
+                            briHandler.getClient().setCombinedSinkSlaves(((Sink) device), slaves);
                         }
                     }
                 } else {
-                    logger.error("{} is no combined sink", device);
+                    logger.warn("{} is no combined sink", device);
                 }
             } else if (channelUID.getId().equals(ROUTE_TO_SINK_CHANNEL)) {
                 if (device instanceof SinkInput) {
                     Sink newSink = null;
                     if (command instanceof DecimalType) {
-                        newSink = bridge.getClient().getSink(((DecimalType) command).intValue());
+                        newSink = briHandler.getClient().getSink(((DecimalType) command).intValue());
                     } else {
-                        newSink = bridge.getClient().getSink(command.toString());
+                        newSink = briHandler.getClient().getSink(command.toString());
                     }
                     if (newSink != null) {
                         logger.debug("rerouting {} to {}", device, newSink);
-                        bridge.getClient().moveSinkInput(((SinkInput) device), newSink);
+                        briHandler.getClient().moveSinkInput(((SinkInput) device), newSink);
                         updateState = new StringType(newSink.getPaName());
                     } else {
-                        logger.error("no sink {} found", command.toString());
+                        logger.warn("no sink {} found", command.toString());
                     }
                 }
             }
@@ -361,25 +386,31 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
      */
     public int getLastVolume() {
         if (savedVolume == null) {
-            PulseaudioBridgeHandler bridge = getPulseaudioBridgeHandler();
-            // refresh to get the current volume level
-            bridge.getClient().update();
-            AbstractAudioDeviceConfig device = bridge.getDevice(name);
-            if (device != null) {
-                savedVolume = device.getVolume();
+            PulseaudioBridgeHandler briHandler = getPulseaudioBridgeHandler();
+            if (briHandler != null) {
+                // refresh to get the current volume level
+                briHandler.getClient().update();
+                AbstractAudioDeviceConfig device = briHandler.getDevice(name);
+                if (device != null) {
+                    savedVolume = device.getVolume();
+                }
             }
         }
         return savedVolume == null ? 50 : savedVolume;
     }
 
     public void setVolume(int volume) {
-        PulseaudioBridgeHandler bridge = getPulseaudioBridgeHandler();
-        AbstractAudioDeviceConfig device = bridge.getDevice(name);
+        PulseaudioBridgeHandler briHandler = getPulseaudioBridgeHandler();
+        if (briHandler == null) {
+            logger.warn("bridge is not ready");
+            return;
+        }
+        AbstractAudioDeviceConfig device = briHandler.getDevice(name);
         if (device == null) {
             logger.warn("missing device info, aborting");
             return;
         }
-        bridge.getClient().setVolumePercent(device, volume);
+        briHandler.getClient().setVolumePercent(device, volume);
         updateState(VOLUME_CHANNEL, new PercentType(volume));
         savedVolume = volume;
     }
@@ -411,7 +442,7 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
         if (bridge != null) {
             return (String) bridge.getConfiguration().get(PulseaudioBindingConstants.BRIDGE_PARAMETER_HOST);
         } else {
-            logger.error("A bridge must be configured for this pulseaudio thing");
+            logger.warn("A bridge must be configured for this pulseaudio thing");
             return "null";
         }
     }
@@ -425,8 +456,11 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
      * @throws InterruptedException when interrupted during the loading module wait
      */
     public int getSimpleTcpPort() throws IOException, InterruptedException {
-        var bridgeHandler = getPulseaudioBridgeHandler();
-        AbstractAudioDeviceConfig device = bridgeHandler.getDevice(name);
+        var briHandler = getPulseaudioBridgeHandler();
+        if (briHandler == null) {
+            throw new IOException("bridge is not ready");
+        }
+        AbstractAudioDeviceConfig device = briHandler.getDevice(name);
         if (device == null) {
             throw new IOException("missing device info, device appears to be offline");
         }
@@ -439,7 +473,7 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
         BigDecimal simpleRate = (BigDecimal) getThing().getConfiguration().get(DEVICE_PARAMETER_AUDIO_SOURCE_RATE);
         BigDecimal simpleChannels = (BigDecimal) getThing().getConfiguration()
                 .get(DEVICE_PARAMETER_AUDIO_SOURCE_CHANNELS);
-        return getPulseaudioBridgeHandler().getClient()
+        return briHandler.getClient()
                 .loadModuleSimpleProtocolTcpIfNeeded(device, simpleTcpPort, simpleFormat, simpleRate, simpleChannels)
                 .orElse(simpleTcpPort);
     }