Add support for telnet connections for Denonavr integration (#85980)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
b4c343b1a2
commit
3d9d79684d
10 changed files with 105 additions and 12 deletions
|
@ -1,21 +1,26 @@
|
|||
"""The denonavr component."""
|
||||
import logging
|
||||
|
||||
from denonavr import DenonAVR
|
||||
from denonavr.exceptions import AvrNetworkError, AvrTimoutError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP, Platform
|
||||
from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
|
||||
from .config_flow import (
|
||||
CONF_SHOW_ALL_SOURCES,
|
||||
CONF_UPDATE_AUDYSSEY,
|
||||
CONF_USE_TELNET,
|
||||
CONF_ZONE2,
|
||||
CONF_ZONE3,
|
||||
DEFAULT_SHOW_SOURCES,
|
||||
DEFAULT_TIMEOUT,
|
||||
DEFAULT_UPDATE_AUDYSSEY,
|
||||
DEFAULT_USE_TELNET,
|
||||
DEFAULT_ZONE2,
|
||||
DEFAULT_ZONE3,
|
||||
DOMAIN,
|
||||
|
@ -40,6 +45,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
entry.options.get(CONF_SHOW_ALL_SOURCES, DEFAULT_SHOW_SOURCES),
|
||||
entry.options.get(CONF_ZONE2, DEFAULT_ZONE2),
|
||||
entry.options.get(CONF_ZONE3, DEFAULT_ZONE3),
|
||||
entry.options.get(CONF_USE_TELNET, DEFAULT_USE_TELNET),
|
||||
entry.options.get(CONF_UPDATE_AUDYSSEY, DEFAULT_UPDATE_AUDYSSEY),
|
||||
lambda: get_async_client(hass),
|
||||
)
|
||||
try:
|
||||
|
@ -56,6 +63,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
}
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
use_telnet = entry.options.get(CONF_USE_TELNET, DEFAULT_USE_TELNET)
|
||||
|
||||
async def _async_disconnect(event: Event) -> None:
|
||||
"""Disconnect from Telnet."""
|
||||
if use_telnet and receiver is not None:
|
||||
await receiver.async_telnet_disconnect()
|
||||
|
||||
if use_telnet:
|
||||
entry.async_on_unload(
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_disconnect)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -66,6 +84,10 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
|||
config_entry, PLATFORMS
|
||||
)
|
||||
|
||||
if config_entry.options.get(CONF_USE_TELNET, DEFAULT_USE_TELNET):
|
||||
receiver: DenonAVR = hass.data[DOMAIN][config_entry.entry_id][CONF_RECEIVER]
|
||||
await receiver.async_telnet_disconnect()
|
||||
|
||||
hass.data[DOMAIN][config_entry.entry_id][UNDO_UPDATE_LISTENER]()
|
||||
|
||||
# Remove zone2 and zone3 entities if needed
|
||||
|
|
|
@ -31,12 +31,15 @@ CONF_ZONE3 = "zone3"
|
|||
CONF_MANUFACTURER = "manufacturer"
|
||||
CONF_SERIAL_NUMBER = "serial_number"
|
||||
CONF_UPDATE_AUDYSSEY = "update_audyssey"
|
||||
CONF_USE_TELNET = "use_telnet"
|
||||
|
||||
DEFAULT_SHOW_SOURCES = False
|
||||
DEFAULT_TIMEOUT = 5
|
||||
DEFAULT_ZONE2 = False
|
||||
DEFAULT_ZONE3 = False
|
||||
DEFAULT_UPDATE_AUDYSSEY = False
|
||||
DEFAULT_USE_TELNET = False
|
||||
DEFAULT_USE_TELNET_NEW_INSTALL = True
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({vol.Optional(CONF_HOST): str})
|
||||
|
||||
|
@ -77,6 +80,12 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||
CONF_UPDATE_AUDYSSEY, DEFAULT_UPDATE_AUDYSSEY
|
||||
),
|
||||
): bool,
|
||||
vol.Optional(
|
||||
CONF_USE_TELNET,
|
||||
default=self.config_entry.options.get(
|
||||
CONF_USE_TELNET, DEFAULT_USE_TELNET
|
||||
),
|
||||
): bool,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -97,6 +106,7 @@ class DenonAvrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
self.show_all_sources = DEFAULT_SHOW_SOURCES
|
||||
self.zone2 = DEFAULT_ZONE2
|
||||
self.zone3 = DEFAULT_ZONE3
|
||||
self.use_telnet = DEFAULT_USE_TELNET_NEW_INSTALL
|
||||
self.d_receivers: list[dict[str, Any]] = []
|
||||
|
||||
@staticmethod
|
||||
|
@ -176,7 +186,9 @@ class DenonAvrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
self.show_all_sources,
|
||||
self.zone2,
|
||||
self.zone3,
|
||||
lambda: get_async_client(self.hass),
|
||||
use_telnet=False,
|
||||
update_audyssey=False,
|
||||
async_client_getter=lambda: get_async_client(self.hass),
|
||||
)
|
||||
|
||||
try:
|
||||
|
@ -216,6 +228,7 @@ class DenonAvrFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
CONF_MANUFACTURER: receiver.manufacturer,
|
||||
CONF_SERIAL_NUMBER: self.serial_number,
|
||||
},
|
||||
options={CONF_USE_TELNET: DEFAULT_USE_TELNET_NEW_INSTALL},
|
||||
)
|
||||
|
||||
async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult:
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
"codeowners": ["@ol-iver", "@starkillerOG"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/denonavr",
|
||||
"iot_class": "local_polling",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["denonavr"],
|
||||
"requirements": ["denonavr==0.10.12"],
|
||||
"requirements": ["denonavr==0.11.1"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Denon",
|
||||
|
|
|
@ -247,12 +247,47 @@ class DenonDevice(MediaPlayerEntity):
|
|||
and MediaPlayerEntityFeature.SELECT_SOUND_MODE
|
||||
)
|
||||
|
||||
self._receiver.register_callback("ALL", self._telnet_callback)
|
||||
|
||||
self._telnet_was_healthy: bool | None = None
|
||||
|
||||
async def _telnet_callback(self, zone, event, parameter):
|
||||
"""Process a telnet command callback."""
|
||||
if zone != self._receiver.zone:
|
||||
return
|
||||
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Clean up the entity."""
|
||||
self._receiver.unregister_callback("ALL", self._telnet_callback)
|
||||
|
||||
@async_log_errors
|
||||
async def async_update(self) -> None:
|
||||
"""Get the latest status information from device."""
|
||||
await self._receiver.async_update()
|
||||
receiver = self._receiver
|
||||
|
||||
# We can only skip the update if telnet was healthy after
|
||||
# the last update and is still healthy now to ensure that
|
||||
# we don't miss any state changes while telnet is down
|
||||
# or reconnecting.
|
||||
if (
|
||||
telnet_is_healthy := receiver.telnet_connected and receiver.telnet_healthy
|
||||
) and self._telnet_was_healthy:
|
||||
await receiver.input.async_update_media_state()
|
||||
return
|
||||
|
||||
# if async_update raises an exception, we don't want to skip the next update
|
||||
# so we set _telnet_was_healthy to None here and only set it to the value
|
||||
# before the update if the update was successful
|
||||
self._telnet_was_healthy = None
|
||||
|
||||
await receiver.async_update()
|
||||
|
||||
self._telnet_was_healthy = telnet_is_healthy
|
||||
|
||||
if self._update_audyssey:
|
||||
await self._receiver.async_update_audyssey()
|
||||
await receiver.async_update_audyssey()
|
||||
|
||||
@property
|
||||
def state(self) -> MediaPlayerState | None:
|
||||
|
|
|
@ -19,6 +19,8 @@ class ConnectDenonAVR:
|
|||
show_all_inputs: bool,
|
||||
zone2: bool,
|
||||
zone3: bool,
|
||||
use_telnet: bool,
|
||||
update_audyssey: bool,
|
||||
async_client_getter: Callable,
|
||||
) -> None:
|
||||
"""Initialize the class."""
|
||||
|
@ -27,6 +29,8 @@ class ConnectDenonAVR:
|
|||
self._host = host
|
||||
self._show_all_inputs = show_all_inputs
|
||||
self._timeout = timeout
|
||||
self._use_telnet = use_telnet
|
||||
self._update_audyssey = update_audyssey
|
||||
|
||||
self._zones: dict[str, str | None] = {}
|
||||
if zone2:
|
||||
|
@ -85,5 +89,11 @@ class ConnectDenonAVR:
|
|||
# Use httpx.AsyncClient getter provided by Home Assistant
|
||||
receiver.set_async_client_getter(self._async_client_getter)
|
||||
await receiver.async_setup()
|
||||
# Do an initial update if telnet is used.
|
||||
if self._use_telnet:
|
||||
await receiver.async_update()
|
||||
if self._update_audyssey:
|
||||
await receiver.async_update_audyssey()
|
||||
await receiver.async_telnet_connect()
|
||||
|
||||
self._receiver = receiver
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"flow_title": "{name}",
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "By default, this integration uses a Telnet connection to your receiver to receive real-time updates. Only one Telnet connection to your receiver can be established at a time. The Telnet connection can be disabled after setting up the integration.",
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::ip%]"
|
||||
},
|
||||
|
@ -40,7 +41,8 @@
|
|||
"show_all_sources": "Show all sources",
|
||||
"zone2": "Set up Zone 2",
|
||||
"zone3": "Set up Zone 3",
|
||||
"update_audyssey": "Update Audyssey settings"
|
||||
"update_audyssey": "Update Audyssey settings",
|
||||
"use_telnet": "Use Telnet connection"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -997,7 +997,7 @@
|
|||
"denonavr": {
|
||||
"integration_type": "hub",
|
||||
"config_flow": true,
|
||||
"iot_class": "local_polling",
|
||||
"iot_class": "local_push",
|
||||
"name": "Denon AVR Network Receivers"
|
||||
},
|
||||
"heos": {
|
||||
|
|
|
@ -586,7 +586,7 @@ deluge-client==1.7.1
|
|||
demetriek==0.4.0
|
||||
|
||||
# homeassistant.components.denonavr
|
||||
denonavr==0.10.12
|
||||
denonavr==0.11.1
|
||||
|
||||
# homeassistant.components.devolo_home_control
|
||||
devolo-home-control-api==0.18.2
|
||||
|
|
|
@ -463,7 +463,7 @@ deluge-client==1.7.1
|
|||
demetriek==0.4.0
|
||||
|
||||
# homeassistant.components.denonavr
|
||||
denonavr==0.10.12
|
||||
denonavr==0.11.1
|
||||
|
||||
# homeassistant.components.devolo_home_control
|
||||
devolo-home-control-api==0.18.2
|
||||
|
|
|
@ -11,6 +11,7 @@ from homeassistant.components.denonavr.config_flow import (
|
|||
CONF_SHOW_ALL_SOURCES,
|
||||
CONF_TYPE,
|
||||
CONF_UPDATE_AUDYSSEY,
|
||||
CONF_USE_TELNET,
|
||||
CONF_ZONE2,
|
||||
CONF_ZONE3,
|
||||
DOMAIN,
|
||||
|
@ -96,6 +97,7 @@ async def test_config_flow_manual_host_success(hass: HomeAssistant) -> None:
|
|||
CONF_MANUFACTURER: TEST_MANUFACTURER,
|
||||
CONF_SERIAL_NUMBER: TEST_SERIALNUMBER,
|
||||
}
|
||||
assert result["options"] == {CONF_USE_TELNET: True}
|
||||
|
||||
|
||||
async def test_config_flow_manual_discover_1_success(hass: HomeAssistant) -> None:
|
||||
|
@ -129,6 +131,7 @@ async def test_config_flow_manual_discover_1_success(hass: HomeAssistant) -> Non
|
|||
CONF_MANUFACTURER: TEST_MANUFACTURER,
|
||||
CONF_SERIAL_NUMBER: TEST_SERIALNUMBER,
|
||||
}
|
||||
assert result["options"] == {CONF_USE_TELNET: True}
|
||||
|
||||
|
||||
async def test_config_flow_manual_discover_2_success(hass: HomeAssistant) -> None:
|
||||
|
@ -171,6 +174,7 @@ async def test_config_flow_manual_discover_2_success(hass: HomeAssistant) -> Non
|
|||
CONF_MANUFACTURER: TEST_MANUFACTURER,
|
||||
CONF_SERIAL_NUMBER: TEST_SERIALNUMBER,
|
||||
}
|
||||
assert result["options"] == {CONF_USE_TELNET: True}
|
||||
|
||||
|
||||
async def test_config_flow_manual_discover_error(hass: HomeAssistant) -> None:
|
||||
|
@ -322,6 +326,7 @@ async def test_config_flow_ssdp(hass: HomeAssistant) -> None:
|
|||
CONF_MANUFACTURER: TEST_MANUFACTURER,
|
||||
CONF_SERIAL_NUMBER: TEST_SERIALNUMBER,
|
||||
}
|
||||
assert result["options"] == {CONF_USE_TELNET: True}
|
||||
|
||||
|
||||
async def test_config_flow_ssdp_not_denon(hass: HomeAssistant) -> None:
|
||||
|
@ -421,7 +426,12 @@ async def test_options_flow(hass: HomeAssistant) -> None:
|
|||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={CONF_SHOW_ALL_SOURCES: True, CONF_ZONE2: True, CONF_ZONE3: True},
|
||||
user_input={
|
||||
CONF_SHOW_ALL_SOURCES: True,
|
||||
CONF_ZONE2: True,
|
||||
CONF_ZONE3: True,
|
||||
CONF_USE_TELNET: False,
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
||||
|
@ -430,6 +440,7 @@ async def test_options_flow(hass: HomeAssistant) -> None:
|
|||
CONF_ZONE2: True,
|
||||
CONF_ZONE3: True,
|
||||
CONF_UPDATE_AUDYSSEY: False,
|
||||
CONF_USE_TELNET: False,
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue