puda-drivers 0.0.8__tar.gz → 0.0.10__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.
Files changed (37) hide show
  1. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/.gitignore +3 -0
  2. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/PKG-INFO +57 -118
  3. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/README.md +55 -117
  4. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/pyproject.toml +2 -1
  5. puda_drivers-0.0.10/src/puda_drivers/cv/__init__.py +4 -0
  6. puda_drivers-0.0.10/src/puda_drivers/cv/camera.py +434 -0
  7. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/machines/first.py +66 -7
  8. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/tests/first.py +6 -1
  9. puda_drivers-0.0.10/tests/webcam.py +28 -0
  10. puda_drivers-0.0.10/uv.lock +209 -0
  11. puda_drivers-0.0.8/uv.lock +0 -23
  12. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/LICENSE +0 -0
  13. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/__init__.py +0 -0
  14. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/core/__init__.py +0 -0
  15. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/core/logging.py +0 -0
  16. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/core/position.py +0 -0
  17. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/core/serialcontroller.py +0 -0
  18. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/labware/__init__.py +0 -0
  19. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/labware/labware.py +0 -0
  20. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/labware/opentrons_96_tiprack_300ul.json +0 -0
  21. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/labware/polyelectric_8_wellplate_30000ul.json +0 -0
  22. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/labware/trash_bin.json +0 -0
  23. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/machines/__init__.py +0 -0
  24. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/move/__init__.py +0 -0
  25. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/move/deck.py +0 -0
  26. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/move/gcode.py +0 -0
  27. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/move/grbl/__init__.py +0 -0
  28. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/move/grbl/api.py +0 -0
  29. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/move/grbl/constants.py +0 -0
  30. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/py.typed +0 -0
  31. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/transfer/liquid/sartorius/__init__.py +0 -0
  32. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/transfer/liquid/sartorius/api.py +0 -0
  33. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/transfer/liquid/sartorius/constants.py +0 -0
  34. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/src/puda_drivers/transfer/liquid/sartorius/sartorius.py +0 -0
  35. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/tests/example.py +0 -0
  36. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/tests/pipette.py +0 -0
  37. {puda_drivers-0.0.8 → puda_drivers-0.0.10}/tests/qubot.py +0 -0
@@ -133,3 +133,6 @@ dmypy.json
133
133
  # Log files
134
134
  logs/
135
135
  *.log
136
+
137
+ # captures
138
+ captures/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: puda-drivers
3
- Version: 0.0.8
3
+ Version: 0.0.10
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
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.14
15
15
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
16
  Classifier: Topic :: System :: Hardware
17
17
  Requires-Python: >=3.8
18
+ Requires-Dist: opencv-python>=4.12.0.88
18
19
  Requires-Dist: pyserial~=3.5
19
20
  Description-Content-Type: text/markdown
20
21
 
