oep-client-python 0.0.18__tar.gz → 0.0.19__tar.gz
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_python-0.0.18 → oep_client_python-0.0.19}/CHANGELOG.md +18 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/PKG-INFO +10 -7
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/README.ja.md +9 -6
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/README.md +9 -6
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/pyproject.toml +1 -1
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/__init__.py +1 -1
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/__main__.py +8 -6
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/capture.py +5 -5
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/catalog.py +6 -5
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/ch32_flash.py +8 -8
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/config.py +28 -21
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/console.py +1 -1
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/decode.py +1 -1
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/endpoint.py +111 -30
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/fake.py +17 -17
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/fake_capture.py +5 -5
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/fake_serve.py +1 -1
- oep_client_python-0.0.19/src/oep_client/fixture.py +233 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/host.py +39 -2
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/interfaces.py +4 -4
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/link.py +47 -1
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/message.py +12 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/registry.py +12 -6
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/riscv.py +1 -1
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/uiapduino.py +8 -8
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/usb_stream.py +8 -1
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/tests/test_capabilities.py +7 -6
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/tests/test_config.py +44 -8
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/tests/test_fake_capture.py +3 -2
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/tests/test_fake_spec.py +6 -5
- oep_client_python-0.0.18/tests/test_esp32_targets.py → oep_client_python-0.0.19/tests/test_fixture_targets.py +7 -6
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/tests/test_interfaces.py +1 -1
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/tests/test_session.py +1 -1
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/tests/test_target_parts.py +1 -1
- oep_client_python-0.0.18/src/oep_client/esp32_targets.py +0 -137
- oep_client_python-0.0.18/src/oep_client/fixture.py +0 -121
- oep_client_python-0.0.18/src/oep_client/target.py +0 -16
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/.gitignore +0 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/LICENSE +0 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/arm.py +0 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/cobs.py +0 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/core.py +0 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/dump.py +0 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/fake_serial.py +0 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/frames.py +0 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/hid_stream.py +0 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/names.py +0 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/src/oep_client/rp2350.py +0 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/tests/test_capture.py +0 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/tests/test_cobs.py +0 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/tests/test_flash_console.py +0 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/tests/test_hid_stream.py +0 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/tests/test_link_host.py +0 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/tests/test_link_serial.py +0 -0
- {oep_client_python-0.0.18 → oep_client_python-0.0.19}/tests/test_usb_stream.py +0 -0
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.0.19
|
|
6
|
+
- (EN) The pre-freeze decisions (oep-spec docs/v1-freeze-decisions.ja.md), in the fake and the client at once:
|
|
7
|
+
- `oep.fixture.capture` is `oep.fixture.logic`; the I2C / SPI targets are the standard `oep.fixture.i2c-target` / `spi-target` (`fixture.I2cTarget` / `SpiTarget`; `esp32_targets` is gone). Their status is separate fields (`I2cStatus(state=, mode=, armed=, queued=, …)`); `read_hw` is gone, `set_stretch` is `stretch` (op 0x07).
|
|
8
|
+
- Answer lists put each element's length first (core §2.3): list, scan, connections, marks, capture segments (`message.Reader.element()`, `message.element()`); what an element carries after the known fields is skipped.
|
|
9
|
+
- probe.config: a slot's lock is length-prefixed (`lock_len`, 0 = none); settings are saved with the (name, instance, revision) of each interface they name and renumbered at boot, so another interface added or moved leaves them in force (the fake: `saved_ids`); the storage says why it is unreadable (`State.unreadable`). The item classes (`Plan`, `Label`, `Idle`, `Slot`, `Bind`) take keyword arguments only.
|
|
10
|
+
- rejected unavailable carries core §4.3's TLVs: `host.Unavailable` with `.cause`, `.channels`, `.holder_fn`, `.holder_kind` (and `.tlvs`; gpio's refused position is its TLV 0x40). An unknown console stream is `no_connection`.
|
|
11
|
+
- The analog's zero and scale are signed (i32).
|
|
12
|
+
- USB: the vendor transport is the class 0xFF interface's bulk pair (not the first bulk pair); `open_host("usb:<unit id>")` finds the OEP probe whose USB serial is that unit id; unit_id is text (the fake: `esp32p4` / `esp32` / `rp2350` models, lowercase hex unit ids).
|
|
13
|
+
- `target` (the first draft's re-exports) is gone: import the modules; README lists the public modules and the version pairing with the firmware.
|
|
14
|
+
- (JA) 凍結前の決定(oep-spec docs/v1-freeze-decisions.ja.md)を、fake とクライアントに一度に入れた:
|
|
15
|
+
- `oep.fixture.capture` は `oep.fixture.logic` になった。I2C / SPI の target は標準の `oep.fixture.i2c-target` / `spi-target`(`fixture.I2cTarget` / `SpiTarget`。`esp32_targets` は無くなった)。status は分けたフィールド(`I2cStatus(state=, mode=, armed=, queued=, …)`)、`read_hw` は無くなり、`set_stretch` は `stretch`(op 0x07)。
|
|
16
|
+
- 応答の並びは要素の前に長さを置く(core §2.3): list、scan、connections、marks、capture の segments(`message.Reader.element()`、`message.element()`)。要素の知っているフィールドの後ろは飛ばす。
|
|
17
|
+
- probe.config: スロットの錠は長さ付き(`lock_len`、0 は錠なし)。設定は、指すインターフェースの (name、instance、revision) と一緒に保存し、起動時に番号を読み替える。ほかのインターフェースを足したり動かしたりしても設定は効いたまま(fake: `saved_ids`)。storage は読めない理由を返す(`State.unreadable`)。項目のクラス(`Plan`、`Label`、`Idle`、`Slot`、`Bind`)はキーワード引数だけを取る。
|
|
18
|
+
- rejected unavailable は core §4.3 の TLV を持つ: `host.Unavailable` の `.cause`、`.channels`、`.holder_fn`、`.holder_kind`(と `.tlvs`。gpio の断った位置は、その TLV 0x40)。知らない console の stream は `no_connection`。
|
|
19
|
+
- アナログの zero と scale は符号付き(i32)。
|
|
20
|
+
- USB: vendor の経路は class 0xFF のインターフェースの bulk の組(最初の bulk の組ではない)。`open_host("usb:<unit id>")` は、USB の serial がその unit id の OEP の probe を探す。unit_id は text(fake: model は `esp32p4` / `esp32` / `rp2350`、unit id は小文字の 16 進)。
|
|
21
|
+
- `target`(最初の版の再輸出)は無くなった: モジュールを直接 import する。README に公開のモジュールと、firmware との版の組を書いた。
|
|
22
|
+
|
|
5
23
|
## 0.0.18
|
|
6
24
|
- (EN) No user-facing changes recorded.
|
|
7
25
|
- (JA) ユーザー向け変更の記録はありません。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: oep-client-python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.19
|
|
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
|
|
@@ -49,6 +49,10 @@ under Unreleased in CHANGELOG.md, (EN) and (JA).
|
|
|
49
49
|
|
|
50
50
|
## Modules (`oep_client`)
|
|
51
51
|
|
|
52
|
+
The modules below are the public API; import them directly (`from oep_client import riscv`). Anything not listed, and
|
|
53
|
+
names starting with `_`, may change. A probe firmware and this client go together by version: OpenEmbeddedProbe X.Y.Z with
|
|
54
|
+
oep-client-python X.Y.Z (until the v1 freeze every release may break the wire; the versions move together).
|
|
55
|
+
|
|
52
56
|
| Module | Contents |
|
|
53
57
|
|---|---|
|
|
54
58
|
| `host` | requests and results, the session id and the lock, `call()` (raises unless it worked), pipelining, the errors (`OepError` / `Rejected` / `Failed`) |
|
|
@@ -56,10 +60,9 @@ under Unreleased in CHANGELOG.md, (EN) and (JA).
|
|
|
56
60
|
| `core` | interfaces by name (cached), confirm, the probe's describe (labels, the transport list), taking the lock (`take`), the pin plan, the `Interface` base |
|
|
57
61
|
| `riscv` | `oep.wire.rvswd` / `oep.wire.swio`, `oep.target.riscv-dm`, finding the reset line, attach through GPIO |
|
|
58
62
|
| `console` | `oep.target.console` (position streams) and `ConsoleIO`, read as bytes |
|
|
59
|
-
| `fixture` | `oep.fixture.gpio` / `uart` (revision 1) |
|
|
63
|
+
| `fixture` | `oep.fixture.gpio` / `uart` / `i2c-target` / `spi-target` (revision 1) |
|
|
60
64
|
| `config` | `oep.probe.config` (slots, binds, plan / label / idle items, get / set / save / erase, the live slot and bind state) |
|
|
61
|
-
| `capture` | `oep.fixture.
|
|
62
|
-
| `esp32_targets` | the custom interfaces `io.github.ch32-riscv-ug.esp32.i2c-target` / `spi-target` (the ESP32 I2C / SPI targets of oep-probe-arduino) |
|
|
65
|
+
| `capture` | `oep.fixture.logic` (revision 1, oep-spec oep-if-capture). Every segment read goes to the `Host.on_capture` callbacks as a `CaptureRecord` (the hook for run recorders; no wireskein dependency) |
|
|
63
66
|
| `decode` | decoding capture channels (I2C) |
|
|
64
67
|
| `registry` | generated from oep-spec's number table (never edited; copied again from oep-spec) |
|
|
65
68
|
| `arm` | `oep.wire.swd`, `oep.target.arm-adi`, MEM-AP, halting and calling functions on a Cortex-M |
|
|
@@ -68,7 +71,6 @@ under Unreleased in CHANGELOG.md, (EN) and (JA).
|
|
|
68
71
|
| `uiapduino` | into and out of the UIAPduino bootloader |
|
|
69
72
|
| `catalog` / `names` / `interfaces` / `dump` | the capability list and describe shapes, display |
|
|
70
73
|
| `fake` / `endpoint` / `fake_serial` / `fake_serve` | the fake probe (below) |
|
|
71
|
-
| `target` | one place to import the main ones from |
|
|
72
74
|
|
|
73
75
|
## Example
|
|
74
76
|
|
|
@@ -76,7 +78,8 @@ under Unreleased in CHANGELOG.md, (EN) and (JA).
|
|
|
76
78
|
from oep_client import core, link, riscv, ch32_flash
|
|
77
79
|
|
|
78
80
|
hst = link.open_host("/run/board-identify/by-id/esp32-series-30eda0e31108") # pipelined
|
|
79
|
-
# a serial port (always COBS), "tcp://127.0.0.1:PORT" (a broker), "usb
|
|
81
|
+
# a serial port (always COBS), "tcp://127.0.0.1:PORT" (a broker), "usb:<unit id>" (the probe whose USB serial it is),
|
|
82
|
+
# "usb" / "usb:303a:0002[:SERIAL]" (vendor, then HID)
|
|
80
83
|
core.take(hst, 30000, owner="flash script") # the only way in: force; else wait out the lease, name the holder
|
|
81
84
|
wire = riscv.Wire(hst, "oep.wire.rvswd")
|
|
82
85
|
conn, _ = wire.attach(halt=True)
|
|
@@ -111,7 +114,7 @@ against it (when the spec changes, this is brought in line before the firmware).
|
|
|
111
114
|
chooses), `fake_serial` the byte side of a serial port (COBS candidates, raw bytes and binds, held during a session and
|
|
112
115
|
resumed after it).
|
|
113
116
|
|
|
114
|
-
`fake_capture` is `oep.fixture.
|
|
117
|
+
`fake_capture` is `oep.fixture.logic` (logic): one-shot, repeat (segments with the clock at the actual rate, a ring,
|
|
115
118
|
release) and streaming (data pushes while subscribed), level / edge triggers with a pretrigger, and events. What it
|
|
116
119
|
captures is known: sample i is the counter i, channel k its bit k (a square wave of period 2^(k+1) samples), in the layout
|
|
117
120
|
the profile allows (`p4-x035`: w 1-16 as the P4's PARLIO, three channels in w 4; `esp32-v003`: w 8 as the classic ESP32's
|
|
@@ -23,6 +23,10 @@ GitHub Release、PyPI(Trusted Publishing)へ出す。変更は CHANGELOG.md
|
|
|
23
23
|
|
|
24
24
|
## モジュール(`oep_client`)
|
|
25
25
|
|
|
26
|
+
下の表のモジュールが公開の API です。直接 import します(`from oep_client import riscv`)。表に無いものと、`_` で始まる名前は
|
|
27
|
+
変わることがあります。probe の firmware とこのクライアントは版で組になります: OpenEmbeddedProbe X.Y.Z と oep-client-python
|
|
28
|
+
X.Y.Z(v1 の凍結までは、どのリリースも wire を壊しうるので、版を一緒に動かします)。
|
|
29
|
+
|
|
26
30
|
| モジュール | 中身 |
|
|
27
31
|
|---|---|
|
|
28
32
|
| `host` | 要求と結果、session_id とロック、`call()`(失敗なら例外)、pipeline、エラーの階層(`OepError` / `Rejected` / `Failed`) |
|
|
@@ -30,10 +34,9 @@ GitHub Release、PyPI(Trusted Publishing)へ出す。変更は CHANGELOG.md
|
|
|
30
34
|
| `core` | インターフェースを名前で探す(キャッシュつき)、confirm、probe の describe(ラベル、transport の一覧)、ロックの取り方(`take`)、ピンの割り当て(plan)、`Interface` の土台 |
|
|
31
35
|
| `riscv` | `oep.wire.rvswd` / `oep.wire.swio`、`oep.target.riscv-dm`、リセット線の探索、GPIO 経由の attach |
|
|
32
36
|
| `console` | `oep.target.console`(位置つきのストリーム)と、バイト列として読む `ConsoleIO` |
|
|
33
|
-
| `fixture` | `oep.fixture.gpio` / `uart`(revision 1) |
|
|
37
|
+
| `fixture` | `oep.fixture.gpio` / `uart` / `i2c-target` / `spi-target`(revision 1) |
|
|
34
38
|
| `config` | `oep.probe.config`(スロット、bind、plan / label / idle の項目、get / set / save / erase、スロットと bind の今の状態) |
|
|
35
|
-
| `capture` | `oep.fixture.
|
|
36
|
-
| `esp32_targets` | 独自インターフェース `io.github.ch32-riscv-ug.esp32.i2c-target` / `spi-target`(oep-probe-arduino の ESP32 の I2C / SPI の target) |
|
|
39
|
+
| `capture` | `oep.fixture.logic`(revision 1、oep-spec の oep-if-capture)。読んだ区画は `Host.on_capture` の callback に `CaptureRecord` で渡る(記録の受け口。wireskein には依存しない) |
|
|
37
40
|
| `decode` | キャプチャのチャネルの復号(I2C) |
|
|
38
41
|
| `registry` | oep-spec の番号の表から生成したモジュール(編集しない。oep-spec から写し直す) |
|
|
39
42
|
| `arm` | `oep.wire.swd`、`oep.target.arm-adi`、MEM-AP、Cortex-M の停止と関数呼び出し |
|
|
@@ -42,7 +45,6 @@ GitHub Release、PyPI(Trusted Publishing)へ出す。変更は CHANGELOG.md
|
|
|
42
45
|
| `uiapduino` | UIAPduino のブートローダへの出入り |
|
|
43
46
|
| `catalog` / `names` / `interfaces` / `dump` | 能力の一覧と describe の形、表示 |
|
|
44
47
|
| `fake` / `endpoint` / `fake_serial` / `fake_serve` | 偽の probe(下の「偽の probe」) |
|
|
45
|
-
| `target` | 上の主なものを 1 か所から import する入口(最初の版に合わせて書いた呼び出し側のため) |
|
|
46
48
|
|
|
47
49
|
## 使い方の例
|
|
48
50
|
|
|
@@ -50,7 +52,8 @@ GitHub Release、PyPI(Trusted Publishing)へ出す。変更は CHANGELOG.md
|
|
|
50
52
|
from oep_client import core, link, riscv, ch32_flash
|
|
51
53
|
|
|
52
54
|
hst = link.open_host("/run/board-identify/by-id/esp32-series-30eda0e31108") # pipelining つき
|
|
53
|
-
# a serial port (always COBS), "tcp://127.0.0.1:PORT" (a broker), "usb
|
|
55
|
+
# a serial port (always COBS), "tcp://127.0.0.1:PORT" (a broker), "usb:<unit id>" (the probe whose USB serial it is),
|
|
56
|
+
# "usb" / "usb:303a:0002[:SERIAL]" (vendor, then HID)
|
|
54
57
|
core.take(hst, 30000, owner="flash script") # the only way in: force; else wait out the lease, name the holder
|
|
55
58
|
wire = riscv.Wire(hst, "oep.wire.rvswd")
|
|
56
59
|
conn, _ = wire.attach(halt=True)
|
|
@@ -83,7 +86,7 @@ oep config save <probe> # 再起動の後も残す(remove
|
|
|
83
86
|
`p4-x035`、`esp32-v003`、`p4-bench` = スロット 3 か所と席 2 つの架空の治具、`rp2350-pins` = host がピンを選ぶ wire)、`fake_serial` は
|
|
84
87
|
シリアルの口のバイトの側(COBS の候補、生のバイトと bind、セッション中の停止と再開)。
|
|
85
88
|
|
|
86
|
-
`fake_capture` は `oep.fixture.
|
|
89
|
+
`fake_capture` は `oep.fixture.logic`(ロジック): ワンショット、リピート(実際のレートで時計どおりに区画ができ、リング、release)、
|
|
87
90
|
ストリーミング(購読している間のデータの push)、レベル / エッジのトリガとプリトリガ、出来事。取れるものは決まっている: サンプル i は
|
|
88
91
|
カウンタの値 i で、チャネル k はそのビット k(周期 2^(k+1) サンプルの方形波)。layout は profile が許す形(`p4-x035`: P4 の
|
|
89
92
|
PARLIO と同じ w 1〜16、3 本なら w 4。`esp32-v003`: classic ESP32 の sampler と同じ w 8、ワンショットだけ)。capture は聞くだけ
|
|
@@ -27,6 +27,10 @@ under Unreleased in CHANGELOG.md, (EN) and (JA).
|
|
|
27
27
|
|
|
28
28
|
## Modules (`oep_client`)
|
|
29
29
|
|
|
30
|
+
The modules below are the public API; import them directly (`from oep_client import riscv`). Anything not listed, and
|
|
31
|
+
names starting with `_`, may change. A probe firmware and this client go together by version: OpenEmbeddedProbe X.Y.Z with
|
|
32
|
+
oep-client-python X.Y.Z (until the v1 freeze every release may break the wire; the versions move together).
|
|
33
|
+
|
|
30
34
|
| Module | Contents |
|
|
31
35
|
|---|---|
|
|
32
36
|
| `host` | requests and results, the session id and the lock, `call()` (raises unless it worked), pipelining, the errors (`OepError` / `Rejected` / `Failed`) |
|
|
@@ -34,10 +38,9 @@ under Unreleased in CHANGELOG.md, (EN) and (JA).
|
|
|
34
38
|
| `core` | interfaces by name (cached), confirm, the probe's describe (labels, the transport list), taking the lock (`take`), the pin plan, the `Interface` base |
|
|
35
39
|
| `riscv` | `oep.wire.rvswd` / `oep.wire.swio`, `oep.target.riscv-dm`, finding the reset line, attach through GPIO |
|
|
36
40
|
| `console` | `oep.target.console` (position streams) and `ConsoleIO`, read as bytes |
|
|
37
|
-
| `fixture` | `oep.fixture.gpio` / `uart` (revision 1) |
|
|
41
|
+
| `fixture` | `oep.fixture.gpio` / `uart` / `i2c-target` / `spi-target` (revision 1) |
|
|
38
42
|
| `config` | `oep.probe.config` (slots, binds, plan / label / idle items, get / set / save / erase, the live slot and bind state) |
|
|
39
|
-
| `capture` | `oep.fixture.
|
|
40
|
-
| `esp32_targets` | the custom interfaces `io.github.ch32-riscv-ug.esp32.i2c-target` / `spi-target` (the ESP32 I2C / SPI targets of oep-probe-arduino) |
|
|
43
|
+
| `capture` | `oep.fixture.logic` (revision 1, oep-spec oep-if-capture). Every segment read goes to the `Host.on_capture` callbacks as a `CaptureRecord` (the hook for run recorders; no wireskein dependency) |
|
|
41
44
|
| `decode` | decoding capture channels (I2C) |
|
|
42
45
|
| `registry` | generated from oep-spec's number table (never edited; copied again from oep-spec) |
|
|
43
46
|
| `arm` | `oep.wire.swd`, `oep.target.arm-adi`, MEM-AP, halting and calling functions on a Cortex-M |
|
|
@@ -46,7 +49,6 @@ under Unreleased in CHANGELOG.md, (EN) and (JA).
|
|
|
46
49
|
| `uiapduino` | into and out of the UIAPduino bootloader |
|
|
47
50
|
| `catalog` / `names` / `interfaces` / `dump` | the capability list and describe shapes, display |
|
|
48
51
|
| `fake` / `endpoint` / `fake_serial` / `fake_serve` | the fake probe (below) |
|
|
49
|
-
| `target` | one place to import the main ones from |
|
|
50
52
|
|
|
51
53
|
## Example
|
|
52
54
|
|
|
@@ -54,7 +56,8 @@ under Unreleased in CHANGELOG.md, (EN) and (JA).
|
|
|
54
56
|
from oep_client import core, link, riscv, ch32_flash
|
|
55
57
|
|
|
56
58
|
hst = link.open_host("/run/board-identify/by-id/esp32-series-30eda0e31108") # pipelined
|
|
57
|
-
# a serial port (always COBS), "tcp://127.0.0.1:PORT" (a broker), "usb
|
|
59
|
+
# a serial port (always COBS), "tcp://127.0.0.1:PORT" (a broker), "usb:<unit id>" (the probe whose USB serial it is),
|
|
60
|
+
# "usb" / "usb:303a:0002[:SERIAL]" (vendor, then HID)
|
|
58
61
|
core.take(hst, 30000, owner="flash script") # the only way in: force; else wait out the lease, name the holder
|
|
59
62
|
wire = riscv.Wire(hst, "oep.wire.rvswd")
|
|
60
63
|
conn, _ = wire.attach(halt=True)
|
|
@@ -89,7 +92,7 @@ against it (when the spec changes, this is brought in line before the firmware).
|
|
|
89
92
|
chooses), `fake_serial` the byte side of a serial port (COBS candidates, raw bytes and binds, held during a session and
|
|
90
93
|
resumed after it).
|
|
91
94
|
|
|
92
|
-
`fake_capture` is `oep.fixture.
|
|
95
|
+
`fake_capture` is `oep.fixture.logic` (logic): one-shot, repeat (segments with the clock at the actual rate, a ring,
|
|
93
96
|
release) and streaming (data pushes while subscribed), level / edge triggers with a pretrigger, and events. What it
|
|
94
97
|
captures is known: sample i is the counter i, channel k its bit k (a square wave of period 2^(k+1) samples), in the layout
|
|
95
98
|
the profile allows (`p4-x035`: w 1-16 as the P4's PARLIO, three channels in w 4; `esp32-v003`: w 8 as the classic ESP32's
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "oep-client-python"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.19"
|
|
8
8
|
description = "Open Embedded Probe (OEP) v1 host: serial / USB / TCP transports, the session rules, the standard interfaces, and a fake probe"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -186,12 +186,14 @@ def _config(args) -> int:
|
|
|
186
186
|
if args.lock:
|
|
187
187
|
mask, _, value = args.lock.partition(":")
|
|
188
188
|
lock = (1, struct.pack("<I", int(mask, 16)), struct.pack("<I", int(value, 16)))
|
|
189
|
-
it = config.Slot(args.slot, fn, _pins(hst, fn, args.pins, args.wire), args.name,
|
|
190
|
-
args.
|
|
189
|
+
it = config.Slot(slot=args.slot, wire_fn=fn, pins=_pins(hst, fn, args.pins, args.wire), name=args.name,
|
|
190
|
+
attach=args.attach, retry_s=args.retry, mechanism=args.mechanism, lock=lock,
|
|
191
|
+
max_speed=args.max_speed, idle_clock=args.idle_clock)
|
|
191
192
|
_change(hst, cfg, [it], args.save)
|
|
192
193
|
elif args.action == "bind":
|
|
193
194
|
slots = {it.name: it.slot for it in cfg.items() if isinstance(it, config.Slot)}
|
|
194
|
-
it = config.Bind(args.port, args.mode, [_stream(s, slots) for s in args.stream],
|
|
195
|
+
it = config.Bind(port=args.port, mode=args.mode, streams=[_stream(s, slots) for s in args.stream],
|
|
196
|
+
selected=args.select)
|
|
195
197
|
_change(hst, cfg, [it], args.save)
|
|
196
198
|
elif args.action == "plan":
|
|
197
199
|
fn, roles = _plan_fn(hst, args.fn)
|
|
@@ -203,12 +205,12 @@ def _config(args) -> int:
|
|
|
203
205
|
number = int(role) if role.isdigit() else roles.get(role.lower())
|
|
204
206
|
if number is None:
|
|
205
207
|
raise SystemExit(f"{role}: not a role of fn {fn} (roles: {', '.join(sorted(roles)) or 'numbers only'})")
|
|
206
|
-
items.append(config.Plan(fn, number, int(ch, 0)))
|
|
208
|
+
items.append(config.Plan(fn=fn, role=number, channel=int(ch, 0)))
|
|
207
209
|
_change(hst, cfg, items, args.save)
|
|
208
210
|
elif args.action == "label":
|
|
209
|
-
_change(hst, cfg, [config.Label(args.channel, args.text)], args.save)
|
|
211
|
+
_change(hst, cfg, [config.Label(channel=args.channel, text=args.text)], args.save)
|
|
210
212
|
elif args.action == "idle":
|
|
211
|
-
_change(hst, cfg, [config.Idle(args.channel, args.mode)], args.save)
|
|
213
|
+
_change(hst, cfg, [config.Idle(channel=args.channel, mode=args.mode)], args.save)
|
|
212
214
|
elif args.action == "remove":
|
|
213
215
|
_change(hst, cfg, [config.remove(args.kind, args.key)], args.save)
|
|
214
216
|
else:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""oep.fixture.
|
|
1
|
+
"""oep.fixture.logic / oep.fixture.analog / oep.fixture.capture-group revision 1 (oep-spec docs/oep-if-capture.ja.md:
|
|
2
2
|
§1 layouts, §2 segments, §3 operations, §3.8 calibration, §4 groups). Numbers from `registry`.
|
|
3
3
|
|
|
4
4
|
Times are the probe's one clock (ns since its boot, comparable within one boot_id): estimates with an uncertainty, the
|
|
@@ -20,7 +20,7 @@ from . import cobs, host as h, message as m, registry as reg
|
|
|
20
20
|
from .frames import FramingLost
|
|
21
21
|
from .core import Interface, confirm
|
|
22
22
|
|
|
23
|
-
_CAP = reg.
|
|
23
|
+
_CAP = reg.FIXTURE_LOGIC
|
|
24
24
|
_ANA = reg.FIXTURE_ANALOG
|
|
25
25
|
_GRP = reg.FIXTURE_CAPTURE_GROUP
|
|
26
26
|
# configure TLVs; bit 7 of a tag = critical (the probe must reject what it cannot do)
|
|
@@ -158,7 +158,7 @@ def _config(payload: bytes, analog: bool) -> Config:
|
|
|
158
158
|
elif tag == RATE_ACCURACY:
|
|
159
159
|
c.rate_measured, c.rate_ppm = v[0] == 1, struct.unpack_from("<I", v, 1)[0]
|
|
160
160
|
elif tag == SCALE and analog:
|
|
161
|
-
role, zero, scale = struct.unpack("<
|
|
161
|
+
role, zero, scale = struct.unpack("<Bii", v) # signed: an inverting frontend (§3.3)
|
|
162
162
|
c.zero[role], c.scale_nv[role] = zero, scale
|
|
163
163
|
elif tag == SKEW and analog:
|
|
164
164
|
role, ns = struct.unpack("<BI", v)
|
|
@@ -177,7 +177,7 @@ def _config(payload: bytes, analog: bool) -> Config:
|
|
|
177
177
|
|
|
178
178
|
class LogicCapture(Interface):
|
|
179
179
|
"""Basic logic capture. Channels are the plan's roles 0..C-1."""
|
|
180
|
-
NAME = "oep.fixture.
|
|
180
|
+
NAME = "oep.fixture.logic"
|
|
181
181
|
REVISION = 1
|
|
182
182
|
ANALOG = False
|
|
183
183
|
CONFIGURE, START, STOP, FORCE, STATUS, READ, SEGMENTS, RELEASE, QUERY_OP = (
|
|
@@ -301,7 +301,7 @@ class LogicCapture(Interface):
|
|
|
301
301
|
|
|
302
302
|
def segments(self, from_serial: int = 0) -> list[Segment]:
|
|
303
303
|
rd = m.Reader(self._call(self.SEGMENTS, struct.pack("<I", from_serial), locked=False).payload)
|
|
304
|
-
out = [Segment.unpack(rd.bytes(SEGMENT_BYTES)) for _ in range(rd.u8())]
|
|
304
|
+
out = [Segment.unpack(rd.element().bytes(SEGMENT_BYTES)) for _ in range(rd.u8())]
|
|
305
305
|
rd.tail()
|
|
306
306
|
return out
|
|
307
307
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Draft wire forms for capability discovery by name (oep-spec docs/capability-declaration-model.ja.md).
|
|
2
2
|
|
|
3
3
|
list request : flags(u8) first(u16) prefix_len(u8) prefix flags bit0 = exact
|
|
4
|
-
list result : total(u16) count(u8)
|
|
4
|
+
list result : total(u16) count(u8) count x (len(u8) entry) [TLV tail] oep.core (fn 0) is the first entry
|
|
5
5
|
list entry : fn(u16) instance(u16) revision(u8) flags(u8) name_len(u8) name
|
|
6
6
|
describe : request fn(u16) first(u16); result more(u8) then TLV bytes (tag u8, len u8, value;
|
|
7
7
|
tag bit 7 = critical). more = 1: TLVs remain after this page, ask again from first + count
|
|
@@ -15,7 +15,7 @@ from __future__ import annotations
|
|
|
15
15
|
import struct
|
|
16
16
|
from dataclasses import dataclass, field
|
|
17
17
|
|
|
18
|
-
from .message import Reader, split_tlvs
|
|
18
|
+
from .message import Reader, element, split_tlvs
|
|
19
19
|
|
|
20
20
|
LIST_EXACT = 0x01
|
|
21
21
|
|
|
@@ -65,7 +65,7 @@ def pack_entry(e: ListEntry) -> bytes:
|
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
def pack_list_result(total: int, entries: list[ListEntry]) -> bytes:
|
|
68
|
-
return struct.pack("<HB", total, len(entries)) + b"".join(pack_entry(e) for e in entries)
|
|
68
|
+
return struct.pack("<HB", total, len(entries)) + b"".join(element(pack_entry(e)) for e in entries)
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
def unpack_list_result(payload: bytes) -> tuple[int, list[ListEntry]]:
|
|
@@ -74,8 +74,9 @@ def unpack_list_result(payload: bytes) -> tuple[int, list[ListEntry]]:
|
|
|
74
74
|
total, count = rd.take("HB")
|
|
75
75
|
out = []
|
|
76
76
|
for _ in range(count):
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
e = rd.element()
|
|
78
|
+
fn, instance, revision, flags, n = e.take("HHBBB")
|
|
79
|
+
out.append(ListEntry(fn, instance, revision, flags, e.bytes(n).decode("ascii", "replace")))
|
|
79
80
|
rd.tail()
|
|
80
81
|
return total, out
|
|
81
82
|
|
|
@@ -17,7 +17,7 @@ import struct
|
|
|
17
17
|
import time
|
|
18
18
|
from dataclasses import dataclass, field
|
|
19
19
|
|
|
20
|
-
from . import host as h, riscv
|
|
20
|
+
from . import core, host as h, riscv
|
|
21
21
|
|
|
22
22
|
# oep-spec experiments/flash-primitives/x035_loader.S, assembled at 0x20000000 (riscv32-esp-elf binutils)
|
|
23
23
|
FAST_PAGE_LOADER = bytes.fromhex(
|
|
@@ -92,13 +92,13 @@ V003_INPUT, V003_STACK, V003_EBREAK, V003_RUN = 0x20000200, 0x20000800, 0x200001
|
|
|
92
92
|
|
|
93
93
|
|
|
94
94
|
def _block_size(hst: h.Host) -> int:
|
|
95
|
-
info =
|
|
95
|
+
info = core.confirm(hst)
|
|
96
96
|
# request header 6 + session 4 + connection 1 + address 4 + count 2 (the read answer's 5 + done 2 + status 1 is
|
|
97
97
|
# smaller); a little slack for the result header
|
|
98
98
|
return (info["max_frame"] - 5 - 6 - 4 - 1 - 4 - 2) // 4 * 4
|
|
99
99
|
|
|
100
100
|
|
|
101
|
-
def _write_requests(dm:
|
|
101
|
+
def _write_requests(dm: riscv.RiscvDm, address: int, data: bytes, block: int) -> list[tuple[int, int, bytes]]:
|
|
102
102
|
return [dm.request(dm.WRITE_BLOCK, dm.write_block_body(address + off, data[off:off + block]))
|
|
103
103
|
for off in range(0, len(data), block)]
|
|
104
104
|
|
|
@@ -108,20 +108,20 @@ def _write_ok(r) -> bool:
|
|
|
108
108
|
if not r.succeeded:
|
|
109
109
|
return False
|
|
110
110
|
try:
|
|
111
|
-
rd =
|
|
111
|
+
rd = riscv.ran(r)
|
|
112
112
|
rd.u16()
|
|
113
113
|
return rd.u8() == riscv.OK
|
|
114
114
|
except h.OepError:
|
|
115
115
|
return False
|
|
116
116
|
|
|
117
117
|
|
|
118
|
-
def _write(dm:
|
|
118
|
+
def _write(dm: riscv.RiscvDm, address: int, data: bytes, block: int) -> None:
|
|
119
119
|
for r in dm.host.pipeline_calls(_write_requests(dm, address, data, block)):
|
|
120
120
|
if not _write_ok(r):
|
|
121
|
-
raise riscv.TargetError("write_block",
|
|
121
|
+
raise riscv.TargetError("write_block", riscv.ran(r).take("HB")[1], r)
|
|
122
122
|
|
|
123
123
|
|
|
124
|
-
def _read(dm:
|
|
124
|
+
def _read(dm: riscv.RiscvDm, address: int, length: int, block: int) -> bytes:
|
|
125
125
|
"""Read back in blocks, pipelined when the host has the link's exchange (reads are independent)."""
|
|
126
126
|
spans = [(address + off, min(block, length - off) // 4) for off in range(0, length, block)]
|
|
127
127
|
out = b""
|
|
@@ -137,7 +137,7 @@ def _read(dm: target.RiscvDm, address: int, length: int, block: int) -> bytes:
|
|
|
137
137
|
PIPELINE_PAGES = 16 # pages per pipelined batch: enough to keep the link busy, small enough to show progress
|
|
138
138
|
|
|
139
139
|
|
|
140
|
-
def program(hst: h.Host, dm:
|
|
140
|
+
def program(hst: h.Host, dm: riscv.RiscvDm, image: bytes, profile: FlashProfile) -> ProgramResult:
|
|
141
141
|
"""Program `image` at profile.base on a halted, attached target (dm), verify by reading back."""
|
|
142
142
|
image = image + b"\xff" * (-len(image) % profile.page)
|
|
143
143
|
if profile.size and len(image) > profile.size:
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
pins, slots, binds - read and set as items, saved when the host says so, and the live slot / bind state.
|
|
3
3
|
|
|
4
4
|
cfg = config.ProbeConfig(hst)
|
|
5
|
-
cfg.set([config.Slot(0, wire_fn, (2, 54), "x035", attach="at-boot", retry_s=1
|
|
6
|
-
config.Bind(1, "last-reset", [("slot", 0)])])
|
|
5
|
+
cfg.set([config.Slot(slot=0, wire_fn=wire_fn, pins=(2, 54), name="x035", attach="at-boot", retry_s=1),
|
|
6
|
+
config.Bind(port=1, mode="last-reset", streams=[("slot", 0)])])
|
|
7
7
|
cfg.save()
|
|
8
8
|
cfg.items(), cfg.state()
|
|
9
9
|
|
|
@@ -31,13 +31,14 @@ 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
32
|
IDLE_CLOCK = dict(reg.WIRE_RVSWD.enum["idle_clock"]) # a slot's idle_clock (oep-if-debug §3)
|
|
33
33
|
STORAGE_STATE = {v: k for k, v in _CFG.enum["storage_state"].items()}
|
|
34
|
+
UNREADABLE = {1: "unreadable form", 2: "an interface it names is gone or of another revision", 3: "refused when applied"}
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
def _name(table: dict[str, int], value: int) -> str:
|
|
37
38
|
return next((k for k, v in table.items() if v == value), str(value))
|
|
38
39
|
|
|
39
40
|
|
|
40
|
-
@dataclass
|
|
41
|
+
@dataclass(kw_only=True)
|
|
41
42
|
class Plan:
|
|
42
43
|
fn: int
|
|
43
44
|
role: int
|
|
@@ -48,7 +49,7 @@ class Plan:
|
|
|
48
49
|
return struct.pack("<HBH", self.fn, self.role, self.channel)
|
|
49
50
|
|
|
50
51
|
|
|
51
|
-
@dataclass
|
|
52
|
+
@dataclass(kw_only=True)
|
|
52
53
|
class Label:
|
|
53
54
|
channel: int
|
|
54
55
|
text: str
|
|
@@ -58,7 +59,7 @@ class Label:
|
|
|
58
59
|
return struct.pack("<H", self.channel) + self.text.encode()
|
|
59
60
|
|
|
60
61
|
|
|
61
|
-
@dataclass
|
|
62
|
+
@dataclass(kw_only=True)
|
|
62
63
|
class Idle:
|
|
63
64
|
channel: int
|
|
64
65
|
mode: str = "pull-up" # hi-z, pull-up, pull-down
|
|
@@ -68,7 +69,7 @@ class Idle:
|
|
|
68
69
|
return struct.pack("<HB", self.channel, IDLE[self.mode])
|
|
69
70
|
|
|
70
71
|
|
|
71
|
-
@dataclass
|
|
72
|
+
@dataclass(kw_only=True)
|
|
72
73
|
class Slot:
|
|
73
74
|
"""A place a target is wired to (probe.config §1.1). pins: (swdio, swclk), swclk 0xFFFF on one wire (swio).
|
|
74
75
|
lock: (scheme, mask, value) - the target_id a connection must show, e.g. (1, mask u32 LE, value u32 LE)."""
|
|
@@ -90,14 +91,14 @@ class Slot:
|
|
|
90
91
|
self.retry_s if self.attach == "at-boot" else 0, self.max_speed, IDLE_CLOCK[self.idle_clock],
|
|
91
92
|
MECHANISM[self.mechanism], len(name)) + name
|
|
92
93
|
if self.lock is None:
|
|
93
|
-
return v + b"\x00"
|
|
94
|
+
return v + b"\x00" # lock_len 0: no lock
|
|
94
95
|
scheme, mask, value = self.lock
|
|
95
|
-
if len(mask) != len(value) or not mask:
|
|
96
|
-
raise ValueError("a lock
|
|
97
|
-
return v + bytes([scheme]) + mask + value
|
|
96
|
+
if len(mask) != len(value) or not mask or not scheme:
|
|
97
|
+
raise ValueError("a lock has a scheme and a mask and value of the same length, at least 1 byte")
|
|
98
|
+
return v + bytes([1 + 2 * len(mask), scheme]) + mask + value
|
|
98
99
|
|
|
99
100
|
|
|
100
|
-
@dataclass
|
|
101
|
+
@dataclass(kw_only=True)
|
|
101
102
|
class Bind:
|
|
102
103
|
"""What serial port `port` (the describe transport index) carries (probe.config §1.2). streams: ("slot", n) or
|
|
103
104
|
("uart", fn); selected: manual's choice (an index into streams)."""
|
|
@@ -126,25 +127,28 @@ def remove(kind: str, key: int) -> bytes:
|
|
|
126
127
|
def decode(tag: int, v: bytes):
|
|
127
128
|
"""One item as one of the classes above (an unknown tag: (tag, value))."""
|
|
128
129
|
if tag == ITEM["plan"] and len(v) == 5:
|
|
129
|
-
|
|
130
|
+
fn, role, channel = struct.unpack("<HBH", v)
|
|
131
|
+
return Plan(fn=fn, role=role, channel=channel)
|
|
130
132
|
if tag == ITEM["label"] and len(v) >= 2:
|
|
131
|
-
return Label(struct.unpack_from("<H", v)[0], v[2:].decode("utf-8", "replace"))
|
|
133
|
+
return Label(channel=struct.unpack_from("<H", v)[0], text=v[2:].decode("utf-8", "replace"))
|
|
132
134
|
if tag == ITEM["idle"] and len(v) == 3:
|
|
133
|
-
return Idle(struct.unpack_from("<H", v)[0], _name(IDLE, v[2]))
|
|
135
|
+
return Idle(channel=struct.unpack_from("<H", v)[0], mode=_name(IDLE, v[2]))
|
|
134
136
|
if tag == ITEM["slot"] and len(v) >= 18:
|
|
135
137
|
n, wire_fn, swdio, swclk, attach, retry_s, max_speed, idle, mech, name_len = struct.unpack_from("<BHHHBHIBBB", v)
|
|
136
138
|
name = v[17:17 + name_len].decode("ascii", "replace")
|
|
137
|
-
|
|
139
|
+
at = 17 + name_len
|
|
140
|
+
lock_len = v[at] if at < len(v) else 0
|
|
141
|
+
part = v[at + 1:at + 1 + lock_len] # after it: later fields (core §2.3), skipped
|
|
138
142
|
lock = None
|
|
139
|
-
if
|
|
140
|
-
half = (
|
|
141
|
-
lock = (
|
|
142
|
-
return Slot(n, wire_fn, (swdio, swclk), name, _name(ATTACH, attach), retry_s,
|
|
143
|
-
max_speed, _name(IDLE_CLOCK, idle))
|
|
143
|
+
if lock_len >= 3:
|
|
144
|
+
half = (lock_len - 1) // 2
|
|
145
|
+
lock = (part[0], part[1:1 + half], part[1 + half:])
|
|
146
|
+
return Slot(slot=n, wire_fn=wire_fn, pins=(swdio, swclk), name=name, attach=_name(ATTACH, attach), retry_s=retry_s,
|
|
147
|
+
mechanism=_name(MECHANISM, mech), lock=lock, max_speed=max_speed, idle_clock=_name(IDLE_CLOCK, idle))
|
|
144
148
|
if tag == ITEM["bind"] and len(v) >= 4:
|
|
145
149
|
port, mode, selected, n = struct.unpack_from("<BBBB", v)
|
|
146
150
|
streams = [(_name(STREAM, v[4 + 3 * k]), struct.unpack_from("<H", v, 5 + 3 * k)[0]) for k in range(n)]
|
|
147
|
-
return Bind(port, _name(MODE, mode), streams, selected)
|
|
151
|
+
return Bind(port=port, mode=_name(MODE, mode), streams=streams, selected=selected)
|
|
148
152
|
return (tag, v)
|
|
149
153
|
|
|
150
154
|
|
|
@@ -170,6 +174,7 @@ class State:
|
|
|
170
174
|
storage_bytes: int = 0
|
|
171
175
|
storage: str = "none" # none, applied, unreadable
|
|
172
176
|
saved_hash: int = 0
|
|
177
|
+
unreadable: str | None = None # why, when unreadable (probe.config §4)
|
|
173
178
|
items: list[int] = field(default_factory=list)
|
|
174
179
|
slots_max: int = 0
|
|
175
180
|
bind_modes: list[str] = field(default_factory=list)
|
|
@@ -216,6 +221,8 @@ class ProbeConfig(Interface):
|
|
|
216
221
|
if tag == DESCRIBE["storage"] and len(v) >= 9:
|
|
217
222
|
st.storage_bytes, state, st.saved_hash = struct.unpack_from("<IBI", v)
|
|
218
223
|
st.storage = STORAGE_STATE.get(state, str(state))
|
|
224
|
+
if len(v) >= 14 and v[13]:
|
|
225
|
+
st.unreadable = UNREADABLE.get(v[13], str(v[13]))
|
|
219
226
|
elif tag == DESCRIBE["items"]:
|
|
220
227
|
st.items = list(v)
|
|
221
228
|
elif tag == DESCRIBE["slots_max"] and v:
|
|
@@ -65,7 +65,7 @@ class PositionStream(Interface):
|
|
|
65
65
|
"""One answer's marks with serial >= from_serial (in serial order). -> (marks, more)."""
|
|
66
66
|
rd = m.Reader(self._call(self.MARKS, self._stream_prefix() + struct.pack("<I", from_serial), locked=False).payload)
|
|
67
67
|
more, count = rd.take("BB")
|
|
68
|
-
marks = [Mark(*rd.take("IQBIB")) for _ in range(count)]
|
|
68
|
+
marks = [Mark(*rd.element().take("IQBIB")) for _ in range(count)]
|
|
69
69
|
rd.tail()
|
|
70
70
|
return marks, bool(more)
|
|
71
71
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Host-side decoders for capture channels (oep.fixture.
|
|
1
|
+
"""Host-side decoders for capture channels (oep.fixture.logic: `LogicCapture.channel(data, k)` gives one value
|
|
2
2
|
per sample)."""
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|