oep-client-python 0.0.1__tar.gz → 0.0.2__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.
Files changed (50) hide show
  1. {oep_client_python-0.0.1 → oep_client_python-0.0.2}/CHANGELOG.md +6 -0
  2. {oep_client_python-0.0.1 → oep_client_python-0.0.2}/PKG-INFO +7 -7
  3. {oep_client_python-0.0.1 → oep_client_python-0.0.2}/README.ja.md +6 -6
  4. {oep_client_python-0.0.1 → oep_client_python-0.0.2}/pyproject.toml +2 -2
  5. oep_client_python-0.0.2/src/oep_client/__init__.py +5 -0
  6. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/__main__.py +4 -4
  7. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/fake_serve.py +54 -12
  8. oep_client_python-0.0.1/tests/test_v1_capabilities.py → oep_client_python-0.0.2/tests/test_capabilities.py +1 -1
  9. oep_client_python-0.0.1/tests/test_v1_capture.py → oep_client_python-0.0.2/tests/test_capture.py +3 -3
  10. oep_client_python-0.0.1/tests/test_v1_cobs.py → oep_client_python-0.0.2/tests/test_cobs.py +1 -1
  11. oep_client_python-0.0.1/tests/test_v1_esp32_targets.py → oep_client_python-0.0.2/tests/test_esp32_targets.py +2 -2
  12. oep_client_python-0.0.1/tests/test_v1_fake_spec.py → oep_client_python-0.0.2/tests/test_fake_spec.py +30 -4
  13. oep_client_python-0.0.1/tests/test_v1_flash_console.py → oep_client_python-0.0.2/tests/test_flash_console.py +2 -2
  14. oep_client_python-0.0.1/tests/test_v1_hid_stream.py → oep_client_python-0.0.2/tests/test_hid_stream.py +1 -1
  15. oep_client_python-0.0.1/tests/test_v1_interfaces.py → oep_client_python-0.0.2/tests/test_interfaces.py +3 -3
  16. oep_client_python-0.0.1/tests/test_v1_link_host.py → oep_client_python-0.0.2/tests/test_link_host.py +1 -1
  17. oep_client_python-0.0.1/tests/test_v1_link_serial.py → oep_client_python-0.0.2/tests/test_link_serial.py +2 -2
  18. oep_client_python-0.0.1/tests/test_v1_session.py → oep_client_python-0.0.2/tests/test_session.py +3 -3
  19. oep_client_python-0.0.1/tests/test_v1_target_parts.py → oep_client_python-0.0.2/tests/test_target_parts.py +2 -2
  20. oep_client_python-0.0.1/src/oep_client/__init__.py +0 -5
  21. oep_client_python-0.0.1/src/oep_client/v1/__init__.py +0 -1
  22. {oep_client_python-0.0.1 → oep_client_python-0.0.2}/.gitignore +0 -0
  23. {oep_client_python-0.0.1 → oep_client_python-0.0.2}/LICENSE +0 -0
  24. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/arm.py +0 -0
  25. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/capture.py +0 -0
  26. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/catalog.py +0 -0
  27. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/ch32_flash.py +0 -0
  28. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/cobs.py +0 -0
  29. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/console.py +0 -0
  30. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/core.py +0 -0
  31. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/decode.py +0 -0
  32. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/dump.py +0 -0
  33. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/endpoint.py +0 -0
  34. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/esp32_targets.py +0 -0
  35. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/fake.py +0 -0
  36. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/fake_serial.py +0 -0
  37. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/fixture.py +0 -0
  38. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/frames.py +0 -0
  39. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/hid_stream.py +0 -0
  40. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/host.py +0 -0
  41. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/interfaces.py +0 -0
  42. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/link.py +0 -0
  43. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/message.py +0 -0
  44. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/names.py +0 -0
  45. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/registry.py +0 -0
  46. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/riscv.py +0 -0
  47. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/rp2350.py +0 -0
  48. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/target.py +0 -0
  49. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/uiapduino.py +0 -0
  50. {oep_client_python-0.0.1/src/oep_client/v1 → oep_client_python-0.0.2/src/oep_client}/usb_stream.py +0 -0
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.0.2
6
+ - (EN) Breaking: the modules move from `oep_client.v1.*` to `oep_client.*` (`from oep_client import link`, `python -m oep_client`, `python -m oep_client.fake_serve`). The protocol revision stays in the registry and confirm.
7
+ - (JA) 破壊的変更: モジュールを `oep_client.v1.*` から `oep_client.*` に移した(`from oep_client import link`、`python -m oep_client`、`python -m oep_client.fake_serve`)。プロトコルの revision は registry と confirm が持つ。
8
+ - (EN) `fake_serve --uart-plan` puts the first fixture UART's RX / TX plan in at boot (as a saved config), and `--uart-rx TEXT` feeds its RX every `--every` ms once configured.
9
+ - (JA) `fake_serve --uart-plan` で最初の fixture UART の RX / TX の plan を起動時に入れる(保存した設定として)。`--uart-rx TEXT` で、configure の後、その RX に `--every` ms ごとに文字を入れる。
10
+
5
11
  ## 0.0.1
