diff --git a/intg-denonavr/avr.py b/intg-denonavr/avr.py index dd3b2b5..c144be4 100644 --- a/intg-denonavr/avr.py +++ b/intg-denonavr/avr.py @@ -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 @@ -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 @@ -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() @@ -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: @@ -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 @@ -671,7 +676,7 @@ 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 @@ -679,7 +684,11 @@ async def set_volume_level(self, volume: float | None) -> ucapi.StatusCodes: @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: @@ -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: @@ -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() @@ -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 @@ -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 diff --git a/intg-denonavr/config.py b/intg-denonavr/config.py index f474560..dfb2bfc 100644 --- a/intg-denonavr/config.py +++ b/intg-denonavr/config.py @@ -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 @@ -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 @@ -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), diff --git a/intg-denonavr/receiver.py b/intg-denonavr/receiver.py index 3ec4f80..a5f4a59 100644 --- a/intg-denonavr/receiver.py +++ b/intg-denonavr/receiver.py @@ -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.""" @@ -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] = {} @@ -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() diff --git a/intg-denonavr/setup_flow.py b/intg-denonavr/setup_flow.py index 43bf7a6..9cf2bd3 100644 --- a/intg-denonavr/setup_flow.py +++ b/intg-denonavr/setup_flow.py @@ -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, ) @@ -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", @@ -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.", } } }, @@ -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)) @@ -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 ) @@ -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,