oep-client-python 0.0.3__tar.gz → 0.0.4__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 (51) hide show
  1. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/CHANGELOG.md +8 -0
  2. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/PKG-INFO +1 -1
  3. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/pyproject.toml +1 -1
  4. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/__init__.py +1 -1
  5. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/__main__.py +68 -3
  6. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/fake_serial.py +4 -2
  7. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/tests/test_config.py +32 -0
  8. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/tests/test_fake_spec.py +13 -0
  9. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/tests/test_link_serial.py +7 -2
  10. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/.gitignore +0 -0
  11. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/LICENSE +0 -0
  12. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/README.ja.md +0 -0
  13. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/README.md +0 -0
  14. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/arm.py +0 -0
  15. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/capture.py +0 -0
  16. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/catalog.py +0 -0
  17. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/ch32_flash.py +0 -0
  18. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/cobs.py +0 -0
  19. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/config.py +0 -0
  20. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/console.py +0 -0
  21. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/core.py +0 -0
  22. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/decode.py +0 -0
  23. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/dump.py +0 -0
  24. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/endpoint.py +0 -0
  25. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/esp32_targets.py +0 -0
  26. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/fake.py +0 -0
  27. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/fake_serve.py +0 -0
  28. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/fixture.py +0 -0
  29. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/frames.py +0 -0
  30. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/hid_stream.py +0 -0
  31. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/host.py +0 -0
  32. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/interfaces.py +0 -0
  33. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/link.py +0 -0
  34. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/message.py +0 -0
  35. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/names.py +0 -0
  36. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/registry.py +0 -0
  37. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/riscv.py +0 -0
  38. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/rp2350.py +0 -0
  39. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/target.py +0 -0
  40. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/uiapduino.py +0 -0
  41. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/src/oep_client/usb_stream.py +0 -0
  42. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/tests/test_capabilities.py +0 -0
  43. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/tests/test_capture.py +0 -0
  44. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/tests/test_cobs.py +0 -0
  45. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/tests/test_esp32_targets.py +0 -0
  46. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/tests/test_flash_console.py +0 -0
  47. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/tests/test_hid_stream.py +0 -0
  48. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/tests/test_interfaces.py +0 -0
  49. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/tests/test_link_host.py +0 -0
  50. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/tests/test_session.py +0 -0
  51. {oep_client_python-0.0.3 → oep_client_python-0.0.4}/tests/test_target_parts.py +0 -0
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.0.4
6
+ - (EN) Fake serial port: a candidate that is only its 0x00 is not raw after the 200 ms gap (the probe firmware had the same bug: a stray 0x00 reached the target's console).
7
+ - (JA) 偽のシリアルの口: 0x00 だけの候補は、200 ms の後も生のバイトにしない(probe の firmware にも同じバグがあり、target のコンソールに 0x00 が届いていた)。
8
+ - (EN) `oep config slot` takes the probe's only RISC-V wire when `--wire` is left out (a SWIO-only probe needed `--wire swio`).
9
+ - (JA) `oep config slot` は `--wire` を省くと、probe の唯一の RISC-V の線を使う(SWIO だけの probe で `--wire swio` が要っていた)。
10
+ - (EN) `oep config plan <probe> <fn | name#k> ROLE=CH ...` (role names from the registry: rx, tx, line...), `oep config label` and `oep config idle`.
11
+ - (JA) `oep config plan <probe> <fn | 名前#k> ROLE=CH ...`(role の名前は registry から: rx、tx、line など)、`oep config label`、`oep config idle`。
12
+
5
13
  ## 0.0.3
6
14
  - (EN) `oep_client.config` (oep.probe.config: slots, binds, plan / label / idle items, get / set / save / erase, the live slot and bind state) and the `oep config show | slot | bind | remove | save | erase` command. An English README.md, also the PyPI page.
7
15
  - (JA) `oep_client.config`(oep.probe.config: スロット、bind、plan / label / idle の項目、get / set / save / erase、スロットと bind の今の状態)と、`oep config show | slot | bind | remove | save | erase` の命令。英語の README.md(PyPI のページも)。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: oep-client-python
3
- Version: 0.0.3
3
+ Version: 0.0.4
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "oep-client-python"
7
- version = "0.0.3"
7
+ version = "0.0.4"
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"
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.0.3"
5
+ __version__ = "0.0.4"
@@ -4,6 +4,7 @@
4
4
  oep config show <probe>
5
5
  oep config slot <probe> --name x035 --wire rvswd --pins 2,54 --attach at-boot --retry 1 --mechanism dmseq
6
6
  oep config bind <probe> --port 1 --mode last-reset --stream slot:x035
7
+ oep config plan <probe> oep.fixture.uart#2 rx=48 tx=49 (the fn's whole plan; fn number or name#instance)
7
8
  oep config remove <probe> bind 1 oep config save <probe> oep config erase <probe>
8
9
 
9
10
  <probe>: a serial port, tcp://HOST:PORT or usb[:VID:PID[:SERIAL]]. A change takes the lock (owner "oep config") and
@@ -58,7 +59,7 @@ def _config_parser(sub) -> None:
58
59
  slot.add_argument("probe")
59
60
  slot.add_argument("--slot", type=int, default=0, help="the slot number (default 0)")
60
61
  slot.add_argument("--name", required=True, help="1-32 of a-z 0-9 - _ (the oep://<probe>/<name> address)")
61
- slot.add_argument("--wire", default="rvswd", help="rvswd or swio (or an fn)")
62
+ slot.add_argument("--wire", help="rvswd or swio (or an fn); default: the probe's only wire")
62
63
  slot.add_argument("--pins", help="swdio,swclk (one pin on swio); default: the wire's only pin set")
63
64
  slot.add_argument("--attach", choices=sorted(config.ATTACH), default="host")
64
65
  slot.add_argument("--retry", type=int, default=0, help="at-boot: try again every N s while absent (0: never)")
@@ -72,6 +73,21 @@ def _config_parser(sub) -> None:
72
73
  bind.add_argument("--stream", action="append", required=True, help="slot:NAME, slot:N or uart:FN (repeatable)")
73
74
  bind.add_argument("--select", type=int, default=0, help="manual: the stream it carries (index in --stream)")
74
75
  bind.add_argument("--save", action="store_true")
76
+ plan = cs.add_parser("plan", help="the pins an interface keeps (its whole plan, kept as a setting)")
77
+ plan.add_argument("probe")
78
+ plan.add_argument("fn", help="an fn, or an interface name with #k for its k-th instance (1 = the first)")
79
+ plan.add_argument("roles", nargs="+", help="ROLE=CHANNEL, ROLE a number or the interface's role name (rx, tx, line...)")
80
+ plan.add_argument("--save", action="store_true")
81
+ label = cs.add_parser("label", help="name a channel (shown in oep.core's describe)")
82
+ label.add_argument("probe")
83
+ label.add_argument("channel", type=int)
84
+ label.add_argument("text")
85
+ label.add_argument("--save", action="store_true")
86
+ idle = cs.add_parser("idle", help="the state of a free pin")
87
+ idle.add_argument("probe")
88
+ idle.add_argument("channel", type=int)
89
+ idle.add_argument("mode", choices=sorted(config.IDLE))
90
+ idle.add_argument("--save", action="store_true")
75
91
  rm = cs.add_parser("remove", help="remove one item: slot N, bind PORT, plan FN, label CH, idle CH")
76
92
  rm.add_argument("probe")
77
93
  rm.add_argument("kind", choices=["slot", "bind", "plan", "label", "idle"])
@@ -95,8 +111,40 @@ def _pins(hst, fn: int, text: str | None, wire: str) -> tuple[int, int]:
95
111
  return groups[0]
96
112
 
97
113
 
98
- def _wire_fn(hst, wire: str) -> int:
99
- return int(wire) if wire.isdigit() else core.find(hst, wire if wire.startswith("oep.") else f"oep.wire.{wire}")
114
+ def _plan_fn(hst, spec: str) -> tuple[int, dict[str, int]]:
115
+ """fn and its role names (from the registry) for `spec`: an fn number, or name[#k] (k-th instance, 1-based)."""
116
+ from . import registry as reg
117
+ if spec.isdigit():
118
+ fn = int(spec)
119
+ name = next((e.name for e in core.list_entries(hst) if e.fn == fn), "")
120
+ else:
121
+ name, _, k = spec.partition("#")
122
+ fns = core.find_all(hst, name)
123
+ if not fns:
124
+ raise SystemExit(f"the probe offers no {name}")
125
+ index = int(k) - 1 if k else 0
126
+ if not 0 <= index < len(fns):
127
+ raise SystemExit(f"{name}: {len(fns)} instance(s), fns {fns}")
128
+ fn = fns[index]
129
+ iface = reg.INTERFACES.get(name)
130
+ roles = dict(iface.enum.get("role", {})) if iface else {}
131
+ return fn, roles
132
+
133
+
134
+ def _wire_fn(hst, wire: str | None) -> int:
135
+ if wire is None:
136
+ wires = [e for e in core.list_entries(hst, "oep.wire") if e.name in ("oep.wire.rvswd", "oep.wire.swio")]
137
+ if len(wires) != 1:
138
+ raise SystemExit("this probe has " + (", ".join(f"{e.name} (fn {e.fn})" for e in wires) or "no RISC-V wire")
139
+ + ": name one with --wire")
140
+ return wires[0].fn
141
+ name = wire if wire.startswith("oep.") else f"oep.wire.{wire}"
142
+ if wire.isdigit():
143
+ return int(wire)
144
+ try:
145
+ return core.find(hst, name)
146
+ except LookupError:
147
+ raise SystemExit(f"this probe does not offer {name}") from None
100
148
 
101
149
 
102
150
  def _stream(spec: str, slots: dict[str, int]) -> tuple[str, int]:
@@ -129,6 +177,7 @@ def _config(args) -> int:
129
177
  return _show(hst, cfg, args.json)
130
178
  if args.action == "slot":
131
179
  fn = _wire_fn(hst, args.wire)
180
+ args.wire = args.wire or str(fn)
132
181
  lock = None
133
182
  if args.lock:
134
183
  mask, _, value = args.lock.partition(":")
@@ -140,6 +189,22 @@ def _config(args) -> int:
140
189
  slots = {it.name: it.slot for it in cfg.items() if isinstance(it, config.Slot)}
141
190
  it = config.Bind(args.port, args.mode, [_stream(s, slots) for s in args.stream], args.select)
142
191
  _change(hst, cfg, [it], args.save)
192
+ elif args.action == "plan":
193
+ fn, roles = _plan_fn(hst, args.fn)
194
+ items = []
195
+ for spec in args.roles:
196
+ role, _, ch = spec.partition("=")
197
+ if not ch:
198
+ raise SystemExit(f"{spec}: want ROLE=CHANNEL")
199
+ number = int(role) if role.isdigit() else roles.get(role.lower())
200
+ if number is None:
201
+ raise SystemExit(f"{role}: not a role of fn {fn} (roles: {', '.join(sorted(roles)) or 'numbers only'})")
202
+ items.append(config.Plan(fn, number, int(ch, 0)))
203
+ _change(hst, cfg, items, args.save)
204
+ elif args.action == "label":
205
+ _change(hst, cfg, [config.Label(args.channel, args.text)], args.save)
206
+ elif args.action == "idle":
207
+ _change(hst, cfg, [config.Idle(args.channel, args.mode)], args.save)
143
208
  elif args.action == "remove":
144
209
  _change(hst, cfg, [config.remove(args.kind, args.key)], args.save)
145
210
  else:
@@ -5,7 +5,8 @@
5
5
 
6
6
  - a candidate runs from a 0x00 to the next 0x00; if it decodes and its CRC matches it is a request, otherwise it
7
7
  (with its leading 0x00) is raw bytes, and the closing 0x00 starts the next candidate; bytes outside a candidate
8
- are raw at once; a candidate that stops for 200 ms (`tick`) is raw; an empty candidate (0x00 0x00) is nothing
8
+ are raw at once; a candidate that stops for 200 ms (`tick`) is raw; a candidate that is only its 0x00 (0x00 0x00,
9
+ or the closing 0x00 of a frame when nothing follows) is nothing
9
10
  - raw bytes go to the endpoint's bind for this port (`Endpoint.port_input`)
10
11
  - answers go out as 0x00 <COBS> 0x00, ahead of raw chunks (`Endpoint.port_output`); one writer, never a raw byte
11
12
  inside a frame
@@ -80,7 +81,8 @@ class FakeSerialPort:
80
81
  def _gap(self, now: int) -> None:
81
82
  if self.cand is not None and now - self.last_ms >= GAP_MS:
82
83
  raw, self.cand = bytes(self.cand), None
83
- self._raw(bytearray(raw))
84
+ if len(raw) > 1: # only its 0x00: a delimiter (a frame's closing 0x00), not raw
85
+ self._raw(bytearray(raw))
84
86
 
85
87
  def _raw(self, raw: bytearray) -> None:
86
88
  if raw:
@@ -65,3 +65,35 @@ def test_the_command(capsys, monkeypatch):
65
65
  assert cli.main(["config", "show", "x"]) == 0
66
66
  out = capsys.readouterr().out
67
67
  assert "0 x035: fn 1 pins 2,3 at-boot retry 1 s dmseq" in out and "port 0 (usb_serial_jtag): last-reset [slot:x035]" in out
68
+
69
+
70
+ def test_plan_label_idle_from_the_command(capsys, monkeypatch):
71
+ ep, hst = open_bench()
72
+
73
+ class FakeLink:
74
+ def close(self):
75
+ pass
76
+ hst.link = FakeLink()
77
+ hst.end()
78
+ monkeypatch.setattr(cli.link, "open_host", lambda target: hst)
79
+ assert cli.main(["config", "plan", "x", "oep.fixture.uart", "rx=20", "tx=21"]) == 0
80
+ assert cli.main(["config", "label", "x", "20", "DUT TX"]) == 0
81
+ assert cli.main(["config", "idle", "x", "21", "pull-up", "--save"]) == 0
82
+ assert {(5, 1, 20), (5, 2, 21)} <= ep.plan and 5 in ep.plan_from_config
83
+ items = config.ProbeConfig(hst).items()
84
+ assert config.Label(20, "DUT TX") in items and config.Idle(21, "pull-up") in items
85
+ assert ep.saved is not None
86
+
87
+
88
+ def test_slot_takes_the_only_wire_by_default(monkeypatch):
89
+ ep = endpoint.Endpoint(fake.esp32_v003(), Clock()) # oep.wire.swio only
90
+ hst = h.Host(lambda b: ep.handle(b, 0))
91
+
92
+ class FakeLink:
93
+ def close(self):
94
+ pass
95
+ hst.link = FakeLink()
96
+ monkeypatch.setattr(cli.link, "open_host", lambda target: hst)
97
+ assert cli.main(["config", "slot", "x", "--name", "v003", "--attach", "at-boot", "--retry", "1"]) == 0
98
+ (slot,) = [i for i in config.ProbeConfig(hst).items() if isinstance(i, config.Slot)]
99
+ assert slot.wire_fn == 1 and slot.pins == (16, 0xFFFF)
@@ -370,3 +370,16 @@ def test_fake_serve_uart_plan_and_rx():
370
370
  finally:
371
371
  proc.stdin.close()
372
372
  proc.wait(timeout=5)
373
+
374
+
375
+ def test_the_closing_0x00_of_a_frame_is_not_raw_after_the_gap():
376
+ clock = Clock()
377
+ ep = endpoint.Endpoint(fake.p4_bench(), clock)
378
+ p = ep.pairs[1]
379
+ ep.load_config([slot_item(0, 1, p[0]), bind_item(0, MODE["manual"], [(KIND["slot_console"], 0)])])
380
+ port = fake_serial.FakeSerialPort(ep, 0)
381
+ port.feed(framed(m.Request(1, 0, m.OP_LOCK_STATE, b""))) # lock-free: the port is not held
382
+ clock.t += 250
383
+ port.tick()
384
+ sid = ep.stream_keys[(ep._conn_at(1, p[0]), 2)]
385
+ assert bytes(ep.streams[sid].written) == b"" # no stray 0x00 at the target
@@ -82,9 +82,13 @@ def test_open_host_on_a_serial_port_with_console_bytes_and_tiocexcl():
82
82
  if os.geteuid() != 0:
83
83
  with pytest.raises(link.PortBusy):
84
84
  link.open_host(where[1]) # exclusive: one host at a time
85
+ import time
86
+ deadline = time.monotonic() + 3 # lock-free reads among the console bytes,
87
+ while hst.link.noise == 0 and time.monotonic() < deadline: # before a session holds the port's raw side
88
+ assert [k for _, k, _ in core.transports(hst)] == [1] # a UART bridge
89
+ time.sleep(0.02)
85
90
  opened = core.take(hst, 3000, owner="test") # the only way in: by force at once
86
91
  assert opened.lease_ms == 3000
87
- assert [k for _, k, _ in core.transports(hst)] == [1] # a UART bridge
88
92
  for _ in range(20): # requests among the console bytes
89
93
  hst.keepalive()
90
94
  hst.end()
@@ -100,7 +104,8 @@ def test_the_same_host_over_tcp_length_frames_and_a_holder_that_keeps_its_lease_
100
104
  try:
101
105
  a = link.open_host(f"tcp://127.0.0.1:{where[1]}", timeout=1.0)
102
106
  a.open(60000, owner="ch32rv monitor")
103
- assert a.lock_owner() == (True, a.lock_owner()[1], "ch32rv monitor")
107
+ locked, remaining, owner = a.lock_owner() # one read: the lease ticks down between two
108
+ assert locked and 0 < remaining <= 60000 and owner == "ch32rv monitor"
104
109
  b = h.Host(a.link.send) # another host on the same link (a broker's client)
105
110
  b.link = a.link
106
111
  b.revision = 1