oep-client-python 0.0.27__tar.gz → 0.0.28__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.27 → oep_client_python-0.0.28}/CHANGELOG.md +24 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/PKG-INFO +64 -2
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/README.ja.md +62 -1
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/README.md +63 -1
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/pyproject.toml +1 -1
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/__init__.py +1 -1
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/__main__.py +44 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/config.py +57 -1
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/endpoint.py +314 -13
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/fake.py +41 -10
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/fixture.py +54 -5
- oep_client_python-0.0.28/src/oep_client/pins.py +614 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/registry.py +5 -5
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/riscv.py +32 -8
- oep_client_python-0.0.28/src/oep_client/targets.py +97 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/README.ja.md +13 -1
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/README.md +13 -1
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/record.py +34 -1
- oep_client_python-0.0.28/tests/hw/results/9489dd2ae0953650-0.0.27+ec1df99-0.0.27.json +337 -0
- oep_client_python-0.0.28/tests/hw/results/esp32-pico-d4-50029191fe34-0.0.27+ac80e07-0.0.27.json +1006 -0
- oep_client_python-0.0.28/tests/hw/results/esp32-pico-d4-50029191fe34-0.0.27+ec1df99-0.0.27.json +1009 -0
- oep_client_python-0.0.28/tests/hw/results/esp32-pico-d4-50029191fe34-0.0.27-0.0.27.json +1010 -0
- oep_client_python-0.0.28/tests/hw/results/esp32-pico-d4-50029191fe34-unknown-0.0.26.json +99 -0
- oep_client_python-0.0.28/tests/hw/results/esp32-series-30eda0e343c6-0.0.27+ec1df99-0.0.27.json +492 -0
- oep_client_python-0.0.28/tests/hw/test_probe.py +923 -0
- oep_client_python-0.0.28/tests/test_config_idle.py +164 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/test_fake_spec.py +5 -5
- oep_client_python-0.0.28/tests/test_fake_targets.py +350 -0
- oep_client_python-0.0.28/tests/test_pins.py +188 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/test_session.py +7 -7
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/test_target_parts.py +66 -1
- oep_client_python-0.0.27/tests/hw/test_probe.py +0 -389
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/.gitignore +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/LICENSE +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/arm.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/capture.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/catalog.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/ch32_flash.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/cobs.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/console.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/core.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/decode.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/dump.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/fake_capture.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/fake_serial.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/fake_serve.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/frames.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/hid_stream.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/host.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/interfaces.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/link.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/linktest.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/message.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/names.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/rp2350.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/speed_record.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/uiapduino.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/src/oep_client/usb_stream.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/__init__.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/boards.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/conftest.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/firmware.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/flash.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/results/.gitignore +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/results/9489dd2ae0953650-0.0.26+5bafb92-0.0.26.json +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/results/9489dd2ae0953650-0.0.26+654b06a-0.0.26.json +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/results/esp32-d0wd-v3-0070070d9394-0.0.26+4fad559-0.0.26.json +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/results/esp32-d0wd-v3-0070070d9394-0.0.26+5bafb92-0.0.26.json +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/results/esp32-pico-d4-50029191fe34-0.0.25-0.0.26.json +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/results/esp32-pico-d4-50029191fe34-0.0.26+5bafb92-0.0.26.json +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/results/esp32-pico-d4-50029191fe34-0.0.26+654b06a-0.0.26.json +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/results/esp32-pico-d4-50029191fe34-0.0.26+e877631-0.0.26.json +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/results/esp32-series-30eda0e31108-0.0.26+5bafb92-0.0.26.json +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/results/esp32-series-30eda0e31108-0.0.26+654b06a-0.0.26.json +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/results/esp32-series-30eda0e343c6-0.0.26+5bafb92-0.0.26.json +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/hw/results/esp32-series-30eda0e343c6-0.0.26+654b06a-0.0.26.json +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/test_capabilities.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/test_capture.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/test_cobs.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/test_config.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/test_config_disable.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/test_fake_capture.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/test_fixture_targets.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/test_flash_console.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/test_hid_stream.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/test_interfaces.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/test_link_host.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/test_link_serial.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/test_linktest.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/test_port_speed.py +0 -0
- {oep_client_python-0.0.27 → oep_client_python-0.0.28}/tests/test_usb_stream.py +0 -0
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.0.28
|
|
6
|
+
- (EN) `oep pins` suggests the lines it found as probe.config labels (`<slot>.nrst`, `<slot>.power_hi`, host-development-guide §8.1) and an output idle for the power channel, instead of a slot reset_channel (not adopted: oep-spec v1-open-proposals §10); `--save` writes the slot and the labels.
|
|
7
|
+
- (JA) `oep pins` は見つけた線を probe.config の label(`<slot>.nrst`、`<slot>.power_hi`、host 開発ガイド §8.1)と、電源の channel の出力の idle として案内する(スロットの reset_channel は採らなかった: oep-spec v1-open-proposals §10)。`--save` はスロットと label を書く。
|
|
8
|
+
- (EN) Idle modes 3 / 4 (oep-spec 5013ffb / 7b3c319; registry synced, hash 050ffee525722713: idle_mode `output_low` 3, `output_high` 4). `config.Idle` takes `output-low` / `output-high` (`output_low` / `output_high` too, normalised to the hyphen form like `pull-up`), and `oep config idle <probe> <ch> output-high` offers them. `config.find_line(hst_or_items, name, slot=None)` finds a target's labelled line (`nrst`, `power_hi`, `power_lo`, `config.LINE_NAMES`; host-development-guide §8.1): `<slot>.<name>` first, then the bare name only while the settings hold at most one slot; with several slots and no slot given, or one label on two channels, it raises `config.AmbiguousLine` listing the candidates; None when there is none. The fake: an output idle drives its level while the channel is free, refused unsupported on `Endpoint.input_only` channels; every release (plan_release, a channel leaving a replaced plan - it used to keep the old channels' gpio modes - the lease lapse, force) goes to the idle state, and a gpio channel in both the old and the new plan keeps its state and drive; a gpio line a plan takes keeps that state until its first set (a read sees the idle level); at boot the idle states are applied before the plans and the at-boot attach (they came after the attach).
|
|
9
|
+
- (JA) idle の mode 3 / 4(oep-spec 5013ffb / 7b3c319。registry を写し直した、hash 050ffee525722713: idle_mode の `output_low` 3、`output_high` 4)。`config.Idle` が `output-low` / `output-high` を受ける(`output_low` / `output_high` も。`pull-up` と同じくハイフンの形にそろえる)。`oep config idle <probe> <ch> output-high` でも選べる。`config.find_line(hst_or_items, name, slot=None)` は target の label の付いた線(`nrst`、`power_hi`、`power_lo`、`config.LINE_NAMES`。host-development-guide §8.1)を探す: `<スロット>.<名前>` を先に、無ければ名前だけ(設定のスロットが 1 つ以下のときだけ)。スロットが 2 つ以上で slot を渡さないときと、1 つの label が 2 つの channel にあるときは、候補を並べた `config.AmbiguousLine` を上げる。無ければ None。fake: 出力の idle は channel が空いている間その level で駆動し、`Endpoint.input_only` の channel では unsupported で断る。解放はどの道でも(plan_release、置き換えた plan から外れる channel - これまでは古い channel の gpio の mode が残っていた - lease の失効、force)空きの状態に戻り、古い plan にも新しい plan にもある gpio の channel は状態と駆動を保つ。plan が取った gpio の線は最初の set までその状態を保つ(read は idle の level を見る)。起動では idle を plan と at boot の attach より先に掛ける(これまでは attach の後だった)。
|
|
10
|
+
- (EN) `oep pins <probe> [--power CH] [--exclude CH,...] [--wire swio|rvswd|swd] [--steps ...] [--save] [--json]` (module `pins`, `PinFinder`): finds where a target is wired on a probe whose pins the host chooses. Steps: classify every gpio channel from 16 reads under pull-up, both pulls, then pull-down (floating / pulled-up / driven-high / driven-low / active; with `--power` the target is read switched off first and the channels that read otherwise "follow power"); hold each floating / pulled-up channel low (open drain) while an active channel is watched (the watch lasts 3 x the longest lull of the running activity, 0.3-1 s); scan the wire over the floating / pulled-up channels only (rvswd / swd: pairs, at most 600); attach (halt), name the family by target_id and read a CH32V00x's option bytes (NRST on or off; read only); confirm the reset line by attach under reset (dpc = reset vector); print a suggested slot (`reset_channel` as a comment while `config.Slot` has none) and write it only with `--save`. Driven / active channels are never driven, scanned or held, the power channel is touched only with `--power`, every plan is released at the end, the run stays under 60 s. A new gpio plan lets every pin of the old one go first (the power channel too), so with `--power` each new plan is followed by a clean power cycle. On the ESP32-P4 with a CH32V003 (power GPIO5): SWIO 19 and NRST 4 found untold in 7-8 s, 6 runs of 6; the idle-high UART (22 / 23) is classified driven and left alone, the app's output 21 active.
|
|
11
|
+
- (JA) `oep pins <probe> [--power CH] [--exclude CH,...] [--wire swio|rvswd|swd] [--steps ...] [--save] [--json]`(モジュール `pins`、`PinFinder`): ピンを host が選ぶ probe で、target がどこにつながっているかを探す。段: gpio のすべての channel を pull-up、両方の pull、pull-down の順に 16 回ずつ読んで分ける(floating / pulled-up / driven-high / driven-low / active。`--power` では先に target の電源を切って読み、違って読めた channel を「電源に従う」とする)。floating / pulled-up の channel を 1 本ずつオープンドレインで low に保ち、active の channel を見る(見る長さは走っている時の最長の静かな間の 3 倍、0.3〜1 秒)。floating / pulled-up の channel だけで線を scan する(rvswd / swd は組で 600 組まで)。attach(halt)して target_id で系統を見て、CH32V00x なら option bytes で NRST の有無を読む(読むだけ)。リセットをかけながらの attach(dpc = リセットのベクタ)でリセットの線を確かめる。スロットの行を勧め(`config.Slot` に無い間 `reset_channel` はコメント)、`--save` のときだけ書く。driven / active の channel は駆動も scan も保持もしない、電源の channel は `--power` のときだけ触る、最後に plan をすべて解く、全体は 60 秒以内。gpio の plan を新しくすると前の plan のピンが(電源の channel も)いったん離れるので、`--power` では新しい plan のたびにきれいに電源を入れ直す。ESP32-P4 と CH32V003(電源 GPIO5)で、SWIO 19 と NRST 4 を指定なしで 7〜8 秒で見つけた(6 回中 6 回)。idle-high の UART(22 / 23)は driven として触らず、アプリの出力 21 は active。
|
|
12
|
+
- (EN) `targets`: what the host knows per target family in one table, `FAMILIES` (wire, target_id match on the WCH DMI 0x7F value, reset vector, an option-byte NRST reader, max_speed / idle_clock, notes): ch32v00x (swio, ids 0x002-0x007 in the top 12 bits, `v00x_nrst`: 0x1FFFF800 word0 USER bits[4:3] = RST_MODE, 11 = PD7 is a GPIO), ch32x035 (rvswd, 0x035, idle_clock high), ch32l103 (rvswd, 1 MHz, idle_clock low; id not recorded), ch32v203 (idle_clock low; id not recorded). `identify(target_id)`, `by_name()`, `describe_id()`. oep-spec docs/target-scan-notes.ja.md records the sources.
|
|
13
|
+
- (JA) `targets`: host が target の系統ごとに知っていることを 1 つの表 `FAMILIES` に(線、WCH DMI 0x7F の値での target_id の照合、リセットのベクタ、NRST を option で読む関数、max_speed / idle_clock、注): ch32v00x(swio、上位 12 ビットが 0x002〜0x007、`v00x_nrst`: 0x1FFFF800 の word0 の USER bits[4:3] = RST_MODE、11 = PD7 は GPIO)、ch32x035(rvswd、0x035、idle_clock high)、ch32l103(rvswd、1 MHz、idle_clock low。id は未記録)、ch32v203(idle_clock low。id は未記録)。`identify(target_id)`、`by_name()`、`describe_id()`。出典は oep-spec の docs/target-scan-notes.ja.md。
|
|
14
|
+
- (EN) `riscv.Wire.find_reset_line(..., pins=None)`: it returned [] on the ESP32-P4 with the CH32V003's real NRST among the candidates. Root cause: it attached without pins; on a wire whose pins the host chooses (role_channels) such an attach only joins the wire's one live connection, and the first try's detach closed that connection, so every later attach was rejected unavailable - and a rejection was taken for "not a reset line" and skipped. Now `pins` is passed to every attach (None: the pins of the wire's one live connection), a connection that was there before the search is left open, only unsupported or an unavailable naming the channel skips it, any other rejection is raised. On the P4: [4] over 48 candidates with tries=3 in 4.2 s, with and without a plain attach first. Hold time is not the cause: an attach under reset through 4 stopped at dpc 0 with 1, 2, 5, 10 and 20 ms holds, 3 of 3 each (RST_MODE 10, the 12 ms window).
|
|
15
|
+
- (JA) `riscv.Wire.find_reset_line(..., pins=None)`: ESP32-P4 と CH32V003 で、本物の NRST が候補にあるのに [] を返した。原因: pins を付けずに attach していた。ピンを host が選ぶ線(role_channels)では、pins の無い attach はその線の唯一の生きた接続に加わるだけで、1 回目の試しの detach がその接続を閉じた後は、どの attach も unavailable で断られ、断りを「リセットの線ではない」と読んで飛ばしていた。今は毎回の attach に `pins` を付け(None ならその線の唯一の生きた接続のピン)、探す前からあった接続は閉じない。飛ばすのは unsupported と、その channel を名指しした unavailable だけで、ほかの断りは例外にする。P4 で 48 候補、tries=3 で [4](4.2 秒)。先に普通の attach をしてあってもなくても同じ。保持時間は原因ではない: 4 でリセットをかけながらの attach は 1、2、5、10、20 ms の保持でどれも 3 回中 3 回 dpc 0(RST_MODE 10、12 ms の窓)。
|
|
16
|
+
- (EN) The fake: `Endpoint.gpio_world(channel, mode)` (a test hook: the level an input-mode read sees, from the lines outside) and `FakeTarget.reset_line` (an attach under reset through any other channel halts where the hart ran, off the vector). `tests/test_pins.py` runs `oep pins` against a P4-shaped profile with a V003-like world.
|
|
17
|
+
- (JA) fake: `Endpoint.gpio_world(channel, mode)`(試験の hook: 入力の mode の読みが見る外の線のレベル)と `FakeTarget.reset_line`(ほかの channel でリセットをかけながら attach すると、hart は走っていた所で止まり、ベクタに来ない)。`tests/test_pins.py` が P4 の形の profile と V003 らしい外の線で `oep pins` を動かす。
|
|
18
|
+
- (EN) Registry synced to oep-spec 75ca9ee (hash 3861100e11fb3b22): oep.fixture.i2c-target describe `max_stretch_us` (0x41, u32). The fake follows fixture §3 / §4 as 75ca9ee closed them: an i2c-target / spi-target plan holds each role exactly once on distinct channels (a missing role, a role twice or two roles on one channel -> malformed, before held_by_settings); the mode-1 wait survives a frame (armed stays 1 until the next arm_rx / reset / configure / plan release); an address-only write counts nothing in any mode; read_rx in state 0 is unavailable cause 6 on both targets; a 0-bit SPI transfer (`spi_transfer(..., bits=0)`, or empty MOSI) counts nothing and the arm keeps waiting, and the queued data is bits / 8 rounded up, capped at length. `p4-x035` declares max_stretch_us 100000 and stretch refuses more as unsupported (the fixed `endpoint.STRETCH_MAX_US` is gone); `esp32-v003` (no features bit1) declares none. `fixture.I2cTarget` gains `max_length`, `max_clock_hz`, `features`, `queue_depth` and `max_stretch_us` (None when not declared; features 0), `fixture.SpiTarget` the same but max_stretch_us, read once from describe. `tests/hw` i2c_target records `declared.max_stretch_us`.
|
|
19
|
+
- (JA) registry を oep-spec 75ca9ee に合わせた(hash 3861100e11fb3b22): oep.fixture.i2c-target の describe に `max_stretch_us`(0x41、u32)。fake を 75ca9ee で埋まった fixture §3 / §4 に合わせた: i2c-target / spi-target の plan は各 role をちょうど 1 つずつ別々の channel で持つ(role が欠ける、2 つある、2 つの role が同じ channel は malformed。held_by_settings より先)。mode 1 の待ちはフレームを受けても終わらない(次の arm_rx / reset / configure / plan を解くまで armed は 1)。アドレスだけの書き込みはどの mode でも何も数えない。state 0 の read_rx は両 target で unavailable cause 6。0 ビットの SPI 転送(`spi_transfer(..., bits=0)`、または MOSI が空)は何も数えず arm は待ち続ける。積む data は bits / 8 の切り上げで length で止める。`p4-x035` は max_stretch_us 100000 を宣言し、それを超える stretch は unsupported(固定の `endpoint.STRETCH_MAX_US` は無くした)。`esp32-v003`(features bit1 なし)は宣言しない。`fixture.I2cTarget` に `max_length`、`max_clock_hz`、`features`、`queue_depth`、`max_stretch_us`(宣言が無ければ None、features は 0)、`fixture.SpiTarget` に max_stretch_us 以外の同じものを足した(describe から 1 回読む)。`tests/hw` の i2c_target は `declared.max_stretch_us` を記録する。
|
|
20
|
+
- (EN) `tests/hw` spi_target: with no controller wired, an arm must stay armed (0 transactions, 0 errors) and a second arm is refused unavailable, as fixture §4 says. The check was loosened for phantom CS-edge transactions the probe counted; oep-probe-arduino a3dcabd stopped counting a CS edge without SCK. ATOM with the probe at ac80e07: 12 passed.
|
|
21
|
+
- (JA) `tests/hw` の spi_target: コントローラーをつないでいないとき、arm は armed のまま(transactions 0、errors 0)で、2 回目の arm は unavailable で断られること(fixture §4 のとおり)。probe が CS の縁だけで数えていた幻の転送のために緩めていた確かめを元に戻した(oep-probe-arduino a3dcabd で SCK の無い CS の縁を数えなくなった)。probe ac80e07 の ATOM で 12 passed。
|
|
22
|
+
- (EN) The fake probe runs `oep.fixture.i2c-target` and `oep.fixture.spi-target` (oep-spec oep-if-fixture §3 / §4); it only declared them before, so `tests/hw` `i2c_target` / `spi_target` failed on `fake-esp32-v003` and now pass. The plan takes SDA / SCL and SCK / MOSI / MISO / CS with the gpio / uart checks, plus role_channels per role and an exact channel_group (core §7.4); configure / arm_rx / arm / preload_tx / read_rx (TLV ns) / status (lock-free) / reset / stretch (features bit1 only, else unknown_operation) with the refusals in core §4.3's order; a plan change puts the target back to state 0. Declared: `p4-x035` i2c max_length 128, max_clock_hz 1 MHz, features 0b11, queue_depth 8; spi max_length 64, 3 MHz, features 0b1 (LSB first), queue_depth 8. `esp32-v003` i2c 16, 100 kHz, features 0b01, queue_depth 4; spi (two channel_groups) 32, 3 MHz, features 0, queue_depth 4. No bus controller: test hooks `Endpoint.i2c_write` / `i2c_read` / `spi_transfer` run one transaction (queued, consumed, overflow, not armed - transactions and errors counted). `fake.with_stand_in(probe)` adds the fn with the stand-in operations the session tests used the esp32-v003 i2c-target for.
|
|
23
|
+
- (JA) 偽の probe が `oep.fixture.i2c-target` と `oep.fixture.spi-target`(oep-spec oep-if-fixture §3 / §4)を動かす。これまでは宣言だけで、`fake-esp32-v003` の `tests/hw` の `i2c_target` / `spi_target` は落ちていた。今は通る。plan は SDA / SCL と SCK / MOSI / MISO / CS を、gpio / uart と同じ確かめに、role ごとの role_channels と channel_group の完全一致(core §7.4)を足して受ける。configure / arm_rx / arm / preload_tx / read_rx(TLV ns)/ status(ロック不要)/ reset / stretch(features bit1 のときだけ。ほかは unknown_operation)。断りは core §4.3 の順。plan が変わると target は state 0 に戻る。宣言: `p4-x035` の i2c は max_length 128、max_clock_hz 1 MHz、features 0b11、queue_depth 8。spi は 64、3 MHz、features 0b1(LSB 先)、queue_depth 8。`esp32-v003` の i2c は 16、100 kHz、features 0b01、queue_depth 4。spi(channel_group 2 つ)は 32、3 MHz、features 0、queue_depth 4。バスの controller は無い: 試験の hook `Endpoint.i2c_write` / `i2c_read` / `spi_transfer` が 1 回のトランザクションを起こす(積む、取り出す、あふれ、未 arm。transactions と errors を数える)。セッションの試験が esp32-v003 の i2c-target を代わりに使っていた仮の操作の fn は `fake.with_stand_in(probe)` が足す。
|
|
24
|
+
- (EN) `tests/hw` covers the interfaces it did not yet, each skipping when the probe does not list it, after `uart` and before `port_speed`: `capture` (oep.fixture.logic: a one-shot at the lowest declared rate over 10 ms on the two free channels, pulled up then down through fixture gpio planned on the same pins - all ones, then all zeros; sample count, timing within the declared uncertainty, generation +1 per start), `capture_analog` (one-shot on a free ADC channel, widest frontend; the pull bands are judged only when the probe lets gpio share the pin), `capture_group` (logic + analog bound and started together, both read back, the tracks' offsets from the group's start_ns), `i2c_target` / `spi_target` (configure, status, a preloaded queue / an armed transaction, reset, release - no bus controller, so floating-line glitches are recorded, not judged), `console` (with `OEP_HW_TARGET`: open with mechanism dmseq on the wire connection, read 1 s, close). `OEP_HW_ANALOG`, `OEP_HW_I2C`, `OEP_HW_SPI` name channels. ATOM, firmware 0.0.27: logic 400 kHz w 8, 4000 / 4000 ones then 0 / 4000 on both pins, done 24 ms after start for a 10 ms window, 4 KB read in 0.38 s; analog 20 kHz s 16 b 12, the floating pin at 2.8 / 4095; the group's tracks start 25 µs (logic) and 516 µs (analog) after the group; the analog refuses to share its pin with gpio (unavailable, as oep-if-capture §1.2 allows) and an idle pull-up on it reads only 137 mV mean once the ADC owns the pad; the SPI target counts 1-2 phantom transactions on its floating SCK / CS right after an arm.
|
|
25
|
+
- (JA) `tests/hw` に、まだ無かった interface の試験を足した(probe が list に出さなければ skip。`uart` の後、`port_speed` の前): `capture`(oep.fixture.logic: 空き 2 チャネルで、同じピンに plan した fixture gpio で pull-up → pull-down し、宣言の最低のレートで 10 ms のワンショット。全部 1、次に全部 0。サンプル数、宣言の不確かさの内の時間、start ごとに世代 +1)、`capture_analog`(空きの ADC チャネルでいちばん広い frontend のワンショット。pull の帯は probe が gpio とピンを共有させるときだけ判定)、`capture_group`(ロジックとアナログを bind して一緒に start、両方読み出し、組の start_ns からの各トラックのずれ)、`i2c_target` / `spi_target`(configure、status、preload の列 / arm した転送、reset、release。バスに controller は無いので浮いた線のグリッチは記録して判定しない)、`console`(`OEP_HW_TARGET` のとき: 線の connection に mechanism dmseq で open、1 秒読む、close)。`OEP_HW_ANALOG`、`OEP_HW_I2C`、`OEP_HW_SPI` でチャネルを指定できる。ATOM、firmware 0.0.27: ロジック 400 kHz w 8、両ピンとも 4000 / 4000 が 1、次に 0 / 4000、10 ms の窓に対し start の 24 ms 後に done、4 KB の読み出しに 0.38 秒。アナログ 20 kHz s 16 b 12、浮いたピンは 2.8 / 4095。組のトラックは組の 25 µs(ロジック)と 516 µs(アナログ)後に始まる。アナログは gpio とのピンの共有を断り(unavailable。oep-if-capture §1.2 が許す)、その上の idle の pull-up は ADC がパッドを持つと平均 137 mV しか読めない。SPI の target は arm の直後、浮いた SCK / CS で 1〜2 の幻の転送を数える。
|
|
26
|
+
- (EN) `tests/hw` port_speed: the run fails only when no candidate passes; a candidate that fails the guide's verdict is recorded (`passed_rates` / `failed_rates`). The ATOM's bridge puts 1500000 over the threshold in some runs (11-14 % on one flow against a 2-5 % baseline) while 500000 passes every time - a fact about that bridge, not about the probe or the client.
|
|
27
|
+
- (JA) `tests/hw` の port_speed: 通る候補が 1 つも無いときだけ落とす。ガイドの判定に落ちた候補は記録に残す(`passed_rates` / `failed_rates`)。ATOM の変換は 1500000 が回によって閾値を超え(基準 2〜5 % に対し 1 つの流し方で 11〜14 %)、500000 は毎回通る。probe や client ではなく変換の性質。
|
|
28
|
+
|
|
5
29
|
## 0.0.27
|
|
6
30
|
- (EN) `tests/hw` port_speed verdict follows the host guide §7.3.2: a one-at-a-time cell at a raised rate fails only when broken + lost ≥ 3 and its ratio is over max(2 × the boot-speed ratio of the same flow, 5 % `OEP_HW_ERROR_MAX`) - the absolute 1 % failed the CH340 jig on bursts the boot speed shows too. Results with the probe 5bafb92 (port_speed handshake per core §3.5, block length) on the ATOM, the V003 jig (SWIO) and the X035 jig (DFU, RVSWD): all pass.
|
|
7
31
|
- (JA) `tests/hw` の port_speed の判定を host 開発ガイド §7.3.2 に合わせた: 上げた速さの 1 つずつの条件で、broken + lost が 3 以上かつ割合が max(起動時の速さの同じ流し方の割合 × 2, 5 %(`OEP_HW_ERROR_MAX`)) を超えたときだけ落とす(絶対の 1 % は、起動時の速さにもある CH340 のまとまった落ちで治具を落としていた)。probe 5bafb92(core §3.5 の握手、block 長)での結果: ATOM、V003 治具(SWIO)、X035 治具(DFU、RVSWD)すべて pass。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: oep-client-python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.28
|
|
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
|
|
@@ -59,7 +59,9 @@ oep-client-python X.Y.Z (until the v1 freeze every release may break the wire; t
|
|
|
59
59
|
| `host` | requests and results, the session id and the lock, `call()` (raises unless it worked), pipelining, the errors (`OepError` / `Rejected` / `Failed`; `Expired` when the lease lapsed - the session is never re-opened behind the caller's back, `Host.epoch` moves) |
|
|
60
60
|
| `link` | transports: serial ports (always COBS + CRC as `0x00 <COBS> 0x00`, bytes outside frames skipped as noise, opened exclusively), USB vendor bulk / HID and TCP (length frames, the §5.1 resync); matching by corr and resending; `open_host(target)` |
|
|
61
61
|
| `core` | interfaces by name (cached), confirm (with the probe's `boot_id`), the probe's describe (declarations only, cached per boot: labels, the transport list, `max_op_ms`), taking the lock (`take`), the pin plan, the `Interface` base |
|
|
62
|
-
| `riscv` | `oep.wire.rvswd` / `oep.wire.swio` (scan, attach - `max_speed` always sent, `reset=(channel, hold_ms)` for an attach under reset -, detach, connections), `oep.target.riscv-dm` (answers count their values; `RunResult.not_halted`), finding the reset line, attach through GPIO |
|
|
62
|
+
| `riscv` | `oep.wire.rvswd` / `oep.wire.swio` (scan, attach - `max_speed` always sent, `reset=(channel, hold_ms)` for an attach under reset -, detach, connections), `oep.target.riscv-dm` (answers count their values; `RunResult.not_halted`), finding the reset line (`find_reset_line(candidates, pins=...)`), attach through GPIO |
|
|
63
|
+
| `targets` | what the host knows per target family, in one table (`FAMILIES`: wire, target_id match, reset vector, option-byte NRST reader, max_speed / idle_clock); `identify(target_id)` |
|
|
64
|
+
| `pins` | `oep pins`: classify the channels, hold-low search, scan, identify, confirm the reset line, suggest a slot (`PinFinder`) |
|
|
63
65
|
| `console` | `oep.target.console` (position streams: read answers carry their length, marks are `time_ns`, the lock-free `streams()` list) and `ConsoleIO`, read as bytes |
|
|
64
66
|
| `fixture` | `oep.fixture.gpio` / `uart` (its stream is the plan's; `status()`) / `i2c-target` / `spi-target` (revision 1) |
|
|
65
67
|
| `config` | `oep.probe.config` (slots, binds, plan / label / idle / uart items, get / set / unset / save / erase; `describe()` = the declarations, `state()` = the live storage / slot / bind state; `hash_of(items)` = the probe's hash) |
|
|
@@ -103,6 +105,7 @@ oep config bind <probe> --port 1 --mode last-reset --stream slot:x035
|
|
|
103
105
|
oep config uart <probe> oep.fixture.uart 115200 --format 8N1 # applied whenever that UART's plan has RX or TX
|
|
104
106
|
oep config save <probe> # kept over a restart (also: remove = unset, erase)
|
|
105
107
|
oep speed <probe> 1500000,921600,500000 # port_speed: try faster rates on a UART bridge, print the report (below)
|
|
108
|
+
oep pins <probe> --power 5 --wire swio # where the target is wired: debug pins, reset line, a slot (below)
|
|
106
109
|
```
|
|
107
110
|
|
|
108
111
|
`<probe>` is a serial port, `tcp://HOST:PORT` or `usb[:VID:PID[:SERIAL]]`. A change takes the lock (owner "oep config") and
|
|
@@ -110,6 +113,60 @@ ends the session after it; it takes effect at once and, after `save`, stays over
|
|
|
110
113
|
|
|
111
114
|
A run on hardware: ArduinoCore-CH32's `tests/manual/oep_smoke/` (`oep_smoke.py`, `oep_probe_checks.py`).
|
|
112
115
|
|
|
116
|
+
## Finding the pins (`oep pins`)
|
|
117
|
+
|
|
118
|
+
`oep pins <probe> [--power CH] [--exclude CH,...] [--wire swio|rvswd|swd] [--steps ...] [--save] [--json]` finds where a
|
|
119
|
+
target is wired to a probe whose pins the host chooses. Each step says what it did; the whole run stays under a minute and
|
|
120
|
+
every plan is released at the end. The procedure and its reasons are in oep-spec's host development guide (「ピンの探し方
|
|
121
|
+
(参考)」); what each target family needs is in one table, `targets.FAMILIES` (wire, reset vector, the option-byte reader
|
|
122
|
+
for the reset line, max_speed / idle_clock), which oep-spec `docs/target-scan-notes.ja.md` records the sources of.
|
|
123
|
+
|
|
124
|
+
1. **classify**: every channel `oep.fixture.gpio` allows is read 16 times under pull-up, both pulls, then pull-down:
|
|
125
|
+
floating, pulled-up (both pulls still read 1: a weak pull-up such as a reset line's), driven-high / driven-low (the same
|
|
126
|
+
level under both pulls: push-pull or a strong pull - an idle-high UART line looks like this), active (changes between
|
|
127
|
+
reads). With `--power CH` the target is first read switched off: the channels that read otherwise follow its power.
|
|
128
|
+
These are candidates only.
|
|
129
|
+
2. **hold**: each floating / pulled-up channel is held low (open drain) while an active channel is watched; a channel
|
|
130
|
+
that stops it is a reset-line candidate.
|
|
131
|
+
3. **scan**: the wire over the floating / pulled-up channels (rvswd / swd: pairs, at most 600) - never over a driven or
|
|
132
|
+
active channel, the power channel or `--exclude`.
|
|
133
|
+
4. **identify**: attach (halt) on what answered; the target_id names the family; a CH32V00x's option bytes say whether
|
|
134
|
+
NRST exists (read only, never written); then resume.
|
|
135
|
+
5. **reset**: attach under reset through each candidate - the real line stops the hart at the reset vector.
|
|
136
|
+
6. **slot**: a suggested `oep config slot` line (the reset channel as a comment while `config.Slot` has no
|
|
137
|
+
`reset_channel`); `--save` writes it (set + save). Nothing is written without `--save`.
|
|
138
|
+
|
|
139
|
+
Safety: a driven or active channel is never driven, scanned or held; the power channel is touched only with `--power`;
|
|
140
|
+
holding low is open drain only. A new gpio plan lets every pin of the old one go first - the power channel too - so with
|
|
141
|
+
`--power` each new plan is followed by a clean power cycle (`power_cycles` in the report).
|
|
142
|
+
|
|
143
|
+
ESP32-P4 with a CH32V003 (power on GPIO5), 2026-10-02:
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
$ oep pins /run/board-identify/by-id/esp32-series-30eda0ea068b --power 5 --wire swio
|
|
147
|
+
power: channel 5 low 300 ms (target off: read), then high 400 ms before the reads
|
|
148
|
+
classify: 52 channels, 16 reads each under pull-up, both pulls, pull-down
|
|
149
|
+
floating 0-3,6,9-20,26-34,36-50,52-54
|
|
150
|
+
pulled-up 4
|
|
151
|
+
driven-high 7-8,22-23,35 (never scanned or held)
|
|
152
|
+
driven-low 51 (never scanned or held)
|
|
153
|
+
active 21 (21: 8 changes under pull-up)
|
|
154
|
+
follow power 4,6,9-11,13,15-16,19-23,32-33 (read otherwise with the target off: wired to it; candidates only)
|
|
155
|
+
reset line (hold low): 45 candidates, each held low (open drain) up to 390 ms while watching 21 (105 changes in 1.0 s running, longest lull 130 ms)
|
|
156
|
+
hold 4 low: 21 stopped
|
|
157
|
+
45 held in 3.4 s: stopped by 4
|
|
158
|
+
scan swio: 45 channels (0-4,6,9-20,26-34,36-50,52-54; not 7-8,21-23,35,51: driven / active) in 0.14 s -> 19
|
|
159
|
+
attach swio 19: target_id 00310510 (WCH DMI 0x7F) -> ch32v00x, halted at dpc 0x108
|
|
160
|
+
option bytes (read only): RST_MODE 10 (USER 0xf7): NRST on PD7, 12 ms ignore window
|
|
161
|
+
attach under reset through 4 (held 20 ms): dpc 0x0 -> the reset line
|
|
162
|
+
slot: oep config slot ... --name ch32v00x --wire swio --pins 19 (not written; --save writes it)
|
|
163
|
+
# reset_channel 4 (probe.config §1.1; this client's slot has no reset_channel yet: name it in the attach's reset TLV)
|
|
164
|
+
released every plan (channel 5 is back to its idle state: the target is powered only while something drives it)
|
|
165
|
+
done in 7.3 s
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
22 / 23 are the target's UART (idle high: driven, so neither scanned nor held); 21 is an output its app toggles.
|
|
169
|
+
|
|
113
170
|
## A faster UART bridge (port_speed, opt-in)
|
|
114
171
|
|
|
115
172
|
A probe whose describe declares `port_speed` (oep-core §3.5; the classic ESP32 reference firmware does) lets the host
|
|
@@ -173,6 +230,11 @@ period 64 (k // 2 + 1) samples, 12-bit values in 16-bit slots; ESP32-style front
|
|
|
173
230
|
Vrefint) and `oep.fixture.capture-group` binding the logic and the analog: started together, the analog 5 us later
|
|
174
231
|
(+-2 us), the trigger of one marked on both. Times are ns on the probe's one clock.
|
|
175
232
|
|
|
233
|
+
`oep.fixture.i2c-target` and `oep.fixture.spi-target` (`p4-x035`, `esp32-v003`) take their roles from the plan (SDA / SCL;
|
|
234
|
+
SCK / MOSI / MISO / CS - on `esp32-v003` one of two fixed channel_groups exactly) and answer every op of fixture §3 / §4.
|
|
235
|
+
There is no bus controller: an arm stays armed and nothing is received until a test calls a hook on the endpoint
|
|
236
|
+
(`i2c_write` / `i2c_read` / `spi_transfer`: one transaction on the bus).
|
|
237
|
+
|
|
176
238
|
Other programs' tests run `fake_serve` as a child process:
|
|
177
239
|
|
|
178
240
|
```sh
|
|
@@ -33,7 +33,9 @@ X.Y.Z(v1 の凍結までは、どのリリースも wire を壊しうるので
|
|
|
33
33
|
| `host` | 要求と結果、session_id とロック、`call()`(失敗なら例外)、pipeline、エラーの階層(`OepError` / `Rejected` / `Failed`。lease 切れなら `Expired`。黙って open し直さず、`Host.epoch` が進む) |
|
|
34
34
|
| `link` | transport: シリアルの口(常に COBS + CRC、`0x00 <COBS> 0x00`、フレームの外は雑音として捨てる、排他で開く)、USB vendor bulk / HID と TCP(長さつきフレーム、§5.1 の立て直し)、corr による照合と送り直し、`open_host(target)` |
|
|
35
35
|
| `core` | インターフェースを名前で探す(キャッシュつき)、confirm(probe の `boot_id` つき)、probe の describe(宣言だけ。起動の間 cache: ラベル、transport の一覧、`max_op_ms`)、ロックの取り方(`take`)、ピンの割り当て(plan)、`Interface` の土台 |
|
|
36
|
-
| `riscv` | `oep.wire.rvswd` / `oep.wire.swio`(scan、attach: `max_speed` は常に送る、`reset=(channel, hold_ms)` でリセットをかけながら attach、detach、connections)、`oep.target.riscv-dm`(応答は値の数を持つ。`RunResult.not_halted
|
|
36
|
+
| `riscv` | `oep.wire.rvswd` / `oep.wire.swio`(scan、attach: `max_speed` は常に送る、`reset=(channel, hold_ms)` でリセットをかけながら attach、detach、connections)、`oep.target.riscv-dm`(応答は値の数を持つ。`RunResult.not_halted`)、リセット線の探索(`find_reset_line(candidates, pins=...)`)、GPIO 経由の attach |
|
|
37
|
+
| `targets` | host が target の系統ごとに知っていることを 1 つの表に(`FAMILIES`: 線、target_id の照合、リセットのベクタ、NRST を option で読む関数、max_speed / idle_clock)。`identify(target_id)` |
|
|
38
|
+
| `pins` | `oep pins`: channel の分類、low に保つ探索、scan、識別、リセットの線の確かめ、スロットの提案(`PinFinder`) |
|
|
37
39
|
| `console` | `oep.target.console`(位置つきのストリーム: read の応答は長さを持ち、マークは `time_ns`、ロック不要の `streams()`)と、バイト列として読む `ConsoleIO` |
|
|
38
40
|
| `fixture` | `oep.fixture.gpio` / `uart`(ストリームは plan が作る。`status()`)/ `i2c-target` / `spi-target`(revision 1) |
|
|
39
41
|
| `config` | `oep.probe.config`(スロット、bind、plan / label / idle / uart の項目、get / set / unset / save / erase。`describe()` = 宣言、`state()` = 保存・スロット・bind の今の状態、`hash_of(items)` = probe と同じ hash) |
|
|
@@ -77,12 +79,67 @@ oep config bind <probe> --port 1 --mode last-reset --stream slot:x035
|
|
|
77
79
|
oep config uart <probe> oep.fixture.uart 115200 --format 8N1 # その UART の plan に RX / TX が付くたびに掛かる
|
|
78
80
|
oep config save <probe> # 再起動の後も残す(remove = unset、erase もある)
|
|
79
81
|
oep speed <probe> [--candidates 921600,500000] [--verify [--flows out:2]] # port_speed: 速い速さを試し、結果を出す(下)
|
|
82
|
+
oep pins <probe> --power 5 --wire swio # target のつながり方: debug のピン、リセットの線、スロット(下)
|
|
80
83
|
```
|
|
81
84
|
|
|
82
85
|
`<probe>` はシリアルの口、`tcp://HOST:PORT`、`usb[:VID:PID[:SERIAL]]`。変更はロックを取り(owner "oep config")、終わったら
|
|
83
86
|
セッションを閉じる。変更はすぐ効き、`save` の後は再起動しても残る。
|
|
84
87
|
実機での一通りの確認は ArduinoCore-CH32 の `tests/manual/oep_smoke/`(`oep_smoke.py`、`oep_probe_checks.py`)。
|
|
85
88
|
|
|
89
|
+
## ピンを探す(`oep pins`)
|
|
90
|
+
|
|
91
|
+
`oep pins <probe> [--power CH] [--exclude CH,...] [--wire swio|rvswd|swd] [--steps ...] [--save] [--json]` は、ピンを host
|
|
92
|
+
が選ぶ probe で、target がどこにつながっているかを探します。各段は何をしたかを出し、全体は 1 分以内に終わり、最後に plan を
|
|
93
|
+
すべて解きます。手順とその理由は oep-spec の host 開発ガイド(「ピンの探し方(参考)」)にあります。target の系統ごとに要る
|
|
94
|
+
こと(線、リセットのベクタ、リセットの線の有無を読む option の読み方、max_speed / idle_clock)は 1 つの表 `targets.FAMILIES`
|
|
95
|
+
にまとめ、その出典は oep-spec の `docs/target-scan-notes.ja.md` に記録します。
|
|
96
|
+
|
|
97
|
+
1. **classify**: `oep.fixture.gpio` が許すすべての channel を、pull-up、両方の pull、pull-down の順に 16 回ずつ読む: floating、
|
|
98
|
+
pulled-up(両方の pull でも 1: リセットの線のような弱い pull-up)、driven-high / driven-low(どちらの pull でも同じレベル:
|
|
99
|
+
push-pull か強い pull。idle-high の UART の線はこう見える)、active(読むたびに変わる)。`--power CH` では先に target の
|
|
100
|
+
電源を切って読み、違って読めた channel が電源に従う。どれも候補を出すだけ。
|
|
101
|
+
2. **hold**: floating / pulled-up の channel を 1 本ずつオープンドレインで low に保ち、active の channel を見る。止まれば
|
|
102
|
+
リセットの線の候補。
|
|
103
|
+
3. **scan**: floating / pulled-up の channel で線を scan する(rvswd / swd は組で、600 組まで)。driven / active の channel、
|
|
104
|
+
電源の channel、`--exclude` は scan しない。
|
|
105
|
+
4. **identify**: 答えた線に attach(halt)し、target_id で系統を見る。CH32V00x は option bytes で NRST の有無を見る(読むだけ、
|
|
106
|
+
書かない)。その後 resume。
|
|
107
|
+
5. **reset**: 候補ごとにリセットをかけながら attach する。本物の線なら hart はリセットのベクタで止まる。
|
|
108
|
+
6. **slot**: `oep config slot` の行を勧める(`config.Slot` に `reset_channel` が無い間、リセットの channel はコメント)。
|
|
109
|
+
`--save` で書く(set + save)。`--save` が無ければ何も書かない。
|
|
110
|
+
|
|
111
|
+
安全: driven / active の channel は駆動も scan も保持もしない。電源の channel は `--power` のときだけ触る。low に保つのは
|
|
112
|
+
オープンドレインだけ。gpio の plan を新しくすると、probe は前の plan のピンを(電源の channel も)いったん離すので、`--power`
|
|
113
|
+
では新しい plan のたびにきれいに電源を入れ直す(報告の `power_cycles`)。
|
|
114
|
+
|
|
115
|
+
ESP32-P4 と CH32V003(電源は GPIO5)、2026-10-02:
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
$ oep pins /run/board-identify/by-id/esp32-series-30eda0ea068b --power 5 --wire swio
|
|
119
|
+
power: channel 5 low 300 ms (target off: read), then high 400 ms before the reads
|
|
120
|
+
classify: 52 channels, 16 reads each under pull-up, both pulls, pull-down
|
|
121
|
+
floating 0-3,6,9-20,26-34,36-50,52-54
|
|
122
|
+
pulled-up 4
|
|
123
|
+
driven-high 7-8,22-23,35 (never scanned or held)
|
|
124
|
+
driven-low 51 (never scanned or held)
|
|
125
|
+
active 21 (21: 8 changes under pull-up)
|
|
126
|
+
follow power 4,6,9-11,13,15-16,19-23,32-33 (read otherwise with the target off: wired to it; candidates only)
|
|
127
|
+
reset line (hold low): 45 candidates, each held low (open drain) up to 390 ms while watching 21 (105 changes in 1.0 s running, longest lull 130 ms)
|
|
128
|
+
hold 4 low: 21 stopped
|
|
129
|
+
45 held in 3.4 s: stopped by 4
|
|
130
|
+
scan swio: 45 channels (0-4,6,9-20,26-34,36-50,52-54; not 7-8,21-23,35,51: driven / active) in 0.14 s -> 19
|
|
131
|
+
attach swio 19: target_id 00310510 (WCH DMI 0x7F) -> ch32v00x, halted at dpc 0x108
|
|
132
|
+
option bytes (read only): RST_MODE 10 (USER 0xf7): NRST on PD7, 12 ms ignore window
|
|
133
|
+
attach under reset through 4 (held 20 ms): dpc 0x0 -> the reset line
|
|
134
|
+
slot: oep config slot ... --name ch32v00x --wire swio --pins 19 (not written; --save writes it)
|
|
135
|
+
# reset_channel 4 (probe.config §1.1; this client's slot has no reset_channel yet: name it in the attach's reset TLV)
|
|
136
|
+
released every plan (channel 5 is back to its idle state: the target is powered only while something drives it)
|
|
137
|
+
done in 7.3 s
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
19 が SWIO、4 が NRST(hold で 21 が止まり、リセットをかけながらの attach で dpc 0)。22 / 23 は target の UART(idle high
|
|
141
|
+
なので driven として scan も保持もしない)、21 はアプリが動かす出力。
|
|
142
|
+
|
|
86
143
|
## UART bridge を速くする(port_speed、使うときだけ)
|
|
87
144
|
|
|
88
145
|
describe に `port_speed` を宣言する probe(oep-core §3.5。参照の classic ESP32 の firmware は宣言する)では、host は 1 つのセッションの
|
|
@@ -138,6 +195,10 @@ PARLIO と同じ w 1〜16、3 本なら w 4。`esp32-v003`: classic ESP32 の sa
|
|
|
138
195
|
frontend、作り物の 2 点の較正と Vrefint)と、ロジックとアナログを束ねる `oep.fixture.capture-group` もある(一緒に始まり、
|
|
139
196
|
アナログは 5 µs 遅れ(±2 µs)、片方のトリガが両方に印される)。時刻は probe の 1 本の時計の ns。
|
|
140
197
|
|
|
198
|
+
`oep.fixture.i2c-target` と `oep.fixture.spi-target`(`p4-x035`、`esp32-v003`)は plan で役割を受け(SDA / SCL、SCK / MOSI / MISO / CS。
|
|
199
|
+
`esp32-v003` の SPI は 2 つの channel_group のどれかに完全に一致)、fixture §3 / §4 の op にすべて答える。バスの controller は無い:
|
|
200
|
+
試験が endpoint の hook(`i2c_write` / `i2c_read` / `spi_transfer`: バス上の 1 回のトランザクション)を呼ぶまで、arm は待ったままで何も受けない。
|
|
201
|
+
|
|
141
202
|
外のプログラムの試験には `fake_serve` を子プロセスで使う:
|
|
142
203
|
|
|
143
204
|
```sh
|
|
@@ -37,7 +37,9 @@ oep-client-python X.Y.Z (until the v1 freeze every release may break the wire; t
|
|
|
37
37
|
| `host` | requests and results, the session id and the lock, `call()` (raises unless it worked), pipelining, the errors (`OepError` / `Rejected` / `Failed`; `Expired` when the lease lapsed - the session is never re-opened behind the caller's back, `Host.epoch` moves) |
|
|
38
38
|
| `link` | transports: serial ports (always COBS + CRC as `0x00 <COBS> 0x00`, bytes outside frames skipped as noise, opened exclusively), USB vendor bulk / HID and TCP (length frames, the §5.1 resync); matching by corr and resending; `open_host(target)` |
|
|
39
39
|
| `core` | interfaces by name (cached), confirm (with the probe's `boot_id`), the probe's describe (declarations only, cached per boot: labels, the transport list, `max_op_ms`), taking the lock (`take`), the pin plan, the `Interface` base |
|
|
40
|
-
| `riscv` | `oep.wire.rvswd` / `oep.wire.swio` (scan, attach - `max_speed` always sent, `reset=(channel, hold_ms)` for an attach under reset -, detach, connections), `oep.target.riscv-dm` (answers count their values; `RunResult.not_halted`), finding the reset line, attach through GPIO |
|
|
40
|
+
| `riscv` | `oep.wire.rvswd` / `oep.wire.swio` (scan, attach - `max_speed` always sent, `reset=(channel, hold_ms)` for an attach under reset -, detach, connections), `oep.target.riscv-dm` (answers count their values; `RunResult.not_halted`), finding the reset line (`find_reset_line(candidates, pins=...)`), attach through GPIO |
|
|
41
|
+
| `targets` | what the host knows per target family, in one table (`FAMILIES`: wire, target_id match, reset vector, option-byte NRST reader, max_speed / idle_clock); `identify(target_id)` |
|
|
42
|
+
| `pins` | `oep pins`: classify the channels, hold-low search, scan, identify, confirm the reset line, suggest a slot (`PinFinder`) |
|
|
41
43
|
| `console` | `oep.target.console` (position streams: read answers carry their length, marks are `time_ns`, the lock-free `streams()` list) and `ConsoleIO`, read as bytes |
|
|
42
44
|
| `fixture` | `oep.fixture.gpio` / `uart` (its stream is the plan's; `status()`) / `i2c-target` / `spi-target` (revision 1) |
|
|
43
45
|
| `config` | `oep.probe.config` (slots, binds, plan / label / idle / uart items, get / set / unset / save / erase; `describe()` = the declarations, `state()` = the live storage / slot / bind state; `hash_of(items)` = the probe's hash) |
|
|
@@ -81,6 +83,7 @@ oep config bind <probe> --port 1 --mode last-reset --stream slot:x035
|
|
|
81
83
|
oep config uart <probe> oep.fixture.uart 115200 --format 8N1 # applied whenever that UART's plan has RX or TX
|
|
82
84
|
oep config save <probe> # kept over a restart (also: remove = unset, erase)
|
|
83
85
|
oep speed <probe> 1500000,921600,500000 # port_speed: try faster rates on a UART bridge, print the report (below)
|
|
86
|
+
oep pins <probe> --power 5 --wire swio # where the target is wired: debug pins, reset line, a slot (below)
|
|
84
87
|
```
|
|
85
88
|
|
|
86
89
|
`<probe>` is a serial port, `tcp://HOST:PORT` or `usb[:VID:PID[:SERIAL]]`. A change takes the lock (owner "oep config") and
|
|
@@ -88,6 +91,60 @@ ends the session after it; it takes effect at once and, after `save`, stays over
|
|
|
88
91
|
|
|
89
92
|
A run on hardware: ArduinoCore-CH32's `tests/manual/oep_smoke/` (`oep_smoke.py`, `oep_probe_checks.py`).
|
|
90
93
|
|
|
94
|
+
## Finding the pins (`oep pins`)
|
|
95
|
+
|
|
96
|
+
`oep pins <probe> [--power CH] [--exclude CH,...] [--wire swio|rvswd|swd] [--steps ...] [--save] [--json]` finds where a
|
|
97
|
+
target is wired to a probe whose pins the host chooses. Each step says what it did; the whole run stays under a minute and
|
|
98
|
+
every plan is released at the end. The procedure and its reasons are in oep-spec's host development guide (「ピンの探し方
|
|
99
|
+
(参考)」); what each target family needs is in one table, `targets.FAMILIES` (wire, reset vector, the option-byte reader
|
|
100
|
+
for the reset line, max_speed / idle_clock), which oep-spec `docs/target-scan-notes.ja.md` records the sources of.
|
|
101
|
+
|
|
102
|
+
1. **classify**: every channel `oep.fixture.gpio` allows is read 16 times under pull-up, both pulls, then pull-down:
|
|
103
|
+
floating, pulled-up (both pulls still read 1: a weak pull-up such as a reset line's), driven-high / driven-low (the same
|
|
104
|
+
level under both pulls: push-pull or a strong pull - an idle-high UART line looks like this), active (changes between
|
|
105
|
+
reads). With `--power CH` the target is first read switched off: the channels that read otherwise follow its power.
|
|
106
|
+
These are candidates only.
|
|
107
|
+
2. **hold**: each floating / pulled-up channel is held low (open drain) while an active channel is watched; a channel
|
|
108
|
+
that stops it is a reset-line candidate.
|
|
109
|
+
3. **scan**: the wire over the floating / pulled-up channels (rvswd / swd: pairs, at most 600) - never over a driven or
|
|
110
|
+
active channel, the power channel or `--exclude`.
|
|
111
|
+
4. **identify**: attach (halt) on what answered; the target_id names the family; a CH32V00x's option bytes say whether
|
|
112
|
+
NRST exists (read only, never written); then resume.
|
|
113
|
+
5. **reset**: attach under reset through each candidate - the real line stops the hart at the reset vector.
|
|
114
|
+
6. **slot**: a suggested `oep config slot` line (the reset channel as a comment while `config.Slot` has no
|
|
115
|
+
`reset_channel`); `--save` writes it (set + save). Nothing is written without `--save`.
|
|
116
|
+
|
|
117
|
+
Safety: a driven or active channel is never driven, scanned or held; the power channel is touched only with `--power`;
|
|
118
|
+
holding low is open drain only. A new gpio plan lets every pin of the old one go first - the power channel too - so with
|
|
119
|
+
`--power` each new plan is followed by a clean power cycle (`power_cycles` in the report).
|
|
120
|
+
|
|
121
|
+
ESP32-P4 with a CH32V003 (power on GPIO5), 2026-10-02:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
$ oep pins /run/board-identify/by-id/esp32-series-30eda0ea068b --power 5 --wire swio
|
|
125
|
+
power: channel 5 low 300 ms (target off: read), then high 400 ms before the reads
|
|
126
|
+
classify: 52 channels, 16 reads each under pull-up, both pulls, pull-down
|
|
127
|
+
floating 0-3,6,9-20,26-34,36-50,52-54
|
|
128
|
+
pulled-up 4
|
|
129
|
+
driven-high 7-8,22-23,35 (never scanned or held)
|
|
130
|
+
driven-low 51 (never scanned or held)
|
|
131
|
+
active 21 (21: 8 changes under pull-up)
|
|
132
|
+
follow power 4,6,9-11,13,15-16,19-23,32-33 (read otherwise with the target off: wired to it; candidates only)
|
|
133
|
+
reset line (hold low): 45 candidates, each held low (open drain) up to 390 ms while watching 21 (105 changes in 1.0 s running, longest lull 130 ms)
|
|
134
|
+
hold 4 low: 21 stopped
|
|
135
|
+
45 held in 3.4 s: stopped by 4
|
|
136
|
+
scan swio: 45 channels (0-4,6,9-20,26-34,36-50,52-54; not 7-8,21-23,35,51: driven / active) in 0.14 s -> 19
|
|
137
|
+
attach swio 19: target_id 00310510 (WCH DMI 0x7F) -> ch32v00x, halted at dpc 0x108
|
|
138
|
+
option bytes (read only): RST_MODE 10 (USER 0xf7): NRST on PD7, 12 ms ignore window
|
|
139
|
+
attach under reset through 4 (held 20 ms): dpc 0x0 -> the reset line
|
|
140
|
+
slot: oep config slot ... --name ch32v00x --wire swio --pins 19 (not written; --save writes it)
|
|
141
|
+
# reset_channel 4 (probe.config §1.1; this client's slot has no reset_channel yet: name it in the attach's reset TLV)
|
|
142
|
+
released every plan (channel 5 is back to its idle state: the target is powered only while something drives it)
|
|
143
|
+
done in 7.3 s
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
22 / 23 are the target's UART (idle high: driven, so neither scanned nor held); 21 is an output its app toggles.
|
|
147
|
+
|
|
91
148
|
## A faster UART bridge (port_speed, opt-in)
|
|
92
149
|
|
|
93
150
|
A probe whose describe declares `port_speed` (oep-core §3.5; the classic ESP32 reference firmware does) lets the host
|
|
@@ -151,6 +208,11 @@ period 64 (k // 2 + 1) samples, 12-bit values in 16-bit slots; ESP32-style front
|
|
|
151
208
|
Vrefint) and `oep.fixture.capture-group` binding the logic and the analog: started together, the analog 5 us later
|
|
152
209
|
(+-2 us), the trigger of one marked on both. Times are ns on the probe's one clock.
|
|
153
210
|
|
|
211
|
+
`oep.fixture.i2c-target` and `oep.fixture.spi-target` (`p4-x035`, `esp32-v003`) take their roles from the plan (SDA / SCL;
|
|
212
|
+
SCK / MOSI / MISO / CS - on `esp32-v003` one of two fixed channel_groups exactly) and answer every op of fixture §3 / §4.
|
|
213
|
+
There is no bus controller: an arm stays armed and nothing is received until a test calls a hook on the endpoint
|
|
214
|
+
(`i2c_write` / `i2c_read` / `spi_transfer`: one transaction on the bus).
|
|
215
|
+
|
|
154
216
|
Other programs' tests run `fake_serve` as a child process:
|
|
155
217
|
|
|
156
218
|
```sh
|
|
@@ -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.28"
|
|
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"
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
oep speed <probe> [--candidates 921600,500000] [--verify [--flows in:2,out:2]] (port_speed, core §3.5 and the host
|
|
13
13
|
guide §7: try the candidates in order on a UART bridge, report)
|
|
14
14
|
oep linktest <probe> --rates now,921600 --patterns in,out,duplex --inflight 1,2 --sizes 128,496 --frames 300
|
|
15
|
+
oep pins <probe> --power 5 --wire swio (find the target's debug pins and reset line: classify the channels, scan,
|
|
16
|
+
identify, hold-low + attach under reset; prints a slot, --save writes it)
|
|
15
17
|
|
|
16
18
|
<probe>: a serial port, tcp://HOST:PORT or usb[:VID:PID[:SERIAL]]. A change takes the lock (owner "oep config") and
|
|
17
19
|
ends the session after it; it takes effect at once, and stays over a restart only after `save` (or --save).
|
|
@@ -63,6 +65,19 @@ def main(argv=None) -> int:
|
|
|
63
65
|
lt.add_argument("--baud", type=int, default=link.BASE_BAUD, help="the boot speed to open at (default 115200)")
|
|
64
66
|
lt.add_argument("--low-latency", choices=("on", "off"), default="on", help="the serial driver's low-latency mode")
|
|
65
67
|
lt.add_argument("--json", action="store_true", help="one JSON object per cell")
|
|
68
|
+
pn = sub.add_parser("pins", help="find where a target is wired: classify the channels, scan the wire, find the "
|
|
69
|
+
"reset line, suggest a slot (writes nothing without --save)")
|
|
70
|
+
pn.add_argument("probe", help="a probe: a serial port, tcp://HOST:PORT or usb[:VID:PID[:SERIAL]]")
|
|
71
|
+
pn.add_argument("--power", type=int, help="the gpio channel that powers the target: switched off, then on, to "
|
|
72
|
+
"see which channels follow it (never touched without this)")
|
|
73
|
+
pn.add_argument("--exclude", default="", help="channels never to read, scan or hold, comma-separated")
|
|
74
|
+
pn.add_argument("--wire", choices=("swio", "rvswd", "swd"), default="swio")
|
|
75
|
+
pn.add_argument("--steps", default="classify,hold,scan,identify,reset,slot",
|
|
76
|
+
help="the steps to run, comma-separated (default all: classify, hold, scan, identify, reset, slot)")
|
|
77
|
+
pn.add_argument("--save", action="store_true", help="write the suggested slot (config set, then save)")
|
|
78
|
+
pn.add_argument("--slot", type=int, default=0, help="--save: the slot number (default 0)")
|
|
79
|
+
pn.add_argument("--name", help="--save: the slot's name (default: the target family, else 'target')")
|
|
80
|
+
pn.add_argument("--json", action="store_true", help="the report as JSON (the steps' lines go to stderr)")
|
|
66
81
|
d = sub.add_parser("dump", help="list and describe every interface a probe offers")
|
|
67
82
|
src = d.add_mutually_exclusive_group(required=True)
|
|
68
83
|
src.add_argument("--fake", choices=sorted(fake.PROFILES), help="in-process example probe")
|
|
@@ -77,6 +92,8 @@ def main(argv=None) -> int:
|
|
|
77
92
|
return _speed(args)
|
|
78
93
|
if args.command == "linktest":
|
|
79
94
|
return _linktest(args)
|
|
95
|
+
if args.command == "pins":
|
|
96
|
+
return _pins_cmd(args)
|
|
80
97
|
|
|
81
98
|
if args.fake:
|
|
82
99
|
call = fake.PROFILES[args.fake]().call
|
|
@@ -88,6 +105,33 @@ def main(argv=None) -> int:
|
|
|
88
105
|
return 0
|
|
89
106
|
|
|
90
107
|
|
|
108
|
+
# ---- oep pins -----------------------------------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
def _pins_cmd(args) -> int:
|
|
111
|
+
from . import pins
|
|
112
|
+
exclude = [int(x, 0) for x in args.exclude.replace(" ", "").split(",") if x]
|
|
113
|
+
say = (lambda t: print(t, file=sys.stderr, flush=True)) if args.json else (lambda t: print(t, flush=True)) # noqa: E731
|
|
114
|
+
hst = link.open_host(args.probe)
|
|
115
|
+
try:
|
|
116
|
+
core.take(hst, 10000, owner="oep pins")
|
|
117
|
+
finder = pins.PinFinder(hst, wire=args.wire, power=args.power, exclude=exclude, say=say, probe=args.probe,
|
|
118
|
+
save=args.save, slot=args.slot, name=args.name)
|
|
119
|
+
report = finder.run(steps=tuple(s for s in args.steps.split(",") if s))
|
|
120
|
+
say(f"done in {report.elapsed_s:.1f} s")
|
|
121
|
+
if args.json:
|
|
122
|
+
print(json.dumps(report.as_dict(), indent=2))
|
|
123
|
+
try:
|
|
124
|
+
hst.end()
|
|
125
|
+
except host.OepError:
|
|
126
|
+
pass
|
|
127
|
+
except host.Rejected as e:
|
|
128
|
+
print(f"refused: {e}", file=sys.stderr)
|
|
129
|
+
return 2
|
|
130
|
+
finally:
|
|
131
|
+
hst.link.close()
|
|
132
|
+
return 0 if report.found else 1
|
|
133
|
+
|
|
134
|
+
|
|
91
135
|
# ---- oep linktest -------------------------------------------------------------------------------------------------
|
|
92
136
|
|
|
93
137
|
def _linktest(args) -> int:
|
|
@@ -73,14 +73,23 @@ class Label:
|
|
|
73
73
|
|
|
74
74
|
@dataclass(kw_only=True)
|
|
75
75
|
class Idle:
|
|
76
|
+
"""The state of a channel no plan or connection uses (probe.config §1): at boot and after every release. mode:
|
|
77
|
+
hi-z, pull-up, pull-down, output-low, output-high (output_low / output_high too). An output mode keeps driving that
|
|
78
|
+
level while the channel is free - a target's power switch kept on - and a gpio plan that takes the channel keeps
|
|
79
|
+
it until its first set (fixture §1); a probe that cannot drive the channel refuses it unsupported."""
|
|
76
80
|
channel: int
|
|
77
|
-
mode: str = "pull-up" # hi-z, pull-up, pull-down
|
|
81
|
+
mode: str = "pull-up" # hi-z, pull-up, pull-down, output-low, output-high
|
|
78
82
|
TAG = ITEM["idle"]
|
|
79
83
|
|
|
84
|
+
def __post_init__(self):
|
|
85
|
+
self.mode = self.mode.replace("_", "-")
|
|
86
|
+
|
|
80
87
|
def key(self) -> tuple:
|
|
81
88
|
return (self.channel,)
|
|
82
89
|
|
|
83
90
|
def value(self) -> bytes:
|
|
91
|
+
if self.mode not in IDLE:
|
|
92
|
+
raise ValueError(f"idle mode {self.mode!r}: one of {', '.join(IDLE)}")
|
|
84
93
|
return struct.pack("<HB", self.channel, IDLE[self.mode])
|
|
85
94
|
|
|
86
95
|
|
|
@@ -395,3 +404,50 @@ class ProbeConfig(Interface):
|
|
|
395
404
|
return st
|
|
396
405
|
first_slot += n_slots
|
|
397
406
|
first_bind += n_binds
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
# The label names of a target's power and reset lines (host-development-guide §8.1): `nrst` its reset, `power_hi` high
|
|
410
|
+
# powers it, `power_lo` low powers it. On a probe with several slots they are `<slot name>.<name>`.
|
|
411
|
+
LINE_NAMES = ("nrst", "power_hi", "power_lo")
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
class AmbiguousLine(LookupError):
|
|
415
|
+
"""find_line could not tell which channel is meant; `candidates` holds (label text, channel)."""
|
|
416
|
+
|
|
417
|
+
def __init__(self, message: str, candidates: list[tuple[str, int]]):
|
|
418
|
+
super().__init__(message)
|
|
419
|
+
self.candidates = candidates
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def find_line(hst_or_items, name: str, slot: int | str | None = None) -> int | None:
|
|
423
|
+
"""The channel labelled for `name` (nrst, power_hi, power_lo: host-development-guide §8.1), or None when there is
|
|
424
|
+
none. hst_or_items: a Host (its settings are read: `ProbeConfig.items()`, no lock) or the decoded items. slot: the
|
|
425
|
+
slot's name or number; None picks the probe's one slot.
|
|
426
|
+
|
|
427
|
+
`<slot>.<name>` first, then the bare `name` - the bare name only when the settings hold at most one slot (a bare
|
|
428
|
+
name is for a one-slot probe). With several slots and no `slot`, or two channels with the same label, it raises
|
|
429
|
+
AmbiguousLine listing the candidates."""
|
|
430
|
+
items = hst_or_items if isinstance(hst_or_items, (list, tuple)) else ProbeConfig(hst_or_items).items()
|
|
431
|
+
labels = [(it.text, it.channel) for it in items if isinstance(it, Label)]
|
|
432
|
+
slots = [it for it in items if isinstance(it, Slot)]
|
|
433
|
+
if isinstance(slot, int):
|
|
434
|
+
named = [s.name for s in slots if s.slot == slot]
|
|
435
|
+
if not named:
|
|
436
|
+
raise LookupError(f"no slot {slot} in the probe's settings")
|
|
437
|
+
slot = named[0]
|
|
438
|
+
if slot is None and len(slots) > 1:
|
|
439
|
+
candidates = sorted((t, c) for t, c in labels if t == name or t.endswith("." + name))
|
|
440
|
+
if candidates:
|
|
441
|
+
listed = ", ".join(f"{t} (channel {c})" for t, c in candidates)
|
|
442
|
+
raise AmbiguousLine(f"{name}: {len(slots)} slots and no slot given; candidates: {listed}", candidates)
|
|
443
|
+
return None
|
|
444
|
+
if slot is None and slots:
|
|
445
|
+
slot = slots[0].name
|
|
446
|
+
texts = ([f"{slot}.{name}"] if slot is not None else []) + ([name] if len(slots) <= 1 else [])
|
|
447
|
+
for text in texts:
|
|
448
|
+
found = sorted(c for t, c in labels if t == text)
|
|
449
|
+
if len(found) > 1:
|
|
450
|
+
raise AmbiguousLine(f"{text}: on {len(found)} channels", [(text, c) for c in found])
|
|
451
|
+
if found:
|
|
452
|
+
return found[0]
|
|
453
|
+
return None
|