"""Initialize pyvizio.""" from asyncio import sleep from datetime import datetime, timedelta import logging from typing import Any, Dict, KeysView, List, Optional, Union from urllib.parse import urlsplit from aiohttp import ClientSession import requests import xmltodict from pyvizio.api._protocol import KEY_CODE, async_invoke_api, async_invoke_api_auth from pyvizio.api.apps import ( AppConfig, GetCurrentAppConfigCommand, GetCurrentAppNameCommand, LaunchAppConfigCommand, LaunchAppNameCommand, ) from pyvizio.api.base import CommandBase from pyvizio.api.input import ( ChangeInputCommand, GetCurrentInputCommand, GetInputsListCommand, InputItem, ) from pyvizio.api.item import ( GetAltESNCommand, GetAltSerialNumberCommand, GetAltVersionCommand, GetCurrentPowerStateCommand, GetCurrentChargingStatusCommand, GetBatteryLevelCommand, GetDeviceInfoCommand, GetESNCommand, GetModelNameCommand, GetSerialNumberCommand, GetVersionCommand, ) from pyvizio.api.pair import ( BeginPairCommand, BeginPairResponse, CancelPairCommand, PairChallengeCommand, PairChallengeResponse, ) from pyvizio.api.remote import EmulateRemoteCommand from pyvizio.api.settings import ( ChangeSettingCommand, GetAllSettingsCommand, GetAllSettingsOptionsCommand, GetAllSettingsOptionsXListCommand, GetAllSettingTypesCommand, GetSettingCommand, GetSettingOptionsCommand, GetSettingOptionsXListCommand, ) from pyvizio.const import ( APP_HOME, APPS, DEFAULT_DEVICE_CLASS, DEFAULT_PORTS, DEFAULT_TIMEOUT, DEVICE_CLASS_SPEAKER, DEVICE_CLASS_TV, DEVICE_CLASS_CRAVE360, MAX_VOLUME, ) from pyvizio.discovery.ssdp import SSDPDevice, discover as discover_ssdp from pyvizio.discovery.zeroconf import ZeroconfDevice, discover as discover_zc from pyvizio.helpers import async_to_sync, open_port from pyvizio.util import gen_apps_list_from_url _LOGGER = logging.getLogger(__name__) class VizioAsync: """Asynchronous class to interact with Vizio SmartCast devices.""" def __init__( self, device_id: str, ip: str, name: str, auth_token: Optional[str] = "", device_type: str = DEFAULT_DEVICE_CLASS, session: Optional[ClientSession] = None, timeout: int = DEFAULT_TIMEOUT, ) -> None: """Initialize asynchronous class to interact with Vizio SmartCast devices.""" self.device_type = device_type.lower() if ( self.device_type != DEVICE_CLASS_TV and self.device_type != DEVICE_CLASS_SPEAKER # noqa: W503 and self.device_type != DEVICE_CLASS_CRAVE360 ): raise Exception( f"Invalid device type specified. Use either '{DEVICE_CLASS_TV}' or " f"'{DEVICE_CLASS_SPEAKER}' or '{DEVICE_CLASS_CRAVE360}''" ) self._auth_token = auth_token self.ip = ip self.name = name self.device_id = device_id self._session = session self._timeout = timeout self._latest_apps = None self._latest_apps_last_updated = None def __repr__(self) -> str: return f"{type(self).__name__}({self.__dict__})" def __eq__(self, other) -> bool: return self is other or self.__dict__ == other.__dict__ async def __add_port(self) -> None: """Asynchronously add first open port from known ports list to `ip` property.""" for port in DEFAULT_PORTS: if await open_port(self.ip, port): self.ip = f"{self.ip}:{port}" async def __invoke_api( self, cmd: CommandBase, log_api_exception: bool = True ) -> Any: """Asynchronously call SmartCast API without auth token.""" if ":" not in self.ip: await self.__add_port() return await async_invoke_api( self.ip, cmd, _LOGGER, custom_timeout=self._timeout, log_api_exception=log_api_exception, session=self._session, ) async def __invoke_api_auth( self, cmd: CommandBase, log_api_exception: bool = True ) -> Any: """Asynchronously call SmartCast API with auth token.""" if ":" not in self.ip: await self.__add_port() return await async_invoke_api_auth( self.ip, cmd, _LOGGER, auth_token=self._auth_token, custom_timeout=self._timeout, log_api_exception=log_api_exception, session=self._session, ) async def __invoke_api_may_need_auth( self, cmd: CommandBase, log_api_exception: bool = True ) -> Any: """Asynchronously call SmartCast API command with or without auth token depending on device type.""" if not self._auth_token: if self.device_type == DEVICE_CLASS_SPEAKER or self.device_type == DEVICE_CLASS_CRAVE360: return await self.__invoke_api(cmd, log_api_exception=log_api_exception) else: raise Exception( f"Empty auth token. To target a speaker and bypass auth " f"requirements, pass '{DEVICE_CLASS_SPEAKER}' as device_type" ) return await self.__invoke_api_auth(cmd, log_api_exception=log_api_exception) async def __remote( self, key_list: Union[str, List[str]], log_api_exception: bool = True ) -> bool: """Asynchronously call key press API with list of keys.""" key_codes = [] if not isinstance(key_list, list): key_list = [key_list] for key in key_list: if key not in KEY_CODE[self.device_type]: _LOGGER.error( "Key Code of '%s' not found for device type of '%s'", key, self.device_type, ) return False else: key_codes.append(KEY_CODE[self.device_type][key]) result = await self.__invoke_api_may_need_auth( EmulateRemoteCommand(key_codes, self.device_type), log_api_exception=log_api_exception, ) return result is not None async def __remote_multiple( self, key_code: str, num: int, log_api_exception: bool = True ) -> bool: """Asynchronously call key press API with list of same key repeated multiple times.""" key_codes = [key_code for _ in range(num)] return await self.__remote(key_codes, log_api_exception=log_api_exception) async def __get_cached_apps_list(self) -> List[str]: if ( self._latest_apps and datetime.now() - self._latest_apps_last_updated < timedelta(days=1) ): await sleep(0) return self._latest_apps else: self._latest_apps = ( await gen_apps_list_from_url(session=self._session) or APPS ) self._latest_apps_last_updated = datetime.now() return self._latest_apps @staticmethod def discovery_zeroconf(timeout: int = DEFAULT_TIMEOUT) -> List[ZeroconfDevice]: """Discover Vizio devices on network using zeroconf.""" results = discover_zc("_viziocast._tcp.local.", timeout=timeout) _LOGGER.info(results) return results @staticmethod def discovery_ssdp(timeout: int = DEFAULT_TIMEOUT) -> List[SSDPDevice]: """Discover Vizio devices on network using SSDP.""" results = [] devices = discover_ssdp( "urn:dial-multiscreen-org:device:dial:1", timeout=timeout ) for dev in devices: data = xmltodict.parse( requests.get(dev.location, verify=False, timeout=timeout).text ) if "root" not in data or "device" not in data["root"]: continue root = data["root"]["device"] manufacturer = root["manufacturer"] if manufacturer is None or "VIZIO" != manufacturer: continue split_url = urlsplit(dev.location) device = SSDPDevice( split_url.hostname, root["friendlyName"], root["modelName"], root["UDN"] ) results.append(device) _LOGGER.info(results) return results @staticmethod async def validate_ha_config( ip: str, auth_token: str, device_type: str, session: Optional[ClientSession] = None, timeout: int = DEFAULT_TIMEOUT, ) -> bool: """Asynchronously return whether or not HomeAssistant config will allow HomeAssistant to make successful calls to Vizio SmartCast API.""" return await VizioAsync( "", ip, "", auth_token, device_type, session=session, timeout=timeout ).can_connect_with_auth_check() @staticmethod async def get_unique_id( ip: str, device_type: str, timeout: int = DEFAULT_TIMEOUT, session: Optional[ClientSession] = None, ) -> Optional[str]: """Asynchronously get unique identifier for Vizio device.""" return await VizioAsync( "", ip, "", "", device_type, session=session, timeout=timeout ).get_serial_number(log_api_exception=False) async def can_connect_with_auth_check(self) -> bool: """Asynchronously return whether or not device API can be connected to with valid authorization.""" return bool( await VizioAsync.get_all_audio_settings(self, log_api_exception=False) ) async def can_connect_no_auth_check(self) -> bool: """Asynchronously return whether or not device API can be connected to regardless of authorization.""" return bool( await self.__invoke_api( GetDeviceInfoCommand(self.device_type), log_api_exception=False ) ) async def get_esn(self, log_api_exception: bool = True) -> Optional[str]: """Asynchronously get device's ESN (electronic serial number?).""" item = await self.__invoke_api_may_need_auth( GetESNCommand(self.device_type), log_api_exception=log_api_exception ) or await self.__invoke_api_may_need_auth( GetAltESNCommand(self.device_type), log_api_exception=log_api_exception ) if item and item.value: return item.value return None async def get_serial_number(self, log_api_exception: bool = True) -> Optional[str]: """Asynchronously get device's serial number.""" item = await self.__invoke_api( GetSerialNumberCommand(self.device_type), log_api_exception=log_api_exception, ) or await self.__invoke_api( GetAltSerialNumberCommand(self.device_type), log_api_exception=log_api_exception, ) if item and item.value: return item.value return None async def get_version(self, log_api_exception: bool = True) -> Optional[str]: """Asynchronously get SmartCast software version on device.""" item = await self.__invoke_api( GetVersionCommand(self.device_type), log_api_exception=log_api_exception ) or await self.__invoke_api( GetAltVersionCommand(self.device_type), log_api_exception=log_api_exception ) if item and item.value: return item.value return None async def get_model_name(self, log_api_exception: bool = True) -> Optional[str]: """Asynchronously get device's model number.""" return await self.__invoke_api( GetModelNameCommand(self.device_type), log_api_exception=log_api_exception ) async def start_pair( self, log_api_exception: bool = True ) -> Optional[BeginPairResponse]: """Asynchronously begin pairing process to obtain challenge type and challenge token.""" return await self.__invoke_api( BeginPairCommand(self.device_id, self.name, self.device_type), log_api_exception=log_api_exception, ) async def stop_pair(self, log_api_exception: bool = True) -> Optional[bool]: """Asynchronously cancel pairing process.""" return await self.__invoke_api( CancelPairCommand(self.device_id, self.name, self.device_type), log_api_exception=log_api_exception, ) async def pair( self, ch_type: Union[int, str], token: Union[int, str], pin: str = "", log_api_exception: bool = True, ) -> Optional[PairChallengeResponse]: """Asynchronously complete pairing process to obtain auth token.""" if self.device_type == DEVICE_CLASS_SPEAKER: pin = "0000" return await self.__invoke_api( PairChallengeCommand(self.device_id, ch_type, token, pin, self.device_type), log_api_exception=log_api_exception, ) async def get_inputs_list( self, log_api_exception: bool = True ) -> Optional[List[InputItem]]: """Asynchronously get list of available inputs.""" return await self.__invoke_api_may_need_auth( GetInputsListCommand(self.device_type), log_api_exception=log_api_exception ) async def get_current_input(self, log_api_exception: bool = True) -> Optional[str]: """Asynchronously get device's active input.""" item = await self.__invoke_api_may_need_auth( GetCurrentInputCommand(self.device_type), log_api_exception=log_api_exception, ) if item: return item.meta_name return None async def next_input(self, log_api_exception: bool = True) -> Optional[bool]: """Asynchronously switch active input to next input.""" # HACK: Single call just invoking overlay menu with current input return await self.__remote_multiple( "INPUT_NEXT", 2, log_api_exception=log_api_exception ) async def set_input( self, name: str, log_api_exception: bool = True ) -> Optional[bool]: """Asynchronously switch active input to named input.""" curr_input_item = await self.__invoke_api_may_need_auth( GetCurrentInputCommand(self.device_type), log_api_exception=log_api_exception, ) if not curr_input_item: _LOGGER.error("Couldn't detect current input") return None return await self.__invoke_api_may_need_auth( ChangeInputCommand(self.device_type, curr_input_item.id, name), log_api_exception=log_api_exception, ) async def get_power_state(self, log_api_exception: bool = True) -> Optional[bool]: """Asynchronously get device's current power state.""" item = await self.__invoke_api_may_need_auth( GetCurrentPowerStateCommand(self.device_type), log_api_exception=log_api_exception, ) if item: return bool(item.value) return None async def get_charging_status(self, log_api_exception: bool = True) -> Optional[int]: """Asynchronously get device's current charging state.""" item = await self.__invoke_api_may_need_auth( GetCurrentChargingStatusCommand(self.device_type), log_api_exception=log_api_exception, ) if item: return int(item.value) return None async def get_battery_level(self, log_api_exception: bool = True) -> Optional[int]: """Asynchronously get device's current battery level (will be 0 if charging).""" item = await self.__invoke_api_may_need_auth( GetBatteryLevelCommand(self.device_type), log_api_exception=log_api_exception, ) if item: return int(item.value) return None async def pow_on(self, log_api_exception: bool = True) -> Optional[bool]: """Asynchronously power device on.""" return await self.__remote("POW_ON", log_api_exception=log_api_exception) async def pow_off(self, log_api_exception: bool = True) -> Optional[bool]: """Asynchronously power device off.""" return await self.__remote("POW_OFF", log_api_exception=log_api_exception) async def pow_toggle(self, log_api_exception: bool = True) -> Optional[bool]: """Asynchronously toggle device power.""" return await self.__remote("POW_TOGGLE", log_api_exception=log_api_exception) async def vol_up( self, num: int = 1, log_api_exception: bool = True ) -> Optional[bool]: """Asynchronously increase volume by number of steps.""" return await self.__remote_multiple( "VOL_UP", num, log_api_exception=log_api_exception ) async def vol_down( self, num: int = 1, log_api_exception: bool = True ) -> Optional[bool]: """Asynchronously decrease volume by number of steps.""" return await self.__remote_multiple( "VOL_DOWN", num, log_api_exception=log_api_exception ) async def get_current_volume(self, log_api_exception: bool = True) -> Optional[int]: """Asynchronously get device's current volume level.""" volume = await VizioAsync.get_audio_setting( self, "volume", log_api_exception=log_api_exception ) return int(volume) if volume else None async def is_muted(self, log_api_exception: bool = True) -> Optional[bool]: """Asynchronously determine whether or not device is muted.""" # If None is returned lower() will fail, if not we can do a simple boolean check try: return ( str( await VizioAsync.get_audio_setting( self, "mute", log_api_exception=log_api_exception ) ).lower() == "on" ) except AttributeError: return None def get_max_volume(self) -> int: """Get device's max volume based on device type.""" return MAX_VOLUME[self.device_type] async def ch_up( self, num: int = 1, log_api_exception: bool = True ) -> Optional[bool]: """Asynchronously channel up by number of steps.""" return await self.__remote_multiple( "CH_UP", num, log_api_exception=log_api_exception ) async def ch_down( self, num: int = 1, log_api_exception: bool = True ) -> Optional[bool]: """Asynchronously channel down by number of steps.""" return await self.__remote_multiple( "CH_DOWN", num, log_api_exception=log_api_exception ) async def ch_prev(self, log_api_exception: bool = True) -> Optional[bool]: """Asynchronously go to previous channel.""" return await self.__remote("CH_PREV", log_api_exception=log_api_exception) async def mute_on(self, log_api_exception: bool = True) -> Optional[bool]: """Asynchronously mute sound.""" return await self.__remote("MUTE_ON", log_api_exception=log_api_exception) async def mute_off(self, log_api_exception: bool = True) -> Optional[bool]: """Asynchronously unmute sound.""" return await self.__remote("MUTE_OFF", log_api_exception=log_api_exception) async def mute_toggle(self, log_api_exception: bool = True) -> Optional[bool]: """Asynchronously toggle sound mute.""" return await self.__remote("MUTE_TOGGLE", log_api_exception=log_api_exception) async def play(self, log_api_exception: bool = True) -> Optional[bool]: """Asynchronously emulate 'play' key press.""" return await self.__remote("PLAY", log_api_exception=log_api_exception) async def pause(self, log_api_exception: bool = True) -> Optional[bool]: """Asynchronously emulate 'pause' key press.""" return await self.__remote("PAUSE", log_api_exception=log_api_exception) async def remote(self, key: str, log_api_exception: bool = True) -> Optional[bool]: """Asynchronously emulate key press by key name.""" return await self.__remote(key, log_api_exception=log_api_exception) def get_remote_keys_list(self) -> KeysView[str]: """Get list of remote key names.""" return KEY_CODE[self.device_type].keys() async def get_setting_types_list( self, log_api_exception: bool = True ) -> Optional[List[str]]: """Asynchronously get list of all setting types.""" items = await self.__invoke_api_may_need_auth( GetAllSettingTypesCommand(self.device_type), log_api_exception=log_api_exception, ) if items: return items return None async def get_all_settings( self, setting_type: str, log_api_exception: bool = True ) -> Optional[Dict[str, Union[int, str]]]: """Asynchronously get all setting names and corresponding values.""" item = await self.__invoke_api_may_need_auth( GetAllSettingsCommand(self.device_type, setting_type), log_api_exception=log_api_exception, ) if item: return item return None async def get_all_settings_options( self, setting_type: str, log_api_exception: bool = True ) -> Optional[Dict[str, Union[int, str]]]: """Asynchronously get all setting names and corresponding options.""" item = await self.__invoke_api_may_need_auth( GetAllSettingsOptionsCommand(self.device_type, setting_type), log_api_exception=log_api_exception, ) if item: return item return None async def get_all_settings_options_xlist( self, setting_type: str, log_api_exception: bool = True ) -> Optional[Dict[str, Union[int, str]]]: """Asynchronously get all setting names and corresponding options for settings that are based on a user defined list.""" item = await self.__invoke_api_may_need_auth( GetAllSettingsOptionsXListCommand(self.device_type, setting_type), log_api_exception=log_api_exception, ) if item: return item return None async def get_setting( self, setting_type: str, setting_name: str, log_api_exception: bool = True ) -> Optional[Union[int, str]]: """Asynchronously get current value of named setting.""" item = await self.__invoke_api_may_need_auth( GetSettingCommand(self.device_type, setting_type, setting_name), log_api_exception=log_api_exception, ) # coerce to int if possible if item: try: return int(item.value) except ValueError: return item.value return None async def get_setting_options( self, setting_type: str, setting_name: str, log_api_exception: bool = True ) -> Optional[Union[List[str], Dict[str, Union[int, str]]]]: """Asynchronously get options of named setting.""" return await self.__invoke_api_may_need_auth( GetSettingOptionsCommand(self.device_type, setting_type, setting_name), log_api_exception=log_api_exception, ) async def get_setting_options_xlist( self, setting_type: str, setting_name: str, log_api_exception: bool = True ) -> Optional[List[str]]: """Asynchronously get options of named setting for settings based on a user defined list.""" return await self.__invoke_api_may_need_auth( GetSettingOptionsXListCommand(self.device_type, setting_type, setting_name), log_api_exception=log_api_exception, ) async def set_setting( self, setting_type: str, setting_name: str, new_value: Union[int, str], log_api_exception: bool = True, ) -> Optional[bool]: """Asynchronously set new value for setting.""" setting_item = await self.__invoke_api_may_need_auth( GetSettingCommand(self.device_type, setting_type, setting_name), log_api_exception=log_api_exception, ) if not setting_item: _LOGGER.error( "Couldn't detect setting for %s of setting type %s", setting_name, setting_type, ) return None return await self.__invoke_api_may_need_auth( ChangeSettingCommand( self.device_type, setting_item.id, setting_type, setting_name, new_value ), log_api_exception=log_api_exception, ) async def get_all_audio_settings( self, log_api_exception: bool = True ) -> Optional[Dict[str, Union[int, str]]]: """Asynchronously get all audio setting names and corresponding values.""" return await VizioAsync.get_all_settings( self, "audio", log_api_exception=log_api_exception ) async def get_all_audio_settings_options( self, log_api_exception: bool = True ) -> Optional[Dict[str, Union[int, str]]]: """Asynchronously get all audio setting names and corresponding options.""" return await VizioAsync.get_all_settings_options( self, "audio", log_api_exception=log_api_exception ) async def get_audio_setting( self, setting_name: str, log_api_exception: bool = True ) -> Optional[Union[int, str]]: """Asynchronously get current value of named audio setting.""" return await VizioAsync.get_setting( self, "audio", setting_name, log_api_exception=log_api_exception ) async def get_audio_setting_options( self, setting_name: str, log_api_exception: bool = True ) -> Optional[Union[List[str], Dict[str, Union[int, str]]]]: """Asynchronously get options of named audio setting.""" return await VizioAsync.get_setting_options( self, "audio", setting_name, log_api_exception=log_api_exception ) async def set_audio_setting( self, setting_name: str, new_value: Union[int, str], log_api_exception: bool = True, ) -> Optional[bool]: """Asynchronously set new value for named audio setting.""" return await VizioAsync.set_setting( self, "audio", setting_name, new_value, log_api_exception=log_api_exception ) @staticmethod async def get_apps_list( country: str = "all", apps_list: List[Dict[str, Union[str, List[Union[str, Dict[str, Any]]]]]] = None, session: ClientSession = None, ) -> List[str]: """Get list of known apps by name optionally filtered by supported country.""" # Assumes "*" means all countries are supported if not apps_list: apps_list = await gen_apps_list_from_url(session=session) or APPS if country.lower() != "all": return [ APP_HOME["name"], *sorted( [ str(app["name"]) for app in apps_list if "*" in app["country"] or country.lower() in app["country"] ] ), ] return [APP_HOME["name"], *sorted([str(app["name"]) for app in apps_list])] async def launch_app( self, app_name: str, apps_list: List[Dict[str, Union[str, List[Union[str, Dict[str, Any]]]]]] = None, log_api_exception: bool = True, ) -> Optional[bool]: """Asynchronously launch known app by name.""" if not apps_list: apps_list = await self.__get_cached_apps_list() return await self.__invoke_api_may_need_auth( LaunchAppNameCommand(self.device_type, app_name, apps_list), log_api_exception=log_api_exception, ) async def launch_app_config( self, APP_ID: str, NAME_SPACE: int, MESSAGE: str = None, log_api_exception: bool = True, ) -> Optional[bool]: """Asynchronously launch app using app's config values.""" return await self.__invoke_api_may_need_auth( LaunchAppConfigCommand(self.device_type, APP_ID, NAME_SPACE, MESSAGE), log_api_exception=log_api_exception, ) async def get_current_app( self, apps_list: List[Dict[str, Union[str, List[Union[str, Dict[str, Any]]]]]] = None, log_api_exception: bool = True, ) -> Optional[str]: """Asynchronously get name of currently running app. Returns const APP_NOT_RUNNING if no app is currently running, const UNKNOWN_APP if app config isn't known by pyvizio.""" if not apps_list: apps_list = await self.__get_cached_apps_list() return await self.__invoke_api_may_need_auth( GetCurrentAppNameCommand(self.device_type, apps_list), log_api_exception=log_api_exception, ) async def get_current_app_config( self, log_api_exception: bool = True ) -> Optional[AppConfig]: """Asynchronously get config values of currently running app. Returns empty AppConfig if no app is currently running.""" return await self.__invoke_api_may_need_auth( GetCurrentAppConfigCommand(self.device_type), log_api_exception=log_api_exception, ) async def async_guess_device_type( ip: str, port: Optional[str] = None, timeout: int = DEFAULT_TIMEOUT ) -> str: """ Attempt to guess the device type by getting power state with no auth token. NOTE: The `ip` and `port` values passed in have to be valid for the device in order for this to work. This function is being used as part of a zeroconf discovery workflow in HomeAssistant which is why it is safe to assume that `ip` and `port` are valid. """ if port: if ":" in ip: raise Exception("Port can't be included in both `ip` and `port` parameters") device = VizioAsync( "test", f"{ip}:{port}", "test", "", DEVICE_CLASS_SPEAKER, timeout=timeout ) else: if ":" not in ip: _LOGGER.warning( "May not return correct device type since a port was not specified." ) device = VizioAsync( "test", ip, "test", "", DEVICE_CLASS_SPEAKER, timeout=timeout ) if await device.can_connect_with_auth_check(): return DEVICE_CLASS_SPEAKER else: return DEVICE_CLASS_TV class Vizio(VizioAsync): """Synchronous class to interact with Vizio SmartCast devices.""" def __init__( self, device_id: str, ip: str, name: str, auth_token: str = "", device_type: str = DEFAULT_DEVICE_CLASS, timeout: int = DEFAULT_TIMEOUT, ) -> None: """Initialize synchronous class to interact with Vizio SmartCast devices.""" super(Vizio, self).__init__( device_id, ip, name, auth_token, device_type, session=None, timeout=timeout ) @staticmethod def discovery_zeroconf(timeout: int = DEFAULT_TIMEOUT) -> List[ZeroconfDevice]: """Discover Vizio devices on network using zeroconf.""" return super(Vizio, Vizio).discovery_zeroconf(timeout) @staticmethod def discovery_ssdp(timeout: int = DEFAULT_TIMEOUT) -> List[SSDPDevice]: """Discover Vizio devices on network using SSDP.""" return super(Vizio, Vizio).discovery_ssdp(timeout) @staticmethod @async_to_sync async def validate_ha_config( ip: str, auth_token: str, device_type: str, session: Optional[ClientSession] = None, timeout: int = DEFAULT_TIMEOUT, ) -> bool: """Return whether or not HomeAssistant config will allow HomeAssistant to make successful calls to Vizio SmartCast API.""" return await super(Vizio, Vizio).validate_ha_config( ip, auth_token, device_type, session=session, timeout=timeout ) @staticmethod @async_to_sync async def get_unique_id( ip: str, device_type: str, timeout: int = DEFAULT_TIMEOUT ) -> Optional[str]: """Get unique identifier for Vizio device.""" return await super(Vizio, Vizio).get_unique_id(ip, device_type, timeout=timeout) @async_to_sync async def can_connect_with_auth_check(self) -> bool: """Return whether or not device API can be connected to with valid authorization.""" return await super(Vizio, self).can_connect_with_auth_check() @async_to_sync async def can_connect_no_auth_check(self) -> bool: """Return whether or not device API can be connected to regardless of auth config.""" return await super(Vizio, self).can_connect_no_auth_check() @async_to_sync async def get_esn(self, log_api_exception: bool = True) -> Optional[str]: """Get device's ESN (electronic serial number?).""" return await super(Vizio, self).get_esn(log_api_exception=log_api_exception) @async_to_sync async def get_serial_number(self, log_api_exception: bool = True) -> Optional[str]: """Get device's serial number.""" return await super(Vizio, self).get_serial_number( log_api_exception=log_api_exception ) @async_to_sync async def get_version(self, log_api_exception: bool = True) -> Optional[str]: """Get SmartCast software version on device.""" return await super(Vizio, self).get_version(log_api_exception=log_api_exception) @async_to_sync async def get_model_name(self, log_api_exception: bool = True) -> Optional[str]: """Get device's model number.""" return await super(Vizio, self).get_model_name( log_api_exception=log_api_exception ) @async_to_sync async def start_pair( self, log_api_exception: bool = True ) -> Optional[BeginPairResponse]: """Begin pairing process to obtain challenge type and challenge token.""" return await super(Vizio, self).start_pair(log_api_exception=log_api_exception) @async_to_sync async def stop_pair(self, log_api_exception: bool = True) -> Optional[bool]: """Cancel pairing process.""" return await super(Vizio, self).stop_pair(log_api_exception=log_api_exception) @async_to_sync async def pair( self, ch_type: int, token: int, pin: str, log_api_exception: bool = True ) -> Optional[PairChallengeResponse]: """Complete pairing process to obtain auth token.""" return await super(Vizio, self).pair( ch_type, token, pin, log_api_exception=log_api_exception ) @async_to_sync async def get_inputs_list( self, log_api_exception: bool = True ) -> Optional[List[InputItem]]: """Get list of available inputs.""" return await super(Vizio, self).get_inputs_list( log_api_exception=log_api_exception ) @async_to_sync async def get_current_input(self, log_api_exception: bool = True) -> Optional[str]: """Get device's active input.""" return await super(Vizio, self).get_current_input( log_api_exception=log_api_exception ) @async_to_sync async def next_input(self, log_api_exception: bool = True) -> Optional[bool]: """Switch active input to next input.""" return await super(Vizio, self).next_input(log_api_exception=log_api_exception) @async_to_sync async def set_input( self, name: str, log_api_exception: bool = True ) -> Optional[bool]: """Switch active input to named input.""" return await super(Vizio, self).set_input( name, log_api_exception=log_api_exception ) @async_to_sync async def get_power_state(self, log_api_exception: bool = True) -> Optional[bool]: """Get device's current power state.""" return await super(Vizio, self).get_power_state( log_api_exception=log_api_exception ) @async_to_sync async def pow_on(self, log_api_exception: bool = True) -> Optional[bool]: """Power device off.""" return await super(Vizio, self).pow_on(log_api_exception=log_api_exception) @async_to_sync async def pow_off(self, log_api_exception: bool = True) -> Optional[bool]: """Power device off.""" return await super(Vizio, self).pow_off(log_api_exception=log_api_exception) @async_to_sync async def pow_toggle(self, log_api_exception: bool = True) -> Optional[bool]: """Toggle device power.""" return await super(Vizio, self).pow_toggle(log_api_exception=log_api_exception) @async_to_sync async def vol_up( self, num: int = 1, log_api_exception: bool = True ) -> Optional[bool]: """Increase volume by number of steps.""" return await super(Vizio, self).vol_up(num, log_api_exception=log_api_exception) @async_to_sync async def vol_down( self, num: int = 1, log_api_exception: bool = True ) -> Optional[bool]: """Decrease volume by number of steps.""" return await super(Vizio, self).vol_down( num, log_api_exception=log_api_exception ) @async_to_sync async def get_current_volume(self, log_api_exception: bool = True) -> Optional[int]: """Get device's current volume level.""" return await super(Vizio, self).get_current_volume( log_api_exception=log_api_exception ) @async_to_sync async def is_muted(self, log_api_exception: bool = True) -> Optional[bool]: """Return whether or not device is muted.""" return await super(Vizio, self).is_muted(log_api_exception=log_api_exception) def get_max_volume(self) -> int: """Return device's max volume based on device type.""" return super(Vizio, self).get_max_volume() @async_to_sync async def ch_up( self, num: int = 1, log_api_exception: bool = True ) -> Optional[bool]: """Channel up by number of steps.""" return await super(Vizio, self).ch_up(num, log_api_exception=log_api_exception) @async_to_sync async def ch_down( self, num: int = 1, log_api_exception: bool = True ) -> Optional[bool]: """Channel down by number of steps.""" return await super(Vizio, self).ch_down( num, log_api_exception=log_api_exception ) @async_to_sync async def ch_prev(self, log_api_exception: bool = True) -> Optional[bool]: """Go to previous channel.""" return await super(Vizio, self).ch_prev(log_api_exception=log_api_exception) @async_to_sync async def mute_on(self, log_api_exception: bool = True) -> Optional[bool]: """Mute sound.""" return await super(Vizio, self).mute_on(log_api_exception=log_api_exception) @async_to_sync async def mute_off(self, log_api_exception: bool = True) -> Optional[bool]: """Unmute sound.""" return await super(Vizio, self).mute_off(log_api_exception=log_api_exception) @async_to_sync async def mute_toggle(self, log_api_exception: bool = True) -> Optional[bool]: """Toggle sound mute.""" return await super(Vizio, self).mute_toggle(log_api_exception=log_api_exception) @async_to_sync async def play(self, log_api_exception: bool = True) -> Optional[bool]: """Emulate 'play' key press.""" return await super(Vizio, self).play(log_api_exception=log_api_exception) @async_to_sync async def pause(self, log_api_exception: bool = True) -> Optional[bool]: """Emulate 'pause' key press.""" return await super(Vizio, self).pause(log_api_exception=log_api_exception) @async_to_sync async def remote(self, key: str, log_api_exception: bool = True) -> Optional[bool]: """Emulate key press by key name.""" return await super(Vizio, self).remote(key, log_api_exception=log_api_exception) def get_remote_keys_list(self) -> KeysView[str]: """Get list of remote key names.""" return super(Vizio, self).get_remote_keys_list() @async_to_sync async def get_setting_types_list( self, setting_type: str, log_api_exception: bool = True ) -> Optional[List[str]]: """Get list of all setting types.""" return await super(Vizio, self).get_setting_types_list( log_api_exception=log_api_exception ) @async_to_sync async def get_all_settings( self, setting_type: str, log_api_exception: bool = True ) -> Optional[Dict[str, Union[int, str]]]: """Get all setting names and corresponding values.""" return await super(Vizio, self).get_all_settings( setting_type, log_api_exception=log_api_exception ) @async_to_sync async def get_all_settings_options( self, setting_type: str, log_api_exception: bool = True ) -> Optional[Dict[str, Union[int, str]]]: """Get all setting names and corresponding options.""" return await super(Vizio, self).get_all_settings_options( setting_type, log_api_exception=log_api_exception ) @async_to_sync async def get_setting( self, setting_type: str, setting_name: str, log_api_exception: bool = True ) -> Optional[Union[int, str]]: """Get current value of named setting.""" return await super(Vizio, self).get_setting( setting_type, setting_name, log_api_exception=log_api_exception ) @async_to_sync async def get_setting_options( self, setting_type: str, setting_name: str, log_api_exception: bool = True ) -> Optional[Union[List[str], Dict[str, Union[int, str]]]]: """Get options of named setting.""" return await super(Vizio, self).get_setting_options( setting_type, setting_name, log_api_exception=log_api_exception ) async def get_setting_options_xlist( self, setting_type: str, setting_name: str, log_api_exception: bool = True ) -> Optional[List[str]]: """Get options of named setting for settings based on a user defined list.""" return await super(Vizio, self).get_setting_options_xlist( setting_type, setting_name, log_api_exception=log_api_exception ) @async_to_sync async def set_setting( self, setting_type: str, setting_name: str, new_value: Union[int, str], log_api_exception: bool = True, ) -> Optional[bool]: """Set new value for named setting.""" return await super(Vizio, self).set_setting( setting_type, setting_name, new_value, log_api_exception=log_api_exception ) @async_to_sync async def get_all_audio_settings( self, log_api_exception: bool = True ) -> Optional[Dict[str, Union[int, str]]]: """Get all audio setting names and corresponding values.""" return await super(Vizio, self).get_all_audio_settings( log_api_exception=log_api_exception ) @async_to_sync async def get_all_audio_settings_options( self, log_api_exception: bool = True ) -> Optional[Dict[str, Union[int, str]]]: """Get all audio setting names and corresponding options.""" return await super(Vizio, self).get_all_audio_settings_options( log_api_exception=log_api_exception ) @async_to_sync async def get_audio_setting( self, setting_name: str, log_api_exception: bool = True ) -> Optional[Union[int, str]]: """Get current value of named audio setting.""" return await super(Vizio, self).get_audio_setting( setting_name, log_api_exception=log_api_exception ) @async_to_sync async def get_audio_setting_options( self, setting_name: str, log_api_exception: bool = True ) -> Optional[Union[List[str], Dict[str, Union[int, str]]]]: """Get options of named audio setting.""" return await super(Vizio, self).get_audio_setting_options( setting_name, log_api_exception=log_api_exception ) @async_to_sync async def set_audio_setting( self, setting_name: str, new_value: Union[int, str], log_api_exception: bool = True, ) -> Optional[bool]: """Set new value for named audio setting.""" return await super(Vizio, self).set_audio_setting( setting_name, new_value, log_api_exception=log_api_exception ) @async_to_sync @staticmethod async def get_apps_list( country: str = "all", apps_list: List[Dict[str, Union[str, List[Union[str, Dict[str, Any]]]]]] = None, session: ClientSession = None, ) -> List[str]: """Get list of known apps by name optionally filtered by supported country.""" return await super(Vizio, Vizio).get_apps_list( country=country, apps_list=apps_list, session=session ) @async_to_sync async def launch_app( self, app_name: str, apps_list: List[Dict[str, Union[str, List[Union[str, Dict[str, Any]]]]]] = None, log_api_exception: bool = True, ) -> Optional[bool]: """Launch known app by name.""" return await super(Vizio, self).launch_app( app_name, apps_list, log_api_exception=log_api_exception ) @async_to_sync async def launch_app_config( self, app_name: str, APP_ID: str, NAME_SPACE: int, MESSAGE: str = None, log_api_exception: bool = True, ) -> Optional[bool]: """Launch app using app's config values.""" return await super(Vizio, self).launch_app_config( APP_ID, NAME_SPACE, MESSAGE, log_api_exception=log_api_exception ) @async_to_sync async def get_current_app( self, apps_list: List[Dict[str, Union[str, List[Union[str, Dict[str, Any]]]]]] = None, log_api_exception: bool = True, ) -> Optional[str]: """Get name of currently running app. Returns const APP_NOT_RUNNING if no app is currently running, const UNKNOWN_APP if app config isn't known by pyvizio.""" return await super(Vizio, self).get_current_app( apps_list, log_api_exception=log_api_exception ) @async_to_sync async def get_current_app_config( self, log_api_exception: bool = True ) -> Optional[AppConfig]: """Get config values of currently running app. Returns empty AppConfig if no app is currently running.""" return await super(Vizio, self).get_current_app_config( log_api_exception=log_api_exception ) @async_to_sync async def guess_device_type( ip: str, port: Optional[str] = None, timeout: int = DEFAULT_TIMEOUT ) -> str: """ Attempt to guess the device type by getting power state with no auth token. NOTE: The `ip` and `port` values passed in have to be valid for the device in order for this to work. This function is being used as part of a zeroconf discovery workflow in HomeAssistant which is why it is safe to assume that `ip` and `port` are valid. """ return await async_guess_device_type(ip, port, timeout)