Use voluptuous for PulseAudio Loopback (#3160)
* Migrate to voluptuous * Fix conf var
This commit is contained in:
parent
f55095df83
commit
abff2f2b36
1 changed files with 60 additions and 59 deletions
|
@ -9,69 +9,75 @@ import re
|
||||||
import socket
|
import socket
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
from homeassistant.components.switch import SwitchDevice
|
from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA)
|
||||||
from homeassistant.util import convert
|
from homeassistant.const import (CONF_NAME, CONF_HOST, CONF_PORT)
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
_PULSEAUDIO_SERVERS = {}
|
_PULSEAUDIO_SERVERS = {}
|
||||||
|
|
||||||
DEFAULT_NAME = "paloopback"
|
CONF_BUFFER_SIZE = 'buffer_size'
|
||||||
DEFAULT_HOST = "localhost"
|
CONF_SINK_NAME = 'sink_name'
|
||||||
DEFAULT_PORT = 4712
|
CONF_SOURCE_NAME = 'source_name'
|
||||||
DEFAULT_BUFFER_SIZE = 1024
|
CONF_TCP_TIMEOUT = 'tcp_timeout'
|
||||||
DEFAULT_TCP_TIMEOUT = 3
|
|
||||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
|
||||||
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
|
|
||||||
|
|
||||||
LOAD_CMD = "load-module module-loopback sink={0} source={1}"
|
DEFAULT_BUFFER_SIZE = 1024
|
||||||
UNLOAD_CMD = "unload-module {0}"
|
DEFAULT_HOST = 'localhost'
|
||||||
MOD_REGEX = r"index: ([0-9]+)\s+name: <module-loopback>" \
|
DEFAULT_NAME = 'paloopback'
|
||||||
r"\s+argument: (?=<.*sink={0}.*>)(?=<.*source={1}.*>)"
|
DEFAULT_PORT = 4712
|
||||||
|
DEFAULT_TCP_TIMEOUT = 3
|
||||||
|
|
||||||
IGNORED_SWITCH_WARN = "Switch is already in the desired state. Ignoring."
|
IGNORED_SWITCH_WARN = "Switch is already in the desired state. Ignoring."
|
||||||
|
|
||||||
|
LOAD_CMD = "load-module module-loopback sink={0} source={1}"
|
||||||
|
|
||||||
|
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
|
||||||
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||||
|
MOD_REGEX = r"index: ([0-9]+)\s+name: <module-loopback>" \
|
||||||
|
r"\s+argument: (?=<.*sink={0}.*>)(?=<.*source={1}.*>)"
|
||||||
|
|
||||||
|
UNLOAD_CMD = "unload-module {0}"
|
||||||
|
|
||||||
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
|
vol.Required(CONF_SINK_NAME): cv.string,
|
||||||
|
vol.Required(CONF_SOURCE_NAME): cv.string,
|
||||||
|
vol.Optional(CONF_BUFFER_SIZE, default=DEFAULT_BUFFER_SIZE):
|
||||||
|
cv.positive_int,
|
||||||
|
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
|
||||||
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||||
|
vol.Optional(CONF_TCP_TIMEOUT, default=DEFAULT_TCP_TIMEOUT):
|
||||||
|
cv.positive_int,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Read in all of our configuration, and initialize the loopback switch."""
|
"""Read in all of our configuration, and initialize the loopback switch."""
|
||||||
if config.get('sink_name') is None:
|
name = config.get(CONF_NAME)
|
||||||
_LOGGER.error("Missing required variable: sink_name")
|
sink_name = config.get(CONF_SINK_NAME)
|
||||||
return False
|
source_name = config.get(CONF_SOURCE_NAME)
|
||||||
|
host = config.get(CONF_HOST)
|
||||||
if config.get('source_name') is None:
|
port = config.get(CONF_PORT)
|
||||||
_LOGGER.error("Missing required variable: source_name")
|
buffer_size = config.get(CONF_BUFFER_SIZE)
|
||||||
return False
|
tcp_timeout = config.get(CONF_TCP_TIMEOUT)
|
||||||
|
|
||||||
name = convert(config.get('name'), str, DEFAULT_NAME)
|
|
||||||
sink_name = config.get('sink_name')
|
|
||||||
source_name = config.get('source_name')
|
|
||||||
host = convert(config.get('host'), str, DEFAULT_HOST)
|
|
||||||
port = convert(config.get('port'), int, DEFAULT_PORT)
|
|
||||||
buffer_size = convert(config.get('buffer_size'), int, DEFAULT_BUFFER_SIZE)
|
|
||||||
tcp_timeout = convert(config.get('tcp_timeout'), int, DEFAULT_TCP_TIMEOUT)
|
|
||||||
|
|
||||||
server_id = str.format("{0}:{1}", host, port)
|
server_id = str.format("{0}:{1}", host, port)
|
||||||
|
|
||||||
if server_id in _PULSEAUDIO_SERVERS:
|
if server_id in _PULSEAUDIO_SERVERS:
|
||||||
server = _PULSEAUDIO_SERVERS[server_id]
|
server = _PULSEAUDIO_SERVERS[server_id]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
server = PAServer(host, port, buffer_size, tcp_timeout)
|
server = PAServer(host, port, buffer_size, tcp_timeout)
|
||||||
|
|
||||||
_PULSEAUDIO_SERVERS[server_id] = server
|
_PULSEAUDIO_SERVERS[server_id] = server
|
||||||
|
|
||||||
add_devices_callback([PALoopbackSwitch(
|
add_devices([PALoopbackSwitch(hass, name, server, sink_name, source_name)])
|
||||||
hass,
|
|
||||||
name,
|
|
||||||
server,
|
|
||||||
sink_name,
|
|
||||||
source_name
|
|
||||||
)])
|
|
||||||
|
|
||||||
|
|
||||||
class PAServer():
|
class PAServer():
|
||||||
"""Represents a pulseaudio server."""
|
"""Representation of a Pulseaudio server."""
|
||||||
|
|
||||||
_current_module_state = ""
|
_current_module_state = ""
|
||||||
|
|
||||||
|
@ -88,11 +94,11 @@ class PAServer():
|
||||||
sock.settimeout(self._tcp_timeout)
|
sock.settimeout(self._tcp_timeout)
|
||||||
try:
|
try:
|
||||||
sock.connect((self._pa_host, self._pa_port))
|
sock.connect((self._pa_host, self._pa_port))
|
||||||
_LOGGER.info("Calling pulseaudio:" + cmd)
|
_LOGGER.info("Calling pulseaudio: %s", cmd)
|
||||||
sock.send((cmd + "\n").encode("utf-8"))
|
sock.send((cmd + "\n").encode("utf-8"))
|
||||||
if response_expected:
|
if response_expected:
|
||||||
return_data = self._get_full_response(sock)
|
return_data = self._get_full_response(sock)
|
||||||
_LOGGER.debug("Data received from pulseaudio: " + return_data)
|
_LOGGER.debug("Data received from pulseaudio: %s", return_data)
|
||||||
else:
|
else:
|
||||||
return_data = ""
|
return_data = ""
|
||||||
finally:
|
finally:
|
||||||
|
@ -103,11 +109,11 @@ class PAServer():
|
||||||
"""Helper method to get the full response back from pulseaudio."""
|
"""Helper method to get the full response back from pulseaudio."""
|
||||||
result = ""
|
result = ""
|
||||||
rcv_buffer = sock.recv(self._buffer_size)
|
rcv_buffer = sock.recv(self._buffer_size)
|
||||||
result += rcv_buffer.decode("utf-8")
|
result += rcv_buffer.decode('utf-8')
|
||||||
|
|
||||||
while len(rcv_buffer) == self._buffer_size:
|
while len(rcv_buffer) == self._buffer_size:
|
||||||
rcv_buffer = sock.recv(self._buffer_size)
|
rcv_buffer = sock.recv(self._buffer_size)
|
||||||
result += rcv_buffer.decode("utf-8")
|
result += rcv_buffer.decode('utf-8')
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -118,10 +124,7 @@ class PAServer():
|
||||||
|
|
||||||
def turn_on(self, sink_name, source_name):
|
def turn_on(self, sink_name, source_name):
|
||||||
"""Send a command to pulseaudio to turn on the loopback."""
|
"""Send a command to pulseaudio to turn on the loopback."""
|
||||||
self._send_command(str.format(LOAD_CMD,
|
self._send_command(str.format(LOAD_CMD, sink_name, source_name), False)
|
||||||
sink_name,
|
|
||||||
source_name),
|
|
||||||
False)
|
|
||||||
|
|
||||||
def turn_off(self, module_idx):
|
def turn_off(self, module_idx):
|
||||||
"""Send a command to pulseaudio to turn off the loopback."""
|
"""Send a command to pulseaudio to turn off the loopback."""
|
||||||
|
@ -129,8 +132,7 @@ class PAServer():
|
||||||
|
|
||||||
def get_module_idx(self, sink_name, source_name):
|
def get_module_idx(self, sink_name, source_name):
|
||||||
"""For a sink/source, return it's module id in our cache, if found."""
|
"""For a sink/source, return it's module id in our cache, if found."""
|
||||||
result = re.search(str.format(MOD_REGEX,
|
result = re.search(str.format(MOD_REGEX, re.escape(sink_name),
|
||||||
re.escape(sink_name),
|
|
||||||
re.escape(source_name)),
|
re.escape(source_name)),
|
||||||
self._current_module_state)
|
self._current_module_state)
|
||||||
if result and result.group(1).isdigit():
|
if result and result.group(1).isdigit():
|
||||||
|
@ -141,11 +143,10 @@ class PAServer():
|
||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
class PALoopbackSwitch(SwitchDevice):
|
class PALoopbackSwitch(SwitchDevice):
|
||||||
"""Represents the presence or absence of a pa loopback module."""
|
"""Representation the presence or absence of a PA loopback module."""
|
||||||
|
|
||||||
def __init__(self, hass, name, pa_server,
|
def __init__(self, hass, name, pa_server, sink_name, source_name):
|
||||||
sink_name, source_name):
|
"""Initialize the Pulseaudio switch."""
|
||||||
"""Initialize the switch."""
|
|
||||||
self._module_idx = -1
|
self._module_idx = -1
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
self._name = name
|
self._name = name
|
||||||
|
@ -168,8 +169,8 @@ class PALoopbackSwitch(SwitchDevice):
|
||||||
if not self.is_on:
|
if not self.is_on:
|
||||||
self._pa_svr.turn_on(self._sink_name, self._source_name)
|
self._pa_svr.turn_on(self._sink_name, self._source_name)
|
||||||
self._pa_svr.update_module_state(no_throttle=True)
|
self._pa_svr.update_module_state(no_throttle=True)
|
||||||
self._module_idx = self._pa_svr.get_module_idx(self._sink_name,
|
self._module_idx = self._pa_svr.get_module_idx(
|
||||||
self._source_name)
|
self._sink_name, self._source_name)
|
||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning(IGNORED_SWITCH_WARN)
|
_LOGGER.warning(IGNORED_SWITCH_WARN)
|
||||||
|
@ -179,8 +180,8 @@ class PALoopbackSwitch(SwitchDevice):
|
||||||
if self.is_on:
|
if self.is_on:
|
||||||
self._pa_svr.turn_off(self._module_idx)
|
self._pa_svr.turn_off(self._module_idx)
|
||||||
self._pa_svr.update_module_state(no_throttle=True)
|
self._pa_svr.update_module_state(no_throttle=True)
|
||||||
self._module_idx = self._pa_svr.get_module_idx(self._sink_name,
|
self._module_idx = self._pa_svr.get_module_idx(
|
||||||
self._source_name)
|
self._sink_name, self._source_name)
|
||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning(IGNORED_SWITCH_WARN)
|
_LOGGER.warning(IGNORED_SWITCH_WARN)
|
||||||
|
@ -188,5 +189,5 @@ class PALoopbackSwitch(SwitchDevice):
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Refresh state in case an alternate process modified this data."""
|
"""Refresh state in case an alternate process modified this data."""
|
||||||
self._pa_svr.update_module_state()
|
self._pa_svr.update_module_state()
|
||||||
self._module_idx = self._pa_svr.get_module_idx(self._sink_name,
|
self._module_idx = self._pa_svr.get_module_idx(
|
||||||
self._source_name)
|
self._sink_name, self._source_name)
|
||||||
|
|
Loading…
Add table
Reference in a new issue