6
12
  - (EN) First beta on PyPI (`pip install oep-client-python`, imported as `oep_client`). OEP v1 host: serial ports always COBS with the probe's raw bytes skipped as noise and opened exclusively (TIOCEXCL), USB vendor bulk / HID and TCP (a broker) with length frames, all under one `Host` (`link.open_host(target)`); the session rules (owner, resend with the same corr, `core.take` for the lock); the standard interfaces; `Host.on_capture` for run recorders; a fake probe (`oep_client.v1.endpoint`) and `python -m oep_client.v1.fake_serve` (pty / TCP) as the working spec for other hosts' tests.
7
13
  - (JA) PyPI での最初のβ版(`pip install oep-client-python`、import は `oep_client`)。OEP v1 の host: シリアルの口は常に COBS(probe の生のバイトは雑音として捨てる、排他で開く TIOCEXCL)、USB の vendor bulk / HID と TCP(ブローカー)は長さつきのフレームで、どれも同じ `Host`(`link.open_host(target)`)。セッションの規則(owner、同じ corr での送り直し、ロックの取り方 `core.take`)、標準インターフェース、記録の受け口 `Host.on_capture`。ほかの host の試験のための「動く spec」として、偽の probe(`oep_client.v1.endpoint`)と `python -m oep_client.v1.fake_serve`(pty / TCP)。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: oep-client-python
3
- Version: 0.0.1
3
+ Version: 0.0.2
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
@@ -23,7 +23,7 @@ Description-Content-Type: text/markdown
23
23
  # OEP Python client
24
24
 
25
25
  Open Embedded Probe の host 側。v1(oep-spec の `docs/oep-core.ja.md` と `docs/oep-if-*.ja.md`、固める候補の形)を話す。番号は oep-spec の
26
- `generated/oep-v1/oep_v1_registry.py` をそのまま写した `oep_client.v1.registry` から取る。破壊的変更を前提とする
26
+ `generated/oep-v1/oep_v1_registry.py` をそのまま写した `oep_client.registry` から取る。破壊的変更を前提とする
27
27
  実験段階で、互換 API は約束しない。OEP を初めて読む人は oep-spec の `docs/review-guide.ja.md`(どこに何が書いてあるか)から。
28
28
 
29
29
  target の知識は host にある、という OEP の分担に従う。probe は線と DMI / DP・AP の転送しか知らず、CH32 の flash
@@ -34,14 +34,14 @@ pip install oep-client-python # PyPI (import oep_client); a checkout: pip in
34
34
  uv run pytest # in a checkout
35
35
  ```
36
36
 
37
- `import oep_client.v1` だけで使える(`sys.path` に `src/` を足す使い方は不要になった)。番号の表は `tools/sync_registry.sh` で
37
+ `import oep_client` だけで使える(`sys.path` に `src/` を足す使い方は不要になった)。番号の表は `tools/sync_registry.sh` で
38
38
  oep-spec から写す。PyPI の `oep-client` は別のプロジェクトなので、配布名は `oep-client-python`。
39
39
 
40
40
  リリースは GitHub Actions の Release(workflow_dispatch、version = X.Y.Z か X.Y.ZbN): `tools/prepare_release.py` が
41
41
  pyproject.toml と `oep_client.__version__` を書き換え、CHANGELOG.md の Unreleased をその版にし、試験と build の後に commit と tag、
42
42
  GitHub Release、PyPI(Trusted Publishing)へ出す。変更は CHANGELOG.md の Unreleased に (EN) / (JA) で書き足しておく。
43
43
 
44
- ## モジュール(`oep_client.v1`)
44
+ ## モジュール(`oep_client`)
45
45
 
46
46
  | モジュール | 中身 |
47
47
  |---|---|
@@ -66,7 +66,7 @@ GitHub Release、PyPI(Trusted Publishing)へ出す。変更は CHANGELOG.md
66
66
  ## 使い方の例
67
67
 
68
68
  ```python
69
- from oep_client.v1 import core, link, riscv, ch32_flash
69
+ from oep_client import core, link, riscv, ch32_flash
70
70
 
71
71
  hst = link.open_host("/run/board-identify/by-id/esp32-series-30eda0e31108") # pipelining つき
72
72
  # a serial port (always COBS), "tcp://127.0.0.1:PORT" (a broker), "usb" / "usb:303a:0002[:SERIAL]" (vendor, then HID)
@@ -81,7 +81,7 @@ wire.detach(conn)
81
81
  hst.end()
