1 """The Panasonic Viera integration."""
3 from collections.abc
import Callable
4 from functools
import partial
7 from urllib.error
import HTTPError, URLError
9 from panasonic_viera
import EncryptionRequired, Keys, RemoteControl, SOAPError
10 import voluptuous
as vol
32 _LOGGER = logging.getLogger(__name__)
34 CONFIG_SCHEMA = vol.Schema(
41 vol.Required(CONF_HOST): cv.string,
42 vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
43 vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
44 vol.Optional(CONF_ON_ACTION): cv.SCRIPT_SCHEMA,
50 extra=vol.ALLOW_EXTRA,
53 PLATFORMS = [Platform.MEDIA_PLAYER, Platform.REMOTE]
56 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
57 """Set up Panasonic Viera from configuration.yaml."""
58 if DOMAIN
not in config:
61 for conf
in config[DOMAIN]:
62 hass.async_create_task(
63 hass.config_entries.flow.async_init(
64 DOMAIN, context={
"source": SOURCE_IMPORT}, data=conf
72 """Set up Panasonic Viera from a config entry."""
73 panasonic_viera_data = hass.data.setdefault(DOMAIN, {})
75 config = config_entry.data
77 host = config[CONF_HOST]
78 port = config[CONF_PORT]
80 if (on_action := config[CONF_ON_ACTION])
is not None:
81 on_action =
Script(hass, on_action, config[CONF_NAME], DOMAIN)
84 if CONF_APP_ID
in config
and CONF_ENCRYPTION_KEY
in config:
85 params[
"app_id"] = config[CONF_APP_ID]
86 params[
"encryption_key"] = config[CONF_ENCRYPTION_KEY]
88 remote =
Remote(hass, host, port, on_action, **params)
89 await remote.async_create_remote_control(during_setup=
True)
91 panasonic_viera_data[config_entry.entry_id] = {ATTR_REMOTE: remote}
94 if ATTR_DEVICE_INFO
not in config
or config[ATTR_DEVICE_INFO]
is None:
95 device_info = await remote.async_get_device_info()
96 unique_id = config_entry.unique_id
97 if device_info
is None:
99 "Couldn't gather device info; Please restart Home Assistant with your"
100 " TV turned on and connected to your network"
103 unique_id = device_info[ATTR_UDN]
104 hass.config_entries.async_update_entry(
107 data={**config, ATTR_DEVICE_INFO: device_info},
110 await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
116 """Unload a config entry."""
117 unload_ok = await hass.config_entries.async_unload_platforms(
118 config_entry, PLATFORMS
121 hass.data[DOMAIN].pop(config_entry.entry_id)
127 """The Remote class. It stores the TV properties and the remote control connection itself."""
134 on_action: Script |
None =
None,
135 app_id: str |
None =
None,
136 encryption_key: str |
None =
None,
138 """Initialize the Remote class."""
149 self.
_control_control: RemoteControl |
None =
None
150 self.
statestate: MediaPlayerState |
None =
None
152 self.
volumevolume: float = 0
153 self.
mutedmuted: bool =
False
154 self.playing: bool =
True
157 """Create remote control."""
161 params[
"app_id"] = self.
_app_id_app_id
165 partial(RemoteControl, self.
_host_host, self.
_port_port, **params)
170 except (URLError, SOAPError, OSError)
as err:
171 _LOGGER.debug(
"Could not establish remote connection: %s", err)
173 self.
statestate = MediaPlayerState.OFF
176 _LOGGER.exception(
"An unknown error occurred")
178 self.
statestate = MediaPlayerState.OFF
182 """Update device data."""
187 await self._handle_errors(self.
_update_update)
190 """Retrieve the latest data."""
191 assert self.
_control_control
is not None
196 """Send a key to the TV and handle exceptions."""
198 key = getattr(Keys, key.upper())
199 except (AttributeError, TypeError):
200 key = getattr(key,
"value", key)
202 assert self.
_control_control
is not None
203 await self._handle_errors(self.
_control_control.send_key, key)
206 """Turn on the TV."""
210 elif self.
statestate
is not MediaPlayerState.ON:
215 """Turn off the TV."""
216 if self.
statestate
is not MediaPlayerState.OFF:
218 self.
statestate = MediaPlayerState.OFF
222 """Set mute based on 'enable'."""
223 assert self.
_control_control
is not None
224 await self._handle_errors(self.
_control_control.set_mute, enable)
227 """Set volume level, range 0..1."""
228 assert self.
_control_control
is not None
229 volume =
int(volume * 100)
230 await self._handle_errors(self.
_control_control.set_volume, volume)
234 assert self.
_control_control
is not None
235 _LOGGER.debug(
"Play media: %s (%s)", media_id, media_type)
236 await self._handle_errors(self.
_control_control.open_webpage, media_id)
239 """Return device info."""
242 device_info = await self._handle_errors(self.
_control_control.get_device_info)
243 _LOGGER.debug(
"Fetched device info: %s",
str(device_info))
247 self, func: Callable[[*_Ts], _R], *args: *_Ts
249 """Handle errors from func, set available and reconnect if needed."""
251 result = await self.
_hass_hass.async_add_executor_job(func, *args)
252 except EncryptionRequired:
254 "The connection couldn't be encrypted. Please reconfigure your TV"
258 except (SOAPError, HTTPError)
as err:
259 _LOGGER.debug(
"An error occurred: %s", err)
260 self.
statestate = MediaPlayerState.OFF
264 except (URLError, OSError)
as err:
265 _LOGGER.debug(
"An error occurred: %s", err)
266 self.
statestate = MediaPlayerState.OFF
271 _LOGGER.exception(
"An unknown error occurred")
272 self.
statestate = MediaPlayerState.OFF
275 self.
statestate = MediaPlayerState.ON
None async_play_media(self, MediaType media_type, str media_id)
None __init__(self, HomeAssistant hass, str host, int port, Script|None on_action=None, str|None app_id=None, str|None encryption_key=None)
None async_set_volume(self, float volume)
dict[str, Any]|None async_get_device_info(self)
None async_turn_on(self, Context|None context)
None async_turn_off(self)
None async_set_mute(self, bool enable)
None async_send_key(self, Keys|str key)
None async_create_remote_control(self, bool during_setup=False)
bool async_setup(HomeAssistant hass, ConfigType config)
bool async_setup_entry(HomeAssistant hass, ConfigEntry config_entry)
bool async_unload_entry(HomeAssistant hass, ConfigEntry config_entry)
def async_run(config_dir)