oep-client-python 0.0.22__tar.gz → 0.0.24__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.22 → oep_client_python-0.0.24}/CHANGELOG.md +14 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/PKG-INFO +31 -2
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/README.ja.md +28 -1
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/README.md +30 -1
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/pyproject.toml +1 -1
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/__init__.py +1 -1
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/__main__.py +38 -1
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/config.py +21 -5
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/core.py +3 -1
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/endpoint.py +230 -11
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/fake.py +3 -2
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/fake_serial.py +81 -2
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/fake_serve.py +14 -0
- oep_client_python-0.0.24/src/oep_client/link.py +835 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/registry.py +4 -4
- oep_client_python-0.0.24/tests/test_config_disable.py +212 -0
- oep_client_python-0.0.24/tests/test_port_speed.py +290 -0
- oep_client_python-0.0.22/src/oep_client/link.py +0 -461
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/.gitignore +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/LICENSE +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/arm.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/capture.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/catalog.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/ch32_flash.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/cobs.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/console.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/decode.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/dump.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/fake_capture.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/fixture.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/frames.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/hid_stream.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/host.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/interfaces.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/message.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/names.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/riscv.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/rp2350.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/uiapduino.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/src/oep_client/usb_stream.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/tests/test_capabilities.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/tests/test_capture.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/tests/test_cobs.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/tests/test_config.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/tests/test_fake_capture.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/tests/test_fake_spec.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/tests/test_fixture_targets.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/tests/test_flash_console.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/tests/test_hid_stream.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/tests/test_interfaces.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/tests/test_link_host.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/tests/test_link_serial.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/tests/test_session.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/tests/test_target_parts.py +0 -0
- {oep_client_python-0.0.22 → oep_client_python-0.0.24}/tests/test_usb_stream.py +0 -0
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.0.24
|
|
6
|
+
- (EN) A serial link whose port a session holds sends a request again as soon as a broken frame arrives, instead of after the timeout: the probe sends no raw bytes on a held port (oep-core §3.4), so the broken frame was the reply, and the repeat is answered from the probe's retry table. A 60 KB logic capture read on an M5Stack ATOM went from 4.0 to 8.3 KB/s at 115200 and runs at 76 KB/s at 1.5 Mbaud (dogfooding port_speed, 2026-10-01).
|
|
7
|
+
- (JA) セッションが口を持っているシリアルの経路では、壊れたフレームが来たら時間切れを待たずにすぐ送り直す: 持たれている口には probe が生のバイトを送らない(oep-core §3.4)ので、壊れたフレームは応答で、送り直しには probe の送り直しの表が答える。M5Stack ATOM で 60 KB のロジックのキャプチャの読み出しが 115200 で 4.0 → 8.3 KB/s、1.5 Mbaud で 76 KB/s になった(port_speed のドッグフーディング、2026-10-01)。
|
|
8
|
+
- (EN) `raise_speed` (used on an M5Stack ATOM, 2026-10-01): waits 20 ms after a baud change and finds the new rate with a confirm before measuring; verifies pipelined first and, when frames break with both ways busy (the ATOM's FTDI at 500 kbaud and up), once more one request at a time, then keeps that limit on the link (`SpeedTrial.inflight`, `link.inflight_cap`); a rate whose confirm never comes back says so. The ATOM now commits 1.5 Mbaud at 76 / 58 KB/s (in flight 1) where the first version rejected every rate.
|
|
9
|
+
- (JA) `raise_speed`(M5Stack ATOM で使った、2026-10-01): 速さを変えたら 20 ms 待ち、測る前に confirm で新しい速さを確かめる。まずパイプラインで確かめ、両方向が同時に動くとフレームが壊れる線(ATOM の FTDI の 500 kbaud 以上)では 1 つずつでもう一度確かめ、その上限を link に残す(`SpeedTrial.inflight`、`link.inflight_cap`)。confirm が返らない速さはそう書く。最初の版はどの速さも断ったが、今は ATOM で 1.5 Mbaud が 76 / 58 KB/s(同時 1)で決まる。
|
|
10
|
+
- (EN) port_speed (oep-spec 3b9311c, core §3.5, fn 0 op 0x14; registry synced), opt-in. `link.raise_speed(hst, rates, *, verify_bytes=32768, verify_s=1.0, verify_ms=None, idle_ms=0, port=None)` tries the rates in order on the UART bridge this host opened: try -> the host switches its baud -> link_source / link_sink both ways with max_frame-sized frames (pipelined) for verify_bytes or verify_s, broken frames counted and KB/s measured each way -> commit when nothing broke, else revert (step 2) and back to the boot speed, re-confirmed (waiting out verify_ms). The first rate that passes is kept. The `SpeedReport` (`rate`, `chosen`, `in_kb_s` / `out_kb_s`, `trials` of `SpeedTrial`, `to_text()`) is kept as `link.speed`. `open_host(target, ..., baud=115200, port_speed=None | [rates], lease_ms=3000, owner=None)`: with port_speed it takes the lock and runs it (the session stays open). A completed `end` or revert puts the link back at the boot speed at once; a request unanswered at a raised rate (its resend too) goes back to the boot speed, confirms and goes once more (`link.speed_lost`). A probe without it (no describe port_speed, or unknown_operation) is reported not supported and stays at its speed; TCP / USB links are not this host's serial port. Serial ports open in pyserial's low-latency mode where the driver has it. `oep speed <probe> <rate,rate,...>` prints the report. Fake: the op and its state machine (try / commit / revert, verify_ms, idle_ms, a broken candidate while trying, 3 within 1 s when committed, the session's end, wrong port cause 6, unmakeable rates unsupported), on in `esp32-v003` (`Endpoint.port_speed_base`, None = off), the line modelled by `Endpoint.broken_rates` (`BrokenRate(min_size, to_host, to_probe)`), `fake_serial.FakeSerialStream` (an in-process pyserial-shaped port that garbles while the host's rate differs from the probe's), `fake_serve --no-port-speed` / `--broken-rate RATE[:MIN][:in|out]`.
|
|
11
|
+
- (JA) port_speed(oep-spec 3b9311c、core §3.5、fn 0 の op 0x14。registry を写し直した)、使うときだけ。`link.raise_speed(hst, rates, *, verify_bytes=32768, verify_s=1.0, verify_ms=None, idle_ms=0, port=None)` は、この host が開いた UART bridge で速さを順に試す: 試す → host が baud を切り替える → 両方向に max_frame の大きさで link_source / link_sink(パイプライン)を verify_bytes か verify_s の分流し、壊れたフレームを数え、向きごとの KB/s を測る → 何も壊れなければ決める、壊れれば戻す(step 2)して起動時の速さに戻り confirm し直す(verify_ms を待つ)。最初に通った速さを使う。`SpeedReport`(`rate`、`chosen`、`in_kb_s` / `out_kb_s`、`SpeedTrial` の `trials`、`to_text()`)は `link.speed` に残る。`open_host(target, ..., baud=115200, port_speed=None | [速さ], lease_ms=3000, owner=None)`: port_speed があればロックを取って行う(セッションは開いたまま)。完了した `end` と戻すでは link もすぐ起動時の速さに戻り、上げた速さで応答の来ない要求(送り直しも)は起動時の速さに戻って confirm し、もう一度送る(`link.speed_lost`)。機能の無い probe(describe に port_speed が無い、または unknown_operation)は not supported と報告し、速さは変えない。TCP / USB の link はこの host のシリアルの口ではない。シリアルの口はドライバにあれば pyserial の low-latency のモードで開く。`oep speed <probe> <速さ,速さ,...>` は結果を出す。偽の probe: op と状態の遷移(試す / 決める / 戻す、verify_ms、idle_ms、試しの間の壊れた候補、決めた後の 1 秒に 3 つ、セッションの終わり、違う口は cause 6、作れない速さは unsupported)。`esp32-v003` で ON(`Endpoint.port_speed_base`、None で OFF)。線は `Endpoint.broken_rates`(`BrokenRate(min_size, to_host, to_probe)`)で表す。`fake_serial.FakeSerialStream`(同じプロセスの pyserial の形の口。host の速さが probe と違う間は壊す)、`fake_serve --no-port-speed` / `--broken-rate RATE[:MIN][:in|out]`。
|
|
12
|
+
- (EN) `oep.probe.config` item `disable` (0x07, oep-spec 6088438; registry synced): `config.Disable(channel)`, removed with `remove("disable", ch)` / `unset([("disable", ch)])`, in the canonical hash; CLI `oep config disable <probe> <channel...> [--save]`. `core.PinsTaken` names a disabled channel. The fake probe follows it: any request naming a disabled channel (plan_apply, a settings plan, a slot's pins, an attach's pins / reset TLV, scan pairs, gpio set / read) is rejected unavailable cause 5 with the channel; scan's count-0 list and a pinless attach skip it; disabling a channel in use (a plan, a connection, a slot) is cause 1; idle and disable on one channel is malformed; the free pins' idle state (`Endpoint.parked`, set at boot after the saved items and whenever a pin is released) never touches a disabled channel; describe is unchanged. Following oep-spec 4842e95 (registry resynced): a cause 5 refusal for a disabled channel carries holder_kind 6 `disabled` (`Unavailable.holder_kind == "disabled"`), and disabling a channel the probe does not declare is rejected unsupported (as idle).
|
|
13
|
+
- (JA) `oep.probe.config` の項目 `disable`(0x07、oep-spec 6088438。registry を写し直した): `config.Disable(channel)`。`remove("disable", ch)` / `unset([("disable", ch)])` で消す。正規形の hash に入る。CLI `oep config disable <probe> <channel...> [--save]`。`core.PinsTaken` は無効にした channel を示す。fake の probe も追随した: 無効にした channel を指す要求(plan_apply、設定の plan、スロットのピン、attach の pins / reset TLV、scan の組、gpio の set / read)は channel 付きの rejected unavailable cause 5。scan の count 0 の並びとピンを指さない attach はそれを飛ばす。使われている channel(plan、接続、スロット)を無効にする set は cause 1。同じ channel の idle と disable は malformed。空きのピンの状態(`Endpoint.parked`。起動時は保存の項目の後に、ピンが解放されるたびに掛ける)は無効にした channel に触れない。describe は変わらない。oep-spec 4842e95 に追随(registry を写し直した): 無効にした channel の cause 5 の断りは holder_kind 6 `disabled` を持ち(`Unavailable.holder_kind == "disabled"`)、probe が宣言していない channel の disable は rejected unsupported(idle と同じ)。
|
|
14
|
+
|
|
15
|
+
## 0.0.23
|
|
16
|
+
- (EN) `link.open_host("tcp://…")` waits 15 s for a reply and does not send a request again by itself (`resend=False`): behind ch32rv's broker, the broker retries towards the probe, and a second copy from the client only raced it (V003 jig, 2026-10-01). `open_host(..., timeout=, resend=)` set both explicitly; serial and USB keep 3 s and one resend.
|
|
17
|
+
- (JA) `link.open_host("tcp://…")` は応答を 15 秒待ち、自分では送り直さない(`resend=False`)。ch32rv のブローカーの後ろでは、ブローカーが probe に送り直すので、client からの 2 通目は競合するだけだった(V003 ジグ、2026-10-01)。`open_host(..., timeout=, resend=)` で明示できる。シリアルと USB は 3 秒と 1 回の送り直しのまま。
|
|
18
|
+
|
|
5
19
|
## 0.0.22
|
|
6
20
|
- (EN) Breaking: the wire follows oep-spec's zero-base rewrite of 2026-10-01 (docs/v1-zero-base-proposal.ja.md §3 / §7; registry synced). Every answer that ended in data or a list now carries its length and may be followed by TLVs: read answers `start flags len data [TLV]` (console, fixture UART; capture `len` u32), gpio read `n n×level`, dmi `done status nvals values`, arm-adi transfer `done status ack nvals values`, run `status stopped dpc elapsed_us nvals values` (stopped 2 = the hart could not be halted: `RunResult.not_halted`), data frames `position len data [TLV]`, events `kind fixed-part [TLV]`. TLVs have a long form (`tag 0xFF len(u16) value` for 255 bytes and more; `message.tlv` / `split_tlvs`, the long form with a short value is `message.BadTlv`). confirm answers the probe's `boot_id`; subscribe's `max_delay_ms` is u32; `rejected unsupported` is always `tag [TLV]` (0x00 = a fixed-part value: `Unsupported.tag` None, `.tlvs`).
|
|
7
21
|
- (JA) 破壊的変更: wire を oep-spec の 2026-10-01 のゼロベース見直し(docs/v1-zero-base-proposal.ja.md §3 / §7。registry を写し直した)に揃えた。データや並びで終わっていた応答はすべて長さを持ち、後ろに TLV を置ける: read の応答 `start flags len data [TLV]`(console、fixture UART。capture の `len` は u32)、gpio read `n n×level`、dmi `done status nvals values`、arm-adi transfer `done status ack nvals values`、run `status stopped dpc elapsed_us nvals values`(stopped 2 = hart を止められなかった: `RunResult.not_halted`)、データフレーム `position len data [TLV]`、出来事 `kind 固定部 [TLV]`。TLV に長い形(255 byte 以上は `tag 0xFF len(u16) value`。`message.tlv` / `split_tlvs`、短い値の長い形は `message.BadTlv`)。confirm は probe の `boot_id` を返す。subscribe の `max_delay_ms` は u32。`rejected unsupported` は常に `tag [TLV]`(0x00 = 固定部の値: `Unsupported.tag` は None、`.tlvs`)。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: oep-client-python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.24
|
|
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
|
|
@@ -101,6 +101,7 @@ oep config slot <probe> --name x035 --wire rvswd --pins 2,54 --attach at-boot --
|
|
|
101
101
|
oep config bind <probe> --port 1 --mode last-reset --stream slot:x035
|
|
102
102
|
oep config uart <probe> oep.fixture.uart 115200 --format 8N1 # applied whenever that UART's plan has RX or TX
|
|
103
103
|
oep config save <probe> # kept over a restart (also: remove = unset, erase)
|
|
104
|
+
oep speed <probe> 1500000,921600,500000 # port_speed: try faster rates on a UART bridge, print the report (below)
|
|
104
105
|
```
|
|
105
106
|
|
|
106
107
|
`<probe>` is a serial port, `tcp://HOST:PORT` or `usb[:VID:PID[:SERIAL]]`. A change takes the lock (owner "oep config") and
|
|
@@ -108,6 +109,32 @@ ends the session after it; it takes effect at once and, after `save`, stays over
|
|
|
108
109
|
|
|
109
110
|
A run on hardware: ArduinoCore-CH32's `tests/manual/oep_smoke/` (`oep_smoke.py`, `oep_probe_checks.py`).
|
|
110
111
|
|
|
112
|
+
## A faster UART bridge (port_speed, opt-in)
|
|
113
|
+
|
|
114
|
+
A probe whose describe declares `port_speed` (oep-core §3.5; the classic ESP32 reference firmware does) lets the host
|
|
115
|
+
run its UART bridge faster than the boot speed (115200) for one session. Nothing changes unless the host asks:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
hst = link.open_host("/dev/ttyUSB0", port_speed=[1500000, 921600, 500000]) # takes the lock, keeps the session open
|
|
119
|
+
print(hst.link.speed.to_text()) # every rate tried: actual, in / out KB/s, broken frames, the one in force
|
|
120
|
+
# or, in a session already taken: report = link.raise_speed(hst, [1500000, 921600], verify_bytes=32768, verify_s=1.0)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Each rate in order: `try` (answered at the speed now, then the probe switches) -> the host switches -> a verify both ways
|
|
124
|
+
with max_frame-sized frames (link_source / link_sink, pipelined) for `verify_bytes` or `verify_s`, counting broken frames
|
|
125
|
+
and measuring KB/s each way -> `commit` when nothing broke, else revert and back to the boot speed, confirmed there (a
|
|
126
|
+
probe whose revert was lost waits out its `verify_ms`). The first rate that passes is kept; a rate the probe's UART cannot
|
|
127
|
+
make is skipped. Which rates pass depends on the bridge chip and its driver (an FTDI took only 3 MHz / n, a CH340 921600
|
|
128
|
+
but not 1500000 towards the host: oep-spec docs/uart-speed-negotiation.ja.md), so the host chooses them. The report
|
|
129
|
+
(`link.speed`: `rate`, `chosen`, `in_kb_s` / `out_kb_s`, `trials`) is there to budget a capture or a write.
|
|
130
|
+
|
|
131
|
+
The probe goes back to the boot speed by itself when the session ends (`end`, a lapse, a force), when frames break or the
|
|
132
|
+
line goes quiet (`idle_ms`): the link follows an `end` at once, and a request that goes unanswered at a raised rate (its
|
|
133
|
+
resend too) takes the link back to the boot speed, confirmed, and goes once more there - it never wedges. A probe
|
|
134
|
+
without the feature answers `not supported` and stays at its speed. Behind a broker (TCP) the broker does this, not the
|
|
135
|
+
client. Serial ports are also opened in the driver's low-latency mode where it has one (an FTDI's latency timer 16 -> 1 ms
|
|
136
|
+
tripled a UART bridge's throughput). `open_host(..., baud=)` names the boot speed when the board's profile is not 115200.
|
|
137
|
+
|
|
111
138
|
## The fake probe (a working spec)
|
|
112
139
|
|
|
113
140
|
`endpoint.Endpoint` is a fake probe that answers as oep-spec says (2026-10-01: every answer carries the length of its
|
|
@@ -140,5 +167,7 @@ The pty is a serial port (the host opens it with TIOCEXCL); `--tcp PORT` is `--f
|
|
|
140
167
|
`--framing length` (the vendor bulk / TCP form). Faults: `--drop N` (the N-th answer is not sent, once; the request did run,
|
|
141
168
|
so a resend gets the remembered result), `--noise TEXT` (noise before every answer), `--corrupt N` (the N-th answer's CRC
|
|
142
169
|
broken once). `--uart-plan` / `--uart-rx` give the first fixture UART a plan and RX bytes, `--run-hook` a host's own model of
|
|
143
|
-
riscv-dm run, `--capture-slipped` flags bit2 on every capture segment.
|
|
170
|
+
riscv-dm run, `--capture-slipped` flags bit2 on every capture segment. port_speed: `esp32-v003` has it
|
|
171
|
+
(`--no-port-speed` turns it off), and `--broken-rate RATE[:MIN_SIZE][:in|out]` makes a rate break frames (in process,
|
|
172
|
+
`fake_serial.FakeSerialStream` also garbles everything while the host's own rate differs from the probe's). Events and data pushes go out on the pty and on TCP
|
|
144
173
|
(both framings). The rest: `--help`.
|
|
@@ -75,12 +75,37 @@ oep config slot <probe> --name x035 --wire rvswd --pins 2,54 --attach at-boot --
|
|
|
75
75
|
oep config bind <probe> --port 1 --mode last-reset --stream slot:x035
|
|
76
76
|
oep config uart <probe> oep.fixture.uart 115200 --format 8N1 # その UART の plan に RX / TX が付くたびに掛かる
|
|
77
77
|
oep config save <probe> # 再起動の後も残す(remove = unset、erase もある)
|
|
78
|
+
oep speed <probe> 1500000,921600,500000 # port_speed: UART bridge の速い速さを試し、結果を出す(下)
|
|
78
79
|
```
|
|
79
80
|
|
|
80
81
|
`<probe>` はシリアルの口、`tcp://HOST:PORT`、`usb[:VID:PID[:SERIAL]]`。変更はロックを取り(owner "oep config")、終わったら
|
|
81
82
|
セッションを閉じる。変更はすぐ効き、`save` の後は再起動しても残る。
|
|
82
83
|
実機での一通りの確認は ArduinoCore-CH32 の `tests/manual/oep_smoke/`(`oep_smoke.py`、`oep_probe_checks.py`)。
|
|
83
84
|
|
|
85
|
+
## UART bridge を速くする(port_speed、使うときだけ)
|
|
86
|
+
|
|
87
|
+
describe に `port_speed` を宣言する probe(oep-core §3.5。参照の classic ESP32 の firmware は宣言する)では、host は 1 つのセッションの
|
|
88
|
+
間、UART bridge を起動時の速さ(115200)より速くできます。host が頼まない限り何も変わりません:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
hst = link.open_host("/dev/ttyUSB0", port_speed=[1500000, 921600, 500000]) # ロックを取り、セッションを開いたままにする
|
|
92
|
+
print(hst.link.speed.to_text()) # 試した速さごとに、実際の速さ、in / out の KB/s、壊れたフレーム、今の速さ
|
|
93
|
+
# 取ってあるセッションの中では: report = link.raise_speed(hst, [1500000, 921600], verify_bytes=32768, verify_s=1.0)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
速さごとに順に: `試す`(今の速さで応答してから probe が切り替える)→ host も切り替える → 両方向に max_frame の大きさのフレームで
|
|
97
|
+
(link_source / link_sink、パイプライン)`verify_bytes` か `verify_s` の分だけ確かめ、壊れたフレームを数え、それぞれの向きの KB/s を
|
|
98
|
+
測る → 何も壊れなければ `決める`、壊れれば戻して起動時の速さに戻り、そこで confirm し直す(戻すが届かなかった probe は `verify_ms`
|
|
99
|
+
を待って戻る)。最初に通った速さを使う。probe の UART が作れない速さは飛ばす。通る速さは変換チップとドライバで決まる(FTDI は
|
|
100
|
+
3 MHz ÷ n だけ、CH340 は 921600 は通り 1500000 は probe → host が壊れた: oep-spec docs/uart-speed-negotiation.ja.md)ので、速さは
|
|
101
|
+
host が選ぶ。結果(`link.speed`: `rate`、`chosen`、`in_kb_s` / `out_kb_s`、`trials`)はキャプチャや書き込みの予算を立てるのに使う。
|
|
102
|
+
|
|
103
|
+
probe はセッションが終わったとき(`end`、lease の期限切れ、force)、フレームが壊れたとき、線が黙ったとき(`idle_ms`)に自分で
|
|
104
|
+
起動時の速さに戻る: link は `end` にはすぐ合わせ、上げた速さで応答の来ない要求(送り直しも)があれば、起動時の速さに戻って
|
|
105
|
+
confirm し、そこでもう一度送る(固まらない)。機能の無い probe は `not supported` で、速さは変わらない。ブローカー(TCP)の後ろでは
|
|
106
|
+
client ではなくブローカーが行う。シリアルの口は、ドライバにあれば low-latency のモードで開く(FTDI の latency timer 16 → 1 ms で
|
|
107
|
+
UART bridge の速度が 3 倍になった)。ボードの起動時の速さが 115200 でなければ `open_host(..., baud=)` で渡す。
|
|
108
|
+
|
|
84
109
|
## 偽の probe(動く spec)
|
|
85
110
|
|
|
86
111
|
`endpoint.Endpoint` は oep-spec の規範どおりに答える偽の probe で(2026-10-01: 応答のデータと並びは長さを持ち、どの応答にも TLV が
|
|
@@ -109,6 +134,8 @@ uv run python -m oep_client.fake_serve --pty --profile p4-bench --slot x035 --bi
|
|
|
109
134
|
pty がシリアルの口(host が TIOCEXCL を掛けて開く)、`--tcp PORT` は `--framing cobs`(シリアルの口)か `--framing length`
|
|
110
135
|
(vendor bulk / TCP の形)。故障の注入は `--drop N`(N 番目の答えを 1 回出さない。要求は実行済みなので送り直しは覚えた答えを
|
|
111
136
|
受ける)、`--noise TEXT`(答えの前に雑音)、`--corrupt N`(N 番目の答えの CRC を 1 回壊す)。`--capture-slipped` は capture の
|
|
112
|
-
区画すべてに flags bit2
|
|
137
|
+
区画すべてに flags bit2 を立てる。port_speed: `esp32-v003` は持つ(`--no-port-speed` で外す)。
|
|
138
|
+
`--broken-rate RATE[:MIN_SIZE][:in|out]` はその速さでフレームを壊す(同じプロセスの `fake_serial.FakeSerialStream` は、host の速さが
|
|
139
|
+
probe の速さと違う間、両方向のバイトをすべて壊す)。出来事とデータの push は pty にも TCP(両方の framing)にも出る。ほかは `--help`。
|
|
113
140
|
|
|
114
141
|
v0 の client(`oep_client.v0`)は 2026-09-26 に消した(git の履歴に残る)。v0 を話す probe はもう無い。
|
|
@@ -79,6 +79,7 @@ oep config slot <probe> --name x035 --wire rvswd --pins 2,54 --attach at-boot --
|
|
|
79
79
|
oep config bind <probe> --port 1 --mode last-reset --stream slot:x035
|
|
80
80
|
oep config uart <probe> oep.fixture.uart 115200 --format 8N1 # applied whenever that UART's plan has RX or TX
|
|
81
81
|
oep config save <probe> # kept over a restart (also: remove = unset, erase)
|
|
82
|
+
oep speed <probe> 1500000,921600,500000 # port_speed: try faster rates on a UART bridge, print the report (below)
|
|
82
83
|
```
|
|
83
84
|
|
|
84
85
|
`<probe>` is a serial port, `tcp://HOST:PORT` or `usb[:VID:PID[:SERIAL]]`. A change takes the lock (owner "oep config") and
|
|
@@ -86,6 +87,32 @@ ends the session after it; it takes effect at once and, after `save`, stays over
|
|
|
86
87
|
|
|
87
88
|
A run on hardware: ArduinoCore-CH32's `tests/manual/oep_smoke/` (`oep_smoke.py`, `oep_probe_checks.py`).
|
|
88
89
|
|
|
90
|
+
## A faster UART bridge (port_speed, opt-in)
|
|
91
|
+
|
|
92
|
+
A probe whose describe declares `port_speed` (oep-core §3.5; the classic ESP32 reference firmware does) lets the host
|
|
93
|
+
run its UART bridge faster than the boot speed (115200) for one session. Nothing changes unless the host asks:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
hst = link.open_host("/dev/ttyUSB0", port_speed=[1500000, 921600, 500000]) # takes the lock, keeps the session open
|
|
97
|
+
print(hst.link.speed.to_text()) # every rate tried: actual, in / out KB/s, broken frames, the one in force
|
|
98
|
+
# or, in a session already taken: report = link.raise_speed(hst, [1500000, 921600], verify_bytes=32768, verify_s=1.0)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Each rate in order: `try` (answered at the speed now, then the probe switches) -> the host switches -> a verify both ways
|
|
102
|
+
with max_frame-sized frames (link_source / link_sink, pipelined) for `verify_bytes` or `verify_s`, counting broken frames
|
|
103
|
+
and measuring KB/s each way -> `commit` when nothing broke, else revert and back to the boot speed, confirmed there (a
|
|
104
|
+
probe whose revert was lost waits out its `verify_ms`). The first rate that passes is kept; a rate the probe's UART cannot
|
|
105
|
+
make is skipped. Which rates pass depends on the bridge chip and its driver (an FTDI took only 3 MHz / n, a CH340 921600
|
|
106
|
+
but not 1500000 towards the host: oep-spec docs/uart-speed-negotiation.ja.md), so the host chooses them. The report
|
|
107
|
+
(`link.speed`: `rate`, `chosen`, `in_kb_s` / `out_kb_s`, `trials`) is there to budget a capture or a write.
|
|
108
|
+
|
|
109
|
+
The probe goes back to the boot speed by itself when the session ends (`end`, a lapse, a force), when frames break or the
|
|
110
|
+
line goes quiet (`idle_ms`): the link follows an `end` at once, and a request that goes unanswered at a raised rate (its
|
|
111
|
+
resend too) takes the link back to the boot speed, confirmed, and goes once more there - it never wedges. A probe
|
|
112
|
+
without the feature answers `not supported` and stays at its speed. Behind a broker (TCP) the broker does this, not the
|
|
113
|
+
client. Serial ports are also opened in the driver's low-latency mode where it has one (an FTDI's latency timer 16 -> 1 ms
|
|
114
|
+
tripled a UART bridge's throughput). `open_host(..., baud=)` names the boot speed when the board's profile is not 115200.
|
|
115
|
+
|
|
89
116
|
## The fake probe (a working spec)
|
|
90
117
|
|
|
91
118
|
`endpoint.Endpoint` is a fake probe that answers as oep-spec says (2026-10-01: every answer carries the length of its
|
|
@@ -118,5 +145,7 @@ The pty is a serial port (the host opens it with TIOCEXCL); `--tcp PORT` is `--f
|
|
|
118
145
|
`--framing length` (the vendor bulk / TCP form). Faults: `--drop N` (the N-th answer is not sent, once; the request did run,
|
|
119
146
|
so a resend gets the remembered result), `--noise TEXT` (noise before every answer), `--corrupt N` (the N-th answer's CRC
|
|
120
147
|
broken once). `--uart-plan` / `--uart-rx` give the first fixture UART a plan and RX bytes, `--run-hook` a host's own model of
|
|
121
|
-
riscv-dm run, `--capture-slipped` flags bit2 on every capture segment.
|
|
148
|
+
riscv-dm run, `--capture-slipped` flags bit2 on every capture segment. port_speed: `esp32-v003` has it
|
|
149
|
+
(`--no-port-speed` turns it off), and `--broken-rate RATE[:MIN_SIZE][:in|out]` makes a rate break frames (in process,
|
|
150
|
+
`fake_serial.FakeSerialStream` also garbles everything while the host's own rate differs from the probe's). Events and data pushes go out on the pty and on TCP
|
|
122
151
|
(both framings). The rest: `--help`.
|
|
@@ -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.24"
|
|
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"
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
oep config bind <probe> --port 1 --mode last-reset --stream slot:x035
|
|
8
8
|
oep config plan <probe> oep.fixture.uart#1 rx=48 tx=49 (the fn's whole plan; fn number or name#instance)
|
|
9
9
|
oep config uart <probe> oep.fixture.uart#1 115200 --format 8N1
|
|
10
|
+
oep config disable <probe> 3 4 (channels the probe never uses or touches; remove disable CH re-enables)
|
|
10
11
|
oep config remove <probe> bind 1 oep config save <probe> oep config erase <probe>
|
|
12
|
+
oep speed <probe> 1500000,921600,500000 (port_speed, core §3.5: try the rates in order on a UART bridge, report)
|
|
11
13
|
|
|
12
14
|
<probe>: a serial port, tcp://HOST:PORT or usb[:VID:PID[:SERIAL]]. A change takes the lock (owner "oep config") and
|
|
13
15
|
ends the session after it; it takes effect at once, and stays over a restart only after `save` (or --save).
|
|
@@ -28,6 +30,13 @@ def main(argv=None) -> int:
|
|
|
28
30
|
parser = argparse.ArgumentParser(prog="oep", description="Open Embedded Probe: what a probe offers, its settings")
|
|
29
31
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
30
32
|
_config_parser(sub)
|
|
33
|
+
sp = sub.add_parser("speed", help="port_speed (core §3.5): try faster rates on a UART bridge, print the report")
|
|
34
|
+
sp.add_argument("probe", help="the probe's serial port (a UART bridge this host opens)")
|
|
35
|
+
sp.add_argument("rates", help="comma-separated, in order of preference (the first that passes is kept)")
|
|
36
|
+
sp.add_argument("--baud", type=int, default=link.BASE_BAUD, help="the boot speed (default 115200)")
|
|
37
|
+
sp.add_argument("--verify-bytes", type=int, default=32768)
|
|
38
|
+
sp.add_argument("--verify-s", type=float, default=1.0)
|
|
39
|
+
sp.add_argument("--json", action="store_true")
|
|
31
40
|
d = sub.add_parser("dump", help="list and describe every interface a probe offers")
|
|
32
41
|
src = d.add_mutually_exclusive_group(required=True)
|
|
33
42
|
src.add_argument("--fake", choices=sorted(fake.PROFILES), help="in-process example probe")
|
|
@@ -38,6 +47,8 @@ def main(argv=None) -> int:
|
|
|
38
47
|
args = parser.parse_args(argv)
|
|
39
48
|
if args.command == "config":
|
|
40
49
|
return _config(args)
|
|
50
|
+
if args.command == "speed":
|
|
51
|
+
return _speed(args)
|
|
41
52
|
|
|
42
53
|
if args.fake:
|
|
43
54
|
call = fake.PROFILES[args.fake]().call
|
|
@@ -49,6 +60,26 @@ def main(argv=None) -> int:
|
|
|
49
60
|
return 0
|
|
50
61
|
|
|
51
62
|
|
|
63
|
+
# ---- oep speed ----------------------------------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
def _speed(args) -> int:
|
|
66
|
+
"""Take the lock, raise_speed, print the report; the session's end puts the port back at its boot speed."""
|
|
67
|
+
import dataclasses
|
|
68
|
+
rates = [int(r) for r in args.rates.replace(" ", "").split(",") if r]
|
|
69
|
+
hst = link.open_host(args.probe, baud=args.baud)
|
|
70
|
+
try:
|
|
71
|
+
core.take(hst, 5000, owner="oep speed")
|
|
72
|
+
report = link.raise_speed(hst, rates, verify_bytes=args.verify_bytes, verify_s=args.verify_s)
|
|
73
|
+
if args.json:
|
|
74
|
+
sys.stdout.write(json.dumps(dataclasses.asdict(report), indent=2) + "\n")
|
|
75
|
+
else:
|
|
76
|
+
sys.stdout.write(report.to_text())
|
|
77
|
+
hst.end()
|
|
78
|
+
finally:
|
|
79
|
+
hst.link.close()
|
|
80
|
+
return 0 if report.supported else 2
|
|
81
|
+
|
|
82
|
+
|
|
52
83
|
# ---- oep config ----------------------------------------------------------------------------------------------------
|
|
53
84
|
|
|
54
85
|
def _config_parser(sub) -> None:
|
|
@@ -98,13 +129,17 @@ def _config_parser(sub) -> None:
|
|
|
98
129
|
idle.add_argument("channel", type=int)
|
|
99
130
|
idle.add_argument("mode", choices=sorted(config.IDLE))
|
|
100
131
|
idle.add_argument("--save", action="store_true")
|
|
132
|
+
dis = cs.add_parser("disable", help="channels the probe never uses or touches (not on this board)")
|
|
133
|
+
dis.add_argument("probe")
|
|
134
|
+
dis.add_argument("channels", type=int, nargs="+")
|
|
135
|
+
dis.add_argument("--save", action="store_true")
|
|
101
136
|
uart = cs.add_parser("uart", help="a fixture UART's baud / format, applied whenever its plan has RX or TX")
|
|
102
137
|
uart.add_argument("probe")
|
|
103
138
|
uart.add_argument("fn", help="an fn, or name#instance of the oep.fixture.uart")
|
|
104
139
|
uart.add_argument("baud", type=int)
|
|
105
140
|
uart.add_argument("--format", default="8N1", help="data bits, parity, stop bits: 8N1 (default), 8E1, 7O2 ...")
|
|
106
141
|
uart.add_argument("--save", action="store_true")
|
|
107
|
-
rm = cs.add_parser("remove", help="remove one item (unset): slot N, bind PORT, plan FN, label CH, idle CH, uart FN")
|
|
142
|
+
rm = cs.add_parser("remove", help="remove one item (unset): slot N, bind PORT, plan FN, label CH, idle CH, uart FN, disable CH")
|
|
108
143
|
rm.add_argument("probe")
|
|
109
144
|
rm.add_argument("kind", choices=sorted(config.ITEM))
|
|
110
145
|
rm.add_argument("key", type=int)
|
|
@@ -235,6 +270,8 @@ def _config(args) -> int:
|
|
|
235
270
|
_change(hst, cfg, [config.Label(channel=args.channel, text=args.text)], args.save)
|
|
236
271
|
elif args.action == "idle":
|
|
237
272
|
_change(hst, cfg, [config.Idle(channel=args.channel, mode=args.mode)], args.save)
|
|
273
|
+
elif args.action == "disable":
|
|
274
|
+
_change(hst, cfg, [config.Disable(channel=ch) for ch in args.channels], args.save)
|
|
238
275
|
elif args.action == "remove":
|
|
239
276
|
_change(hst, cfg, [config.remove(args.kind, args.key)], args.save)
|
|
240
277
|
else:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""oep.probe.config revision 1 (oep-spec docs/oep-if-probe-config.ja.md): the probe's settings - plan, labels, idle
|
|
2
|
-
pins, slots, binds, fixture UART settings - read and set as items, removed with unset, saved when the host says so, and
|
|
2
|
+
pins, slots, binds, fixture UART settings, disabled channels - read and set as items, removed with unset, saved when the host says so, and
|
|
3
3
|
the live slot / bind / storage state as its own lock-free operation (describe is declarations only, core §7.3).
|
|
4
4
|
|
|
5
5
|
cfg = config.ProbeConfig(hst)
|
|
@@ -84,6 +84,20 @@ class Idle:
|
|
|
84
84
|
return struct.pack("<HB", self.channel, IDLE[self.mode])
|
|
85
85
|
|
|
86
86
|
|
|
87
|
+
@dataclass(kw_only=True)
|
|
88
|
+
class Disable:
|
|
89
|
+
"""A channel the probe never uses or touches (probe.config §1, item 0x07): not on this board, or wired to another
|
|
90
|
+
part. Any request naming it is refused unavailable (cause 5, held by settings); describe still declares it."""
|
|
91
|
+
channel: int
|
|
92
|
+
TAG = ITEM["disable"]
|
|
93
|
+
|
|
94
|
+
def key(self) -> tuple:
|
|
95
|
+
return (self.channel,)
|
|
96
|
+
|
|
97
|
+
def value(self) -> bytes:
|
|
98
|
+
return struct.pack("<H", self.channel)
|
|
99
|
+
|
|
100
|
+
|
|
87
101
|
@dataclass(kw_only=True)
|
|
88
102
|
class Slot:
|
|
89
103
|
"""A place a target is wired to (probe.config §1.1). pins: (swdio, swclk), swclk 0xFFFF on one wire (swio).
|
|
@@ -160,7 +174,7 @@ class Removal:
|
|
|
160
174
|
key: int
|
|
161
175
|
|
|
162
176
|
def encoded(self) -> bytes:
|
|
163
|
-
"""len(u8) tag(u8) key: the key is fn(u16) for plan / uart, channel(u16) for label / idle, slot(u8), port(u8)."""
|
|
177
|
+
"""len(u8) tag(u8) key: the key is fn(u16) for plan / uart, channel(u16) for label / idle / disable, slot(u8), port(u8)."""
|
|
164
178
|
tag = ITEM[self.kind]
|
|
165
179
|
key = bytes([self.key]) if self.kind in ("slot", "bind") else struct.pack("<H", self.key)
|
|
166
180
|
return bytes([1 + len(key), tag]) + key
|
|
@@ -172,7 +186,7 @@ def item(it) -> bytes:
|
|
|
172
186
|
|
|
173
187
|
def remove(kind: str, key: int) -> Removal:
|
|
174
188
|
"""The removal of the item of this key (for `unset`, or in a `set` list): kind plan (key fn: its whole plan), label
|
|
175
|
-
/ idle (channel), slot, bind (port), uart (fn)."""
|
|
189
|
+
/ idle / disable (channel), slot, bind (port), uart (fn)."""
|
|
176
190
|
if kind not in ITEM:
|
|
177
191
|
raise ValueError(f"no item kind {kind!r}")
|
|
178
192
|
return Removal(kind, key)
|
|
@@ -187,6 +201,8 @@ def decode(tag: int, v: bytes):
|
|
|
187
201
|
return Label(channel=struct.unpack_from("<H", v)[0], text=v[2:].decode("utf-8", "replace"))
|
|
188
202
|
if tag == ITEM["idle"] and len(v) >= 3:
|
|
189
203
|
return Idle(channel=struct.unpack_from("<H", v)[0], mode=_name(IDLE, v[2]))
|
|
204
|
+
if tag == ITEM["disable"] and len(v) >= 2:
|
|
205
|
+
return Disable(channel=struct.unpack_from("<H", v)[0])
|
|
190
206
|
if tag == ITEM["slot"] and len(v) >= SLOT_HEAD.size + 1:
|
|
191
207
|
n, wire_fn, swdio, swclk, attach, retry_ms, max_speed, idle, mech, name_len = SLOT_HEAD.unpack_from(v)
|
|
192
208
|
at = SLOT_HEAD.size
|
|
@@ -219,7 +235,7 @@ def decode(tag: int, v: bytes):
|
|
|
219
235
|
|
|
220
236
|
def _sort_key(tag: int, value: bytes) -> tuple:
|
|
221
237
|
"""The canonical order's key of one item (probe.config §2): tag, then the key - plan (fn, role, channel), label /
|
|
222
|
-
idle channel, slot, port, uart fn."""
|
|
238
|
+
idle / disable channel, slot, port, uart fn."""
|
|
223
239
|
if tag == ITEM["plan"] and len(value) >= 5:
|
|
224
240
|
return (tag,) + struct.unpack_from("<HBH", value)
|
|
225
241
|
if tag in (ITEM["slot"], ITEM["bind"]):
|
|
@@ -299,7 +315,7 @@ class ProbeConfig(Interface):
|
|
|
299
315
|
return h, out
|
|
300
316
|
|
|
301
317
|
def items(self) -> list:
|
|
302
|
-
"""The current settings, decoded (Plan, Label, Idle, Slot, Bind, Uart)."""
|
|
318
|
+
"""The current settings, decoded (Plan, Label, Idle, Slot, Bind, Uart, Disable)."""
|
|
303
319
|
return [decode(t, v) for t, v in self.get()[1]]
|
|
304
320
|
|
|
305
321
|
def set(self, items: list) -> int:
|
|
@@ -140,7 +140,7 @@ def plan_apply(hst: h.Host, assignments: list[tuple[int, int, int]]) -> None:
|
|
|
140
140
|
|
|
141
141
|
class PinsTaken(h.Rejected):
|
|
142
142
|
"""plan_apply refused (unavailable) and the probe's settings say why: a pin kept by another fn's saved plan or by a
|
|
143
|
-
slot (oep.probe.config). `holders`: (channel, what holds it)."""
|
|
143
|
+
slot, or a channel the settings disable (oep.probe.config). `holders`: (channel, what holds it)."""
|
|
144
144
|
|
|
145
145
|
def __init__(self, result, holders: list[tuple[int, str]]):
|
|
146
146
|
super().__init__(result)
|
|
@@ -167,6 +167,8 @@ def _pin_holders(hst: h.Host, assignments: list[tuple[int, int, int]]) -> list[t
|
|
|
167
167
|
out.append((ch, f"the saved plan of fn {it.fn} (role {it.role})"))
|
|
168
168
|
elif isinstance(it, config.Slot) and ch in it.pins:
|
|
169
169
|
out.append((ch, f"slot {it.slot} {it.name}"))
|
|
170
|
+
elif isinstance(it, config.Disable) and it.channel == ch:
|
|
171
|
+
out.append((ch, "the settings (disabled; oep config remove <probe> disable <channel> enables it)"))
|
|
170
172
|
return out
|
|
171
173
|
|
|
172
174
|
|