mirror of
https://github.com/Adam-Ant/home-assistant
synced 2024-12-20 12:54:35 +00:00
Alarm Clock triggers MPD
This commit is contained in:
parent
6aa165b85f
commit
f1e9fd8290
@ -73,6 +73,8 @@ input_boolean: !include input_boolean.yaml
|
||||
|
||||
input_slider: !include input_slider.yaml
|
||||
|
||||
shell_command: !include shell_command.yaml
|
||||
|
||||
script: !include_dir_named scripts
|
||||
|
||||
automation: !include_dir_merge_list automation
|
||||
|
10
ext_scripts/alarm_off.py
Normal file
10
ext_scripts/alarm_off.py
Normal file
@ -0,0 +1,10 @@
|
||||
import mpd
|
||||
|
||||
client = mpd.MPDClient()
|
||||
client.connect("192.168.1.62",6600)
|
||||
client.stop()
|
||||
client.clear()
|
||||
client.random(0)
|
||||
client.consume(0)
|
||||
client.close()
|
||||
client.disconnect()
|
11
ext_scripts/alarm_on.py
Normal file
11
ext_scripts/alarm_on.py
Normal file
@ -0,0 +1,11 @@
|
||||
import mpd
|
||||
|
||||
client = mpd.MPDClient()
|
||||
client.connect("192.168.1.62",6600)
|
||||
client.clear()
|
||||
client.load("morning")
|
||||
client.random(1)
|
||||
client.consume(1)
|
||||
client.play()
|
||||
client.close()
|
||||
client.disconnect()
|
455
ext_scripts/mpd.py
Normal file
455
ext_scripts/mpd.py
Normal file
@ -0,0 +1,455 @@
|
||||
# python-mpd: Python MPD client library
|
||||
# Copyright (C) 2008-2010 J. Alexander Treuman <jat@spatialrift.net>
|
||||
#
|
||||
# python-mpd is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# python-mpd is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with python-mpd. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import socket
|
||||
|
||||
|
||||
HELLO_PREFIX = "OK MPD "
|
||||
ERROR_PREFIX = "ACK "
|
||||
SUCCESS = "OK"
|
||||
NEXT = "list_OK"
|
||||
|
||||
|
||||
class MPDError(Exception):
|
||||
pass
|
||||
|
||||
class ConnectionError(MPDError):
|
||||
pass
|
||||
|
||||
class ProtocolError(MPDError):
|
||||
pass
|
||||
|
||||
class CommandError(MPDError):
|
||||
pass
|
||||
|
||||
class CommandListError(MPDError):
|
||||
pass
|
||||
|
||||
class PendingCommandError(MPDError):
|
||||
pass
|
||||
|
||||
class IteratingError(MPDError):
|
||||
pass
|
||||
|
||||
|
||||
class _NotConnected(object):
|
||||
def __getattr__(self, attr):
|
||||
return self._dummy
|
||||
|
||||
def _dummy(*args):
|
||||
raise ConnectionError("Not connected")
|
||||
|
||||
class MPDClient(object):
|
||||
def __init__(self):
|
||||
self.iterate = False
|
||||
self._reset()
|
||||
self._commands = {
|
||||
# Status Commands
|
||||
"clearerror": self._fetch_nothing,
|
||||
"currentsong": self._fetch_object,
|
||||
"idle": self._fetch_list,
|
||||
"noidle": None,
|
||||
"status": self._fetch_object,
|
||||
"stats": self._fetch_object,
|
||||
# Playback Option Commands
|
||||
"consume": self._fetch_nothing,
|
||||
"crossfade": self._fetch_nothing,
|
||||
"mixrampdb": self._fetch_nothing,
|
||||
"mixrampdelay": self._fetch_nothing,
|
||||
"random": self._fetch_nothing,
|
||||
"repeat": self._fetch_nothing,
|
||||
"setvol": self._fetch_nothing,
|
||||
"single": self._fetch_nothing,
|
||||
"replay_gain_mode": self._fetch_nothing,
|
||||
"replay_gain_status": self._fetch_item,
|
||||
"volume": self._fetch_nothing,
|
||||
# Playback Control Commands
|
||||
"next": self._fetch_nothing,
|
||||
"pause": self._fetch_nothing,
|
||||
"play": self._fetch_nothing,
|
||||
"playid": self._fetch_nothing,
|
||||
"previous": self._fetch_nothing,
|
||||
"seek": self._fetch_nothing,
|
||||
"seekid": self._fetch_nothing,
|
||||
"stop": self._fetch_nothing,
|
||||
# Playlist Commands
|
||||
"add": self._fetch_nothing,
|
||||
"addid": self._fetch_item,
|
||||
"clear": self._fetch_nothing,
|
||||
"delete": self._fetch_nothing,
|
||||
"deleteid": self._fetch_nothing,
|
||||
"move": self._fetch_nothing,
|
||||
"moveid": self._fetch_nothing,
|
||||
"playlist": self._fetch_playlist,
|
||||
"playlistfind": self._fetch_songs,
|
||||
"playlistid": self._fetch_songs,
|
||||
"playlistinfo": self._fetch_songs,
|
||||
"playlistsearch": self._fetch_songs,
|
||||
"plchanges": self._fetch_songs,
|
||||
"plchangesposid": self._fetch_changes,
|
||||
"shuffle": self._fetch_nothing,
|
||||
"swap": self._fetch_nothing,
|
||||
"swapid": self._fetch_nothing,
|
||||
# Stored Playlist Commands
|
||||
"listplaylist": self._fetch_list,
|
||||
"listplaylistinfo": self._fetch_songs,
|
||||
"listplaylists": self._fetch_playlists,
|
||||
"load": self._fetch_nothing,
|
||||
"playlistadd": self._fetch_nothing,
|
||||
"playlistclear": self._fetch_nothing,
|
||||
"playlistdelete": self._fetch_nothing,
|
||||
"playlistmove": self._fetch_nothing,
|
||||
"rename": self._fetch_nothing,
|
||||
"rm": self._fetch_nothing,
|
||||
"save": self._fetch_nothing,
|
||||
# Database Commands
|
||||
"count": self._fetch_object,
|
||||
"find": self._fetch_songs,
|
||||
"findadd": self._fetch_nothing,
|
||||
"list": self._fetch_list,
|
||||
"listall": self._fetch_database,
|
||||
"listallinfo": self._fetch_database,
|
||||
"lsinfo": self._fetch_database,
|
||||
"search": self._fetch_songs,
|
||||
"update": self._fetch_item,
|
||||
"rescan": self._fetch_item,
|
||||
# Sticker Commands
|
||||
"sticker get": self._fetch_item,
|
||||
"sticker set": self._fetch_nothing,
|
||||
"sticker delete": self._fetch_nothing,
|
||||
"sticker list": self._fetch_list,
|
||||
"sticker find": self._fetch_songs,
|
||||
# Connection Commands
|
||||
"close": None,
|
||||
"kill": None,
|
||||
"password": self._fetch_nothing,
|
||||
"ping": self._fetch_nothing,
|
||||
# Audio Output Commands
|
||||
"disableoutput": self._fetch_nothing,
|
||||
"enableoutput": self._fetch_nothing,
|
||||
"outputs": self._fetch_outputs,
|
||||
# Reflection Commands
|
||||
"commands": self._fetch_list,
|
||||
"notcommands": self._fetch_list,
|
||||
"tagtypes": self._fetch_list,
|
||||
"urlhandlers": self._fetch_list,
|
||||
"decoders": self._fetch_plugins,
|
||||
}
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr.startswith("send_"):
|
||||
command = attr.replace("send_", "", 1)
|
||||
wrapper = self._send
|
||||
elif attr.startswith("fetch_"):
|
||||
command = attr.replace("fetch_", "", 1)
|
||||
wrapper = self._fetch
|
||||
else:
|
||||
command = attr
|
||||
wrapper = self._execute
|
||||
if command not in self._commands:
|
||||
command = command.replace("_", " ")
|
||||
if command not in self._commands:
|
||||
raise AttributeError("'%s' object has no attribute '%s'" %
|
||||
(self.__class__.__name__, attr))
|
||||
return lambda *args: wrapper(command, args)
|
||||
|
||||
def _send(self, command, args):
|
||||
if self._command_list is not None:
|
||||
raise CommandListError("Cannot use send_%s in a command list" %
|
||||
command.replace(" ", "_"))
|
||||
self._write_command(command, args)
|
||||
retval = self._commands[command]
|
||||
if retval is not None:
|
||||
self._pending.append(command)
|
||||
|
||||
def _fetch(self, command, args=None):
|
||||
if self._command_list is not None:
|
||||
raise CommandListError("Cannot use fetch_%s in a command list" %
|
||||
command.replace(" ", "_"))
|
||||
if self._iterating:
|
||||
raise IteratingError("Cannot use fetch_%s while iterating" %
|
||||
command.replace(" ", "_"))
|
||||
if not self._pending:
|
||||
raise PendingCommandError("No pending commands to fetch")
|
||||
if self._pending[0] != command:
|
||||
raise PendingCommandError("'%s' is not the currently "
|
||||
"pending command" % command)
|
||||
del self._pending[0]
|
||||
retval = self._commands[command]
|
||||
if callable(retval):
|
||||
return retval()
|
||||
return retval
|
||||
|
||||
def _execute(self, command, args):
|
||||
if self._iterating:
|
||||
raise IteratingError("Cannot execute '%s' while iterating" %
|
||||
command)
|
||||
if self._pending:
|
||||
raise PendingCommandError("Cannot execute '%s' with "
|
||||
"pending commands" % command)
|
||||
retval = self._commands[command]
|
||||
if self._command_list is not None:
|
||||
if not callable(retval):
|
||||
raise CommandListError("'%s' not allowed in command list" %
|
||||
command)
|
||||
self._write_command(command, args)
|
||||
self._command_list.append(retval)
|
||||
else:
|
||||
self._write_command(command, args)
|
||||
if callable(retval):
|
||||
return retval()
|
||||
return retval
|
||||
|
||||
def _write_line(self, line):
|
||||
self._wfile.write("%s\n" % line)
|
||||
self._wfile.flush()
|
||||
|
||||
def _write_command(self, command, args=[]):
|
||||
parts = [command]
|
||||
for arg in args:
|
||||
parts.append('"%s"' % escape(str(arg)))
|
||||
self._write_line(" ".join(parts))
|
||||
|
||||
def _read_line(self):
|
||||
line = self._rfile.readline()
|
||||
if not line.endswith("\n"):
|
||||
raise ConnectionError("Connection lost while reading line")
|
||||
line = line.rstrip("\n")
|
||||
if line.startswith(ERROR_PREFIX):
|
||||
error = line[len(ERROR_PREFIX):].strip()
|
||||
raise CommandError(error)
|
||||
if self._command_list is not None:
|
||||
if line == NEXT:
|
||||
return
|
||||
if line == SUCCESS:
|
||||
raise ProtocolError("Got unexpected '%s'" % SUCCESS)
|
||||
elif line == SUCCESS:
|
||||
return
|
||||
return line
|
||||
|
||||
def _read_pair(self, separator):
|
||||
line = self._read_line()
|
||||
if line is None:
|
||||
return
|
||||
pair = line.split(separator, 1)
|
||||
if len(pair) < 2:
|
||||
raise ProtocolError("Could not parse pair: '%s'" % line)
|
||||
return pair
|
||||
|
||||
def _read_pairs(self, separator=": "):
|
||||
pair = self._read_pair(separator)
|
||||
while pair:
|
||||
yield pair
|
||||
pair = self._read_pair(separator)
|
||||
|
||||
def _read_list(self):
|
||||
seen = None
|
||||
for key, value in self._read_pairs():
|
||||
if key != seen:
|
||||
if seen is not None:
|
||||
raise ProtocolError("Expected key '%s', got '%s'" %
|
||||
(seen, key))
|
||||
seen = key
|
||||
yield value
|
||||
|
||||
def _read_playlist(self):
|
||||
for key, value in self._read_pairs(":"):
|
||||
yield value
|
||||
|
||||
def _read_objects(self, delimiters=[]):
|
||||
obj = {}
|
||||
for key, value in self._read_pairs():
|
||||
key = key.lower()
|
||||
if obj:
|
||||
if key in delimiters:
|
||||
yield obj
|
||||
obj = {}
|
||||
elif key in obj:
|
||||
if not isinstance(obj[key], list):
|
||||
obj[key] = [obj[key], value]
|
||||
else:
|
||||
obj[key].append(value)
|
||||
continue
|
||||
obj[key] = value
|
||||
if obj:
|
||||
yield obj
|
||||
|
||||
def _read_command_list(self):
|
||||
try:
|
||||
for retval in self._command_list:
|
||||
yield retval()
|
||||
finally:
|
||||
self._command_list = None
|
||||
self._fetch_nothing()
|
||||
|
||||
def _iterator_wrapper(self, iterator):
|
||||
try:
|
||||
for item in iterator:
|
||||
yield item
|
||||
finally:
|
||||
self._iterating = False
|
||||
|
||||
def _wrap_iterator(self, iterator):
|
||||
if not self.iterate:
|
||||
return list(iterator)
|
||||
self._iterating = True
|
||||
return self._iterator_wrapper(iterator)
|
||||
|
||||
def _fetch_nothing(self):
|
||||
line = self._read_line()
|
||||
if line is not None:
|
||||
raise ProtocolError("Got unexpected return value: '%s'" % line)
|
||||
|
||||
def _fetch_item(self):
|
||||
pairs = list(self._read_pairs())
|
||||
if len(pairs) != 1:
|
||||
return
|
||||
return pairs[0][1]
|
||||
|
||||
def _fetch_list(self):
|
||||
return self._wrap_iterator(self._read_list())
|
||||
|
||||
def _fetch_playlist(self):
|
||||
return self._wrap_iterator(self._read_playlist())
|
||||
|
||||
def _fetch_object(self):
|
||||
objs = list(self._read_objects())
|
||||
if not objs:
|
||||
return {}
|
||||
return objs[0]
|
||||
|
||||
def _fetch_objects(self, delimiters):
|
||||
return self._wrap_iterator(self._read_objects(delimiters))
|
||||
|
||||
def _fetch_changes(self):
|
||||
return self._fetch_objects(["cpos"])
|
||||
|
||||
def _fetch_songs(self):
|
||||
return self._fetch_objects(["file"])
|
||||
|
||||
def _fetch_playlists(self):
|
||||
return self._fetch_objects(["playlist"])
|
||||
|
||||
def _fetch_database(self):
|
||||
return self._fetch_objects(["file", "directory", "playlist"])
|
||||
|
||||
def _fetch_outputs(self):
|
||||
return self._fetch_objects(["outputid"])
|
||||
|
||||
def _fetch_plugins(self):
|
||||
return self._fetch_objects(["plugin"])
|
||||
|
||||
def _fetch_command_list(self):
|
||||
return self._wrap_iterator(self._read_command_list())
|
||||
|
||||
def _hello(self):
|
||||
line = self._rfile.readline()
|
||||
if not line.endswith("\n"):
|
||||
raise ConnectionError("Connection lost while reading MPD hello")
|
||||
line = line.rstrip("\n")
|
||||
if not line.startswith(HELLO_PREFIX):
|
||||
raise ProtocolError("Got invalid MPD hello: '%s'" % line)
|
||||
self.mpd_version = line[len(HELLO_PREFIX):].strip()
|
||||
|
||||
def _reset(self):
|
||||
self.mpd_version = None
|
||||
self._iterating = False
|
||||
self._pending = []
|
||||
self._command_list = None
|
||||
self._sock = None
|
||||
self._rfile = _NotConnected()
|
||||
self._wfile = _NotConnected()
|
||||
|
||||
def _connect_unix(self, path):
|
||||
if not hasattr(socket, "AF_UNIX"):
|
||||
raise ConnectionError("Unix domain sockets not supported "
|
||||
"on this platform")
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.connect(path)
|
||||
return sock
|
||||
|
||||
def _connect_tcp(self, host, port):
|
||||
try:
|
||||
flags = socket.AI_ADDRCONFIG
|
||||
except AttributeError:
|
||||
flags = 0
|
||||
err = None
|
||||
for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
|
||||
socket.SOCK_STREAM, socket.IPPROTO_TCP,
|
||||
flags):
|
||||
af, socktype, proto, canonname, sa = res
|
||||
sock = None
|
||||
try:
|
||||
sock = socket.socket(af, socktype, proto)
|
||||
sock.connect(sa)
|
||||
return sock
|
||||
except socket.error, err:
|
||||
if sock is not None:
|
||||
sock.close()
|
||||
if err is not None:
|
||||
raise err
|
||||
else:
|
||||
raise ConnectionError("getaddrinfo returns an empty list")
|
||||
|
||||
def connect(self, host, port):
|
||||
if self._sock is not None:
|
||||
raise ConnectionError("Already connected")
|
||||
if host.startswith("/"):
|
||||
self._sock = self._connect_unix(host)
|
||||
else:
|
||||
self._sock = self._connect_tcp(host, port)
|
||||
self._rfile = self._sock.makefile("rb")
|
||||
self._wfile = self._sock.makefile("wb")
|
||||
try:
|
||||
self._hello()
|
||||
except:
|
||||
self.disconnect()
|
||||
raise
|
||||
|
||||
def disconnect(self):
|
||||
self._rfile.close()
|
||||
self._wfile.close()
|
||||
self._sock.close()
|
||||
self._reset()
|
||||
|
||||
def fileno(self):
|
||||
if self._sock is None:
|
||||
raise ConnectionError("Not connected")
|
||||
return self._sock.fileno()
|
||||
|
||||
def command_list_ok_begin(self):
|
||||
if self._command_list is not None:
|
||||
raise CommandListError("Already in command list")
|
||||
if self._iterating:
|
||||
raise IteratingError("Cannot begin command list while iterating")
|
||||
if self._pending:
|
||||
raise PendingCommandError("Cannot begin command list "
|
||||
"with pending commands")
|
||||
self._write_command("command_list_ok_begin")
|
||||
self._command_list = []
|
||||
|
||||
def command_list_end(self):
|
||||
if self._command_list is None:
|
||||
raise CommandListError("Not in command list")
|
||||
if self._iterating:
|
||||
raise IteratingError("Already iterating over a command list")
|
||||
self._write_command("command_list_end")
|
||||
return self._fetch_command_list()
|
||||
|
||||
|
||||
def escape(text):
|
||||
return text.replace("\\", "\\\\").replace('"', '\\"')
|
||||
|
BIN
ext_scripts/mpd.pyc
Normal file
BIN
ext_scripts/mpd.pyc
Normal file
Binary file not shown.
2
shell_command.yaml
Normal file
2
shell_command.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
alarm_off: python2 /config/ext_scripts/alarm_off.py
|
||||
alarm_on: python2 /config/ext_scripts/alarm_on.py
|
Loading…
Reference in New Issue
Block a user