puda-drivers 0.0.2__py3-none-any.whl → 0.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- puda_drivers/core/serialcontroller.py +59 -44
- puda_drivers/move/gcode.py +410 -145
- puda_drivers/transfer/liquid/sartorius/sartorius.py +289 -149
- {puda_drivers-0.0.2.dist-info → puda_drivers-0.0.4.dist-info}/METADATA +1 -3
- {puda_drivers-0.0.2.dist-info → puda_drivers-0.0.4.dist-info}/RECORD +7 -7
- {puda_drivers-0.0.2.dist-info → puda_drivers-0.0.4.dist-info}/WHEEL +0 -0
- {puda_drivers-0.0.2.dist-info → puda_drivers-0.0.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,12 +3,11 @@ Generic Serial Controller for communicating with devices over serial ports.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import serial
|
|
6
|
-
import sys
|
|
7
6
|
import time
|
|
8
7
|
import serial.tools.list_ports
|
|
9
8
|
import logging
|
|
10
9
|
from typing import Optional, List, Tuple
|
|
11
|
-
from abc import ABC
|
|
10
|
+
from abc import ABC
|
|
12
11
|
|
|
13
12
|
logger = logging.getLogger(__name__)
|
|
14
13
|
|
|
@@ -58,23 +57,6 @@ class SerialController(ABC):
|
|
|
58
57
|
|
|
59
58
|
self.connect()
|
|
60
59
|
|
|
61
|
-
@staticmethod
|
|
62
|
-
def list_ports(filter_desc: Optional[str] = None) -> List[Tuple[str, str, str]]:
|
|
63
|
-
"""
|
|
64
|
-
Lists available serial ports on the system.
|
|
65
|
-
|
|
66
|
-
.. deprecated:: 0.0.1
|
|
67
|
-
Use the module-level function :func:`list_serial_ports` instead.
|
|
68
|
-
This method is kept for backward compatibility.
|
|
69
|
-
|
|
70
|
-
Args:
|
|
71
|
-
filter_desc: Optional string to filter ports by description (case-insensitive).
|
|
72
|
-
|
|
73
|
-
Returns:
|
|
74
|
-
List of tuples, where each tuple contains (port_name, description, hwid).
|
|
75
|
-
"""
|
|
76
|
-
return list_serial_ports(filter_desc)
|
|
77
|
-
|
|
78
60
|
def connect(self) -> None:
|
|
79
61
|
"""
|
|
80
62
|
Establishes the serial connection to the port.
|
|
@@ -89,7 +71,9 @@ class SerialController(ABC):
|
|
|
89
71
|
|
|
90
72
|
try:
|
|
91
73
|
self._logger.info(
|
|
92
|
-
|
|
74
|
+
"Attempting connection to %s at %s baud.",
|
|
75
|
+
self.port_name,
|
|
76
|
+
self.baudrate,
|
|
93
77
|
)
|
|
94
78
|
self._serial = serial.Serial(
|
|
95
79
|
port=self.port_name,
|
|
@@ -97,10 +81,10 @@ class SerialController(ABC):
|
|
|
97
81
|
timeout=self.timeout,
|
|
98
82
|
)
|
|
99
83
|
self._serial.flush()
|
|
100
|
-
self._logger.info(
|
|
84
|
+
self._logger.info("Successfully connected to %s.", self.port_name)
|
|
101
85
|
except serial.SerialException as e:
|
|
102
86
|
self._serial = None
|
|
103
|
-
self._logger.error(
|
|
87
|
+
self._logger.error("Error connecting to port %s: %s", self.port_name, e)
|
|
104
88
|
raise serial.SerialException(
|
|
105
89
|
f"Error connecting to port {self.port_name}: {e}"
|
|
106
90
|
)
|
|
@@ -113,7 +97,7 @@ class SerialController(ABC):
|
|
|
113
97
|
port_name = self._serial.port
|
|
114
98
|
self._serial.close()
|
|
115
99
|
self._serial = None
|
|
116
|
-
self._logger.info(
|
|
100
|
+
self._logger.info("Serial connection to %s closed.", port_name)
|
|
117
101
|
else:
|
|
118
102
|
self._logger.warning(
|
|
119
103
|
"Serial port already disconnected or was never connected."
|
|
@@ -131,29 +115,31 @@ class SerialController(ABC):
|
|
|
131
115
|
"""
|
|
132
116
|
if not self.is_connected or not self._serial:
|
|
133
117
|
self._logger.error(
|
|
134
|
-
|
|
118
|
+
"Attempt to send command '%s' failed: Device not connected.",
|
|
119
|
+
command,
|
|
135
120
|
)
|
|
136
121
|
# Retain raising an error for being disconnected, as that's a connection state issue
|
|
137
122
|
raise serial.SerialException("Device not connected. Call connect() first.")
|
|
138
123
|
|
|
139
|
-
|
|
140
|
-
self._logger.info(f"-> Sending: {repr(command)}")
|
|
124
|
+
self._logger.info("-> Sending: %r", command)
|
|
141
125
|
|
|
142
126
|
# Send the command
|
|
143
127
|
try:
|
|
144
128
|
self._serial.reset_input_buffer() # clear input buffer
|
|
145
129
|
self._serial.reset_output_buffer() # clear output buffer
|
|
146
|
-
self._serial.write(
|
|
130
|
+
self._serial.write(bytes(command, "utf-8"))
|
|
147
131
|
self._serial.flush()
|
|
148
132
|
|
|
149
133
|
except serial.SerialTimeoutException as e:
|
|
150
134
|
# Log the timeout error and return None as requested (no re-raise)
|
|
151
|
-
self._logger.error(
|
|
135
|
+
self._logger.error("Timeout on command '%s'. Error: %s", command, e)
|
|
152
136
|
return None
|
|
153
137
|
|
|
154
138
|
except serial.SerialException as e:
|
|
155
139
|
self._logger.error(
|
|
156
|
-
|
|
140
|
+
"Serial error writing or reading command '%s'. Error: %s",
|
|
141
|
+
command,
|
|
142
|
+
e,
|
|
157
143
|
)
|
|
158
144
|
return None
|
|
159
145
|
|
|
@@ -173,22 +159,51 @@ class SerialController(ABC):
|
|
|
173
159
|
# Read all available bytes
|
|
174
160
|
response += self._serial.read(self._serial.in_waiting)
|
|
175
161
|
|
|
176
|
-
#
|
|
177
|
-
if b"ok" in response:
|
|
178
|
-
|
|
179
|
-
return response.decode("utf-8", errors="ignore").strip()
|
|
180
|
-
if b"err" in response:
|
|
181
|
-
self._logger.error(f"<- Received error: {response!r}")
|
|
182
|
-
return response.decode("utf-8", errors="ignore").strip()
|
|
162
|
+
# Check for expected response markers for early return
|
|
163
|
+
if b"ok" in response or b"err" in response:
|
|
164
|
+
break
|
|
183
165
|
else:
|
|
184
166
|
time.sleep(0.1)
|
|
185
167
|
|
|
168
|
+
# Timeout reached - check what we got
|
|
186
169
|
if not response:
|
|
187
|
-
self._logger.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
)
|
|
194
|
-
|
|
170
|
+
self._logger.error("No response within %s seconds.", self.timeout)
|
|
171
|
+
raise serial.SerialTimeoutException(
|
|
172
|
+
f"No response received within {self.timeout} seconds."
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Decode once and check the decoded string
|
|
176
|
+
decoded_response = response.decode("utf-8", errors="ignore").strip()
|
|
177
|
+
|
|
178
|
+
if "ok" in decoded_response.lower():
|
|
179
|
+
self._logger.debug("<- Received response: %r", decoded_response)
|
|
180
|
+
elif "err" in decoded_response.lower():
|
|
181
|
+
self._logger.error("<- Received error: %r", decoded_response)
|
|
182
|
+
else:
|
|
183
|
+
self._logger.warning(
|
|
184
|
+
"<- Received unexpected response (no 'ok' or 'err'): %r", decoded_response
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
return decoded_response
|
|
188
|
+
|
|
189
|
+
def execute(self, command: str) -> str:
|
|
190
|
+
"""
|
|
191
|
+
Send a command and read the response atomically.
|
|
192
|
+
|
|
193
|
+
This method combines sending a command and reading its response into a
|
|
194
|
+
single atomic operation, ensuring the response corresponds to the command
|
|
195
|
+
that was just sent. This is the preferred method for commands that
|
|
196
|
+
require a response.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
command: Command string to send (should include protocol terminator if needed)
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
Response string from the device
|
|
203
|
+
|
|
204
|
+
Raises:
|
|
205
|
+
serial.SerialException: If device is not connected or communication fails
|
|
206
|
+
serial.SerialTimeoutException: If no response is received within timeout
|
|
207
|
+
"""
|
|
208
|
+
self._send_command(command)
|
|
209
|
+
return self._read_response()
|