From 6a8ae401e545d587b67dfd0d4b21a027bbb27c7c Mon Sep 17 00:00:00 2001 From: Michael Osthege Date: Mon, 26 Jan 2026 20:23:08 +0100 Subject: [PATCH 1/2] Don't try to start already-running threads --- cri_lib/cri_controller.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cri_lib/cri_controller.py b/cri_lib/cri_controller.py index 3f8f135..ba5cb6a 100644 --- a/cri_lib/cri_controller.py +++ b/cri_lib/cri_controller.py @@ -116,10 +116,12 @@ def connect( self.connected = True # Start receiving commands - self.receive_thread.start() + if not self.receive_thread.is_alive(): + self.receive_thread.start() # Start sending ALIVEJOG message - self.jog_thread.start() + if not self.jog_thread.is_alive(): + self.jog_thread.start() hello_msg = f'INFO Hello "{application_name}" {application_version} {datetime.now(timezone.utc).strftime(format="%Y-%m-%dT%H:%M:%S")}' From 273b61578973237a19c48c019364ce2b3f2c0ad6 Mon Sep 17 00:00:00 2001 From: Michael Osthege Date: Wed, 28 Jan 2026 19:02:10 +0100 Subject: [PATCH 2/2] Recreate threads when connecting --- cri_lib/cri_controller.py | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/cri_lib/cri_controller.py b/cri_lib/cri_controller.py index ba5cb6a..2dea64d 100644 --- a/cri_lib/cri_controller.py +++ b/cri_lib/cri_controller.py @@ -51,10 +51,9 @@ def __init__(self) -> None: self.can_mode: bool = False self.can_queue: Queue = Queue() - self.jog_thread = threading.Thread(target=self._bg_alivejog_thread, daemon=True) - self.receive_thread = threading.Thread( - target=self._bg_receive_thread, daemon=True - ) + # Threads are created upon connect() and should be accessed through read-only properties. + self._jog_thread: threading.Thread | None = None + self._receive_thread: threading.Thread | None = None self.sent_command_counter_lock = threading.Lock() self.sent_command_counter = 0 @@ -79,6 +78,22 @@ def __init__(self) -> None: } self.jog_speeds_lock = threading.Lock() + @property + def receive_thread(self) -> threading.Thread: + if self._receive_thread is None: + raise CRIConnectionError( + "Receive thread not started yet. Call connect() first." + ) + return self._receive_thread + + @property + def jog_thread(self) -> threading.Thread: + if self._jog_thread is None: + raise CRIConnectionError( + "Jog thread not started yet. Call connect() first." + ) + return self._jog_thread + def connect( self, host: str, @@ -113,20 +128,24 @@ def connect( ip = socket.gethostbyname(host) self.sock.connect((ip, port)) logger.debug("\t Robot connected: %s:%d", host, port) - self.connected = True # Start receiving commands - if not self.receive_thread.is_alive(): - self.receive_thread.start() + self._receive_thread = threading.Thread( + target=self._bg_receive_thread, daemon=True + ) + self.receive_thread.start() # Start sending ALIVEJOG message - if not self.jog_thread.is_alive(): - self.jog_thread.start() + self._jog_thread = threading.Thread( + target=self._bg_alivejog_thread, daemon=True + ) + self.jog_thread.start() hello_msg = f'INFO Hello "{application_name}" {application_version} {datetime.now(timezone.utc).strftime(format="%Y-%m-%dT%H:%M:%S")}' self._send_command(hello_msg) + self.connected = True return True except ConnectionRefusedError: @@ -154,9 +173,11 @@ def close(self) -> None: if self.jog_thread.is_alive(): self.jog_thread.join() + self._jog_thread = None if self.receive_thread.is_alive(): self.receive_thread.join() + self._receive_thread = None self.sock.close()