puda-drivers 0.0.16__py3-none-any.whl → 0.0.17__py3-none-any.whl
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/core/serialcontroller.py +21 -30
- puda_drivers/labware/trash_bin.json +2 -1
- puda_drivers/machines/first.py +30 -0
- puda_drivers/move/deck.py +35 -1
- puda_drivers/move/gcode.py +2 -2
- {puda_drivers-0.0.16.dist-info → puda_drivers-0.0.17.dist-info}/METADATA +60 -19
- {puda_drivers-0.0.16.dist-info → puda_drivers-0.0.17.dist-info}/RECORD +9 -9
- {puda_drivers-0.0.16.dist-info → puda_drivers-0.0.17.dist-info}/WHEEL +0 -0
- {puda_drivers-0.0.16.dist-info → puda_drivers-0.0.17.dist-info}/licenses/LICENSE +0 -0
|
@@ -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)
|
puda_drivers/machines/first.py
CHANGED
|
@@ -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.
|
puda_drivers/move/deck.py
CHANGED
|
@@ -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)
|
puda_drivers/move/gcode.py
CHANGED
|
@@ -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()
|
|
@@ -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
|
|
@@ -3,19 +3,19 @@ puda_drivers/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
3
3
|
puda_drivers/core/__init__.py,sha256=XbCdXsU6NMDsmEAtavAGiSZZPla5d7zc2L7Qx9qKHdY,214
|
|
4
4
|
puda_drivers/core/logging.py,sha256=prOeJ3CGEbm37TtMRyAOTQQiMU5_ImZTRXmcUJxkenc,2892
|
|
5
5
|
puda_drivers/core/position.py,sha256=f4efmDSrKKCtqrR-GUJxVitPG20MiuGSDOWt-9TVISk,12628
|
|
6
|
-
puda_drivers/core/serialcontroller.py,sha256=
|
|
6
|
+
puda_drivers/core/serialcontroller.py,sha256=8Yf9DYIhtQTIVGB1Pm6COa6qgJcM0ZxdGwOGW8Om9ss,8654
|
|
7
7
|
puda_drivers/cv/__init__.py,sha256=DYiPwYOLSUsZ9ivWiHoMGGD3MZ3Ygu9qLYja_OiUodU,100
|
|
8
8
|
puda_drivers/cv/camera.py,sha256=Tzxt1h3W5Z8XFanud_z-PhhJ2UYsC4OEhcak9TVu8jM,16490
|
|
9
9
|
puda_drivers/labware/__init__.py,sha256=RlRxrJn2zyzyxv4c1KGt8Gmxv2cRO8V4zZVnnyL-I00,288
|
|
10
10
|
puda_drivers/labware/labware.py,sha256=hZhOzSyb1GP_bm1LvQsREx4YSqLCWBTKNWDgDqfFavI,5317
|
|
11
11
|
puda_drivers/labware/opentrons_96_tiprack_300ul.json,sha256=jmNaworu688GEgFdxMxNRSaEp4ZOg9IumFk8bVHSzYY,19796
|
|
12
12
|
puda_drivers/labware/polyelectric_8_wellplate_30000ul.json,sha256=esu2tej0ORs7Pfd4HwoQVUpU5mPvp2AYzE3zsCC2FDk,3104
|
|
13
|
-
puda_drivers/labware/trash_bin.json,sha256=
|
|
13
|
+
puda_drivers/labware/trash_bin.json,sha256=6dH0prXKJVXMRm096rPSiJgMr0nPRtHUKo7Z6xIxdnA,596
|
|
14
14
|
puda_drivers/machines/__init__.py,sha256=zmIk_r2T8nbPA68h3Cko8N6oL7ncoBpmvhNcAqzHmc4,45
|
|
15
|
-
puda_drivers/machines/first.py,sha256=
|
|
15
|
+
puda_drivers/machines/first.py,sha256=GYYEUGi6xx6ev-ByX-_y9_GnMaBdhPCeiu-bCnKDZw0,21417
|
|
16
16
|
puda_drivers/move/__init__.py,sha256=NKIKckcqgyviPM0EGFcmIoaqkJM4qekR4babfdddRzM,96
|
|
17
|
-
puda_drivers/move/deck.py,sha256=
|
|
18
|
-
puda_drivers/move/gcode.py,sha256=
|
|
17
|
+
puda_drivers/move/deck.py,sha256=CprlB8i9jvNImrc5IpHUZSSvvFe8uei76fNQ4iN6n34,2394
|
|
18
|
+
puda_drivers/move/gcode.py,sha256=ioqRS-Kom5S7Pyp9-wg4MRXGIbIDEz7SsihBBOM2m30,23784
|
|
19
19
|
puda_drivers/move/grbl/__init__.py,sha256=vBeeti8DVN2dACi1rLmHN_UGIOdo0s-HZX6mIepLV5I,98
|
|
20
20
|
puda_drivers/move/grbl/api.py,sha256=loj8_Vap7S9qaD0ReHhgxr9Vkl6Wp7DGzyLkZyZ6v_k,16995
|
|
21
21
|
puda_drivers/move/grbl/constants.py,sha256=4736CRDzLGWVqGscLajMlrIQMyubsHfthXi4RF1CHNg,9585
|
|
@@ -23,7 +23,7 @@ puda_drivers/transfer/liquid/sartorius/__init__.py,sha256=7fljIbu0KkumsI3NI3O64d
|
|
|
23
23
|
puda_drivers/transfer/liquid/sartorius/api.py,sha256=jxwIJmY2k1K2ts6NC2ZgFTe4MOiH8TGnJeqYOqNa3rE,28250
|
|
24
24
|
puda_drivers/transfer/liquid/sartorius/constants.py,sha256=mcsjLrVBH-RSodH-pszstwcEL9wwbV0vOgHbGNxZz9w,2770
|
|
25
25
|
puda_drivers/transfer/liquid/sartorius/rLine.py,sha256=FWEFO9tZN3cbneiozXRs77O-47Jg_8vYzWJvUrWpYxA,14531
|
|
26
|
-
puda_drivers-0.0.
|
|
27
|
-
puda_drivers-0.0.
|
|
28
|
-
puda_drivers-0.0.
|
|
29
|
-
puda_drivers-0.0.
|
|
26
|
+
puda_drivers-0.0.17.dist-info/METADATA,sha256=YFBVlnKBI0al6Z6F9w0urGImcN133jmzsJVTcojNfEc,8420
|
|
27
|
+
puda_drivers-0.0.17.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
28
|
+
puda_drivers-0.0.17.dist-info/licenses/LICENSE,sha256=7EI8xVBu6h_7_JlVw-yPhhOZlpY9hP8wal7kHtqKT_E,1074
|
|
29
|
+
puda_drivers-0.0.17.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|