oep-client-python 0.0.25__py3-none-any.whl → 0.0.26__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/__main__.py +3 -1
- oep_client/arm.py +1 -1
- oep_client/capture.py +3 -2
- oep_client/config.py +3 -1
- oep_client/core.py +15 -5
- oep_client/endpoint.py +24 -4
- oep_client/fake_serial.py +34 -5
- oep_client/fake_serve.py +7 -4
- oep_client/host.py +30 -5
- oep_client/link.py +220 -65
- oep_client/riscv.py +20 -3
- {oep_client_python-0.0.25.dist-info → oep_client_python-0.0.26.dist-info}/METADATA +8 -3
- {oep_client_python-0.0.25.dist-info → oep_client_python-0.0.26.dist-info}/RECORD +17 -17
- {oep_client_python-0.0.25.dist-info → oep_client_python-0.0.26.dist-info}/WHEEL +0 -0
- {oep_client_python-0.0.25.dist-info → oep_client_python-0.0.26.dist-info}/entry_points.txt +0 -0
- {oep_client_python-0.0.25.dist-info → oep_client_python-0.0.26.dist-info}/licenses/LICENSE +0 -0
oep_client/__init__.py
CHANGED
oep_client/__main__.py
CHANGED
|
@@ -36,6 +36,7 @@ def main(argv=None) -> int:
|
|
|
36
36
|
sp.add_argument("--baud", type=int, default=link.BASE_BAUD, help="the boot speed (default 115200)")
|
|
37
37
|
sp.add_argument("--verify-bytes", type=int, default=32768)
|
|
38
38
|
sp.add_argument("--verify-s", type=float, default=1.0)
|
|
39
|
+
sp.add_argument("--duplex-s", type=float, default=1.0, help="both ways at once for this long (default 1)")
|
|
39
40
|
sp.add_argument("--json", action="store_true")
|
|
40
41
|
d = sub.add_parser("dump", help="list and describe every interface a probe offers")
|
|
41
42
|
src = d.add_mutually_exclusive_group(required=True)
|
|
@@ -69,7 +70,8 @@ def _speed(args) -> int:
|
|
|
69
70
|
hst = link.open_host(args.probe, baud=args.baud)
|
|
70
71
|
try:
|
|
71
72
|
core.take(hst, 5000, owner="oep speed")
|
|
72
|
-
report = link.raise_speed(hst, rates, verify_bytes=args.verify_bytes, verify_s=args.verify_s
|
|
73
|
+
report = link.raise_speed(hst, rates, verify_bytes=args.verify_bytes, verify_s=args.verify_s,
|
|
74
|
+
duplex_s=args.duplex_s)
|
|
73
75
|
if args.json:
|
|
74
76
|
sys.stdout.write(json.dumps(dataclasses.asdict(report), indent=2) + "\n")
|
|
75
77
|
else:
|
oep_client/arm.py
CHANGED
|
@@ -37,7 +37,7 @@ class SwdWire(WireBase):
|
|
|
37
37
|
body = bytes([0]) + self._speed_tlv(self._speed_or_default(max_speed)) + self._pins_tlv(pins) + self._reset_tlv(reset)
|
|
38
38
|
if targetsel is not None:
|
|
39
39
|
body += m.tlv(self.TAG_TARGETSEL, struct.pack("<I", targetsel), critical=True)
|
|
40
|
-
rd = m.Reader(self._call(self.ATTACH, body).payload)
|
|
40
|
+
rd = m.Reader(self._call(self.ATTACH, body, expect_ms=reset[1] if reset else 0).payload)
|
|
41
41
|
conn, dpidr, self.flags, self.speed_hz = rd.take("HIBI")
|
|
42
42
|
self.existing = bool(self.flags & self.FLAGS["existing"])
|
|
43
43
|
rd.tail()
|
oep_client/capture.py
CHANGED
|
@@ -335,7 +335,7 @@ class LogicCapture(Interface):
|
|
|
335
335
|
|
|
336
336
|
def start(self) -> int:
|
|
337
337
|
"""-> blocking_ms (0: the probe keeps answering while it captures). self.generation: the new capture's."""
|
|
338
|
-
rd = m.Reader(self._call(self.START).payload)
|
|
338
|
+
rd = m.Reader(self._call(self.START, expect_ms=self.config.blocking_ms if self.config else 0).payload)
|
|
339
339
|
blocking, self.generation = rd.take("II")
|
|
340
340
|
rd.tail()
|
|
341
341
|
self.armed_s = time.monotonic()
|
|
@@ -576,7 +576,8 @@ class CaptureGroup(Interface):
|
|
|
576
576
|
def start(self, tracks: list[LogicCapture] = ()) -> tuple[int, int]:
|
|
577
577
|
"""-> (blocking_ms, the group's start_ns). `tracks`: whose armed_s and generation to set (the answer's TLV
|
|
578
578
|
generations names each track's new generation; self.generations keeps them by fn)."""
|
|
579
|
-
rd = m.Reader(self._call(self.START
|
|
579
|
+
rd = m.Reader(self._call(self.START, expect_ms=max([t.config.blocking_ms for t in tracks if t.config] or [0]))
|
|
580
|
+
.payload)
|
|
580
581
|
blocking, start_ns = rd.take("IQ")
|
|
581
582
|
gens = rd.tail().get(GROUP_GENERATIONS) or b""
|
|
582
583
|
self.generations = {fn: g for fn, g in struct.iter_unpack("<HI", gens[:len(gens) // 6 * 6])}
|
oep_client/config.py
CHANGED
|
@@ -292,6 +292,8 @@ class State:
|
|
|
292
292
|
binds: list[BindState] = field(default_factory=list)
|
|
293
293
|
|
|
294
294
|
|
|
295
|
+
SAVE_EXPECT_MS = 2000 # a save writes the probe's flash: the link waits at least this (Host.expecting)
|
|
296
|
+
|
|
295
297
|
class ProbeConfig(Interface):
|
|
296
298
|
NAME = "oep.probe.config"
|
|
297
299
|
REVISION = 1
|
|
@@ -346,7 +348,7 @@ class ProbeConfig(Interface):
|
|
|
346
348
|
|
|
347
349
|
def save(self) -> int:
|
|
348
350
|
"""Save the current settings (a probe with storage; the whole is replaced). -> the hash saved."""
|
|
349
|
-
return self._hash_answer(self._call(self.SAVE))
|
|
351
|
+
return self._hash_answer(self._call(self.SAVE, expect_ms=SAVE_EXPECT_MS))
|
|
350
352
|
|
|
351
353
|
def erase(self) -> None:
|
|
352
354
|
self._call(self.ERASE)
|
oep_client/core.py
CHANGED
|
@@ -4,6 +4,8 @@ entry, and calls that raise unless they worked)."""
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
import contextlib
|
|
8
|
+
|
|
7
9
|
import struct
|
|
8
10
|
|
|
9
11
|
from . import catalog, host as h, message as m, registry as reg
|
|
@@ -196,12 +198,20 @@ class Interface:
|
|
|
196
198
|
raise UnsupportedRevision(f"{self.name} (fn {self.fn}) is revision {rev} on this probe; this client "
|
|
197
199
|
f"speaks revision {self.REVISION} only")
|
|
198
200
|
|
|
199
|
-
def
|
|
200
|
-
|
|
201
|
+
def _expecting(self, ms: int):
|
|
202
|
+
"""Host.expecting(ms) when the host has it (a stand-in host may not)."""
|
|
203
|
+
expecting = getattr(self.host, "expecting", None) if ms else None
|
|
204
|
+
return expecting(ms) if expecting else contextlib.nullcontext()
|
|
205
|
+
|
|
206
|
+
def _call(self, op: int, body: bytes = b"", *, locked: bool = True, expect_ms: int = 0) -> m.Result:
|
|
207
|
+
with self._expecting(expect_ms):
|
|
208
|
+
return self.host.call(self.fn, op, self.prefix + body, locked=locked)
|
|
201
209
|
|
|
202
|
-
def _request(self, op: int, body: bytes = b"", *, locked: bool = True) -> m.Result:
|
|
203
|
-
"""Rejections raise; completed results of any outcome come back for the caller to decode.
|
|
204
|
-
|
|
210
|
+
def _request(self, op: int, body: bytes = b"", *, locked: bool = True, expect_ms: int = 0) -> m.Result:
|
|
211
|
+
"""Rejections raise; completed results of any outcome come back for the caller to decode. expect_ms: how
|
|
212
|
+
long it may take on the probe (Host.expecting)."""
|
|
213
|
+
with self._expecting(expect_ms):
|
|
214
|
+
return self.host.request(self.fn, op, self.prefix + body, locked=locked)
|
|
205
215
|
|
|
206
216
|
def request(self, op: int, body: bytes = b"") -> tuple[int, int, bytes]:
|
|
207
217
|
"""The raw (fn, op, payload) of one operation, for Host.pipeline / pipeline_calls."""
|
oep_client/endpoint.py
CHANGED
|
@@ -39,7 +39,8 @@ define:
|
|
|
39
39
|
verify_ms without a commit or at a broken candidate, a commit after idle_ms (at most port_speed_idle_max_ms, 3000;
|
|
40
40
|
0 and anything longer count as that maximum) with no good frame or 3 broken
|
|
41
41
|
candidates within 1 s, the session's end reverts after its answer. The line itself is modelled by `broken_rates`
|
|
42
|
-
(rate -> BrokenRate): frames at such a rate break, from a size and in the directions given
|
|
42
|
+
(rate -> BrokenRate): frames at such a rate break, from a size and in the directions given, only both ways at once
|
|
43
|
+
(`duplex`), only every Nth (`every`) (`fake_serial` applies it).
|
|
43
44
|
|
|
44
45
|
Every other non-core fn gets two stand-in operations so the session rules can be exercised - FAKE ONLY, they mean
|
|
45
46
|
nothing on a real probe: 0x01 write(u32) changes state, 0x02 read -> u32 needs no lock.
|
|
@@ -317,6 +318,16 @@ class BrokenRate:
|
|
|
317
318
|
min_size: int = 0
|
|
318
319
|
to_host: bool = True
|
|
319
320
|
to_probe: bool = True
|
|
321
|
+
duplex: bool = False # only while both ways carry such frames at once (a request of min_size and more comes in
|
|
322
|
+
# while an answer of min_size and more is still unread): the request breaks (to_probe),
|
|
323
|
+
# the unread answer breaks (to_host)
|
|
324
|
+
every: int = 1 # of the frames that would break, only every Nth does (1: all)
|
|
325
|
+
seen: int = 0 # the frames that would have broken so far (`every` counts these)
|
|
326
|
+
|
|
327
|
+
def hit(self) -> bool:
|
|
328
|
+
"""One more frame that would break: True when this one does (`every`)."""
|
|
329
|
+
self.seen += 1
|
|
330
|
+
return self.seen % max(1, self.every) == 0
|
|
320
331
|
|
|
321
332
|
|
|
322
333
|
PORT_SPEED_TAG = reg.CORE.tlv["describe"]["port_speed"]
|
|
@@ -1218,10 +1229,19 @@ class Endpoint:
|
|
|
1218
1229
|
return self.speed_rate
|
|
1219
1230
|
return self.port_speed_base or 115200
|
|
1220
1231
|
|
|
1221
|
-
def breaks(self, port: int, size: int, to_host: bool) -> bool:
|
|
1222
|
-
"""The line model: a frame of `size` bytes on `port` at its rate now breaks (`broken_rates`).
|
|
1232
|
+
def breaks(self, port: int, size: int, to_host: bool, duplex: bool = False) -> bool:
|
|
1233
|
+
"""The line model: a frame of `size` bytes on `port` at its rate now breaks (`broken_rates`). duplex: the other
|
|
1234
|
+
way carries a frame of the rate's min_size or more at the same time (a BrokenRate with `duplex` breaks only
|
|
1235
|
+
then)."""
|
|
1236
|
+
b = self.broken_rates.get(self.port_baud(port))
|
|
1237
|
+
if b is None or size < b.min_size or not (b.to_host if to_host else b.to_probe) or (b.duplex and not duplex):
|
|
1238
|
+
return False
|
|
1239
|
+
return b.hit()
|
|
1240
|
+
|
|
1241
|
+
def duplex_rate(self, port: int) -> BrokenRate | None:
|
|
1242
|
+
"""The rate `port` runs at now when it breaks only both ways at once (fake_serial checks the unread answers)."""
|
|
1223
1243
|
b = self.broken_rates.get(self.port_baud(port))
|
|
1224
|
-
return b is not None and
|
|
1244
|
+
return b if b is not None and b.duplex else None
|
|
1225
1245
|
|
|
1226
1246
|
def speed_after_answer(self) -> None:
|
|
1227
1247
|
"""The answer that asked for a switch or a revert is out (at the old speed): now do it."""
|
oep_client/fake_serial.py
CHANGED
|
@@ -17,7 +17,8 @@ bytes to send, or None to send nothing (fault injection for tests).
|
|
|
17
17
|
port_speed (core §3.5): every closed candidate tells the endpoint whether it was a frame (`Endpoint.speed_frame`), a
|
|
18
18
|
switch or revert a request asked for happens once its answer is queued (at the old speed), and the endpoint's
|
|
19
19
|
`broken_rates` break frames at the port's rate now: a candidate from the host is then not a frame, an answer or push
|
|
20
|
-
goes out with a spoiled CRC
|
|
20
|
+
goes out with a spoiled CRC (a `duplex` one only while a request of its size comes in with such an answer still
|
|
21
|
+
unread: the request, or that answer, breaks). `FakeSerialStream` is the port as a pyserial-shaped stream for an in-process host, with
|
|
21
22
|
the host's own `baudrate`: while it differs from the probe's, every byte either way arrives garbled.
|
|
22
23
|
"""
|
|
23
24
|
|
|
@@ -40,6 +41,7 @@ class FakeSerialPort:
|
|
|
40
41
|
self.frames: list[bytes] = [] # framed answers waiting to go out
|
|
41
42
|
self.answers = 0
|
|
42
43
|
self.garble: Callable[[bytes, int], bytes] | None = None # (frame, the rate it goes at) -> what arrives
|
|
44
|
+
self.spoiled: set[int] = set() # the waiting answers a duplex BrokenRate already broke
|
|
43
45
|
|
|
44
46
|
def feed(self, data: bytes) -> None:
|
|
45
47
|
now = self.ep.now()
|
|
@@ -65,8 +67,10 @@ class FakeSerialPort:
|
|
|
65
67
|
self.cand = None
|
|
66
68
|
if not body:
|
|
67
69
|
return # 0x00 0x00: an empty frame
|
|
70
|
+
size = len(body) + 2
|
|
71
|
+
duplex = self._both_ways(size)
|
|
68
72
|
try:
|
|
69
|
-
if self.ep.breaks(self.index,
|
|
73
|
+
if self.ep.breaks(self.index, size, to_host=False, duplex=duplex):
|
|
70
74
|
raise cobs.CorruptFrame("the line's rate breaks this frame")
|
|
71
75
|
msg = cobs.unframe(body)
|
|
72
76
|
except cobs.CorruptFrame:
|
|
@@ -90,6 +94,22 @@ class FakeSerialPort:
|
|
|
90
94
|
self.frames.append(self._line(wire))
|
|
91
95
|
self.ep.speed_after_answer() # port_speed: switch (or revert) now the answer is out
|
|
92
96
|
|
|
97
|
+
def _both_ways(self, size: int) -> bool:
|
|
98
|
+
"""A request of `size` bytes came in: with a BrokenRate that breaks only both ways at once (`duplex`), whether
|
|
99
|
+
an answer of its min_size or more is still unread (both ways busy) - and then that answer breaks if the rate
|
|
100
|
+
breaks frames towards the host."""
|
|
101
|
+
b = self.ep.duplex_rate(self.index)
|
|
102
|
+
if b is None or size < b.min_size:
|
|
103
|
+
return False
|
|
104
|
+
big = [k for k, f in enumerate(self.frames) if len(f) >= b.min_size]
|
|
105
|
+
if not big:
|
|
106
|
+
return False
|
|
107
|
+
k = big[-1] # the answer on the line now (the latest); never spoiled twice
|
|
108
|
+
if k not in self.spoiled and self.ep.breaks(self.index, len(self.frames[k]), to_host=True, duplex=True):
|
|
109
|
+
self.frames[k] = _spoil(self.frames[k])
|
|
110
|
+
self.spoiled.add(k)
|
|
111
|
+
return True
|
|
112
|
+
|
|
93
113
|
def _gap(self, now: int) -> None:
|
|
94
114
|
if self.cand is not None and now - self.last_ms >= GAP_MS:
|
|
95
115
|
raw, self.cand = bytes(self.cand), None
|
|
@@ -109,9 +129,7 @@ class FakeSerialPort:
|
|
|
109
129
|
def _line(self, wire: bytes) -> bytes:
|
|
110
130
|
"""A framed message as the line delivers it at the port's rate now (`Endpoint.broken_rates`, `garble`)."""
|
|
111
131
|
if self.ep.breaks(self.index, len(wire), to_host=True):
|
|
112
|
-
|
|
113
|
-
body[-1] ^= 0xFF # the CRC's high byte
|
|
114
|
-
wire = b"\x00" + cobs.encode(bytes(body)) + b"\x00"
|
|
132
|
+
wire = _spoil(wire)
|
|
115
133
|
return self.garble(wire, self.ep.port_baud(self.index)) if self.garble else wire
|
|
116
134
|
|
|
117
135
|
def output(self, room: int = 4096) -> bytes:
|
|
@@ -119,6 +137,7 @@ class FakeSerialPort:
|
|
|
119
137
|
out = bytearray()
|
|
120
138
|
while self.frames and len(out) + len(self.frames[0]) <= max(room, len(self.frames[0])):
|
|
121
139
|
out += self.frames.pop(0)
|
|
140
|
+
self.spoiled = {k - 1 for k in self.spoiled if k}
|
|
122
141
|
if len(out) >= room:
|
|
123
142
|
return bytes(out)
|
|
124
143
|
while len(out) < room:
|
|
@@ -129,6 +148,16 @@ class FakeSerialPort:
|
|
|
129
148
|
return bytes(out)
|
|
130
149
|
|
|
131
150
|
|
|
151
|
+
def _spoil(wire: bytes) -> bytes:
|
|
152
|
+
"""A framed message with its CRC's high byte spoiled (a frame the line broke); bytes that are no frame as they are."""
|
|
153
|
+
try:
|
|
154
|
+
body = bytearray(cobs.decode(wire[1:-1]))
|
|
155
|
+
except cobs.CorruptFrame:
|
|
156
|
+
return wire
|
|
157
|
+
body[-1] ^= 0xFF
|
|
158
|
+
return b"\x00" + cobs.encode(bytes(body)) + b"\x00"
|
|
159
|
+
|
|
160
|
+
|
|
132
161
|
def _garble(data: bytes) -> bytes:
|
|
133
162
|
"""Bytes sent at one rate and read at another: a different byte for every byte, 0x00s among them (so the reader
|
|
134
163
|
sees broken candidates, not silence)."""
|
oep_client/fake_serve.py
CHANGED
|
@@ -30,9 +30,10 @@ Options:
|
|
|
30
30
|
"DUT RX" labels when the profile has them, else the first free channels); configure then works
|
|
31
31
|
--uart-rx TEXT what arrives on that UART's RX every --every ms once it is configured (%d = a counter)
|
|
32
32
|
--no-port-speed the profile's port_speed (core §3.5; esp32-v003 has it) off: the op is unknown_operation
|
|
33
|
-
--broken-rate SPEC port_speed's line model: frames at RATE break (repeatable). SPEC is
|
|
34
|
-
only frames of MIN_SIZE bytes and more on the wire
|
|
35
|
-
host (in) or the probe (out) (default both)
|
|
33
|
+
--broken-rate SPEC port_speed's line model: frames at RATE break (repeatable). SPEC is
|
|
34
|
+
RATE[:MIN_SIZE][:in|out][:duplex][:everyN]: only frames of MIN_SIZE bytes and more on the wire
|
|
35
|
+
(default every frame), only towards the host (in) or the probe (out) (default both), only
|
|
36
|
+
while both ways carry such frames at once (duplex), only every Nth of them (everyN). The fake cannot see the host's own rate (a pty,
|
|
36
37
|
TCP): only the probe's rate decides
|
|
37
38
|
--run-hook SPEC what riscv-dm run does on every target: SPEC is module:function or path/file.py:function,
|
|
38
39
|
called as function(target, pc, regs) -> (stopped, dpc, elapsed_us). `target` is the
|
|
@@ -83,7 +84,9 @@ def build(a: argparse.Namespace) -> endpoint.Endpoint:
|
|
|
83
84
|
rate, *rest = spec.split(":")
|
|
84
85
|
size = next((int(x) for x in rest if x.isdigit()), 0)
|
|
85
86
|
way = next((x for x in rest if x in ("in", "out")), None)
|
|
86
|
-
|
|
87
|
+
every = next((int(x[5:]) for x in rest if x.startswith("every") and x[5:].isdigit()), 1)
|
|
88
|
+
ep.broken_rates[int(rate)] = endpoint.BrokenRate(size, to_host=way != "out", to_probe=way != "in",
|
|
89
|
+
duplex="duplex" in rest, every=every)
|
|
87
90
|
for n in a.absent:
|
|
88
91
|
ep.targets[(wire, ep.pairs[wire][n])].present = False
|
|
89
92
|
items = []
|
oep_client/host.py
CHANGED
|
@@ -15,6 +15,7 @@ opens again (host guide §2.5).
|
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
|
+
import contextlib
|
|
18
19
|
import random
|
|
19
20
|
import struct
|
|
20
21
|
from dataclasses import dataclass, field
|
|
@@ -194,14 +195,32 @@ class Host:
|
|
|
194
195
|
_revisions: dict = field(default_factory=dict) # fn -> interface revision from list
|
|
195
196
|
_describes: dict = field(default_factory=dict) # fn -> its describe TLVs (declarations: valid for one boot_id)
|
|
196
197
|
_boot_id: int | None = None
|
|
198
|
+
expect_ms: int = 0 # how long the request going out now may take on the probe (`expecting`)
|
|
199
|
+
# Called before a request takes its corr (the link's keepalive at a raised port_speed rate): what it sends first
|
|
200
|
+
# must carry the lower corr, or the probe takes the request for an old one (core §4.1)
|
|
201
|
+
before_request: Callable[[], None] | None = None
|
|
202
|
+
|
|
203
|
+
@contextlib.contextmanager
|
|
204
|
+
def expecting(self, ms: int):
|
|
205
|
+
"""Requests sent inside take up to `ms` on the probe (a run's timeout_ms, a dmi list's waits, an attach's
|
|
206
|
+
hold_ms, a capture's blocking, a save; core §6.1: the probe does not count the lease meanwhile). The link waits
|
|
207
|
+
at least that and a margin for each answer - also at a raised port_speed rate, where an ordinary request waits
|
|
208
|
+
a quarter of the lease. Nested: the longest holds."""
|
|
209
|
+
saved = self.expect_ms
|
|
210
|
+
self.expect_ms = max(saved, int(ms or 0))
|
|
211
|
+
try:
|
|
212
|
+
yield
|
|
213
|
+
finally:
|
|
214
|
+
self.expect_ms = saved
|
|
197
215
|
|
|
198
216
|
def next_corr(self) -> int:
|
|
199
217
|
self._corr = self._corr % 0xFFFF + 1
|
|
200
218
|
return self._corr
|
|
201
219
|
|
|
202
|
-
def call(self, fn: int, op: int, payload: bytes = b"", *, locked: bool = True) -> m.Result:
|
|
220
|
+
def call(self, fn: int, op: int, payload: bytes = b"", *, locked: bool = True, expect_ms: int = 0) -> m.Result:
|
|
203
221
|
"""request() that also raises Failed unless the probe says it worked: what every operation wants."""
|
|
204
|
-
|
|
222
|
+
with self.expecting(expect_ms):
|
|
223
|
+
r = self.request(fn, op, payload, locked=locked)
|
|
205
224
|
if not r.succeeded:
|
|
206
225
|
raise Failed(r)
|
|
207
226
|
return r
|
|
@@ -212,11 +231,15 @@ class Host:
|
|
|
212
231
|
self.require_v1()
|
|
213
232
|
return self.session
|
|
214
233
|
|
|
215
|
-
def request(self, fn: int, op: int, payload: bytes = b"", *, locked: bool = True) -> m.Result:
|
|
234
|
+
def request(self, fn: int, op: int, payload: bytes = b"", *, locked: bool = True, expect_ms: int = 0) -> m.Result:
|
|
216
235
|
"""locked=True sends the session id (role 0x81) once a session is open; lock-free requests may leave it off.
|
|
217
|
-
|
|
236
|
+
expect_ms: how long this request may take on the probe (`expecting`). Rejections raise; any other answer is
|
|
237
|
+
returned (Result.succeeded / .ran say what it was)."""
|
|
238
|
+
if self.before_request is not None:
|
|
239
|
+
self.before_request()
|
|
218
240
|
req = m.Request(self.next_corr(), fn, op, payload, self._session_for(locked))
|
|
219
|
-
|
|
241
|
+
with self.expecting(expect_ms):
|
|
242
|
+
result = m.Result.unpack(self.send(req.pack()))
|
|
220
243
|
if result.corr != req.corr:
|
|
221
244
|
raise ProtocolError(f"result for correlation {result.corr}, expected {req.corr}")
|
|
222
245
|
if result.resolution == m.REJECTED:
|
|
@@ -253,6 +276,8 @@ class Host:
|
|
|
253
276
|
"""Several requests in flight (`exchange` keeps the probe's in-flight and window limits); results in
|
|
254
277
|
order, rejects NOT raised - the caller looks at each result. Without `exchange`, one at a time."""
|
|
255
278
|
session = self._session_for(locked)
|
|
279
|
+
if self.before_request is not None:
|
|
280
|
+
self.before_request()
|
|
256
281
|
reqs = [m.Request(self.next_corr(), fn, op, payload, session) for fn, op, payload in requests]
|
|
257
282
|
packed = [r.pack() for r in reqs]
|
|
258
283
|
exchange = exchange or self.exchange
|
oep_client/link.py
CHANGED
|
@@ -23,9 +23,12 @@ throughput of a UART bridge, oep-spec docs/uart-speed-negotiation.ja.md §3b), w
|
|
|
23
23
|
port_speed (oep-core §3.5, optional, opt-in): `raise_speed` (or `open_host(..., port_speed=[rates])`) asks a probe that
|
|
24
24
|
declares it for faster rates on the UART bridge this host opened: try a rate, switch the port, verify it with a sized
|
|
25
25
|
transfer both ways (link_source / link_sink with max_frame-sized frames) that counts broken frames and measures the
|
|
26
|
-
throughput, then commit it, or revert and wait the probe out and re-confirm at the boot speed. The report stays on the
|
|
26
|
+
throughput, then both ways at once (the two interleaved, pipelined as the in/out passes did), then commit it, or revert and wait the probe out and re-confirm at the boot speed. The report stays on the
|
|
27
27
|
link (`link.speed`). Once raised, a request whose answer never comes even after its resend sends the link back to the
|
|
28
|
-
boot speed (the probe reverts by itself) and goes once more there: the link never wedges at a rate the probe left
|
|
28
|
+
boot speed (the probe reverts by itself) and goes once more there: the link never wedges at a rate the probe left;
|
|
29
|
+
while raised each wait is at most a quarter of the lease, so this ends inside it. In use, STRIKE_MAX (3) broken frames
|
|
30
|
+
or resends within STRIKE_WINDOW_S (5 s) step down: port_speed revert at the raised rate, the boot speed, a confirm.
|
|
31
|
+
Either way the rate is not used again in that session (`link.speed.stepped_down`, `down_why`).
|
|
29
32
|
The probe also goes back after port_speed_idle_max_ms (3 s) with no good frame, so while raised the link sends a
|
|
30
33
|
keepalive before a request when it has been quiet for 1 s, and `keep_alive()` does the same for a caller that sits
|
|
31
34
|
idle for long. A host opening a serial port retries its first confirm for that maximum and a little (4 s in all): a
|
|
@@ -63,6 +66,11 @@ IDLE_MAX_MS = reg.TIMING["port_speed_idle_max_ms"] # a committed rate goes bac
|
|
|
63
66
|
KEEPALIVE_S = 1.0 # raised: a keepalive once the link has been quiet this long (well inside IDLE_MAX_MS)
|
|
64
67
|
OPEN_RETRY_S = IDLE_MAX_MS / 1000 + 1.0 # opening a serial port: confirm retried this long (a raised rate left over)
|
|
65
68
|
OPEN_TRY_S = 0.5 # each of those confirms waits this long (at most the link's timeout)
|
|
69
|
+
STRIKE_MAX, STRIKE_WINDOW_S = 3, 5.0 # raised, in use: this many broken frames / resends within the window step down
|
|
70
|
+
STEP_DOWN_WAIT_S = 0.2 # the step down's revert (step 2) at the raised rate waits this long, never sent again
|
|
71
|
+
RAISED_WAIT_MIN_S = 0.3 # raised, in use: each wait for an answer is a quarter of the lease, at least this
|
|
72
|
+
EXPECT_MARGIN_S = 0.5 # a request that may take long on the probe (Host.expecting): waited that long and this
|
|
73
|
+
LINK_ERRORS = (cobs.CorruptFrame, TimeoutError, FramingLost)
|
|
66
74
|
|
|
67
75
|
|
|
68
76
|
def open_serial(port: str, baud: int = BASE_BAUD):
|
|
@@ -164,7 +172,16 @@ class SerialLink:
|
|
|
164
172
|
self.baud = self.base_baud # the rate the host side runs at now
|
|
165
173
|
self.speed: SpeedReport | None = None # port_speed: the last raise_speed's report (rate in force, KB/s)
|
|
166
174
|
self.speed_lost = 0 # times a raised rate was found gone (back to the boot speed)
|
|
167
|
-
self.fallback = True # a raised rate
|
|
175
|
+
self.fallback = True # a raised rate in use (not raise_speed's own trial): fall back
|
|
176
|
+
self.strikes: list[float] = [] # raised, in use: when frames broke / requests went again (window)
|
|
177
|
+
self.step_due = "" # raised, in use: why the link steps down at the next safe point
|
|
178
|
+
self.speed_port: int | None = None # the transport index the raised rate is on (the revert names it)
|
|
179
|
+
self.unusable: dict[int, str] = {} # rates stepped down from in this session -> why (not tried again)
|
|
180
|
+
self.unusable_session: int | None = None # the session `unusable` belongs to
|
|
181
|
+
self.session_frame = None # (op, payload) -> a core request in the session, once bound
|
|
182
|
+
self.lease_s = lambda: None # the session's lease, once bound (raised: bounds every wait)
|
|
183
|
+
self.expected_s = lambda: 0.0 # how long the request going out may take on the probe (Host.expecting)
|
|
184
|
+
self._own = 0 # >0: the link's own short requests (confirms, keepalive, revert)
|
|
168
185
|
self.corr_source = self._own_corr # the host's correlation counter once bound (open_host)
|
|
169
186
|
self.keepalive_frame = None # raised: a keepalive request in the session, once bound (attach_host)
|
|
170
187
|
self.last_tx = time.monotonic() # when the link last wrote (raised: quiet for KEEPALIVE_S = keepalive)
|
|
@@ -192,11 +209,11 @@ class SerialLink:
|
|
|
192
209
|
|
|
193
210
|
def _recv(self) -> bytes:
|
|
194
211
|
if self.framing == "length":
|
|
195
|
-
reply = self.frames.recv(self.
|
|
212
|
+
reply = self.frames.recv(self._wait())
|
|
196
213
|
if reply is None:
|
|
197
214
|
raise TimeoutError("no result from the probe")
|
|
198
215
|
return reply
|
|
199
|
-
deadline = time.monotonic() + self.
|
|
216
|
+
deadline = time.monotonic() + self._wait()
|
|
200
217
|
while True:
|
|
201
218
|
end = self._buf.find(0)
|
|
202
219
|
if end >= 0:
|
|
@@ -259,6 +276,7 @@ class SerialLink:
|
|
|
259
276
|
saved = self.timeout
|
|
260
277
|
deadline = time.monotonic() + timeout
|
|
261
278
|
n = 0
|
|
279
|
+
self._own += 1 # its reads wait what it says, whatever a caller expects
|
|
262
280
|
try:
|
|
263
281
|
while True:
|
|
264
282
|
if self._raised(): # a long pump at a raised rate keeps the line alive (core §3.5)
|
|
@@ -277,8 +295,9 @@ class SerialLink:
|
|
|
277
295
|
self.timeout = saved
|
|
278
296
|
self.resync()
|
|
279
297
|
return n
|
|
280
|
-
except cobs.CorruptFrame:
|
|
298
|
+
except cobs.CorruptFrame as e:
|
|
281
299
|
self.corrupt += 1
|
|
300
|
+
self._strike(e)
|
|
282
301
|
continue
|
|
283
302
|
n += 1
|
|
284
303
|
if not self._route(frame):
|
|
@@ -287,6 +306,8 @@ class SerialLink:
|
|
|
287
306
|
return n
|
|
288
307
|
finally:
|
|
289
308
|
self.timeout = saved
|
|
309
|
+
self._own -= 1
|
|
310
|
+
self._step_down_if_due()
|
|
290
311
|
|
|
291
312
|
def resync(self, tries: int = 3) -> None:
|
|
292
313
|
"""oep-core §5.1: read and discard until the input is quiet for 50 ms, then prove the link with a confirm (a read,
|
|
@@ -328,14 +349,15 @@ class SerialLink:
|
|
|
328
349
|
self._keep_raised()
|
|
329
350
|
try:
|
|
330
351
|
reply = self._send(message)
|
|
331
|
-
except
|
|
332
|
-
if not self._speed_fallback():
|
|
352
|
+
except LINK_ERRORS as e:
|
|
353
|
+
if not self._speed_fallback(e):
|
|
333
354
|
raise
|
|
334
355
|
reply = self._send(message) # once more at the boot speed (the probe answers a repeat from what it kept)
|
|
335
356
|
if self.baud != self.base_baud and self._reverts(message, reply):
|
|
336
357
|
self.set_baud(self.base_baud) # the probe went back right after this answer (core §3.5)
|
|
337
358
|
if self.speed is not None:
|
|
338
359
|
self.speed.rate, self.speed.chosen = self.base_baud, None
|
|
360
|
+
self._step_down_if_due()
|
|
339
361
|
return reply
|
|
340
362
|
|
|
341
363
|
@staticmethod
|
|
@@ -358,6 +380,7 @@ class SerialLink:
|
|
|
358
380
|
except (cobs.CorruptFrame, TimeoutError, FramingLost) as e:
|
|
359
381
|
if isinstance(e, cobs.CorruptFrame):
|
|
360
382
|
self.corrupt += 1
|
|
383
|
+
self._strike(e)
|
|
361
384
|
self._recover()
|
|
362
385
|
if attempt or not self.resend or (message[0] & m.ROLE_SESSION and self.ended_blind):
|
|
363
386
|
raise
|
|
@@ -377,20 +400,24 @@ class SerialLink:
|
|
|
377
400
|
self._keep_raised()
|
|
378
401
|
replies: list[bytes] = []
|
|
379
402
|
try:
|
|
380
|
-
|
|
381
|
-
except
|
|
403
|
+
out = self._exchange_once(messages, max_inflight, window_bytes, replies)
|
|
404
|
+
except LINK_ERRORS as e:
|
|
405
|
+
self._strike(e)
|
|
382
406
|
if self.ended_blind or not self.resend:
|
|
383
407
|
raise
|
|
384
408
|
self.retries += 1
|
|
385
409
|
rest = messages[len(replies):]
|
|
386
410
|
more: list[bytes] = []
|
|
387
411
|
try:
|
|
388
|
-
|
|
389
|
-
except
|
|
390
|
-
|
|
412
|
+
out = replies + self._exchange_once(rest, max_inflight, window_bytes, more)
|
|
413
|
+
except LINK_ERRORS as e2:
|
|
414
|
+
self._strike(e2)
|
|
415
|
+
if not self._speed_fallback(e2):
|
|
391
416
|
raise
|
|
392
417
|
rest = rest[len(more):]
|
|
393
|
-
|
|
418
|
+
out = replies + more + self._exchange_once(rest, max_inflight, window_bytes, [])
|
|
419
|
+
self._step_down_if_due()
|
|
420
|
+
return out
|
|
394
421
|
|
|
395
422
|
# ---- port_speed (core §3.5) ------------------------------------------------------------------
|
|
396
423
|
def set_baud(self, rate: int) -> None:
|
|
@@ -409,6 +436,7 @@ class SerialLink:
|
|
|
409
436
|
self._write([m.Request(corr, m.CORE_FN, m.OP_CONFIRM, m.CONFIRM_REQUEST + bytes([0, 0xFF])).pack()])
|
|
410
437
|
saved = self.timeout
|
|
411
438
|
self.timeout = timeout
|
|
439
|
+
self._own += 1
|
|
412
440
|
try:
|
|
413
441
|
self._recv_for(corr)
|
|
414
442
|
return True
|
|
@@ -416,6 +444,7 @@ class SerialLink:
|
|
|
416
444
|
return False
|
|
417
445
|
finally:
|
|
418
446
|
self.timeout = saved
|
|
447
|
+
self._own -= 1
|
|
419
448
|
|
|
420
449
|
def _confirm_within(self, wait_s: float, each_s: float) -> bool:
|
|
421
450
|
"""Confirms, each waiting `each_s`, until one is answered (True) or `wait_s` has passed (False)."""
|
|
@@ -449,7 +478,11 @@ class SerialLink:
|
|
|
449
478
|
if time.monotonic() - self.last_tx < KEEPALIVE_S:
|
|
450
479
|
return False
|
|
451
480
|
self.last_tx = time.monotonic() # before sending: send() asks again and must not recurse
|
|
452
|
-
self.
|
|
481
|
+
self._own += 1
|
|
482
|
+
try:
|
|
483
|
+
self.send(self.keepalive_frame())
|
|
484
|
+
finally:
|
|
485
|
+
self._own -= 1
|
|
453
486
|
return True
|
|
454
487
|
|
|
455
488
|
def _keep_raised(self) -> None:
|
|
@@ -459,18 +492,89 @@ class SerialLink:
|
|
|
459
492
|
except (TimeoutError, cobs.CorruptFrame, FramingLost):
|
|
460
493
|
pass
|
|
461
494
|
|
|
462
|
-
def
|
|
463
|
-
"""A
|
|
464
|
-
|
|
465
|
-
|
|
495
|
+
def _in_use(self) -> bool:
|
|
496
|
+
"""A raised rate in force outside raise_speed's own trial (where every failure is handled there)."""
|
|
497
|
+
return self.fallback and self._raised()
|
|
498
|
+
|
|
499
|
+
def _wait(self) -> float:
|
|
500
|
+
"""How long one answer is waited for: the link's timeout; raised and in use, at most a quarter of the
|
|
501
|
+
session's lease (at least RAISED_WAIT_MIN_S) - a probe that went back by itself (broken candidates, core §3.5
|
|
502
|
+
item 5) hears nothing at the raised rate, and the fallback (both waits, the confirm at the boot speed, the
|
|
503
|
+
request again there) must end well inside the lease. A request that may take longer on the probe
|
|
504
|
+
(Host.expecting: a run's timeout_ms, a dmi list's waits, ...; the probe does not count the lease meanwhile,
|
|
505
|
+
core §6.1) waits at least that and EXPECT_MARGIN_S, at any rate; the link's own requests never do."""
|
|
506
|
+
wait = self.timeout
|
|
507
|
+
if self._in_use():
|
|
508
|
+
lease = self.lease_s()
|
|
509
|
+
if lease is not None:
|
|
510
|
+
wait = min(self.timeout, max(RAISED_WAIT_MIN_S, lease / 4))
|
|
511
|
+
expected = 0.0 if self._own else self.expected_s()
|
|
512
|
+
return max(wait, expected + EXPECT_MARGIN_S) if expected > 0 else wait
|
|
513
|
+
|
|
514
|
+
def _strike(self, e: Exception) -> None:
|
|
515
|
+
"""Raised and in use: a frame broke or a request is sent again (`e`). STRIKE_MAX within STRIKE_WINDOW_S: the
|
|
516
|
+
link steps down at the next safe point (core §3.5 item 7)."""
|
|
517
|
+
if not self._in_use():
|
|
518
|
+
return
|
|
519
|
+
now = time.monotonic()
|
|
520
|
+
self.strikes = [t for t in self.strikes if now - t < STRIKE_WINDOW_S] + [now]
|
|
521
|
+
if len(self.strikes) >= STRIKE_MAX and not self.step_due:
|
|
522
|
+
self.step_due = (f"{len(self.strikes)} broken frames / resends within {STRIKE_WINDOW_S:g} s at {self.baud} "
|
|
523
|
+
f"(the last: {type(e).__name__})")
|
|
524
|
+
|
|
525
|
+
def _step_down_if_due(self) -> None:
|
|
526
|
+
if self.step_due and self._in_use():
|
|
527
|
+
self._step_down(self.step_due)
|
|
528
|
+
|
|
529
|
+
def _step_down(self, why: str) -> None:
|
|
530
|
+
"""Leave the raised rate for the rest of the session: port_speed step 2 at it (STEP_DOWN_WAIT_S, never sent
|
|
531
|
+
again: a probe that already went back cannot hear it, and a committed one reverts at the broken candidates
|
|
532
|
+
the confirms make), the host at the boot speed, a confirm there. ConnectionError when none is answered."""
|
|
533
|
+
rate = self.baud
|
|
534
|
+
self.step_due = ""
|
|
535
|
+
self.strikes.clear()
|
|
536
|
+
if self.session_frame is not None and self.speed_port is not None:
|
|
537
|
+
saved = self.timeout, self.resend, self.fallback
|
|
538
|
+
self.timeout, self.resend, self.fallback = STEP_DOWN_WAIT_S, False, False
|
|
539
|
+
self._own += 1
|
|
540
|
+
try:
|
|
541
|
+
self._send(self.session_frame(OP_PORT_SPEED, struct.pack("<BIBHI", self.speed_port, 0,
|
|
542
|
+
SPEED_STEP["revert"], 0, 0)))
|
|
543
|
+
except LINK_ERRORS:
|
|
544
|
+
pass
|
|
545
|
+
finally:
|
|
546
|
+
self.timeout, self.resend, self.fallback = saved
|
|
547
|
+
self._own -= 1
|
|
548
|
+
if not self.back_to_base():
|
|
549
|
+
raise ConnectionError(f"the probe answers neither at {rate} nor at the boot speed {self.base_baud}")
|
|
550
|
+
self._stepped(rate, why)
|
|
551
|
+
|
|
552
|
+
def _stepped(self, rate: int, why: str) -> None:
|
|
553
|
+
"""`rate` is not used again in this session (raise_speed skips it), and the report says so."""
|
|
554
|
+
self.unusable[rate] = why
|
|
555
|
+
if self.speed is not None:
|
|
556
|
+
self.speed.rate, self.speed.chosen = self.base_baud, None
|
|
557
|
+
self.speed.stepped_down, self.speed.down_why = True, why
|
|
558
|
+
|
|
559
|
+
def _speed_fallback(self, e: Exception | None = None) -> bool:
|
|
560
|
+
"""A request failed (its resend too) while a raised rate was in use. Broken frames (the probe still answers
|
|
561
|
+
at that rate) or a step down already due: step down (`_step_down`). No answer at all: the probe went back
|
|
562
|
+
(idle_ms, broken candidates, a lapse) - back to the boot speed, confirmed. Either way the rate is not used
|
|
563
|
+
again in this session. True = send again there."""
|
|
564
|
+
if not self._in_use() or self.base_baud is None:
|
|
466
565
|
return False
|
|
566
|
+
rate = self.baud
|
|
567
|
+
if self.step_due or not isinstance(e, TimeoutError):
|
|
568
|
+
self._step_down(self.step_due or f"frames kept breaking at {rate} ({type(e).__name__})")
|
|
569
|
+
return True
|
|
467
570
|
if not self.back_to_base():
|
|
468
571
|
raise ConnectionError(f"the probe answers neither at {self.baud} nor at the boot speed {self.base_baud}")
|
|
469
572
|
self.speed_lost += 1
|
|
573
|
+
self.step_due = ""
|
|
574
|
+
self.strikes.clear()
|
|
470
575
|
if self.speed is not None:
|
|
471
|
-
self.speed.rate = self.base_baud
|
|
472
|
-
self.speed.chosen = None
|
|
473
576
|
self.speed.lost = True
|
|
577
|
+
self._stepped(rate, f"no answer at {rate} (the probe went back by itself)")
|
|
474
578
|
return True
|
|
475
579
|
|
|
476
580
|
def _exchange_once(self, messages: list[bytes], max_inflight: int, window_bytes: int,
|
|
@@ -509,7 +613,11 @@ class SerialLink:
|
|
|
509
613
|
self.corr_source = hst.next_corr
|
|
510
614
|
self.blind = hst.blind_stop
|
|
511
615
|
self.held = lambda: hst.session is not None
|
|
512
|
-
self.
|
|
616
|
+
self.session_frame = lambda op, payload: m.Request(hst.next_corr(), m.CORE_FN, op, payload, hst.session).pack()
|
|
617
|
+
self.keepalive_frame = lambda: self.session_frame(m.OP_KEEPALIVE, b"")
|
|
618
|
+
self.lease_s = lambda: hst.lease_ms / 1000 if hst.session is not None and hst.lease_ms else None
|
|
619
|
+
hst.before_request = self._keep_raised # the keepalive before the request's corr is taken (core §4.1)
|
|
620
|
+
self.expected_s = lambda: hst.expect_ms / 1000
|
|
513
621
|
hst.link = self
|
|
514
622
|
if self.framing == "cobs" and getattr(self, "transport", None) == "serial":
|
|
515
623
|
self.wait_boot_speed()
|
|
@@ -670,7 +778,7 @@ UART_BRIDGE = reg.CORE.enum["transport_kind"]["uart_bridge"]
|
|
|
670
778
|
@dataclass
|
|
671
779
|
class SpeedTrial:
|
|
672
780
|
"""One rate tried: what the probe said it runs at, the verify's bytes, KB/s (1000 B/s) and broken frames each way
|
|
673
|
-
(in = probe to host, link_source; out = host to probe, link_sink), and whether it was committed (why not)."""
|
|
781
|
+
(in = probe to host, link_source; out = host to probe, link_sink; duplex = both at once), and whether it was committed (why not)."""
|
|
674
782
|
rate: int
|
|
675
783
|
actual: int | None = None
|
|
676
784
|
in_bytes: int = 0
|
|
@@ -679,6 +787,9 @@ class SpeedTrial:
|
|
|
679
787
|
out_kb_s: float | None = None
|
|
680
788
|
broken_in: int = 0
|
|
681
789
|
broken_out: int = 0
|
|
790
|
+
duplex_bytes: int = 0 # both ways at once (link_source and link_sink interleaved), at the in-flight that passed
|
|
791
|
+
duplex_kb_s: float | None = None
|
|
792
|
+
broken_duplex: int = 0
|
|
682
793
|
committed: bool = False
|
|
683
794
|
why: str = ""
|
|
684
795
|
inflight: int = 0 # the requests kept in flight the verify passed with (0: none passed)
|
|
@@ -688,8 +799,10 @@ class SpeedTrial:
|
|
|
688
799
|
class SpeedReport:
|
|
689
800
|
"""raise_speed's answer, kept as `link.speed`: the boot speed, the rate in force now (`rate`), the committed one
|
|
690
801
|
(`chosen`, None: the boot speed), every trial in order, and why nothing was tried (`supported` False).
|
|
691
|
-
`lost`: a raised rate was later found gone (the link went back to the boot speed).
|
|
692
|
-
rate
|
|
802
|
+
`lost`: a raised rate was later found gone (the link went back to the boot speed). `stepped_down`: in use, the
|
|
803
|
+
link left the raised rate for the rest of the session (frames kept breaking, or no answer came), `down_why` says
|
|
804
|
+
why. in_kb_s / out_kb_s / duplex_kb_s: the chosen rate's measured throughput (None at the boot speed) - for
|
|
805
|
+
budgeting a transfer."""
|
|
693
806
|
base: int
|
|
694
807
|
supported: bool
|
|
695
808
|
rate: int
|
|
@@ -697,6 +810,8 @@ class SpeedReport:
|
|
|
697
810
|
trials: list[SpeedTrial] = field(default_factory=list)
|
|
698
811
|
why: str = ""
|
|
699
812
|
lost: bool = False
|
|
813
|
+
stepped_down: bool = False # in use, the raised rate was left for the rest of the session (`down_why`)
|
|
814
|
+
down_why: str = ""
|
|
700
815
|
|
|
701
816
|
def _chosen(self) -> SpeedTrial | None:
|
|
702
817
|
return next((t for t in self.trials if t.committed), None) if self.chosen else None
|
|
@@ -711,15 +826,23 @@ class SpeedReport:
|
|
|
711
826
|
t = self._chosen()
|
|
712
827
|
return t.out_kb_s if t else None
|
|
713
828
|
|
|
829
|
+
@property
|
|
830
|
+
def duplex_kb_s(self) -> float | None:
|
|
831
|
+
t = self._chosen()
|
|
832
|
+
return t.duplex_kb_s if t else None
|
|
833
|
+
|
|
714
834
|
def to_text(self) -> str:
|
|
715
835
|
if not self.supported:
|
|
716
836
|
return f"port_speed not supported: {self.why} (stays at {self.rate})\n"
|
|
717
|
-
lines = [f"{'rate':>9} {'actual':>9} {'in KB/s':>8} {'out KB/s':>8} {'
|
|
837
|
+
lines = [f"{'rate':>9} {'actual':>9} {'in KB/s':>8} {'out KB/s':>8} {'duplex KB/s':>11} "
|
|
838
|
+
f"{'broken in/out/duplex':>20} result"]
|
|
718
839
|
for t in self.trials:
|
|
719
|
-
kb = lambda v: f"{v:
|
|
840
|
+
kb = lambda v, w=8: f"{v:{w}.1f}" if v is not None else f"{'-':>{w}}" # noqa: E731
|
|
720
841
|
lines.append(f"{t.rate:>9} {t.actual if t.actual else '-':>9} {kb(t.in_kb_s)} {kb(t.out_kb_s)} "
|
|
721
|
-
f"{f'{t.broken_in}/{t.broken_out}':>
|
|
842
|
+
f"{kb(t.duplex_kb_s, 11)} {f'{t.broken_in}/{t.broken_out}/{t.broken_duplex}':>20} "
|
|
722
843
|
f"{(f'committed (in flight {t.inflight})' if t.committed else t.why)}")
|
|
844
|
+
if self.stepped_down:
|
|
845
|
+
lines.append(f"stepped down: {self.down_why} - the boot speed for the rest of the session")
|
|
723
846
|
lines.append(f"in force: {self.rate}" + (" (raised)" if self.chosen else " (the boot speed)"))
|
|
724
847
|
return "\n".join(lines) + "\n"
|
|
725
848
|
|
|
@@ -737,10 +860,12 @@ def _speed_port(hst) -> tuple[int | None, str]:
|
|
|
737
860
|
|
|
738
861
|
|
|
739
862
|
def _verify(hst, lk: SerialLink, rate: int, trial: SpeedTrial, verify_bytes: int, verify_s: float,
|
|
740
|
-
inflight: int | None = None)
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
863
|
+
inflight: int | None = None, ways: tuple[str, ...] = ("in", "out"), duplex_s: float = 1.0,
|
|
864
|
+
duplex_frames: int = 64) -> bool:
|
|
865
|
+
"""Max_frame-sized frames, pipelined as `inflight` (default: as the probe allows), for each of `ways`: "in"
|
|
866
|
+
(link_source) and "out" (link_sink) up to half of verify_bytes or verify_s each; "duplex" both interleaved
|
|
867
|
+
(link_source, link_sink, ...) for duplex_s and at least duplex_frames, so frames go both ways at once. Stops at
|
|
868
|
+
the first frame that breaks (lost, or its content wrong)."""
|
|
744
869
|
limits = hst.limits or hst.confirm()
|
|
745
870
|
max_frame, window = limits["max_frame"], limits["window"]
|
|
746
871
|
inflight = max(1, inflight or limits["max_inflight"])
|
|
@@ -750,41 +875,48 @@ def _verify(hst, lk: SerialLink, rate: int, trial: SpeedTrial, verify_bytes: int
|
|
|
750
875
|
saved = lk.timeout, lk.resend
|
|
751
876
|
lk.timeout, lk.resend = max(0.3, 4 * wire * inflight + 0.1), False
|
|
752
877
|
pattern = bytes(k & 0xFF for k in range(n_in))
|
|
878
|
+
sink_body = bytes((k * 7) & 0xFF for k in range(n_out))
|
|
879
|
+
|
|
880
|
+
def request(k: int, way: str) -> tuple[bytes, bool]:
|
|
881
|
+
source = way == "in" or (way == "duplex" and k % 2 == 0)
|
|
882
|
+
body, op = (struct.pack("<I", n_in), m.OP_LINK_SOURCE) if source else (sink_body, m.OP_LINK_SINK)
|
|
883
|
+
return m.Request(hst.next_corr(), m.CORE_FN, op, body).pack(), source
|
|
884
|
+
|
|
885
|
+
def more(way: str, moved: int, frames: int, t0: float) -> bool:
|
|
886
|
+
if way == "duplex":
|
|
887
|
+
return time.perf_counter() - t0 < duplex_s or frames < duplex_frames
|
|
888
|
+
return moved < verify_bytes / 2 and time.perf_counter() - t0 < verify_s / 2
|
|
889
|
+
|
|
753
890
|
try:
|
|
754
|
-
for way in
|
|
755
|
-
moved, t0 = 0, time.perf_counter()
|
|
756
|
-
while moved
|
|
757
|
-
|
|
758
|
-
body, op = struct.pack("<I", n_in), m.OP_LINK_SOURCE
|
|
759
|
-
else:
|
|
760
|
-
body, op = bytes((k * 7) & 0xFF for k in range(n_out)), m.OP_LINK_SINK
|
|
761
|
-
msgs = [m.Request(hst.next_corr(), m.CORE_FN, op, body).pack() for _ in range(inflight * 2)]
|
|
891
|
+
for way in ways:
|
|
892
|
+
moved, frames, broken, t0 = 0, 0, 0, time.perf_counter()
|
|
893
|
+
while more(way, moved, frames, t0):
|
|
894
|
+
batch = [request(k, way) for k in range(inflight * 2)]
|
|
762
895
|
replies: list[bytes] = []
|
|
763
896
|
try:
|
|
764
|
-
lk._exchange_once(
|
|
897
|
+
lk._exchange_once([msg for msg, _ in batch], inflight, window, replies)
|
|
765
898
|
except (cobs.CorruptFrame, TimeoutError, FramingLost):
|
|
766
899
|
pass # the frames not answered are the broken ones
|
|
767
900
|
good = 0
|
|
768
|
-
for r in replies:
|
|
901
|
+
for r, (_, source) in zip(replies, batch):
|
|
769
902
|
res = m.Result.unpack(r)
|
|
770
|
-
if not (res.succeeded and (res.payload == pattern if
|
|
903
|
+
if not (res.succeeded and (res.payload == pattern if source
|
|
771
904
|
else res.payload[:4] == struct.pack("<I", n_out))):
|
|
772
905
|
break
|
|
773
906
|
good += 1
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
trial.broken_out += len(msgs) - good
|
|
781
|
-
break
|
|
782
|
-
seconds = max(time.perf_counter() - t0, 1e-6)
|
|
907
|
+
moved += n_in if source else n_out
|
|
908
|
+
frames += good
|
|
909
|
+
if good < len(batch):
|
|
910
|
+
broken = len(batch) - good
|
|
911
|
+
break
|
|
912
|
+
kb_s = moved / max(time.perf_counter() - t0, 1e-6) / 1000
|
|
783
913
|
if way == "in":
|
|
784
|
-
trial.in_bytes, trial.in_kb_s = moved,
|
|
914
|
+
trial.in_bytes, trial.in_kb_s, trial.broken_in = moved, kb_s, broken
|
|
915
|
+
elif way == "out":
|
|
916
|
+
trial.out_bytes, trial.out_kb_s, trial.broken_out = moved, kb_s, broken
|
|
785
917
|
else:
|
|
786
|
-
trial.
|
|
787
|
-
if
|
|
918
|
+
trial.duplex_bytes, trial.duplex_kb_s, trial.broken_duplex = moved, kb_s, broken
|
|
919
|
+
if broken:
|
|
788
920
|
return False
|
|
789
921
|
return True
|
|
790
922
|
finally:
|
|
@@ -795,18 +927,23 @@ SWITCH_SETTLE_S = 0.02 # after a baud change, before the first byte at the new
|
|
|
795
927
|
|
|
796
928
|
|
|
797
929
|
def raise_speed(hst, rates: list[int], *, verify_bytes: int = 32768, verify_s: float = 1.0,
|
|
798
|
-
verify_ms: int | None = None, idle_ms: int = IDLE_MAX_MS, port: int | None = None
|
|
930
|
+
verify_ms: int | None = None, idle_ms: int = IDLE_MAX_MS, port: int | None = None,
|
|
931
|
+
duplex_s: float = 1.0, duplex_frames: int = 64) -> SpeedReport:
|
|
799
932
|
"""port_speed (oep-core §3.5), opt-in: try `rates` in order on the UART bridge this host opened, and commit the
|
|
800
933
|
first that passes. Each: try (answered at the speed now) -> the host switches -> verify both ways with
|
|
801
|
-
max_frame-sized frames for verify_bytes or verify_s in all (broken frames counted, KB/s measured each way)
|
|
802
|
-
|
|
934
|
+
max_frame-sized frames for verify_bytes or verify_s in all (broken frames counted, KB/s measured each way),
|
|
935
|
+
pipelined as the probe allows and, when that breaks, one at a time -> both ways at once at the in-flight that
|
|
936
|
+
passed (link_source and link_sink interleaved, duplex_s and at least duplex_frames; a broken frame fails the rate:
|
|
937
|
+
"broke both ways at once") -> commit at the new speed when nothing broke; else revert (step 2, at the new speed) and back to the boot speed,
|
|
803
938
|
re-confirmed there (waiting out the probe's verify_ms when the revert was lost), and the next rate. A rate the
|
|
804
939
|
probe's UART cannot make is skipped (unsupported). The session must be open (the rate lasts as long as it does).
|
|
805
940
|
|
|
806
941
|
verify_ms: how long the probe waits for the commit (default: verify_s + 1.5 s, at most 65535). idle_ms: once
|
|
807
942
|
committed, the probe reverts after this long with no good frame (default and at most port_speed_idle_max_ms,
|
|
808
|
-
3000; 0 and anything longer mean that maximum). The link keeps the line alive meanwhile (`keep_alive`)
|
|
809
|
-
|
|
943
|
+
3000; 0 and anything longer mean that maximum). The link keeps the line alive meanwhile (`keep_alive`), and
|
|
944
|
+
steps down to the boot speed for the rest of the session when frames break or requests go again STRIKE_MAX times
|
|
945
|
+
within STRIKE_WINDOW_S, or an answer does not come at all (`report.stepped_down`); a rate stepped down from in
|
|
946
|
+
this session is not tried again. port: the transport index (default: the probe's first UART bridge). -> the report, also kept as `hst.link.speed`."""
|
|
810
947
|
lk = getattr(hst, "link", None)
|
|
811
948
|
base = getattr(lk, "base_baud", None)
|
|
812
949
|
report = SpeedReport(base or 0, False, getattr(lk, "baud", None) or 0)
|
|
@@ -824,19 +961,26 @@ def raise_speed(hst, rates: list[int], *, verify_bytes: int = 32768, verify_s: f
|
|
|
824
961
|
raise _host.OepError("raise_speed needs an open session (the rate lasts as long as the session)")
|
|
825
962
|
port = where if port is None else port
|
|
826
963
|
report.supported = True
|
|
827
|
-
wait = (verify_ms if verify_ms is not None
|
|
964
|
+
wait = (verify_ms if verify_ms is not None
|
|
965
|
+
else min(65535, int((verify_s + duplex_s) * 1000) + 1500))
|
|
966
|
+
if lk.unusable_session != hst.session:
|
|
967
|
+
lk.unusable, lk.unusable_session = {}, hst.session
|
|
828
968
|
lk.fallback = False # every failure here is handled here
|
|
829
969
|
idle_ms = idle_ms if 0 < idle_ms <= IDLE_MAX_MS else IDLE_MAX_MS
|
|
830
970
|
try:
|
|
831
|
-
return _raise(hst, lk, report, rates, port, base, wait, verify_bytes, verify_s, idle_ms)
|
|
971
|
+
return _raise(hst, lk, report, rates, port, base, wait, verify_bytes, verify_s, idle_ms, duplex_s, duplex_frames)
|
|
832
972
|
finally:
|
|
833
973
|
lk.fallback = True
|
|
834
974
|
|
|
835
975
|
|
|
836
|
-
def _raise(hst, lk, report, rates, port, base, wait, verify_bytes, verify_s, idle_ms
|
|
976
|
+
def _raise(hst, lk, report, rates, port, base, wait, verify_bytes, verify_s, idle_ms, duplex_s,
|
|
977
|
+
duplex_frames) -> SpeedReport:
|
|
837
978
|
for rate in rates:
|
|
838
979
|
trial = SpeedTrial(rate)
|
|
839
980
|
report.trials.append(trial)
|
|
981
|
+
if rate in lk.unusable:
|
|
982
|
+
trial.why = f"stepped down from earlier in this session ({lk.unusable[rate]})"
|
|
983
|
+
continue
|
|
840
984
|
try:
|
|
841
985
|
r = hst.call(m.CORE_FN, OP_PORT_SPEED, struct.pack("<BIBHI", port, rate, SPEED_STEP["try"], wait, 0))
|
|
842
986
|
except TimeoutError:
|
|
@@ -863,24 +1007,35 @@ def _raise(hst, lk, report, rates, port, base, wait, verify_bytes, verify_s, idl
|
|
|
863
1007
|
# 500 kbaud and up behind usbip, 2026-10-01) gets a second verify with one request at a time
|
|
864
1008
|
full = max(1, (hst.limits or hst.confirm())["max_inflight"])
|
|
865
1009
|
tries = [full] if full == 1 else [full, 1]
|
|
1010
|
+
broke = "frames broke"
|
|
866
1011
|
for n in tries if ok else []:
|
|
867
|
-
trial.broken_in = trial.broken_out = 0
|
|
868
1012
|
ok = _verify(hst, lk, rate, trial, verify_bytes, verify_s, n)
|
|
869
1013
|
if ok:
|
|
870
1014
|
trial.inflight = n
|
|
871
1015
|
break
|
|
872
1016
|
any(lk.confirm_raw(0.2) for _ in range(3)) # the broken frames' leftovers read past
|
|
1017
|
+
if ok:
|
|
1018
|
+
# both ways at once at the in-flight that passed: a line that carries each way alone can still break
|
|
1019
|
+
# under sustained duplex use (a CH340 at 921600 broke a frame every ~0.5 s, 2026-10-01)
|
|
1020
|
+
ok = _verify(hst, lk, rate, trial, verify_bytes, verify_s, trial.inflight, ("duplex",), duplex_s,
|
|
1021
|
+
duplex_frames)
|
|
1022
|
+
if not ok:
|
|
1023
|
+
broke, trial.inflight = "broke both ways at once", 0
|
|
1024
|
+
any(lk.confirm_raw(0.2) for _ in range(3))
|
|
873
1025
|
if ok:
|
|
874
1026
|
try:
|
|
875
1027
|
hst.call(m.CORE_FN, OP_PORT_SPEED, struct.pack("<BIBHI", port, rate, SPEED_STEP["commit"], 0, idle_ms))
|
|
876
1028
|
trial.committed = True
|
|
877
1029
|
report.rate, report.chosen = rate, rate
|
|
878
1030
|
lk.inflight_cap = trial.inflight if trial.inflight < full else 0
|
|
1031
|
+
lk.speed_port = port
|
|
1032
|
+
lk.strikes.clear()
|
|
1033
|
+
lk.step_due = ""
|
|
879
1034
|
return report
|
|
880
1035
|
except (TimeoutError, _host.Rejected) as e:
|
|
881
1036
|
trial.why = f"the commit failed: {e}"
|
|
882
1037
|
else:
|
|
883
|
-
trial.why =
|
|
1038
|
+
trial.why = broke if heard else "no confirm at the new rate"
|
|
884
1039
|
saved = lk.timeout, lk.resend
|
|
885
1040
|
lk.timeout, lk.resend = 0.3, False
|
|
886
1041
|
try:
|
oep_client/riscv.py
CHANGED
|
@@ -238,7 +238,8 @@ class Wire(WireBase):
|
|
|
238
238
|
low for hold_ms, then attach - halting before the first instruction with halt=True - the way back from firmware
|
|
239
239
|
that turns the debug pins into GPIOs. self.target_id: (scheme, value) of the target's identity when the probe
|
|
240
240
|
could read one."""
|
|
241
|
-
rd = m.Reader(self._call(self.ATTACH, self.attach_body(halt, max_speed, pins, idle_clock, reset)
|
|
241
|
+
rd = m.Reader(self._call(self.ATTACH, self.attach_body(halt, max_speed, pins, idle_clock, reset),
|
|
242
|
+
expect_ms=reset[1] if reset else 0).payload)
|
|
242
243
|
conn, status, self.flags, self.speed_hz = rd.take("HIBI")
|
|
243
244
|
self.had_reset = bool(self.flags & self.FLAGS["havereset_acked"])
|
|
244
245
|
self.existing = bool(self.flags & self.FLAGS["existing"])
|
|
@@ -336,6 +337,22 @@ def count_steps(steps: bytes) -> list[int]:
|
|
|
336
337
|
return kinds
|
|
337
338
|
|
|
338
339
|
|
|
340
|
+
def dmi_wait_ms(raw: bytes) -> int:
|
|
341
|
+
"""The time a step list may take by its waits and time-bounded polls (wait_us, poll_us), in ms (rounded up)."""
|
|
342
|
+
us, at = 0, 0
|
|
343
|
+
while at < len(raw):
|
|
344
|
+
kind = raw[at]
|
|
345
|
+
if kind == STEP_WAIT_US:
|
|
346
|
+
us += struct.unpack_from("<I", raw, at + 1)[0]
|
|
347
|
+
elif kind == STEP_POLL_US:
|
|
348
|
+
us += struct.unpack_from("<I", raw, at + 10)[0]
|
|
349
|
+
size = STEP_SIZES.get(kind)
|
|
350
|
+
if size is None:
|
|
351
|
+
break
|
|
352
|
+
at += size
|
|
353
|
+
return -(-us // 1000)
|
|
354
|
+
|
|
355
|
+
|
|
339
356
|
def dmi_value_count(kinds: list[int], done: int, status: int) -> int:
|
|
340
357
|
"""§5.5: the reads and polls among the first `done` steps, plus the failed step's last value when it is a poll
|
|
341
358
|
that timed out (a poll whose read failed on the line adds nothing)."""
|
|
@@ -483,7 +500,7 @@ class RiscvDm(Interface):
|
|
|
483
500
|
it). timeout_ms None: the probe's max_op_ms (core §7.5), the most it allows. Other statuses raise TargetError."""
|
|
484
501
|
if timeout_ms is None:
|
|
485
502
|
timeout_ms = max_op_ms(self.host)
|
|
486
|
-
r = self._request(self.RUN, self.run_body(pc, regs, timeout_ms, outs))
|
|
503
|
+
r = self._request(self.RUN, self.run_body(pc, regs, timeout_ms, outs), expect_ms=timeout_ms)
|
|
487
504
|
res = self.run_result(r)
|
|
488
505
|
if res.status == STATUS["timeout"] and not res.stopped:
|
|
489
506
|
return res
|
|
@@ -496,7 +513,7 @@ class RiscvDm(Interface):
|
|
|
496
513
|
raises StepListError (with what it did read): a caller cannot mistake an unfinished poll for a met one."""
|
|
497
514
|
raw = b"".join(steps) if isinstance(steps, list) else steps
|
|
498
515
|
kinds = count_steps(raw)
|
|
499
|
-
r = self._request(self.DMI, struct.pack("<H", len(kinds)) + raw)
|
|
516
|
+
r = self._request(self.DMI, struct.pack("<H", len(kinds)) + raw, expect_ms=dmi_wait_ms(raw))
|
|
500
517
|
rd = ran(r)
|
|
501
518
|
done, status, nvals = rd.take("HBH")
|
|
502
519
|
values = rd.words(nvals)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: oep-client-python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.26
|
|
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
|
|
@@ -122,7 +122,9 @@ print(hst.link.speed.to_text()) # every rate tried: actual, in / out
|
|
|
122
122
|
|
|
123
123
|
Each rate in order: `try` (answered at the speed now, then the probe switches) -> the host switches -> a verify both ways
|
|
124
124
|
with max_frame-sized frames (link_source / link_sink, pipelined) for `verify_bytes` or `verify_s`, counting broken frames
|
|
125
|
-
and measuring KB/s each way ->
|
|
125
|
+
and measuring KB/s each way -> both ways at once at the in-flight that passed (link_source and link_sink interleaved,
|
|
126
|
+
about 1 s and at least 64 frames, `duplex_kb_s`; a line that carries each way alone can still break under duplex use - a
|
|
127
|
+
CH340 at 921600 did) -> `commit` when nothing broke, else revert and back to the boot speed, confirmed there (a
|
|
126
128
|
probe whose revert was lost waits out its `verify_ms`). The first rate that passes is kept; a rate the probe's UART cannot
|
|
127
129
|
make is skipped. Which rates pass depends on the bridge chip and its driver (an FTDI took only 3 MHz / n, a CH340 921600
|
|
128
130
|
but not 1500000 towards the host: oep-spec docs/uart-speed-negotiation.ja.md), so the host chooses them. The report
|
|
@@ -133,7 +135,10 @@ line goes quiet (`idle_ms`, at most `port_speed_idle_max_ms` = 3 s; a host that
|
|
|
133
135
|
that): while raised the link sends a keepalive before a request when it has been quiet for 1 s, and `hst.link.keep_alive()`
|
|
134
136
|
does the same for a caller that sits idle for long; opening a serial port retries its first confirm for about 4 s to
|
|
135
137
|
wait out a rate left over. The link follows an `end` at once, and a request that goes unanswered at a raised rate (its
|
|
136
|
-
resend too) takes the link back to the boot speed, confirmed, and goes once more there - it never wedges
|
|
138
|
+
resend too) takes the link back to the boot speed, confirmed, and goes once more there - it never wedges; while raised
|
|
139
|
+
each wait for an answer is at most a quarter of the lease, so this ends well inside it. In use, 3 broken frames or
|
|
140
|
+
resends within 5 s step the link down: port_speed revert at the raised rate, the boot speed, a confirm, the request
|
|
141
|
+
again there. A rate left either way is not used again in that session (`speed.stepped_down`, `speed.down_why`). A probe
|
|
137
142
|
without the feature answers `not supported` and stays at its speed. Behind a broker (TCP) the broker does this, not the
|
|
138
143
|
client. Serial ports are also opened in the driver's low-latency mode where it has one (an FTDI's latency timer 16 -> 1 ms
|
|
139
144
|
tripled a UART bridge's throughput). `open_host(..., baud=)` names the boot speed when the board's profile is not 115200.
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
oep_client/__init__.py,sha256=
|
|
2
|
-
oep_client/__main__.py,sha256=
|
|
3
|
-
oep_client/arm.py,sha256=
|
|
4
|
-
oep_client/capture.py,sha256=
|
|
1
|
+
oep_client/__init__.py,sha256=KqFhyYXLqtEQs_WvY0ZfnJTyG1JZxKIBKfe0wUuglJc,164
|
|
2
|
+
oep_client/__main__.py,sha256=em34bE60p_ut43LFGCRgxsofMOiTRL0wWiC-HGIKLVA,19852
|
|
3
|
+
oep_client/arm.py,sha256=X7xrKDOXkv3lFtc9CG6ls051xZxy8C3XUaGoIrJ2_nM,12744
|
|
4
|
+
oep_client/capture.py,sha256=R3ItIUjE6-Kqe3tjTVfYW8rKO4cYF-d480FAVbWr-SE,32148
|
|
5
5
|
oep_client/catalog.py,sha256=m43MARRSJ5AwzNBMvRJUMAZxZc53t5eN-QaJ9049YHY,7016
|
|
6
6
|
oep_client/ch32_flash.py,sha256=GxlpZY-o7pOjT6Yod9Gk0EflNtVKT_w_fXwkXBq5zrM,12641
|
|
7
7
|
oep_client/cobs.py,sha256=hqddQI_X7rsBvBvcP_MBE3bXPyB13-Azjqp_9JfdQ1g,2288
|
|
8
|
-
oep_client/config.py,sha256=
|
|
8
|
+
oep_client/config.py,sha256=7KQxMhTklUhT9uzlj4rnT1xyXpJeFFfNNE4oIqeyMy0,17676
|
|
9
9
|
oep_client/console.py,sha256=Srzlbto7-NZmkxUFMy_nUAanf9X0ZW1ib5tl6tX4rXU,9790
|
|
10
|
-
oep_client/core.py,sha256=
|
|
10
|
+
oep_client/core.py,sha256=TooBzLBYcZ5-ymNJwcYYFSbbWDqkb7J6ccjedZpKe-0,12008
|
|
11
11
|
oep_client/decode.py,sha256=6pTFVBT0MLAKedZLVKyd5yT8hjrRw4fdDjnoDiE9XRs,2380
|
|
12
12
|
oep_client/dump.py,sha256=GAnmu2abuDmGmbWcbbEfC3EUuPp5Hady9vQVNcQ-724,6743
|
|
13
|
-
oep_client/endpoint.py,sha256=
|
|
13
|
+
oep_client/endpoint.py,sha256=k8O_izvyb05jNIyjlE5ILVhpCO0RaH9dhMUlGh6-5GM,138248
|
|
14
14
|
oep_client/fake.py,sha256=LG56WG0RrFtmgfodVZhfLWvbojtNFWnk2lycjR6zhiE,19385
|
|
15
15
|
oep_client/fake_capture.py,sha256=6oo-2byIBuZS0g5hk5KXHkqHNrO9PSRO-5MuetZvk80,26916
|
|
16
|
-
oep_client/fake_serial.py,sha256=
|
|
17
|
-
oep_client/fake_serve.py,sha256=
|
|
16
|
+
oep_client/fake_serial.py,sha256=WEh-1WIboot1Z_Mq30S4Ny_ceWt1DTUJTjIl9giUdGQ,9831
|
|
17
|
+
oep_client/fake_serve.py,sha256=pwdJPcB8MnPpzoFWtxmLMpLZLJ3kFkzkWOgA_4fiRic,16524
|
|
18
18
|
oep_client/fixture.py,sha256=sskFYFxI_7SOR_E5AxNf6tSYoUvoJOh0z_VjVdXcp6o,12527
|
|
19
19
|
oep_client/frames.py,sha256=xjgjnXmXUqNJZ5m0h91r4Cy48WoVK1mEMW4gRg-mlMI,3991
|
|
20
20
|
oep_client/hid_stream.py,sha256=99Dsm4Io1keukuETG_UNqjBGiVYZZeb__Pv4ytaKg9k,10695
|
|
21
|
-
oep_client/host.py,sha256=
|
|
21
|
+
oep_client/host.py,sha256=rLSHJhQFgPo5e75Z8dNjDx7zRC8vebAGxbmSiFH0mWU,21700
|
|
22
22
|
oep_client/interfaces.py,sha256=8HXr9iGJASIwBpTHBrCK_0b6NfbclEjCbBCsc62euzg,5693
|
|
23
|
-
oep_client/link.py,sha256=
|
|
23
|
+
oep_client/link.py,sha256=1dlZTEfqV4md4jicl0yd1x1GOG1F67D5UhnmA8lRBbQ,56592
|
|
24
24
|
oep_client/message.py,sha256=eQ-27lxN2Y9JadnUFpEmkEW2X7oucrKFFFs4_nnDClU,12322
|
|
25
25
|
oep_client/names.py,sha256=ztCZFWs__aR-a5G14TnJP-Q4YfLhbyrzvOVOMJuG93k,3653
|
|
26
26
|
oep_client/registry.py,sha256=YEL_iMWFmqAaUY0zTkhs5CqbXBHBx4W8kjm37KPs190,15726
|
|
27
|
-
oep_client/riscv.py,sha256=
|
|
27
|
+
oep_client/riscv.py,sha256=245t-x1qM-wTTxM5OYNJz8c3oH0H8ORH6aexqcUE8oA,29941
|
|
28
28
|
oep_client/rp2350.py,sha256=7OMyj270k3AY1e3-ljicPwOjf9jOlokbT6QNkA0b0yY,4227
|
|
29
29
|
oep_client/uiapduino.py,sha256=kDB8C9GvIPBw94PaCKMzVWNtoQDu_axrz_n_6zbYJGU,6082
|
|
30
30
|
oep_client/usb_stream.py,sha256=_KQvWq906DnQ4EjW1to7d9ufh3pnGkkkbCzti6GYVCY,11131
|
|
31
|
-
oep_client_python-0.0.
|
|
32
|
-
oep_client_python-0.0.
|
|
33
|
-
oep_client_python-0.0.
|
|
34
|
-
oep_client_python-0.0.
|
|
35
|
-
oep_client_python-0.0.
|
|
31
|
+
oep_client_python-0.0.26.dist-info/METADATA,sha256=jVnIRVW010EUmGV9-b3drDiPRe84Vi4DZCsR6hswjOQ,13812
|
|
32
|
+
oep_client_python-0.0.26.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
33
|
+
oep_client_python-0.0.26.dist-info/entry_points.txt,sha256=h6_8QX89Ww1ogALEGG5g4kspT3IOV6O51lwKqf5BWco,49
|
|
34
|
+
oep_client_python-0.0.26.dist-info/licenses/LICENSE,sha256=ntlalztb3L1OaNAxmQdHlubu5fVhjMc9oRZi3PeQhXg,1076
|
|
35
|
+
oep_client_python-0.0.26.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|