oep-client-python 0.0.53__py3-none-any.whl → 0.0.54__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 +89 -0
- oep_client/call.py +284 -0
- oep_client/catalog.py +70 -0
- oep_client/conformance/__init__.py +1 -1
- oep_client/conformance/core_smoke.py +1 -1
- oep_client/conformance/host_core.py +1 -1
- oep_client/conformance/host_deadline.py +2 -2
- oep_client/conformance/host_requests.py +2 -2
- oep_client/conformance/host_wait.py +2 -2
- oep_client/dump.py +7 -1
- oep_client/registry.py +5 -5
- oep_client/virtual_bench.py +14 -2
- {oep_client_python-0.0.53.dist-info → oep_client_python-0.0.54.dist-info}/METADATA +1 -1
- {oep_client_python-0.0.53.dist-info → oep_client_python-0.0.54.dist-info}/RECORD +18 -17
- {oep_client_python-0.0.53.dist-info → oep_client_python-0.0.54.dist-info}/WHEEL +0 -0
- {oep_client_python-0.0.53.dist-info → oep_client_python-0.0.54.dist-info}/entry_points.txt +0 -0
- {oep_client_python-0.0.53.dist-info → oep_client_python-0.0.54.dist-info}/licenses/LICENSE +0 -0
oep_client/__init__.py
CHANGED
oep_client/__main__.py
CHANGED
|
@@ -125,6 +125,17 @@ def main(argv=None) -> int:
|
|
|
125
125
|
rs.add_argument("--reopen-s", type=float, default=0.0, metavar="S",
|
|
126
126
|
help="when the probe is not back within its restart_max_ms, open it again for up to S more seconds "
|
|
127
127
|
"(your reopen: a host where the device returns late, e.g. WSL re-attaching it through usbipd)")
|
|
128
|
+
ca = sub.add_parser("call", help="run any interface's op: by number with the request as hex bytes, or by the name and "
|
|
129
|
+
"fields its describe gives (core §7.4 op_name / op_field)")
|
|
130
|
+
ca.add_argument("probe", help="a probe: oep://UNIT_ID[?via=...], a serial port, tcp://HOST:PORT or usb")
|
|
131
|
+
ca.add_argument("interface", help="the interface's name (io.github.you.thing); its fn is looked up")
|
|
132
|
+
ca.add_argument("op", help="the op: a number (0x10) or its name")
|
|
133
|
+
ca.add_argument("args", nargs="*", help="NAME=VALUE per field (integers 10 or 0x, bools true / false, lists 1,2,3, "
|
|
134
|
+
"bytes hex, text as it is), or the request as hex bytes")
|
|
135
|
+
ca.add_argument("--text", action="store_true", help="the request is ARGS joined by spaces, as UTF-8 bytes")
|
|
136
|
+
ca.add_argument("--wait", type=float, metavar="S", help="the op is a job (oep::SimpleInterface::job): wait up to S "
|
|
137
|
+
"seconds for it by the interface's status op, then show that")
|
|
138
|
+
ca.add_argument("--json", action="store_true")
|
|
128
139
|
fd = sub.add_parser("find", help="probes on the local network that announce _oep._tcp (DNS-SD over mDNS)")
|
|
129
140
|
fd.add_argument("--timeout", type=float, default=2.0, help="seconds to listen for answers (default 2)")
|
|
130
141
|
fd.add_argument("--engine", choices=("auto", "zeroconf", "minimal"), default="auto",
|
|
@@ -157,6 +168,8 @@ def main(argv=None) -> int:
|
|
|
157
168
|
return _restart_cmd(args)
|
|
158
169
|
if args.command == "find":
|
|
159
170
|
return _find_cmd(args)
|
|
171
|
+
if args.command == "call":
|
|
172
|
+
return _call_cmd(args)
|
|
160
173
|
|
|
161
174
|
confirm = (0, 1)
|
|
162
175
|
if args.virtual:
|
|
@@ -237,6 +250,82 @@ def _restart_cmd(args) -> int:
|
|
|
237
250
|
return 0
|
|
238
251
|
|
|
239
252
|
|
|
253
|
+
# ---- oep call -----------------------------------------------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
def _field_value(field, text: str):
|
|
256
|
+
from . import call as c
|
|
257
|
+
t = field.type
|
|
258
|
+
if t & c.LIST:
|
|
259
|
+
items = [x for x in text.replace(" ", "").split(",") if x]
|
|
260
|
+
return [(x.lower() in ("1", "true")) if (t & ~c.LIST) == c.T["bool"] else int(x, 0) for x in items]
|
|
261
|
+
if t == c.T["bool"]:
|
|
262
|
+
if text.lower() not in ("0", "1", "true", "false"):
|
|
263
|
+
raise SystemExit(f"oep call: {field.name} takes true or false")
|
|
264
|
+
return text.lower() in ("1", "true")
|
|
265
|
+
if t == c.T["bytes"]:
|
|
266
|
+
return bytes.fromhex(text)
|
|
267
|
+
if t == c.T["text"]:
|
|
268
|
+
return text
|
|
269
|
+
return int(text, 0)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _call_cmd(args) -> int:
|
|
273
|
+
import json
|
|
274
|
+
from . import call as c
|
|
275
|
+
hst = _open_host(args.probe)
|
|
276
|
+
try:
|
|
277
|
+
forms = c.ops(hst, args.interface)
|
|
278
|
+
except c.NotOffered as e:
|
|
279
|
+
raise SystemExit(f"oep call: {e}") from None
|
|
280
|
+
op: int | str = int(args.op, 0) if args.op[:1].isdigit() else args.op
|
|
281
|
+
form = forms.get(op) if isinstance(op, int) else next((f for f in forms.values() if f.name == op), None)
|
|
282
|
+
payload, fields = None, {}
|
|
283
|
+
if args.text:
|
|
284
|
+
payload = " ".join(args.args).encode()
|
|
285
|
+
elif form is not None and form.form and (any("=" in a for a in args.args) or not args.args):
|
|
286
|
+
by_name = {f.name: f for f in form.request}
|
|
287
|
+
for item in args.args:
|
|
288
|
+
name, eq, value = item.partition("=")
|
|
289
|
+
if not eq or name not in by_name:
|
|
290
|
+
raise SystemExit(f"oep call: {form.signature()}: give NAME=VALUE for its fields ({item!r})")
|
|
291
|
+
fields[name] = _field_value(by_name[name], value)
|
|
292
|
+
else:
|
|
293
|
+
try:
|
|
294
|
+
payload = bytes.fromhex("".join(args.args))
|
|
295
|
+
except ValueError:
|
|
296
|
+
raise SystemExit("oep call: the request as hex bytes (its describe gives this op no fields)") from None
|
|
297
|
+
if not (form is not None and form.lock_free):
|
|
298
|
+
core.take(hst, 10000 + int((args.wait or 0) * 1000), owner="oep call") # a job's wait inside the lease
|
|
299
|
+
try:
|
|
300
|
+
if args.wait is not None:
|
|
301
|
+
r = c.call_wait(hst, args.interface, op, payload, timeout=args.wait, **fields)
|
|
302
|
+
else:
|
|
303
|
+
r = c.call(hst, args.interface, op, payload, **fields)
|
|
304
|
+
except (c.JobFailed, c.JobCancelled, c.JobTimeout, c.ProbeRestarted) as e:
|
|
305
|
+
st = getattr(e, "status", {})
|
|
306
|
+
print(json.dumps({"result": type(e).__name__, "why": str(e), "status": st}, default=str) if args.json
|
|
307
|
+
else f"{type(e).__name__} {e}")
|
|
308
|
+
return 1
|
|
309
|
+
except TypeError as e:
|
|
310
|
+
raise SystemExit(f"oep call: {e}") from None
|
|
311
|
+
except c.NotOffered as e:
|
|
312
|
+
raise SystemExit(f"oep call: {e}") from None
|
|
313
|
+
except c.CommandFailed as e:
|
|
314
|
+
out = {"result": "failed", "op": e.op, "fields": e.fields, "payload": e.payload.hex()}
|
|
315
|
+
print(json.dumps(out, default=str) if args.json else f"failed {e}" + ("" if e.fields else f" {e.payload.hex()}"))
|
|
316
|
+
return 1
|
|
317
|
+
except c.CommandRejected as e:
|
|
318
|
+
print(json.dumps({"result": "rejected", "op": e.op, "why": str(e)}) if args.json else f"rejected {e}")
|
|
319
|
+
return 1
|
|
320
|
+
shown = {k: (v.hex() if isinstance(v, bytes) else v) for k, v in r.items()}
|
|
321
|
+
if args.json:
|
|
322
|
+
print(json.dumps({"result": "completed", "revision": r.revision, "fields": shown, "payload": r.payload.hex()}))
|
|
323
|
+
else:
|
|
324
|
+
body = " ".join(f"{k}={v}" for k, v in shown.items()) if shown or (form and form.form) else r.payload.hex()
|
|
325
|
+
print(f"completed rev {r.revision} {body}".rstrip())
|
|
326
|
+
return 0
|
|
327
|
+
|
|
328
|
+
|
|
240
329
|
# ---- oep pins -----------------------------------------------------------------------------------------------------
|
|
241
330
|
|
|
242
331
|
def _pins_cmd(args) -> int:
|
oep_client/call.py
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""Call any interface's op by its number, or by the name and fields its describe gives (oep-core §7.4 op_name /
|
|
2
|
+
op_field) - an interface this host has no module for, such as a sketch's oep::SimpleInterface.
|
|
3
|
+
|
|
4
|
+
from oep_client import link, core
|
|
5
|
+
from oep_client.call import call
|
|
6
|
+
hst = link.open_host("/dev/ttyACM0")
|
|
7
|
+
core.take(hst, 10000, owner="my-test")
|
|
8
|
+
r = call(hst, "io.github.yourname.mine", "add", a=2, b=3)
|
|
9
|
+
r["sum"], r.sum, r.revision # the success fields by name; the interface's revision
|
|
10
|
+
call(hst, "io.github.yourname.mine", 0x10, b"\\x02\\0\\0\\0\\x03\\0\\0\\0").payload # by number, the bytes as they are
|
|
11
|
+
|
|
12
|
+
Errors: InterfaceMissing / RevisionMismatch / OpMissing (all NotOffered: a test can turn them into a skip),
|
|
13
|
+
CommandRejected (the probe refused it: the reason), CommandFailed (completed failed: code and message when the op
|
|
14
|
+
gives them).
|
|
15
|
+
|
|
16
|
+
A job (oep::SimpleInterface::job: it starts work on the probe's worker and answers at once) and its status op:
|
|
17
|
+
|
|
18
|
+
st = call_wait(hst, "io.github.yourname.mine", "collect", count=3, timeout=10)
|
|
19
|
+
st.start_ns, st.end_ns # on the probe's clock (a capture's markers' one)
|
|
20
|
+
# wait=: a stream's wait (acquire.Request(during=...)) keeps the capture collecting while this waits
|
|
21
|
+
|
|
22
|
+
JobFailed (the job's Worker::fail: code, message), JobCancelled, JobTimeout (cancel sent first), ProbeRestarted (its
|
|
23
|
+
boot_id changed while the job ran)."""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import struct
|
|
28
|
+
import time
|
|
29
|
+
from typing import Any, Callable
|
|
30
|
+
|
|
31
|
+
from . import catalog, core, host as h
|
|
32
|
+
|
|
33
|
+
T = catalog.OP_FIELD_TYPES
|
|
34
|
+
LIST = catalog.OP_FIELD_LIST
|
|
35
|
+
_INT = {T["u8"]: "<B", T["u16"]: "<H", T["u32"]: "<I", T["u64"]: "<Q",
|
|
36
|
+
T["i8"]: "<b", T["i16"]: "<h", T["i32"]: "<i", T["i64"]: "<q", T["bool"]: "<B"}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class NotOffered(LookupError):
|
|
40
|
+
"""The probe does not offer what was asked: no such interface, another revision, no such op."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class InterfaceMissing(NotOffered):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class RevisionMismatch(NotOffered):
|
|
48
|
+
def __init__(self, interface: str, revision: int, wanted: int):
|
|
49
|
+
super().__init__(f"{interface} is revision {revision} on this probe, {wanted} asked")
|
|
50
|
+
self.revision, self.wanted = revision, wanted
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class OpMissing(NotOffered):
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CommandRejected(h.Rejected):
|
|
58
|
+
"""The probe refused the op (resolution rejected): `result.detail` is the reason (core §4.3)."""
|
|
59
|
+
|
|
60
|
+
def __init__(self, op: str, result):
|
|
61
|
+
super().__init__(result)
|
|
62
|
+
self.op = op
|
|
63
|
+
self.args = (f"{op}: {result.describe()}",)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class CommandFailed(h.Failed):
|
|
67
|
+
"""The op ran and did not work (completed failed / partial): `fields` the failed part decoded (code, message for
|
|
68
|
+
an oep::SimpleInterface op), `payload` the bytes."""
|
|
69
|
+
|
|
70
|
+
def __init__(self, op: str, result, fields: dict | None):
|
|
71
|
+
why = f"{op}: {result.describe()}"
|
|
72
|
+
if fields and "code" in fields:
|
|
73
|
+
why += f" (code {fields['code']}" + (f": {fields['message']}" if fields.get("message") else "") + ")"
|
|
74
|
+
super().__init__(result, why)
|
|
75
|
+
self.op, self.fields, self.payload = op, fields or {}, result.payload
|
|
76
|
+
self.code, self.message = self.fields.get("code"), self.fields.get("message")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class JobFailed(h.OepError):
|
|
80
|
+
"""The job ended failed: `code` and `message` from its Worker::fail(), `status` the status op's fields."""
|
|
81
|
+
|
|
82
|
+
def __init__(self, op: str, status: dict):
|
|
83
|
+
super().__init__(f"{op}: failed (code {status.get('code')}"
|
|
84
|
+
+ (f": {status['message']}" if status.get("message") else "") + ")")
|
|
85
|
+
self.op, self.status, self.code, self.message = op, status, status.get("code"), status.get("message")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class JobCancelled(h.OepError):
|
|
89
|
+
"""The job stopped on a cancel (`status` the status op's fields)."""
|
|
90
|
+
|
|
91
|
+
def __init__(self, op: str, status: dict):
|
|
92
|
+
super().__init__(f"{op}: cancelled")
|
|
93
|
+
self.op, self.status = op, status
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class JobTimeout(h.OepError, TimeoutError):
|
|
97
|
+
"""The job still ran at the timeout; a cancel was sent (`status` the last status op's fields)."""
|
|
98
|
+
|
|
99
|
+
def __init__(self, op: str, timeout: float, status: dict):
|
|
100
|
+
super().__init__(f"{op}: still running after {timeout} s (cancel sent)")
|
|
101
|
+
self.op, self.status = op, status
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class ProbeRestarted(h.OepError):
|
|
105
|
+
"""The probe's boot_id changed while the job ran (core §6.5): it restarted, the job with it."""
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# The status op's state (oep::Worker::State): the firmware's convention, not the registry's
|
|
109
|
+
JOB_STATES = {"idle": 0, "running": 1, "done": 2, "failed": 3, "cancelled": 4}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class CallResult(dict):
|
|
113
|
+
"""The success fields by name (a dict, and attributes), the raw payload, the interface's revision and the op."""
|
|
114
|
+
|
|
115
|
+
def __init__(self, fields: dict, payload: bytes, revision: int, op: catalog.OpForm | int):
|
|
116
|
+
super().__init__(fields)
|
|
117
|
+
self.payload, self.revision, self.op = payload, revision, op
|
|
118
|
+
|
|
119
|
+
def __getattr__(self, name: str) -> Any:
|
|
120
|
+
try:
|
|
121
|
+
return self[name]
|
|
122
|
+
except KeyError:
|
|
123
|
+
raise AttributeError(name) from None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def encode(fields: list[catalog.OpField], values: dict) -> bytes:
|
|
127
|
+
"""A request's fixed part from values by field name (ints, bools, bytes, str, lists of ints / bools)."""
|
|
128
|
+
missing = [f.name for f in fields if f.name not in values]
|
|
129
|
+
extra = sorted(set(values) - {f.name for f in fields})
|
|
130
|
+
if missing or extra:
|
|
131
|
+
raise TypeError(f"fields {', '.join(f'{f.name}: {f.type_name}' for f in fields) or 'none'}"
|
|
132
|
+
+ (f"; missing {missing}" if missing else "") + (f"; not fields {extra}" if extra else ""))
|
|
133
|
+
out = b""
|
|
134
|
+
for f in fields:
|
|
135
|
+
v = values[f.name]
|
|
136
|
+
if f.type & LIST:
|
|
137
|
+
items = list(v)
|
|
138
|
+
out += struct.pack("<H", len(items)) + b"".join(struct.pack(_INT[f.type & ~LIST], int(x)) for x in items)
|
|
139
|
+
elif f.type == T["bytes"]:
|
|
140
|
+
b = bytes(v)
|
|
141
|
+
out += struct.pack("<H", len(b)) + b
|
|
142
|
+
elif f.type == T["text"]:
|
|
143
|
+
b = str(v).encode()
|
|
144
|
+
out += struct.pack("<H", len(b)) + b
|
|
145
|
+
elif f.type in _INT:
|
|
146
|
+
out += struct.pack(_INT[f.type], int(v))
|
|
147
|
+
else:
|
|
148
|
+
raise TypeError(f"{f.name}: type {f.type} unknown to this host")
|
|
149
|
+
return out
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def decode(fields: list[catalog.OpField], payload: bytes) -> tuple[dict, bytes]:
|
|
153
|
+
"""A fixed part's fields by name, and what follows them (the TLVs)."""
|
|
154
|
+
out, at = {}, 0
|
|
155
|
+
for f in fields:
|
|
156
|
+
if f.type & LIST:
|
|
157
|
+
(n,) = struct.unpack_from("<H", payload, at)
|
|
158
|
+
fmt = _INT[f.type & ~LIST]
|
|
159
|
+
w = struct.calcsize(fmt)
|
|
160
|
+
vals = [struct.unpack_from(fmt, payload, at + 2 + i * w)[0] for i in range(n)]
|
|
161
|
+
out[f.name] = [bool(x) for x in vals] if (f.type & ~LIST) == T["bool"] else vals
|
|
162
|
+
at += 2 + n * w
|
|
163
|
+
elif f.type in (T["bytes"], T["text"]):
|
|
164
|
+
(n,) = struct.unpack_from("<H", payload, at)
|
|
165
|
+
if at + 2 + n > len(payload):
|
|
166
|
+
raise h.ShortPayload(f"{f.name}: {n} bytes, the answer ends before")
|
|
167
|
+
raw = payload[at + 2:at + 2 + n]
|
|
168
|
+
out[f.name] = raw.decode("utf-8", "replace") if f.type == T["text"] else raw
|
|
169
|
+
at += 2 + n
|
|
170
|
+
else:
|
|
171
|
+
fmt = _INT[f.type]
|
|
172
|
+
out[f.name] = struct.unpack_from(fmt, payload, at)[0]
|
|
173
|
+
if f.type == T["bool"]:
|
|
174
|
+
out[f.name] = bool(out[f.name])
|
|
175
|
+
at += struct.calcsize(fmt)
|
|
176
|
+
return out, payload[at:]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def ops(hst: h.Host, interface: str | int) -> dict[int, catalog.OpForm]:
|
|
180
|
+
"""The op_name / op_field an interface's describe gives (by op number; empty when it gives none)."""
|
|
181
|
+
fn = interface if isinstance(interface, int) else _fn(hst, interface)
|
|
182
|
+
return catalog.decode_description(b"".join(catalog.tlv(t, v) for t, v in core.describe(hst, fn))).op_forms
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _fn(hst: h.Host, interface: str) -> int:
|
|
186
|
+
try:
|
|
187
|
+
return core.find(hst, interface)
|
|
188
|
+
except LookupError as e:
|
|
189
|
+
raise InterfaceMissing(str(e)) from None
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def call(hst: h.Host, interface: str, op: int | str, payload: bytes | None = None, *, locked: bool | None = None,
|
|
193
|
+
revision: int | None = None, expect_ms: int = 0, **fields) -> CallResult:
|
|
194
|
+
"""Run `op` (a number, or a name from the describe's op_name) of `interface` on the session `hst` holds.
|
|
195
|
+
payload: the request as bytes (then no fields); otherwise the fields by name, packed as op_field says.
|
|
196
|
+
locked: send it in the session (default: unless op_name says lock_free). revision: the one expected."""
|
|
197
|
+
fn = _fn(hst, interface)
|
|
198
|
+
rev = core.revision(hst, interface, fn)
|
|
199
|
+
if revision is not None and rev != revision:
|
|
200
|
+
raise RevisionMismatch(interface, rev, revision)
|
|
201
|
+
forms = ops(hst, fn)
|
|
202
|
+
if isinstance(op, str):
|
|
203
|
+
form = next((f for f in forms.values() if f.name == op), None)
|
|
204
|
+
if form is None:
|
|
205
|
+
raise OpMissing(f"{interface} has no op named {op!r}" + (f" (it names {', '.join(f.name for f in forms.values())})"
|
|
206
|
+
if forms else " (its describe names none)"))
|
|
207
|
+
code = form.op
|
|
208
|
+
else:
|
|
209
|
+
code, form = op, forms.get(op)
|
|
210
|
+
label = form.name if form else f"0x{code:02x}"
|
|
211
|
+
if not core.offers(hst, fn, code):
|
|
212
|
+
raise OpMissing(f"{interface} does not offer op {label}")
|
|
213
|
+
if payload is None:
|
|
214
|
+
if fields:
|
|
215
|
+
if form is None or not form.form:
|
|
216
|
+
raise TypeError(f"{interface} {label}: its describe gives no fields; pass payload= as bytes")
|
|
217
|
+
payload = encode(form.request, fields)
|
|
218
|
+
elif form is not None and form.form and form.request:
|
|
219
|
+
payload = encode(form.request, fields) # raises: the fields it needs
|
|
220
|
+
else:
|
|
221
|
+
payload = b""
|
|
222
|
+
elif fields:
|
|
223
|
+
raise TypeError("payload= or fields, not both")
|
|
224
|
+
if locked is None:
|
|
225
|
+
locked = not (form is not None and form.lock_free)
|
|
226
|
+
try:
|
|
227
|
+
r = hst.request(fn, code, payload, locked=locked, expect_ms=expect_ms)
|
|
228
|
+
except h.Rejected as e:
|
|
229
|
+
raise CommandRejected(label, e.result) from None
|
|
230
|
+
if r.succeeded:
|
|
231
|
+
got = decode(form.success, r.payload)[0] if form is not None and form.form else {}
|
|
232
|
+
return CallResult(got, r.payload, rev, form or code)
|
|
233
|
+
failed = None
|
|
234
|
+
if r.ran and form is not None and form.form and form.failed:
|
|
235
|
+
try:
|
|
236
|
+
failed = decode(form.failed, r.payload)[0]
|
|
237
|
+
except (struct.error, h.ShortPayload):
|
|
238
|
+
failed = None # a failed answer without its fields (a firmware bug)
|
|
239
|
+
raise CommandFailed(label, r, failed)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def call_wait(hst: h.Host, interface: str, op: int | str, payload: bytes | None = None, *, timeout: float,
|
|
243
|
+
wait: Callable[[float], None] | None = None, interval: float = 0.05, status: str = "status",
|
|
244
|
+
cancel: str = "cancel", revision: int | None = None, **fields) -> CallResult:
|
|
245
|
+
"""Start the job `op` (as call()) and wait for it by its `status` op: the status's fields when it ended done.
|
|
246
|
+
wait(seconds) sleeps between the status reads (time.sleep by default; a stream's wait keeps a capture going)."""
|
|
247
|
+
boot = hst.clock().boot_id
|
|
248
|
+
started = call(hst, interface, op, payload, revision=revision, **fields)
|
|
249
|
+
code = started.op.op if isinstance(started.op, catalog.OpForm) else started.op
|
|
250
|
+
label = started.op.name if isinstance(started.op, catalog.OpForm) else f"0x{code:02x}"
|
|
251
|
+
pause = wait or time.sleep
|
|
252
|
+
deadline = time.monotonic() + timeout
|
|
253
|
+
while True:
|
|
254
|
+
try:
|
|
255
|
+
st = call(hst, interface, status)
|
|
256
|
+
except (h.OepError, OSError):
|
|
257
|
+
_restarted(hst, boot, label)
|
|
258
|
+
raise
|
|
259
|
+
if st["op"] != code or st["state"] == JOB_STATES["idle"]:
|
|
260
|
+
_restarted(hst, boot, label)
|
|
261
|
+
raise ProbeRestarted(f"{label}: the status names op {st['op']} in state {st['state']}, not this job")
|
|
262
|
+
if st["state"] != JOB_STATES["running"]:
|
|
263
|
+
break
|
|
264
|
+
if time.monotonic() >= deadline:
|
|
265
|
+
try:
|
|
266
|
+
call(hst, interface, cancel)
|
|
267
|
+
except (h.OepError, OSError):
|
|
268
|
+
pass # the timeout is the error to report
|
|
269
|
+
raise JobTimeout(label, timeout, dict(st))
|
|
270
|
+
pause(interval)
|
|
271
|
+
if st["state"] == JOB_STATES["failed"]:
|
|
272
|
+
raise JobFailed(label, dict(st))
|
|
273
|
+
if st["state"] == JOB_STATES["cancelled"]:
|
|
274
|
+
raise JobCancelled(label, dict(st))
|
|
275
|
+
return st
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _restarted(hst: h.Host, boot: int, label: str) -> None:
|
|
279
|
+
try:
|
|
280
|
+
now = hst.clock().boot_id
|
|
281
|
+
except (h.OepError, OSError):
|
|
282
|
+
return
|
|
283
|
+
if now != boot:
|
|
284
|
+
raise ProbeRestarted(f"{label}: the probe restarted while the job ran (boot_id {boot:#x} -> {now:#x})")
|
oep_client/catalog.py
CHANGED
|
@@ -32,6 +32,13 @@ MIN_CLOCK_HZ = _COMMON["min_clock_hz"] # u32
|
|
|
32
32
|
FEATURES = _COMMON["features"] # u32, bits defined by the interface
|
|
33
33
|
CHANNEL_GROUP = _COMMON["channel_group"] # group(u8) n(u8) then n x (role(u8), channel(u16)): a fixed pin set
|
|
34
34
|
OPS = _COMMON["ops"] # base(u8) bitmap: the ops this fn offers (core §1.2, §7.4)
|
|
35
|
+
OP_NAME = _COMMON["op_name"] # op(u8) flags(u8) name: an op's name (core §7.4, optional)
|
|
36
|
+
OP_FIELD = _COMMON["op_field"] # op(u8) part(u8) type(u8) name: one field of an op's fixed part (core §7.4)
|
|
37
|
+
OP_NAME_FORM = reg.CORE.enum["op_name_flag"]["form"]
|
|
38
|
+
OP_NAME_LOCK_FREE = reg.CORE.enum["op_name_flag"]["lock_free"]
|
|
39
|
+
OP_FIELD_PARTS = reg.CORE.enum["op_field_part"] # request / success / failed
|
|
40
|
+
OP_FIELD_TYPES = reg.CORE.enum["op_field_type"] # u8 ... text, and the list bit
|
|
41
|
+
OP_FIELD_LIST = OP_FIELD_TYPES["list"]
|
|
35
42
|
assert OPS == m.TAG_OPS
|
|
36
43
|
CRITICAL = 0x80
|
|
37
44
|
INTERFACE_TAG_FIRST = 0x40
|
|
@@ -197,6 +204,50 @@ def text(tag: int, s: str) -> bytes:
|
|
|
197
204
|
return tlv(tag, s.encode("ascii"))
|
|
198
205
|
|
|
199
206
|
|
|
207
|
+
class OpField:
|
|
208
|
+
"""One field of an op's fixed part (core §7.4 op_field): its name and type (registry op_field_type)."""
|
|
209
|
+
|
|
210
|
+
def __init__(self, name: str, type: int):
|
|
211
|
+
self.name, self.type = name, type
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def type_name(self) -> str:
|
|
215
|
+
names = {v: k for k, v in OP_FIELD_TYPES.items() if k != "list"}
|
|
216
|
+
base = self.type & ~OP_FIELD_LIST
|
|
217
|
+
label = names.get(base, f"type{base}")
|
|
218
|
+
return label + "[]" if self.type & OP_FIELD_LIST else label
|
|
219
|
+
|
|
220
|
+
def __repr__(self) -> str:
|
|
221
|
+
return f"{self.name}: {self.type_name}"
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@dataclass
|
|
225
|
+
class OpForm:
|
|
226
|
+
"""An op's name and, with form, its request / success / failed fixed parts' fields (core §7.4)."""
|
|
227
|
+
|
|
228
|
+
op: int
|
|
229
|
+
name: str
|
|
230
|
+
flags: int
|
|
231
|
+
request: list[OpField] = field(default_factory=list)
|
|
232
|
+
success: list[OpField] = field(default_factory=list)
|
|
233
|
+
failed: list[OpField] = field(default_factory=list)
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def form(self) -> bool:
|
|
237
|
+
return bool(self.flags & OP_NAME_FORM)
|
|
238
|
+
|
|
239
|
+
@property
|
|
240
|
+
def lock_free(self) -> bool:
|
|
241
|
+
return bool(self.flags & OP_NAME_LOCK_FREE)
|
|
242
|
+
|
|
243
|
+
def signature(self) -> str:
|
|
244
|
+
"""measure(mode: u8) -> temp: i32, hum: u32 (the name alone without form)"""
|
|
245
|
+
if not self.form:
|
|
246
|
+
return self.name
|
|
247
|
+
s = f"{self.name}({', '.join(map(repr, self.request))})"
|
|
248
|
+
return s + (" -> " + ", ".join(map(repr, self.success)) if self.success else "")
|
|
249
|
+
|
|
250
|
+
|
|
200
251
|
@dataclass
|
|
201
252
|
class Description:
|
|
202
253
|
"""Common tags decoded; interface-specific and unknown tags kept raw, in order."""
|
|
@@ -212,11 +263,13 @@ class Description:
|
|
|
212
263
|
specific: list[tuple[int, bytes]] = field(default_factory=list)
|
|
213
264
|
unknown_critical: list[int] = field(default_factory=list)
|
|
214
265
|
broken: list[int] = field(default_factory=list) # common tags whose value is not their defined length (not used)
|
|
266
|
+
op_forms: dict[int, OpForm] = field(default_factory=dict) # op -> its op_name / op_field (core §7.4)
|
|
215
267
|
|
|
216
268
|
|
|
217
269
|
def decode_description(data: bytes) -> Description:
|
|
218
270
|
d = Description()
|
|
219
271
|
seen: set[int] = set()
|
|
272
|
+
fields: list[tuple[int, int, int, str]] = [] # op_field (op, part, type, name), in the describe's order
|
|
220
273
|
for tag, value in split_tlv(data):
|
|
221
274
|
if tag & CRITICAL:
|
|
222
275
|
continue # bit 7 in an answer: an unknown tag, skipped (core §2.2)
|
|
@@ -248,11 +301,28 @@ def decode_description(data: bytes) -> Description:
|
|
|
248
301
|
d.ops_invalid = why
|
|
249
302
|
else:
|
|
250
303
|
d.ops = unpack_ops(value)
|
|
304
|
+
elif t == OP_NAME:
|
|
305
|
+
if len(value) < 3:
|
|
306
|
+
d.broken.append(tag)
|
|
307
|
+
continue
|
|
308
|
+
if value[0] in d.op_forms:
|
|
309
|
+
continue # a second op_name for an op: the first is used
|
|
310
|
+
d.op_forms[value[0]] = OpForm(value[0], value[2:].decode("ascii", "replace"), value[1])
|
|
311
|
+
elif t == OP_FIELD:
|
|
312
|
+
if len(value) < 4:
|
|
313
|
+
d.broken.append(tag)
|
|
314
|
+
continue
|
|
315
|
+
fields.append((value[0], value[1], value[2], value[3:].decode("ascii", "replace")))
|
|
251
316
|
elif t >= INTERFACE_TAG_FIRST:
|
|
252
317
|
d.specific.append((tag, value))
|
|
253
318
|
elif tag & CRITICAL:
|
|
254
319
|
d.unknown_critical.append(tag)
|
|
255
320
|
# an unknown non-critical common tag is skipped
|
|
321
|
+
parts = {OP_FIELD_PARTS["request"]: "request", OP_FIELD_PARTS["success"]: "success", OP_FIELD_PARTS["failed"]: "failed"}
|
|
322
|
+
for op, part, type_, name in fields: # only an op named with form takes its fields
|
|
323
|
+
form = d.op_forms.get(op)
|
|
324
|
+
if form is not None and form.form and part in parts:
|
|
325
|
+
getattr(form, parts[part]).append(OpField(name, type_))
|
|
256
326
|
for role in d.roles:
|
|
257
327
|
d.roles[role] = sorted(set(d.roles[role]))
|
|
258
328
|
if d.ops_invalid:
|
|
@@ -145,7 +145,7 @@ def checker_sources_sha256():
|
|
|
145
145
|
for name in CHECKER_SOURCES}
|
|
146
146
|
|
|
147
147
|
|
|
148
|
-
REVIEWED_CORE_SHA256 = '
|
|
148
|
+
REVIEWED_CORE_SHA256 = '81cffc532f2626812b109d0aa5eef81636799011f410a8bc2fb9ea520fcbf4bc' # oep-spec docs/oep-core.ja.md
|
|
149
149
|
|
|
150
150
|
|
|
151
151
|
def require_reviewed_contract(registry, spec=None):
|
|
@@ -8,7 +8,7 @@ import struct
|
|
|
8
8
|
from . import ops, require, tlvs
|
|
9
9
|
from .. import conformance as _helpers
|
|
10
10
|
|
|
11
|
-
REVIEWED_CORE_SHA256 = '
|
|
11
|
+
REVIEWED_CORE_SHA256 = '81cffc532f2626812b109d0aa5eef81636799011f410a8bc2fb9ea520fcbf4bc' # oep-spec docs/oep-core.ja.md
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class CoreSmokeChecks:
|
|
@@ -4,7 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
import struct
|
|
5
5
|
from . import require,spec_identity
|
|
6
6
|
|
|
7
|
-
REVIEWED_CORE_SHA256='
|
|
7
|
+
REVIEWED_CORE_SHA256='81cffc532f2626812b109d0aa5eef81636799011f410a8bc2fb9ea520fcbf4bc'
|
|
8
8
|
CORE_OPS={'confirm':1,'list':2,'describe':3,'clock':4,'open':16,'end':17,'keepalive':18,'lock_state':19}
|
|
9
9
|
# the settings' ops (core §13.5 / §13.7, transports §7.3): their numbers reviewed, their results not read here
|
|
10
10
|
SETTINGS_OPS={'get':32,'set':33,'unset':34,'save':35,'state':36,'network':37}
|
|
@@ -9,8 +9,8 @@ from pathlib import Path
|
|
|
9
9
|
from . import require,spec_identity
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
REVIEWED={'docs/oep-core.ja.md':'
|
|
13
|
-
'docs/oep-transports.ja.md':'
|
|
12
|
+
REVIEWED={'docs/oep-core.ja.md':'81cffc532f2626812b109d0aa5eef81636799011f410a8bc2fb9ea520fcbf4bc',
|
|
13
|
+
'docs/oep-transports.ja.md':'a22754ee25997058be0e698bd62ea132537627914f38a06caffd48e3d79f096e'}
|
|
14
14
|
QUICK_MS=1000 # core_quick_op_max_ms
|
|
15
15
|
|
|
16
16
|
|
|
@@ -12,11 +12,11 @@ from . import require, spec_identity
|
|
|
12
12
|
REVIEWED = {
|
|
13
13
|
'interfaces/oep-if-plan.ja.md': 'da8bd60af53499573c07bc33dbd8c4df93eb61a93b91dc57907f26620ae36bbf',
|
|
14
14
|
'interfaces/oep-if-fixture.ja.md': '777a972349a14d1efa6e4575d3118b4e0566b81a6f22320401d529e4dca585f1',
|
|
15
|
-
'docs/oep-core.ja.md': '
|
|
15
|
+
'docs/oep-core.ja.md': '81cffc532f2626812b109d0aa5eef81636799011f410a8bc2fb9ea520fcbf4bc',
|
|
16
16
|
'interfaces/oep-if-debug.ja.md': '6f21171b494e67d50bfc78b4da9e16949033c9e24d960efb42887335da021681',
|
|
17
17
|
'interfaces/oep-if-capture.ja.md': 'f1035dc14cf469d929d782dbe710fc2c1e1567006da41a001abe3c5c25761324',
|
|
18
18
|
}
|
|
19
|
-
REVIEWED_REGISTRY_SHA256='
|
|
19
|
+
REVIEWED_REGISTRY_SHA256='183922c36d78c0173cedece530410478dc3f1de0094d1922d3a960e752863d1e'
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def reviewed_identity(spec):
|
|
@@ -6,8 +6,8 @@ from .. import registry as _reg
|
|
|
6
6
|
SAVE=_reg.CORE.op['save'] # fn 0's save (core §13.7)
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
REVIEWED={'docs/oep-core.ja.md':'
|
|
10
|
-
'docs/oep-transports.ja.md':'
|
|
9
|
+
REVIEWED={'docs/oep-core.ja.md':'81cffc532f2626812b109d0aa5eef81636799011f410a8bc2fb9ea520fcbf4bc',
|
|
10
|
+
'docs/oep-transports.ja.md':'a22754ee25997058be0e698bd62ea132537627914f38a06caffd48e3d79f096e'}
|
|
11
11
|
QUICK_MS=1000 # core_quick_op_max_ms
|
|
12
12
|
|
|
13
13
|
|
oep_client/dump.py
CHANGED
|
@@ -179,7 +179,11 @@ def describe_offer(o: Offer) -> dict:
|
|
|
179
179
|
if getattr(d, key) is not None:
|
|
180
180
|
out[key] = getattr(d, key)
|
|
181
181
|
if d.ops is not None:
|
|
182
|
-
out["ops"] =
|
|
182
|
+
out["ops"] = [d.op_forms[op].name if op in d.op_forms else n
|
|
183
|
+
for op, n in zip(sorted(d.ops), interfaces.op_names(name, d.ops))]
|
|
184
|
+
forms = [f.signature() for op, f in sorted(d.op_forms.items()) if f.form]
|
|
185
|
+
if forms:
|
|
186
|
+
out["op_forms"] = forms
|
|
183
187
|
if d.features is not None:
|
|
184
188
|
out["features"] = _features(d.features, known.features if known else {})
|
|
185
189
|
specific = {}
|
|
@@ -241,6 +245,8 @@ def to_text(caps: Capabilities) -> str:
|
|
|
241
245
|
lines.append(f"{pad} " + ", ".join(limits))
|
|
242
246
|
if "ops" in r:
|
|
243
247
|
lines.append(f"{pad} ops: " + (", ".join(r["ops"]) or "none"))
|
|
248
|
+
for sig in r.get("op_forms", []):
|
|
249
|
+
lines.append(f"{pad} {sig}")
|
|
244
250
|
if r.get("features"):
|
|
245
251
|
lines.append(f"{pad} features: " + ", ".join(r["features"]))
|
|
246
252
|
for k, v in r.get("declares", {}).items():
|
oep_client/registry.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
from types import SimpleNamespace as _NS
|
|
5
5
|
|
|
6
|
-
REGISTRY_HASH = "
|
|
6
|
+
REGISTRY_HASH = "183922c36d78c017"
|
|
7
7
|
SCHEMA = 1
|
|
8
8
|
PROTOCOL_REVISION = 1
|
|
9
9
|
CONFIRM_REQUEST_MAGIC = 'OEP?'
|
|
@@ -19,16 +19,16 @@ RESOLUTIONS = {"rejected": 0x00, "completed": 0x01}
|
|
|
19
19
|
OUTCOMES = {"success": 0x00, "failed": 0x01, "partial": 0x02}
|
|
20
20
|
REJECT_REASONS = {"unknown_function": 0x01, "unknown_operation": 0x02, "malformed": 0x03, "unavailable": 0x04, "window_exceeded": 0x06, "no_session": 0x07, "locked": 0x08, "session_required": 0x09, "no_resource": 0x0A, "unsupported": 0x0B, "result_lost": 0x0C}
|
|
21
21
|
STATUS = {"ok": 0x00, "wait": 0x01, "line": 0x02, "fault": 0x03, "timeout": 0x04, "state": 0x05}
|
|
22
|
-
DESCRIBE_COMMON = {"role_channels": 0x01, "max_clock_hz": 0x02, "max_length": 0x03, "min_clock_hz": 0x04, "features": 0x05, "channel_group": 0x06, "ops": 0x07, "setting": 0x08, "setting_choice": 0x09}
|
|
22
|
+
DESCRIBE_COMMON = {"role_channels": 0x01, "max_clock_hz": 0x02, "max_length": 0x03, "min_clock_hz": 0x04, "features": 0x05, "channel_group": 0x06, "ops": 0x07, "setting": 0x08, "setting_choice": 0x09, "op_name": 0x0A, "op_field": 0x0B}
|
|
23
23
|
TIMING = {"probe_frame_gap_ms": 0xC8, "uart_bridge_boot_baud": 0x1C200, "port_speed_idle_ms": 0xBB8, "host_wait_add_ms": 0x3E8, "core_quick_op_max_ms": 0x3E8, "notify_pending_max_frames": 0x02, "port_speed_switch_wait_ms": 0x14}
|
|
24
24
|
USB = {"project_vid": 0x1209, "project_pid": 0x4F45, "vendor_bulk_class": 0xFF, "vendor_bulk_subclass": 0x4F, "vendor_bulk_protocol": 0x45, "hid_usage_page": 0xFF4F, "hid_usage": 0x45}
|
|
25
|
-
LIMITS = {"request_header_bytes": 0x0A, "result_header_bytes": 0x05, "corr_max": 0xFFFF, "corr_half_range": 0x8000, "resource_id_bytes": 0x04, "resource_id_max": 0xFFFFFFFF, "lease_min_ms": 0x3E8, "lease_max_ms": 0xEA60, "owner_max_bytes": 0x20, "unit_id_max_bytes": 0x20, "wifi_ssid_max_bytes": 0x20, "wifi_min_max_frame": 0x52, "label_max_bytes": 0x20, "setting_name_max_bytes": 0x30, "setting_choice_name_max_bytes": 0x10, "channel_name_max_bytes": 0x10, "setting_get_overhead_bytes": 0x12, "interface_name_max_bytes": 0x30, "port_speed_tolerance_pct": 0x02, "uart_baud_tolerance_pct": 0x05, "max_op_ms_max": 0x927C0}
|
|
25
|
+
LIMITS = {"request_header_bytes": 0x0A, "result_header_bytes": 0x05, "corr_max": 0xFFFF, "corr_half_range": 0x8000, "resource_id_bytes": 0x04, "resource_id_max": 0xFFFFFFFF, "lease_min_ms": 0x3E8, "lease_max_ms": 0xEA60, "owner_max_bytes": 0x20, "unit_id_max_bytes": 0x20, "wifi_ssid_max_bytes": 0x20, "wifi_min_max_frame": 0x52, "label_max_bytes": 0x20, "setting_name_max_bytes": 0x30, "op_name_max_bytes": 0x18, "setting_choice_name_max_bytes": 0x10, "channel_name_max_bytes": 0x10, "setting_get_overhead_bytes": 0x12, "interface_name_max_bytes": 0x30, "port_speed_tolerance_pct": 0x02, "uart_baud_tolerance_pct": 0x05, "max_op_ms_max": 0x927C0}
|
|
26
26
|
COMMON = _NS(enum={"target_id_scheme": {"dmi_7f": 0x01, "dpidr": 0x02}, "read_from": {"position": 0x00, "oldest": 0x01, "now": 0x02, "last_mark": 0x03}, "read_flags": {"more": 0x01, "gap": 0x02}, "mark_kind": {"lost": 0x01, "clear": 0x02, "host": 0x03}, "mark_detail_lost": {"overflow": 0x01, "framing": 0x02, "parity": 0x03}})
|
|
27
27
|
|
|
28
28
|
INTERFACES = {}
|
|
29
29
|
CORE = _NS(op={"confirm": 0x01, "list": 0x02, "describe": 0x03, "clock": 0x04, "open": 0x10, "end": 0x11, "keepalive": 0x12, "lock_state": 0x13, "get": 0x20, "set": 0x21, "unset": 0x22, "save": 0x23, "state": 0x24, "network": 0x25}, lock_free={0x01, 0x02, 0x03, 0x04, 0x10, 0x13, 0x20, 0x24, 0x25},
|
|
30
|
-
tlv={"confirm_answer": {"transport": 0x01}, "open": {"owner": 0x01}, "lock_state_answer": {"owner": 0x01}, "locked_payload": {"owner": 0x01}, "unavailable_payload": {"cause": 0x01, "channel": 0x02, "item": 0x03, "fn": 0x05}, "unsupported_payload": {"supported": 0x01, "channel": 0x02, "item": 0x03, "fn": 0x05}, "describe": {"firmware": 0x40, "model": 0x41, "unit_id": 0x42, "channels": 0x43, "channel_name": 0x44, "netif": 0x45, "transport": 0x49, "chip": 0x4C, "max_op_ms": 0x4D}}, event={}, enum={"unavailable_cause": {"pin_in_use": 0x01, "limit": 0x02, "storage_full": 0x03, "bound_in_group": 0x04, "held_by_settings": 0x05, "wrong_state": 0x06, "policy_denied": 0x07}, "transport_kind": {"uart_bridge": 0x01, "usb_cdc": 0x02, "usb_serial_jtag": 0x03, "vendor_bulk": 0x04, "hid": 0x05, "tcp": 0x06}, "route_kind": {"bridge": 0x01, "cdc": 0x02, "usj": 0x03, "bulk": 0x04, "hid": 0x05, "tcp": 0x06}, "setting_form": {"bytes": 0x01, "text": 0x02, "u32": 0x03, "ipv4": 0x04, "bool": 0x05, "enum": 0x06, "channel": 0x07, "transport": 0x08, "fn": 0x09}, "setting_key_kind": {"none": 0x00, "channel": 0x01, "index": 0x02, "transport": 0x03, "netif": 0x04}, "setting_flag": {"secret": 0x01, "restart": 0x02}, "storage_state": {"none": 0x00, "applied": 0x01, "unreadable": 0x02, "partial": 0x03}, "storage_reason": {"none": 0x00, "form": 0x01, "item": 0x02, "capacity": 0x03}, "channel_idle": {"hi_z": 0x00, "pull_up": 0x01, "pull_down": 0x02, "low": 0x03, "high": 0x04}, "ipv4_mode": {"dhcp": 0x00, "static": 0x01}, "network_state": {"off": 0x00, "connecting": 0x01, "up": 0x02, "waiting": 0x03}, "network_reason": {"none": 0x00, "not_found": 0x01, "auth": 0x02, "no_address": 0x03, "other": 0x04}}, own={},
|
|
31
|
-
line_names={'swdio': 'the debug data line (2 wires: rvswd, swd) (Host only)', 'swclk': 'the debug clock line (2 wires: rvswd, swd) (Host only)', 'swio': 'the one debug wire (swio) (Host only)', 'rx': 'the fixture UART receive, wired to the target TX (Host only)', 'tx': 'the fixture UART transmit, wired to the target RX (Host only)', 'nrst': 'the target reset line (Host only)', 'power_hi': 'target power on when high (Host only)', 'power_lo': 'target power on when low (Host only)'}, names={'netif': {'wifi': 'Wi-Fi station', 'ethernet': 'wired Ethernet'}}, settings={'channel.{}.label': "text 1-32, channel; the pin's label (NRST)", 'channel.{}.idle': 'enum channel_idle, channel; the idle state (core §8)', 'channel.{}.disable': 'bool, channel; true: the probe neither drives nor configures the pin', 'wifi.{}.ssid': "text 1-32, index (entry); the network's name", 'wifi.{}.passphrase': 'text 8-64, index (entry), secret; 8-63 printable passphrase or 64 hex digits', 'ipv4.{}.mode': 'enum ipv4_mode, netif; dhcp when absent', 'ipv4.{}.address': 'ipv4, netif; static address', 'ipv4.{}.prefix': 'u32 1-32, netif; static prefix length', 'ipv4.{}.gateway': 'ipv4, netif; static gateway, none when absent', 'ipv4.{}.dns': 'ipv4, netif; static DNS server, none when absent'})
|
|
30
|
+
tlv={"confirm_answer": {"transport": 0x01}, "open": {"owner": 0x01}, "lock_state_answer": {"owner": 0x01}, "locked_payload": {"owner": 0x01}, "unavailable_payload": {"cause": 0x01, "channel": 0x02, "item": 0x03, "fn": 0x05}, "unsupported_payload": {"supported": 0x01, "channel": 0x02, "item": 0x03, "fn": 0x05}, "describe": {"firmware": 0x40, "model": 0x41, "unit_id": 0x42, "channels": 0x43, "channel_name": 0x44, "netif": 0x45, "transport": 0x49, "chip": 0x4C, "max_op_ms": 0x4D}}, event={}, enum={"unavailable_cause": {"pin_in_use": 0x01, "limit": 0x02, "storage_full": 0x03, "bound_in_group": 0x04, "held_by_settings": 0x05, "wrong_state": 0x06, "policy_denied": 0x07}, "transport_kind": {"uart_bridge": 0x01, "usb_cdc": 0x02, "usb_serial_jtag": 0x03, "vendor_bulk": 0x04, "hid": 0x05, "tcp": 0x06}, "route_kind": {"bridge": 0x01, "cdc": 0x02, "usj": 0x03, "bulk": 0x04, "hid": 0x05, "tcp": 0x06}, "setting_form": {"bytes": 0x01, "text": 0x02, "u32": 0x03, "ipv4": 0x04, "bool": 0x05, "enum": 0x06, "channel": 0x07, "transport": 0x08, "fn": 0x09}, "setting_key_kind": {"none": 0x00, "channel": 0x01, "index": 0x02, "transport": 0x03, "netif": 0x04}, "setting_flag": {"secret": 0x01, "restart": 0x02}, "op_name_flag": {"form": 0x01, "lock_free": 0x02}, "op_field_part": {"request": 0x00, "success": 0x01, "failed": 0x02}, "op_field_type": {"u8": 0x01, "u16": 0x02, "u32": 0x03, "u64": 0x04, "i8": 0x05, "i16": 0x06, "i32": 0x07, "i64": 0x08, "bool": 0x09, "bytes": 0x0A, "text": 0x0B, "list": 0x80}, "storage_state": {"none": 0x00, "applied": 0x01, "unreadable": 0x02, "partial": 0x03}, "storage_reason": {"none": 0x00, "form": 0x01, "item": 0x02, "capacity": 0x03}, "channel_idle": {"hi_z": 0x00, "pull_up": 0x01, "pull_down": 0x02, "low": 0x03, "high": 0x04}, "ipv4_mode": {"dhcp": 0x00, "static": 0x01}, "network_state": {"off": 0x00, "connecting": 0x01, "up": 0x02, "waiting": 0x03}, "network_reason": {"none": 0x00, "not_found": 0x01, "auth": 0x02, "no_address": 0x03, "other": 0x04}}, own={},
|
|
31
|
+
line_names={'swdio': 'the debug data line (2 wires: rvswd, swd) (Host only)', 'swclk': 'the debug clock line (2 wires: rvswd, swd) (Host only)', 'swio': 'the one debug wire (swio) (Host only)', 'rx': 'the fixture UART receive, wired to the target TX (Host only)', 'tx': 'the fixture UART transmit, wired to the target RX (Host only)', 'nrst': 'the target reset line (Host only)', 'power_hi': 'target power on when high (Host only)', 'power_lo': 'target power on when low (Host only)'}, names={'netif': {'wifi': 'Wi-Fi station', 'ethernet': 'wired Ethernet'}}, settings={'channel.{}.label': "text 1-32, channel; the pin's label (NRST)", 'channel.{}.idle': 'enum channel_idle, channel; the idle state (core §8)', 'channel.{}.disable': 'bool, channel; true: the probe neither drives nor configures the pin', 'wifi.{}.ssid': "text 1-32, index (entry); the network's name", 'wifi.{}.passphrase': 'text 8-64, index (entry), secret; 8-63 printable passphrase or 64 hex digits', 'ipv4.{}.mode': 'enum ipv4_mode, netif; dhcp when absent', 'ipv4.{}.address': 'ipv4, netif; static address', 'ipv4.{}.prefix': 'u32 1-32, netif; static prefix length', 'ipv4.{}.gateway': 'ipv4, netif; static gateway, none when absent', 'ipv4.{}.dns': 'ipv4, netif; static DNS server, none when absent', 'usb.transports': "enum (the probe's choices: transport short names in describe order joined by -, a count after a repeated one: bulk-hid-cdc2), restart; the OEP transports the probe's own USB device offers after a restart, its default set when absent (transports §3.1)"})
|
|
32
32
|
PROBE_PLAN = _NS(name="oep.probe.plan", revision=1, target=None, op={"plan_apply": 0x10, "plan_release": 0x11}, lock_free=set(),
|
|
33
33
|
tlv={"plan_apply": {"role_assignment": 0x10}, "describe": {"plan_roles": 0x40}}, event={}, enum={}, own={},
|
|
34
34
|
line_names={}, names={}, settings={})
|
oep_client/virtual_bench.py
CHANGED
|
@@ -320,7 +320,9 @@ NETWORK_ITEMS = (Item("wifi.{}.ssid", "text", "index", WIFI_ENTRIES, reg.LIMITS[
|
|
|
320
320
|
Item("ipv4.{}.mode", "enum", "netif", choices=tuple(choices_of(reg.CORE.enum["ipv4_mode"]).items())),
|
|
321
321
|
Item("ipv4.{}.address", "ipv4", "netif"), Item("ipv4.{}.prefix", "u32", "netif"),
|
|
322
322
|
Item("ipv4.{}.gateway", "ipv4", "netif"), Item("ipv4.{}.dns", "ipv4", "netif"))
|
|
323
|
-
|
|
323
|
+
# transports §3.1: a probe whose own USB device can start more than one set of transports (none of the profiles here)
|
|
324
|
+
USB_ITEMS = (Item("usb.transports", "enum", flags=FLAG["restart"], choices=((0, "bulk-hid-cdc"), (1, "bulk-hid"))),)
|
|
325
|
+
assert {i.name for i in CORE_ITEMS + NETWORK_ITEMS + USB_ITEMS} == set(_CS), "the core's items as the registry names them"
|
|
324
326
|
HOLD = Item("channel.{}.hold", "bool", "channel")
|
|
325
327
|
REST = Item("channel.{}.rest", "enum", "channel", choices=tuple(choices_of(reg.WIRE_RVSWD.enum["rest"]).items()))
|
|
326
328
|
|
|
@@ -672,6 +674,16 @@ def rp2350_pins() -> VirtualProbe:
|
|
|
672
674
|
|
|
673
675
|
|
|
674
676
|
STAND_IN = f"{NS}.stand-in"
|
|
677
|
+
# The stand-in ops' names and fields (core §7.4 op_name / op_field): write(value: u32), read() -> value: u32 (lock-free)
|
|
678
|
+
_OPN, _OPF = reg.DESCRIBE_COMMON["op_name"], reg.DESCRIBE_COMMON["op_field"]
|
|
679
|
+
_FLAG, _PART, _TYPE = (reg.CORE.enum[k] for k in ("op_name_flag", "op_field_part", "op_field_type"))
|
|
680
|
+
_WRITE, _READ = STAND_IN_OPS
|
|
681
|
+
STAND_IN_FORMS = (
|
|
682
|
+
catalog.tlv(_OPN, bytes([_WRITE, _FLAG["form"]]) + b"write"),
|
|
683
|
+
catalog.tlv(_OPF, bytes([_WRITE, _PART["request"], _TYPE["u32"]]) + b"value"),
|
|
684
|
+
catalog.tlv(_OPN, bytes([_READ, _FLAG["form"] | _FLAG["lock_free"]]) + b"read"),
|
|
685
|
+
catalog.tlv(_OPF, bytes([_READ, _PART["success"], _TYPE["u32"]]) + b"value"),
|
|
686
|
+
)
|
|
675
687
|
|
|
676
688
|
|
|
677
689
|
def _again(probe: VirtualProbe, offered: list[Offered]) -> VirtualProbe:
|
|
@@ -731,7 +743,7 @@ def with_stand_in(probe: VirtualProbe) -> VirtualProbe:
|
|
|
731
743
|
"""The profile plus one fn (the next number) the endpoint does not simulate: it answers only the two stand-in
|
|
732
744
|
operations (endpoint.TOY_WRITE / TOY_READ), for tests of the session rules. VIRTUAL BENCH ONLY."""
|
|
733
745
|
fn = max(o.fn for o in probe.offered) + 1
|
|
734
|
-
return _again(probe, list(probe.offered) + [Offered(fn, 0, STAND_IN)])
|
|
746
|
+
return _again(probe, list(probe.offered) + [Offered(fn, 0, STAND_IN, STAND_IN_FORMS)])
|
|
735
747
|
|
|
736
748
|
|
|
737
749
|
ATTACH_HALT = reg.WIRE_RVSWD.enum["features"]["attach_halt"] # attach method 1 (debug §3)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: oep-client-python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.54
|
|
4
4
|
Summary: Open Embedded Probe (OEP) v1 host: serial / USB / TCP transports, the session rules, the standard interfaces, and a virtual bench
|
|
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
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
oep_client/__init__.py,sha256=
|
|
2
|
-
oep_client/__main__.py,sha256=
|
|
1
|
+
oep_client/__init__.py,sha256=xps99W-P4elEjl5RY8jG6Pr_sjJfRFSzMeiUo3p0pco,164
|
|
2
|
+
oep_client/__main__.py,sha256=SMeCK8A4Ru8tvCW3u4cXV1oFI_eUyEUJnBZukCZeVNg,36319
|
|
3
3
|
oep_client/acquire.py,sha256=CXjK5s-kjdCoGg9Yt_46rr4te6T2fbX4tl89PjOnURA,102541
|
|
4
4
|
oep_client/arm.py,sha256=zsgq4jsGzroE_9yF5D48rJF7dAe07MR8RY4RMCgjiwc,16411
|
|
5
|
+
oep_client/call.py,sha256=4h8jGv_12CaTIfjOVlNXJYcu_ZCiPpPAwGdvbUv86Vc,12806
|
|
5
6
|
oep_client/capture.py,sha256=92ZznVWu2i3_5mIk0bMSIeqNqJ0LpjbMi3JRb2qrYrs,75848
|
|
6
7
|
oep_client/capture_blocks.py,sha256=B8T2dKozQfGTWg4noG0cZ0DZ9KrrfiFFdbTrBKWfWhY,17330
|
|
7
|
-
oep_client/catalog.py,sha256=
|
|
8
|
+
oep_client/catalog.py,sha256=aWzWWLAzeGcpNqTz8ZM2AupTNLY1jsADr3CjHrzx-_0,13960
|
|
8
9
|
oep_client/ch32_flash.py,sha256=lY_HKxf2foSfhNda91lcWefgRnY9ebTljsg4zJEj-Ks,12885
|
|
9
10
|
oep_client/cobs.py,sha256=RbdPrqgzD3SukxOIXX9m2dZpVTud8xAYYEhm3rcW2nA,2513
|
|
10
11
|
oep_client/console.py,sha256=hDxoaXDCzk6KiOo-Y3RqC4yZuLPkCXOVi886_C4xXaE,12910
|
|
11
12
|
oep_client/core.py,sha256=o7qJjZc68RS9p91TfV3aLsLcTq-jWBO1atB9kGL_tkA,22188
|
|
12
13
|
oep_client/discovery.py,sha256=oES0_NkR1Hc9E_cgPcfW8qfevQcVuYmayMaHuqufdO8,20180
|
|
13
|
-
oep_client/dump.py,sha256=
|
|
14
|
+
oep_client/dump.py,sha256=ic3Nv_ZX1iLjgSkjyFwgXVkjCnPwClnkqZnagQY9N-c,13317
|
|
14
15
|
oep_client/endpoint.py,sha256=A4fCeCwMCXOpsY4zeVy1LG_rG5SvYfImKRP153dIC5I,237768
|
|
15
16
|
oep_client/fixture.py,sha256=gMc6TRAlXBEMBFg6aCBkGGJvbIJsGBSfUdk0genD_Y8,34397
|
|
16
17
|
oep_client/frames.py,sha256=FM1AGn5NY_e-r1yKBsDkEEOqNrk5XTrlKA5Mq6Ycqiw,4984
|
|
@@ -28,7 +29,7 @@ oep_client/pins.py,sha256=iXswbqvze0KED1WNHLnjG5sn-_SjoJrIvcjj84LU_jU,53670
|
|
|
28
29
|
oep_client/probe_log.py,sha256=gAamfON2M7lMH3xOn4Wpmes_Wp0H4c2EBNbpXjIvCMM,4927
|
|
29
30
|
oep_client/pytest_conformance.py,sha256=-bcJU2E1UE4xjQ7HZjaIgtp0l65cIibBTNFnaW2tews,2929
|
|
30
31
|
oep_client/pytest_rp2040_peer.py,sha256=9buM_lpXx9lhi-H-tIWAfS5xyDmgGUBwfHlOkBG3jiA,2873
|
|
31
|
-
oep_client/registry.py,sha256=
|
|
32
|
+
oep_client/registry.py,sha256=yzp4URjmECxBlbl6vt2KaeB4v-93hQFy-brsT7_17II,23339
|
|
32
33
|
oep_client/riscv.py,sha256=1QloF51Uci2-6gonkBxQNh9XleF6yCvKG-L7XNm4sgI,60460
|
|
33
34
|
oep_client/rp2040.py,sha256=m_3eOWxrZOTWB7TGYv2_OmtfHREBdwCfdoVXjP_Qq0A,3392
|
|
34
35
|
oep_client/rp2040_peer.py,sha256=BQc6H79wPhjEcj0sl4O5mDPH_YZIHB2FbrZ8vMdxdg8,14505
|
|
@@ -39,7 +40,7 @@ oep_client/stimulus.py,sha256=pfX4xLjyG3UWIJRWUHnkg9c1ueL1MeozHj3CnlJ7dpA,6072
|
|
|
39
40
|
oep_client/targets.py,sha256=vEXPyhI1UNeaZKIpbNIVf-XsmpvnV9r0pPyXYb3Ddaw,7994
|
|
40
41
|
oep_client/uiapduino.py,sha256=oafP1SyKPO3cox-2RYbWUZusy4vc6NsEVC7Z-Y1sjWI,6337
|
|
41
42
|
oep_client/usb_stream.py,sha256=zRGGHkvtOme6lIZ-gkrCe2xKn9hC62JGqmc7pKQDAJI,23540
|
|
42
|
-
oep_client/virtual_bench.py,sha256=
|
|
43
|
+
oep_client/virtual_bench.py,sha256=YekAy5xxchLUOMarWMvdgshTVnFMe9VJZcib0Cpz4Y8,51203
|
|
43
44
|
oep_client/virtual_bench_capture.py,sha256=KxnXoCZ71qy9WwujyMt43jDJLsU4xHicBlS16NJMwkg,101716
|
|
44
45
|
oep_client/virtual_bench_mdns.py,sha256=cnJNBKg5zTduXUcH-wqN_ZBDGl7DyL-tL3CBn2jro0E,13305
|
|
45
46
|
oep_client/virtual_bench_serial.py,sha256=EvbdOYeGrM6CRti2b90I0PzpmmherfoJRaAg4v7Iq9Y,13205
|
|
@@ -49,7 +50,7 @@ oep_client/virtual_bench_usb.py,sha256=cnewgbsl30b0PgwkfebwSf3xjKyPg9y8vTd5Jew5r
|
|
|
49
50
|
oep_client/virtual_core.py,sha256=F-UxtQIld4eE04_M8j4ANMlQ12jXANzfTnfn59kUWHk,10716
|
|
50
51
|
oep_client/virtual_fixture_drive.py,sha256=3i5ss_bxIJFsRDb5S-GJ0Nb9QDyOBv_SwF9CDP6nPfE,21844
|
|
51
52
|
oep_client/virtual_names.py,sha256=p72ME65R9Jy56xmVYnVCtapK3Id8iFpAdI5xLe2tNZ0,1449
|
|
52
|
-
oep_client/conformance/__init__.py,sha256=
|
|
53
|
+
oep_client/conformance/__init__.py,sha256=r3xk9VyzN4LgETYlN0E3pyA-b1uI9Km0TXra-Bl-ShY,64739
|
|
53
54
|
oep_client/conformance/allocator.py,sha256=dYR5ZcCVW-LjoZVx4W9Ad76L8Ol8Pu1aOkj78A9UY1E,6251
|
|
54
55
|
oep_client/conformance/allocator_model.py,sha256=j6l1E1Hlju1jNeFQ_N0a2mDaegaTeyk6iScqzMpvvTc,1563
|
|
55
56
|
oep_client/conformance/allocator_sample.py,sha256=xtiXuZa3VDTilrZArIWOiRyOkuXJ98_rY2M65sIVvpo,855
|
|
@@ -84,7 +85,7 @@ oep_client/conformance/clock.py,sha256=bDqBjih71zg_mhkgCSryzKI_N8FEoGoGv9jO4PMYD
|
|
|
84
85
|
oep_client/conformance/clock_model.py,sha256=jyB8oTVR5sqbzeJCCWxdQBnzNNl4_uITLLYQ-yx8pRU,2568
|
|
85
86
|
oep_client/conformance/clock_sample.py,sha256=x24mSpQoOamrXrinmnF_Tk4NPk76LwS6ZhfuK2SCwCo,673
|
|
86
87
|
oep_client/conformance/core_fixed_fields.py,sha256=LRFP9yGOjDHQM1diX1HMdwXh9jMgAOY6xIC-woCikSk,6297
|
|
87
|
-
oep_client/conformance/core_smoke.py,sha256=
|
|
88
|
+
oep_client/conformance/core_smoke.py,sha256=6fvQ6JYX1PTFGKtpCTffKigzKF5ejsYLffMFXxOCVQE,10815
|
|
88
89
|
oep_client/conformance/corr.py,sha256=P6ljdsXpBq-vyEXRWOB1MqACQjT6HRBXgfAxgkyPjl4,6552
|
|
89
90
|
oep_client/conformance/cut.py,sha256=4B8szd6_QtcGtYMWgP86ABsXUDOFVgwWaWCpTWMDO80,7942
|
|
90
91
|
oep_client/conformance/cut_model.py,sha256=bHPnK8vKoInKVslW0KlJXRRgpGK-7ktIVGUEXo60X34,4791
|
|
@@ -119,21 +120,21 @@ oep_client/conformance/gpio_lifecycle_model.py,sha256=K0nzlXf-43kj3jsfaAuzEWOftO
|
|
|
119
120
|
oep_client/conformance/gpio_lifecycle_sample.py,sha256=Yk1WENQ-2tbPCM2yUY4Yk9dgWkjIfL42VtUSpx1qhhM,369
|
|
120
121
|
oep_client/conformance/gpio_model.py,sha256=CXy42K8cdRSTBxLC44h-gTYhjh3HrduSehsgsbvmDpo,5633
|
|
121
122
|
oep_client/conformance/gpio_sample.py,sha256=JHsOrjkYcM738IGSzP5q7v5vANETwKT6-csCTxH55MQ,490
|
|
122
|
-
oep_client/conformance/host_core.py,sha256=
|
|
123
|
+
oep_client/conformance/host_core.py,sha256=7yaDKXpam5qBKuAl41a0eBHq4ijspQIpQhk-JKNvFoQ,12634
|
|
123
124
|
oep_client/conformance/host_core_sample.py,sha256=egU8Xw-f3eHx_CmUooN1yW1lzltOam6IsJbnD8uOx9I,3803
|
|
124
|
-
oep_client/conformance/host_deadline.py,sha256=
|
|
125
|
+
oep_client/conformance/host_deadline.py,sha256=uGm6LhWDsv-HsO7M80MROhjGYEmd12qjEKk8B-aT2Mg,6978
|
|
125
126
|
oep_client/conformance/host_deadline_sample.py,sha256=G471jEc2HxDIu52Acw_aHDSxFAEC72OBCuvn2YQtAdU,5136
|
|
126
127
|
oep_client/conformance/host_notifications.py,sha256=LGRJhOS66_36MYG8fHLcyHroXtoZRQVJgammDieVhQY,9307
|
|
127
128
|
oep_client/conformance/host_notifications_sample.py,sha256=auze2DdWtUOdQMHnfDF8WXLAiETv3OuODR6y77VmsTQ,1788
|
|
128
129
|
oep_client/conformance/host_request_sample.py,sha256=Oq60q350EhDxhe2FESnUF04YhJ6HDjDSHTe4FR55H9M,1848
|
|
129
|
-
oep_client/conformance/host_requests.py,sha256=
|
|
130
|
+
oep_client/conformance/host_requests.py,sha256=Z_gaT55iel6jGgYHytGWQpph78xbhTScvFoWpbsZyEs,10718
|
|
130
131
|
oep_client/conformance/host_resend.py,sha256=P8tFWtr6TwQ33ph-JO0l8EXyJGxSjC6fBeyKQ4AhqxU,5519
|
|
131
132
|
oep_client/conformance/host_resend_sample.py,sha256=nwaru9ZVcxpcTi1OEritp8PSF6Qfic_IRGuutH2aBA4,4808
|
|
132
133
|
oep_client/conformance/host_tlv.py,sha256=h5SZT9r3DCsxMLcRacdyKOoZNQHSL8DzRgT1VPu_86A,6140
|
|
133
134
|
oep_client/conformance/host_tlv_sample.py,sha256=V6ORb5E2i76dc8O73Bew0eO2CbjG-ZPc02JLi6w5vmY,743
|
|
134
135
|
oep_client/conformance/host_values.py,sha256=VnjEB5TQbI34fuyAx0OGVbiyfI2ZH_of9I4TLT7Dyyk,4742
|
|
135
136
|
oep_client/conformance/host_values_sample.py,sha256=7C58mSP9jjrkNSgSb3uNK44VrdPXOznof_oe5UCcZro,1532
|
|
136
|
-
oep_client/conformance/host_wait.py,sha256=
|
|
137
|
+
oep_client/conformance/host_wait.py,sha256=N7Aj70EIE5A8mg849kbQ192BPG1gXj6fHAHz-rF2MSw,5591
|
|
137
138
|
oep_client/conformance/host_wait_sample.py,sha256=7SkJ8H_9ScMv8gLnT85xBKj_GXciPvAzmH-KxfMWYCg,3257
|
|
138
139
|
oep_client/conformance/i2c.py,sha256=fNDweyCLlCsv7fCwmmQMC7di5AG1HzDCflW0miWwcjc,18041
|
|
139
140
|
oep_client/conformance/i2c_model.py,sha256=HLYqRKdKMvlCGMcgYxSS3dy50Zf7aPojN_gO5fBvy6U,9759
|
|
@@ -259,8 +260,8 @@ oep_client/conformance/writer_lease_model.py,sha256=FxhDqqzdFOa_SSHSvjdQFh49Wn3Y
|
|
|
259
260
|
oep_client/conformance/writer_lease_sample.py,sha256=oowRwYUNAE4f6rEE2HmYlSS9dDWE0TGegkcsqVH8Nak,455
|
|
260
261
|
oep_client/pyserial/__init__.py,sha256=JSsbRkLYgFVN8V4H9ovdqyDr6JW3P3qi4RYoElg050o,121
|
|
261
262
|
oep_client/pyserial/protocol_oep.py,sha256=3dC8nbtddNG2lfonLUkbBTKfsfJoNHFfabuedn0n7Ho,2497
|
|
262
|
-
oep_client_python-0.0.
|
|
263
|
-
oep_client_python-0.0.
|
|
264
|
-
oep_client_python-0.0.
|
|
265
|
-
oep_client_python-0.0.
|
|
266
|
-
oep_client_python-0.0.
|
|
263
|
+
oep_client_python-0.0.54.dist-info/METADATA,sha256=26-ib8waK12oL_vgWAHFD-j7voBa-hsBbAPksQg5LXE,55222
|
|
264
|
+
oep_client_python-0.0.54.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
265
|
+
oep_client_python-0.0.54.dist-info/entry_points.txt,sha256=XEt39Jz7oQyU9Hfg_tV6eDGofNxWYrjSfslxKP-fh70,135
|
|
266
|
+
oep_client_python-0.0.54.dist-info/licenses/LICENSE,sha256=ntlalztb3L1OaNAxmQdHlubu5fVhjMc9oRZi3PeQhXg,1076
|
|
267
|
+
oep_client_python-0.0.54.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|