diff --git a/.coveragerc b/.coveragerc index aced69a4714..6f96cccaf5f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -407,6 +407,7 @@ omit = homeassistant/components/fritzbox_callmonitor/const.py homeassistant/components/fritzbox_callmonitor/base.py homeassistant/components/fritzbox_callmonitor/sensor.py + homeassistant/components/frontier_silicon/const.py homeassistant/components/frontier_silicon/media_player.py homeassistant/components/futurenow/light.py homeassistant/components/garadget/cover.py diff --git a/CODEOWNERS b/CODEOWNERS index 259fbc77aab..3772a7f2dfa 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -364,6 +364,7 @@ build.json @home-assistant/supervisor /tests/components/fronius/ @nielstron @farmio /homeassistant/components/frontend/ @home-assistant/frontend /tests/components/frontend/ @home-assistant/frontend +/homeassistant/components/frontier_silicon/ @wlcrs /homeassistant/components/garages_amsterdam/ @klaasnicolaas /tests/components/garages_amsterdam/ @klaasnicolaas /homeassistant/components/gdacs/ @exxamalte diff --git a/homeassistant/components/frontier_silicon/const.py b/homeassistant/components/frontier_silicon/const.py new file mode 100644 index 00000000000..4638e63c2f2 --- /dev/null +++ b/homeassistant/components/frontier_silicon/const.py @@ -0,0 +1,6 @@ +"""Constants for the Frontier Silicon Media Player integration.""" + +DOMAIN = "frontier_silicon" + +DEFAULT_PIN = "1234" +DEFAULT_PORT = 80 diff --git a/homeassistant/components/frontier_silicon/manifest.json b/homeassistant/components/frontier_silicon/manifest.json index 3eb982e8118..20092b941a9 100644 --- a/homeassistant/components/frontier_silicon/manifest.json +++ b/homeassistant/components/frontier_silicon/manifest.json @@ -2,7 +2,7 @@ "domain": "frontier_silicon", "name": "Frontier Silicon", "documentation": "https://www.home-assistant.io/integrations/frontier_silicon", - "requirements": ["afsapi==0.0.4"], - "codeowners": [], - "iot_class": "local_push" + "requirements": ["afsapi==0.2.4"], + "codeowners": ["@wlcrs"], + "iot_class": "local_polling" } diff --git a/homeassistant/components/frontier_silicon/media_player.py b/homeassistant/components/frontier_silicon/media_player.py index 034762a09ac..66d1f304b1e 100644 --- a/homeassistant/components/frontier_silicon/media_player.py +++ b/homeassistant/components/frontier_silicon/media_player.py @@ -3,8 +3,7 @@ from __future__ import annotations import logging -from afsapi import AFSAPI -import requests +from afsapi import AFSAPI, ConnectionError as FSConnectionError, PlayState import voluptuous as vol from homeassistant.components.media_player import ( @@ -20,25 +19,27 @@ from homeassistant.const import ( CONF_PORT, STATE_IDLE, STATE_OFF, + STATE_OPENING, STATE_PAUSED, STATE_PLAYING, + STATE_UNAVAILABLE, STATE_UNKNOWN, ) from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -_LOGGER = logging.getLogger(__name__) +from .const import DEFAULT_PIN, DEFAULT_PORT, DOMAIN -DEFAULT_PORT = 80 -DEFAULT_PASSWORD = "1234" +_LOGGER = logging.getLogger(__name__) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, + vol.Optional(CONF_PASSWORD, default=DEFAULT_PIN): cv.string, vol.Optional(CONF_NAME): cv.string, } ) @@ -52,8 +53,14 @@ async def async_setup_platform( ) -> None: """Set up the Frontier Silicon platform.""" if discovery_info is not None: + webfsapi_url = await AFSAPI.get_webfsapi_endpoint( + discovery_info["ssdp_description"] + ) + afsapi = AFSAPI(webfsapi_url, DEFAULT_PIN) + + name = await afsapi.get_friendly_name() async_add_entities( - [AFSAPIDevice(discovery_info["ssdp_description"], DEFAULT_PASSWORD, None)], + [AFSAPIDevice(name, afsapi)], True, ) return @@ -64,11 +71,12 @@ async def async_setup_platform( name = config.get(CONF_NAME) try: - async_add_entities( - [AFSAPIDevice(f"http://{host}:{port}/device", password, name)], True + webfsapi_url = await AFSAPI.get_webfsapi_endpoint( + f"http://{host}:{port}/device" ) - _LOGGER.debug("FSAPI device %s:%s -> %s", host, port, password) - except requests.exceptions.RequestException: + afsapi = AFSAPI(webfsapi_url, password) + async_add_entities([AFSAPIDevice(name, afsapi)], True) + except FSConnectionError: _LOGGER.error( "Could not add the FSAPI device at %s:%s -> %s", host, port, password ) @@ -93,10 +101,15 @@ class AFSAPIDevice(MediaPlayerEntity): | MediaPlayerEntityFeature.SELECT_SOURCE ) - def __init__(self, device_url, password, name): + def __init__(self, name: str | None, afsapi: AFSAPI) -> None: """Initialize the Frontier Silicon API device.""" - self._device_url = device_url - self._password = password + self.fs_device = afsapi + + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, afsapi.webfsapi_endpoint)}, + name=name, + ) + self._state = None self._name = name @@ -110,17 +123,7 @@ class AFSAPIDevice(MediaPlayerEntity): self._max_volume = None self._volume_level = None - # Properties - @property - def fs_device(self): - """ - Create a fresh fsapi session. - - A new session is created for each request in case someone else - connected to the device in between the updates and invalidated the - existing session (i.e UNDOK). - """ - return AFSAPI(self._device_url, self._password) + self.__modes_by_label = None @property def name(self): @@ -175,43 +178,61 @@ class AFSAPIDevice(MediaPlayerEntity): async def async_update(self): """Get the latest date and update device state.""" - fs_device = self.fs_device - - if not self._name: - self._name = await fs_device.get_friendly_name() - - if not self._source_list: - self._source_list = await fs_device.get_mode_list() - - # The API seems to include 'zero' in the number of steps (e.g. if the range is - # 0-40 then get_volume_steps returns 41) subtract one to get the max volume. - # If call to get_volume fails set to 0 and try again next time. - if not self._max_volume: - self._max_volume = int(await fs_device.get_volume_steps() or 1) - 1 - - if await fs_device.get_power(): - status = await fs_device.get_play_status() - self._state = { - "playing": STATE_PLAYING, - "paused": STATE_PAUSED, - "stopped": STATE_IDLE, - "unknown": STATE_UNKNOWN, - None: STATE_IDLE, - }.get(status, STATE_UNKNOWN) + afsapi = self.fs_device + try: + if await afsapi.get_power(): + status = await afsapi.get_play_status() + self._state = { + PlayState.PLAYING: STATE_PLAYING, + PlayState.PAUSED: STATE_PAUSED, + PlayState.STOPPED: STATE_IDLE, + PlayState.LOADING: STATE_OPENING, + None: STATE_IDLE, + }.get(status, STATE_UNKNOWN) + else: + self._state = STATE_OFF + except FSConnectionError: + if self._attr_available: + _LOGGER.warning( + "Could not connect to %s. Did it go offline?", + self._name or afsapi.webfsapi_endpoint, + ) + self._state = STATE_UNAVAILABLE + self._attr_available = False else: - self._state = STATE_OFF + if not self._attr_available: + _LOGGER.info( + "Reconnected to %s", + self._name or afsapi.webfsapi_endpoint, + ) - if self._state != STATE_OFF: - info_name = await fs_device.get_play_name() - info_text = await fs_device.get_play_text() + self._attr_available = True + if not self._name: + self._name = await afsapi.get_friendly_name() + + if not self._source_list: + self.__modes_by_label = { + mode.label: mode.key for mode in await afsapi.get_modes() + } + self._source_list = list(self.__modes_by_label.keys()) + + # The API seems to include 'zero' in the number of steps (e.g. if the range is + # 0-40 then get_volume_steps returns 41) subtract one to get the max volume. + # If call to get_volume fails set to 0 and try again next time. + if not self._max_volume: + self._max_volume = int(await afsapi.get_volume_steps() or 1) - 1 + + if self._state not in [STATE_OFF, STATE_UNAVAILABLE]: + info_name = await afsapi.get_play_name() + info_text = await afsapi.get_play_text() self._title = " - ".join(filter(None, [info_name, info_text])) - self._artist = await fs_device.get_play_artist() - self._album_name = await fs_device.get_play_album() + self._artist = await afsapi.get_play_artist() + self._album_name = await afsapi.get_play_album() - self._source = await fs_device.get_mode() - self._mute = await fs_device.get_mute() - self._media_image_url = await fs_device.get_play_graphic() + self._source = (await afsapi.get_mode()).label + self._mute = await afsapi.get_mute() + self._media_image_url = await afsapi.get_play_graphic() volume = await self.fs_device.get_volume() @@ -296,4 +317,5 @@ class AFSAPIDevice(MediaPlayerEntity): async def async_select_source(self, source): """Select input source.""" - await self.fs_device.set_mode(source) + await self.fs_device.set_power(True) + await self.fs_device.set_mode(self.__modes_by_label.get(source)) diff --git a/requirements_all.txt b/requirements_all.txt index de4bcbef89e..16b059d385e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -86,7 +86,7 @@ adguardhome==0.5.1 advantage_air==0.3.1 # homeassistant.components.frontier_silicon -afsapi==0.0.4 +afsapi==0.2.4 # homeassistant.components.agent_dvr agent-py==0.0.23