Skip to content

Commit acc0c57

Browse files
committed
Tell the user why a device is unreachable instead of just 'TimeOut'
An unconnected UDP socket cannot distinguish 'nothing is listening' from 'packets went nowhere' -- the kernel discards the ICMP port-unreachable and FetchResult reports a bare TimeoutError either way. That single message currently covers causes as different as a cloud-only WiFi module, a wrong encryption key, and a typo'd IP address, which is a large part of why these issues are impossible to triage from the outside. Once the retries are spent, probe the device once through a *connected* socket, which does surface ICMP errors, and report which of three situations it is: refused - reachable, but nothing listening on UDP 7000: the unit is not running the local protocol at all (cloud-only firmware). Retrying or changing encryption_version cannot help. responded - port open and answering discovery, so the network is fine and the encrypted exchange is what fails: key/encryption_version. silent - nothing came back: wrong IP, firewall/VLAN, or device offline. The request path itself is untouched -- same socket, same sends, same retries, same exception raised. The probe only runs on the already-failed path, so a working device behaves exactly as before. Verified against three real cases: cloud-only unit (hid U-WB05WR11V2.10, ver V3.2.M) -> refused working unit (hid U-WB05RT11V1.44, ver V3.4.M) -> responded unused address -> silent README updated so users can self-diagnose before opening an issue.
1 parent 4a8db99 commit acc0c57

2 files changed

Lines changed: 80 additions & 1 deletion

File tree

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,22 @@ This project is based on the work of several contributors and projects:
152152

153153
Due to the many issues being created revolving "TimeOut"/"Cannot connect" errors, I will be closing these. Feel free to make a PR fixing your TimeOut/Cannot connect error.
154154
More information on the "why" can be found here: https://github.com/RobHofmann/HomeAssistant-GreeClimateComponent/issues/405#issuecomment-4300110823
155+
156+
### Reading the failure message
157+
158+
When all attempts fail, the component now probes the device once more to work out *why*, and the
159+
log says which of three situations you are in. Please check this before opening an issue.
160+
161+
**"The device REFUSED the request (ICMP port unreachable)"**
162+
The device is on the network but nothing is listening on UDP 7000 — it is not running the local
163+
Gree protocol at all. This is typically a WiFi module whose firmware ships without local control,
164+
in which case there is nothing to fix on this side; the unit is cloud-only. Retrying, changing the
165+
encryption version or re-pairing will not help.
166+
167+
**"the device DID answer a plain discovery probe"**
168+
The port is open and the device is talking, so the network is fine and the *encrypted* exchange is
169+
what is failing. Look at the device key and `encryption_version` rather than at connectivity.
170+
171+
**"No response of any kind"**
172+
Nothing came back at all: wrong IP address, a firewall or VLAN in between, or the device is
173+
offline. This is the case where the usual network troubleshooting applies.

custom_components/gree/gree_protocol.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,41 @@
4949
SIOCGIFBRDADDR = 0x8919
5050

5151

52+
def _classify_unreachable(ip_addr, port, timeout=2):
53+
"""Work out *why* a device is not answering. Diagnostics only.
54+
55+
An unconnected UDP socket cannot tell "nothing is listening" apart from
56+
"packets vanished": the kernel discards the ICMP port-unreachable and the
57+
caller just sees a timeout. Connecting a socket makes those errors
58+
surface, so one extra probe once the retries are spent turns an opaque
59+
TimeOut into something the user can act on.
60+
61+
Deliberately separate from the request path above, which is unchanged.
62+
63+
Returns one of: "refused", "responded", "silent", or "error: ...".
64+
"""
65+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
66+
try:
67+
sock.settimeout(timeout)
68+
# connect() on UDP sends nothing; it just fixes the peer so the kernel
69+
# reports ICMP errors for it instead of dropping them.
70+
sock.connect((ip_addr, port))
71+
sock.send(b'{"t":"scan"}')
72+
sock.recv(64000)
73+
return "responded"
74+
except ConnectionRefusedError:
75+
return "refused"
76+
except (socket.timeout, TimeoutError):
77+
return "silent"
78+
except OSError as exc:
79+
return f"error: {exc}"
80+
finally:
81+
try:
82+
sock.close()
83+
except Exception: # noqa: BLE001 - best effort cleanup
84+
pass
85+
86+
5287
async def FetchResult(cipher, ip_addr, port, json_data, encryption_version=1, max_retries=8):
5388
"""Send a request to a Gree device and fetch the result, with retries and timeouts."""
5489

@@ -93,7 +128,32 @@ async def FetchResult(cipher, ip_addr, port, json_data, encryption_version=1, ma
93128
except Exception as e:
94129
if attempt == max_retries - 1:
95130
error_msg = f"{type(e).__name__}: {str(e)}" if str(e) else f"{type(e).__name__}"
96-
_LOGGER.error(f"All {max_retries} attempts failed for {ip_addr}:{port}. Error: {error_msg}")
131+
state = await asyncio.get_event_loop().run_in_executor(
132+
None, _classify_unreachable, ip_addr, port
133+
)
134+
if state == "refused":
135+
_LOGGER.error(
136+
f"All {max_retries} attempts failed for {ip_addr}:{port}. The device "
137+
f"REFUSED the request (ICMP port unreachable): it is reachable on the "
138+
f"network, but nothing is listening on UDP {port}, so it is not running "
139+
f"the local Gree protocol at all. Some newer WiFi module firmware ships "
140+
f"without it; those units can only be controlled via the cloud. "
141+
f"Retrying or changing the encryption version will not help."
142+
)
143+
elif state == "responded":
144+
_LOGGER.error(
145+
f"All {max_retries} attempts failed for {ip_addr}:{port}, but the device "
146+
f"DID answer a plain discovery probe. The port is open and the device is "
147+
f"speaking, so this is not a network problem -- the encrypted exchange is "
148+
f"failing. Check the device key and the encryption version. "
149+
f"Original error: {error_msg}"
150+
)
151+
else:
152+
_LOGGER.error(
153+
f"All {max_retries} attempts failed for {ip_addr}:{port}. No response of "
154+
f"any kind ({state}) -- wrong IP address, a firewall or VLAN in the way, "
155+
f"or the device is offline. Original error: {error_msg}"
156+
)
97157
raise
98158

99159
finally:

0 commit comments

Comments
 (0)