[pulseaudio] Allow flexible parameters to find a given pulseaudio device ()

* [pulseaudio] Allow flexible parameters to find a given pulseaudio device

To identify the device on the pulseaudio server, you can now use the description instead of the technical id (a.k.a. "name").
To filter furthermore, you can also use the parameter additionalFilters (optional regular expressions that need to match a property value of a device on the pulseaudio server)

Closes 

Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com>
This commit is contained in:
Gwendal Roulleau 2022-04-28 23:25:23 +02:00 committed by GitHub
parent 1408899ed3
commit ae20f93f19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 260 additions and 62 deletions

@ -38,8 +38,12 @@ binding.pulseaudio:sourceOutput=false
## Thing Configuration
The Pulseaudio bridge requires the host (ip address or a hostname) and a port (default: 4712) as a configuration value in order for the binding to know where to access it.
You can use `pactl -s <ip-address|hostname> list sinks | grep "name:"` to find the name of a sink.
The Pulseaudio bridge requires the host (ip address or a hostname) and a port (default: 4712) as a configuration value in order for the binding to know where to access it.
A Pulseaudio device requires at least an identifier. For sinks and sources, you can use the name or the description. For sink inputs and source outputs, you can use the name or the application name.
To know without hesitation the correct value to use, you should use the command line utility `pactl`. For example, to find the name of a sink:
`pactl -s <ip-address|hostname> list sinks | grep "name:"`
If you need to narrow the identification of a device (in case name or description are not consistent and sufficient), you can use the `additionalFilters` parameter (optional/advanced parameter), in the form of one or several (separator '###') regular expression(s), each one matching a property value of the pulseaudio device. You can use every properties listed with `pactl`.
## Channels
@ -74,7 +78,7 @@ This requires the module **module-simple-protocol-tcp** to be present on the tar
```
Bridge pulseaudio:bridge:<bridgname> "<Bridge Label>" @ "<Room>" [ host="<ipAddress>", port=4712 ] {
Things:
Thing sink multiroom "Snapcast" @ "Room" [name="alsa_card.pci-0000_00_1f.3", activateSimpleProtocolSink=true, simpleProtocolSinkPort=4711] // the name corresponds to `pactl list sinks` output
Thing sink multiroom "Snapcast" @ "Room" [name="alsa_card.pci-0000_00_1f.3", activateSimpleProtocolSink=true, simpleProtocolSinkPort=4711, additionalFilters="analog-stereo###internal"]
Thing source microphone "microphone" @ "Room" [name="alsa_input.pci-0000_00_14.2.analog-stereo"]
Thing sink-input openhabTTS "OH-Voice" @ "Room" [name="alsa_output.pci-0000_00_1f.3.hdmi-stereo-extra1"]
Thing source-output remotePulseSink "Other Room Speaker" @ "Other Room" [name="alsa_input.pci-0000_00_14.2.analog-stereo"]

@ -48,7 +48,8 @@ public class PulseaudioBindingConstants {
public static final String BRIDGE_PARAMETER_PORT = "port";
public static final String BRIDGE_PARAMETER_REFRESH_INTERVAL = "refresh";
public static final String DEVICE_PARAMETER_NAME = "name";
public static final String DEVICE_PARAMETER_NAME_OR_DESCRIPTION = "name";
public static final String DEVICE_PARAMETER_ADDITIONAL_FILTERS = "additionalFilters";
public static final String DEVICE_PARAMETER_AUDIO_SINK_ACTIVATION = "activateSimpleProtocolSink";
public static final String DEVICE_PARAMETER_AUDIO_SINK_PORT = "simpleProtocolSinkPort";
public static final String DEVICE_PARAMETER_AUDIO_SINK_IDLE_TIMEOUT = "simpleProtocolSinkIdleTimeout";

@ -23,13 +23,16 @@ import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pulseaudio.internal.cli.Parser;
import org.openhab.binding.pulseaudio.internal.handler.DeviceIdentifier;
import org.openhab.binding.pulseaudio.internal.items.AbstractAudioDeviceConfig;
import org.openhab.binding.pulseaudio.internal.items.AbstractAudioDeviceConfig.State;
import org.openhab.binding.pulseaudio.internal.items.Module;
@ -258,15 +261,23 @@ public class PulseaudioClient {
}
/**
* retrieves a {@link AbstractAudioDeviceConfig} by its name
* retrieves a {@link AbstractAudioDeviceConfig} by its identifier
* If several devices correspond to the deviceIdentifier, returns the first one (aphabetical order)
*
* @param The device identifier to match against
* @return the corresponding {@link AbstractAudioDeviceConfig} to the given <code>name</code>
*/
public @Nullable AbstractAudioDeviceConfig getGenericAudioItem(String name) {
for (AbstractAudioDeviceConfig item : items) {
if (item.getPaName().equalsIgnoreCase(name)) {
return item;
}
public @Nullable AbstractAudioDeviceConfig getGenericAudioItem(DeviceIdentifier deviceIdentifier) {
List<AbstractAudioDeviceConfig> matchingDevices = items.stream()
.filter(device -> device.matches(deviceIdentifier))
.sorted(Comparator.comparing(AbstractAudioDeviceConfig::getPaName)).collect(Collectors.toList());
if (matchingDevices.size() == 1) {
return matchingDevices.get(0);
} else if (matchingDevices.size() > 1) {
logger.debug(
"Cannot select exactly one audio device, so choosing the first. To choose without ambiguity between the {} devices matching the identifier {}, you can maybe use a more restrictive 'additionalFilter' parameter",
matchingDevices.size(), deviceIdentifier.getNameOrDescription());
return matchingDevices.get(0);
}
return null;
}

@ -89,7 +89,7 @@ public class PulseaudioHandlerFactory extends BaseThingHandlerFactory {
private ThingUID getPulseaudioDeviceUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID,
Configuration configuration, @Nullable ThingUID bridgeUID) {
if (thingUID == null) {
String name = (String) configuration.get(PulseaudioBindingConstants.DEVICE_PARAMETER_NAME);
String name = (String) configuration.get(PulseaudioBindingConstants.DEVICE_PARAMETER_NAME_OR_DESCRIPTION);
return new ThingUID(thingTypeUID, name, bridgeUID == null ? null : bridgeUID.getId());
}
return thingUID;
@ -101,7 +101,9 @@ public class PulseaudioHandlerFactory extends BaseThingHandlerFactory {
if (serviceRegistration != null) {
PulseaudioDeviceDiscoveryService service = (PulseaudioDeviceDiscoveryService) bundleContext
.getService(serviceRegistration.getReference());
service.deactivate();
if (service != null) {
service.deactivate();
}
serviceRegistration.unregister();
}
discoveryServiceReg.remove(thingHandler);

@ -128,7 +128,7 @@ public class Parser {
}
}
if (properties.containsKey("name")) {
Sink sink = new Sink(id, properties.get("name"),
Sink sink = new Sink(id, properties.get("name"), properties.get("device.description"), properties,
client.getModule(getNumberValue(properties.get("module"))));
if (properties.containsKey("state")) {
try {
@ -198,7 +198,8 @@ public class Parser {
if (properties.containsKey("sink")) {
String name = properties.containsKey("media.name") ? properties.get("media.name")
: properties.get("sink");
SinkInput item = new SinkInput(id, name, client.getModule(getNumberValue(properties.get("module"))));
SinkInput item = new SinkInput(id, name, properties.get("application.name"), properties,
client.getModule(getNumberValue(properties.get("module"))));
if (properties.containsKey("state")) {
try {
item.setState(AbstractAudioDeviceConfig.State.valueOf(properties.get("state")));
@ -256,7 +257,7 @@ public class Parser {
}
}
if (properties.containsKey("name")) {
Source source = new Source(id, properties.get("name"),
Source source = new Source(id, properties.get("name"), properties.get("device.description"), properties,
client.getModule(getNumberValue(properties.get("module"))));
if (properties.containsKey("state")) {
try {
@ -316,8 +317,8 @@ public class Parser {
}
}
if (properties.containsKey("source")) {
SourceOutput item = new SourceOutput(id, properties.get("source"),
client.getModule(getNumberValue(properties.get("module"))));
SourceOutput item = new SourceOutput(id, properties.get("source"), properties.get("application.name"),
properties, client.getModule(getNumberValue(properties.get("module"))));
if (properties.containsKey("state")) {
try {
item.setState(AbstractAudioDeviceConfig.State.valueOf(properties.get("state")));

@ -13,12 +13,14 @@
package org.openhab.binding.pulseaudio.internal.discovery;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.regex.PatternSyntaxException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pulseaudio.internal.PulseaudioBindingConstants;
import org.openhab.binding.pulseaudio.internal.handler.DeviceIdentifier;
import org.openhab.binding.pulseaudio.internal.handler.DeviceStatusListener;
import org.openhab.binding.pulseaudio.internal.handler.PulseaudioBridgeHandler;
import org.openhab.binding.pulseaudio.internal.handler.PulseaudioHandler;
@ -27,6 +29,7 @@ import org.openhab.binding.pulseaudio.internal.items.Sink;
import org.openhab.binding.pulseaudio.internal.items.SinkInput;
import org.openhab.binding.pulseaudio.internal.items.Source;
import org.openhab.binding.pulseaudio.internal.items.SourceOutput;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
@ -70,7 +73,7 @@ public class PulseaudioDeviceDiscoveryService extends AbstractDiscoveryService i
@Override
public void onDeviceAdded(Thing bridge, AbstractAudioDeviceConfig device) {
if (getAlreadyConfiguredThings().contains(device.getPaName())) {
if (getAlreadyConfiguredThings().stream().anyMatch(deviceIdentifier -> device.matches(deviceIdentifier))) {
return;
}
@ -79,7 +82,7 @@ public class PulseaudioDeviceDiscoveryService extends AbstractDiscoveryService i
ThingTypeUID thingType = null;
Map<String, Object> properties = new HashMap<>();
// All devices need this parameter
properties.put(PulseaudioBindingConstants.DEVICE_PARAMETER_NAME, uidName);
properties.put(PulseaudioBindingConstants.DEVICE_PARAMETER_NAME_OR_DESCRIPTION, uidName);
if (device instanceof Sink) {
if (((Sink) device).isCombinedSink()) {
thingType = PulseaudioBindingConstants.COMBINED_SINK_THING_TYPE;
@ -104,10 +107,20 @@ public class PulseaudioDeviceDiscoveryService extends AbstractDiscoveryService i
}
}
public Set<String> getAlreadyConfiguredThings() {
return pulseaudioBridgeHandler.getThing().getThings().stream().map(Thing::getConfiguration)
.map(conf -> (String) conf.get(PulseaudioBindingConstants.DEVICE_PARAMETER_NAME))
.collect(Collectors.toSet());
public Set<DeviceIdentifier> getAlreadyConfiguredThings() {
Set<DeviceIdentifier> alreadyConfiguredThings = new HashSet<>();
for (Thing thing : pulseaudioBridgeHandler.getThing().getThings()) {
Configuration configuration = thing.getConfiguration();
try {
alreadyConfiguredThings.add(new DeviceIdentifier(
(String) configuration.get(PulseaudioBindingConstants.DEVICE_PARAMETER_NAME_OR_DESCRIPTION),
(String) configuration.get(PulseaudioBindingConstants.DEVICE_PARAMETER_ADDITIONAL_FILTERS)));
} catch (PatternSyntaxException p) {
logger.debug(
"There is an error with an already configured things. Cannot compare with discovery, skipping it");
}
}
return alreadyConfiguredThings;
}
@Override

@ -0,0 +1,63 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pulseaudio.internal.handler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* All informations needed to precisely identify a device
*
* @author Gwendal Roulleau - Initial contribution
*
*/
@NonNullByDefault
public class DeviceIdentifier {
private String nameOrDescription;
private List<Pattern> additionalFilters = new ArrayList<>();
public DeviceIdentifier(String nameOrDescription, @Nullable String additionalFilters)
throws PatternSyntaxException {
super();
this.nameOrDescription = nameOrDescription;
if (additionalFilters != null && !additionalFilters.isEmpty()) {
Arrays.asList(additionalFilters.split("###")).stream()
.forEach(ad -> this.additionalFilters.add(Pattern.compile(ad)));
}
}
public String getNameOrDescription() {
return nameOrDescription;
}
public List<Pattern> getAdditionalFilters() {
return additionalFilters;
}
@Override
public String toString() {
List<Pattern> additionalFiltersFinal = additionalFilters;
String additionalPatternToString = additionalFiltersFinal.stream().map(Pattern::pattern)
.collect(Collectors.joining("###"));
return "DeviceIdentifier [nameOrDescription=" + nameOrDescription + ", additionalFilter="
+ additionalPatternToString + "]";
}
}

@ -97,7 +97,7 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA
} else {
// browse all child handlers to update status according to the result of the query to the pulse audio server
for (PulseaudioHandler pulseaudioHandler : childHandlersInitialized) {
pulseaudioHandler.deviceUpdate(getDevice(pulseaudioHandler.getName()));
pulseaudioHandler.deviceUpdate(getDevice(pulseaudioHandler.getDeviceIdentifier()));
}
}
// browse query result to notify add event
@ -129,8 +129,8 @@ public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseA
}
}
public @Nullable AbstractAudioDeviceConfig getDevice(String name) {
return getClient().getGenericAudioItem(name);
public @Nullable AbstractAudioDeviceConfig getDevice(@Nullable DeviceIdentifier deviceIdentifier) {
return deviceIdentifier == null ? null : getClient().getGenericAudioItem(deviceIdentifier);
}
public PulseaudioClient getClient() {

@ -24,6 +24,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -78,7 +79,7 @@ public class PulseaudioHandler extends BaseThingHandler {
SOURCE_THING_TYPE, SOURCE_OUTPUT_THING_TYPE).collect(Collectors.toSet()));
private final Logger logger = LoggerFactory.getLogger(PulseaudioHandler.class);
private String name = "";
private @Nullable DeviceIdentifier deviceIdentifier;
private @Nullable PulseAudioAudioSink audioSink;
private @Nullable PulseAudioAudioSource audioSource;
private @Nullable Integer savedVolume;
@ -96,12 +97,20 @@ public class PulseaudioHandler extends BaseThingHandler {
@Override
public void initialize() {
Configuration config = getThing().getConfiguration();
name = (String) config.get(DEVICE_PARAMETER_NAME);
try {
deviceIdentifier = new DeviceIdentifier((String) config.get(DEVICE_PARAMETER_NAME_OR_DESCRIPTION),
(String) config.get(DEVICE_PARAMETER_ADDITIONAL_FILTERS));
} catch (PatternSyntaxException p) {
deviceIdentifier = null;
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Incorrect regular expression: " + (String) config.get(DEVICE_PARAMETER_ADDITIONAL_FILTERS));
return;
}
initializeWithTheBridge();
}
public String getName() {
return name;
public @Nullable DeviceIdentifier getDeviceIdentifier() {
return deviceIdentifier;
}
private void audioSinkSetup() {
@ -214,7 +223,7 @@ public class PulseaudioHandler extends BaseThingHandler {
@Override
public void dispose() {
logger.trace("Thing {} {} disposed.", getThing().getUID(), name);
logger.trace("Thing {} {} disposed.", getThing().getUID(), safeGetDeviceNameOrDescription());
super.dispose();
audioSinkUnsetup();
audioSourceUnsetup();
@ -232,21 +241,22 @@ public class PulseaudioHandler extends BaseThingHandler {
} else if (pulseaudioBridgeHandler.getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
} else {
deviceUpdate(pulseaudioBridgeHandler.getDevice(name));
deviceUpdate(pulseaudioBridgeHandler.getDevice(deviceIdentifier));
}
}
private synchronized @Nullable PulseaudioBridgeHandler getPulseaudioBridgeHandler() {
Bridge bridge = getBridge();
if (bridge == null) {
logger.debug("Required bridge not defined for device {}.", name);
logger.debug("Required bridge not defined for device {}.", safeGetDeviceNameOrDescription());
return null;
}
ThingHandler handler = bridge.getHandler();
if (handler instanceof PulseaudioBridgeHandler) {
return (PulseaudioBridgeHandler) handler;
} else {
logger.debug("No available bridge handler found for device {} bridge {} .", name, bridge.getUID());
logger.debug("No available bridge handler found for device {} bridge {} .",
safeGetDeviceNameOrDescription(), bridge.getUID());
return null;
}
}
@ -263,9 +273,9 @@ public class PulseaudioHandler extends BaseThingHandler {
return;
}
AbstractAudioDeviceConfig device = briHandler.getDevice(name);
AbstractAudioDeviceConfig device = briHandler.getDevice(deviceIdentifier);
if (device == null) {
logger.warn("device {} not found", name);
logger.warn("device {} not found", safeGetDeviceNameOrDescription());
deviceUpdate(null);
return;
} else {
@ -274,7 +284,7 @@ public class PulseaudioHandler extends BaseThingHandler {
if (command instanceof IncreaseDecreaseType) {
// refresh to get the current volume level
briHandler.getClient().update();
device = briHandler.getDevice(name);
device = briHandler.getDevice(deviceIdentifier);
if (device == null) {
logger.warn("missing device info, aborting");
return;
@ -360,7 +370,7 @@ public class PulseaudioHandler extends BaseThingHandler {
if (briHandler != null) {
// refresh to get the current volume level
briHandler.getClient().update();
AbstractAudioDeviceConfig device = briHandler.getDevice(name);
AbstractAudioDeviceConfig device = briHandler.getDevice(deviceIdentifier);
if (device != null) {
savedVolume = savedVolumeFinal = device.getVolume();
}
@ -375,7 +385,7 @@ public class PulseaudioHandler extends BaseThingHandler {
logger.warn("bridge is not ready");
return;
}
AbstractAudioDeviceConfig device = briHandler.getDevice(name);
AbstractAudioDeviceConfig device = briHandler.getDevice(deviceIdentifier);
if (device == null) {
logger.warn("missing device info, aborting");
return;
@ -386,7 +396,7 @@ public class PulseaudioHandler extends BaseThingHandler {
}
public void deviceUpdate(@Nullable AbstractAudioDeviceConfig device) {
if (device != null && device.getPaName().equals(name)) {
if (device != null) {
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
logger.debug("Updating states of {} id: {}", device, VOLUME_CHANNEL);
int actualVolume = device.getVolume();
@ -404,7 +414,7 @@ public class PulseaudioHandler extends BaseThingHandler {
}
audioSinkSetup();
audioSourceSetup();
} else if (device == null) {
} else {
updateState(VOLUME_CHANNEL, UnDefType.UNDEF);
updateState(MUTE_CHANNEL, UnDefType.UNDEF);
updateState(STATE_CHANNEL, UnDefType.UNDEF);
@ -443,9 +453,10 @@ public class PulseaudioHandler extends BaseThingHandler {
if (briHandler == null) {
throw new IOException("bridge is not ready");
}
AbstractAudioDeviceConfig device = briHandler.getDevice(name);
AbstractAudioDeviceConfig device = briHandler.getDevice(deviceIdentifier);
if (device == null) {
throw new IOException("missing device info, device appears to be offline");
throw new IOException(
"missing device info, device " + safeGetDeviceNameOrDescription() + " appears to be offline");
}
String simpleTcpPortPrefName = (device instanceof Source) ? DEVICE_PARAMETER_AUDIO_SOURCE_PORT
: DEVICE_PARAMETER_AUDIO_SINK_PORT;
@ -501,7 +512,7 @@ public class PulseaudioHandler extends BaseThingHandler {
var idleTimeout = 3000;
var handler = getPulseaudioBridgeHandler();
if (handler != null) {
AbstractAudioDeviceConfig device = handler.getDevice(name);
AbstractAudioDeviceConfig device = handler.getDevice(deviceIdentifier);
String idleTimeoutPropName = (device instanceof Source) ? DEVICE_PARAMETER_AUDIO_SOURCE_IDLE_TIMEOUT
: DEVICE_PARAMETER_AUDIO_SINK_IDLE_TIMEOUT;
var idleTimeoutB = (BigDecimal) getThing().getConfiguration().get(idleTimeoutPropName);
@ -512,6 +523,11 @@ public class PulseaudioHandler extends BaseThingHandler {
return idleTimeout;
}
private String safeGetDeviceNameOrDescription() {
DeviceIdentifier deviceIdentifierFinal = deviceIdentifier;
return deviceIdentifierFinal == null ? "UNKNOWN" : deviceIdentifierFinal.getNameOrDescription();
}
public int getBasicProtocolSOTimeout() {
var soTimeout = (BigDecimal) getThing().getConfiguration().get(DEVICE_PARAMETER_AUDIO_SOCKET_SO_TIMEOUT);
return soTimeout != null ? soTimeout.intValue() : 500;

@ -12,8 +12,13 @@
*/
package org.openhab.binding.pulseaudio.internal.items;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pulseaudio.internal.handler.DeviceIdentifier;
/**
* GenericAudioItems are any kind of items that deal with audio data and can be
@ -36,10 +41,40 @@ public abstract class AbstractAudioDeviceConfig extends AbstractDeviceConfig {
protected boolean muted;
protected int volume;
protected @Nullable Module module;
protected String secondaryIdentifier;
protected Map<String, String> properties;
public AbstractAudioDeviceConfig(int id, String name, @Nullable Module module) {
public AbstractAudioDeviceConfig(int id, String name, @Nullable String secondaryIdentifier,
Map<String, String> properties, @Nullable Module module) {
super(id, name);
this.module = module;
this.secondaryIdentifier = secondaryIdentifier == null ? "" : secondaryIdentifier;
this.properties = properties;
}
/**
*
* @param deviceIdentifier The device identifier to check against
* @return true if this device match the requested identifier, false otherwise
*/
public boolean matches(DeviceIdentifier deviceIdentifier) {
boolean matches = getPaName().equalsIgnoreCase(deviceIdentifier.getNameOrDescription())
|| secondaryIdentifier.equalsIgnoreCase(deviceIdentifier.getNameOrDescription());
if (!matches) {
return false; // stop analysis right here, no need to parse properties
} else {
List<Pattern> additionalFilters = deviceIdentifier.getAdditionalFilters();
if (additionalFilters.isEmpty()) { // the additionalFilter property is not defined, don't check against
return true;
} else {
for (Pattern patternToMatch : additionalFilters) {
if (!properties.values().stream().anyMatch(value -> patternToMatch.matcher(value).find())) {
return false;
}
}
return true;
}
}
}
public @Nullable Module getModule() {

@ -14,6 +14,7 @@ package org.openhab.binding.pulseaudio.internal.items;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@ -31,8 +32,8 @@ public class Sink extends AbstractAudioDeviceConfig {
protected List<String> combinedSinkNames;
protected List<Sink> combinedSinks;
public Sink(int id, String name, @Nullable Module module) {
super(id, name, module);
public Sink(int id, String name, String description, Map<String, String> properties, @Nullable Module module) {
super(id, name, description, properties, module);
combinedSinkNames = new ArrayList<>();
combinedSinks = new ArrayList<>();
}

@ -12,6 +12,8 @@
*/
package org.openhab.binding.pulseaudio.internal.items;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@ -26,8 +28,8 @@ public class SinkInput extends AbstractAudioDeviceConfig {
@Nullable
private Sink sink;
public SinkInput(int id, String name, @Nullable Module module) {
super(id, name, module);
public SinkInput(int id, String name, String description, Map<String, String> properties, @Nullable Module module) {
super(id, name, description, properties, module);
}
public @Nullable Sink getSink() {

@ -12,6 +12,8 @@
*/
package org.openhab.binding.pulseaudio.internal.items;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@ -27,8 +29,8 @@ public class Source extends AbstractAudioDeviceConfig {
@Nullable
protected Sink monitorOf;
public Source(int id, String name, @Nullable Module module) {
super(id, name, module);
public Source(int id, String name, String description, Map<String, String> properties, @Nullable Module module) {
super(id, name, description, properties, module);
}
public @Nullable Sink getMonitorOf() {

@ -12,6 +12,8 @@
*/
package org.openhab.binding.pulseaudio.internal.items;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@ -26,8 +28,9 @@ public class SourceOutput extends AbstractAudioDeviceConfig {
@Nullable
private Source source;
public SourceOutput(int id, String name, @Nullable Module module) {
super(id, name, module);
public SourceOutput(int id, String name, String description, Map<String, String> properties,
@Nullable Module module) {
super(id, name, description, properties, module);
}
public @Nullable Source getSource() {

@ -42,7 +42,9 @@ thing-type.config.pulseaudio.combinedSink.name.description = The name of the com
thing-type.config.pulseaudio.sink.activateSimpleProtocolSink.label = Create an Audio Sink with simple-protocol-tcp
thing-type.config.pulseaudio.sink.activateSimpleProtocolSink.description = Activation of a corresponding sink in OpenHAB (module-simple-protocol-tcp must be available on the pulseaudio server)
thing-type.config.pulseaudio.sink.name.label = Name
thing-type.config.pulseaudio.sink.name.description = The name of one specific device.
thing-type.config.pulseaudio.sink.name.description = The name of one specific device. You can also use the description.
thing-type.config.pulseaudio.sink.additionalFilters.label = Additional Filters
thing-type.config.pulseaudio.sink.additionalFilters.description = Additional filters to select the proper device on the pulseaudio server, in case of ambiguity. To be selected, the device should have at least a property value matching this regular expression. You can use multiple regular expressions (separator is ###).
thing-type.config.pulseaudio.sink.simpleProtocolSOTimeout.label = Simple Protocol SO Timeout
thing-type.config.pulseaudio.sink.simpleProtocolSOTimeout.description = Socket SO timeout when connecting to pulseaudio server though module-simple-protocol-tcp. You can tune this option if the socket disconnect frequently.
thing-type.config.pulseaudio.sink.simpleProtocolSinkIdleTimeout.label = Idle Timeout
@ -50,11 +52,15 @@ thing-type.config.pulseaudio.sink.simpleProtocolSinkIdleTimeout.description = Ti
thing-type.config.pulseaudio.sink.simpleProtocolSinkPort.label = Simple Protocol Port
thing-type.config.pulseaudio.sink.simpleProtocolSinkPort.description = Default Port to allocate for use by module-simple-protocol-tcp on the pulseaudio server
thing-type.config.pulseaudio.sinkInput.name.label = Name
thing-type.config.pulseaudio.sinkInput.name.description = The name of one specific device.
thing-type.config.pulseaudio.sinkInput.name.description = The name of one specific device. You can also use the application name.
thing-type.config.pulseaudio.sinkInput.additionalFilters.label = Additional Filters
thing-type.config.pulseaudio.sinkInput.additionalFilters.description = Additional filters to select the proper device on the pulseaudio server, in case of ambiguity. To be selected, the device should have at least a property value matching this regular expression. You can use multiple regular expressions (separator is ###).
thing-type.config.pulseaudio.source.activateSimpleProtocolSource.label = Create an Audio Source with simple-protocol-tcp
thing-type.config.pulseaudio.source.activateSimpleProtocolSource.description = Activation of a corresponding source in OpenHAB (module-simple-protocol-tcp must be available on the pulseaudio server)
thing-type.config.pulseaudio.source.name.label = Name
thing-type.config.pulseaudio.source.name.description = The name of one specific device.
thing-type.config.pulseaudio.source.name.description = The name of one specific device. You can also use the description.
thing-type.config.pulseaudio.source.additionalFilters.label = Additional Filters
thing-type.config.pulseaudio.source.additionalFilters.description = Additional filters to select the proper device on the pulseaudio server, in case of ambiguity. To be selected, the device should have at least a property value matching this regular expression. You can use multiple regular expressions (separator is ###).
thing-type.config.pulseaudio.source.simpleProtocolSOTimeout.label = Simple Protocol SO Timeout
thing-type.config.pulseaudio.source.simpleProtocolSOTimeout.description = Socket SO timeout when connecting to pulseaudio server though module-simple-protocol-tcp. You can tune this option if the socket disconnect frequently.
thing-type.config.pulseaudio.source.simpleProtocolSourceChannels.label = Simple Protocol Channels
@ -75,7 +81,9 @@ thing-type.config.pulseaudio.source.simpleProtocolSourcePort.description = Defau
thing-type.config.pulseaudio.source.simpleProtocolSourceRate.label = Simple Protocol Rate
thing-type.config.pulseaudio.source.simpleProtocolSourceRate.description = The audio sample rate to be used by module-simple-protocol-tcp on the pulseaudio server
thing-type.config.pulseaudio.sourceOutput.name.label = Name
thing-type.config.pulseaudio.sourceOutput.name.description = The name of one specific device.
thing-type.config.pulseaudio.sourceOutput.name.description = The name of one specific device. You can also use the application name.
thing-type.config.pulseaudio.sourceOutput.additionalFilters.label = Additional Filters
thing-type.config.pulseaudio.sourceOutput.additionalFilters.description = Additional filters to select the proper device on the pulseaudio server, in case of ambiguity. To be selected, the device should have at least a property value matching this regular expression. You can use multiple regular expressions (separator is ###).
# channel types

@ -20,7 +20,16 @@
<config-description>
<parameter name="name" type="text" required="true">
<label>Name</label>
<description>The name of one specific device.</description>
<description>The name of one specific device. You can also use the application name.</description>
</parameter>
<parameter name="additionalFilters" type="text" required="false">
<label>Additional Filters</label>
<advanced>true</advanced>
<description>Additional filters to select the proper device on the pulseaudio server, in case of ambiguity.
To be
selected, the device should have at least a property value matching this regular expression. You can use
multiple
regular expressions (separator ###).</description>
</parameter>
</config-description>
</thing-type>

@ -20,7 +20,7 @@
<config-description>
<parameter name="name" type="text" required="true">
<label>Name</label>
<description>The name of one specific device.</description>
<description>The name of one specific device. You can also use the description.</description>
</parameter>
<parameter name="activateSimpleProtocolSink" type="boolean" required="false">
<label>Create an Audio Sink with simple-protocol-tcp</label>
@ -28,6 +28,15 @@
pulseaudio server)</description>
<default>false</default>
</parameter>
<parameter name="additionalFilters" type="text" required="false">
<label>Additional Filters</label>
<advanced>true</advanced>
<description>Additional filters to select the proper device on the pulseaudio server, in case of ambiguity.
To be
selected, the device should have at least a property value matching this regular expression. You can use
multiple
regular expressions (separator ###).</description>
</parameter>
<parameter name="simpleProtocolSinkPort" type="integer" required="false">
<label>Simple Protocol Port</label>
<description>Default Port to allocate for use by module-simple-protocol-tcp on the pulseaudio server</description>

@ -19,7 +19,16 @@
<config-description>
<parameter name="name" type="text" required="true">
<label>Name</label>
<description>The name of one specific device.</description>
<description>The name of one specific device. You can also use the application name.</description>
</parameter>
<parameter name="additionalFilters" type="text" required="false">
<label>Additional Filters</label>
<advanced>true</advanced>
<description>Additional filters to select the proper device on the pulseaudio server, in case of ambiguity.
To be
selected, the device should have at least a property value matching this regular expression. You can use
multiple
regular expressions (separator is ###).</description>
</parameter>
</config-description>
</thing-type>

@ -19,7 +19,16 @@
<config-description>
<parameter name="name" type="text" required="true">
<label>Name</label>
<description>The name of one specific device.</description>
<description>The name of one specific device. You can also use the description.</description>
</parameter>
<parameter name="additionalFilters" type="text" required="false">
<label>Additional Filters</label>
<advanced>true</advanced>
<description>Additional filters to select the proper device on the pulseaudio server, in case of ambiguity.
To be
selected, the device should have at least a property value matching this regular expression. You can use
multiple
regular expressions (separator ###).</description>
</parameter>
<parameter name="activateSimpleProtocolSource" type="boolean" required="false">
<label>Create an Audio Source with simple-protocol-tcp</label>