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;
*/
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;
}
/**
* 0 - 65536)
*/
public void setVolume(AbstractAudioDeviceConfig item, int vol) {
- if (item == null) {
- return;
- }
String itemCommandName = getItemCommandName(item);
if (itemCommandName == null) {
return;
.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) {
* @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);
}
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);
}
}
}
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];
} 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;
+ }
}
}
* 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);
}
}
}
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;
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())) {
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 {
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());
}
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);
}
}
}
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);
@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();
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;
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();
}
@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());
}
}
+ @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);
@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);
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;
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)) {
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());
}
}
}
*/
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;
}
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";
}
}
* @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");
}
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);
}