puda-drivers 0.0.11__tar.gz → 0.0.12__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 (36) hide show
  1. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/PKG-INFO +9 -14
  2. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/README.md +8 -13
  3. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/pyproject.toml +1 -1
  4. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/machines/first.py +115 -7
  5. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/tests/first.py +10 -9
  6. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/uv.lock +1 -1
  7. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/.gitignore +0 -0
  8. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/LICENSE +0 -0
  9. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/__init__.py +0 -0
  10. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/core/__init__.py +0 -0
  11. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/core/logging.py +0 -0
  12. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/core/position.py +0 -0
  13. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/core/serialcontroller.py +0 -0
  14. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/cv/__init__.py +0 -0
  15. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/cv/camera.py +0 -0
  16. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/labware/__init__.py +0 -0
  17. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/labware/labware.py +0 -0
  18. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/labware/opentrons_96_tiprack_300ul.json +0 -0
  19. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/labware/polyelectric_8_wellplate_30000ul.json +0 -0
  20. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/labware/trash_bin.json +0 -0
  21. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/machines/__init__.py +0 -0
  22. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/move/__init__.py +0 -0
  23. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/move/deck.py +0 -0
  24. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/move/gcode.py +0 -0
  25. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/move/grbl/__init__.py +0 -0
  26. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/move/grbl/api.py +0 -0
  27. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/move/grbl/constants.py +0 -0
  28. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/py.typed +0 -0
  29. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/transfer/liquid/sartorius/__init__.py +0 -0
  30. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/transfer/liquid/sartorius/api.py +0 -0
  31. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/transfer/liquid/sartorius/constants.py +0 -0
  32. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/src/puda_drivers/transfer/liquid/sartorius/sartorius.py +0 -0
  33. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/tests/example.py +0 -0
  34. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/tests/pipette.py +0 -0
  35. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/tests/qubot.py +0 -0
  36. {puda_drivers-0.0.11 → puda_drivers-0.0.12}/tests/webcam.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: puda-drivers