82
82
  ```
83
83
 
84
- 能力の一覧は `uv run python -m oep_client.v1 dump --port <probe>`(`--fake p4-x035` でハードウェアなし)。
84
+ 能力の一覧は `uv run python -m oep_client dump --port <probe>`(`--fake p4-x035` でハードウェアなし)。
85
85
  実機での一通りの確認は ArduinoCore-CH32 の `tests/manual/oep_smoke/`(`oep_smoke.py`、`oep_probe_checks.py`)。
86
86
 
87
87
  ## 偽の probe(動く spec)
@@ -94,7 +94,7 @@ hst.end()
94
94
  外のプログラムの試験には `fake_serve` を子プロセスで使う:
95
95
 
96
96
  ```sh
97
- uv run python -m oep_client.v1.fake_serve --pty --profile p4-bench --slot x035 --bind last-reset \
97
+ uv run python -m oep_client.fake_serve --pty --profile p4-bench --slot x035 --bind last-reset \
98
98
  --console 'uptime %d\r\n' --every 100
99
99
  # 最初の行: PTY /dev/pts/N(--tcp 0 なら PORT n)。stdin を閉じると終わる
100
100
  ```
@@ -1,7 +1,7 @@
1
1
  # OEP Python client
2
2
 
3
3
  Open Embedded Probe の host 側。v1(oep-spec の `docs/oep-core.ja.md` と `docs/oep-if-*.ja.md`、固める候補の形)を話す。番号は oep-spec の
4
- `generated/oep-v1/oep_v1_registry.py` をそのまま写した `oep_client.v1.registry` から取る。破壊的変更を前提とする
4
+ `generated/oep-v1/oep_v1_registry.py` をそのまま写した `oep_client.registry` から取る。破壊的変更を前提とする
5
5
  実験段階で、互換 API は約束しない。OEP を初めて読む人は oep-spec の `docs/review-guide.ja.md`(どこに何が書いてあるか)から。
6
6
 
7
7
  target の知識は host にある、という OEP の分担に従う。probe は線と DMI / DP・AP の転送しか知らず、CH32 の flash
@@ -12,14 +12,14 @@ pip install oep-client-python # PyPI (import oep_client); a checkout: pip in
12
12
  uv run pytest # in a checkout
13
13
  ```
14
14
 
15
- `import oep_client.v1` だけで使える(`sys.path` に `src/` を足す使い方は不要になった)。番号の表は `tools/sync_registry.sh` で
15
+ `import oep_client` だけで使える(`sys.path` に `src/` を足す使い方は不要になった)。番号の表は `tools/sync_registry.sh` で
16
16
  oep-spec から写す。PyPI の `oep-client` は別のプロジェクトなので、配布名は `oep-client-python`。
17
17
 
18
18
  リリースは GitHub Actions の Release(workflow_dispatch、version = X.Y.Z か X.Y.ZbN): `tools/prepare_release.py` が
19
19
  pyproject.toml と `oep_client.__version__` を書き換え、CHANGELOG.md の Unreleased をその版にし、試験と build の後に commit と tag、
20
20
  GitHub Release、PyPI(Trusted Publishing)へ出す。変更は CHANGELOG.md の Unreleased に (EN) / (JA) で書き足しておく。
21
21
 
22
- ## モジュール(`oep_client.v1`)
22
+ ## モジュール(`oep_client`)
23
23
 
24
24
  | モジュール | 中身 |
25
25
  |---|---|
@@ -44,7 +44,7 @@ GitHub Release、PyPI(Trusted Publishing)へ出す。変更は CHANGELOG.md
44
44
  ## 使い方の例
45
45
 
46
46
  ```python
47
- from oep_client.v1 import core, link, riscv, ch32_flash
47
+ from oep_client import core, link, riscv, ch32_flash
48
48
 
49
49
  hst = link.open_host("/run/board-identify/by-id/esp32-series-30eda0e31108") # pipelining つき
50
50
  # a serial port (always COBS), "tcp://127.0.0.1:PORT" (a broker), "usb" / "usb:303a:0002[:SERIAL]" (vendor, then HID)
@@ -59,7 +59,7 @@ wire.detach(conn)
59
59
  hst.end()
60
60
  ```
61
61
 
62
- 能力の一覧は `uv run python -m oep_client.v1 dump --port <probe>`(`--fake p4-x035` でハードウェアなし)。
62
+ 能力の一覧は `uv run python -m oep_client dump --port <probe>`(`--fake p4-x035` でハードウェアなし)。
63
63
  実機での一通りの確認は ArduinoCore-CH32 の `tests/manual/oep_smoke/`(`oep_smoke.py`、`oep_probe_checks.py`)。
64
64
 
65
65
  ## 偽の probe(動く spec)
