LOGGER = logging.getLogger(PROV_ID)
-CONFIG_ENTRIES = []
+CONFIG_ENTRIES = [] # we don't have any provider config entries (for now)
PLAYER_FEATURES = [PlayerFeature.QUEUE, PlayerFeature.CROSSFADE, PlayerFeature.GAPLESS]
-PLAYER_CONFIG_ENTRIES = [
- ConfigEntry(entry_key=CONF_LAST_POWER, entry_type=ConfigEntryType.BOOL, hidden=True),
- ConfigEntry(entry_key=CONF_LAST_VOLUME, entry_type=ConfigEntryType.INT, hidden=True),
-]
+PLAYER_CONFIG_ENTRIES = [] # we don't have any player config entries (for now)
async def async_setup(mass):
socket_client = self._socket_clients.get(player_id)
if socket_client:
await socket_client.async_cmd_power(True)
- # store last power state as we need it when the player (re)connects
- self.mass.config.player_settings[player_id][CONF_LAST_POWER] = True
+ # save power and volume state in cache
+ cache_str = f"squeezebox_player_state_{player_id}"
+ await self.mass.cache.async_set(cache_str, (True, socket_client.volume_level))
else:
LOGGER.warning("Received command for unavailable player: %s", player_id)
if socket_client:
await socket_client.async_cmd_power(False)
# store last power state as we need it when the player (re)connects
- self.mass.config.player_settings[player_id][CONF_LAST_POWER] = False
+ # save power and volume state in cache
+ cache_str = f"squeezebox_player_state_{player_id}"
+ await self.mass.cache.async_set(cache_str, (False, socket_client.volume_level))
else:
LOGGER.warning("Received command for unavailable player: %s", player_id)
socket_client = self._socket_clients.get(player_id)
if socket_client:
await socket_client.async_cmd_volume_set(volume_level)
- # store last volume state as we need it when the player (re)connects
- self.mass.config.player_settings[player_id][CONF_LAST_VOLUME] = volume_level
+ # save power and volume state in cache
+ cache_str = f"squeezebox_player_state_{player_id}"
+ await self.mass.cache.async_set(cache_str, (socket_client.powered, volume_level))
else:
LOGGER.warning("Received command for unavailable player: %s", player_id)
socket_client.features = PLAYER_FEATURES
socket_client.config_entries = PLAYER_CONFIG_ENTRIES
# restore power/volume states
- conf = self.mass.config.player_settings[socket_client.player_id]
- last_volume = conf.get(CONF_LAST_VOLUME, 40)
+ cache_str = f"squeezebox_player_state_{socket_client.player_id}"
+ cache_data = await self.mass.cache.async_get(cache_str)
+ last_power, last_volume = cache_data if cache_data else (False, 40)
await socket_client.async_cmd_volume_set(last_volume)
- last_power = conf.get(CONF_LAST_POWER, False)
await socket_client.async_cmd_power(last_power)
await self.mass.player_manager.async_add_player(socket_client)
self._socket_clients[socket_client.player_id] = socket_client
elif event == Event.EVENT_DISCONNECTED:
await self.mass.player_manager.async_remove_player(socket_client.player_id)
self._socket_clients.pop(socket_client.player_id)
+ del socket_client
elif event == Event.EVENT_DECODER_READY:
# player is ready for the next track (if any)
player_id = socket_client.player_id
"""Socketclient implementation for Squeezebox emulated player provider."""
import asyncio
-import decimal
import logging
-import os
-import random
import re
-import socket
import struct
-import sys
import time
-from collections import OrderedDict
from enum import Enum
-from typing import Awaitable, List, Tuple
+from typing import Awaitable
-from music_assistant.utils import (
- callback,
- get_hostname,
- get_ip,
- run_periodic,
- try_parse_int,
-)
+from music_assistant.utils import callback, run_periodic
from .constants import PROV_ID
for task in self._tasks:
if not task.cancelled():
task.cancel()
+ asyncio.create_task(self._event_callback(Event.EVENT_DISCONNECTED, self))
@property
def player_id(self) -> str:
async def async_cmd_volume_set(self, volume_level: int):
"""Send new volume level command to player."""
self._volume_control.volume = volume_level
- og = self._volume_control.old_gain()
- ng = self._volume_control.new_gain()
- await self.__async_send_frame(b"audg", struct.pack("!LLBBLL", og, og, 1, 255, ng, ng))
+ old_gain = self._volume_control.old_gain()
+ new_gain = self._volume_control.new_gain()
+ await self.__async_send_frame(
+ b"audg", struct.pack("!LLBBLL", old_gain, old_gain, 1, 255, new_gain, new_gain)
+ )
self._volume_level = volume_level
async def async_cmd_mute(self, muted: bool = False):
async def __async_send_frame(self, command, data):
"""Send command to Squeeze player."""
+ if self._reader.at_eof() or self._writer.is_closing():
+ LOGGER.debug("Socket is disconnected.")
+ return self.close()
packet = struct.pack("!H", len(data) + 4) + command + data
self._writer.write(packet)
await self._writer.drain()
"""Handle incoming data from socket."""
buffer = b""
# keep reading bytes from the socket
- while not self._reader.at_eof():
+ while not (self._reader.at_eof() or self._writer.is_closing()):
data = await self._reader.read(64)
# handle incoming data from socket
buffer = buffer + data
operation = operation.strip(b"!").strip().decode().lower()
handler = getattr(self, f"_process_{operation}", None)
if handler is None:
- LOGGER.warning("No handler for %s" % operation)
+ LOGGER.warning("No handler for %s", operation)
else:
handler(packet)
# EOF reached: socket is disconnected
LOGGER.info("Socket disconnected: %s", self._writer.get_extra_info("peername"))
self.close()
- asyncio.create_task(self._event_callback(Event.EVENT_DISCONNECTED, self))
@callback
def __pack_stream(
@callback
def _process_helo(self, data):
"""Process incoming HELO event from player (player connected)."""
+ # pylint: disable=unused-variable
# player connected
(dev_id, rev, mac) = struct.unpack("BB6s", data[:8])
device_mac = ":".join("%02x" % x for x in mac)
@callback
def _process_stat_aude(self, data):
"""Process incoming stat AUDe message (power level and mute)."""
- LOGGER.debug("AUDe received (spdif_enable, dac_enable): %s", data)
(spdif_enable, dac_enable) = struct.unpack("2B", data[:4])
powered = spdif_enable or dac_enable
self._powered = powered
@callback
def _process_stat_stmd(self, data):
"""Process incoming stat STMd message (decoder ready)."""
- LOGGER.debug("STMu received - Decoder Ready for next track: %s", data)
+ #pylint: disable=unused-argument
+ LOGGER.debug("STMu received - Decoder Ready for next track.")
asyncio.create_task(self._event_callback(Event.EVENT_DECODER_READY, self))
-
+
@callback
def _process_stat_stmf(self, data):
"""Process incoming stat STMf message (connection closed)."""
- LOGGER.debug("STMf received - connection closed: %s", data)
+ #pylint: disable=unused-argument
+ LOGGER.debug("STMf received - connection closed.")
self._state = State.Stopped
asyncio.create_task(self._event_callback(Event.EVENT_UPDATED, self))
def _process_stat_stmo(self, data):
"""Process incoming stat STMo message:
No more decoded (uncompressed) data to play; triggers rebuffering."""
- LOGGER.debug("STMo received - output underrun: %s", data)
+ #pylint: disable=unused-argument
+ LOGGER.debug("STMo received - output underrun.")
LOGGER.debug("Output Underrun")
@callback
def _process_stat_stmp(self, data):
"""Process incoming stat STMp message: Pause confirmed."""
- LOGGER.debug("STMp received - pause confirmed: %s", data)
+ #pylint: disable=unused-argument
+ LOGGER.debug("STMp received - pause confirmed.")
self._state = State.Paused
asyncio.create_task(self._event_callback(Event.EVENT_UPDATED, self))
@callback
def _process_stat_stmr(self, data):
"""Process incoming stat STMr message: Resume confirmed."""
- LOGGER.debug("STMr received - resume confirmed: %s", data)
+ #pylint: disable=unused-argument
+ LOGGER.debug("STMr received - resume confirmed.")
self._state = State.Playing
asyncio.create_task(self._event_callback(Event.EVENT_UPDATED, self))
@callback
def _process_stat_stms(self, data):
"""Process incoming stat STMs message: Playback of new track has started."""
- LOGGER.debug("STMs received - playback of new track has started: %s", data)
+ LOGGER.debug("STMs received - playback of new track has started.")
+ #pylint: disable=unused-argument
self._state = State.Playing
asyncio.create_task(self._event_callback(Event.EVENT_UPDATED, self))
@callback
def _process_stat_stmu(self, data):
"""Process incoming stat STMu message: Buffer underrun: Normal end of playback."""
- LOGGER.debug("STMu received - end of playback: %s", data)
+ #pylint: disable=unused-argument
+ LOGGER.debug("STMu received - end of playback.")
self.state = State.Stopped
asyncio.create_task(self._event_callback(Event.EVENT_UPDATED, self))
@callback
def _process_resp(self, data):
"""Process incoming RESP message: Response received at player."""
- LOGGER.debug("RESP received: %s", data)
+ #pylint: disable=unused-argument
# send continue
asyncio.create_task(self.__async_send_frame(b"cont", b"0"))
@callback
def _process_setd(self, data):
"""Process incoming SETD message: Get/set player firmware settings."""
- LOGGER.debug("SETD received %s", data)
cmd_id = data[0]
if cmd_id == 0:
# received player name
player_id = params[0]
cmds = params[1]
cmd_str = " ".join(cmds)
- player = self.mass.player_manager.get_player(player_id)
- if not player:
- return web.Response(status=404)
if cmd_str == "play":
- await player.async_cmd_play()
+ await self.mass.player_manager.async_cmd_play(player_id)
elif cmd_str == "pause":
- await player.async_cmd_pause()
+ await self.mass.player_manager.async_cmd_pause(player_id)
elif cmd_str == "stop":
- await player.async_cmd_stop()
+ await self.mass.player_manager.async_cmd_stop(player_id)
elif cmd_str == "next":
- await player.async_cmd_next()
+ await self.mass.player_manager.async_cmd_next(player_id)
elif cmd_str == "previous":
- await player.async_cmd_previous()
+ await self.mass.player_manager.async_cmd_previous(player_id)
elif "power" in cmd_str:
- args = cmds[1] if len(cmds) > 1 else None
- await player.async_cmd_power(args)
+ powered = cmds[1] if len(cmds) > 1 else False
+ if powered:
+ await self.mass.player_manager.async_cmd_power_on(player_id)
+ else:
+ await self.mass.player_manager.async_cmd_power_off(player_id)
elif cmd_str == "playlist index +1":
- await player.async_cmd_next()
+ await self.mass.player_manager.async_cmd_next(player_id)
elif cmd_str == "playlist index -1":
- await player.async_cmd_previous()
+ await self.mass.player_manager.async_cmd_previous(player_id)
elif "mixer volume" in cmd_str and "+" in cmds[2]:
+ player = self.mass.player_manager.get_player(player_id)
volume_level = player.volume_level + int(cmds[2].split("+")[1])
- await player.async_cmd_volume_set(volume_level)
+ await self.mass.player_manager.async_cmd_volume_set(player_id, volume_level)
elif "mixer volume" in cmd_str and "-" in cmds[2]:
+ player = self.mass.player_manager.get_player(player_id)
volume_level = player.volume_level - int(cmds[2].split("-")[1])
- await player.async_cmd_volume_set(volume_level)
+ await self.mass.player_manager.async_cmd_volume_set(player_id, volume_level)
elif "mixer volume" in cmd_str:
- await player.async_cmd_volume_set(cmds[2])
+ await self.mass.player_manager.async_cmd_volume_set(player_id, cmds[2])
elif cmd_str == "mixer muting 1":
- await player.async_cmd_volume_mute(True)
+ await self.mass.player_manager.async_cmd_volume_mute(player_id, True)
elif cmd_str == "mixer muting 0":
- await player.async_cmd_volume_mute(False)
+ await self.mass.player_manager.async_cmd_volume_mute(player_id, False)
elif cmd_str == "button volup":
- await player.async_cmd_volume_up()
+ await self.mass.player_manager.async_cmd_volume_up(player_id)
elif cmd_str == "button voldown":
- await player.async_cmd_volume_down()
+ await self.mass.player_manager.async_cmd_volume_down(player_id)
elif cmd_str == "button power":
- await player.async_cmd_power_toggle()
+ await self.mass.player_manager.async_cmd_power_toggle(player_id)
else:
return web.Response(text="command not supported")
return web.Response(text="success")