From f1e9fd8290d85f3133bebfd83ea55d824db4c38b Mon Sep 17 00:00:00 2001 From: Adam Dodman Date: Wed, 30 Nov 2016 14:50:36 +0000 Subject: [PATCH] Alarm Clock triggers MPD --- configuration.yaml | 2 + ext_scripts/alarm_off.py | 10 + ext_scripts/alarm_on.py | 11 + ext_scripts/mpd.py | 455 +++++++++++++++++++++++++++++++++++++++ ext_scripts/mpd.pyc | Bin 0 -> 17314 bytes shell_command.yaml | 2 + 6 files changed, 480 insertions(+) create mode 100644 ext_scripts/alarm_off.py create mode 100644 ext_scripts/alarm_on.py create mode 100644 ext_scripts/mpd.py create mode 100644 ext_scripts/mpd.pyc create mode 100644 shell_command.yaml diff --git a/configuration.yaml b/configuration.yaml index 10db989..20b39c0 100644 --- a/configuration.yaml +++ b/configuration.yaml @@ -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 diff --git a/ext_scripts/alarm_off.py b/ext_scripts/alarm_off.py new file mode 100644 index 0000000..cf7fe63 --- /dev/null +++ b/ext_scripts/alarm_off.py @@ -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() diff --git a/ext_scripts/alarm_on.py b/ext_scripts/alarm_on.py new file mode 100644 index 0000000..d943657 --- /dev/null +++ b/ext_scripts/alarm_on.py @@ -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() diff --git a/ext_scripts/mpd.py b/ext_scripts/mpd.py new file mode 100644 index 0000000..c2bb010 --- /dev/null +++ b/ext_scripts/mpd.py @@ -0,0 +1,455 @@ +# python-mpd: Python MPD client library +# Copyright (C) 2008-2010 J. Alexander Treuman +# +# 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 . + +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('"', '\\"') + diff --git a/ext_scripts/mpd.pyc b/ext_scripts/mpd.pyc new file mode 100644 index 0000000000000000000000000000000000000000..29aa73b64eb51d7c1c9ac7c5039b11706c63dee3 GIT binary patch literal 17314 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 literal 0 HcmV?d00001 diff --git a/shell_command.yaml b/shell_command.yaml new file mode 100644 index 0000000..130b2ef --- /dev/null +++ b/shell_command.yaml @@ -0,0 +1,2 @@ +alarm_off: python2 /config/ext_scripts/alarm_off.py +alarm_on: python2 /config/ext_scripts/alarm_on.py