@@ -72,7 +72,7 @@ hst.end()
72
72
  外のプログラムの試験には `fake_serve` を子プロセスで使う:
73
73
 
74
74
  ```sh
75
- uv run python -m oep_client.v1.fake_serve --pty --profile p4-bench --slot x035 --bind last-reset \
75
+ uv run python -m oep_client.fake_serve --pty --profile p4-bench --slot x035 --bind last-reset \
76
76
  --console 'uptime %d\r\n' --every 100
77
77
  # 最初の行: PTY /dev/pts/N(--tcp 0 なら PORT n)。stdin を閉じると終わる
78
78
  ```
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "oep-client-python"
7
- version = "0.0.1"
7
+ version = "0.0.2"
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.ja.md"
10
10
  license = "MIT"
@@ -25,7 +25,7 @@ usb-async = ["libusb1>=3"] # queued asynchronous IN transfers: streaming near
25
25
  hid = ["hidapi>=0.14"] # the vendor HID way in through the OS HID driver (Windows without WinUSB, Linux hidraw)
26
26
 
27
27
  [project.scripts]
28
- oep = "oep_client.v1.__main__:main"
28
+ oep = "oep_client.__main__:main"
29
29
 
30
30
  [project.urls]
31
31
  Homepage = "https://github.com/Open-Embedded-Probe/oep-client-python"
@@ -0,0 +1,5 @@
1
+ """Open Embedded Probe (OEP) host client. `oep_client` speaks the v1 protocol (oep-spec docs/oep-core.ja.md)."""
2
+
3
+ __all__ = ["__version__"]
4
+
5
+ __version__ = "0.0.2"
@@ -1,9 +1,9 @@
1
1
  """Draft OEP capability discovery by name, on an in-process fake or a v1 draft probe.
2
2
 
3
- uv run python -m oep_client.v1 dump --port /run/board-identify/by-id/<probe>
4
- uv run python -m oep_client.v1 dump --fake p4-x035
5
- uv run python -m oep_client.v1 dump --fake esp32-v003 --prefix oep.fixture
6
- uv run python -m oep_client.v1 dump --fake p4-x035 --prefix oep.target --json
3
+ uv run python -m oep_client dump --port /run/board-identify/by-id/<probe>
4
+ uv run python -m oep_client dump --fake p4-x035
5
+ uv run python -m oep_client dump --fake esp32-v003 --prefix oep.fixture
6
+ uv run python -m oep_client dump --fake p4-x035 --prefix oep.target --json
7
7
  """
8
8
 
9
9
  from __future__ import annotations