@@ -79,160 +80,96 @@ setup_logging(
79
80
 
80
81
  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
82
 
82
- ### Gantry Control (GCode)
83
+ ### First Machine Example
83
84
 
84
- ```python
85
- from puda_drivers.move import GCodeController
86
-
87
- # Initialize and connect to a G-code device
88
- gantry = GCodeController(port_name="/dev/ttyACM0", feed=3000)
89
- gantry.connect()
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
-
97
- # Home the gantry
98
- gantry.home()
85
+ The `First` machine integrates motion control, deck management, liquid handling, and camera capabilities:
99
86
 
100
- # Move to absolute position (validated against limits)
101
- gantry.move_absolute(x=50.0, y=-100.0, z=-10.0)
102
-
103
- # Move relative to current position (validated after conversion to absolute)
104
- gantry.move_relative(x=20.0, y=-10.0)
105
-
106
- # Query current position
107
- position = gantry.query_position()
108
- print(f"Current position: {position}")
87
+ ```python
88
+ import logging
89
+ from puda_drivers.machines import First
90
+ from puda_drivers.core.logging import setup_logging
109
91
 
110
- # Disconnect when done
111
- gantry.disconnect()
112
- ```
92
+ # Configure logging
93
+ setup_logging(
94
+ enable_file_logging=False,
95
+ log_level=logging.DEBUG,
96
+ )
113
97
 
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.
98
+ # Initialize the First machine
99
+ machine = First(
100
+ qubot_port="/dev/ttyACM0",
101
+ sartorius_port="/dev/ttyUSB0",
102
+ camera_index=0,
103
+ )
115
104
 
116
- ### Liquid Handling (Sartorius)
105
+ # Connect all devices
106
+ machine.connect()
117
107
 
118
- ```python
119
- from puda_drivers.transfer.liquid.sartorius import SartoriusController
108
+ # Home the gantry
109
+ machine.qubot.home()
120
110
 
121
- # Initialize and connect to pipette
122
- pipette = SartoriusController(port_name="/dev/ttyUSB0")
123
- pipette.connect()
124
- pipette.initialize()
111
+ # Initialize the pipette
112
+ machine.pipette.initialize()
125
113
 
126
- # Attach tip
127
- pipette.attach_tip()
114
+ # Load labware onto the deck
115
+ machine.load_deck({
116
+ "C1": "trash_bin",
117
+ "C2": "polyelectric_8_wellplate_30000ul",
118
+ "A3": "opentrons_96_tiprack_300ul",
119
+ })
128
120
 
129
- # Aspirate liquid
130
- pipette.aspirate(amount=50.0) # 50 µL
121
+ # Start video recording
122
+ machine.camera.start_video_recording()
131
123
 
132
- # Dispense liquid
133
- pipette.dispense(amount=50.0)
124
+ # Perform liquid handling operations
125
+ machine.attach_tip(slot="A3", well="G8")
126
+ machine.aspirate_from(slot="C2", well="A1", amount=100)
127
+ machine.dispense_to(slot="C2", well="B4", amount=100)
128
+ machine.drop_tip(slot="C1", well="A1")
134
129
 
135
- # Eject tip
136
- pipette.eject_tip()
130
+ # Stop video recording
131
+ machine.camera.stop_video_recording()
137
132
 
138
- # Disconnect when done
139
- pipette.disconnect()
133
+ # Disconnect all devices
134
+ machine.disconnect()
140
135
  ```
141
136
 
142
- ### Combined Workflow
137
+ **Discovering Available Methods**: To explore what methods are available on any class instance, you can use Python's built-in `help()` function:
143
138
 
144
139
  ```python
145
- from puda_drivers.move import GCodeController
146
- from puda_drivers.transfer.liquid.sartorius import SartoriusController
147
-
148
- # Initialize both devices
149
- gantry = GCodeController(port_name="/dev/ttyACM0")
150
- pipette = SartoriusController(port_name="/dev/ttyUSB0")
151
-
152
- gantry.connect()
153
- pipette.connect()
154
-
155
- # Move to source well
156
- gantry.move_absolute(x=50.0, y=-50.0, z=-20.0)
157
- pipette.aspirate(amount=50.0)
158
-
159
- # Move to destination well
160
- gantry.move_absolute(x=150.0, y=-150.0, z=-20.0)
161
- pipette.dispense(amount=50.0)
162
-
163
- # Cleanup
164
- pipette.eject_tip()
165
- gantry.disconnect()
166
- pipette.disconnect()
140
+ machine = First()
141
+ help(machine) # See methods for the First machine
142
+ help(machine.qubot) # See GCodeController methods
143
+ help(machine.pipette) # See SartoriusController methods
144
+ help(machine.camera) # See CameraController methods
167
145
  ```
168
146
 
169
- ## Device Support
170
-
171
- ### Motion Systems
147
+ Alternatively, you can read the source code directly in the `src/puda_drivers/` directory.
172
148
 
173
- - **QuBot** (GCode) - Multi-axis gantry systems compatible with G-code commands
174
- - Supports X, Y, Z, and A axes
175
- - Configurable feed rates
176
- - Position synchronization and homing
177
- - Automatic axis limit validation for safe operation
149
+ ## Device Support
178
150
 
179
- ### Liquid Handling
151
+ The following device types are supported:
180
152
 
153
+ - **GCode** - G-code compatible motion systems (e.g., QuBot)
181
154
  - **Sartorius rLINE®** - Electronic pipettes and robotic dispensers
182
- - Aspirate and dispense operations
183
- - Tip attachment and ejection
184
- - Configurable speeds and volumes
155
+ - **Camera** - Webcams and USB cameras for image and video capture
185
156
 
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
157
+ ## Logging Best Practices
219
158
 
220
159
  For production applications, configure logging at the start of your script:
221
160
 
222
161
  ```python
223
162
  import logging
224
163
  from puda_drivers.core.logging import setup_logging
225
- from puda_drivers.move import GCodeController
226
164
 
227
165
  # Configure logging first, before initializing devices
228
166
  setup_logging(
229
167
  enable_file_logging=True,
230
168
  log_level=logging.INFO,
231
- log_file_name="gantry_operation"
169
+ log_file_name="experiment"
232
170
  )
233
171
 
234
172
  # Now all device operations will be logged
235
- gantry = GCodeController(port_name="/dev/ttyACM0")
236
173
  # ... rest of your code
237
174
  ```
238
175
 
@@ -264,6 +201,8 @@ sartorius_ports = list_serial_ports(filter_desc="Sartorius")
264
201
 
265
202
  ### Setup Development Environment
266
203
 
204
+ First, install `uv` if you haven't already. See the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/) for platform-specific instructions.
205
+
267
206
  ```bash
268
207
  # Create virtual environment
269
208
  uv venv
@@ -59,160 +59,96 @@ setup_logging(
59
59
 
60
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
61
 
62
- ### Gantry Control (GCode)
62
+ ### First Machine Example
63
63
 
64
- ```python
65
- from puda_drivers.move import GCodeController
66
-
67
- # Initialize and connect to a G-code device
68
- gantry = GCodeController(port_name="/dev/ttyACM0", feed=3000)
69
- gantry.connect()
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
-
77
- # Home the gantry
78
- gantry.home()
64
+ The `First` machine integrates motion control, deck management, liquid handling, and camera capabilities:
79
65
 
80
- # Move to absolute position (validated against limits)
81
- gantry.move_absolute(x=50.0, y=-100.0, z=-10.0)
82
-
83
- # Move relative to current position (validated after conversion to absolute)
84
- gantry.move_relative(x=20.0, y=-10.0)
85
-
86
- # Query current position
87
- position = gantry.query_position()
88
- print(f"Current position: {position}")
66
+ ```python
67
+ import logging
68
+ from puda_drivers.machines import First
69
+ from puda_drivers.core.logging import setup_logging
89
70
 
90
- # Disconnect when done
91
- gantry.disconnect()
92
- ```
71
+ # Configure logging
72
+ setup_logging(
73
+ enable_file_logging=False,
74
+ log_level=logging.DEBUG,
75
+ )
93
76
 
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.
77
+ # Initialize the First machine
78
+ machine = First(
79
+ qubot_port="/dev/ttyACM0",
80
+ sartorius_port="/dev/ttyUSB0",
81
+ camera_index=0,
82
+ )
95
83
 
96
- ### Liquid Handling (Sartorius)
84
+ # Connect all devices
85
+ machine.connect()
97
86
 
98
- ```python
99
- from puda_drivers.transfer.liquid.sartorius import SartoriusController
87
+ # Home the gantry
88
+ machine.qubot.home()
100
89
 
101
- # Initialize and connect to pipette
102
- pipette = SartoriusController(port_name="/dev/ttyUSB0")
103
- pipette.connect()
104
- pipette.initialize()
90
+ # Initialize the pipette
91
+ machine.pipette.initialize()
105
92
 
106
- # Attach tip
107
- pipette.attach_tip()
93
+ # Load labware onto the deck
94
+ machine.load_deck({
95
+ "C1": "trash_bin",
96
+ "C2": "polyelectric_8_wellplate_30000ul",
97
+ "A3": "opentrons_96_tiprack_300ul",
98
+ })
108
99
 
109
- # Aspirate liquid
110
- pipette.aspirate(amount=50.0) # 50 µL
100
+ # Start video recording
101
+ machine.camera.start_video_recording()
111
102
 
112
- # Dispense liquid
113
- pipette.dispense(amount=50.0)
103
+ # Perform liquid handling operations
104
+ machine.attach_tip(slot="A3", well="G8")
105
+ machine.aspirate_from(slot="C2", well="A1", amount=100)
106
+ machine.dispense_to(slot="C2", well="B4", amount=100)
107
+ machine.drop_tip(slot="C1", well="A1")
114
108
 
115
- # Eject tip
116
- pipette.eject_tip()
109
+ # Stop video recording
110
+ machine.camera.stop_video_recording()
117
111
 
118
- # Disconnect when done
119
- pipette.disconnect()
112
+ # Disconnect all devices
113
+ machine.disconnect()
120
114
  ```
121
115
 
122
- ### Combined Workflow
116
+ **Discovering Available Methods**: To explore what methods are available on any class instance, you can use Python's built-in `help()` function:
123
117
 
124
118
  ```python
125
- from puda_drivers.move import GCodeController
126
- from puda_drivers.transfer.liquid.sartorius import SartoriusController
127
-
128
- # Initialize both devices
129
- gantry = GCodeController(port_name="/dev/ttyACM0")
130
- pipette = SartoriusController(port_name="/dev/ttyUSB0")
131
-
132
- gantry.connect()
133
- pipette.connect()
134
-
135
- # Move to source well
136
- gantry.move_absolute(x=50.0, y=-50.0, z=-20.0)
137
- pipette.aspirate(amount=50.0)
138
-
139
- # Move to destination well
140
- gantry.move_absolute(x=150.0, y=-150.0, z=-20.0)
141
- pipette.dispense(amount=50.0)
142
-
143
- # Cleanup
144
- pipette.eject_tip()
145
- gantry.disconnect()
146
- pipette.disconnect()
119
+ machine = First()
120
+ help(machine) # See methods for the First machine
121
+ help(machine.qubot) # See GCodeController methods
122
+ help(machine.pipette) # See SartoriusController methods
123
+ help(machine.camera) # See CameraController methods
147
124
  ```
148
125
 
149
- ## Device Support
150
-
151
- ### Motion Systems
126
+ Alternatively, you can read the source code directly in the `src/puda_drivers/` directory.
152
127
 
153
- - **QuBot** (GCode) - Multi-axis gantry systems compatible with G-code commands
154
- - Supports X, Y, Z, and A axes
155
- - Configurable feed rates
156
- - Position synchronization and homing
157
- - Automatic axis limit validation for safe operation
128
+ ## Device Support
158
129
 
159
- ### Liquid Handling
130
+ The following device types are supported:
160
131
 
132
+ - **GCode** - G-code compatible motion systems (e.g., QuBot)
161
133
  - **Sartorius rLINE®** - Electronic pipettes and robotic dispensers
162
- - Aspirate and dispense operations
163
- - Tip attachment and ejection
164
- - Configurable speeds and volumes
134
+ - **Camera** - Webcams and USB cameras for image and video capture
165
135
 
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
136
+ ## Logging Best Practices
199
137
 
200
138
  For production applications, configure logging at the start of your script:
201
139
 
202
140
  ```python
203
141
  import logging
204
142
  from puda_drivers.core.logging import setup_logging
205
- from puda_drivers.move import GCodeController
206
143
 
207
144
  # Configure logging first, before initializing devices
208
145
  setup_logging(
209
146
  enable_file_logging=True,
210
147
  log_level=logging.INFO,
211
- log_file_name="gantry_operation"
148
+ log_file_name="experiment"
212
149
  )
213
150
 
214
151
  # Now all device operations will be logged
215
- gantry = GCodeController(port_name="/dev/ttyACM0")
216
152
  # ... rest of your code
217
153
  ```
218
154
 
@@ -244,6 +180,8 @@ sartorius_ports = list_serial_ports(filter_desc="Sartorius")
244
180
 
245
181
  ### Setup Development Environment
246
182
 
183
+ First, install `uv` if you haven't already. See the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/) for platform-specific instructions.
184
+
247
185
  ```bash
248
186
  # Create virtual environment
249
187
  uv venv
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "puda-drivers"
3
- version = "0.0.8"
3
+ version = "0.0.10"
4
4
  description = "Hardware drivers for the PUDA platform."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -19,6 +19,7 @@ classifiers = [
19
19
  license = "MIT"
20
20
  license-files = ["LICEN[CS]E*"]
21
21
  dependencies = [
22
+ "opencv-python>=4.12.0.88",
22
23
  "pyserial~=3.5",
23
24
  ]
24
25
 
@@ -0,0 +1,4 @@
1
+ from .camera import CameraController, list_cameras
2
+
3
+ __all__ = ["CameraController", "list_cameras"]
4
+