diff --git a/denonavr/denonavr.py b/denonavr/denonavr.py index b92f61a..48d0595 100644 --- a/denonavr/denonavr.py +++ b/denonavr/denonavr.py @@ -7,6 +7,7 @@ :license: MIT, see LICENSE for more details. """ +import asyncio import logging import time @@ -76,6 +77,7 @@ class DenonAVR(DenonAVRFoundation): attr.validators.instance_of(dict)), default=attr.Factory(dict), init=False) + _setup_lock: asyncio.Lock = attr.ib(default=attr.Factory(asyncio.Lock)) audyssey: DenonAVRAudyssey = attr.ib( validator=attr.validators.instance_of(DenonAVRAudyssey), default=attr.Factory(audyssey_factory, takes_self=True), @@ -128,19 +130,20 @@ def create_zones(self, add_zones): async def async_setup(self) -> None: """Ensure that configuration is loaded from receiver asynchronously.""" - # Device setup - await self._device.async_setup() - if self._name is None: - self._name = self._device.friendly_name - - # Setup other functions - self.input.setup() - await self.soundmode.async_setup() - self.tonecontrol.setup() - self.vol.setup() - self.audyssey.setup() - - self._is_setup = True + async with self._setup_lock: + # Device setup + await self._device.async_setup() + if self._name is None: + self._name = self._device.friendly_name + + # Setup other functions + self.input.setup() + await self.soundmode.async_setup() + self.tonecontrol.setup() + self.vol.setup() + self.audyssey.setup() + + self._is_setup = True @run_async_synchronously(async_func=async_setup) def setup(self) -> None: diff --git a/denonavr/foundation.py b/denonavr/foundation.py index a835ff1..c753aa4 100644 --- a/denonavr/foundation.py +++ b/denonavr/foundation.py @@ -7,6 +7,7 @@ :license: MIT, see LICENSE for more details. """ +import asyncio import logging import xml.etree.ElementTree as ET @@ -67,6 +68,7 @@ class DenonAVRDeviceInfo: _is_setup: bool = attr.ib(converter=bool, default=False, init=False) _allow_recovery: bool = attr.ib( converter=bool, default=False, init=False) + _setup_lock: asyncio.Lock = attr.ib(default=attr.Factory(asyncio.Lock)) def __attrs_post_init__(self) -> None: """Initialize special attributes.""" @@ -93,15 +95,24 @@ def get_own_zone(self): async def async_setup(self) -> None: """Ensure that configuration is loaded from receiver asynchronously.""" - # Own setup - await self.async_identify_receiver() - await self.async_get_device_info() - await self.async_identify_update_method() + async with self._setup_lock: + # Own setup + # Reduce read timeout during receiver identification + # deviceinfo endpoint takes very long to return 404 + timeout = self.api.timeout + self.api.timeout = httpx.Timeout(self.api.timeout.connect) + try: + await self.async_identify_receiver() + await self.async_get_device_info() + finally: + self.api.timeout = timeout + await self.async_identify_update_method() - # Add tags for a potential AppCommand.xml update - self.api.add_appcommand_update_tag(AppCommands.GetAllZonePowerStatus) + # Add tags for a potential AppCommand.xml update + self.api.add_appcommand_update_tag( + AppCommands.GetAllZonePowerStatus) - self._is_setup = True + self._is_setup = True async def async_update( self, diff --git a/denonavr/soundmode.py b/denonavr/soundmode.py index e378bc5..4750d77 100644 --- a/denonavr/soundmode.py +++ b/denonavr/soundmode.py @@ -7,6 +7,7 @@ :license: MIT, see LICENSE for more details. """ +import asyncio from copy import deepcopy import logging @@ -76,6 +77,7 @@ class DenonAVRSoundMode(DenonAVRFoundation): attr.validators.instance_of(dict)), default=attr.Factory(sound_mode_rev_map_factory, takes_self=True), init=False) + _setup_lock: asyncio.Lock = attr.ib(default=attr.Factory(asyncio.Lock)) # Update tags for attributes # AppCommand.xml interface @@ -89,18 +91,19 @@ class DenonAVRSoundMode(DenonAVRFoundation): async def async_setup(self) -> None: """Ensure that the instance is initialized.""" - # Add tags for a potential AppCommand.xml update - for tag in self.appcommand_attrs: - self._device.api.add_appcommand_update_tag(tag) - - # Soundmode is always available for AVR-X and AVR-X-2016 receivers - # For AVR receiver it will be tested druing the first update - if self._device.receiver in [AVR_X, AVR_X_2016]: - self._support_sound_mode = True - else: - await self.async_update_sound_mode() - - self._is_setup = True + async with self._setup_lock: + # Add tags for a potential AppCommand.xml update + for tag in self.appcommand_attrs: + self._device.api.add_appcommand_update_tag(tag) + + # Soundmode is always available for AVR-X and AVR-X-2016 receivers + # For AVR receiver it will be tested druing the first update + if self._device.receiver in [AVR_X, AVR_X_2016]: + self._support_sound_mode = True + else: + await self.async_update_sound_mode() + + self._is_setup = True async def async_update( self,