@@ -1,6 +1,6 @@
1
1
  """Serve a fake probe (`endpoint.Endpoint`) on a pty or a TCP port, for other programs' tests.
2
2
 
3
- python -m oep_client.v1.fake_serve [--pty | --tcp PORT] [options]
3
+ python -m oep_client.fake_serve [--pty | --tcp PORT] [options]
4
4
 
5
5
  --pty (the default) opens a pseudo terminal that is the probe's serial port (oep-core §3.4): COBS frames
6
6
  0x00 <COBS> 0x00 and the raw bytes of the port's bind on one line. The host opens the printed path itself (and
@@ -25,6 +25,9 @@ Options:
25
25
  --bind MODE bind the serial port to every --slot: last-reset, manual or mixed
26
26
  --target-id HEX the target_id every target's attach reports (wch_dmi_7f)
27
27
  --absent N the N-th pin pair of the first wire has no target (repeatable)
28
+ --uart-plan the first oep.fixture.uart gets its RX / TX plan at boot, as if saved (the jig's "DUT TX" /
29
+ "DUT RX" labels when the profile has them, else the first free channels); configure then works
30
+ --uart-rx TEXT what arrives on that UART's RX every --every ms once it is configured (%d = a counter)
28
31
  --run-hook SPEC what riscv-dm run does on every target: SPEC is module:function or path/file.py:function,
29
32
  called as function(target, pc, regs) -> (stopped, dpc, elapsed_us). `target` is the
30
33
  endpoint.FakeTarget (mem = word address -> value, regs = regno -> value, halted, dpc), so a
@@ -45,7 +48,7 @@ import sys
45
48
  import time
46
49
  import tty
47
50
 
48
- from . import endpoint, fake, fake_serial, registry as reg
51
+ from . import catalog, endpoint, fake, fake_serial, registry as reg
49
52
 
50
53
  _ITEM = reg.PROBE_CONFIG.tlv["item"]
51
54
  _MODES = reg.PROBE_CONFIG.enum["bind_mode"]
@@ -82,11 +85,40 @@ def build(a: argparse.Namespace) -> endpoint.Endpoint:
82
85
  for n in range(len(a.slot)))
83
86
  value = struct.pack("<BBBB", a.port_index, _MODES[a.bind.replace("-", "_")], 0, len(a.slot)) + streams
84
87
  items.append(bytes([_ITEM["bind"], len(value)]) + value)
88
+ if a.uart_plan:
89
+ fn = uart_fn(ep)
90
+ rx, tx = _uart_channels(ep, fn)
91
+ for role, ch in ((1, rx), (2, tx)):
92
+ items.append(bytes([_ITEM["plan"], 5]) + struct.pack("<HBH", fn, role, ch))
85
93
  if items:
86
94
  ep.load_config(items, saved=True)
87
95
  return ep
88
96
 
89
97
 
98
+ def uart_fn(ep: endpoint.Endpoint) -> int:
99
+ fn = ep.fns.get("oep.fixture.uart")
100
+ if fn is None:
101
+ raise SystemExit("this profile offers no oep.fixture.uart")
102
+ return fn
103
+
104
+
105
+ def _uart_channels(ep: endpoint.Endpoint, fn: int) -> tuple[int, int]:
106
+ """RX, TX for the UART's plan: the jig's DUT TX (probe RX) / DUT RX (probe TX) labels, else the first two free."""
107
+ labels = {name: ch for ch, name in ep.static_labels.items()}
108
+ if "DUT TX" in labels and "DUT RX" in labels:
109
+ return labels["DUT TX"], labels["DUT RX"]
110
+ allowed = []
111
+ for t in ep.static[fn]:
112
+ if t[0] == catalog.ROLE_CHANNELS and t[2] == 1:
113
+ base = struct.unpack_from("<H", t, 3)[0]
114
+ allowed = catalog.bitmap_to_channels(base, t[5:2 + t[1]])
115
+ taken = {p for s in ep.slots.values() for p in s.pair} | {a[2] for a in ep.plan}
116
+ free = [ch for ch in allowed if ch not in taken]
117
+ if len(free) < 2:
118
+ raise SystemExit("no two free channels for the UART's plan")
119
+ return free[0], free[1]
120
+
121
+
90
122
  def _load_hook(spec: str):
91
123
  where, _, name = spec.rpartition(":")
92
124
  if not where or not name:
@@ -118,20 +150,28 @@ def _filter(a: argparse.Namespace):
118
150
  return answer
119
151
 
120
152
 
153
+ def _text(fmt: str, count: int) -> bytes:
154
+ text = fmt.replace("%d", str(count)) if "%d" in fmt else fmt
155
+ return text.encode().decode("unicode_escape").encode("latin-1")
156
+
157
+
121
158
  class Console:
122
- def __init__(self, ep: endpoint.Endpoint, fmt: str | None, every_ms: int):
123
- self.ep, self.fmt, self.every = ep, fmt, every_ms
159
+ """Every `every_ms`: the targets write `fmt` to their consoles, and `uart_rx` arrives on the UART's RX (fn
160
+ `uart`) while it runs (configured)."""
161
+
162
+ def __init__(self, ep: endpoint.Endpoint, fmt: str | None, every_ms: int, uart_rx: str | None = None,
163
+ uart: int | None = None):
164
+ self.ep, self.fmt, self.every, self.uart_rx, self.uart = ep, fmt, every_ms, uart_rx, uart
124
165
  self.next_ms, self.count = 0, 0
125
166
 
126
167
  def tick(self) -> None:
127
- if not self.fmt or self.ep.now() < self.next_ms:
168
+ if not (self.fmt or self.uart_rx) or self.ep.now() < self.next_ms:
128
169
  return
129
170
  self.next_ms = self.ep.now() + self.every
130
- for i, tg in enumerate(self.ep.targets.values()):
131
- text = self.fmt.replace("{t}", str(i))
132
- if "%d" in text:
133
- text = text.replace("%d", str(self.count))
134
- self.ep.target_says(text.encode().decode("unicode_escape").encode("latin-1"), tg)
171
+ for i, tg in enumerate(self.ep.targets.values()) if self.fmt else ():
172
+ self.ep.target_says(_text(self.fmt.replace("{t}", str(i)), self.count), tg)
173
+ if self.uart_rx and self.uart in self.ep.uarts:
174
+ self.ep.uart_rx(self.uart, _text(self.uart_rx, self.count))
135
175
  self.count += 1
136
176
 
137
177
 
@@ -257,7 +297,7 @@ def _serve_conn(a, ep, console, conn, watch_stdin) -> bool:
257
297
 
258
298
 
259
299
  def main(argv: list[str] | None = None) -> None:
260
- ap = argparse.ArgumentParser(prog="python -m oep_client.v1.fake_serve", description=__doc__.split("\n\n")[0])
300
+ ap = argparse.ArgumentParser(prog="python -m oep_client.fake_serve", description=__doc__.split("\n\n")[0])
261
301
  where = ap.add_mutually_exclusive_group()
262
302
  where.add_argument("--pty", action="store_true")
263
303
  where.add_argument("--tcp", type=int, metavar="PORT")
@@ -273,6 +313,8 @@ def main(argv: list[str] | None = None) -> None:
273
313
  ap.add_argument("--bind", choices=["last-reset", "manual", "mixed"])
274
314
  ap.add_argument("--target-id")
275
315
  ap.add_argument("--absent", type=int, action="append", default=[])
316
+ ap.add_argument("--uart-plan", action="store_true")
317
+ ap.add_argument("--uart-rx")
276
318
  ap.add_argument("--run-hook")
277
319
  ap.add_argument("--once", action="store_true")
278
320
  a = ap.parse_args(argv)
@@ -285,7 +327,7 @@ def main(argv: list[str] | None = None) -> None:
285
327
  probe_ep = endpoint.Endpoint(profile(), lambda: 0)
286
328
  a.port_index = min(probe_ep.serial_ports) if probe_ep.serial_ports else 0
287
329
  ep = build(a)
288
- console = Console(ep, a.console, _ms(a.every))
330
+ console = Console(ep, a.console, _ms(a.every), a.uart_rx, uart_fn(ep) if a.uart_rx else None)
289
331
  try:
290
332
  if a.tcp is None:
291
333
  serve_pty(a, ep, console)
@@ -4,7 +4,7 @@ import json
4
4
 
5
5
  import pytest
6
6
 
7
- from oep_client.v1 import catalog, dump, fake, names
7
+ from oep_client import catalog, dump, fake, names
8
8
 
9
9
 
10
10
  # ---- names ---------------------------------------------------------------
@@ -1,9 +1,9 @@
1
- """oep_client.v1.capture: the configure answer and the §3.0 logic layout (oep-spec logic-capture.ja.md)."""
1
+ """oep_client.capture: the configure answer and the §3.0 logic layout (oep-spec logic-capture.ja.md)."""
2
2
 
3
3
  import struct
4
4
  from fractions import Fraction
5
5
 
6
- from oep_client.v1 import capture as c
6
+ from oep_client import capture as c
7
7
 
8
8
 
9
9
  def answer(*items):
@@ -92,7 +92,7 @@ def test_stream_does_not_count_the_fns_events_as_lost():
92
92
 
93
93
  def test_read_spans_frames_without_the_header_leaking_into_the_data():
94
94
  """read() splits a long read into frame-sized ones; each answer is position(u64) flags(u8) data."""
95
- from test_v1_target_parts import ScriptedHost, ok
95
+ from test_target_parts import ScriptedHost, ok
96
96
  stream = bytes(range(256)) * 12 # 3072 bytes: more than one 1024-byte frame
97
97
  def read(p):
98
98
  pos, n = struct.unpack("<QI", p[:12])
@@ -4,7 +4,7 @@ import random
4
4
 
5
5
  import pytest
6
6
 
7
- from oep_client.v1 import cobs
7
+ from oep_client import cobs
8
8
 
9
9
 
10
10
  def test_crc_check_value():
@@ -2,8 +2,8 @@
2
2
 
3
3
  import struct
4
4
 
5
- from oep_client.v1 import decode, esp32_targets as et, message as m
6
- from test_v1_target_parts import ScriptedHost, ok
5
+ from oep_client import decode, esp32_targets as et, message as m
6
+ from test_target_parts import ScriptedHost, ok
7
7
 
8
8
  I2C, SPI = 11, 12
9
9
 
@@ -11,7 +11,7 @@ import time
11
11
 
12
12
  import pytest
13
13
 
14
- from oep_client.v1 import cobs, endpoint, fake, fake_serial, message as m, registry as reg
14
+ from oep_client import cobs, endpoint, fake, fake_serial, message as m, registry as reg
15
15
 
16
16
  CFG = reg.PROBE_CONFIG
17
17
  ITEM, ATTACH, MODE, KIND = CFG.tlv["item"], CFG.enum["slot_attach"], CFG.enum["bind_mode"], CFG.enum["bind_stream"]
@@ -289,7 +289,7 @@ def test_last_reset_follows_the_target_the_host_reset():
289
289
 
290
290
  @pytest.mark.skipif(sys.platform != "linux", reason="pty and TIOCEXCL as on Linux")
291
291
  def test_fake_serve_pty_speaks_cobs_with_console_bytes_and_honours_tiocexcl():
292
- proc = subprocess.Popen([sys.executable, "-m", "oep_client.v1.fake_serve", "--pty", "--profile", "p4-bench",
292
+ proc = subprocess.Popen([sys.executable, "-m", "oep_client.fake_serve", "--pty", "--profile", "p4-bench",
293
293
  "--slot", "x035", "--bind", "last-reset", "--console", "tick %d\\n", "--every", "20"],
294
294
  stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
295
295
  try:
@@ -320,11 +320,12 @@ def test_fake_serve_pty_speaks_cobs_with_console_bytes_and_honours_tiocexcl():
320
320
 
321
321
 
322
322
  def test_fake_serve_run_hook_from_a_file(tmp_path):
323
- from oep_client.v1 import fake_serve
323
+ from oep_client import fake_serve
324
324
  hook = tmp_path / "loader.py"
325
325
  hook.write_text("def run(target, pc, regs):\n target.mem[0x100] = regs.get(0x100A, 0)\n return True, pc + 4, 7\n")
326
326
  a = fake_serve.main.__globals__["argparse"].Namespace(
327
- profile="p4-x035", target_id=None, absent=[], slot=[], bind=None, port_index=0, run_hook=f"{hook}:run")
327
+ profile="p4-x035", target_id=None, absent=[], slot=[], bind=None, port_index=0, run_hook=f"{hook}:run",
328
+ uart_plan=False)
328
329
  ep = fake_serve.build(a)
329
330
  assert ep.target.run_hook(0x2000, {0x100A: 5}) == (True, 0x2004, 7) and ep.target.mem[0x100] == 5
330
331
 
@@ -344,3 +345,28 @@ def test_a_closed_console_stays_readable_until_the_same_place_opens_again():
344
345
  a2 = attach(p[0])
345
346
  h.ok(3, 0x01, struct.pack("<HB", a2, 2)) # the same place again: the old one goes
346
347
  assert h.raw(3, 0x02, struct.pack("<HBQH", sa, 1, 0, 16), session=False).detail == m.UNAVAILABLE
348
+
349
+
350
+ def test_fake_serve_uart_plan_and_rx():
351
+ import time
352
+ from oep_client import fixture, link
353
+ proc = subprocess.Popen([sys.executable, "-m", "oep_client.fake_serve", "--tcp", "0", "--framing", "length",
354
+ "--profile", "esp32-v003", "--uart-plan", "--uart-rx", "rx %d\\n", "--every", "10"],
355
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
356
+ try:
357
+ port = proc.stdout.readline().split()[1]
358
+ hst = link.open_host(f"tcp://127.0.0.1:{port}", timeout=1.0)
359
+ hst.open(3000)
360
+ uart = fixture.FixtureUartIO(hst)
361
+ assert uart.configure(115200) > 0 # the saved plan: configure works
362
+ assert uart.configure(9600) > 0 # and again, while it runs
363
+ got, deadline = b"", time.monotonic() + 2
364
+ while b"\n" not in got and time.monotonic() < deadline:
365
+ got += uart.read()
366
+ assert got.startswith(b"rx ") # the RX side, from the configure on
367
+ uart.write(b"to the DUT") # out on TX
368
+ hst.end()
369
+ hst.link.close()
370
+ finally:
371
+ proc.stdin.close()
372
+ proc.wait(timeout=5)
@@ -6,8 +6,8 @@ import struct
6
6
 
7
7
  import pytest
8
8
 
9
- from oep_client.v1 import ch32_flash as cf, console, fixture, host as h, message as m, riscv, uiapduino
10
- from test_v1_target_parts import FNS, ScriptedHost, ok
9
+ from oep_client import ch32_flash as cf, console, fixture, host as h, message as m, riscv, uiapduino
10
+ from test_target_parts import FNS, ScriptedHost, ok
11
11
 
12
12
  WIRE, DM, GPIO = FNS["oep.wire.rvswd"], FNS["oep.target.riscv-dm"], FNS["oep.fixture.gpio"]
13
13
  CONSOLE, UART = 6, 7
@@ -6,7 +6,7 @@ import time
6
6
 
7
7
  import pytest
8
8
 
9
- from oep_client.v1 import hid_stream, link
9
+ from oep_client import hid_stream, link
10
10
 
11
11
 
12
12
  def espusbdevice_vendor_descriptor(size: int) -> bytes:
@@ -7,7 +7,7 @@ import struct
7
7
 
8
8
  import pytest
9
9
 
10
- from oep_client.v1 import capture, console, core, endpoint, fake, fixture, host, message as m, riscv
10
+ from oep_client import capture, console, core, endpoint, fake, fixture, host, message as m, riscv
11
11
 
12
12
  WIRE, DM, CONSOLE, GPIO, UART = 1, 2, 3, 4, 5 # p4_x035
13
13
 
@@ -259,7 +259,7 @@ def test_uart_configure_format_and_reads_that_do_not_consume(bench):
259
259
 
260
260
 
261
261
  def test_capture_critical_tag_it_cannot_honour_is_unsupported():
262
- from test_v1_target_parts import ScriptedHost
262
+ from test_target_parts import ScriptedHost
263
263
 
264
264
  def configure(p):
265
265
  for tag, _ in m.split_tlvs(p):
@@ -351,7 +351,7 @@ def test_a_plan_replaces_only_the_fns_it_names_and_release_takes_a_list(bench):
351
351
 
352
352
 
353
353
  def test_ch32_resume_asks_again_while_dpc_has_not_moved(dm):
354
- from oep_client.v1 import ch32_flash
354
+ from oep_client import ch32_flash
355
355
  ep, hst, d = dm
356
356
  assert d.read_register(d.DPC) == ep.target.dpc
357
357
  ep.target.resume_misses = 2 # two requests that do not take, then it goes
@@ -5,7 +5,7 @@ import struct
5
5
 
6
6
  import pytest
7
7
 
8
- from oep_client.v1 import catalog, core, frames, host as h, link, message as m
8
+ from oep_client import catalog, core, frames, host as h, link, message as m
9
9
 
10
10
 
11
11
  class Stream:
@@ -9,7 +9,7 @@ import sys
9
9
 
10
10
  import pytest
11
11
 
12
- from oep_client.v1 import capture, cobs, core, host as h, link, message as m
12
+ from oep_client import capture, cobs, core, host as h, link, message as m
13
13
 
14
14
 
15
15
  class Scripted:
@@ -67,7 +67,7 @@ def test_a_missing_answer_goes_once_more_with_the_same_corr():
67
67
 
68
68
 
69
69
  def serve(*args):
70
- proc = subprocess.Popen([sys.executable, "-m", "oep_client.v1.fake_serve", *args],
70
+ proc = subprocess.Popen([sys.executable, "-m", "oep_client.fake_serve", *args],
71
71
  stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
72
72
  return proc, proc.stdout.readline().split()
73
73
 
@@ -4,7 +4,7 @@ import struct
4
4
 
5
5
  import pytest
6
6
 
7
- from oep_client.v1 import endpoint, fake, host, message as m
7
+ from oep_client import endpoint, fake, host, message as m
8
8
 
9
9
  TOY = 7 # a fn the fake does not simulate (esp32_v003: the i2c-target) has the stand-in operations
10
10
 
@@ -53,7 +53,7 @@ def test_a_48_byte_name_fits_a_64_byte_frame():
53
53
  assert len(name) == 48
54
54
  probe = fake.FakeProbe("tiny", 64, [fake.Offered(0, 0, "oep.core"), fake.Offered(1, 1, name)])
55
55
  ep = endpoint.Endpoint(probe, Clock())
56
- from oep_client.v1 import catalog
56
+ from oep_client import catalog
57
57
  result = ep.handle(m.Request(1, 0, m.OP_LIST, catalog.pack_list_request(name, True, 0)).pack())
58
58
  assert len(result) == 63 and catalog.unpack_list_result(result[5:])[1][0].name == name # oep-v1 frame budget (v1-core-wire-delta §6)
59
59
 
@@ -228,7 +228,7 @@ def test_the_host_skips_tlvs_it_does_not_know_after_every_result(bench):
228
228
  assert opened.lease_ms == 1000 and not opened.resumed
229
229
  write(a, 9)
230
230
  assert read(a) == 9 and a.lock_state() == (True, 1000)
231
- from oep_client.v1 import riscv
231
+ from oep_client import riscv
232
232
  wire = riscv.Wire(a, "oep.wire.swio")
233
233
  conn, _ = wire.attach()
234
234
  riscv.RiscvDm(a, conn).halt()
@@ -5,7 +5,7 @@ import struct
5
5
 
6
6
  import pytest
7
7
 
8
- from oep_client.v1 import arm, host as h, message as m, target
8
+ from oep_client import arm, host as h, message as m, target
9
9
 
10
10
  FNS = {"oep.wire.rvswd": 1, "oep.target.riscv-dm": 2, "oep.fixture.gpio": 3, "oep.wire.swd": 4,
11
11
  "oep.target.arm-adi": 5}
@@ -302,7 +302,7 @@ def test_cortexm_call_returns_r0_and_clears_maskints():
302
302
 
303
303
 
304
304
  def test_rp2350_flash_program_runs_the_sdk_sequence_and_verifies():
305
- from oep_client.v1 import rp2350
305
+ from oep_client import rp2350
306
306
  fake = FakeCortexM()
307
307
  core = arm.CortexM(fake, bkpt_at=rp2350.BKPT_AT, stack_top=rp2350.STACK_TOP)
308
308
  core.halt()
@@ -1,5 +0,0 @@
1
- """Open Embedded Probe (OEP) host client. `oep_client.v1` speaks the v1 protocol (oep-spec docs/oep-core.ja.md)."""
2
-
3
- __all__ = ["__version__"]
4
-
5
- __version__ = "0.0.1"
@@ -1 +0,0 @@
1
- """Draft: capability discovery by name (oep-spec capability-*.ja.md). No hardware yet."""