Skip to content

Commit

Permalink
feat: Add option for hybrid connection (#53)
Browse files Browse the repository at this point in the history
Using Telnet for regular commands can be slow - limiting its use to only updates and volume brings the best of both options
  • Loading branch information
henrikwidlund authored Jan 10, 2025
1 parent 39b3f8a commit 3e3cb56
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 32 deletions.
35 changes: 24 additions & 11 deletions intg-denonavr/avr.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ def __init__(

self._active: bool = False
self._use_telnet = device.use_telnet
self._use_telnet_for_events = device.use_telnet_for_events
self._telnet_was_healthy: bool | None = None
self._attr_available: bool = True
# expected volume feedback value if telnet isn't used
Expand Down Expand Up @@ -282,7 +283,11 @@ def state(self) -> States:
"""Return the cached state of the device."""
reported_state = self._map_denonavr_state(self._receiver.state)
# Dirty workaround for state reporting issue. Couldn't be reproduced yet.
if self._use_telnet and reported_state == States.OFF and self._expected_state != States.OFF:
if (
(self._use_telnet or self._use_telnet_for_events)
and reported_state == States.OFF
and self._expected_state != States.OFF
):
_LOG.warning("State mismatch! Reported: %s. Using expected: %s", reported_state, self._expected_state)
return self._expected_state
return reported_state
Expand Down Expand Up @@ -408,7 +413,7 @@ async def connect(self):
request_start = time.time()

await self._receiver.async_update()
if self._use_telnet:
if self._use_telnet or self._use_telnet_for_events:
if self._update_audyssey:
await self._receiver.async_update_audyssey()
await self._receiver.async_telnet_connect()
Expand Down Expand Up @@ -489,7 +494,7 @@ async def disconnect(self):
self._active = False

try:
if self._use_telnet:
if self._use_telnet or self._use_telnet_for_events:
try:
self._receiver.unregister_callback(ALL_TELNET_EVENTS, self._telnet_callback)
except ValueError:
Expand Down Expand Up @@ -642,14 +647,14 @@ async def _telnet_callback(self, zone: str, event: str, parameter: str) -> None:
async def power_on(self) -> ucapi.StatusCodes:
"""Send power-on command to AVR."""
await self._receiver.async_power_on()
if not self._use_telnet:
if not self._use_telnet and not self._use_telnet_for_events:
self._set_expected_state(States.ON)

@async_handle_denonlib_errors
async def power_off(self) -> ucapi.StatusCodes:
"""Send power-off command to AVR."""
await self._receiver.async_power_off()
if not self._use_telnet:
if not self._use_telnet and not self._use_telnet_for_events:
self._set_expected_state(States.OFF)

@async_handle_denonlib_errors
Expand All @@ -671,15 +676,19 @@ async def set_volume_level(self, volume: float | None) -> ucapi.StatusCodes:
volume_denon = float(18)
await self._receiver.async_set_volume(volume_denon)
self.events.emit(Events.UPDATE, self.id, {MediaAttr.VOLUME: volume})
if self._use_telnet and not self._update_lock.locked():
if (self._use_telnet or self._use_telnet_for_events) and not self._update_lock.locked():
await self._event_loop.create_task(self.async_update_receiver_data())
else:
self._expected_volume = volume

@async_handle_denonlib_errors
async def volume_up(self) -> ucapi.StatusCodes:
"""Send volume-up command to AVR."""
if self._use_telnet and self._expected_volume is not None and self._volume_step != 0.5:
if (
(self._use_telnet or self._use_telnet_for_events)
and self._expected_volume is not None
and self._volume_step != 0.5
):
self._expected_volume = min(self._expected_volume + self._volume_step, 100)
await self.set_volume_level(self._expected_volume)
else:
Expand All @@ -689,7 +698,11 @@ async def volume_up(self) -> ucapi.StatusCodes:
@async_handle_denonlib_errors
async def volume_down(self) -> ucapi.StatusCodes:
"""Send volume-down command to AVR."""
if self._use_telnet and self._expected_volume is not None and self._volume_step != 0.5:
if (
(self._use_telnet or self._use_telnet_for_events)
and self._expected_volume is not None
and self._volume_step != 0.5
):
self._expected_volume = max(self._expected_volume - self._volume_step, 0)
await self.set_volume_level(self._expected_volume)
else:
Expand All @@ -716,7 +729,7 @@ async def mute(self, muted: bool) -> ucapi.StatusCodes:
"""Send mute command to AVR."""
_LOG.debug("Sending mute: %s", muted)
await self._receiver.async_mute(muted)
if not self._use_telnet:
if not self._use_telnet and not self._use_telnet_for_events:
self.events.emit(Events.UPDATE, self.id, {MediaAttr.MUTED: muted})
else:
await self.async_update_receiver_data()
Expand Down Expand Up @@ -811,7 +824,7 @@ async def send_command(self, cmd: str) -> ucapi.StatusCodes:

def _increase_expected_volume(self):
"""Without telnet, increase expected volume and send update event."""
if not self._use_telnet or self._expected_volume is None:
if not self._use_telnet or not self._use_telnet_for_events or self._expected_volume is None:
return
self._expected_volume = min(self._expected_volume + self._volume_step, 100)
# Send updated volume if no update task in progress
Expand All @@ -820,7 +833,7 @@ def _increase_expected_volume(self):

def _decrease_expected_volume(self):
"""Without telnet, decrease expected volume and send update event."""
if not self._use_telnet or self._expected_volume is None:
if not self._use_telnet or not self._use_telnet_for_events or self._expected_volume is None:
return
self._expected_volume = max(self._expected_volume - self._volume_step, 0)
# Send updated volume if no update task in progress
Expand Down
25 changes: 14 additions & 11 deletions intg-denonavr/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class AvrDevice:
support_sound_mode: bool
show_all_inputs: bool
use_telnet: bool
use_telnet_for_events: bool
update_audyssey: bool
zone2: bool
zone3: bool
Expand Down Expand Up @@ -109,19 +110,20 @@ def get(self, avr_id: str) -> AvrDevice | None:
return dataclasses.replace(item)
return None

def update(self, atv: AvrDevice) -> bool:
def update(self, avr: AvrDevice) -> bool:
"""Update a configured Denon device and persist configuration."""
for item in self._config:
if item.id == atv.id:
item.address = atv.address
item.name = atv.name
item.support_sound_mode = atv.support_sound_mode
item.show_all_inputs = atv.show_all_inputs
item.use_telnet = atv.use_telnet
item.update_audyssey = atv.update_audyssey
item.zone2 = atv.zone2
item.zone3 = atv.zone3
item.volume_step = atv.volume_step
if item.id == avr.id:
item.address = avr.address
item.name = avr.name
item.support_sound_mode = avr.support_sound_mode
item.show_all_inputs = avr.show_all_inputs
item.use_telnet = avr.use_telnet
item.use_telnet_for_events = avr.use_telnet_for_events
item.update_audyssey = avr.update_audyssey
item.zone2 = avr.zone2
item.zone3 = avr.zone3
item.volume_step = avr.volume_step
return self.store()
return False

Expand Down Expand Up @@ -182,6 +184,7 @@ def load(self) -> bool:
item.get("support_sound_mode", True),
item.get("show_all_inputs", False),
item.get("use_telnet", True),
item.get("use_telnet_for_events", False),
item.get("update_audyssey", False),
item.get("zone2", False),
item.get("zone3", False),
Expand Down
4 changes: 3 additions & 1 deletion intg-denonavr/receiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def __init__(
zone2: bool,
zone3: bool,
use_telnet: bool,
use_telnet_for_events: bool,
update_audyssey: bool,
) -> None:
"""Initialize the class."""
Expand All @@ -36,6 +37,7 @@ def __init__(
self._show_all_inputs = show_all_inputs
self._timeout = timeout
self._use_telnet = use_telnet
self._use_telnet_for_events = use_telnet_for_events
self._update_audyssey = update_audyssey

self._zones: dict[str, str | None] = {}
Expand Down Expand Up @@ -91,7 +93,7 @@ async def async_init_receiver_class(self) -> None:
)
await receiver.async_setup()
# Do an initial update if telnet is used.
if self._use_telnet:
if self._use_telnet or self._use_telnet_for_events:
await receiver.async_update()
if self._update_audyssey:
await receiver.async_update_audyssey()
Expand Down
61 changes: 52 additions & 9 deletions intg-denonavr/setup_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ async def handle_configuration_mode(msg: UserDataResponse) -> RequestUserInput |
zone2=False,
zone3=False,
use_telnet=False,
use_telnet_for_events=False,
update_audyssey=False,
)

Expand Down Expand Up @@ -221,13 +222,43 @@ async def handle_configuration_mode(msg: UserDataResponse) -> RequestUserInput |
# "field": {"checkbox": {"value": False}},
# },
{
"id": "use_telnet",
"id": "connection_mode",
"label": {
"en": "Use Telnet connection",
"de": "Telnet-Verbindung verwenden",
"fr": "Utilise une connexion Telnet",
"en": "Connection mode",
"de": "Verbindungstyp",
"fr": "Mode de connexion",
},
"field": {
"dropdown": {
"value": "use_telnet",
"items": [
{
"id": "use_telnet",
"label": {
"en": "Use Telnet connection",
"de": "Telnet-Verbindung verwenden",
"fr": "Utilise une connexion Telnet",
},
},
{
"id": "use_telnet_for_events",
"label": {
"en": "Use Telnet connection for events",
"de": "Telnet-Verbindung für Ereignisse verwenden",
"fr": "Utilise une connexion Telnet pour les événements",
},
},
{
"id": "use_http",
"label": {
"en": "Use HTTP connection",
"de": "HTTP-Verbindung verwenden",
"fr": "Utilise une connexion HTTP",
},
},
],
}
},
"field": {"checkbox": {"value": True}},
},
{
"id": "volume_step",
Expand All @@ -247,15 +278,23 @@ async def handle_configuration_mode(msg: UserDataResponse) -> RequestUserInput |
"value": {
"en": "Using telnet provides realtime updates for many values but "
"certain receivers allow a single connection only! If you enable this "
"setting, other apps or systems may no longer work.",
"setting, other apps or systems may no longer work. "
"Using Telnet for events is faster for regular commands while still providing realtime"
" updates. Same limitations regarding Telnet apply.",
"de": "Die Verwendung von telnet bietet Echtzeit-Updates für viele "
"Werte, aber bestimmte Verstärker erlauben nur eine einzige "
"Verbindung! Mit dieser Einstellung können andere Apps oder Systeme "
"nicht mehr funktionieren.",
"nicht mehr funktionieren. "
"Die Verwendung von Telnet für Ereignisse ist schneller für normale Befehle, "
"bietet aber immer noch Echtzeit-Updates. Die gleichen Einschränkungen in Bezug auf Telnet"
" gelten.",
"fr": "L'utilisation de telnet fournit des mises à jour en temps réel "
"pour de nombreuses valeurs, mais certains amplificateurs ne "
"permettent qu'une seule connexion! Avec ce paramètre, d'autres "
"applications ou systèmes ne peuvent plus fonctionner.",
"applications ou systèmes ne peuvent plus fonctionner. "
"L'utilisation de Telnet pour les événements est plus rapide pour les commandes classiques "
"tout en fournissant des mises à jour en temps réel. Les mêmes limitations concernant"
" Telnet s'appliquent.",
}
}
},
Expand All @@ -280,7 +319,9 @@ async def handle_device_choice(msg: UserDataResponse) -> SetupComplete | SetupEr
update_audyssey = False # not yet supported
zone2 = msg.input_values.get("zone2") == "true"
zone3 = msg.input_values.get("zone3") == "true"
use_telnet = msg.input_values.get("use_telnet") == "true"
connection_mode = msg.input_values.get("connection_mode")
use_telnet = connection_mode == "use_telnet"
use_telnet_for_events = connection_mode == "use_telnet_for_events"
volume_step = 0.5
try:
volume_step = float(msg.input_values.get("volume_step", 0.5))
Expand All @@ -297,6 +338,7 @@ async def handle_device_choice(msg: UserDataResponse) -> SetupComplete | SetupEr
zone2,
zone3,
use_telnet=False, # always False, connection only used to retrieve model information
use_telnet_for_events=False, # always False, connection only used to retrieve model information
update_audyssey=False, # always False, connection only used to retrieve model information
)

Expand All @@ -323,6 +365,7 @@ async def handle_device_choice(msg: UserDataResponse) -> SetupComplete | SetupEr
receiver.support_sound_mode,
show_all_inputs,
use_telnet=use_telnet,
use_telnet_for_events=use_telnet_for_events,
update_audyssey=update_audyssey,
zone2=zone2,
zone3=zone3,
Expand Down

0 comments on commit 3e3cb56

Please sign in to comment.