puda-drivers 0.0.16__tar.gz → 0.0.18__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.18}/PKG-INFO +62 -19
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/README.md +59 -16
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/pyproject.toml +8 -3
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/core/serialcontroller.py +21 -30
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/labware/labware.py +1 -2
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/labware/trash_bin.json +2 -1
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/machines/first.py +138 -89
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/move/deck.py +35 -1
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/move/gcode.py +2 -2
- puda_drivers-0.0.18/tests/poll_position.py +126 -0
- puda_drivers-0.0.18/tests/test_deck.py +172 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/tests/test_position_polling.py +3 -3
- puda_drivers-0.0.18/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.18}/.gitignore +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/LICENSE +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/__init__.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/core/__init__.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/core/logging.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/core/position.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/cv/__init__.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/cv/camera.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/labware/__init__.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/labware/opentrons_96_tiprack_300ul.json +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/labware/polyelectric_8_wellplate_30000ul.json +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/machines/__init__.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/move/__init__.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/move/grbl/__init__.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/move/grbl/api.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/move/grbl/constants.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/py.typed +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/transfer/liquid/sartorius/__init__.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/transfer/liquid/sartorius/api.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/transfer/liquid/sartorius/constants.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/src/puda_drivers/transfer/liquid/sartorius/rLine.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/tests/example.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/tests/first.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/tests/pipette.py +0 -0
- {puda_drivers-0.0.16 → puda_drivers-0.0.18}/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.18
|
|
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
|
|
6
|
+
Project-URL: Issues, https://github.com/PUDAP/puda/issues
|
|
7
7
|
Author-email: zhao <20024592+agentzhao@users.noreply.github.com>
|
|
8
8
|
License-Expression: MIT
|
|
9
9
|
License-File: LICENSE
|
|
@@ -40,14 +40,6 @@ Hardware drivers for the PUDA (Physical Unified Device Architecture) platform. T
|
|
|
40
40
|
pip install puda-drivers
|
|
41
41
|
```
|
|
42
42
|
|
|
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
43
|
## Quick Start
|
|
52
44
|
|
|
53
45
|
### Logging Configuration
|
|
@@ -197,28 +189,79 @@ sartorius_ports = list_serial_ports(filter_desc="Sartorius")
|
|
|
197
189
|
|
|
198
190
|
### Setup Development Environment
|
|
199
191
|
|
|
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.
|
|
192
|
+
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.
|
|
193
|
+
|
|
194
|
+
**From the repository root:**
|
|
201
195
|
|
|
202
196
|
```bash
|
|
203
|
-
#
|
|
204
|
-
uv
|
|
197
|
+
# Or install dependencies for all workspace packages
|
|
198
|
+
uv sync --all-packages
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
This will:
|
|
202
|
+
- Create a virtual environment at the repository root (`.venv/`)
|
|
203
|
+
- Install all dependencies for all workspace packages
|
|
204
|
+
- Install `puda-drivers` and other workspace packages in editable mode automatically
|
|
205
205
|
|
|
206
|
-
|
|
206
|
+
**Using the package:**
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
# Run Python scripts with workspace context (recommended, works from anywhere in the workspace)
|
|
210
|
+
uv run python your_script.py
|
|
211
|
+
|
|
212
|
+
# Or activate the virtual environment (from repository root where .venv is located)
|
|
207
213
|
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
214
|
+
python your_script.py
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Adding dependencies:**
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
# From the package directory
|
|
221
|
+
cd libs/drivers
|
|
222
|
+
uv add some-package
|
|
223
|
+
|
|
224
|
+
# Or from repository root
|
|
225
|
+
uv add --package puda-drivers some-package
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Note:** Workspace packages are automatically installed in editable mode, so code changes are immediately available without reinstalling.
|
|
208
229
|
|
|
209
|
-
|
|
210
|
-
|
|
230
|
+
### Testing
|
|
231
|
+
|
|
232
|
+
Run tests using pytest with `uv run`:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
# Run all tests
|
|
236
|
+
uv run pytest tests/
|
|
211
237
|
|
|
212
|
-
#
|
|
213
|
-
|
|
238
|
+
# Run a specific test file
|
|
239
|
+
uv run pytest tests/test_deck.py
|
|
240
|
+
|
|
241
|
+
# Run a specific test class
|
|
242
|
+
uv run pytest tests/test_deck.py::TestDeckToDict
|
|
243
|
+
|
|
244
|
+
# Run a specific test function
|
|
245
|
+
uv run pytest tests/test_deck.py::TestDeckToDict::test_to_dict_empty_deck
|
|
246
|
+
|
|
247
|
+
# Run with verbose output
|
|
248
|
+
uv run pytest tests/ -v
|
|
249
|
+
|
|
250
|
+
# Run with coverage report
|
|
251
|
+
uv run pytest tests/ --cov=puda_drivers --cov-report=html
|
|
214
252
|
```
|
|
215
253
|
|
|
254
|
+
**Note:** Make sure you're in the `libs/drivers` directory or use the full path to the tests directory when running pytest commands.
|
|
255
|
+
|
|
216
256
|
### Building and Publishing
|
|
217
257
|
|
|
218
258
|
```bash
|
|
219
259
|
# Build distribution packages
|
|
220
260
|
uv build
|
|
221
261
|
|
|
262
|
+
# cd to puda project root
|
|
263
|
+
cd ...
|
|
264
|
+
|
|
222
265
|
# Publish to PyPI
|
|
223
266
|
uv publish
|
|
224
267
|
# Username: __token__
|
|
@@ -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,28 +167,79 @@ 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:**
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# From the package directory
|
|
199
|
+
cd libs/drivers
|
|
200
|
+
uv add some-package
|
|
201
|
+
|
|
202
|
+
# Or from repository root
|
|
203
|
+
uv add --package puda-drivers some-package
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Note:** Workspace packages are automatically installed in editable mode, so code changes are immediately available without reinstalling.
|
|
186
207
|
|
|
187
|
-
|
|
188
|
-
|
|
208
|
+
### Testing
|
|
209
|
+
|
|
210
|
+
Run tests using pytest with `uv run`:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# Run all tests
|
|
214
|
+
uv run pytest tests/
|
|
189
215
|
|
|
190
|
-
#
|
|
191
|
-
|
|
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
|
|
192
230
|
```
|
|
193
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
|
|
197
237
|
# Build distribution packages
|
|
198
238
|
uv build
|
|
199
239
|
|
|
240
|
+
# cd to puda project root
|
|
241
|
+
cd ...
|
|
242
|
+
|
|
200
243
|
# Publish to PyPI
|
|
201
244
|
uv publish
|
|
202
245
|
# Username: __token__
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "puda-drivers"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.18"
|
|
4
4
|
description = "Hardware drivers for the PUDA platform."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -18,16 +18,21 @@ classifiers = [
|
|
|
18
18
|
]
|
|
19
19
|
license = "MIT"
|
|
20
20
|
license-files = ["LICEN[CS]E*"]
|
|
21
|
+
|
|
21
22
|
dependencies = [
|
|
22
23
|
"nats-py>=2.12.0",
|
|
23
24
|
"opencv-python>=4.12.0.88",
|
|
24
25
|
"pyserial~=3.5",
|
|
25
26
|
]
|
|
27
|
+
[dependency-groups]
|
|
28
|
+
dev = [
|
|
29
|
+
"pytest>=9.0.2",
|
|
30
|
+
]
|
|
26
31
|
|
|
27
32
|
[build-system]
|
|
28
33
|
requires = ["hatchling >= 1.26"]
|
|
29
34
|
build-backend = "hatchling.build"
|
|
30
35
|
|
|
31
36
|
[project.urls]
|
|
32
|
-
Homepage = "https://github.com/
|
|
33
|
-
Issues = "https://github.com/
|
|
37
|
+
Homepage = "https://github.com/PUDAP/puda"
|
|
38
|
+
Issues = "https://github.com/PUDAP/puda/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)
|