Skip to content

Commit 92facab

Browse files
committed
-
1 parent 3b393b9 commit 92facab

File tree

3 files changed

+26
-16
lines changed

3 files changed

+26
-16
lines changed

custom_components/racklink_pdu/__init__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
1818
await api.connect_persistent()
1919

2020
async def async_update_data():
21-
# Check connection with ping
21+
# Try a ping to ensure connection remains valid
2222
try:
2323
await api.ping()
2424
except RackLinkAPIError:
25-
# Try reconnect
2625
_LOGGER.debug("Ping failed, reconnecting...")
2726
await api.close()
2827
try:
2928
await api.connect_persistent()
3029
except RackLinkAPIError:
3130
return {"reachable": False, "outlets": {}, "count": 0, "name": name}
3231

33-
# If reachable, get status
3432
try:
3533
count = await api.get_outlet_count()
3634
outlets = list(range(1, count+1))

custom_components/racklink_pdu/api.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import asyncio
22
import logging
3-
from .const import MAX_RETRIES, RECONNECT_DELAY
3+
from .const import MAX_RETRIES, RECONNECT_DELAY, HANDSHAKE_TIMEOUT
44

55
_LOGGER = logging.getLogger(__name__)
66

@@ -28,9 +28,10 @@ def __init__(self, host, port=60000, username="user", password="cstmcstm"):
2828
self._response_future = None
2929
self._read_task = None
3030
self._stopped = False
31+
self._handshake_done = None # Future that completes when device PING is received and PONG is sent
3132

3233
async def connect_persistent(self):
33-
"""Connect and login, start read loop, and perform initial handshake."""
34+
"""Connect and login, wait for device PING, respond with PONG, completing the handshake."""
3435
await self.close()
3536
for attempt in range(MAX_RETRIES):
3637
try:
@@ -39,12 +40,12 @@ async def connect_persistent(self):
3940
await self._login()
4041
self.connected = True
4142
self._stopped = False
43+
# Prepare handshake future
44+
self._handshake_done = asyncio.get_event_loop().create_future()
4245
self._read_task = asyncio.create_task(self._read_loop())
43-
# After login, we either wait for the device to send a PING or we initiate a ping to finalize handshake.
44-
# Send a ping to ensure the device is ready.
45-
_LOGGER.debug("Sending initial ping after login.")
46-
await self.ping()
47-
_LOGGER.debug("Initial handshake complete.")
46+
# Wait for handshake (device ping and our pong) to complete
47+
await asyncio.wait_for(self._handshake_done, timeout=HANDSHAKE_TIMEOUT)
48+
_LOGGER.debug("Handshake complete.")
4849
return
4950
except Exception as e:
5051
_LOGGER.debug("Connection attempt failed: %s", e)
@@ -75,7 +76,7 @@ async def _login(self):
7576
status = resp[3]
7677
if status != 0x01:
7778
raise RackLinkAuthenticationError("Invalid credentials")
78-
_LOGGER.debug("Login successful.")
79+
_LOGGER.debug("Login successful. Waiting for device to send PING...")
7980

8081
def _calculate_checksum(self, data):
8182
sum_val = 0
@@ -163,18 +164,26 @@ async def _read_loop(self):
163164
try:
164165
while not self._stopped and self.connected:
165166
msg = await self._read_message()
166-
# Handle PING from device
167+
168+
# Check if this is the device's initial PING after login
169+
# This should be a PING SET from device: 0x01 command, 0x01 subcmd
167170
if msg[1] == 0x01 and msg[2] == 0x01:
168-
_LOGGER.debug("Received PING from device, sending PONG.")
171+
_LOGGER.debug("Received PING (SET) from device, responding with PONG (RESPONSE).")
169172
pong = self._form_message(0x01, 0x10)
170173
self.writer.write(pong)
171174
await self.writer.drain()
175+
# Handshake done once we send pong
176+
if self._handshake_done and not self._handshake_done.done():
177+
self._handshake_done.set_result(True)
172178
continue
173179

180+
# Device may send unsolicited messages later, handle them if needed
181+
174182
self._check_for_nack(msg)
175183

176184
if self._response_future and not self._response_future.done():
177185
self._response_future.set_result(msg)
186+
178187
except (asyncio.CancelledError, asyncio.IncompleteReadError, RackLinkAPIError) as e:
179188
_LOGGER.debug("Read loop ended due to error: %s", e)
180189
self.connected = False
@@ -194,13 +203,13 @@ async def _send_and_receive(self, command, subcommand, data=b""):
194203
message = self._form_message(command, subcommand, data)
195204
for attempt in range(MAX_RETRIES):
196205
try:
197-
_LOGGER.debug("Sending command: cmd=0x%02X sub=0x%02X data=%s", command, subcommand, data.hex())
206+
_LOGGER.debug("Sending command: cmd=0x%02X sub=0x%02X data=%s", command, subcommand, data.hex() if data else "")
198207
self.writer.write(message)
199208
await self.writer.drain()
200209
resp = await asyncio.wait_for(self._response_future, timeout=10)
201210
return resp
202-
except (asyncio.TimeoutError, RackLinkAPIError, asyncio.IncompleteReadError):
203-
_LOGGER.debug("Failed attempt to send/receive, retrying...")
211+
except (asyncio.TimeoutError, RackLinkAPIError, asyncio.IncompleteReadError) as e:
212+
_LOGGER.debug("Failed attempt to send/receive (%s), retrying...", e)
204213
await self.close()
205214
await asyncio.sleep(RECONNECT_DELAY)
206215
await self.connect_persistent()
@@ -209,6 +218,8 @@ async def _send_and_receive(self, command, subcommand, data=b""):
209218
raise RackLinkAPIError("Failed to get response after retries")
210219

211220
async def ping(self):
221+
# We can send our own ping if needed, but per doc we must respond to device ping first.
222+
# Still implement if we want to test connectivity later.
212223
resp = await self._send_and_receive(0x01, 0x01)
213224
if resp[1] == 0x01 and resp[2] == 0x10:
214225
_LOGGER.debug("Ping successful, received Pong.")

custom_components/racklink_pdu/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
DEFAULT_POLL_INTERVAL = 10
33
MAX_RETRIES = 3
44
RECONNECT_DELAY = 5
5+
HANDSHAKE_TIMEOUT = 10

0 commit comments

Comments
 (0)