Exemplo n.º 1
0
async def playback(dev: Device, cmd, target, value):
    """Get and set playback settings, e.g. repeat and shuffle.."""
    if target and value:
        dev.set_playback_settings(target, value)
    if cmd == "support":
        click.echo("Supported playback functions:")
        supported = await dev.get_supported_playback_functions("storage:usb1")
        for i in supported:
            print(i)
    elif cmd == "settings":
        print_settings(await dev.get_playback_settings())
        # click.echo("Playback functions:")
        # funcs = await dev.get_available_playback_functions()
        # print(funcs)
    else:
        click.echo("Currently playing: %s" % await dev.get_play_info())
Exemplo n.º 2
0
    async def async_step_user(self, user_input=None):
        """Handle a flow initiated by the user."""
        if user_input is None:
            return self.async_show_form(
                step_id="user",
                data_schema=vol.Schema({vol.Required(CONF_ENDPOINT): str}),
            )

        # Validate input
        endpoint = user_input[CONF_ENDPOINT]
        parsed_url = urlparse(endpoint)

        # Try to connect and get device name
        try:
            device = Device(endpoint)
            await device.get_supported_methods()
            interface_info = await device.get_interface_information()
            name = interface_info.modelName
        except SongpalException as ex:
            _LOGGER.debug("Connection failed: %s", ex)
            return self.async_show_form(
                step_id="user",
                data_schema=vol.Schema({
                    vol.Required(CONF_ENDPOINT,
                                 default=user_input.get(CONF_ENDPOINT, "")):
                    str,
                }),
                errors={"base": "cannot_connect"},
            )

        self.conf = SongpalConfig(name, parsed_url.hostname, endpoint)

        return await self.async_step_init(user_input)
Exemplo n.º 3
0
async def cli(ctx, endpoint, debug, websocket, post):
    """Songpal CLI."""
    lvl = logging.INFO
    if debug:
        lvl = logging.DEBUG
        click.echo("Setting debug level to %s" % debug)
    logging.basicConfig(level=lvl)

    if ctx.invoked_subcommand == "discover":
        ctx.obj = {"debug": debug}
        return

    if endpoint is None:
        err("Endpoint is required except when with 'discover'!")
        return

    protocol = None
    if post and websocket:
        err("You can force either --post or --websocket")
        return
    elif websocket:
        protocol = ProtocolType.WebSocket
    elif post:
        protocol = ProtocolType.XHRPost

    logging.debug("Using endpoint %s", endpoint)
    x = Device(endpoint, force_protocol=protocol, debug=debug)
    try:
        await x.get_supported_methods()
    except (requests.exceptions.ConnectionError, SongpalException) as ex:
        err("Unable to get supported methods: %s" % ex)
        sys.exit(-1)
    ctx.obj = x
Exemplo n.º 4
0
async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry,
                            async_add_entities) -> None:
    """Set up songpal media player."""
    name = config_entry.data[CONF_NAME]
    endpoint = config_entry.data[CONF_ENDPOINT]

    device = Device(endpoint)
    try:
        async with async_timeout.timeout(
                10):  # set timeout to avoid blocking the setup process
            await device.get_supported_methods()
    except (SongpalException, asyncio.TimeoutError) as ex:
        _LOGGER.warning("[%s(%s)] Unable to connect", name, endpoint)
        _LOGGER.debug("Unable to get methods from songpal: %s", ex)
        raise PlatformNotReady from ex

    songpal_entity = SongpalEntity(name, device)
    async_add_entities([songpal_entity], True)

    platform = entity_platform.current_platform.get()
    platform.async_register_entity_service(
        SET_SOUND_SETTING,
        {
            vol.Required(PARAM_NAME): cv.string,
            vol.Required(PARAM_VALUE): cv.string
        },
        "async_set_sound_setting",
    )
Exemplo n.º 5
0
    def __init__(self, name, endpoint, poll=False):
        """Init."""
        self._name = name
        self._endpoint = endpoint
        self._poll = poll
        self.dev = Device(self._endpoint)
        self._sysinfo = None

        self._state = False
        self._available = False
        self._initialized = False

        self._volume_control = None
        self._volume_min = 0
        self._volume_max = 1
        self._volume = 0
        self._is_muted = False

        self._active_source = None
        self._sources = {}
Exemplo n.º 6
0
async def listen(dev: Device):
    """Listen for volume, power and content notifications."""
    async def volume_changed(x):
        print("volume: %s" % x.volume)

    async def power_changed(x):
        print("power: %s" % x)

    async def content_changed(x):
        print("content: %s" % x)

    dev.on_notification(VolumeChange, volume_changed)
    dev.on_notification(PowerChange, power_changed)
    dev.on_notification(ContentChange, content_changed)
    await dev.listen_notifications()
