Hallo smarthome.py-Nutzer,
anbei findet ihr die "Beta" meines Plugins für die Logitech Squeezebox-Familie.
"Beta", weil ich evtl. noch mal am Konzept feilen muss, da das LMS-Interface teilweise nicht ganz so einheitlich antwortet wie es für eine "glatte" Implementierung wünschenswert wäre. Daher sind schon einige Quirks eingebaut (Zustände Play, Stop, Pause, Volume bzw das Abfragen andersnamiger Felder beim Starten um Werte zu bekommen). Die in der untenstehenden Item-Definition "squeezebox.conf" aufgeführten Items funktionieren.
Mithilfe wäre gewünscht, indem jemand mit Kenntnis der Playlist-Struktur sich mal mit Telnet auf dem LMS (ip:9090) einloggt, und mir näher bringt, wie ich jetzt eine "Standard-Playliste" lade und dann in dieser vor- und zurückspringen kann. Oder allgemein Playlisten. Mutige können auch versuchen die Item-Definitionen anzulegen.
plugin/squeezebox/__init__.py
etc/plugin.conf
items/squeezebox.conf
Kurzerklärung:
Viel Spaß!
Grüße
Robert
anbei findet ihr die "Beta" meines Plugins für die Logitech Squeezebox-Familie.
"Beta", weil ich evtl. noch mal am Konzept feilen muss, da das LMS-Interface teilweise nicht ganz so einheitlich antwortet wie es für eine "glatte" Implementierung wünschenswert wäre. Daher sind schon einige Quirks eingebaut (Zustände Play, Stop, Pause, Volume bzw das Abfragen andersnamiger Felder beim Starten um Werte zu bekommen). Die in der untenstehenden Item-Definition "squeezebox.conf" aufgeführten Items funktionieren.
Mithilfe wäre gewünscht, indem jemand mit Kenntnis der Playlist-Struktur sich mal mit Telnet auf dem LMS (ip:9090) einloggt, und mir näher bringt, wie ich jetzt eine "Standard-Playliste" lade und dann in dieser vor- und zurückspringen kann. Oder allgemein Playlisten. Mutige können auch versuchen die Item-Definitionen anzulegen.
plugin/squeezebox/__init__.py
Code:
#!/usr/bin/env python # vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab ######################################################################### # Copyright 2013 Robert Budde robert@projekt131.de ######################################################################### # Squeezebox-Plugin for SmartHome.py. http://mknx.github.com/smarthome/ # # This plugin is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This plugin 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this plugin. If not, see <http://www.gnu.org/licenses/>. ######################################################################### import logging import struct import time import urllib2 import lib.my_asynchat import re logger = logging.getLogger('Squeezebox') class Squeezebox(lib.my_asynchat.AsynChat): def __init__(self, smarthome, host='127.0.0.1', port=9090): lib.my_asynchat.AsynChat.__init__(self, smarthome, host, port) self._sh = smarthome self._val = {} self._obj = {} self._init_cmds = [] smarthome.monitor_connection(self) def _check_mac(self, mac): return re.match("[0-9a-f]{2}([:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", mac.lower()) def _resolv_full_cmd(self, item, attr): if (item.conf[attr][0] == '*'): # check if PlayerID (MAC) is given - if not get from parent object parent_item = item.return_parent() if (parent_item != None) and ('squeezebox_playerid' in parent_item.conf) and self._check_mac(parent_item.conf['squeezebox_playerid']): item.conf[attr] = parent_item.conf['squeezebox_playerid'] + item.conf[attr][1::] else: logger.warning("squeezebox: could not resolve playerid for {0} from parent item {1}".format(item, parent_item)) return None return item.conf[attr] def parse_item(self, item): if 'squeezebox_recv' in item.conf: cmd = self._resolv_full_cmd(item,'squeezebox_recv') if (cmd == None): return None logger.debug("squeezebox: {0} receives updates by {1}".format(item, cmd)) if not cmd in self._val: self._val[cmd] = {'items': [item], 'logics': []} else: if not item in self._val[cmd]['items']: self._val[cmd]['items'].append(item) if ('squeezebox_init' in item.conf): cmd = self._resolv_full_cmd(item,'squeezebox_init') if (cmd == None): return None logger.debug("squeezebox: {0} is initialized by {1}".format(item, cmd)) if not cmd in self._val: self._val[cmd] = {'items': [item], 'logics': []} else: if not item in self._val[cmd]['items']: self._val[cmd]['items'].append(item) if not cmd in self._init_cmds: self._init_cmds.append(cmd) if 'squeezebox_send' in item.conf: cmd = self._resolv_full_cmd(item,'squeezebox_send') if (cmd == None): return None logger.debug("squeezebox: {0} is send to {1}".format(item, cmd)) return self.update_item else: return None def parse_logic(self, logic): pass def update_item(self, item, caller=None, source=None, dest=None): # be careful: as the server echoes ALL comands not using this will result in a loop if caller != 'Squeezebox Media Server': cmd = self._resolv_full_cmd(item, 'squeezebox_send').split() if self._check_mac(cmd[0]): cmd[0] = urllib2.quote(cmd[0]) if isinstance(item, str): value = urllib2.quote(item()) elif (item._type == 'bool'): # convert to get '0'/'1' instead of 'True'/'False' value = int(item()) else: value = item() # special handling if (len(cmd) >= 2) and (cmd[1] == 'play') and not item(): # if 'play' was set to false, send 'pause' cmd[1] = 'pause' self._send(' '.join(cmd_str for cmd_str in cmd).format(value)) def _send(self, cmd): logger.debug("Sending request: {0}".format(cmd)) self.push(cmd+'\r\n') def _parse_response(self, response): data = [urllib2.unquote(data_str) for data_str in response.split()] logger.debug("Got: {0}".format(data)) # if (data[0].lower() == 'player'): # if (data[1].lower() == 'count'): # num_players = int(data[2]) # logger.info("Found {0} player".format(num_players)) # for i in range(0,num_players): # self._send("player name {0} ?".format(i)) # self._send("player id {0} ?".format(i)) # elif (data[1].lower() == 'name'): # logger.info("Players {0} name is: {1}".format(data[2], data[3])) # elif (data[1].lower() == 'id'): # logger.info("Players {0} id is: {1}".format(data[2], data[3])) if (data[0].lower() == 'listen'): value = int(data[1]) if (value == 1): logger.info("Listen-mode enabled") else: logger.info("Listen-mode disabled") if self._check_mac(data[0]): if (data[1] == 'play'): self._update_items_with_data([data[0], 'play', 1]) self._update_items_with_data([data[0], 'stop', 0]) self._update_items_with_data([data[0], 'pause', 0]) return elif (data[1] == 'stop'): self._update_items_with_data([data[0], 'play', 0]) self._update_items_with_data([data[0], 'stop', 1]) self._update_items_with_data([data[0], 'pause', 0]) return elif (data[1] == 'pause'): self._send(data[0] + ' mode ?') return elif (data[1] == 'mode'): self._update_items_with_data([data[0], 'play', data[2] == 'play']) self._update_items_with_data([data[0], 'stop', data[2] == 'stop']) self._update_items_with_data([data[0], 'pause', data[2] == 'pause']) return elif (data[1] == 'prefset'): if (data[2] == 'server'): if (data[3] == 'volume'): # make sure value is always positive - also if muted! data[4] = abs(int(data[4])) elif (data[1] == 'playlist'): if (data[2] == 'newsong'): # trigger reading of other song fields for field in ['genre', 'artist', 'album', 'title', 'duration']: self._send(data[0] + ' ' + field + ' ?') elif (data[1] in ['genre', 'artist', 'album', 'title', 'duration']) and (len(data) == 2): # these fields are returned empty so no update takes plase - append '' to trigger update data.append('') self._update_items_with_data(data) def _update_items_with_data(self, data): cmd = ' '.join(data_str for data_str in data[:-1]) if (cmd in self._val): for item in self._val[cmd]['items']: item(data[-1], 'Squeezebox Media Server', "{}:{}".format(self.addr[0],self.addr[1])) def found_terminator(self): response = self.buffer self.buffer = '' self._parse_response(response) def handle_connect(self): self.discard_buffers() # start discovering players and their respective IDs and names # self._send('players 0') self._send('listen 1') if self._init_cmds != []: if self.is_connected: logger.debug('squeezebox: init read') for cmd in self._init_cmds: self._send(cmd+' ?') def run(self): self.alive = True def stop(self): self.alive = False self.handle_close()
Code:
[squeezebox] class_name = Squeezebox class_path = plugins.squeezebox
Code:
[Squeezebox_2] squeezebox_playerid = 00:04:20:27:ca:1f [[Name]] type = str visu = yes squeezebox_recv = * player name ? squeezebox_init = * player name [[IP]] type = str visu = yes squeezebox_recv = * player ip ? squeezebox_init = * player ip [[Signal_Strength]] type = num visu = yes squeezebox_recv = * signalstrength [[Power]] type = bool visu = yes squeezebox_send = * power {} squeezebox_recv = * prefset server power squeezebox_init = * power [[Mute]] type = bool visu = yes squeezebox_send = * mixer muting {} squeezebox_recv = * prefset server mute squeezebox_init = * mixer muting [[Volume]] type = num visu = yes squeezebox_send = * mixer volume {} squeezebox_recv = * prefset server volume squeezebox_init = * mixer volume [[Volume_Up]] type = bool enforce_updates = true visu = yes squeezebox_send = * button volup [[Volume_Down]] type = bool enforce_updates = true visu = yes squeezebox_send = * button voldown [[Play]] type = bool visu = yes squeezebox_send = * play squeezebox_recv = * play squeezebox_init = * mode [[Stop]] type = bool visu = yes squeezebox_send = * stop squeezebox_recv = * stop squeezebox_init = * mode [[Pause]] type = bool visu = yes squeezebox_send = * pause {} squeezebox_recv = * pause squeezebox_init = * mode [[Current_Title]] type = str visu = yes squeezebox_recv = * playlist newsong squeezebox_init = * current_title [[Genre]] type = str visu = yes squeezebox_recv = * genre [[Artist]] type = str visu = yes squeezebox_recv = * artist [[Album]] type = str visu = yes squeezebox_recv = * album [[Title]] type = str visu = yes squeezebox_recv = * title [[Duration]] type = num visu = yes squeezebox_recv = * duration
Kurzerklärung:
- squeezebox_playerid = MAC der Squeezebox
- squeezebox_recv = String der das Item updated (von LMS gesendet)
- * bedeutet player-id vom parent_item nehmen - sonst direkt angeben! So kann man einfacher bei mehreren Squeezeboxen die Items kopieren.
- ? ist ein extra Zeichen (manchmal braucht es zwei Fragezeichen - eins wird automatisch angefügt!)
- squeezebox_init = String der zu Anfang das Item initialisiert (manchmal ist dieser anders als squeezebox_recv)
- wird squeezebox_init nicht angegeben, wird squeezebox_recv beim Start abgefragt!
- squeezebox_send = String zum Senden zum LMS
- {} ist für das Eintragen des Value - fehlt dies, wird kein Value gesendet ('play' etc.!)
Viel Spaß!
Grüße
Robert
Kommentar