8000 Use voluptuous for PulseAudio Loopback by fabaff · Pull Request #3160 · home-assistant/core · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Use voluptuous for PulseAudio Loopback #3160

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 7, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 56 additions & 55 deletions homeassistant/components/switch/pulseaudio_loopback.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,69 +9,75 @@
import socket
from datetime import timedelta

import voluptuous as vol

import homeassistant.util as util
from homeassistant.components.switch import SwitchDevice
from homeassistant.util import convert
from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_NAME, CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv

_LOGGER = logging.getLogger(__name__)
_PULSEAUDIO_SERVERS = {}

DEFAULT_NAME = "paloopback"
DEFAULT_HOST = "localhost"
DEFAULT_PORT = 4712
CONF_BUFFER_SIZE = 'buffer_size'
CONF_SINK_NAME = 'sink_name'
CONF_SOURCE_NAME = 'source_name'
CONF_TCP_TIMEOUT = 'tcp_timeout'

DEFAULT_BUFFER_SIZE = 1024
DEFAULT_HOST = 'localhost'
DEFAULT_NAME = 'paloopback'
DEFAULT_PORT = 4712
DEFAULT_TCP_TIMEOUT = 3
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)

IGNORED_SWITCH_WARN = "Switch is already in the desired state. Ignoring."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mixing ' and "


LOAD_CMD = "load-module module-loopback sink={0} source={1}"
UNLOAD_CMD = "unload-module {0}"

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}.*>)"

IGNORED_SWITCH_WARN = "Switch is already in the desired state. Ignoring."
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
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."""
if config.get('sink_name') is None:
_LOGGER.error("Missing required variable: sink_name")
return False

if config.get('source_name') is None:
_LOGGER.error("Missing required variable: source_name")
return False

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)
name = config.get(CONF_NAME)
sink_name = config.get(CONF_SINK_NAME)
source_name = config.get(CONF_SOURCE_NAME)
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
buffer_size = config.get(CONF_BUFFER_SIZE)
tcp_timeout = config.get(CONF_TCP_TIMEOUT)

server_id = str.format("{0}:{1}", host, port)

if server_id in _PULSEAUDIO_SERVERS:
server = _PULSEAUDIO_SERVERS[server_id]

else:
server = PAServer(host, port, buffer_size, tcp_timeout)

_PULSEAUDIO_SERVERS[server_id] = server

add_devices_callback([PALoopbackSwitch(
hass,
name,
server,
sink_name,
source_name
)])
add_devices([PALoopbackSwitch(hass, name, server, sink_name, source_name)])


class PAServer():
"""Represents a pulseaudio server."""
"""Representation of a Pulseaudio server."""

_current_module_state = ""

Expand All @@ -88,11 +94,11 @@ def _send_command(self, cmd, response_expected):
sock.settimeout(self._tcp_timeout)
try:
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"))
if response_expected:
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:
return_data = ""
finally:
Expand All @@ -103,11 +109,11 @@ def _get_full_response(self, sock):
"""Helper method to get the full response back from pulseaudio."""
result = ""
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:
rcv_buffer = sock.recv(self._buffer_size)
result += rcv_buffer.decode("utf-8")
result += rcv_buffer.decode('utf-8')

return result

Expand All @@ -118,19 +124,15 @@ def update_module_state(self):

def turn_on(self, sink_name, source_name):
"""Send a command to pulseaudio to turn on the loopback."""
self._send_command(str.format(LOAD_CMD,
sink_name,
source_name),
False)
self._send_command(str.format(LOAD_CMD, sink_name, source_name), False)

def turn_off(self, module_idx):
"""Send a command to pulseaudio to turn off the loopback."""
self._send_command(str.format(UNLOAD_CMD, module_idx), False)

def get_module_idx(self, sink_name, source_name):
"""For a sink/source, return it's module id in our cache, if found."""
result = re.search(str.format(MOD_REGEX,
re.escape(sink_name),
result = re.search(str.format(MOD_REGEX, re.escape(sink_name),
re.escape(source_name)),
self._current_module_state)
if result and result.group(1).isdigit():
Expand All @@ -141,11 +143,10 @@ def get_module_idx(self, sink_name, source_name):

# pylint: disable=too-many-arguments
class PALoopbackSwitch(SwitchDevice):
"""Represents the presence or absence of a pa loopback module."""
"""Representation the presence or absence of a PA loopback module."""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Representation *of the *


def __init__(self, hass, name, pa_server,
sink_name, source_name):
"""Initialize the switch."""
def __init__(self, hass, name, pa_server, sink_name, source_name):
"""Initialize the Pulseaudio switch."""
self._module_idx = -1
self._hass = hass
self._name = name
Expand All @@ -168,8 +169,8 @@ def turn_on(self, **kwargs):
if not self.is_on:
self._pa_svr.turn_on(self._sink_name, self._source_name)
self._pa_svr.update_module_state(no_throttle=True)
self._module_idx = self._pa_svr.get_module_idx(self._sink_name,
self._source_name)
self._module_idx = self._pa_svr.get_module_idx(
self._sink_name, self._source_name)
self.update_ha_state()
else:
_LOGGER.warning(IGNORED_SWITCH_WARN)
Expand All @@ -179,14 +180,14 @@ def turn_off(self, **kwargs):
if self.is_on:
self._pa_svr.turn_off(self._module_idx)
self._pa_svr.update_module_state(no_throttle=True)
self._module_idx = self._pa_svr.get_module_idx(self._sink_name,
self._source_name)
self._module_idx = self._pa_svr.get_module_idx(
self._sink_name, self._source_name)
self.update_ha_state()
else:
_LOGGER.warning(IGNORED_SWITCH_WARN)

def update(self):
"""Refresh state in case an alternate process modified this data."""
self._pa_svr.update_module_state()
self._module_idx = self._pa_svr.get_module_idx(self._sink_name,
self._source_name)
self._module_idx = self._pa_svr.get_module_idx(
self._sink_name, self._source_name)
0