Exemplo n.º 7
0
    async def async_step_import(self, user_input=None):
        """Import a config entry."""
        name = user_input.get(CONF_NAME)
        endpoint = user_input.get(CONF_ENDPOINT)
        parsed_url = urlparse(endpoint)

        # Try to connect to test the endpoint
        try:
            device = Device(endpoint)
            await device.get_supported_methods()
            # Get name
            if name is None:
                interface_info = await device.get_interface_information()
                name = interface_info.modelName
        except SongpalException as ex:
            _LOGGER.error("Import from yaml configuration failed: %s", ex)
            return self.async_abort(reason="cannot_connect")

        self.conf = SongpalConfig(name, parsed_url.hostname, endpoint)

        return await self.async_step_init(user_input)
Exemplo n.º 8
0
    def __init__(self, name, endpoint, poll=False):
        """Init."""
        from songpal import Device
        self._name = name
        self._endpoint = endpoint
        self._poll = poll
        self.dev = Device(self._endpoint)
        self._sysinfo = None

        self._state = False
        self._available = False
        self._initialized = False

        self._volume_control = None
        self._volume_min = 0
        self._volume_max = 1
        self._volume = 0
        self._is_muted = False

        self._active_source = None
        self._sources = {}
Exemplo n.º 9
0
async def listen(dev: Device):
    from .containers import VolumeChange, PowerChange, ContentChange

    async def volume_changed(x):
        print("volume: %s" % x.volume)

    async def power_changed(x):
        print("power: %s" % x)

    async def content_changed(x):
        print("content: %s" % x)

    dev.on_notification(VolumeChange, volume_changed)
    dev.on_notification(PowerChange, power_changed)
    dev.on_notification(ContentChange, content_changed)
    await dev.listen_notifications()
