oep-client-python 0.0.4__py3-none-any.whl → 0.0.5__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.
- oep_client/__init__.py +1 -1
- oep_client/core.py +41 -1
- oep_client/endpoint.py +1 -1
- oep_client/usb_stream.py +32 -4
- {oep_client_python-0.0.4.dist-info → oep_client_python-0.0.5.dist-info}/METADATA +2 -2
- {oep_client_python-0.0.4.dist-info → oep_client_python-0.0.5.dist-info}/RECORD +9 -9
- {oep_client_python-0.0.4.dist-info → oep_client_python-0.0.5.dist-info}/WHEEL +0 -0
- {oep_client_python-0.0.4.dist-info → oep_client_python-0.0.5.dist-info}/entry_points.txt +0 -0
- {oep_client_python-0.0.4.dist-info → oep_client_python-0.0.5.dist-info}/licenses/LICENSE +0 -0
oep_client/__init__.py
CHANGED
oep_client/core.py
CHANGED
|
@@ -109,7 +109,47 @@ def plan_apply(hst: h.Host, assignments: list[tuple[int, int, int]]) -> None:
|
|
|
109
109
|
all interfaces accept their roles or nothing changes. The plan is the
|
|
110
110
|
session's resource: kept over an explicit end, released at a lease lapse or a force takeover (oep-core §9)."""
|
|
111
111
|
tlv = b"".join(bytes([TAG_ROLE_ASSIGNMENT, 5]) + struct.pack("<HBH", fn, role, ch) for fn, role, ch in assignments)
|
|
112
|
-
|
|
112
|
+
try:
|
|
113
|
+
hst.call(m.CORE_FN, OP_PLAN_APPLY, tlv)
|
|
114
|
+
except h.Rejected as e:
|
|
115
|
+
if e.result.detail != m.UNAVAILABLE:
|
|
116
|
+
raise
|
|
117
|
+
why = _pin_holders(hst, assignments)
|
|
118
|
+
if why:
|
|
119
|
+
raise PinsTaken(e.result, why) from None
|
|
120
|
+
raise
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class PinsTaken(h.Rejected):
|
|
124
|
+
"""plan_apply refused (unavailable) and the probe's settings say why: a pin kept by another fn's saved plan or by a
|
|
125
|
+
slot (oep.probe.config). `holders`: (channel, what holds it)."""
|
|
126
|
+
|
|
127
|
+
def __init__(self, result, holders: list[tuple[int, str]]):
|
|
128
|
+
super().__init__(result)
|
|
129
|
+
self.holders = holders
|
|
130
|
+
|
|
131
|
+
def __str__(self) -> str:
|
|
132
|
+
return "pins taken: " + "; ".join(f"channel {ch} by {who}" for ch, who in self.holders) + \
|
|
133
|
+
" (oep config show; oep config remove <probe> plan <fn> frees a saved plan)"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _pin_holders(hst: h.Host, assignments: list[tuple[int, int, int]]) -> list[tuple[int, str]]:
|
|
137
|
+
"""What the settings keep on the channels asked for (best effort: an empty list when there is no oep.probe.config
|
|
138
|
+
or it cannot be read)."""
|
|
139
|
+
from . import config
|
|
140
|
+
try:
|
|
141
|
+
items = config.ProbeConfig(hst).items()
|
|
142
|
+
except (LookupError, h.OepError):
|
|
143
|
+
return []
|
|
144
|
+
fns = {fn for fn, _, _ in assignments}
|
|
145
|
+
out = []
|
|
146
|
+
for fn, role, ch in assignments:
|
|
147
|
+
for it in items:
|
|
148
|
+
if isinstance(it, config.Plan) and it.channel == ch and it.fn not in fns:
|
|
149
|
+
out.append((ch, f"the saved plan of fn {it.fn} (role {it.role})"))
|
|
150
|
+
elif isinstance(it, config.Slot) and ch in it.pins:
|
|
151
|
+
out.append((ch, f"slot {it.slot} {it.name}"))
|
|
152
|
+
return out
|
|
113
153
|
|
|
114
154
|
|
|
115
155
|
def plan_release(hst: h.Host, fns: list[int] | tuple[int, ...] = ()) -> None:
|
oep_client/endpoint.py
CHANGED
|
@@ -923,7 +923,7 @@ class Endpoint:
|
|
|
923
923
|
pos = s.end
|
|
924
924
|
elif frm == 3:
|
|
925
925
|
hits = [mk for mk in s.marks if arg == 0 or mk[2] == arg]
|
|
926
|
-
pos = hits[-1][1] if hits else s.
|
|
926
|
+
pos = hits[-1][1] if hits else s.end # no such mark: from now (common §1.2)
|
|
927
927
|
else:
|
|
928
928
|
raise Reject(m.UNSUPPORTED)
|
|
929
929
|
flags = 0
|
oep_client/usb_stream.py
CHANGED
|
@@ -10,6 +10,7 @@ transfer; if its timeout hits first, pyusb raises and the bytes already received
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
+
import atexit
|
|
13
14
|
import threading
|
|
14
15
|
import time
|
|
15
16
|
|
|
@@ -114,15 +115,21 @@ class UsbAsyncStream:
|
|
|
114
115
|
self.timeout = 0.05
|
|
115
116
|
self._buffer = bytearray()
|
|
116
117
|
self._cond = threading.Condition()
|
|
117
|
-
self._closed = False
|
|
118
|
+
self._closed = False # no more resubmits (close() started)
|
|
119
|
+
self._stop = False # the event thread ends (every transfer is back)
|
|
118
120
|
self._transfers = []
|
|
121
|
+
self._active = 0 # transfers submitted and not yet back for good
|
|
119
122
|
for _ in range(self.DEPTH):
|
|
120
123
|
t = handle.getTransfer()
|
|
121
124
|
t.setBulk(endpoint_in, self.URB_SIZE, callback=self._done, timeout=0)
|
|
122
125
|
t.submit()
|
|
123
126
|
self._transfers.append(t)
|
|
127
|
+
self._active += 1
|
|
124
128
|
self._events = threading.Thread(target=self._run, daemon=True)
|
|
125
129
|
self._events.start()
|
|
130
|
+
# Closed at exit too: left to usb1's own finalizers, the context went while the event thread was inside
|
|
131
|
+
# handleEventsTimeout with transfers out - the process hung at exit or libusb aborted (usbi_mutex_destroy).
|
|
132
|
+
atexit.register(self.close)
|
|
126
133
|
|
|
127
134
|
@classmethod
|
|
128
135
|
def open(cls, vid: int, pid: int, serial: str | None = None) -> "UsbAsyncStream":
|
|
@@ -158,14 +165,23 @@ class UsbAsyncStream:
|
|
|
158
165
|
self._buffer += data
|
|
159
166
|
self._cond.notify_all()
|
|
160
167
|
if not self._closed and status in (usb1.TRANSFER_COMPLETED, usb1.TRANSFER_TIMED_OUT):
|
|
161
|
-
|
|
168
|
+
try:
|
|
169
|
+
transfer.submit()
|
|
170
|
+
return
|
|
171
|
+
except usb1.USBError:
|
|
172
|
+
pass
|
|
173
|
+
with self._cond:
|
|
174
|
+
self._active -= 1 # back for good (cancelled, failed, or closing)
|
|
175
|
+
self._cond.notify_all()
|
|
162
176
|
|
|
163
177
|
def _run(self) -> None:
|
|
164
|
-
while not self.
|
|
178
|
+
while not self._stop:
|
|
165
179
|
try:
|
|
166
180
|
self.context.handleEventsTimeout(0.1)
|
|
167
181
|
except self._usb1.USBErrorInterrupted:
|
|
168
182
|
continue
|
|
183
|
+
except self._usb1.USBError:
|
|
184
|
+
break
|
|
169
185
|
|
|
170
186
|
@property
|
|
171
187
|
def in_waiting(self) -> int:
|
|
@@ -195,15 +211,27 @@ class UsbAsyncStream:
|
|
|
195
211
|
self._buffer.clear()
|
|
196
212
|
|
|
197
213
|
def close(self) -> None:
|
|
214
|
+
"""Cancel the IN transfers and let the event thread take them back before the handle and the context go
|
|
215
|
+
(closing with transfers still out hung the process or aborted libusb). Safe to call twice (atexit)."""
|
|
216
|
+
if self._closed:
|
|
217
|
+
return
|
|
198
218
|
self._closed = True
|
|
219
|
+
atexit.unregister(self.close)
|
|
199
220
|
for t in self._transfers:
|
|
200
221
|
try:
|
|
201
222
|
t.cancel()
|
|
202
223
|
except self._usb1.USBError:
|
|
203
224
|
pass
|
|
204
|
-
|
|
225
|
+
deadline = time.monotonic() + 1.0
|
|
226
|
+
with self._cond:
|
|
227
|
+
while self._active > 0 and self._events.is_alive() and time.monotonic() < deadline:
|
|
228
|
+
self._cond.wait(0.05)
|
|
229
|
+
self._stop = True
|
|
230
|
+
self._events.join(timeout=1.0)
|
|
205
231
|
try:
|
|
206
232
|
self.handle.releaseInterface(self.interface)
|
|
233
|
+
except self._usb1.USBError:
|
|
234
|
+
pass
|
|
207
235
|
finally:
|
|
208
236
|
self.handle.close()
|
|
209
237
|
self.context.close()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: oep-client-python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: Open Embedded Probe (OEP) v1 host: serial / USB / TCP transports, the session rules, the standard interfaces, and a fake probe
|
|
5
5
|
Project-URL: Homepage, https://github.com/Open-Embedded-Probe/oep-client-python
|
|
6
6
|
Project-URL: Repository, https://github.com/Open-Embedded-Probe/oep-client-python
|
|
@@ -43,7 +43,7 @@ uv run pytest # in a checkout
|
|
|
43
43
|
`oep-client` is another project, so the distribution is named `oep-client-python`.
|
|
44
44
|
|
|
45
45
|
Releases: run the GitHub Actions workflow Release (workflow_dispatch, version X.Y.Z or X.Y.ZbN). `tools/prepare_release.py`
|
|
46
|
-
sets the version in pyproject.toml and `oep_client.__version__` and turns CHANGELOG.md's Unreleased into that version; after
|
|
46
|
+
sets the version in pyproject.toml, uv.lock and `oep_client.__version__` and turns CHANGELOG.md's Unreleased into that version; after
|
|
47
47
|
the tests and the build it commits, tags, makes the GitHub Release and publishes to PyPI (Trusted Publishing). Record changes
|
|
48
48
|
under Unreleased in CHANGELOG.md, (EN) and (JA).
|
|
49
49
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
oep_client/__init__.py,sha256=
|
|
1
|
+
oep_client/__init__.py,sha256=arvOlIBZyy_QgLJjeCKna1018c1f7GeNCDiwrNIscWc,163
|
|
2
2
|
oep_client/__main__.py,sha256=Ta7D7jhtg5IhFWuI3KTikbDNYazh9QKCH3FtMMnDMjg,14143
|
|
3
3
|
oep_client/arm.py,sha256=JLyUhLSPcmsk73schZcaJYoOKuHzrvnU8kPmfrEvVuo,12331
|
|
4
4
|
oep_client/capture.py,sha256=RZgDnWqr8Aqy49U7PqQpnKIQEL18djVuDJFydxrmv-c,17996
|
|
@@ -7,10 +7,10 @@ oep_client/ch32_flash.py,sha256=Fc-6jEj15cQtMSKmfC5dmDetMXJ8hAdAuJ9d9odwrsA,1265
|
|
|
7
7
|
oep_client/cobs.py,sha256=hqddQI_X7rsBvBvcP_MBE3bXPyB13-Azjqp_9JfdQ1g,2288
|
|
8
8
|
oep_client/config.py,sha256=JhEmQKU-4KjSAoIovgeEwueKEiMLPUIiGVhZcR5ZlVc,9271
|
|
9
9
|
oep_client/console.py,sha256=YYcuc9Qxe3kihla8wynYbDaLVr7wBLEvswrM-cMK3Tk,6859
|
|
10
|
-
oep_client/core.py,sha256=
|
|
10
|
+
oep_client/core.py,sha256=Cyyaknjipkg6tS76dZe5GMqjmOoQjlfeP0WFtMHkJNs,10403
|
|
11
11
|
oep_client/decode.py,sha256=M-bRARrA2qGZLoJn0BdXeTb4WjL_2Hb2nk_g1IGXIw4,2382
|
|
12
12
|
oep_client/dump.py,sha256=GAnmu2abuDmGmbWcbbEfC3EUuPp5Hady9vQVNcQ-724,6743
|
|
13
|
-
oep_client/endpoint.py,sha256=
|
|
13
|
+
oep_client/endpoint.py,sha256=K6FWTojqKqKIQEPpuOQLmfbaDWLvspXed-ipzFNuKHY,72303
|
|
14
14
|
oep_client/esp32_targets.py,sha256=b8tNH-nb2KC-XgNfWg5l3TO0cyhZOF-gJajaVxNVSGQ,4988
|
|
15
15
|
oep_client/fake.py,sha256=UzpB-A42Ma2QqJj0l-JmwCJkMn6DPVHHN77fcemT59U,13104
|
|
16
16
|
oep_client/fake_serial.py,sha256=uKl0UNcifhdeykQtm8gnRRhqV7TT4vPwBFuBhbf-9BI,4504
|
|
@@ -28,9 +28,9 @@ oep_client/riscv.py,sha256=E0_C-4mBsrp9FUoZpMiqSH9e8sC8Q4EO5HRjFKE6_pc,21392
|
|
|
28
28
|
oep_client/rp2350.py,sha256=7OMyj270k3AY1e3-ljicPwOjf9jOlokbT6QNkA0b0yY,4227
|
|
29
29
|
oep_client/target.py,sha256=R8BvYfFfYNhZF0MysJkNb8b69YgBlmeGXNX8ReZIe5k,1068
|
|
30
30
|
oep_client/uiapduino.py,sha256=IEPG3LbZPksi8g3wmAf6T0CRWSfYKW1_qrH_GBVQdxY,6090
|
|
31
|
-
oep_client/usb_stream.py,sha256=
|
|
32
|
-
oep_client_python-0.0.
|
|
33
|
-
oep_client_python-0.0.
|
|
34
|
-
oep_client_python-0.0.
|
|
35
|
-
oep_client_python-0.0.
|
|
36
|
-
oep_client_python-0.0.
|
|
31
|
+
oep_client/usb_stream.py,sha256=VJMCxsOlQCk005FcS92pqTh33fRSFBizoL__L0VbUMA,10242
|
|
32
|
+
oep_client_python-0.0.5.dist-info/METADATA,sha256=42SpXvaLTywLE9L3yPq1mRj4UNTamkB7Vb0dbZegO24,7368
|
|
33
|
+
oep_client_python-0.0.5.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
34
|
+
oep_client_python-0.0.5.dist-info/entry_points.txt,sha256=h6_8QX89Ww1ogALEGG5g4kspT3IOV6O51lwKqf5BWco,49
|
|
35
|
+
oep_client_python-0.0.5.dist-info/licenses/LICENSE,sha256=ntlalztb3L1OaNAxmQdHlubu5fVhjMc9oRZi3PeQhXg,1076
|
|
36
|
+
oep_client_python-0.0.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|