@@ -60,6 +60,7 @@ def __init__(
6060 self ._write_queue : asyncio .Queue [QueuedCommand ] = asyncio .Queue ()
6161 self ._pending_responses : List [PendingResponse ] = []
6262 self ._pending_responses_lock = asyncio .Lock ()
63+ self ._init_complete_event = asyncio .Event () # NEU: Event für den Abschluss der Initialisierung
6364
6465 # Timer-Handles (jetzt asyncio.Task anstelle von threading.Timer)
6566 self ._heartbeat_task : Optional [asyncio .Task [Any ]] = None
@@ -150,6 +151,8 @@ async def initialize(self) -> None:
150151 self .logger .info ("Initializing device..." )
151152 self .init_retry_count = 0
152153 self .init_reset_flag = False
154+ self .init_version_response = None
155+ self ._init_complete_event .clear () # NEU: Event für erneute Initialisierung zurücksetzen
153156
154157 if self ._stop_event .is_set ():
155158 self .logger .warning ("initialize called but stop event is set." )
@@ -255,6 +258,9 @@ async def _check_version_resp(self, msg: Optional[str]) -> None:
255258
256259 # NEU: Starte Heartbeat-Task
257260 await self ._start_heartbeat_task ()
261+
262+ # NEU: Signalisiere den Abschluss der Initialisierung
263+ self ._init_complete_event .set ()
258264
259265 else :
260266 self .logger .warning ("StartInit: No valid version response." )
@@ -273,8 +279,11 @@ async def _reset_device(self) -> None:
273279 await asyncio .sleep (2.0 )
274280 # NEU: Der Controller ist neu gestartet und muss wieder in den async Kontext eintreten
275281 await self .__aenter__ ()
276-
282+
277283 # Manuell die Initialisierung starten
284+ self .init_version_response = None
285+ self ._init_complete_event .clear () # NEU: Event für erneute Initialisierung zurücksetzen
286+
278287 try :
279288 await self ._send_xq ()
280289 await self ._start_init ()
@@ -491,21 +500,25 @@ def on_response(response: str):
491500 # Warte auf das Future mit Timeout
492501 return await asyncio .wait_for (response_future , timeout = timeout )
493502 except asyncio .TimeoutError :
503+ await asyncio .sleep (0 ) # Gib dem Event-Loop eine Chance, _stop_event zu setzen.
494504 # Code Refactor: Timeout vs. dead connection
495- if self ._stop_event .is_set ():
505+ self .logger .debug ("Command timeout reached for %s" , payload )
506+ # Differentiate between connection drop and normal command timeout
507+ # Check for a closed transport or a stopped controller
508+ if self ._stop_event .is_set () or (self .transport and self .transport .closed ()):
496509 self .logger .error (
497- "Command '%s' timed out. Connection appears to be dead (controller stopping)." , payload
510+ "Command '%s' timed out. Connection appears to be dead (transport closed or controller stopping)." , payload
498511 )
499512 raise SignalduinoConnectionError (
500513 f"Command '{ payload } ' failed: Connection dropped."
501514 ) from None
502-
503- # Annahme: Transport-API wirft SignalduinoConnectionError bei Trennung.
504- # Wenn dies nicht der Fall ist, wird ein Timeout angenommen.
505- self .logger .warning (
506- "Command '%s' timed out. Treating as no response from device." , payload
507- )
508- raise SignalduinoCommandTimeout (f"Command '{ payload } ' timed out" ) from None
515+ else :
516+ # Annahme: Transport-API wirft SignalduinoConnectionError bei Trennung.
517+ # Wenn dies nicht der Fall ist, wird ein Timeout angenommen.
518+ self .logger .warning (
519+ "Command '%s' timed out. Treating as no response from device." , payload
520+ )
521+ raise SignalduinoCommandTimeout (f"Command '{ payload } ' timed out" ) from None
509522
510523 async def _start_heartbeat_task (self ) -> None :
511524 """Schedules the periodic status heartbeat task."""
@@ -670,17 +683,29 @@ async def run(self, timeout: Optional[float] = None) -> None:
670683 """
671684 self .logger .info ("Starting main controller tasks..." )
672685
673- # 1. Initialisierung starten (führt Versionsprüfung durch und startet Heartbeat)
674- await self .initialize ()
675-
676- # 2. Haupt-Tasks erstellen und starten
686+ # 1. Haupt-Tasks erstellen und starten (Muss VOR initialize() erfolgen, damit der Reader
687+ # die Initialisierungsantwort empfangen kann)
677688 reader_task = asyncio .create_task (self ._reader_task (), name = "sd-reader" )
678689 parser_task = asyncio .create_task (self ._parser_task (), name = "sd-parser" )
679690 writer_task = asyncio .create_task (self ._writer_task (), name = "sd-writer" )
680691
681692 self ._main_tasks = [reader_task , parser_task , writer_task ]
693+
694+ # 2. Initialisierung starten (führt Versionsprüfung durch und startet Heartbeat)
695+ await self .initialize ()
696+
697+ # 3. Auf den Abschluss der Initialisierung warten (mit zusätzlichem Timeout)
698+ try :
699+ self .logger .info ("Waiting for initialization to complete..." )
700+ await asyncio .wait_for (self ._init_complete_event .wait (), timeout = SDUINO_CMD_TIMEOUT * 2 )
701+ self .logger .info ("Initialization complete." )
702+ except asyncio .TimeoutError :
703+ self .logger .error ("Initialization timed out after %s seconds." , SDUINO_CMD_TIMEOUT * 2 )
704+ # Wenn die Initialisierung fehlschlägt, stoppen wir den Controller (aexit)
705+ self ._stop_event .set ()
706+ # Der Timeout kann dazu führen, dass die await-Kette unterbrochen wird. Wir fahren fort.
682707
683- # 3 . Auf eine der Haupt-Tasks warten (Reader/Writer werden bei Verbindungsabbruch beendet)
708+ # 4 . Auf eine der kritischen Haupt-Tasks warten (Reader/Writer werden bei Verbindungsabbruch beendet)
684709 # Parser sollte weiterlaufen, bis die Queue leer ist. Reader/Writer sind die kritischen Tasks.
685710 critical_tasks = [reader_task , writer_task ]
686711
0 commit comments