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