44
55from __future__ import annotations
66
7+ import asyncio
78import logging
89import time
910from datetime import datetime
@@ -42,13 +43,15 @@ def __init__(
4243 self ._ip_to_mac : dict [str , str ] = {}
4344 self ._basic_auth_cache : dict [str , tuple [str , str ]] = {}
4445
45- async def _ensure_mac (self , ip : str ) -> str | None :
46+ async def _ensure_mac (self , ip : str , timeout : float | None = None ) -> str | None :
4647 """Get MAC address for an IP, fetching from /shelly if not cached."""
4748 normalized_ip = normalize_mac (ip )
4849 if normalized_ip in self ._ip_to_mac :
4950 return self ._ip_to_mac [normalized_ip ]
5051 try :
51- shelly_data = await self ._http_client .fetch_json (ip , "shelly" )
52+ shelly_data = await self ._http_client .fetch_json (
53+ ip , "shelly" , timeout = timeout
54+ )
5255 mac = shelly_data .get ("mac" )
5356 if mac :
5457 normalized_mac = normalize_mac (mac )
@@ -58,12 +61,14 @@ async def _ensure_mac(self, ip: str) -> str | None:
5861 pass
5962 return None
6063
61- async def _resolve_auth (self , ip : str ) -> tuple [str , str ] | None :
64+ async def _resolve_auth (
65+ self , ip : str , timeout : float | None = None
66+ ) -> tuple [str , str ] | None :
6267 """Resolve Basic Auth credentials for a device by IP."""
6368 if not self ._authentication_service :
6469 return None
6570
66- mac = await self ._ensure_mac (ip )
71+ mac = await self ._ensure_mac (ip , timeout )
6772 if not mac :
6873 return None
6974
@@ -82,18 +87,24 @@ def invalidate_credential_cache(self, mac: str) -> None:
8287 normalized_mac = normalize_mac (mac )
8388 self ._basic_auth_cache .pop (normalized_mac , None )
8489
85- async def discover_device (self , ip : str ) -> DiscoveredDevice | None :
90+ async def discover_device (
91+ self , ip : str , timeout : float | None = None
92+ ) -> DiscoveredDevice | None :
8693 """Discover a legacy Gen1 Shelly device.
8794
8895 Args:
8996 ip: Device IP address
97+ timeout: Per-request timeout in seconds; falls back to the HTTP
98+ client default when not provided.
9099
91100 Returns:
92101 DiscoveredDevice or None if discovery fails
93102 """
94103 try :
95104 start_time = time .perf_counter ()
96- device_info = await self ._http_client .fetch_json (ip , "shelly" )
105+ device_info = await self ._http_client .fetch_json (
106+ ip , "shelly" , timeout = timeout
107+ )
97108 response_time = time .perf_counter () - start_time
98109
99110 # Detect auth requirement from /shelly response
@@ -105,13 +116,16 @@ async def discover_device(self, ip: str) -> DiscoveredDevice | None:
105116 normalized_mac = normalize_mac (mac )
106117 self ._ip_to_mac [normalize_mac (ip )] = normalized_mac
107118 self ._auth_state_cache .mark_auth_required (normalized_mac )
108- auth = await self ._resolve_auth (ip )
109-
110- status_data = await self ._http_client .fetch_json_optional (
111- ip , "status" , auth = auth
112- )
113- settings_data = await self ._http_client .fetch_json_optional (
114- ip , "settings" , auth = auth
119+ auth = await self ._resolve_auth (ip , timeout )
120+
121+ # /status and /settings are independent; fetch them concurrently.
122+ status_data , settings_data = await asyncio .gather (
123+ self ._http_client .fetch_json_optional (
124+ ip , "status" , auth = auth , timeout = timeout
125+ ),
126+ self ._http_client .fetch_json_optional (
127+ ip , "settings" , auth = auth , timeout = timeout
128+ ),
115129 )
116130
117131 device_name = self ._derive_device_name (device_info , settings_data )
@@ -154,29 +168,37 @@ async def discover_device(self, ip: str) -> DiscoveredDevice | None:
154168 )
155169 return None
156170
157- async def get_device_status (self , ip : str ) -> DeviceStatus | None :
171+ async def get_device_status (
172+ self , ip : str , timeout : float | None = None
173+ ) -> DeviceStatus | None :
158174 """Get device status for a legacy Gen1 device.
159175
160176 Args:
161177 ip: Device IP address
178+ timeout: Per-request timeout in seconds; falls back to the HTTP
179+ client default when not provided.
162180
163181 Returns:
164182 DeviceStatus or None if retrieval fails
165183 """
166184 try :
167- device_info = await self ._http_client .fetch_json (ip , "shelly" )
185+ device_info = await self ._http_client .fetch_json (
186+ ip , "shelly" , timeout = timeout
187+ )
168188
169189 auth : tuple [str , str ] | None = None
170190 if device_info .get ("auth" , False ):
171191 mac = device_info .get ("mac" )
172192 if mac :
173193 normalized_mac = normalize_mac (mac )
174194 self ._ip_to_mac [normalize_mac (ip )] = normalized_mac
175- auth = await self ._resolve_auth (ip )
195+ auth = await self ._resolve_auth (ip , timeout )
176196
177- status_data = await self ._http_client .fetch_json (ip , "status" , auth = auth )
197+ status_data = await self ._http_client .fetch_json (
198+ ip , "status" , auth = auth , timeout = timeout
199+ )
178200 settings_data = await self ._http_client .fetch_json_optional (
179- ip , "settings" , auth = auth
201+ ip , "settings" , auth = auth , timeout = timeout
180202 )
181203 except DeviceAuthenticationError :
182204 raise
0 commit comments