Skip to content

Commit aa6d2b7

Browse files
committed
Fix WD packed temperature, Tailscale IP discovery, and timeout error handling
1 parent 5f15518 commit aa6d2b7

2 files changed

Lines changed: 52 additions & 5 deletions

File tree

custom_components/smart_sniffer/config_flow.py

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from __future__ import annotations
44

5+
import asyncio
6+
import ipaddress
57
import logging
68
from typing import Any
79

@@ -66,7 +68,7 @@ async def async_step_user(
6668

6769
try:
6870
await self._test_connection(host, port, token)
69-
except aiohttp.ClientError:
71+
except (aiohttp.ClientError, asyncio.TimeoutError, TimeoutError):
7072
errors["base"] = "cannot_connect"
7173
except Exception: # noqa: BLE001
7274
_LOGGER.exception("Unexpected error during config flow")
@@ -84,11 +86,41 @@ async def async_step_user(
8486
errors=errors,
8587
)
8688

89+
@staticmethod
90+
def _pick_best_ip(discovery_info: ZeroconfServiceInfo) -> str:
91+
"""Choose the best IP from discovery info.
92+
93+
Prefers RFC 1918 private addresses (192.168.x, 10.x, 172.16-31.x)
94+
over VPN/tunnel IPs (Tailscale 100.x, WireGuard, etc.). Falls back
95+
to whatever is available.
96+
"""
97+
# discovery_info may expose ip_address (single) and ip_addresses (list).
98+
candidates: list[str] = []
99+
if hasattr(discovery_info, "ip_addresses") and discovery_info.ip_addresses:
100+
candidates = [str(a) for a in discovery_info.ip_addresses]
101+
elif discovery_info.ip_address:
102+
candidates = [str(discovery_info.ip_address)]
103+
104+
if not candidates:
105+
return str(discovery_info.ip_address)
106+
107+
# Prefer private (RFC 1918) addresses over anything else.
108+
for ip_str in candidates:
109+
try:
110+
addr = ipaddress.ip_address(ip_str)
111+
if addr.is_private and not ip_str.startswith("100."):
112+
return ip_str
113+
except ValueError:
114+
continue
115+
116+
# No private IP found — return the first candidate.
117+
return candidates[0]
118+
87119
async def async_step_zeroconf(
88120
self, discovery_info: ZeroconfServiceInfo
89121
) -> ConfigFlowResult:
90122
"""Handle discovery via mDNS/Zeroconf."""
91-
host = str(discovery_info.ip_address)
123+
host = self._pick_best_ip(discovery_info)
92124
port = discovery_info.port
93125
properties = discovery_info.properties
94126

@@ -126,7 +158,7 @@ async def async_step_zeroconf_confirm(
126158
await self._test_connection(
127159
self._discovery_host, self._discovery_port, token
128160
)
129-
except aiohttp.ClientError:
161+
except (aiohttp.ClientError, asyncio.TimeoutError, TimeoutError):
130162
errors["base"] = "cannot_connect"
131163
except Exception: # noqa: BLE001
132164
_LOGGER.exception("Unexpected error during zeroconf confirm")
@@ -202,7 +234,7 @@ async def async_step_init(
202234
url, headers=headers, timeout=aiohttp.ClientTimeout(total=10)
203235
) as resp:
204236
resp.raise_for_status()
205-
except aiohttp.ClientError:
237+
except (aiohttp.ClientError, asyncio.TimeoutError, TimeoutError):
206238
errors["base"] = "cannot_connect"
207239
except Exception: # noqa: BLE001
208240
_LOGGER.exception("Unexpected error in options flow")

custom_components/smart_sniffer/sensor.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,22 @@ def _extract_attribute(drive_data: dict[str, Any], key: str) -> Any | None:
297297
if attr.get("name") in names:
298298
raw = attr.get("raw", {})
299299
if isinstance(raw, dict):
300-
return raw.get("value")
300+
raw_value = raw.get("value")
301+
# WD/HGST drives pack min/max/current into a single 48-bit
302+
# raw value for Temperature_Celsius (e.g., 214749675563
303+
# instead of 43). The actual temp is in the low 16 bits.
304+
# Parse raw.string first (e.g., "43 (Min/Max 20/50)"),
305+
# fall back to masking if needed.
306+
if key == "temperature" and isinstance(raw_value, int) and raw_value > 300:
307+
raw_string = raw.get("string", "")
308+
if raw_string:
309+
import re
310+
m = re.match(r"(\d+)", str(raw_string))
311+
if m:
312+
return int(m.group(1))
313+
# Fallback: low 16 bits hold current temp.
314+
return raw_value & 0xFFFF
315+
return raw_value
301316
return raw
302317

303318
return None

0 commit comments

Comments
 (0)