Exemplo n.º 10
0
class SongpalDevice(MediaPlayerDevice):
    """Class representing a Songpal device."""
    def __init__(self, name, endpoint, poll=False):
        """Init."""
        from songpal import Device
        self._name = name
        self._endpoint = endpoint
        self._poll = poll
        self.dev = Device(self._endpoint)
        self._sysinfo = None

        self._state = False
        self._available = False
        self._initialized = False

        self._volume_control = None
        self._volume_min = 0
        self._volume_max = 1
        self._volume = 0
        self._is_muted = False

        self._active_source = None
        self._sources = {}

    @property
    def should_poll(self):
        """Return True if the device should be polled."""
        return self._poll

    async def initialize(self):
        """Initialize the device."""
        await self.dev.get_supported_methods()
        self._sysinfo = await self.dev.get_system_info()

    async def async_activate_websocket(self):
        """Activate websocket for listening if wanted."""
        _LOGGER.info("Activating websocket connection..")
        from songpal import (VolumeChange, ContentChange, PowerChange,
                             ConnectChange)

        async def _volume_changed(volume: VolumeChange):
            _LOGGER.debug("Volume changed: %s", volume)
            self._volume = volume.volume
            self._is_muted = volume.mute
            await self.async_update_ha_state()

        async def _source_changed(content: ContentChange):
            _LOGGER.debug("Source changed: %s", content)
            if content.is_input:
                self._active_source = self._sources[content.source]
                _LOGGER.debug("New active source: %s", self._active_source)
                await self.async_update_ha_state()
            else:
                _LOGGER.debug("Got non-handled content change: %s", content)

        async def _power_changed(power: PowerChange):
            _LOGGER.debug("Power changed: %s", power)
            self._state = power.status
            await self.async_update_ha_state()

        async def _try_reconnect(connect: ConnectChange):
            _LOGGER.error("Got disconnected with %s, trying to reconnect.",
                          connect.exception)
            self._available = False
            self.dev.clear_notification_callbacks()
            await self.async_update_ha_state()

            # Try to reconnect forever, a successful reconnect will initialize
            # the websocket connection again.
            delay = 10
            while not self._available:
                _LOGGER.debug("Trying to reconnect in %s seconds", delay)
                await asyncio.sleep(delay)
                # We need to inform HA about the state in case we are coming
                # back from a disconnected state.
                await self.async_update_ha_state(force_refresh=True)
                delay = min(2 * delay, 300)

            _LOGGER.info("Reconnected to %s", self.name)

        self.dev.on_notification(VolumeChange, _volume_changed)
        self.dev.on_notification(ContentChange, _source_changed)
        self.dev.on_notification(PowerChange, _power_changed)
        self.dev.on_notification(ConnectChange, _try_reconnect)

        async def listen_events():
            await self.dev.listen_notifications()

        async def handle_stop(event):
            await self.dev.stop_listen_notifications()

        self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, handle_stop)

        self.hass.loop.create_task(listen_events())

    @property
    def name(self):
        """Return name of the device."""
        return self._name

    @property
    def unique_id(self):
        """Return a unique ID."""
        return self._sysinfo.macAddr

    @property
    def available(self):
        """Return availability of the device."""
        return self._available

    async def async_set_sound_setting(self, name, value):
        """Change a setting on the device."""
        await self.dev.set_sound_settings(name, value)

    async def async_update(self):
        """Fetch updates from the device."""
        from songpal import SongpalException
        try:
            volumes = await self.dev.get_volume_information()
            if not volumes:
                _LOGGER.error("Got no volume controls, bailing out")
                self._available = False
                return

            if len(volumes) > 1:
                _LOGGER.debug("Got %s volume controls, using the first one",
                              volumes)

            volume = volumes[0]
            _LOGGER.debug("Current volume: %s", volume)

            self._volume_max = volume.maxVolume
            self._volume_min = volume.minVolume
            self._volume = volume.volume
            self._volume_control = volume
            self._is_muted = self._volume_control.is_muted

            status = await self.dev.get_power()
            self._state = status.status
            _LOGGER.debug("Got state: %s", status)

            inputs = await self.dev.get_inputs()
            _LOGGER.debug("Got ins: %s", inputs)

            self._sources = OrderedDict()
            for input_ in inputs:
                self._sources[input_.uri] = input_
                if input_.active:
                    self._active_source = input_

            _LOGGER.debug("Active source: %s", self._active_source)

            self._available = True

            # activate notifications if wanted
            if not self._poll:
                await self.hass.async_create_task(
                    self.async_activate_websocket())
        except SongpalException as ex:
            _LOGGER.error("Unable to update: %s", ex)
            self._available = False

    async def async_select_source(self, source):
        """Select source."""
        for out in self._sources.values():
            if out.title == source:
                await out.activate()
                return

        _LOGGER.error("Unable to find output: %s", source)

    @property
    def source_list(self):
        """Return list of available sources."""
        return [src.title for src in self._sources.values()]

    @property
    def state(self):
        """Return current state."""
        if self._state:
            return STATE_ON
        return STATE_OFF

    @property
    def source(self):
        """Return currently active source."""
        # Avoid a KeyError when _active_source is not (yet) populated
        return getattr(self._active_source, 'title', None)

    @property
    def volume_level(self):
        """Return volume level."""
        volume = self._volume / self._volume_max
        return volume

    async def async_set_volume_level(self, volume):
        """Set volume level."""
        volume = int(volume * self._volume_max)
        _LOGGER.debug("Setting volume to %s", volume)
        return await self._volume_control.set_volume(volume)

    async def async_volume_up(self):
        """Set volume up."""
        return await self._volume_control.set_volume("+1")

    async def async_volume_down(self):
        """Set volume down."""
        return await self._volume_control.set_volume("-1")

    async def async_turn_on(self):
        """Turn the device on."""
        return await self.dev.set_power(True)

    async def async_turn_off(self):
        """Turn the device off."""
        return await self.dev.set_power(False)

    async def async_mute_volume(self, mute):
        """Mute or unmute the device."""
        _LOGGER.debug("Set mute: %s", mute)
        return await self._volume_control.set_mute(mute)

    @property
    def is_volume_muted(self):
        """Return whether the device is muted."""
        return self._is_muted

    @property
    def supported_features(self):
        """Return supported features."""
        return SUPPORT_SONGPAL
