11import asyncio
22import 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." )
0 commit comments