puda-drivers 0.0.4__tar.gz → 0.0.6__tar.gz
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-0.0.4 → puda_drivers-0.0.6}/.gitignore +4 -0
- {puda_drivers-0.0.4 → puda_drivers-0.0.6}/PKG-INFO +99 -3
- {puda_drivers-0.0.4 → puda_drivers-0.0.6}/README.md +98 -2
- {puda_drivers-0.0.4 → puda_drivers-0.0.6}/pyproject.toml +1 -1
- puda_drivers-0.0.6/src/puda_drivers/core/__init__.py +4 -0
- puda_drivers-0.0.6/src/puda_drivers/core/logging.py +73 -0
- {puda_drivers-0.0.4 → puda_drivers-0.0.6}/src/puda_drivers/core/serialcontroller.py +13 -7
- {puda_drivers-0.0.4 → puda_drivers-0.0.6}/src/puda_drivers/move/gcode.py +119 -124
- {puda_drivers-0.0.4 → puda_drivers-0.0.6}/tests/example.py +2 -2
- puda_drivers-0.0.6/tests/pipette.py +84 -0
- puda_drivers-0.0.6/tests/qubot.py +108 -0
- {puda_drivers-0.0.4 → puda_drivers-0.0.6}/tests/together.py +28 -20
- {puda_drivers-0.0.4 → puda_drivers-0.0.6}/uv.lock +1 -1
- puda_drivers-0.0.4/src/puda_drivers/core/__init__.py +0 -3
- puda_drivers-0.0.4/tests/qubot.py +0 -77
- puda_drivers-0.0.4/tests/sartorius.py +0 -82
- {puda_drivers-0.0.4 → puda_drivers-0.0.6}/LICENSE +0 -0
- {puda_drivers-0.0.4 → puda_drivers-0.0.6}/src/puda_drivers/__init__.py +0 -0
- {puda_drivers-0.0.4 → puda_drivers-0.0.6}/src/puda_drivers/move/__init__.py +0 -0
- {puda_drivers-0.0.4 → puda_drivers-0.0.6}/src/puda_drivers/move/grbl/__init__.py +0 -0
- {puda_drivers-0.0.4 → puda_drivers-0.0.6}/src/puda_drivers/move/grbl/api.py +0 -0
- {puda_drivers-0.0.4 → puda_drivers-0.0.6}/src/puda_drivers/move/grbl/constants.py +0 -0
- {puda_drivers-0.0.4 → puda_drivers-0.0.6}/src/puda_drivers/py.typed +0 -0
- {puda_drivers-0.0.4 → puda_drivers-0.0.6}/src/puda_drivers/transfer/liquid/sartorius/__init__.py +0 -0
- {puda_drivers-0.0.4 → puda_drivers-0.0.6}/src/puda_drivers/transfer/liquid/sartorius/api.py +0 -0
- {puda_drivers-0.0.4 → puda_drivers-0.0.6}/src/puda_drivers/transfer/liquid/sartorius/constants.py +0 -0
- {puda_drivers-0.0.4 → puda_drivers-0.0.6}/src/puda_drivers/transfer/liquid/sartorius/sartorius.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: puda-drivers
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary: Hardware drivers for the PUDA platform.
|
|
5
5
|
Project-URL: Homepage, https://github.com/zhao-bears/puda-drivers
|
|
6
6
|
Project-URL: Issues, https://github.com/zhao-bears/puda-drivers/issues
|
|
@@ -27,6 +27,7 @@ Hardware drivers for the PUDA (Physical Unified Device Architecture) platform. T
|
|
|
27
27
|
- **Gantry Control**: Control G-code compatible motion systems (e.g., QuBot)
|
|
28
28
|
- **Liquid Handling**: Interface with Sartorius rLINE® pipettes and dispensers
|
|
29
29
|
- **Serial Communication**: Robust serial port management with automatic reconnection
|
|
30
|
+
- **Logging**: Configurable logging with optional file output to logs folder
|
|
30
31
|
- **Cross-platform**: Works on Linux, macOS, and Windows
|
|
31
32
|
|
|
32
33
|
## Installation
|
|
@@ -47,6 +48,37 @@ pip install -e .
|
|
|
47
48
|
|
|
48
49
|
## Quick Start
|
|
49
50
|
|
|
51
|
+
### Logging Configuration
|
|
52
|
+
|
|
53
|
+
Configure logging for your application with optional file output:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import logging
|
|
57
|
+
from puda_drivers.core.logging import setup_logging
|
|
58
|
+
|
|
59
|
+
# Configure logging with file output enabled
|
|
60
|
+
setup_logging(
|
|
61
|
+
enable_file_logging=True,
|
|
62
|
+
log_level=logging.DEBUG,
|
|
63
|
+
logs_folder="logs", # Optional: default to logs
|
|
64
|
+
log_file_name="my_experiment" # Optional: custom log file name
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Or disable file logging (console only)
|
|
68
|
+
setup_logging(
|
|
69
|
+
enable_file_logging=False,
|
|
70
|
+
log_level=logging.INFO
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Logging Options:**
|
|
75
|
+
- `enable_file_logging`: If `True`, logs are written to files in the `logs/` folder. If `False`, logs only go to console (default: `False`)
|
|
76
|
+
- `log_level`: Logging level constant (e.g., `logging.DEBUG`, `logging.INFO`, `logging.WARNING`, `logging.ERROR`, `logging.CRITICAL`) (default: `logging.DEBUG`)
|
|
77
|
+
- `logs_folder`: Name of the folder to store log files (default: `"logs"`)
|
|
78
|
+
- `log_file_name`: Custom name for the log file. If `None` or empty, uses timestamp-based name (e.g., `log_20250101_120000.log`). If provided without `.log` extension, it will be added automatically.
|
|
79
|
+
|
|
80
|
+
When file logging is enabled, logs are saved to timestamped files (unless a custom name is provided) in the `logs/` folder. The logs folder is created automatically if it doesn't exist.
|
|
81
|
+
|
|
50
82
|
### Gantry Control (GCode)
|
|
51
83
|
|
|
52
84
|
```python
|
|
@@ -56,13 +88,19 @@ from puda_drivers.move import GCodeController
|
|
|
56
88
|
gantry = GCodeController(port_name="/dev/ttyACM0", feed=3000)
|
|
57
89
|
gantry.connect()
|
|
58
90
|
|
|
91
|
+
# Configure axis limits for safety (recommended)
|
|
92
|
+
gantry.set_axis_limits("X", 0, 200)
|
|
93
|
+
gantry.set_axis_limits("Y", -200, 0)
|
|
94
|
+
gantry.set_axis_limits("Z", -100, 0)
|
|
95
|
+
gantry.set_axis_limits("A", -180, 180)
|
|
96
|
+
|
|
59
97
|
# Home the gantry
|
|
60
98
|
gantry.home()
|
|
61
99
|
|
|
62
|
-
# Move to absolute position
|
|
100
|
+
# Move to absolute position (validated against limits)
|
|
63
101
|
gantry.move_absolute(x=50.0, y=-100.0, z=-10.0)
|
|
64
102
|
|
|
65
|
-
# Move relative to current position
|
|
103
|
+
# Move relative to current position (validated after conversion to absolute)
|
|
66
104
|
gantry.move_relative(x=20.0, y=-10.0)
|
|
67
105
|
|
|
68
106
|
# Query current position
|
|
@@ -73,6 +111,8 @@ print(f"Current position: {position}")
|
|
|
73
111
|
gantry.disconnect()
|
|
74
112
|
```
|
|
75
113
|
|
|
114
|
+
**Axis Limits and Validation**: The `move_absolute()` and `move_relative()` methods automatically validate that target positions are within configured axis limits. If a position is outside the limits, a `ValueError` is raised before any movement is executed. Use `set_axis_limits()` to configure limits for each axis.
|
|
115
|
+
|
|
76
116
|
### Liquid Handling (Sartorius)
|
|
77
117
|
|
|
78
118
|
```python
|
|
@@ -134,6 +174,7 @@ pipette.disconnect()
|
|
|
134
174
|
- Supports X, Y, Z, and A axes
|
|
135
175
|
- Configurable feed rates
|
|
136
176
|
- Position synchronization and homing
|
|
177
|
+
- Automatic axis limit validation for safe operation
|
|
137
178
|
|
|
138
179
|
### Liquid Handling
|
|
139
180
|
|
|
@@ -142,6 +183,61 @@ pipette.disconnect()
|
|
|
142
183
|
- Tip attachment and ejection
|
|
143
184
|
- Configurable speeds and volumes
|
|
144
185
|
|
|
186
|
+
## Error Handling
|
|
187
|
+
|
|
188
|
+
### Axis Limit Validation
|
|
189
|
+
|
|
190
|
+
Both `move_absolute()` and `move_relative()` validate positions against configured axis limits before executing any movement. If a position is outside the limits, a `ValueError` is raised:
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
from puda_drivers.move import GCodeController
|
|
194
|
+
|
|
195
|
+
gantry = GCodeController(port_name="/dev/ttyACM0")
|
|
196
|
+
gantry.connect()
|
|
197
|
+
|
|
198
|
+
# Set axis limits
|
|
199
|
+
gantry.set_axis_limits("X", 0, 200)
|
|
200
|
+
gantry.set_axis_limits("Y", -200, 0)
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
# This will raise ValueError: Value 250 outside axis limits [0, 200]
|
|
204
|
+
gantry.move_absolute(x=250.0, y=-50.0)
|
|
205
|
+
except ValueError as e:
|
|
206
|
+
print(f"Move rejected: {e}")
|
|
207
|
+
|
|
208
|
+
# Relative moves are also validated after conversion to absolute positions
|
|
209
|
+
try:
|
|
210
|
+
# If current X is 150, moving 100 more would exceed the limit
|
|
211
|
+
gantry.move_relative(x=100.0)
|
|
212
|
+
except ValueError as e:
|
|
213
|
+
print(f"Move rejected: {e}")
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Validation errors are automatically logged at the ERROR level before the exception is raised.
|
|
217
|
+
|
|
218
|
+
### Logging Best Practices
|
|
219
|
+
|
|
220
|
+
For production applications, configure logging at the start of your script:
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
import logging
|
|
224
|
+
from puda_drivers.core.logging import setup_logging
|
|
225
|
+
from puda_drivers.move import GCodeController
|
|
226
|
+
|
|
227
|
+
# Configure logging first, before initializing devices
|
|
228
|
+
setup_logging(
|
|
229
|
+
enable_file_logging=True,
|
|
230
|
+
log_level=logging.INFO,
|
|
231
|
+
log_file_name="gantry_operation"
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Now all device operations will be logged
|
|
235
|
+
gantry = GCodeController(port_name="/dev/ttyACM0")
|
|
236
|
+
# ... rest of your code
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
This ensures all device communication, movements, and errors are captured in log files for debugging and audit purposes.
|
|
240
|
+
|
|
145
241
|
## Finding Serial Ports
|
|
146
242
|
|
|
147
243
|
To discover available serial ports on your system:
|
|
@@ -7,6 +7,7 @@ Hardware drivers for the PUDA (Physical Unified Device Architecture) platform. T
|
|
|
7
7
|
- **Gantry Control**: Control G-code compatible motion systems (e.g., QuBot)
|
|
8
8
|
- **Liquid Handling**: Interface with Sartorius rLINE® pipettes and dispensers
|
|
9
9
|
- **Serial Communication**: Robust serial port management with automatic reconnection
|
|
10
|
+
- **Logging**: Configurable logging with optional file output to logs folder
|
|
10
11
|
- **Cross-platform**: Works on Linux, macOS, and Windows
|
|
11
12
|
|
|
12
13
|
## Installation
|
|
@@ -27,6 +28,37 @@ pip install -e .
|
|
|
27
28
|
|
|
28
29
|
## Quick Start
|
|
29
30
|
|
|
31
|
+
### Logging Configuration
|
|
32
|
+
|
|
33
|
+
Configure logging for your application with optional file output:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import logging
|
|
37
|
+
from puda_drivers.core.logging import setup_logging
|
|
38
|
+
|
|
39
|
+
# Configure logging with file output enabled
|
|
40
|
+
setup_logging(
|
|
41
|
+
enable_file_logging=True,
|
|
42
|
+
log_level=logging.DEBUG,
|
|
43
|
+
logs_folder="logs", # Optional: default to logs
|
|
44
|
+
log_file_name="my_experiment" # Optional: custom log file name
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Or disable file logging (console only)
|
|
48
|
+
setup_logging(
|
|
49
|
+
enable_file_logging=False,
|
|
50
|
+
log_level=logging.INFO
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Logging Options:**
|
|
55
|
+
- `enable_file_logging`: If `True`, logs are written to files in the `logs/` folder. If `False`, logs only go to console (default: `False`)
|
|
56
|
+
- `log_level`: Logging level constant (e.g., `logging.DEBUG`, `logging.INFO`, `logging.WARNING`, `logging.ERROR`, `logging.CRITICAL`) (default: `logging.DEBUG`)
|
|
57
|
+
- `logs_folder`: Name of the folder to store log files (default: `"logs"`)
|
|
58
|
+
- `log_file_name`: Custom name for the log file. If `None` or empty, uses timestamp-based name (e.g., `log_20250101_120000.log`). If provided without `.log` extension, it will be added automatically.
|
|
59
|
+
|
|
60
|
+
When file logging is enabled, logs are saved to timestamped files (unless a custom name is provided) in the `logs/` folder. The logs folder is created automatically if it doesn't exist.
|
|
61
|
+
|
|
30
62
|
### Gantry Control (GCode)
|
|
31
63
|
|
|
32
64
|
```python
|
|
@@ -36,13 +68,19 @@ from puda_drivers.move import GCodeController
|
|
|
36
68
|
gantry = GCodeController(port_name="/dev/ttyACM0", feed=3000)
|
|
37
69
|
gantry.connect()
|
|
38
70
|
|
|
71
|
+
# Configure axis limits for safety (recommended)
|
|
72
|
+
gantry.set_axis_limits("X", 0, 200)
|
|
73
|
+
gantry.set_axis_limits("Y", -200, 0)
|
|
74
|
+
gantry.set_axis_limits("Z", -100, 0)
|
|
75
|
+
gantry.set_axis_limits("A", -180, 180)
|
|
76
|
+
|
|
39
77
|
# Home the gantry
|
|
40
78
|
gantry.home()
|
|
41
79
|
|
|
42
|
-
# Move to absolute position
|
|
80
|
+
# Move to absolute position (validated against limits)
|
|
43
81
|
gantry.move_absolute(x=50.0, y=-100.0, z=-10.0)
|
|
44
82
|
|
|
45
|
-
# Move relative to current position
|
|
83
|
+
# Move relative to current position (validated after conversion to absolute)
|
|
46
84
|
gantry.move_relative(x=20.0, y=-10.0)
|
|
47
85
|
|
|
48
86
|
# Query current position
|
|
@@ -53,6 +91,8 @@ print(f"Current position: {position}")
|
|
|
53
91
|
gantry.disconnect()
|
|
54
92
|
```
|
|
55
93
|
|
|
94
|
+
**Axis Limits and Validation**: The `move_absolute()` and `move_relative()` methods automatically validate that target positions are within configured axis limits. If a position is outside the limits, a `ValueError` is raised before any movement is executed. Use `set_axis_limits()` to configure limits for each axis.
|
|
95
|
+
|
|
56
96
|
### Liquid Handling (Sartorius)
|
|
57
97
|
|
|
58
98
|
```python
|
|
@@ -114,6 +154,7 @@ pipette.disconnect()
|
|
|
114
154
|
- Supports X, Y, Z, and A axes
|
|
115
155
|
- Configurable feed rates
|
|
116
156
|
- Position synchronization and homing
|
|
157
|
+
- Automatic axis limit validation for safe operation
|
|
117
158
|
|
|
118
159
|
### Liquid Handling
|
|
119
160
|
|
|
@@ -122,6 +163,61 @@ pipette.disconnect()
|
|
|
122
163
|
- Tip attachment and ejection
|
|
123
164
|
- Configurable speeds and volumes
|
|
124
165
|
|
|
166
|
+
## Error Handling
|
|
167
|
+
|
|
168
|
+
### Axis Limit Validation
|
|
169
|
+
|
|
170
|
+
Both `move_absolute()` and `move_relative()` validate positions against configured axis limits before executing any movement. If a position is outside the limits, a `ValueError` is raised:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from puda_drivers.move import GCodeController
|
|
174
|
+
|
|
175
|
+
gantry = GCodeController(port_name="/dev/ttyACM0")
|
|
176
|
+
gantry.connect()
|
|
177
|
+
|
|
178
|
+
# Set axis limits
|
|
179
|
+
gantry.set_axis_limits("X", 0, 200)
|
|
180
|
+
gantry.set_axis_limits("Y", -200, 0)
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
# This will raise ValueError: Value 250 outside axis limits [0, 200]
|
|
184
|
+
gantry.move_absolute(x=250.0, y=-50.0)
|
|
185
|
+
except ValueError as e:
|
|
186
|
+
print(f"Move rejected: {e}")
|
|
187
|
+
|
|
188
|
+
# Relative moves are also validated after conversion to absolute positions
|
|
189
|
+
try:
|
|
190
|
+
# If current X is 150, moving 100 more would exceed the limit
|
|
191
|
+
gantry.move_relative(x=100.0)
|
|
192
|
+
except ValueError as e:
|
|
193
|
+
print(f"Move rejected: {e}")
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Validation errors are automatically logged at the ERROR level before the exception is raised.
|
|
197
|
+
|
|
198
|
+
### Logging Best Practices
|
|
199
|
+
|
|
200
|
+
For production applications, configure logging at the start of your script:
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
import logging
|
|
204
|
+
from puda_drivers.core.logging import setup_logging
|
|
205
|
+
from puda_drivers.move import GCodeController
|
|
206
|
+
|
|
207
|
+
# Configure logging first, before initializing devices
|
|
208
|
+
setup_logging(
|
|
209
|
+
enable_file_logging=True,
|
|
210
|
+
log_level=logging.INFO,
|
|
211
|
+
log_file_name="gantry_operation"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Now all device operations will be logged
|
|
215
|
+
gantry = GCodeController(port_name="/dev/ttyACM0")
|
|
216
|
+
# ... rest of your code
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
This ensures all device communication, movements, and errors are captured in log files for debugging and audit purposes.
|
|
220
|
+
|
|
125
221
|
## Finding Serial Ports
|
|
126
222
|
|
|
127
223
|
To discover available serial ports on your system:
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logging configuration utility.
|
|
3
|
+
|
|
4
|
+
This module provides a function to configure logging with optional file output
|
|
5
|
+
to a logs folder.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def setup_logging(
|
|
15
|
+
enable_file_logging: bool = False,
|
|
16
|
+
log_level: int = logging.DEBUG,
|
|
17
|
+
logs_folder: str = "logs",
|
|
18
|
+
log_file_name: Optional[str] = None,
|
|
19
|
+
) -> None:
|
|
20
|
+
"""
|
|
21
|
+
Configure logging with optional file output to a logs folder.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
enable_file_logging: If True, logs will be written to files in the logs folder.
|
|
25
|
+
If False, logs will only be output to console.
|
|
26
|
+
log_level: Logging level constant from logging module (e.g., logging.DEBUG,
|
|
27
|
+
logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL).
|
|
28
|
+
Defaults to logging.DEBUG.
|
|
29
|
+
logs_folder: Name of the folder to store log files (default: "logs")
|
|
30
|
+
log_file_name: Custom name for the log file. If None or empty string, uses
|
|
31
|
+
timestamp-based name. If provided without .log extension, it will
|
|
32
|
+
be added automatically.
|
|
33
|
+
"""
|
|
34
|
+
# Create logs folder if file logging is enabled
|
|
35
|
+
if enable_file_logging:
|
|
36
|
+
log_dir = Path(logs_folder)
|
|
37
|
+
log_dir.mkdir(exist_ok=True)
|
|
38
|
+
|
|
39
|
+
# Create a log file with custom name or timestamp
|
|
40
|
+
if log_file_name and log_file_name.strip(): # None or empty/whitespace strings use timestamp
|
|
41
|
+
# Ensure .log extension if not present
|
|
42
|
+
if not log_file_name.endswith(".log"):
|
|
43
|
+
log_file_name = f"{log_file_name}.log"
|
|
44
|
+
log_file = log_dir / log_file_name
|
|
45
|
+
else:
|
|
46
|
+
# Default: timestamp-based name
|
|
47
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
48
|
+
log_file = log_dir / f"log_{timestamp}.log"
|
|
49
|
+
|
|
50
|
+
# Configure logging with both console and file handlers
|
|
51
|
+
logging.basicConfig(
|
|
52
|
+
level=log_level,
|
|
53
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
54
|
+
handlers=[
|
|
55
|
+
logging.StreamHandler(), # Console output
|
|
56
|
+
logging.FileHandler(log_file, mode="w"), # File output
|
|
57
|
+
],
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Log that file logging is enabled
|
|
61
|
+
logger = logging.getLogger(__name__)
|
|
62
|
+
logger.info("File logging enabled. Log file: %s", log_file)
|
|
63
|
+
else:
|
|
64
|
+
# Configure logging with only console handler
|
|
65
|
+
logging.basicConfig(
|
|
66
|
+
level=log_level,
|
|
67
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
68
|
+
handlers=[logging.StreamHandler()], # Console output only
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Log that file logging is disabled
|
|
72
|
+
logger = logging.getLogger(__name__)
|
|
73
|
+
logger.info("File logging disabled. Logs will only be output to console.")
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
Generic Serial Controller for communicating with devices over serial ports.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import serial
|
|
6
5
|
import time
|
|
7
|
-
import serial.tools.list_ports
|
|
8
6
|
import logging
|
|
9
7
|
from typing import Optional, List, Tuple
|
|
10
8
|
from abc import ABC
|
|
9
|
+
import serial
|
|
10
|
+
import serial.tools.list_ports
|
|
11
11
|
|
|
12
12
|
logger = logging.getLogger(__name__)
|
|
13
13
|
|
|
@@ -45,7 +45,7 @@ def list_serial_ports(filter_desc: Optional[str] = None) -> List[Tuple[str, str,
|
|
|
45
45
|
|
|
46
46
|
class SerialController(ABC):
|
|
47
47
|
DEFAULT_BAUDRATE = 9600
|
|
48
|
-
DEFAULT_TIMEOUT =
|
|
48
|
+
DEFAULT_TIMEOUT = 30 # seconds
|
|
49
49
|
POLL_INTERVAL = 0.1 # seconds
|
|
50
50
|
|
|
51
51
|
def __init__(self, port_name, baudrate=DEFAULT_BAUDRATE, timeout=DEFAULT_TIMEOUT):
|
|
@@ -127,8 +127,8 @@ class SerialController(ABC):
|
|
|
127
127
|
try:
|
|
128
128
|
self._serial.reset_input_buffer() # clear input buffer
|
|
129
129
|
self._serial.reset_output_buffer() # clear output buffer
|
|
130
|
-
self._serial.write(bytes(command, "utf-8"))
|
|
131
130
|
self._serial.flush()
|
|
131
|
+
self._serial.write(bytes(command, "utf-8"))
|
|
132
132
|
|
|
133
133
|
except serial.SerialTimeoutException as e:
|
|
134
134
|
# Log the timeout error and return None as requested (no re-raise)
|
|
@@ -159,9 +159,13 @@ class SerialController(ABC):
|
|
|
159
159
|
# Read all available bytes
|
|
160
160
|
response += self._serial.read(self._serial.in_waiting)
|
|
161
161
|
|
|
162
|
-
# Check for expected response markers for early return
|
|
162
|
+
# Check for expected response markers for early return for qubot
|
|
163
163
|
if b"ok" in response or b"err" in response:
|
|
164
164
|
break
|
|
165
|
+
|
|
166
|
+
# Check for expected response markers for early return for sartorius
|
|
167
|
+
if b"\xba\r" in response:
|
|
168
|
+
break
|
|
165
169
|
else:
|
|
166
170
|
time.sleep(0.1)
|
|
167
171
|
|
|
@@ -175,10 +179,12 @@ class SerialController(ABC):
|
|
|
175
179
|
# Decode once and check the decoded string
|
|
176
180
|
decoded_response = response.decode("utf-8", errors="ignore").strip()
|
|
177
181
|
|
|
178
|
-
if "ok" in decoded_response.lower():
|
|
182
|
+
if "ok" in decoded_response.lower(): # for qubot
|
|
179
183
|
self._logger.debug("<- Received response: %r", decoded_response)
|
|
180
|
-
elif "err" in decoded_response.lower():
|
|
184
|
+
elif "err" in decoded_response.lower(): # for qubot
|
|
181
185
|
self._logger.error("<- Received error: %r", decoded_response)
|
|
186
|
+
elif "º" in decoded_response: # for sartorius
|
|
187
|
+
self._logger.debug("<- Received response: %r", decoded_response)
|
|
182
188
|
else:
|
|
183
189
|
self._logger.warning(
|
|
184
190
|
"<- Received unexpected response (no 'ok' or 'err'): %r", decoded_response
|