Exemplo n.º 11
0
class SongpalDevice(MediaPlayerDevice):
    """Class representing a Songpal device."""

    def __init__(self, name, endpoint, poll=False):
        """Init."""
        from songpal import Device
        self._name = name
        self._endpoint = endpoint
        self._poll = poll
        self.dev = Device(self._endpoint)
        self._sysinfo = None

        self._state = False
        self._available = False
        self._initialized = False

        self._volume_control = None
        self._volume_min = 0
        self._volume_max = 1
        self._volume = 0
        self._is_muted = False

        self._active_source = None
        self._sources = {}

    @property
    def should_poll(self):
        """Return True if the device should be polled."""
        return self._poll

    async def initialize(self):
        """Initialize the device."""
        await self.dev.get_supported_methods()
        self._sysinfo = await self.dev.get_system_info()

    async def async_activate_websocket(self):
        """Activate websocket for listening if wanted."""
        _LOGGER.info("Activating websocket connection..")
        from songpal import (VolumeChange, ContentChange,
                             PowerChange, ConnectChange)

        async def _volume_changed(volume: VolumeChange):
            _LOGGER.debug("Volume changed: %s", volume)
            self._volume = volume.volume
            self._is_muted = volume.mute
            await self.async_update_ha_state()

        async def _source_changed(content: ContentChange):
            _LOGGER.debug("Source changed: %s", content)
            if content.is_input:
                self._active_source = self._sources[content.source]
                _LOGGER.debug("New active source: %s", self._active_source)
                await self.async_update_ha_state()
            else:
                _LOGGER.debug("Got non-handled content change: %s",
                              content)

        async def _power_changed(power: PowerChange):
            _LOGGER.debug("Power changed: %s", power)
            self._state = power.status
            await self.async_update_ha_state()

        async def _try_reconnect(connect: ConnectChange):
            _LOGGER.error("Got disconnected with %s, trying to reconnect.",
                          connect.exception)
            self._available = False
            self.dev.clear_notification_callbacks()
            await self.async_update_ha_state()

            # Try to reconnect forever, a successful reconnect will initialize
            # the websocket connection again.
            delay = 10
            while not self._available:
                _LOGGER.debug("Trying to reconnect in %s seconds", delay)
                await asyncio.sleep(delay)
                # We need to inform HA about the state in case we are coming
                # back from a disconnected state.
                await self.async_update_ha_state(force_refresh=True)
                delay = min(2*delay, 300)

            _LOGGER.info("Reconnected to %s", self.name)

        self.dev.on_notification(VolumeChange, _volume_changed)
        self.dev.on_notification(ContentChange, _source_changed)
        self.dev.on_notification(PowerChange, _power_changed)
        self.dev.on_notification(ConnectChange, _try_reconnect)

        async def listen_events():
            await self.dev.listen_notifications()

        async def handle_stop(event):
            await self.dev.stop_listen_notifications()

        self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, handle_stop)

        self.hass.loop.create_task(listen_events())

    @property
    def name(self):
        """Return name of the device."""
        return self._name

    @property
    def unique_id(self):
        """Return a unique ID."""
        return self._sysinfo.macAddr

    @property
    def available(self):
        """Return availability of the device."""
        return self._available

    async def async_set_sound_setting(self, name, value):
        """Change a setting on the device."""
        await self.dev.set_sound_settings(name, value)

    async def async_update(self):
        """Fetch updates from the device."""
        from songpal import SongpalException
        try:
            volumes = await self.dev.get_volume_information()
            if not volumes:
                _LOGGER.error("Got no volume controls, bailing out")
                self._available = False
                return

            if len(volumes) > 1:
                _LOGGER.debug(
                    "Got %s volume controls, using the first one", volumes)

            volume = volumes[0]
            _LOGGER.debug("Current volume: %s", volume)

            self._volume_max = volume.maxVolume
            self._volume_min = volume.minVolume
            self._volume = volume.volume
            self._volume_control = volume
            self._is_muted = self._volume_control.is_muted

            status = await self.dev.get_power()
            self._state = status.status
            _LOGGER.debug("Got state: %s", status)

            inputs = await self.dev.get_inputs()
            _LOGGER.debug("Got ins: %s", inputs)

            self._sources = OrderedDict()
            for input_ in inputs:
                self._sources[input_.uri] = input_
                if input_.active:
                    self._active_source = input_

            _LOGGER.debug("Active source: %s", self._active_source)

            self._available = True

            # activate notifications if wanted
            if not self._poll:
                await self.hass.async_create_task(
                    self.async_activate_websocket())
        except SongpalException as ex:
            _LOGGER.error("Unable to update: %s", ex)
            self._available = False

    async def async_select_source(self, source):
        """Select source."""
        for out in self._sources.values():
            if out.title == source:
                await out.activate()
                return

        _LOGGER.error("Unable to find output: %s", source)

    @property
    def source_list(self):
        """Return list of available sources."""
        return [src.title for src in self._sources.values()]

    @property
    def state(self):
        """Return current state."""
        if self._state:
            return STATE_ON
        return STATE_OFF

    @property
    def source(self):
        """Return currently active source."""
        # Avoid a KeyError when _active_source is not (yet) populated
        return getattr(self._active_source, 'title', None)

    @property
    def volume_level(self):
        """Return volume level."""
        volume = self._volume / self._volume_max
        return volume

    async def async_set_volume_level(self, volume):
        """Set volume level."""
        volume = int(volume * self._volume_max)
        _LOGGER.debug("Setting volume to %s", volume)
        return await self._volume_control.set_volume(volume)

    async def async_volume_up(self):
        """Set volume up."""
        return await self._volume_control.set_volume("+1")

    async def async_volume_down(self):
        """Set volume down."""
        return await self._volume_control.set_volume("-1")

    async def async_turn_on(self):
        """Turn the device on."""
        return await self.dev.set_power(True)

    async def async_turn_off(self):
        """Turn the device off."""
        return await self.dev.set_power(False)

    async def async_mute_volume(self, mute):
        """Mute or unmute the device."""
        _LOGGER.debug("Set mute: %s", mute)
        return await self._volume_control.set_mute(mute)

    @property
    def is_volume_muted(self):
        """Return whether the device is muted."""
        return self._is_muted

    @property
    def supported_features(self):
        """Return supported features."""
        return SUPPORT_SONGPAL