oep-client-python 0.0.4__py3-none-any.whl → 0.0.6__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 +6 -1
- oep_client/config.py +12 -7
- oep_client/core.py +41 -1
- oep_client/endpoint.py +29 -11
- oep_client/fake.py +2 -2
- oep_client/fake_serve.py +2 -2
- oep_client/registry.py +4 -4
- oep_client/riscv.py +31 -12
- oep_client/usb_stream.py +32 -4
- {oep_client_python-0.0.4.dist-info → oep_client_python-0.0.6.dist-info}/METADATA +2 -2
- {oep_client_python-0.0.4.dist-info → oep_client_python-0.0.6.dist-info}/RECORD +15 -15
- {oep_client_python-0.0.4.dist-info → oep_client_python-0.0.6.dist-info}/WHEEL +0 -0
- {oep_client_python-0.0.4.dist-info → oep_client_python-0.0.6.dist-info}/entry_points.txt +0 -0
- {oep_client_python-0.0.4.dist-info → oep_client_python-0.0.6.dist-info}/licenses/LICENSE +0 -0
oep_client/__init__.py
CHANGED
oep_client/__main__.py
CHANGED
|
@@ -64,6 +64,10 @@ def _config_parser(sub) -> None:
|
|
|
64
64
|
slot.add_argument("--attach", choices=sorted(config.ATTACH), default="host")
|
|
65
65
|
slot.add_argument("--retry", type=int, default=0, help="at-boot: try again every N s while absent (0: never)")
|
|
66
66
|
slot.add_argument("--mechanism", choices=sorted(config.MECHANISM), default="dmseq")
|
|
67
|
+
slot.add_argument("--max-speed", type=int, default=0,
|
|
68
|
+
help="the line's ceiling in Hz for the probe's own attach (0: none); the target's, e.g. 1000000")
|
|
69
|
+
slot.add_argument("--idle-clock", choices=sorted(config.IDLE_CLOCK), default="high",
|
|
70
|
+
help="rvswd: SWCLK while the line rests (the target's: low on CH32L103 / V203)")
|
|
67
71
|
slot.add_argument("--lock", help="MASK:VALUE (hex u32) the target_id (WCH DMI 0x7F) must match, e.g. ffffff0f:035e0600")
|
|
68
72
|
slot.add_argument("--save", action="store_true", help="save after the change")
|
|
69
73
|
bind = cs.add_parser("bind", help="what a serial port carries")
|
|
@@ -183,7 +187,7 @@ def _config(args) -> int:
|
|
|
183
187
|
mask, _, value = args.lock.partition(":")
|
|
184
188
|
lock = (1, struct.pack("<I", int(mask, 16)), struct.pack("<I", int(value, 16)))
|
|
185
189
|
it = config.Slot(args.slot, fn, _pins(hst, fn, args.pins, args.wire), args.name, args.attach, args.retry,
|
|
186
|
-
args.mechanism, lock)
|
|
190
|
+
args.mechanism, lock, args.max_speed, args.idle_clock)
|
|
187
191
|
_change(hst, cfg, [it], args.save)
|
|
188
192
|
elif args.action == "bind":
|
|
189
193
|
slots = {it.name: it.slot for it in cfg.items() if isinstance(it, config.Slot)}
|
|
@@ -250,6 +254,7 @@ def _show(hst, cfg, as_json: bool) -> int:
|
|
|
250
254
|
s = by_slot.get(it.slot)
|
|
251
255
|
pins = f"{it.pins[0]}" if it.pins[1] == 0xFFFF else f"{it.pins[0]},{it.pins[1]}"
|
|
252
256
|
retry = f" retry {it.retry_s} s" if it.attach == "at-boot" else ""
|
|
257
|
+
retry += (f" max {it.max_speed} Hz" if it.max_speed else "") + (" idle-low" if it.idle_clock == "low" else "")
|
|
253
258
|
lock = (f" lock {int.from_bytes(it.lock[1], 'little'):08x}:{int.from_bytes(it.lock[2], 'little'):08x}"
|
|
254
259
|
if it.lock else "")
|
|
255
260
|
live = ""
|
oep_client/config.py
CHANGED
|
@@ -29,6 +29,7 @@ MECHANISM = {k: v for k, v in reg.TARGET_CONSOLE.enum["mechanism"].items()}
|
|
|
29
29
|
SLOT_STATE = {v: k.replace("_", "-") for k, v in _CFG.enum["slot_state"].items()}
|
|
30
30
|
BIND_FLOW = {v: k for k, v in _CFG.enum["bind_flow"].items()}
|
|
31
31
|
IDLE = {k.replace("_", "-"): v for k, v in _CFG.enum["idle_mode"].items()}
|
|
32
|
+
IDLE_CLOCK = dict(reg.WIRE_RVSWD.enum["idle_clock"]) # a slot's idle_clock (oep-if-debug §3)
|
|
32
33
|
STORAGE_STATE = {v: k for k, v in _CFG.enum["storage_state"].items()}
|
|
33
34
|
|
|
34
35
|
|
|
@@ -79,12 +80,15 @@ class Slot:
|
|
|
79
80
|
retry_s: int = 0 # at-boot: try again every retry_s while the target is not there (0: never)
|
|
80
81
|
mechanism: str = "dmseq" # sdi, dmdata, dmseq
|
|
81
82
|
lock: tuple[int, bytes, bytes] | None = None
|
|
83
|
+
max_speed: int = 0 # the line's ceiling in Hz for the probe's own attach (0: none) - the target's
|
|
84
|
+
idle_clock: str = "high" # rvswd: SWCLK while the line rests, high / low - the target's (oep-if-debug §3)
|
|
82
85
|
TAG = ITEM["slot"]
|
|
83
86
|
|
|
84
87
|
def value(self) -> bytes:
|
|
85
88
|
name = self.name.encode()
|
|
86
|
-
v = struct.pack("<
|
|
87
|
-
self.retry_s if self.attach == "at-boot" else 0,
|
|
89
|
+
v = struct.pack("<BHHHBHIBBB", self.slot, self.wire_fn, *self.pins, ATTACH[self.attach],
|
|
90
|
+
self.retry_s if self.attach == "at-boot" else 0, self.max_speed, IDLE_CLOCK[self.idle_clock],
|
|
91
|
+
MECHANISM[self.mechanism], len(name)) + name
|
|
88
92
|
if self.lock is None:
|
|
89
93
|
return v + b"\x00"
|
|
90
94
|
scheme, mask, value = self.lock
|
|
@@ -127,15 +131,16 @@ def decode(tag: int, v: bytes):
|
|
|
127
131
|
return Label(struct.unpack_from("<H", v)[0], v[2:].decode("utf-8", "replace"))
|
|
128
132
|
if tag == ITEM["idle"] and len(v) == 3:
|
|
129
133
|
return Idle(struct.unpack_from("<H", v)[0], _name(IDLE, v[2]))
|
|
130
|
-
if tag == ITEM["slot"] and len(v) >=
|
|
131
|
-
n, wire_fn, swdio, swclk, attach, retry_s, mech, name_len = struct.unpack_from("<
|
|
132
|
-
name = v[
|
|
133
|
-
rest = v[
|
|
134
|
+
if tag == ITEM["slot"] and len(v) >= 18:
|
|
135
|
+
n, wire_fn, swdio, swclk, attach, retry_s, max_speed, idle, mech, name_len = struct.unpack_from("<BHHHBHIBBB", v)
|
|
136
|
+
name = v[17:17 + name_len].decode("ascii", "replace")
|
|
137
|
+
rest = v[17 + name_len:]
|
|
134
138
|
lock = None
|
|
135
139
|
if rest and rest[0]:
|
|
136
140
|
half = (len(rest) - 1) // 2
|
|
137
141
|
lock = (rest[0], rest[1:1 + half], rest[1 + half:])
|
|
138
|
-
return Slot(n, wire_fn, (swdio, swclk), name, _name(ATTACH, attach), retry_s, _name(MECHANISM, mech), lock
|
|
142
|
+
return Slot(n, wire_fn, (swdio, swclk), name, _name(ATTACH, attach), retry_s, _name(MECHANISM, mech), lock,
|
|
143
|
+
max_speed, _name(IDLE_CLOCK, idle))
|
|
139
144
|
if tag == ITEM["bind"] and len(v) >= 4:
|
|
140
145
|
port, mode, selected, n = struct.unpack_from("<BBBB", v)
|
|
141
146
|
streams = [(_name(STREAM, v[4 + 3 * k]), struct.unpack_from("<H", v, 5 + 3 * k)[0]) for k in range(n)]
|
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
|
@@ -60,6 +60,7 @@ WIRES = ("oep.wire.rvswd", "oep.wire.swio")
|
|
|
60
60
|
OWNER = reg.CORE.tlv["open"]["owner"]
|
|
61
61
|
SLOT_NAME = re.compile(r"[a-z0-9_-]{1,32}")
|
|
62
62
|
NO_SLOT, NEVER = 0xFF, 0xFFFFFFFF
|
|
63
|
+
PIN_ROLE_RESET = reg.WIRE_RVSWD.enum["pin_role"]["reset"] # the channels attach_under_reset may take
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
class Reject(Exception):
|
|
@@ -180,6 +181,7 @@ class Connection:
|
|
|
180
181
|
speed: int = 4_000_000
|
|
181
182
|
tid: int | None = None
|
|
182
183
|
users: set = field(default_factory=set) # "host" and/or ("slot", n)
|
|
184
|
+
idle_clock: int = 0 # rvswd: SWCLK while the line rests, 0 high / 1 low
|
|
183
185
|
|
|
184
186
|
|
|
185
187
|
@dataclass(frozen=True)
|
|
@@ -189,6 +191,8 @@ class Slot:
|
|
|
189
191
|
pair: tuple[int, int]
|
|
190
192
|
attach: int
|
|
191
193
|
retry_s: int
|
|
194
|
+
max_speed: int # 0: no ceiling (oep-if-debug §3: the target's, the host's to set)
|
|
195
|
+
idle_clock: int
|
|
192
196
|
mechanism: int
|
|
193
197
|
name: str
|
|
194
198
|
lock: tuple[int, bytes, bytes] | None # (scheme, mask, value)
|
|
@@ -249,9 +253,13 @@ class Endpoint:
|
|
|
249
253
|
self.serial_ports = {i for i, k in enumerate(self.transports) if k in fake.SERIAL_KINDS}
|
|
250
254
|
self.pairs: dict[int, list[tuple[int, int]]] = {} # wire fn -> allowed (swdio, swclk), declared order
|
|
251
255
|
self.max_connections: dict[int, int] = {}
|
|
256
|
+
self.reset_channels: dict[int, set[int]] = {} # wire fn -> channels attach_under_reset may take (role 3)
|
|
252
257
|
for fn, name in self.names.items():
|
|
253
258
|
if name in WIRES:
|
|
254
259
|
self.pairs[fn] = [self._group_pair(name, t) for t in self.static[fn] if t[0] == catalog.CHANNEL_GROUP]
|
|
260
|
+
self.reset_channels[fn] = {
|
|
261
|
+
c for t in self.static[fn] if t[0] == catalog.ROLE_CHANNELS and t[2] == PIN_ROLE_RESET
|
|
262
|
+
for c in catalog.bitmap_to_channels(struct.unpack_from("<H", t, 3)[0], t[5:2 + t[1]])}
|
|
255
263
|
self.max_connections[fn] = next((t[2] for t in self.static[fn] if t[0] == fake.MAX_CONNECTIONS), 1)
|
|
256
264
|
self.targets: dict[tuple[int, tuple[int, int]], FakeTarget] = {
|
|
257
265
|
(fn, p): FakeTarget() for fn in sorted(self.pairs) for p in self.pairs[fn]}
|
|
@@ -575,17 +583,18 @@ class Endpoint:
|
|
|
575
583
|
got.append(struct.unpack_from("<HBH", value))
|
|
576
584
|
elif tag & m.TAG_CRITICAL:
|
|
577
585
|
return m.REJECTED, m.UNSUPPORTED, bytes([tag])
|
|
578
|
-
self._check_plan(got)
|
|
579
586
|
named = {fn for fn, _, _ in got}
|
|
587
|
+
if named & self.plan_from_config: # the settings' plan is the settings' (core §8)
|
|
588
|
+
raise Reject(m.UNAVAILABLE)
|
|
589
|
+
self._check_plan(got)
|
|
580
590
|
self.plan = {a for a in self.plan if a[0] not in named} | set(got)
|
|
581
|
-
self.plan_from_config -= named
|
|
582
591
|
return m.COMPLETED, m.SUCCESS, b""
|
|
583
592
|
if op == m.OP_PLAN_RELEASE: # n(u8) n x fn(u16); n = 0: every fn
|
|
584
593
|
n = t.take("B")
|
|
585
594
|
fns = {t.take("H") for _ in range(n)}
|
|
586
595
|
t.tail()
|
|
587
|
-
for fn in {a[0] for a in self.plan if not fns or a[0] in fns}:
|
|
588
|
-
self._drop_plan(fn)
|
|
596
|
+
for fn in {a[0] for a in self.plan if not fns or a[0] in fns} - self.plan_from_config:
|
|
597
|
+
self._drop_plan(fn) # the settings' plans stay, n = 0 too (core §8)
|
|
589
598
|
return m.COMPLETED, m.SUCCESS, b""
|
|
590
599
|
return m.REJECTED, m.UNKNOWN_OPERATION, b""
|
|
591
600
|
|
|
@@ -657,10 +666,14 @@ class Endpoint:
|
|
|
657
666
|
if method > 1:
|
|
658
667
|
raise Reject(m.UNSUPPORTED)
|
|
659
668
|
else:
|
|
660
|
-
channel, _hold = t.take("HH")
|
|
661
|
-
if channel not in (
|
|
669
|
+
channel, _hold = t.take("HH") # no default reset line (oep-if-debug §3)
|
|
670
|
+
if channel not in self.reset_channels.get(fn, set()) or any(a[2] == channel for a in self.plan):
|
|
662
671
|
raise Reject(m.UNAVAILABLE)
|
|
663
|
-
|
|
672
|
+
rvswd = self.names[fn] == "oep.wire.rvswd"
|
|
673
|
+
got, ignored = t.tail({0x01, 0x03, 0x04} if rvswd else {0x01, 0x03})
|
|
674
|
+
if 0x04 in got and (len(got[0x04]) != 1 or got[0x04][0] > 1):
|
|
675
|
+
raise Reject(m.MALFORMED)
|
|
676
|
+
idle_clock = got[0x04][0] if 0x04 in got else 0
|
|
664
677
|
pair = self._pick_pair(fn, got)
|
|
665
678
|
tg = self.targets[(fn, pair)]
|
|
666
679
|
speed = min(4_000_000, struct.unpack("<I", got[0x01])[0]) if 0x01 in got else 4_000_000
|
|
@@ -676,6 +689,7 @@ class Endpoint:
|
|
|
676
689
|
flags |= 2
|
|
677
690
|
self.conns[cid].speed = min(self.conns[cid].speed, speed)
|
|
678
691
|
c = self.conns[cid]
|
|
692
|
+
c.idle_clock = idle_clock # an existing connection takes the new rest level
|
|
679
693
|
c.users.add("host")
|
|
680
694
|
for n, s in self.slots.items(): # a new connection for an evicted slot: a new cue
|
|
681
695
|
if s.wire_fn == fn and s.pair == pair:
|
|
@@ -923,7 +937,7 @@ class Endpoint:
|
|
|
923
937
|
pos = s.end
|
|
924
938
|
elif frm == 3:
|
|
925
939
|
hits = [mk for mk in s.marks if arg == 0 or mk[2] == arg]
|
|
926
|
-
pos = hits[-1][1] if hits else s.
|
|
940
|
+
pos = hits[-1][1] if hits else s.end # no such mark: from now (common §1.2)
|
|
927
941
|
else:
|
|
928
942
|
raise Reject(m.UNSUPPORTED)
|
|
929
943
|
flags = 0
|
|
@@ -1218,7 +1232,7 @@ class Endpoint:
|
|
|
1218
1232
|
|
|
1219
1233
|
def _parse_slot(self, v: bytes) -> Slot:
|
|
1220
1234
|
t = Take(v)
|
|
1221
|
-
n, wire_fn, swdio, swclk, attach, retry_s, mech, name_len = t.take("
|
|
1235
|
+
n, wire_fn, swdio, swclk, attach, retry_s, max_speed, idle_clock, mech, name_len = t.take("BHHHBHIBBB")
|
|
1222
1236
|
name = t.bytes(name_len)
|
|
1223
1237
|
scheme = t.take("B")
|
|
1224
1238
|
rest = v[t.at:]
|
|
@@ -1226,6 +1240,8 @@ class Endpoint:
|
|
|
1226
1240
|
raise Reject(m.MALFORMED)
|
|
1227
1241
|
if retry_s and attach != SLOT_ATTACH["at_boot"]:
|
|
1228
1242
|
raise Reject(m.MALFORMED)
|
|
1243
|
+
if idle_clock > 1 or (idle_clock and self.names.get(wire_fn) != "oep.wire.rvswd"):
|
|
1244
|
+
raise Reject(m.MALFORMED)
|
|
1229
1245
|
if not SLOT_NAME.fullmatch(name.decode("ascii", "replace")):
|
|
1230
1246
|
raise Reject(m.MALFORMED)
|
|
1231
1247
|
if self.names.get(wire_fn) not in WIRES or (swdio, swclk) not in self.pairs.get(wire_fn, []):
|
|
@@ -1239,7 +1255,7 @@ class Endpoint:
|
|
|
1239
1255
|
lock = (scheme, rest[:len(rest) // 2], rest[len(rest) // 2:])
|
|
1240
1256
|
elif rest:
|
|
1241
1257
|
raise Reject(m.MALFORMED)
|
|
1242
|
-
return Slot(n, wire_fn, (swdio, swclk), attach, retry_s, mech, name.decode(), lock)
|
|
1258
|
+
return Slot(n, wire_fn, (swdio, swclk), attach, retry_s, max_speed, idle_clock, mech, name.decode(), lock)
|
|
1243
1259
|
|
|
1244
1260
|
def _parse_bind(self, v: bytes, slots: dict[int, Slot]) -> Bind:
|
|
1245
1261
|
t = Take(v)
|
|
@@ -1291,7 +1307,9 @@ class Endpoint:
|
|
|
1291
1307
|
if not tg.present:
|
|
1292
1308
|
return
|
|
1293
1309
|
try:
|
|
1294
|
-
|
|
1310
|
+
speed = min(4_000_000, s.max_speed) if s.max_speed else 4_000_000 # the slot's line settings
|
|
1311
|
+
cid = self._seat(s.wire_fn, s.pair, tg, speed, evict=False) # automatic: never evicts
|
|
1312
|
+
self.conns[cid].idle_clock = s.idle_clock
|
|
1295
1313
|
except Reject:
|
|
1296
1314
|
return
|
|
1297
1315
|
tg.havereset = False
|
oep_client/fake.py
CHANGED
|
@@ -185,8 +185,8 @@ def esp32_v003() -> FakeProbe:
|
|
|
185
185
|
_core("3.0.0", "esp32-d0wd", bytes.fromhex("0070070d9394"), 40, reserved, f"{NS}.esp32-v003",
|
|
186
186
|
{16: "SWIO", 23: "NRST", 22: "DUT TX", 21: "DUT RX"},
|
|
187
187
|
_transports([(TRANSPORT["uart_bridge"], 0xFF)])),
|
|
188
|
-
Offered(1, 1, "oep.wire.swio", (catalog.channel_group(1, [(1, 16)]), catalog.
|
|
189
|
-
catalog.u8(MAX_CONNECTIONS, 1))),
|
|
188
|
+
Offered(1, 1, "oep.wire.swio", (catalog.channel_group(1, [(1, 16)]), catalog.role_channels(3, [23]),
|
|
189
|
+
catalog.u8(IMPLEMENTATION, 1), catalog.u8(MAX_CONNECTIONS, 1))),
|
|
190
190
|
Offered(2, 1, "oep.target.riscv-dm", (catalog.u32(FEATURES, 0b0111), catalog.u8(IMPLEMENTATION, 1),
|
|
191
191
|
catalog.u16(MAX_LENGTH, 40))),
|
|
192
192
|
Offered(3, 1, "oep.target.console", (catalog.tlv(MECHANISMS, bytes([0, 1, 2])),)),
|
oep_client/fake_serve.py
CHANGED
|
@@ -77,8 +77,8 @@ def build(a: argparse.Namespace) -> endpoint.Endpoint:
|
|
|
77
77
|
swdio, swclk = ep.pairs[wire][n]
|
|
78
78
|
mech = 2 if 2 in ep.mechanisms else min(ep.mechanisms)
|
|
79
79
|
raw = name.encode()
|
|
80
|
-
value = struct.pack("<
|
|
81
|
-
mech, len(raw)) + raw + b"\x00"
|
|
80
|
+
value = struct.pack("<BHHHBHIBBB", n, wire, swdio, swclk, reg.PROBE_CONFIG.enum["slot_attach"]["at_boot"], 1,
|
|
81
|
+
0, 0, mech, len(raw)) + raw + b"\x00" # no speed ceiling, rests high, no lock
|
|
82
82
|
items.append(bytes([_ITEM["slot"], len(value)]) + value)
|
|
83
83
|
if a.bind:
|
|
84
84
|
streams = b"".join(struct.pack("<BH", reg.PROBE_CONFIG.enum["bind_stream"]["slot_console"], n)
|
oep_client/registry.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from types import SimpleNamespace as _NS
|
|
4
4
|
|
|
5
|
-
REGISTRY_HASH = "
|
|
5
|
+
REGISTRY_HASH = "049bd156b4775990"
|
|
6
6
|
SCHEMA = 1
|
|
7
7
|
PROTOCOL_REVISION = 1
|
|
8
8
|
CONFIRM_REQUEST_MAGIC = 'OEP?'
|
|
@@ -22,13 +22,13 @@ TIMING = {"resync_quiet_ms": 0x32, "probe_frame_gap_ms": 0xC8, "host_frame_pause
|
|
|
22
22
|
|
|
23
23
|
INTERFACES = {}
|
|
24
24
|
CORE = _NS(name="oep.core", revision=1, op={"confirm": 0x01, "list": 0x02, "describe": 0x03, "plan_apply": 0x04, "plan_release": 0x05, "open": 0x10, "end": 0x11, "keepalive": 0x12, "lock_state": 0x13, "subscribe": 0x30, "unsubscribe": 0x32, "link_source": 0x40, "link_sink": 0x41}, lock_free={0x01, 0x02, 0x03, 0x10, 0x13, 0x40, 0x41},
|
|
25
|
-
closed_tail={0x40}, tlv={"plan_apply": {"role_assignment": 0x90}, "open": {"owner": 0x01}, "lock_state_answer": {"owner": 0x01}, "locked_payload": {"owner": 0x01}, "describe": {"firmware": 0x40, "model": 0x41, "unit_id": 0x42, "channels": 0x43, "reserved": 0x44, "profile": 0x45, "label": 0x46, "resets_on_open": 0x47, "transport": 0x49, "oep_pid": 0x4A}}, event={"heartbeat": 0x01}, enum={"transport_kind": {"uart_bridge": 0x01, "usb_cdc": 0x02, "usb_serial_jtag": 0x03, "vendor_bulk": 0x04, "hid": 0x05, "tcp": 0x06}}, own={})
|
|
25
|
+
closed_tail={0x40}, tlv={"plan_apply": {"role_assignment": 0x90}, "open": {"owner": 0x01}, "lock_state_answer": {"owner": 0x01}, "locked_payload": {"owner": 0x01}, "describe": {"firmware": 0x40, "model": 0x41, "unit_id": 0x42, "channels": 0x43, "reserved": 0x44, "profile": 0x45, "label": 0x46, "resets_on_open": 0x47, "transport": 0x49, "oep_pid": 0x4A, "plan_roles": 0x4B}}, event={"heartbeat": 0x01}, enum={"transport_kind": {"uart_bridge": 0x01, "usb_cdc": 0x02, "usb_serial_jtag": 0x03, "vendor_bulk": 0x04, "hid": 0x05, "tcp": 0x06}}, own={})
|
|
26
26
|
INTERFACES["oep.core"] = CORE
|
|
27
27
|
WIRE_RVSWD = _NS(name="oep.wire.rvswd", revision=1, op={"scan": 0x01, "attach": 0x02, "detach": 0x03, "attach_under_reset": 0x04, "connections": 0x05}, lock_free={0x05},
|
|
28
|
-
closed_tail={}, tlv={"describe": {"max_connections": 0x40}, "detach": {"force": 0x01}, "attach": {"max_speed": 0x01, "pins": 0x03}, "attach_under_reset": {"max_speed": 0x01, "pins": 0x03}, "attach_answer": {"target_id": 0x10}}, event={}, enum={"connection_users": {"host_session": 0x01, "slot": 0x02}, "target_id_scheme": {"wch_dmi_7f": 0x01}, "attach_method": {"run": 0x00, "halt": 0x01}, "pin_role": {"swdio": 0x01, "swclk": 0x02}}, own={})
|
|
28
|
+
closed_tail={}, tlv={"describe": {"max_connections": 0x40}, "detach": {"force": 0x01}, "attach": {"max_speed": 0x01, "pins": 0x03, "idle_clock": 0x04}, "attach_under_reset": {"max_speed": 0x01, "pins": 0x03, "idle_clock": 0x04}, "attach_answer": {"target_id": 0x10}}, event={}, enum={"connection_users": {"host_session": 0x01, "slot": 0x02}, "target_id_scheme": {"wch_dmi_7f": 0x01}, "attach_method": {"run": 0x00, "halt": 0x01}, "pin_role": {"swdio": 0x01, "swclk": 0x02, "reset": 0x03}, "idle_clock": {"high": 0x00, "low": 0x01}}, own={})
|
|
29
29
|
INTERFACES["oep.wire.rvswd"] = WIRE_RVSWD
|
|
30
30
|
WIRE_SWIO = _NS(name="oep.wire.swio", revision=1, op={"scan": 0x01, "attach": 0x02, "detach": 0x03, "attach_under_reset": 0x04, "connections": 0x05}, lock_free={0x05},
|
|
31
|
-
closed_tail={}, tlv={"describe": {"max_connections": 0x40}, "detach": {"force": 0x01}, "attach": {"max_speed": 0x01, "pins": 0x03}, "attach_under_reset": {"max_speed": 0x01, "pins": 0x03}, "attach_answer": {"target_id": 0x10}}, event={}, enum={"connection_users": {"host_session": 0x01, "slot": 0x02}, "target_id_scheme": {"wch_dmi_7f": 0x01}, "attach_method": {"run": 0x00, "halt": 0x01}, "pin_role": {"swdio": 0x01}}, own={})
|
|
31
|
+
closed_tail={}, tlv={"describe": {"max_connections": 0x40}, "detach": {"force": 0x01}, "attach": {"max_speed": 0x01, "pins": 0x03}, "attach_under_reset": {"max_speed": 0x01, "pins": 0x03}, "attach_answer": {"target_id": 0x10}}, event={}, enum={"connection_users": {"host_session": 0x01, "slot": 0x02}, "target_id_scheme": {"wch_dmi_7f": 0x01}, "attach_method": {"run": 0x00, "halt": 0x01}, "pin_role": {"swdio": 0x01, "reset": 0x03}}, own={})
|
|
32
32
|
INTERFACES["oep.wire.swio"] = WIRE_SWIO
|
|
33
33
|
WIRE_SWD = _NS(name="oep.wire.swd", revision=1, op={"scan": 0x01, "attach": 0x02, "detach": 0x03, "connections": 0x05}, lock_free={0x05},
|
|
34
34
|
closed_tail={}, tlv={"describe": {"max_connections": 0x40}, "detach": {"force": 0x01}, "attach": {"max_speed": 0x01, "targetsel": 0x02, "pins": 0x03}}, event={}, enum={"connection_users": {"host_session": 0x01, "slot": 0x02}, "pin_role": {"swdio": 0x01, "swclk": 0x02}}, own={})
|
oep_client/riscv.py
CHANGED
|
@@ -14,8 +14,8 @@ import struct
|
|
|
14
14
|
import time
|
|
15
15
|
from dataclasses import dataclass, field
|
|
16
16
|
|
|
17
|
-
from . import host as h, message as m, registry as reg
|
|
18
|
-
from .core import Interface
|
|
17
|
+
from . import catalog, host as h, message as m, registry as reg
|
|
18
|
+
from .core import Interface, describe
|
|
19
19
|
from .fixture import Gpio
|
|
20
20
|
|
|
21
21
|
STATUS = reg.STATUS
|
|
@@ -109,7 +109,9 @@ class WireBase(Interface):
|
|
|
109
109
|
class Wire(WireBase):
|
|
110
110
|
"""oep.wire.rvswd / oep.wire.swio (CH32 debug links to a RISC-V debug module)."""
|
|
111
111
|
NAME = "oep.wire.rvswd"
|
|
112
|
-
|
|
112
|
+
ROLE_RESET = reg.WIRE_RVSWD.enum["pin_role"]["reset"]
|
|
113
|
+
TAG_IDLE_CLOCK = reg.WIRE_RVSWD.tlv["attach"]["idle_clock"]
|
|
114
|
+
IDLE_CLOCK = reg.WIRE_RVSWD.enum["idle_clock"]
|
|
113
115
|
RUN, HALT = reg.WIRE_RVSWD.enum["attach_method"]["run"], reg.WIRE_RVSWD.enum["attach_method"]["halt"]
|
|
114
116
|
TAG_TARGET_ID = reg.WIRE_RVSWD.tlv["attach_answer"]["target_id"]
|
|
115
117
|
SCHEME_WCH_DMI_7F = reg.WIRE_RVSWD.enum["target_id_scheme"]["wch_dmi_7f"]
|
|
@@ -125,12 +127,28 @@ class Wire(WireBase):
|
|
|
125
127
|
v = tail.get(self.TAG_TARGET_ID)
|
|
126
128
|
self.target_id = (v[0], bytes(v[1:])) if v else None
|
|
127
129
|
|
|
128
|
-
def
|
|
130
|
+
def _idle_tlv(self, idle_clock: str | None) -> bytes:
|
|
131
|
+
# rvswd only, critical: a probe that cannot rest the line that way must refuse (oep-if-debug §3)
|
|
132
|
+
return b"" if idle_clock is None else m.tlv(self.TAG_IDLE_CLOCK, bytes([self.IDLE_CLOCK[idle_clock]]), critical=True)
|
|
133
|
+
|
|
134
|
+
def reset_channels(self) -> list[int]:
|
|
135
|
+
"""The channels attach_under_reset may take (describe role_channels, role reset). There is no default reset
|
|
136
|
+
line: the host names one every time (oep-if-debug §3)."""
|
|
137
|
+
out = set()
|
|
138
|
+
for tag, v in describe(self.host, self.fn):
|
|
139
|
+
if tag & ~m.TAG_CRITICAL == catalog.ROLE_CHANNELS and v[0] == self.ROLE_RESET:
|
|
140
|
+
out.update(catalog.bitmap_to_channels(struct.unpack_from("<H", v, 1)[0], v[3:]))
|
|
141
|
+
return sorted(out)
|
|
142
|
+
|
|
143
|
+
def attach(self, halt: bool = True, max_speed: int | None = None, pins: tuple[int, int] | None = None,
|
|
144
|
+
idle_clock: str | None = None) -> tuple[int, int]:
|
|
129
145
|
"""-> (connection, DMSTATUS). Attaching an attached wire returns its connection as it is (self.existing).
|
|
130
146
|
self.had_reset: a pending havereset was acknowledged first (a V00x's DMSTATUS halt / run bits stay frozen
|
|
131
147
|
until then); self.speed_hz: the speed the probe chose; max_speed: a ceiling the probe must keep (critical);
|
|
132
|
-
|
|
133
|
-
|
|
148
|
+
idle_clock: "high" / "low", how rvswd rests SWCLK (critical). Both are the target's, known by the host
|
|
149
|
+
(oep-if-debug §3). self.target_id: (scheme, value) of the target's identity when the probe could read one."""
|
|
150
|
+
body = (bytes([self.HALT if halt else self.RUN]) + self._speed_tlv(max_speed) + self._pins_tlv(pins)
|
|
151
|
+
+ self._idle_tlv(idle_clock))
|
|
134
152
|
rd = m.Reader(self._call(self.ATTACH, body).payload)
|
|
135
153
|
conn, status, flags, self.speed_hz = rd.take("HIBI")
|
|
136
154
|
self.had_reset, self.existing = bool(flags & 1), bool(flags & 2)
|
|
@@ -139,12 +157,13 @@ class Wire(WireBase):
|
|
|
139
157
|
self._take_target_id(tail)
|
|
140
158
|
return conn, status
|
|
141
159
|
|
|
142
|
-
def attach_under_reset(self, channel: int
|
|
143
|
-
|
|
144
|
-
"""Hold the target in reset through `channel` (
|
|
145
|
-
halt it at once - the way back from firmware that turns the
|
|
146
|
-
|
|
147
|
-
|
|
160
|
+
def attach_under_reset(self, channel: int, hold_ms: int = 20, max_speed: int | None = None,
|
|
161
|
+
pins: tuple[int, int] | None = None, idle_clock: str | None = None) -> tuple[int, int]:
|
|
162
|
+
"""Hold the target in reset through `channel` (always named: there is no default reset line; the probe
|
|
163
|
+
allows reset_channels()), attach, release and halt it at once - the way back from firmware that turns the
|
|
164
|
+
debug pins into GPIOs. -> (connection, dpc)"""
|
|
165
|
+
body = (struct.pack("<HH", channel, hold_ms) + self._speed_tlv(max_speed) + self._pins_tlv(pins)
|
|
166
|
+
+ self._idle_tlv(idle_clock))
|
|
148
167
|
rd = m.Reader(self._call(self.ATTACH_UNDER_RESET, body).payload)
|
|
149
168
|
conn, dpc, self.speed_hz = rd.take("HII")
|
|
150
169
|
self._take_target_id(rd.tail())
|
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.6
|
|
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,20 +1,20 @@
|
|
|
1
|
-
oep_client/__init__.py,sha256=
|
|
2
|
-
oep_client/__main__.py,sha256=
|
|
1
|
+
oep_client/__init__.py,sha256=CDEcbic9y9YJgNsC6CNozMA0QBZA-GHlORfTNcv5LyY,163
|
|
2
|
+
oep_client/__main__.py,sha256=qtVGdpAHULBtLY2SedabJf-uwDZZhMf_YmrbqRzM57U,14669
|
|
3
3
|
oep_client/arm.py,sha256=JLyUhLSPcmsk73schZcaJYoOKuHzrvnU8kPmfrEvVuo,12331
|
|
4
4
|
oep_client/capture.py,sha256=RZgDnWqr8Aqy49U7PqQpnKIQEL18djVuDJFydxrmv-c,17996
|
|
5
5
|
oep_client/catalog.py,sha256=EqAQnSgHFxGbCO2GQ_Ee-uheJKbBBUUpL1-0qHfNMXU,6574
|
|
6
6
|
oep_client/ch32_flash.py,sha256=Fc-6jEj15cQtMSKmfC5dmDetMXJ8hAdAuJ9d9odwrsA,12651
|
|
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=a8zlxPI0sG0Z8gwUyg9OuKbXHoB5FctCrRDHULHhxCo,9753
|
|
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=tRx9ZUIiFN0J9NS3OwZ89NorIV7XqmZbYeUMnvJb6bE,74082
|
|
14
14
|
oep_client/esp32_targets.py,sha256=b8tNH-nb2KC-XgNfWg5l3TO0cyhZOF-gJajaVxNVSGQ,4988
|
|
15
|
-
oep_client/fake.py,sha256=
|
|
15
|
+
oep_client/fake.py,sha256=TJpzwSCFDSco-3HCE2ZHzusMx0r0dtrD1bkGn6aos90,13136
|
|
16
16
|
oep_client/fake_serial.py,sha256=uKl0UNcifhdeykQtm8gnRRhqV7TT4vPwBFuBhbf-9BI,4504
|
|
17
|
-
oep_client/fake_serve.py,sha256=
|
|
17
|
+
oep_client/fake_serve.py,sha256=Ke_hGRiOayCyut-uYOa0PaOFzy-zlvPHGwjs4HEZkAQ,14644
|
|
18
18
|
oep_client/fixture.py,sha256=653hCD-Iz0Lfs8dT4Hdg3QyubbbOGs1_xjq_3nb8foU,5564
|
|
19
19
|
oep_client/frames.py,sha256=xjgjnXmXUqNJZ5m0h91r4Cy48WoVK1mEMW4gRg-mlMI,3991
|
|
20
20
|
oep_client/hid_stream.py,sha256=99Dsm4Io1keukuETG_UNqjBGiVYZZeb__Pv4ytaKg9k,10695
|
|
@@ -23,14 +23,14 @@ oep_client/interfaces.py,sha256=P4ehrIj4j-9eg4NjaVdV15pawI2vOqucJwVklSdR-As,5472
|
|
|
23
23
|
oep_client/link.py,sha256=J9VtBWfTgNoIgUQuj8SsH9hHesQM2uEN545IoxKVS8E,19406
|
|
24
24
|
oep_client/message.py,sha256=MEM1UT6KFM7AsT56jvBLt_FJK_6SJ7O_nGAiipW0_zo,9912
|
|
25
25
|
oep_client/names.py,sha256=ztCZFWs__aR-a5G14TnJP-Q4YfLhbyrzvOVOMJuG93k,3653
|
|
26
|
-
oep_client/registry.py,sha256=
|
|
27
|
-
oep_client/riscv.py,sha256=
|
|
26
|
+
oep_client/registry.py,sha256=3Ydg1851I0ck--BHkrcmlUBz_6gzZU6EXTwm2HX2U20,10000
|
|
27
|
+
oep_client/riscv.py,sha256=PNeo_HXlq08b4h083olOOrGxqejD6ggQ7QzVqkIHEMA,22621
|
|
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.6.dist-info/METADATA,sha256=o7bx6WrdIbetzC9oULMTVkfp2mN4t9xKSukKpIbxzS4,7368
|
|
33
|
+
oep_client_python-0.0.6.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
34
|
+
oep_client_python-0.0.6.dist-info/entry_points.txt,sha256=h6_8QX89Ww1ogALEGG5g4kspT3IOV6O51lwKqf5BWco,49
|
|
35
|
+
oep_client_python-0.0.6.dist-info/licenses/LICENSE,sha256=ntlalztb3L1OaNAxmQdHlubu5fVhjMc9oRZi3PeQhXg,1076
|
|
36
|
+
oep_client_python-0.0.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|