def get_sonos_favorites(request): # pragma: no cover, nuimo_app_config_path = request.registry.settings['nuimo_app_config_path'] mac_address = request.matchdict['mac_address'].replace('-', ':') component_id = request.matchdict['component_id'] with open(nuimo_app_config_path, 'r') as f: config = yaml.load(f) try: nuimo = config['nuimos'][mac_address] except (KeyError, TypeError): return HTTPNotFound("No Nuimo with such ID") components = nuimo['components'] try: component = next(c for c in components if c['id'] == component_id) except StopIteration: raise HTTPNotFound("No Component with such ID") if component['type'] != 'sonos': return HTTPNotFound("No Sonos Component with such ID") sonos_controller = SoCo(component['ip_address']) try: favorites = sonos_controller.get_sonos_favorites() except SoCoException: return HTTPNotFound("Sonos Device not reachable") if favorites['returned'] < 3: return HTTPNotFound("less than Three Favorites on Sonos") return {'favorites': favorites}
def get_nuimo_sonos_favorites(request): nuimo_app_config_path = request.registry.settings['nuimo_app_config_path'] mac_address = request.matchdict['mac_address'].replace('-', ':') component_id = request.matchdict['component_id'] with open(nuimo_app_config_path, 'r+') as f: config = yaml.load(f) try: nuimo = config['nuimos'][mac_address] except (KeyError, TypeError): return HTTPNotFound("No Nuimo with such ID") components = nuimo['components'] try: component = next(c for c in components if c['id'] == component_id) except StopIteration: raise HTTPNotFound("No Component with such ID") if component['type'] != 'sonos': return HTTPNotFound("No Sonos Component with such ID") station1 = component.get('station1', None) station2 = component.get('station2', None) station3 = component.get('station3', None) if not any((station1, station2, station3)): # pragma: no cover, sonos_controller = SoCo(component['ip_address']) try: favorites = sonos_controller.get_sonos_favorites(max_items=3) except SoCoException: return HTTPNotFound("Sonos Device not reachable") if favorites['returned'] < 3: return HTTPNotFound("less than Three Favorites on Sonos") station1 = component['station1'] = favorites['favorites'][0] station2 = component['station2'] = favorites['favorites'][1] station3 = component['station3'] = favorites['favorites'][2] f.seek( 0 ) # We want to overwrite the config file with the new configuration f.truncate() yaml.dump(config, f, default_flow_style=False) return {'station1': station1, 'station2': station2, 'station3': station3}
from soco import SoCo def make_safe_filename(s): def safe_char(c): if c.isalnum(): return c else: return "_" return "".join(safe_char(c) for c in s).rstrip("_") soco = SoCo("192.168.1.99") favorites = soco.get_sonos_favorites() ### INPUT_SELECT ### f = open("../entities/input_selects/sonos_playlist.yaml", "w") f.write("---") f.write("\n") f.write("sonosplaylist:" + "\n") f.write(" name: Playlist Selection" + "\n") f.write(" options:" + "\n") f.write(' - "-- Select --"' + "\n") for fav in favorites["favorites"]: title = fav["title"] # uri = fav["uri"] f.write(' - "' + title + '"\n') f.write(' initial: "-- Select --"' + "\n")
class Component(ThreadComponent): MATRIX = matrices.MUSIC_NOTE STATE_PLAYING = 'PLAYING' STATE_PAUSED = 'PAUSED_PLAYBACK' STATE_STOPPED = 'STOPPED' # how many seconds to wait after sending last command before # receiving device state change events EVENT_IDLE_INTERVAL = 2 # seconds def __init__(self, component_config): super().__init__(component_config) self.sonos_controller = SoCo(component_config['ip_address']) self.volume_range = range(0, 100) self.event_listener = event_listener # comes from global scope self.state = None self.volume = None self.nuimo = None self.last_request_time = time() self.sonos_joined_controllers = [] self.station_id_1 = component_config.get('station1', None) self.station_id_2 = component_config.get('station2', None) self.station_id_3 = component_config.get('station3', None) if not any((self.station_id_1, self.station_id_2, self.station_id_3)): try: favorites = self.sonos_controller.get_sonos_favorites( max_items=3) except SoCoException: self.nuimo.display_matrix(matrices.ERROR) if favorites['returned'] >= 3: self.station_id_1 = favorites['favorites'][0] self.station_id_2 = favorites['favorites'][1] self.station_id_3 = favorites['favorites'][2] if len( self.sonos_controller.group.members ) > 1 and self.sonos_controller.group.coordinator.ip_address == component_config[ 'ip_address']: for sonos_controller in self.sonos_controller.group.members: if sonos_controller.ip_address != component_config[ 'ip_address'] and sonos_controller.player_name != self.sonos_controller.group.coordinator.player_name: self.sonos_joined_controllers.append( SoCo(sonos_controller.ip_address)) def run(self): self.subscribe_to_events() self.update_state() try: self.run_loop() finally: self.unsubscribe_from_events() def run_loop(self): while not self.stopped: try: event = self.av_transport_subscription.events.get(timeout=0.1) if time() - self.last_request_time > self.EVENT_IDLE_INTERVAL: logger.debug("avTransport event: %s", pformat(event.variables)) self.state = event.variables['transport_state'] except Empty: pass try: event = self.rendering_control_subscription.events.get( timeout=0.1) if time() - self.last_request_time > self.EVENT_IDLE_INTERVAL: logger.debug("renderingControl event: %s", pformat(event.variables)) self.volume = int(event.variables['volume']['Master']) except Empty: pass def subscribe_to_events(self): self.av_transport_subscription = self.sonos_controller.avTransport.subscribe( ) self.rendering_control_subscription = self.sonos_controller.renderingControl.subscribe( ) def unsubscribe_from_events(self): self.rendering_control_subscription.unsubscribe() self.av_transport_subscription.unsubscribe() def update_state(self): self.state = self.sonos_controller.get_current_transport_info( )['current_transport_state'] self.volume = self.sonos_controller.volume logger.debug("%s state: %s volume: %s", self.sonos_controller.ip_address, self.state, self.volume) def on_rotation(self, delta): if self.state is not None: try: delta = round(self.volume_range.stop * delta) self.volume = clamp_value(self.volume + delta, self.volume_range) self.sonos_controller.volume = self.volume if self.sonos_joined_controllers != []: for sonos_joined_controller in self.sonos_joined_controllers: sonos_joined_controller.volume = self.volume logger.debug("volume update delta: %s volume: %s", delta, self.volume) matrix = matrices.progress_bar(self.volume / self.volume_range.stop) self.nuimo.display_matrix(matrix, fading=True, ignore_duplicates=True) except SoCoException: self.nuimo.display_matrix(matrices.ERROR) self.last_request_time = time() else: self.nuimo.display_matrix(matrices.ERROR) logger.debug("No Active connection with Host") def on_button_press(self): if self.state == self.STATE_PLAYING: self.pause() logger.debug("Play Paused by self.pause() on button press.") elif self.state in [self.STATE_PAUSED, self.STATE_STOPPED]: self.play() logger.debug( "Play started/resumed by self.pause() on button press.") elif self.state is None: self.nuimo.display_matrix(matrices.ERROR) logger.debug("No Active connection with Host.") logger.debug("state toggle: %s", self.state) self.last_request_time = time() def pause(self, show_matrix=True): try: self.sonos_controller.pause() self.state = self.STATE_PAUSED if show_matrix: self.nuimo.display_matrix(matrices.PAUSE) except SoCoException: self.nuimo.display_matrix(matrices.ERROR) def play(self, show_matrix=True): try: self.sonos_controller.play() self.state = self.STATE_PLAYING if show_matrix: self.nuimo.display_matrix(matrices.PLAY) except SoCoException: self.nuimo.display_matrix(matrices.ERROR) def on_swipe_right(self): try: self.sonos_controller.next() if self.state != self.STATE_PLAYING: self.play(show_matrix=False) self.nuimo.display_matrix(matrices.NEXT_SONG) except SoCoException: self.nuimo.display_matrix(matrices.ERROR) self.last_request_time = time() def on_swipe_left(self): try: self.sonos_controller.previous() if self.state != self.STATE_PLAYING: self.play(show_matrix=False) self.nuimo.display_matrix(matrices.PREVIOUS_SONG) except SoCoException: self.nuimo.display_matrix(matrices.ERROR) self.last_request_time = time() def on_longtouch_left(self): logger.debug("favorite left") if self.station_id_1 is not None: try: self.play_track_playlist_or_album(self.station_id_1, matrices.STATION1) except SoCoException: self.nuimo.display_matrix(matrices.ERROR) def on_longtouch_bottom(self): logger.debug("favorite bottom") if self.station_id_2 is not None: try: self.play_track_playlist_or_album(self.station_id_2, matrices.STATION2) except SoCoException: self.nuimo.display_matrix(matrices.ERROR) def on_longtouch_right(self): logger.debug("favorite right") if self.station_id_3 is not None: try: self.play_track_playlist_or_album(self.station_id_3, matrices.STATION3) except SoCoException: self.nuimo.display_matrix(matrices.ERROR) def play_track_playlist_or_album(self, src, matrix): try: if 'object.container.playlistContainer' in src[ 'meta'] or 'object.container.album.musicAlbum' in src[ 'meta']: self._replace_queue_with_playlist(src) self.sonos_controller.play_from_queue(0) else: self.sonos_controller.play_uri(uri=src['uri'], meta=src['meta'], title=src['title']) self.nuimo.display_matrix(matrix) except SoCoException: self.nuimo.display_matrix(matrices.ERROR) def _replace_queue_with_playlist(self, src): """Replace queue with playlist represented by src. Playlists can't be played directly with the self.sonos_controller.play_uri API as they are actually composed of mulitple URLs. Until soco has suppport for playing a playlist, we'll need to parse the playlist item and replace the current queue in order to play it. """ import soco import xml.etree.ElementTree as ET root = ET.fromstring(src['meta']) namespaces = { 'item': 'urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/', 'desc': 'urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/' } desc = root.find('item:item', namespaces).find('desc:desc', namespaces).text res = [ soco.data_structures.DidlResource(uri=src['uri'], protocol_info="DUMMY") ] didl = soco.data_structures.DidlItem(title="DUMMY", parent_id="DUMMY", item_id=src['uri'], desc=desc, resources=res) self.sonos_controller.stop() self.sonos_controller.clear_queue() self.sonos_controller.play_mode = 'NORMAL' self.sonos_controller.add_to_queue(didl)