puda-drivers 0.0.16__tar.gz → 0.0.17__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.16 → puda_drivers-0.0.17}/PKG-INFO +60 -19
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/README.md +56 -16
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/pyproject.toml +4 -3
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/core/serialcontroller.py +21 -30
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/labware/trash_bin.json +2 -1
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/machines/first.py +30 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/move/deck.py +35 -1
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/move/gcode.py +2 -2
- puda_drivers-0.0.17/tests/poll_position.py +126 -0
- puda_drivers-0.0.17/tests/test_deck.py +172 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/tests/test_position_polling.py +3 -3
- puda_drivers-0.0.17/tests/webcam.py +59 -0
- puda_drivers-0.0.16/tests/webcam.py +0 -28
- puda_drivers-0.0.16/uv.lock +0 -114
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/.gitignore +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/LICENSE +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/__init__.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/core/__init__.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/core/logging.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/core/position.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/cv/__init__.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/cv/camera.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/labware/__init__.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/labware/labware.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/labware/opentrons_96_tiprack_300ul.json +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/labware/polyelectric_8_wellplate_30000ul.json +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/machines/__init__.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/move/__init__.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/move/grbl/__init__.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/move/grbl/api.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/move/grbl/constants.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/py.typed +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/transfer/liquid/sartorius/__init__.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/transfer/liquid/sartorius/api.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/transfer/liquid/sartorius/constants.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/transfer/liquid/sartorius/rLine.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/tests/example.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/tests/first.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/tests/pipette.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.17}/tests/qubot.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: puda-drivers
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.17
|
|
4
4
|
Summary: Hardware drivers for the PUDA platform.
|
|
5
|
-
Project-URL: Homepage, https://github.com/
|
|
6
|
-
Project-URL: Issues, https://github.com/
|
|
5
|
+
Project-URL: Homepage, https://github.com/PUDAP/puda-drivers
|
|
6
|
+
Project-URL: Issues, https://github.com/PUDAP/puda-drivers/issues
|
|
7
7
|
Author-email: zhao <20024592+agentzhao@users.noreply.github.com>
|
|
8
8
|
License-Expression: MIT
|
|
9
9
|
License-File: LICENSE
|
|
@@ -18,6 +18,7 @@ Requires-Python: >=3.10
|
|
|
18
18
|
Requires-Dist: nats-py>=2.12.0
|
|
19
19
|
Requires-Dist: opencv-python>=4.12.0.88
|
|
20
20
|
Requires-Dist: pyserial~=3.5
|
|
21
|
+
Requires-Dist: pytest>=9.0.2
|
|
21
22
|
Description-Content-Type: text/markdown
|
|
22
23
|
|
|
23
24
|
# puda-drivers
|
|
@@ -40,14 +41,6 @@ Hardware drivers for the PUDA (Physical Unified Device Architecture) platform. T
|
|
|
40
41
|
pip install puda-drivers
|
|
41
42
|
```
|
|
42
43
|
|
|
43
|
-
### From Source
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
git clone https://github.com/zhao-bears/puda-drivers.git
|
|
47
|
-
cd puda-drivers
|
|
48
|
-
pip install -e .
|
|
49
|
-
```
|
|
50
|
-
|
|
51
44
|
## Quick Start
|
|
52
45
|
|
|
53
46
|
### Logging Configuration
|
|
@@ -197,22 +190,70 @@ sartorius_ports = list_serial_ports(filter_desc="Sartorius")
|
|
|
197
190
|
|
|
198
191
|
### Setup Development Environment
|
|
199
192
|
|
|
200
|
-
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.
|
|
193
|
+
This package is part of a UV workspace monorepo. 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.
|
|
194
|
+
|
|
195
|
+
**From the repository root:**
|
|
201
196
|
|
|
202
197
|
```bash
|
|
203
|
-
#
|
|
204
|
-
uv
|
|
198
|
+
# Or install dependencies for all workspace packages
|
|
199
|
+
uv sync --all-packages
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
This will:
|
|
203
|
+
- Create a virtual environment at the repository root (`.venv/`)
|
|
204
|
+
- Install all dependencies for all workspace packages
|
|
205
|
+
- Install `puda-drivers` and other workspace packages in editable mode automatically
|
|
205
206
|
|
|
206
|
-
|
|
207
|
+
**Using the package:**
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# Run Python scripts with workspace context (recommended, works from anywhere in the workspace)
|
|
211
|
+
uv run python your_script.py
|
|
212
|
+
|
|
213
|
+
# Or activate the virtual environment (from repository root where .venv is located)
|
|
207
214
|
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
215
|
+
python your_script.py
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Adding dependencies:**
|
|
208
219
|
|
|
209
|
-
|
|
210
|
-
|
|
220
|
+
```bash
|
|
221
|
+
# From the package directory
|
|
222
|
+
cd libs/drivers
|
|
223
|
+
uv add some-package
|
|
211
224
|
|
|
212
|
-
#
|
|
213
|
-
|
|
225
|
+
# Or from repository root
|
|
226
|
+
uv add --package puda-drivers some-package
|
|
214
227
|
```
|
|
215
228
|
|
|
229
|
+
**Note:** Workspace packages are automatically installed in editable mode, so code changes are immediately available without reinstalling.
|
|
230
|
+
|
|
231
|
+
### Testing
|
|
232
|
+
|
|
233
|
+
Run tests using pytest with `uv run`:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
# Run all tests
|
|
237
|
+
uv run pytest tests/
|
|
238
|
+
|
|
239
|
+
# Run a specific test file
|
|
240
|
+
uv run pytest tests/test_deck.py
|
|
241
|
+
|
|
242
|
+
# Run a specific test class
|
|
243
|
+
uv run pytest tests/test_deck.py::TestDeckToDict
|
|
244
|
+
|
|
245
|
+
# Run a specific test function
|
|
246
|
+
uv run pytest tests/test_deck.py::TestDeckToDict::test_to_dict_empty_deck
|
|
247
|
+
|
|
248
|
+
# Run with verbose output
|
|
249
|
+
uv run pytest tests/ -v
|
|
250
|
+
|
|
251
|
+
# Run with coverage report
|
|
252
|
+
uv run pytest tests/ --cov=puda_drivers --cov-report=html
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Note:** Make sure you're in the `libs/drivers` directory or use the full path to the tests directory when running pytest commands.
|
|
256
|
+
|
|
216
257
|
### Building and Publishing
|
|
217
258
|
|
|
218
259
|
```bash
|
|
@@ -18,14 +18,6 @@ Hardware drivers for the PUDA (Physical Unified Device Architecture) platform. T
|
|
|
18
18
|
pip install puda-drivers
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
### From Source
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
git clone https://github.com/zhao-bears/puda-drivers.git
|
|
25
|
-
cd puda-drivers
|
|
26
|
-
pip install -e .
|
|
27
|
-
```
|
|
28
|
-
|
|
29
21
|
## Quick Start
|
|
30
22
|
|
|
31
23
|
### Logging Configuration
|
|
@@ -175,22 +167,70 @@ sartorius_ports = list_serial_ports(filter_desc="Sartorius")
|
|
|
175
167
|
|
|
176
168
|
### Setup Development Environment
|
|
177
169
|
|
|
178
|
-
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.
|
|
170
|
+
This package is part of a UV workspace monorepo. 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.
|
|
171
|
+
|
|
172
|
+
**From the repository root:**
|
|
179
173
|
|
|
180
174
|
```bash
|
|
181
|
-
#
|
|
182
|
-
uv
|
|
175
|
+
# Or install dependencies for all workspace packages
|
|
176
|
+
uv sync --all-packages
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
This will:
|
|
180
|
+
- Create a virtual environment at the repository root (`.venv/`)
|
|
181
|
+
- Install all dependencies for all workspace packages
|
|
182
|
+
- Install `puda-drivers` and other workspace packages in editable mode automatically
|
|
183
183
|
|
|
184
|
-
|
|
184
|
+
**Using the package:**
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
# Run Python scripts with workspace context (recommended, works from anywhere in the workspace)
|
|
188
|
+
uv run python your_script.py
|
|
189
|
+
|
|
190
|
+
# Or activate the virtual environment (from repository root where .venv is located)
|
|
185
191
|
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
192
|
+
python your_script.py
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Adding dependencies:**
|
|
186
196
|
|
|
187
|
-
|
|
188
|
-
|
|
197
|
+
```bash
|
|
198
|
+
# From the package directory
|
|
199
|
+
cd libs/drivers
|
|
200
|
+
uv add some-package
|
|
189
201
|
|
|
190
|
-
#
|
|
191
|
-
|
|
202
|
+
# Or from repository root
|
|
203
|
+
uv add --package puda-drivers some-package
|
|
192
204
|
```
|
|
193
205
|
|
|
206
|
+
**Note:** Workspace packages are automatically installed in editable mode, so code changes are immediately available without reinstalling.
|
|
207
|
+
|
|
208
|
+
### Testing
|
|
209
|
+
|
|
210
|
+
Run tests using pytest with `uv run`:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# Run all tests
|
|
214
|
+
uv run pytest tests/
|
|
215
|
+
|
|
216
|
+
# Run a specific test file
|
|
217
|
+
uv run pytest tests/test_deck.py
|
|
218
|
+
|
|
219
|
+
# Run a specific test class
|
|
220
|
+
uv run pytest tests/test_deck.py::TestDeckToDict
|
|
221
|
+
|
|
222
|
+
# Run a specific test function
|
|
223
|
+
uv run pytest tests/test_deck.py::TestDeckToDict::test_to_dict_empty_deck
|
|
224
|
+
|
|
225
|
+
# Run with verbose output
|
|
226
|
+
uv run pytest tests/ -v
|
|
227
|
+
|
|
228
|
+
# Run with coverage report
|
|
229
|
+
uv run pytest tests/ --cov=puda_drivers --cov-report=html
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**Note:** Make sure you're in the `libs/drivers` directory or use the full path to the tests directory when running pytest commands.
|
|
233
|
+
|
|
194
234
|
### Building and Publishing
|
|
195
235
|
|
|
196
236
|
```bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "puda-drivers"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.17"
|
|
4
4
|
description = "Hardware drivers for the PUDA platform."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -22,6 +22,7 @@ dependencies = [
|
|
|
22
22
|
"nats-py>=2.12.0",
|
|
23
23
|
"opencv-python>=4.12.0.88",
|
|
24
24
|
"pyserial~=3.5",
|
|
25
|
+
"pytest>=9.0.2",
|
|
25
26
|
]
|
|
26
27
|
|
|
27
28
|
[build-system]
|
|
@@ -29,5 +30,5 @@ requires = ["hatchling >= 1.26"]
|
|
|
29
30
|
build-backend = "hatchling.build"
|
|
30
31
|
|
|
31
32
|
[project.urls]
|
|
32
|
-
Homepage = "https://github.com/
|
|
33
|
-
Issues = "https://github.com/
|
|
33
|
+
Homepage = "https://github.com/PUDAP/puda-drivers"
|
|
34
|
+
Issues = "https://github.com/PUDAP/puda-drivers/issues"
|
|
@@ -48,7 +48,7 @@ class SerialController(ABC):
|
|
|
48
48
|
self._serial = None
|
|
49
49
|
self.port_name = port_name
|
|
50
50
|
self.baudrate = baudrate
|
|
51
|
-
self.
|
|
51
|
+
self._timeout = timeout
|
|
52
52
|
self._logger = logger
|
|
53
53
|
|
|
54
54
|
# lock to prevent concurrent access to the serial port
|
|
@@ -75,7 +75,7 @@ class SerialController(ABC):
|
|
|
75
75
|
self._serial = serial.Serial(
|
|
76
76
|
port=self.port_name,
|
|
77
77
|
baudrate=self.baudrate,
|
|
78
|
-
timeout=self.
|
|
78
|
+
timeout=self._timeout,
|
|
79
79
|
)
|
|
80
80
|
self._serial.flush()
|
|
81
81
|
self._logger.info("Successfully connected to %s.", self.port_name)
|
|
@@ -107,19 +107,19 @@ class SerialController(ABC):
|
|
|
107
107
|
|
|
108
108
|
def _send_command(self, command: str) -> None:
|
|
109
109
|
"""
|
|
110
|
-
Sends a
|
|
110
|
+
Sends a command to the device.
|
|
111
111
|
Note: This method should be called while holding self._lock to ensure
|
|
112
112
|
atomic command/response pairing.
|
|
113
113
|
"""
|
|
114
|
+
self._logger.info("-> Sending: %r", command)
|
|
115
|
+
|
|
114
116
|
if not self.is_connected or not self._serial:
|
|
115
117
|
self._logger.error(
|
|
116
118
|
"Attempt to send command '%s' failed: Device not connected.",
|
|
117
119
|
command,
|
|
118
120
|
)
|
|
119
121
|
# Retain raising an error for being disconnected, as that's a connection state issue
|
|
120
|
-
raise serial.SerialException("Device
|
|
121
|
-
|
|
122
|
-
self._logger.info("-> Sending: %r", command)
|
|
122
|
+
raise serial.SerialException("Device disconnected. Call connect() first.")
|
|
123
123
|
|
|
124
124
|
try:
|
|
125
125
|
self._serial.reset_input_buffer() # clear input buffer
|
|
@@ -140,7 +140,7 @@ class SerialController(ABC):
|
|
|
140
140
|
)
|
|
141
141
|
return None
|
|
142
142
|
|
|
143
|
-
def _read_response(self) -> str:
|
|
143
|
+
def _read_response(self, timeout: int = None) -> str:
|
|
144
144
|
"""
|
|
145
145
|
Generic, blocking read that respects timeout and returns
|
|
146
146
|
all data that arrived within the timeout period.
|
|
@@ -148,10 +148,13 @@ class SerialController(ABC):
|
|
|
148
148
|
if not self.is_connected or not self._serial:
|
|
149
149
|
raise serial.SerialException("Device not connected.")
|
|
150
150
|
|
|
151
|
+
if timeout is None:
|
|
152
|
+
timeout = self._timeout
|
|
153
|
+
|
|
151
154
|
start_time = time.time()
|
|
152
155
|
response = b""
|
|
153
156
|
|
|
154
|
-
while time.time() - start_time <
|
|
157
|
+
while time.time() - start_time < timeout:
|
|
155
158
|
if self._serial.in_waiting > 0:
|
|
156
159
|
# Read all available bytes
|
|
157
160
|
response += self._serial.read(self._serial.in_waiting)
|
|
@@ -168,14 +171,12 @@ class SerialController(ABC):
|
|
|
168
171
|
|
|
169
172
|
# Timeout reached - check what we got
|
|
170
173
|
if not response:
|
|
171
|
-
self._logger.error("No response within %s seconds.",
|
|
174
|
+
self._logger.error("No response within %s seconds.", timeout)
|
|
172
175
|
raise serial.SerialTimeoutException(
|
|
173
|
-
f"No response received within {
|
|
176
|
+
f"No response received within {timeout} seconds."
|
|
174
177
|
)
|
|
175
178
|
|
|
176
|
-
# Decode once and check the decoded string
|
|
177
179
|
decoded_response = response.decode("utf-8", errors="ignore").strip()
|
|
178
|
-
|
|
179
180
|
if "ok" in decoded_response.lower():
|
|
180
181
|
self._logger.debug("<- Received response: %r", decoded_response)
|
|
181
182
|
elif "err" in decoded_response.lower():
|
|
@@ -193,10 +194,12 @@ class SerialController(ABC):
|
|
|
193
194
|
def _build_command(self, command: str, value: Optional[str] = None) -> str:
|
|
194
195
|
"""
|
|
195
196
|
Build a command string according to the device protocol.
|
|
197
|
+
|
|
198
|
+
There might be special starting and ending characters for devices
|
|
196
199
|
"""
|
|
197
|
-
|
|
200
|
+
raise NotImplementedError
|
|
198
201
|
|
|
199
|
-
def execute(self, command: str, value: Optional[str] = None) -> str:
|
|
202
|
+
def execute(self, command: str, value: Optional[str] = None, timeout: int = None) -> str:
|
|
200
203
|
"""
|
|
201
204
|
Send a command and read the response atomically.
|
|
202
205
|
|
|
@@ -218,22 +221,10 @@ class SerialController(ABC):
|
|
|
218
221
|
serial.SerialException: If device is not connected or communication fails
|
|
219
222
|
serial.SerialTimeoutException: If no response is received within timeout
|
|
220
223
|
"""
|
|
224
|
+
if timeout is None:
|
|
225
|
+
timeout = self._timeout
|
|
221
226
|
# Hold the lock for the entire send+read operation to ensure atomicity
|
|
222
227
|
# This prevents concurrent commands from mixing up responses
|
|
223
228
|
with self._lock:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if "G28" in command.upper():
|
|
227
|
-
self.timeout = original_timeout + 60
|
|
228
|
-
# Also update the serial connection's timeout if connected
|
|
229
|
-
if self.is_connected and self._serial:
|
|
230
|
-
self._serial.timeout = self.timeout
|
|
231
|
-
|
|
232
|
-
try:
|
|
233
|
-
self._send_command(self._build_command(command, value))
|
|
234
|
-
return self._read_response()
|
|
235
|
-
finally:
|
|
236
|
-
# Restore original timeout
|
|
237
|
-
self.timeout = original_timeout
|
|
238
|
-
if self.is_connected and self._serial:
|
|
239
|
-
self._serial.timeout = original_timeout
|
|
229
|
+
self._send_command(self._build_command(command, value))
|
|
230
|
+
return self._read_response(timeout=timeout)
|
|
@@ -176,6 +176,8 @@ class First:
|
|
|
176
176
|
"pipette": sartorius_position,
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
### Labware management ###
|
|
180
|
+
|
|
179
181
|
def load_labware(self, slot: str, labware_name: str):
|
|
180
182
|
"""
|
|
181
183
|
Load a labware object into a slot.
|
|
@@ -190,7 +192,33 @@ class First:
|
|
|
190
192
|
self._logger.info("Loading labware '%s' into slot '%s'", labware_name, slot)
|
|
191
193
|
self.deck.load_labware(slot=slot, labware_name=labware_name)
|
|
192
194
|
self._logger.debug("Labware '%s' loaded into slot '%s'", labware_name, slot)
|
|
195
|
+
|
|
196
|
+
def remove_labware(self, slot: str):
|
|
197
|
+
"""
|
|
198
|
+
Remove labware from a slot.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
slot: Slot name (e.g., 'A1', 'B2')
|
|
202
|
+
|
|
203
|
+
Raises:
|
|
204
|
+
KeyError: If slot is not found in deck
|
|
205
|
+
"""
|
|
206
|
+
self.deck.empty_slot(slot=slot)
|
|
207
|
+
self._logger.debug("Slot '%s' emptied", slot)
|
|
193
208
|
|
|
209
|
+
def get_deck(self):
|
|
210
|
+
"""
|
|
211
|
+
Get the current deck layout.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Dictionary mapping slot names (e.g., "A1") to labware classes.
|
|
215
|
+
|
|
216
|
+
Raises:
|
|
217
|
+
None
|
|
218
|
+
"""
|
|
219
|
+
return self.deck.to_dict()
|
|
220
|
+
|
|
221
|
+
|
|
194
222
|
def load_deck(self, deck_layout: Dict[str, Type[StandardLabware]]):
|
|
195
223
|
"""
|
|
196
224
|
Load multiple labware into the deck at once.
|
|
@@ -211,6 +239,8 @@ class First:
|
|
|
211
239
|
self.load_labware(slot=slot, labware_name=labware_name)
|
|
212
240
|
self._logger.info("Deck layout loaded successfully")
|
|
213
241
|
|
|
242
|
+
### Liquid handling ###
|
|
243
|
+
|
|
214
244
|
def attach_tip(self, slot: str, well: Optional[str] = None):
|
|
215
245
|
"""
|
|
216
246
|
Attach a tip from a slot.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# src/puda_drivers/move/deck.py
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from puda_drivers.labware import StandardLabware
|
|
4
5
|
|
|
5
6
|
|
|
@@ -29,6 +30,20 @@ class Deck:
|
|
|
29
30
|
if slot.upper() not in self.slots:
|
|
30
31
|
raise KeyError(f"Slot {slot} not found in deck")
|
|
31
32
|
self.slots[slot.upper()] = StandardLabware(labware_name=labware_name)
|
|
33
|
+
|
|
34
|
+
def empty_slot(self, slot: str):
|
|
35
|
+
"""
|
|
36
|
+
Empty a slot (remove labware from it).
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
slot: Slot name (e.g., 'A1', 'B2')
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
KeyError: If slot is not found in deck
|
|
43
|
+
"""
|
|
44
|
+
if slot.upper() not in self.slots:
|
|
45
|
+
raise KeyError(f"Slot {slot} not found in deck")
|
|
46
|
+
self.slots[slot.upper()] = None
|
|
32
47
|
|
|
33
48
|
def __str__(self):
|
|
34
49
|
"""
|
|
@@ -44,4 +59,23 @@ class Deck:
|
|
|
44
59
|
|
|
45
60
|
def __getitem__(self, key):
|
|
46
61
|
"""Allows syntax for: my_deck['B4']"""
|
|
47
|
-
return self.slots[key.upper()]
|
|
62
|
+
return self.slots[key.upper()]
|
|
63
|
+
|
|
64
|
+
def to_dict(self) -> dict:
|
|
65
|
+
"""
|
|
66
|
+
Return the deck layout as a dictionary.
|
|
67
|
+
"""
|
|
68
|
+
deck_data = {}
|
|
69
|
+
for slot, labware in self.slots.items():
|
|
70
|
+
if labware is None:
|
|
71
|
+
deck_data[slot] = None
|
|
72
|
+
else:
|
|
73
|
+
deck_data[slot] = labware.name
|
|
74
|
+
return deck_data
|
|
75
|
+
|
|
76
|
+
def to_json(self) -> str:
|
|
77
|
+
"""
|
|
78
|
+
Return the deck layout as a JSON string.
|
|
79
|
+
"""
|
|
80
|
+
# Re-use the logic from to_dict() so you don't have to update it in two places
|
|
81
|
+
return json.dumps(self.to_dict(), indent=2)
|
|
@@ -296,7 +296,7 @@ class GCodeController(SerialController):
|
|
|
296
296
|
home_target = "All"
|
|
297
297
|
|
|
298
298
|
self._logger.info("[%s] homing axis/axes: %s **", cmd, home_target)
|
|
299
|
-
self.execute(cmd)
|
|
299
|
+
self.execute(command=cmd, timeout=90) # 90 seconds timeout for homing
|
|
300
300
|
self._logger.info("Homing of %s completed.\n", home_target)
|
|
301
301
|
|
|
302
302
|
# Update internal position (optimistic zeroing)
|
|
@@ -554,7 +554,7 @@ class GCodeController(SerialController):
|
|
|
554
554
|
self._logger.info("Query position complete. Retrieved positions: %s", position)
|
|
555
555
|
return position
|
|
556
556
|
except asyncio.TimeoutError:
|
|
557
|
-
self._logger.
|
|
557
|
+
self._logger.info(
|
|
558
558
|
"M114 command timed out after 1 second. Falling back to internal position."
|
|
559
559
|
)
|
|
560
560
|
return self.get_internal_position()
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Test script to poll get_position every second while movement commands are running."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
import time
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from puda_drivers.machines import First
|
|
8
|
+
from puda_drivers.core import Position, setup_logging
|
|
9
|
+
|
|
10
|
+
setup_logging(
|
|
11
|
+
enable_file_logging=False,
|
|
12
|
+
log_level=logging.INFO,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def poll_position(machine: First, stop_event: asyncio.Event):
|
|
17
|
+
"""
|
|
18
|
+
Poll get_position every second and print the results.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
machine: First machine instance
|
|
22
|
+
stop_event: Event to signal when to stop polling
|
|
23
|
+
"""
|
|
24
|
+
poll_count = 0
|
|
25
|
+
while not stop_event.is_set():
|
|
26
|
+
try:
|
|
27
|
+
position = await machine.get_position()
|
|
28
|
+
poll_count += 1
|
|
29
|
+
timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]
|
|
30
|
+
|
|
31
|
+
qubot_pos = position["qubot"]
|
|
32
|
+
pipette_pos = position["pipette"]
|
|
33
|
+
|
|
34
|
+
print(f"[{timestamp}] Poll #{poll_count}")
|
|
35
|
+
print(f" QuBot: X={qubot_pos.get('x', 0):.2f}, "
|
|
36
|
+
f"Y={qubot_pos.get('y', 0):.2f}, "
|
|
37
|
+
f"Z={qubot_pos.get('z', 0):.2f}, "
|
|
38
|
+
f"A={qubot_pos.get('a', 0):.2f}")
|
|
39
|
+
print(f" Pipette: {pipette_pos} steps")
|
|
40
|
+
print()
|
|
41
|
+
|
|
42
|
+
except Exception as e:
|
|
43
|
+
print(f"Error polling position: {e}")
|
|
44
|
+
|
|
45
|
+
# Wait 1 second before next poll
|
|
46
|
+
await asyncio.sleep(1.0)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def run_movements(machine: First):
|
|
50
|
+
"""
|
|
51
|
+
Run a sequence of movement commands.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
machine: First machine instance
|
|
55
|
+
"""
|
|
56
|
+
print("Starting movement sequence...")
|
|
57
|
+
|
|
58
|
+
# Example movement sequence
|
|
59
|
+
positions = [
|
|
60
|
+
Position(x=50, y=-100, z=-50),
|
|
61
|
+
Position(x=150, y=-200, z=-50),
|
|
62
|
+
Position(x=250, y=-300, z=-50),
|
|
63
|
+
Position(x=150, y=-200, z=-50),
|
|
64
|
+
Position(x=50, y=-100, z=-50),
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
for i, pos in enumerate(positions, 1):
|
|
68
|
+
print(f"Moving to position {i}/{len(positions)}: {pos}")
|
|
69
|
+
machine.qubot.move_absolute(position=pos)
|
|
70
|
+
time.sleep(0.5) # Small delay between movements
|
|
71
|
+
|
|
72
|
+
print("Movement sequence complete!")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def main():
|
|
76
|
+
"""Main function to run position polling and movements concurrently."""
|
|
77
|
+
# Initialize machine
|
|
78
|
+
machine = First(
|
|
79
|
+
qubot_port="/dev/ttyACM0",
|
|
80
|
+
sartorius_port="/dev/ttyUSB0",
|
|
81
|
+
camera_index=0,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
# Startup machine
|
|
86
|
+
print("Starting up machine...")
|
|
87
|
+
machine.startup()
|
|
88
|
+
print("Machine ready!\n")
|
|
89
|
+
|
|
90
|
+
# Create stop event for polling
|
|
91
|
+
stop_event = asyncio.Event()
|
|
92
|
+
|
|
93
|
+
# Start position polling task
|
|
94
|
+
polling_task = asyncio.create_task(poll_position(machine, stop_event))
|
|
95
|
+
|
|
96
|
+
# Run movements in a thread pool (since they're blocking)
|
|
97
|
+
print("Running movements in background...\n")
|
|
98
|
+
await asyncio.to_thread(run_movements, machine)
|
|
99
|
+
|
|
100
|
+
# Wait a bit more to see final position
|
|
101
|
+
await asyncio.sleep(2)
|
|
102
|
+
|
|
103
|
+
# Stop polling
|
|
104
|
+
stop_event.set()
|
|
105
|
+
await polling_task
|
|
106
|
+
|
|
107
|
+
print("\nTest complete!")
|
|
108
|
+
|
|
109
|
+
except KeyboardInterrupt:
|
|
110
|
+
print("\nInterrupted by user")
|
|
111
|
+
stop_event.set()
|
|
112
|
+
await polling_task
|
|
113
|
+
except Exception as e:
|
|
114
|
+
print(f"\nError: {e}")
|
|
115
|
+
stop_event.set()
|
|
116
|
+
if not polling_task.done():
|
|
117
|
+
await polling_task
|
|
118
|
+
finally:
|
|
119
|
+
# Shutdown machine
|
|
120
|
+
print("\nShutting down machine...")
|
|
121
|
+
machine.shutdown()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
if __name__ == "__main__":
|
|
125
|
+
asyncio.run(main())
|
|
126
|
+
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Tests for Deck class to_dict() and to_json() methods."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from puda_drivers.move.deck import Deck
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestDeckToDict:
|
|
8
|
+
"""Test cases for Deck.to_dict() method."""
|
|
9
|
+
|
|
10
|
+
def test_to_dict_empty_deck(self):
|
|
11
|
+
"""Test to_dict() on an empty deck (all slots are None)."""
|
|
12
|
+
deck = Deck(rows=2, cols=2)
|
|
13
|
+
result = deck.to_dict()
|
|
14
|
+
|
|
15
|
+
# Should have all slots
|
|
16
|
+
assert len(result) == 4
|
|
17
|
+
assert result["A1"] is None
|
|
18
|
+
assert result["A2"] is None
|
|
19
|
+
assert result["B1"] is None
|
|
20
|
+
assert result["B2"] is None
|
|
21
|
+
|
|
22
|
+
def test_to_dict_with_labware(self):
|
|
23
|
+
"""Test to_dict() on a deck with labware loaded."""
|
|
24
|
+
deck = Deck(rows=2, cols=2)
|
|
25
|
+
deck.load_labware("A1", "opentrons_96_tiprack_300ul")
|
|
26
|
+
deck.load_labware("B2", "trash_bin")
|
|
27
|
+
|
|
28
|
+
result = deck.to_dict()
|
|
29
|
+
|
|
30
|
+
# Check that loaded labware have their names
|
|
31
|
+
assert result["A1"] == "Opentrons OT-2 96 Tip Rack 300 µL"
|
|
32
|
+
assert result["B2"] == "Trash Bin"
|
|
33
|
+
|
|
34
|
+
# Check that unloaded slots are None
|
|
35
|
+
assert result["A2"] is None
|
|
36
|
+
assert result["B1"] is None
|
|
37
|
+
|
|
38
|
+
def test_to_dict_mixed_slots(self):
|
|
39
|
+
"""Test to_dict() with a mix of loaded and empty slots."""
|
|
40
|
+
deck = Deck(rows=3, cols=3)
|
|
41
|
+
deck.load_labware("A1", "opentrons_96_tiprack_300ul")
|
|
42
|
+
deck.load_labware("C3", "polyelectric_8_wellplate_30000ul")
|
|
43
|
+
|
|
44
|
+
result = deck.to_dict()
|
|
45
|
+
|
|
46
|
+
# Should have all 9 slots
|
|
47
|
+
assert len(result) == 9
|
|
48
|
+
|
|
49
|
+
# Check loaded slots
|
|
50
|
+
assert result["A1"] == "Opentrons OT-2 96 Tip Rack 300 µL"
|
|
51
|
+
assert result["C3"] == "Polyelectric 8 Well Plate 30000 µL"
|
|
52
|
+
|
|
53
|
+
# Check some empty slots
|
|
54
|
+
assert result["A2"] is None
|
|
55
|
+
assert result["B1"] is None
|
|
56
|
+
assert result["B2"] is None
|
|
57
|
+
assert result["C1"] is None
|
|
58
|
+
|
|
59
|
+
def test_to_dict_all_slots_filled(self):
|
|
60
|
+
"""Test to_dict() when all slots are filled."""
|
|
61
|
+
deck = Deck(rows=2, cols=2)
|
|
62
|
+
deck.load_labware("A1", "opentrons_96_tiprack_300ul")
|
|
63
|
+
deck.load_labware("A2", "trash_bin")
|
|
64
|
+
deck.load_labware("B1", "polyelectric_8_wellplate_30000ul")
|
|
65
|
+
deck.load_labware("B2", "opentrons_96_tiprack_300ul")
|
|
66
|
+
|
|
67
|
+
result = deck.to_dict()
|
|
68
|
+
|
|
69
|
+
# All slots should have labware names
|
|
70
|
+
assert result["A1"] == "Opentrons OT-2 96 Tip Rack 300 µL"
|
|
71
|
+
assert result["A2"] == "Trash Bin"
|
|
72
|
+
assert result["B1"] == "Polyelectric 8 Well Plate 30000 µL"
|
|
73
|
+
assert result["B2"] == "Opentrons OT-2 96 Tip Rack 300 µL"
|
|
74
|
+
|
|
75
|
+
# No None values
|
|
76
|
+
assert None not in result.values()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class TestDeckToJson:
|
|
80
|
+
"""Test cases for Deck.to_json() method."""
|
|
81
|
+
|
|
82
|
+
def test_to_json_empty_deck(self):
|
|
83
|
+
"""Test to_json() on an empty deck."""
|
|
84
|
+
deck = Deck(rows=2, cols=2)
|
|
85
|
+
json_str = deck.to_json()
|
|
86
|
+
|
|
87
|
+
# Should be valid JSON
|
|
88
|
+
parsed = json.loads(json_str)
|
|
89
|
+
assert isinstance(parsed, dict)
|
|
90
|
+
|
|
91
|
+
# Should match to_dict() output
|
|
92
|
+
assert parsed == deck.to_dict()
|
|
93
|
+
|
|
94
|
+
# Should have proper indentation (check for newlines)
|
|
95
|
+
assert "\n" in json_str
|
|
96
|
+
|
|
97
|
+
def test_to_json_with_labware(self):
|
|
98
|
+
"""Test to_json() on a deck with labware loaded."""
|
|
99
|
+
deck = Deck(rows=2, cols=2)
|
|
100
|
+
deck.load_labware("A1", "opentrons_96_tiprack_300ul")
|
|
101
|
+
deck.load_labware("B2", "trash_bin")
|
|
102
|
+
|
|
103
|
+
json_str = deck.to_json()
|
|
104
|
+
|
|
105
|
+
# Should be valid JSON
|
|
106
|
+
parsed = json.loads(json_str)
|
|
107
|
+
assert isinstance(parsed, dict)
|
|
108
|
+
|
|
109
|
+
# Should match to_dict() output exactly
|
|
110
|
+
assert parsed == deck.to_dict()
|
|
111
|
+
|
|
112
|
+
# Should contain labware names in the parsed JSON (checking parsed values
|
|
113
|
+
# instead of raw string since JSON serialization may escape Unicode characters)
|
|
114
|
+
assert parsed["A1"] == "Opentrons OT-2 96 Tip Rack 300 µL"
|
|
115
|
+
assert parsed["B2"] == "Trash Bin"
|
|
116
|
+
|
|
117
|
+
def test_to_json_matches_to_dict(self):
|
|
118
|
+
"""Test that to_json() when parsed matches to_dict() output."""
|
|
119
|
+
deck = Deck(rows=3, cols=3)
|
|
120
|
+
deck.load_labware("A1", "opentrons_96_tiprack_300ul")
|
|
121
|
+
deck.load_labware("B2", "trash_bin")
|
|
122
|
+
deck.load_labware("C3", "polyelectric_8_wellplate_30000ul")
|
|
123
|
+
|
|
124
|
+
dict_result = deck.to_dict()
|
|
125
|
+
json_str = deck.to_json()
|
|
126
|
+
json_result = json.loads(json_str)
|
|
127
|
+
|
|
128
|
+
# Should be identical
|
|
129
|
+
assert dict_result == json_result
|
|
130
|
+
|
|
131
|
+
# Verify structure
|
|
132
|
+
assert json_result["A1"] == "Opentrons OT-2 96 Tip Rack 300 µL"
|
|
133
|
+
assert json_result["B2"] == "Trash Bin"
|
|
134
|
+
assert json_result["C3"] == "Polyelectric 8 Well Plate 30000 µL"
|
|
135
|
+
assert json_result["A2"] is None
|
|
136
|
+
|
|
137
|
+
def test_to_json_indentation(self):
|
|
138
|
+
"""Test that to_json() has proper indentation (indent=2)."""
|
|
139
|
+
deck = Deck(rows=2, cols=2)
|
|
140
|
+
deck.load_labware("A1", "opentrons_96_tiprack_300ul")
|
|
141
|
+
|
|
142
|
+
json_str = deck.to_json()
|
|
143
|
+
|
|
144
|
+
# Check that it has indentation (2 spaces per level)
|
|
145
|
+
lines = json_str.split("\n")
|
|
146
|
+
# First line should be "{"
|
|
147
|
+
assert lines[0].strip() == "{"
|
|
148
|
+
# Second line should have 2 spaces of indentation
|
|
149
|
+
assert lines[1].startswith(" ")
|
|
150
|
+
|
|
151
|
+
# Verify it's still valid JSON
|
|
152
|
+
parsed = json.loads(json_str)
|
|
153
|
+
assert isinstance(parsed, dict)
|
|
154
|
+
|
|
155
|
+
def test_to_json_round_trip(self):
|
|
156
|
+
"""Test that to_json() output can be loaded and matches original."""
|
|
157
|
+
deck = Deck(rows=2, cols=2)
|
|
158
|
+
deck.load_labware("A1", "opentrons_96_tiprack_300ul")
|
|
159
|
+
deck.load_labware("B2", "trash_bin")
|
|
160
|
+
|
|
161
|
+
original_dict = deck.to_dict()
|
|
162
|
+
json_str = deck.to_json()
|
|
163
|
+
loaded_dict = json.loads(json_str)
|
|
164
|
+
|
|
165
|
+
# Round trip should preserve all data
|
|
166
|
+
assert loaded_dict == original_dict
|
|
167
|
+
|
|
168
|
+
# Verify all keys and values match
|
|
169
|
+
for key in original_dict:
|
|
170
|
+
assert key in loaded_dict
|
|
171
|
+
assert loaded_dict[key] == original_dict[key]
|
|
172
|
+
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import json
|
|
5
5
|
import logging
|
|
6
|
-
from datetime import datetime
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
7
|
from puda_drivers.machines import First
|
|
8
8
|
from puda_drivers.labware import get_available_labware
|
|
9
9
|
from puda_drivers.core import setup_logging
|
|
@@ -25,7 +25,7 @@ async def poll_position(machine: First, stop_event: asyncio.Event):
|
|
|
25
25
|
while not stop_event.is_set():
|
|
26
26
|
try:
|
|
27
27
|
position = await machine.get_position()
|
|
28
|
-
timestamp = datetime.
|
|
28
|
+
timestamp = datetime.now(timezone.utc).isoformat()
|
|
29
29
|
result = {
|
|
30
30
|
"timestamp": timestamp,
|
|
31
31
|
"qubot": position.get("qubot", {}),
|
|
@@ -33,7 +33,7 @@ async def poll_position(machine: First, stop_event: asyncio.Event):
|
|
|
33
33
|
}
|
|
34
34
|
print(json.dumps(result))
|
|
35
35
|
except (ValueError, IOError, RuntimeError) as e:
|
|
36
|
-
timestamp = datetime.
|
|
36
|
+
timestamp = datetime.now(timezone.utc).isoformat()
|
|
37
37
|
result = {
|
|
38
38
|
"timestamp": timestamp,
|
|
39
39
|
"qubot": {},
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Tests for camera controller functionality."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import pytest
|
|
5
|
+
from puda_drivers.cv import list_cameras, CameraController
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.fixture
|
|
9
|
+
def camera_controller():
|
|
10
|
+
"""Fixture to create and connect a camera controller."""
|
|
11
|
+
cam = CameraController(camera_index=4)
|
|
12
|
+
cam.connect()
|
|
13
|
+
yield cam
|
|
14
|
+
if cam.is_connected:
|
|
15
|
+
cam.disconnect()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_list_cameras():
|
|
19
|
+
"""Test listing available cameras."""
|
|
20
|
+
cameras = list_cameras()
|
|
21
|
+
assert isinstance(cameras, list)
|
|
22
|
+
print(f"Found {len(cameras)} cameras")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# pylint: disable=redefined-outer-name
|
|
26
|
+
def test_camera_connection(camera_controller):
|
|
27
|
+
"""Test camera connection."""
|
|
28
|
+
assert camera_controller.is_connected, "Camera should be connected"
|
|
29
|
+
print("Camera is connected")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# pylint: disable=redefined-outer-name
|
|
33
|
+
def test_capture_image(camera_controller):
|
|
34
|
+
"""Test image capture functionality."""
|
|
35
|
+
if camera_controller.is_connected:
|
|
36
|
+
image = camera_controller.capture_image()
|
|
37
|
+
assert image is not None
|
|
38
|
+
print("Image captured successfully")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# pylint: disable=redefined-outer-name
|
|
42
|
+
def test_record_video_duration(camera_controller):
|
|
43
|
+
"""Test recording video for a specific duration."""
|
|
44
|
+
if camera_controller.is_connected:
|
|
45
|
+
video_path = camera_controller.record_video(duration_seconds=10)
|
|
46
|
+
assert video_path is not None
|
|
47
|
+
print(f"Video recorded: {video_path}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# pylint: disable=redefined-outer-name
|
|
51
|
+
def test_start_stop_video_recording(camera_controller):
|
|
52
|
+
"""Test starting and stopping video recording manually."""
|
|
53
|
+
if camera_controller.is_connected:
|
|
54
|
+
print("Camera is connected")
|
|
55
|
+
video_path = camera_controller.start_video_recording()
|
|
56
|
+
time.sleep(3)
|
|
57
|
+
stop_path = camera_controller.stop_video_recording()
|
|
58
|
+
assert stop_path is not None or video_path is not None
|
|
59
|
+
print("Video recording started and stopped successfully")
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import time
|
|
2
|
-
from puda_drivers.cv import list_cameras, CameraController
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
# print(list_cameras())
|
|
6
|
-
|
|
7
|
-
camera = CameraController(camera_index=4)
|
|
8
|
-
camera.connect()
|
|
9
|
-
|
|
10
|
-
# Image capture
|
|
11
|
-
# if camera.is_connected:
|
|
12
|
-
# print("Camera is connected")
|
|
13
|
-
# camera.capture_image()
|
|
14
|
-
# camera.disconnect()
|
|
15
|
-
|
|
16
|
-
# Video recording
|
|
17
|
-
# if camera.is_connected:
|
|
18
|
-
# print("Camera is connected")
|
|
19
|
-
# camera.record_video(duration_seconds=10)
|
|
20
|
-
# camera.disconnect()
|
|
21
|
-
|
|
22
|
-
# video recording
|
|
23
|
-
if camera.is_connected:
|
|
24
|
-
print("Camera is connected")
|
|
25
|
-
camera.start_video_recording()
|
|
26
|
-
time.sleep(3)
|
|
27
|
-
camera.stop_video_recording()
|
|
28
|
-
camera.disconnect()
|
puda_drivers-0.0.16/uv.lock
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
version = 1
|
|
2
|
-
revision = 3
|
|
3
|
-
requires-python = ">=3.10"
|
|
4
|
-
|
|
5
|
-
[[package]]
|
|
6
|
-
name = "nats-py"
|
|
7
|
-
version = "2.12.0"
|
|
8
|
-
source = { registry = "https://pypi.org/simple" }
|
|
9
|
-
sdist = { url = "https://files.pythonhosted.org/packages/71/c5/2564d917503fe8d68fe630c74bf6b678fbc15c01b58f2565894761010f57/nats_py-2.12.0.tar.gz", hash = "sha256:2981ca4b63b8266c855573fa7871b1be741f1889fd429ee657e5ffc0971a38a1", size = 119821, upload-time = "2025-10-31T05:27:31.247Z" }
|
|
10
|
-
|
|
11
|
-
[[package]]
|
|
12
|
-
name = "numpy"
|
|
13
|
-
version = "2.2.6"
|
|
14
|
-
source = { registry = "https://pypi.org/simple" }
|
|
15
|
-
sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" }
|
|
16
|
-
wheels = [
|
|
17
|
-
{ url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" },
|
|
18
|
-
{ url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" },
|
|
19
|
-
{ url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" },
|
|
20
|
-
{ url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" },
|
|
21
|
-
{ url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" },
|
|
22
|
-
{ url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" },
|
|
23
|
-
{ url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" },
|
|
24
|
-
{ url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" },
|
|
25
|
-
{ url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" },
|
|
26
|
-
{ url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" },
|
|
27
|
-
{ url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" },
|
|
28
|
-
{ url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" },
|
|
29
|
-
{ url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" },
|
|
30
|
-
{ url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" },
|
|
31
|
-
{ url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" },
|
|
32
|
-
{ url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" },
|
|
33
|
-
{ url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" },
|
|
34
|
-
{ url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" },
|
|
35
|
-
{ url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" },
|
|
36
|
-
{ url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" },
|
|
37
|
-
{ url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" },
|
|
38
|
-
{ url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" },
|
|
39
|
-
{ url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" },
|
|
40
|
-
{ url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" },
|
|
41
|
-
{ url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" },
|
|
42
|
-
{ url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" },
|
|
43
|
-
{ url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" },
|
|
44
|
-
{ url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" },
|
|
45
|
-
{ url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" },
|
|
46
|
-
{ url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" },
|
|
47
|
-
{ url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" },
|
|
48
|
-
{ url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" },
|
|
49
|
-
{ url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" },
|
|
50
|
-
{ url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" },
|
|
51
|
-
{ url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" },
|
|
52
|
-
{ url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" },
|
|
53
|
-
{ url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" },
|
|
54
|
-
{ url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" },
|
|
55
|
-
{ url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" },
|
|
56
|
-
{ url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" },
|
|
57
|
-
{ url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" },
|
|
58
|
-
{ url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" },
|
|
59
|
-
{ url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" },
|
|
60
|
-
{ url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" },
|
|
61
|
-
{ url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" },
|
|
62
|
-
{ url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" },
|
|
63
|
-
{ url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" },
|
|
64
|
-
{ url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" },
|
|
65
|
-
{ url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" },
|
|
66
|
-
{ url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" },
|
|
67
|
-
{ url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" },
|
|
68
|
-
{ url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" },
|
|
69
|
-
{ url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" },
|
|
70
|
-
{ url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" },
|
|
71
|
-
]
|
|
72
|
-
|
|
73
|
-
[[package]]
|
|
74
|
-
name = "opencv-python"
|
|
75
|
-
version = "4.12.0.88"
|
|
76
|
-
source = { registry = "https://pypi.org/simple" }
|
|
77
|
-
dependencies = [
|
|
78
|
-
{ name = "numpy" },
|
|
79
|
-
]
|
|
80
|
-
sdist = { url = "https://files.pythonhosted.org/packages/ac/71/25c98e634b6bdeca4727c7f6d6927b056080668c5008ad3c8fc9e7f8f6ec/opencv-python-4.12.0.88.tar.gz", hash = "sha256:8b738389cede219405f6f3880b851efa3415ccd674752219377353f017d2994d", size = 95373294, upload-time = "2025-07-07T09:20:52.389Z" }
|
|
81
|
-
wheels = [
|
|
82
|
-
{ url = "https://files.pythonhosted.org/packages/85/68/3da40142e7c21e9b1d4e7ddd6c58738feb013203e6e4b803d62cdd9eb96b/opencv_python-4.12.0.88-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:f9a1f08883257b95a5764bf517a32d75aec325319c8ed0f89739a57fae9e92a5", size = 37877727, upload-time = "2025-07-07T09:13:31.47Z" },
|
|
83
|
-
{ url = "https://files.pythonhosted.org/packages/33/7c/042abe49f58d6ee7e1028eefc3334d98ca69b030e3b567fe245a2b28ea6f/opencv_python-4.12.0.88-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:812eb116ad2b4de43ee116fcd8991c3a687f099ada0b04e68f64899c09448e81", size = 57326471, upload-time = "2025-07-07T09:13:41.26Z" },
|
|
84
|
-
{ url = "https://files.pythonhosted.org/packages/62/3a/440bd64736cf8116f01f3b7f9f2e111afb2e02beb2ccc08a6458114a6b5d/opencv_python-4.12.0.88-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:51fd981c7df6af3e8f70b1556696b05224c4e6b6777bdd2a46b3d4fb09de1a92", size = 45887139, upload-time = "2025-07-07T09:13:50.761Z" },
|
|
85
|
-
{ url = "https://files.pythonhosted.org/packages/68/1f/795e7f4aa2eacc59afa4fb61a2e35e510d06414dd5a802b51a012d691b37/opencv_python-4.12.0.88-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:092c16da4c5a163a818f120c22c5e4a2f96e0db4f24e659c701f1fe629a690f9", size = 67041680, upload-time = "2025-07-07T09:14:01.995Z" },
|
|
86
|
-
{ url = "https://files.pythonhosted.org/packages/02/96/213fea371d3cb2f1d537612a105792aa0a6659fb2665b22cad709a75bd94/opencv_python-4.12.0.88-cp37-abi3-win32.whl", hash = "sha256:ff554d3f725b39878ac6a2e1fa232ec509c36130927afc18a1719ebf4fbf4357", size = 30284131, upload-time = "2025-07-07T09:14:08.819Z" },
|
|
87
|
-
{ url = "https://files.pythonhosted.org/packages/fa/80/eb88edc2e2b11cd2dd2e56f1c80b5784d11d6e6b7f04a1145df64df40065/opencv_python-4.12.0.88-cp37-abi3-win_amd64.whl", hash = "sha256:d98edb20aa932fd8ebd276a72627dad9dc097695b3d435a4257557bbb49a79d2", size = 39000307, upload-time = "2025-07-07T09:14:16.641Z" },
|
|
88
|
-
]
|
|
89
|
-
|
|
90
|
-
[[package]]
|
|
91
|
-
name = "puda-drivers"
|
|
92
|
-
version = "0.0.16"
|
|
93
|
-
source = { editable = "." }
|
|
94
|
-
dependencies = [
|
|
95
|
-
{ name = "nats-py" },
|
|
96
|
-
{ name = "opencv-python" },
|
|
97
|
-
{ name = "pyserial" },
|
|
98
|
-
]
|
|
99
|
-
|
|
100
|
-
[package.metadata]
|
|
101
|
-
requires-dist = [
|
|
102
|
-
{ name = "nats-py", specifier = ">=2.12.0" },
|
|
103
|
-
{ name = "opencv-python", specifier = ">=4.12.0.88" },
|
|
104
|
-
{ name = "pyserial", specifier = "~=3.5" },
|
|
105
|
-
]
|
|
106
|
-
|
|
107
|
-
[[package]]
|
|
108
|
-
name = "pyserial"
|
|
109
|
-
version = "3.5"
|
|
110
|
-
source = { registry = "https://pypi.org/simple" }
|
|
111
|
-
sdist = { url = "https://files.pythonhosted.org/packages/1e/7d/ae3f0a63f41e4d2f6cb66a5b57197850f919f59e558159a4dd3a818f5082/pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", size = 159125, upload-time = "2020-11-23T03:59:15.045Z" }
|
|
112
|
-
wheels = [
|
|
113
|
-
{ url = "https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0", size = 90585, upload-time = "2020-11-23T03:59:13.41Z" },
|
|
114
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/labware/opentrons_96_tiprack_300ul.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/transfer/liquid/sartorius/__init__.py
RENAMED
|
File without changes
|
{puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/transfer/liquid/sartorius/api.py
RENAMED
|
File without changes
|
{puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/transfer/liquid/sartorius/constants.py
RENAMED
|
File without changes
|
{puda_drivers-0.0.16 → puda_drivers-0.0.17}/src/puda_drivers/transfer/liquid/sartorius/rLine.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|