3
- Version: 0.0.11
3
+ Version: 0.0.12
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
@@ -85,6 +85,7 @@ When file logging is enabled, logs are saved to timestamped files (unless a cust
85
85
  The `First` machine integrates motion control, deck management, liquid handling, and camera capabilities:
86
86
 
87
87
  ```python
88
+ import time
88
89
  import logging
89
90
  from puda_drivers.machines import First
90
91
  from puda_drivers.core.logging import setup_logging
@@ -102,14 +103,8 @@ machine = First(
102
103
  camera_index=0,
103
104
  )
104
105
 
105
- # Connect all devices
106
- machine.connect()
107
-
108
- # Home the gantry
109
- machine.qubot.home()
110
-
111
- # Initialize the pipette
112
- machine.pipette.initialize()
106
+ # Start up the machine (connects all controllers, homes gantry, and initializes pipette)
107
+ machine.startup()
113
108
 
114
109
  # Load labware onto the deck
115
110
  machine.load_deck({
@@ -119,19 +114,19 @@ machine.load_deck({
119
114
  })
120
115
 
121
116
  # Start video recording
122
- machine.camera.start_video_recording()
117
+ machine.start_video_recording()
123
118
 
124
119
  # Perform liquid handling operations
125
120
  machine.attach_tip(slot="A3", well="G8")
126
121
  machine.aspirate_from(slot="C2", well="A1", amount=100, height_from_bottom=10.0)
127
122
  machine.dispense_to(slot="C2", well="B4", amount=100, height_from_bottom=30.0)
128
- machine.drop_tip(slot="C1", well="A1")
123
+ machine.drop_tip(slot="C1", well="A1", height_from_bottom=10)
129
124
 
130
125
  # Stop video recording
131
- machine.camera.stop_video_recording()
126
+ machine.stop_video_recording()
132
127
 
133
- # Disconnect all devices
134
- machine.disconnect()
128
+ # Shutdown the machine (gracefully disconnects all controllers)
129
+ machine.shutdown()
135
130
  ```
136
131
 
137
132
  **Discovering Available Methods**: To explore what methods are available on any class instance, you can use Python's built-in `help()` function:
@@ -64,6 +64,7 @@ When file logging is enabled, logs are saved to timestamped files (unless a cust
64
64
  The `First` machine integrates motion control, deck management, liquid handling, and camera capabilities:
65
65
 
66
66
  ```python
67
+ import time
67
68
  import logging
68
69
  from puda_drivers.machines import First
69
70
  from puda_drivers.core.logging import setup_logging
@@ -81,14 +82,8 @@ machine = First(
81
82
  camera_index=0,
82
83
  )
83
84
 
84
- # Connect all devices
85
- machine.connect()
86
-
87
- # Home the gantry
88
- machine.qubot.home()
89
-
90
- # Initialize the pipette
91
- machine.pipette.initialize()
85
+ # Start up the machine (connects all controllers, homes gantry, and initializes pipette)
86
+ machine.startup()
92
87
 
93
88
  # Load labware onto the deck
94
89
  machine.load_deck({
@@ -98,19 +93,19 @@ machine.load_deck({
98
93
  })
99
94
 
100
95
  # Start video recording
101
- machine.camera.start_video_recording()
96
+ machine.start_video_recording()
102
97
 
103
98
  # Perform liquid handling operations
104
99
  machine.attach_tip(slot="A3", well="G8")
105
100
  machine.aspirate_from(slot="C2", well="A1", amount=100, height_from_bottom=10.0)
106
101
  machine.dispense_to(slot="C2", well="B4", amount=100, height_from_bottom=30.0)
107
- machine.drop_tip(slot="C1", well="A1")
102
+ machine.drop_tip(slot="C1", well="A1", height_from_bottom=10)
108
103
 
109
104
  # Stop video recording
110
- machine.camera.stop_video_recording()
105
+ machine.stop_video_recording()
111
106
 
112
- # Disconnect all devices
113
- machine.disconnect()
107
+ # Shutdown the machine (gracefully disconnects all controllers)
108
+ machine.shutdown()
114
109
  ```
115
110
 
116
111
  **Discovering Available Methods**: To explore what methods are available on any class instance, you can use Python's built-in `help()` function:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "puda-drivers"
3
- version = "0.0.11"
3
+ version = "0.0.12"
4
4
  description = "Hardware drivers for the PUDA platform."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -9,7 +9,9 @@ This class demonstrates the integration of:
9
9
 
10
10
  import logging
11
11
  import time
12
+ from pathlib import Path
12
13
  from typing import Optional, Dict, Tuple, Type, Union
14
+ import numpy as np
13
15
  from puda_drivers.move import GCodeController, Deck
14
16
  from puda_drivers.core import Position
15
17
  from puda_drivers.transfer.liquid.sartorius import SartoriusController
@@ -118,21 +120,44 @@ class First:
118
120
  camera_index if camera_index is not None else self.DEFAULT_CAMERA_INDEX,
119
121
  )
120
122
 
121
- def connect(self):
122
- """Connect all controllers."""
123
- self._logger.info("Connecting all controllers")
123
+ def startup(self):
124
+ """
125
+ Start up the machine by connecting all controllers and initializing subsystems.
126
+
127
+ This method:
128
+ - Connects to all controllers (gantry, pipette, camera)
129
+ - Homes the gantry to establish a known position
130
+ - Initializes the pipette to reset it to a known state
131
+
132
+ The machine is ready for operations after this method completes.
133
+ """
134
+ self._logger.info("Starting up machine and connecting all controllers")
124
135
  self.qubot.connect()
125
136
  self.pipette.connect()
126
137
  self.camera.connect()
127
138
  self._logger.info("All controllers connected successfully")
128
139
 
129
- def disconnect(self):
130
- """Disconnect all controllers."""
131
- self._logger.info("Disconnecting all controllers")
140
+ # Home the gantry to establish known position
141
+ self._logger.info("Homing gantry...")
142
+ self.qubot.home()
143
+
144
+ # Initialize the pipette (all pipette operations need to wait 5 seconds for completion)
145
+ self._logger.info("Initializing pipette...")
146
+ self.pipette.initialize()
147
+ time.sleep(5)
148
+ self._logger.info("Machine startup complete - ready for operations")
149
+
150
+ def shutdown(self):
151
+ """
152
+ Gracefully shut down the machine by disconnecting all controllers.
153
+
154
+ This method ensures all connections are properly closed and resources are released.
155
+ """
156
+ self._logger.info("Shutting down machine and disconnecting all controllers")
132
157
  self.qubot.disconnect()
133
158
  self.pipette.disconnect()
134
159
  self.camera.disconnect()
135
- self._logger.info("All controllers disconnected successfully")
160
+ self._logger.info("Machine shutdown complete")
136
161
 
137
162
  def load_labware(self, slot: str, labware_name: str):
138
163
  """Load a labware object into a slot."""
@@ -350,3 +375,86 @@ class First:
350
375
  else:
351
376
  self._logger.debug("Absolute A position for slot '%s': %s", slot, pos)
352
377
  return pos
378
+
379
+ def start_video_recording(
380
+ self,
381
+ filename: Optional[Union[str, Path]] = None,
382
+ fps: Optional[float] = None
383
+ ) -> Path:
384
+ """
385
+ Start recording a video.
386
+
387
+ Args:
388
+ filename: Optional filename for the video. If not provided, a timestamped
389
+ filename will be generated. If provided without extension, .mp4 will be added.
390
+ fps: Optional frames per second for the video. Defaults to 30.0 if not specified.
391
+
392
+ Returns:
393
+ Path to the video file where recording is being saved
394
+
395
+ Raises:
396
+ IOError: If camera is not connected or recording fails to start
397
+ ValueError: If already recording
398
+ """
399
+ return self.camera.start_video_recording(filename=filename, fps=fps)
400
+
401
+ def stop_video_recording(self) -> Optional[Path]:
402
+ """
403
+ Stop recording a video.
404
+
405
+ Returns:
406
+ Path to the saved video file, or None if no recording was in progress
407
+
408
+ Raises:
409
+ IOError: If video writer fails to release
410
+ """
411
+ return self.camera.stop_video_recording()
412
+
413
+ def record_video(
414
+ self,
415
+ duration_seconds: float,
416
+ filename: Optional[Union[str, Path]] = None,
417
+ fps: Optional[float] = None
418
+ ) -> Path:
419
+ """
420
+ Record a video for a specified duration.
421
+
422
+ Args:
423
+ duration_seconds: Duration of the video in seconds
424
+ filename: Optional filename for the video. If not provided, a timestamped
425
+ filename will be generated. If provided without extension, .mp4 will be added.
426
+ fps: Optional frames per second for the video. Defaults to 30.0 if not specified.
427
+
428
+ Returns:
429
+ Path to the saved video file
430
+
431
+ Raises:
432
+ IOError: If camera is not connected or recording fails
433
+ """
434
+ return self.camera.record_video(
435
+ duration_seconds=duration_seconds,
436
+ filename=filename,
437
+ fps=fps
438
+ )
439
+
440
+ def capture_image(
441
+ self,
442
+ save: bool = False,
443
+ filename: Optional[Union[str, Path]] = None
444
+ ) -> np.ndarray:
445
+ """
446
+ Capture a single image from the camera.
447
+
448
+ Args:
449
+ save: If True, save the image to the captures folder
450
+ filename: Optional filename for the saved image. If not provided and save=True,
451
+ a timestamped filename will be generated. If provided without extension,
452
+ .jpg will be added.
453
+
454
+ Returns:
455
+ Captured image as a numpy array (BGR format)
456
+
457
+ Raises:
458
+ IOError: If camera is not connected or capture fails
459
+ """
460
+ return self.camera.capture_image(save=save, filename=filename)
@@ -1,6 +1,7 @@
1
1
  """Test script for the First machine driver."""
2
2
 
3
3
  import random
4
+ import time
4
5
  import logging
5
6
  from puda_drivers.machines import First
6
7
  from puda_drivers.labware import get_available_labware
@@ -8,7 +9,7 @@ from puda_drivers.core import setup_logging
8
9
 
9
10
  setup_logging(
10
11
  enable_file_logging=False,
11
- log_level=logging.DEBUG, # Use logging.DEBUG to see all (DEBUG, INFO, WARNING, ERROR, CRITICAL) logs
12
+ log_level=logging.INFO, # Use logging.DEBUG to see all (DEBUG, INFO, WARNING, ERROR, CRITICAL) logs
12
13
  )
13
14
 
14
15
  if __name__ == "__main__":
@@ -34,15 +35,15 @@ if __name__ == "__main__":
34
35
  print(machine.deck)
35
36
  print(machine.deck["C2"])
36
37
 
37
- machine.connect()
38
- machine.qubot.home()
39
- machine.pipette.initialize()
38
+ machine.startup() # Connects all controllers, homes gantry, and initializes pipette
40
39
 
41
- machine.camera.start_video_recording()
40
+ # machine.record_video(duration_seconds=10, filename="test.mp4")
41
+ machine.start_video_recording()
42
42
  machine.attach_tip(slot="A3", well="G8")
43
43
  machine.aspirate_from(slot="C2", well="A1", amount=100, height_from_bottom=10)
44
+ # machine.capture_image()
44
45
  machine.dispense_to(slot="C2", well="B4", amount=100, height_from_bottom=50)
45
- machine.drop_tip(slot="C1", well="A1")
46
+ machine.drop_tip(slot="C1", well="A1", height_from_bottom=10)
46
47
 
47
48
  # tiprack_wells = machine.deck["A3"].wells
48
49
  # # get pick up pipette one by one and drop it in the trash bin
@@ -58,7 +59,7 @@ if __name__ == "__main__":
58
59
  # machine.drop_tip("C1", "A1")
59
60
 
60
61
 
61
- machine.camera.stop_video_recording()
62
+ machine.stop_video_recording()
62
63
 
63
- # Disconnect machine
64
- machine.disconnect()
64
+ # Shutdown machine
65
+ machine.shutdown()
@@ -186,7 +186,7 @@ wheels = [
186
186
 
187
187
  [[package]]
188
188
  name = "puda-drivers"
189
- version = "0.0.11"
189
+ version = "0.0.12"
190
190
  source = { editable = "." }
191
191
  dependencies = [
192
192
  { name = "opencv-python" },
File without changes
File without changes