oep-client-python 0.0.20__py3-none-any.whl → 0.0.22__py3-none-any.whl

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/__init__.py CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.0.20"
5
+ __version__ = "0.0.22"
oep_client/__main__.py CHANGED
@@ -1,10 +1,12 @@
1
1
  """The oep command: what a probe offers (dump) and its settings (config).
2
2
 
3
3
  oep dump --port /run/board-identify/by-id/<probe> oep dump --fake p4-x035 --prefix oep.target --json
4
- oep config show <probe>
4
+ oep config show <probe> (the settings and what the probe declares; lock-free)
5
+ oep config state <probe> (the live slot / bind / storage state; lock-free)
5
6
  oep config slot <probe> --name x035 --wire rvswd --pins 2,54 --attach at-boot --retry 1 --mechanism dmseq
6
7
  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)
8
+ oep config plan <probe> oep.fixture.uart#1 rx=48 tx=49 (the fn's whole plan; fn number or name#instance)
9
+ oep config uart <probe> oep.fixture.uart#1 115200 --format 8N1
8
10
  oep config remove <probe> bind 1 oep config save <probe> oep config erase <probe>
9
11
 
10
12
  <probe>: a serial port, tcp://HOST:PORT or usb[:VID:PID[:SERIAL]]. A change takes the lock (owner "oep config") and
@@ -52,9 +54,12 @@ def main(argv=None) -> int:
52
54
  def _config_parser(sub) -> None:
53
55
  c = sub.add_parser("config", help="the probe's settings (oep.probe.config): slots, binds, save")
54
56
  cs = c.add_subparsers(dest="action", required=True)
55
- show = cs.add_parser("show", help="the settings and the live slot / bind state")
57
+ show = cs.add_parser("show", help="the settings, what the probe declares, and the live slot / bind state")
56
58
  show.add_argument("probe")
57
59
  show.add_argument("--json", action="store_true")
60
+ state = cs.add_parser("state", help="the live slot / bind / storage state (op state, lock-free)")
61
+ state.add_argument("probe")
62
+ state.add_argument("--json", action="store_true")
58
63
  slot = cs.add_parser("slot", help="register a slot (a place a target is wired to)")
59
64
  slot.add_argument("probe")
60
65
  slot.add_argument("--slot", type=int, default=0, help="the slot number (default 0)")
@@ -62,8 +67,9 @@ def _config_parser(sub) -> None:
62
67
  slot.add_argument("--wire", help="rvswd or swio (or an fn); default: the probe's only wire")
63
68
  slot.add_argument("--pins", help="swdio,swclk (one pin on swio); default: the wire's only pin set")
64
69
  slot.add_argument("--attach", choices=sorted(config.ATTACH), default="host")
65
- slot.add_argument("--retry", type=int, default=0, help="at-boot: try again every N s while absent (0: never)")
66
- slot.add_argument("--mechanism", choices=sorted(config.MECHANISM), default="dmseq")
70
+ slot.add_argument("--retry", type=float, default=0, help="at-boot: try again every N s while absent (0: never)")
71
+ slot.add_argument("--mechanism", choices=sorted(config.MECHANISM), default="dmseq",
72
+ help="the console's mechanism, or none (no console on this slot)")
67
73
  slot.add_argument("--max-speed", type=int, default=0,
68
74
  help="the line's ceiling in Hz for the probe's own attach (0: none); the target's, e.g. 1000000")
69
75
  slot.add_argument("--idle-clock", choices=sorted(config.IDLE_CLOCK), default="high",
@@ -79,7 +85,7 @@ def _config_parser(sub) -> None:
79
85
  bind.add_argument("--save", action="store_true")
80
86
  plan = cs.add_parser("plan", help="the pins an interface keeps (its whole plan, kept as a setting)")
81
87
  plan.add_argument("probe")
82
- plan.add_argument("fn", help="an fn, or an interface name with #k for its k-th instance (1 = the first)")
88
+ plan.add_argument("fn", help="an fn, or name#instance (the list instance, 0 = the first; #0 may be left out, core §7.2)")
83
89
  plan.add_argument("roles", nargs="+", help="ROLE=CHANNEL, ROLE a number or the interface's role name (rx, tx, line...)")
84
90
  plan.add_argument("--save", action="store_true")
85
91
  label = cs.add_parser("label", help="name a channel (shown in oep.core's describe)")
@@ -92,9 +98,15 @@ def _config_parser(sub) -> None:
92
98
  idle.add_argument("channel", type=int)
93
99
  idle.add_argument("mode", choices=sorted(config.IDLE))
94
100
  idle.add_argument("--save", action="store_true")
95
- rm = cs.add_parser("remove", help="remove one item: slot N, bind PORT, plan FN, label CH, idle CH")
101
+ uart = cs.add_parser("uart", help="a fixture UART's baud / format, applied whenever its plan has RX or TX")
102
+ uart.add_argument("probe")
103
+ uart.add_argument("fn", help="an fn, or name#instance of the oep.fixture.uart")
104
+ uart.add_argument("baud", type=int)
105
+ uart.add_argument("--format", default="8N1", help="data bits, parity, stop bits: 8N1 (default), 8E1, 7O2 ...")
106
+ 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")
96
108
  rm.add_argument("probe")
97
- rm.add_argument("kind", choices=["slot", "bind", "plan", "label", "idle"])
109
+ rm.add_argument("kind", choices=sorted(config.ITEM))
98
110
  rm.add_argument("key", type=int)
99
111
  rm.add_argument("--save", action="store_true")
100
112
  for name in ("save", "erase"):
@@ -108,7 +120,7 @@ def _pins(hst, fn: int, text: str | None, wire: str) -> tuple[int, int]:
108
120
  groups = []
109
121
  for tag, v in core.describe(hst, fn):
110
122
  if tag & 0x7F == catalog.CHANNEL_GROUP:
111
- roles = {v[1 + 3 * i]: struct.unpack_from("<H", v, 2 + 3 * i)[0] for i in range((len(v) - 1) // 3)}
123
+ roles = dict(catalog.unpack_channel_group(v)[1])
112
124
  groups.append((roles.get(1, 0xFFFF), roles.get(2, 0xFFFF)))
113
125
  if len(groups) != 1:
114
126
  raise SystemExit(f"{wire}: {len(groups)} pin sets on this probe - name one with --pins")
@@ -116,20 +128,21 @@ def _pins(hst, fn: int, text: str | None, wire: str) -> tuple[int, int]:
116
128
 
117
129
 
118
130
  def _plan_fn(hst, spec: str) -> tuple[int, dict[str, int]]:
119
- """fn and its role names (from the registry) for `spec`: an fn number, or name[#k] (k-th instance, 1-based)."""
131
+ """fn and its role names (from the registry) for `spec`: an fn number, or name[#instance] (the list's instance,
132
+ 0-based, core §7.2)."""
120
133
  from . import registry as reg
121
134
  if spec.isdigit():
122
135
  fn = int(spec)
123
136
  name = next((e.name for e in core.list_entries(hst) if e.fn == fn), "")
124
137
  else:
125
138
  name, _, k = spec.partition("#")
126
- fns = core.find_all(hst, name)
127
- if not fns:
139
+ entries = [e for e in core.list_entries(hst, name, exact=True)]
140
+ if not entries:
128
141
  raise SystemExit(f"the probe offers no {name}")
129
- index = int(k) - 1 if k else 0
130
- if not 0 <= index < len(fns):
131
- raise SystemExit(f"{name}: {len(fns)} instance(s), fns {fns}")
132
- fn = fns[index]
142
+ instance = int(k) if k else 0
143
+ fn = next((e.fn for e in entries if e.instance == instance), None)
144
+ if fn is None:
145
+ raise SystemExit(f"{name}: instances {sorted(e.instance for e in entries)}, not {instance}")
133
146
  iface = reg.INTERFACES.get(name)
134
147
  roles = dict(iface.enum.get("role", {})) if iface else {}
135
148
  return fn, roles
@@ -179,6 +192,8 @@ def _config(args) -> int:
179
192
  cfg = config.ProbeConfig(hst)
180
193
  if args.action == "show":
181
194
  return _show(hst, cfg, args.json)
195
+ if args.action == "state":
196
+ return _state(cfg, args.json)
182
197
  if args.action == "slot":
183
198
  fn = _wire_fn(hst, args.wire)
184
199
  args.wire = args.wire or str(fn)
@@ -207,6 +222,15 @@ def _config(args) -> int:
207
222
  raise SystemExit(f"{role}: not a role of fn {fn} (roles: {', '.join(sorted(roles)) or 'numbers only'})")
208
223
  items.append(config.Plan(fn=fn, role=number, channel=int(ch, 0)))
209
224
  _change(hst, cfg, items, args.save)
225
+ elif args.action == "uart":
226
+ fn, _ = _plan_fn(hst, args.fn)
227
+ from .fixture import FixtureUart
228
+ fmt = args.format.upper()
229
+ try:
230
+ byte = FixtureUart.format_byte(int(fmt[0]), fmt[1], int(fmt[2]))
231
+ except (KeyError, ValueError, IndexError):
232
+ raise SystemExit(f"--format {args.format}: want <data bits><parity><stop bits>, e.g. 8N1, 8E2, 7O1") from None
233
+ _change(hst, cfg, [config.Uart(fn=fn, baud=args.baud, format=byte)], args.save)
210
234
  elif args.action == "label":
211
235
  _change(hst, cfg, [config.Label(channel=args.channel, text=args.text)], args.save)
212
236
  elif args.action == "idle":
@@ -231,42 +255,66 @@ def _config(args) -> int:
231
255
  hst.link.close()
232
256
 
233
257
 
258
+ def _plain(o):
259
+ return {k: (v.hex() if isinstance(v, bytes) else v) for k, v in vars(o).items()} if hasattr(o, "__dict__") \
260
+ else list(o)
261
+
262
+
263
+ def _state_dict(st) -> dict:
264
+ return {**{k: v for k, v in vars(st).items() if k not in ("slots", "binds")},
265
+ "slots": [_plain(s) for s in st.slots], "binds": [_plain(b) for b in st.binds]}
266
+
267
+
268
+ def _state(cfg, as_json: bool) -> int:
269
+ """The live state alone (op state, lock-free): what a monitor polls."""
270
+ st = cfg.state()
271
+ if as_json:
272
+ print(json.dumps(_state_dict(st), indent=2, default=lambda o: o.hex() if isinstance(o, bytes) else str(o)))
273
+ return 0
274
+ why = f" ({st.unreadable})" if st.unreadable else ""
275
+ print(f"storage: {st.storage}{why}, saved hash 0x{st.saved_hash:08x}")
276
+ for s in st.slots:
277
+ print(f" slot {s.slot}: {s.state}" + (f", connection {s.connection}" if s.connection else "")
278
+ + (f", tried at {s.last_try_at_ns / 1e9:.3f} s" if s.last_try_at_ns is not None else "")
279
+ + (f", target_id {s.target_id[::-1].hex()}" if s.target_id else ""))
280
+ for b in st.binds:
281
+ print(f" port {b.port}: {b.mode}, {b.flow}" + (f", carrying {b.selected}" if b.selected is not None else ""))
282
+ return 0
283
+
284
+
234
285
  def _show(hst, cfg, as_json: bool) -> int:
235
286
  h, _ = cfg.get()
236
287
  items = cfg.items()
288
+ decl = cfg.describe()
237
289
  st = cfg.state()
238
290
  kinds = {i: _name(k) for i, k, _ in core.transports(hst)}
239
291
  if as_json:
240
- def plain(o):
241
- return {k: (v.hex() if isinstance(v, bytes) else v) for k, v in vars(o).items()} if hasattr(o, "__dict__") \
242
- else list(o)
243
- out = {"hash": h, "items": [dict(type=type(i).__name__, **plain(i)) if hasattr(i, "__dict__") else plain(i)
292
+ out = {"hash": h, "items": [dict(type=type(i).__name__, **_plain(i)) if hasattr(i, "__dict__") else _plain(i)
244
293
  for i in items],
245
- "state": {**{k: v for k, v in vars(st).items() if k not in ("slots", "binds")},
246
- "slots": [plain(s) for s in st.slots], "binds": [plain(b) for b in st.binds]},
247
- "transports": kinds}
294
+ "declared": _plain(decl), "state": _state_dict(st), "transports": kinds}
248
295
  print(json.dumps(out, indent=2, default=lambda o: o.hex() if isinstance(o, bytes) else str(o)))
249
296
  return 0
250
- print(f"storage: {st.storage} ({st.storage_bytes} bytes), saved hash 0x{st.saved_hash:08x}; now 0x{h:08x}")
297
+ why = f" ({st.unreadable})" if st.unreadable else ""
298
+ print(f"storage: {st.storage}{why} ({decl.storage_bytes} bytes), saved hash 0x{st.saved_hash:08x}; now 0x{h:08x}")
251
299
  print("transports: " + ", ".join(f"{i} {k}" for i, k in kinds.items()))
252
300
  by_slot = {s.slot: s for s in st.slots}
253
- print(f"slots (up to {st.slots_max}):")
301
+ print(f"slots (up to {decl.slots_max}):")
254
302
  for it in items:
255
303
  if isinstance(it, config.Slot):
256
304
  s = by_slot.get(it.slot)
257
305
  pins = f"{it.pins[0]}" if it.pins[1] == 0xFFFF else f"{it.pins[0]},{it.pins[1]}"
258
- retry = f" retry {it.retry_s} s" if it.attach == "at-boot" else ""
306
+ retry = f" retry {it.retry_s:g} s" if it.attach == "at-boot" else ""
259
307
  retry += (f" max {it.max_speed} Hz" if it.max_speed else "") + (" idle-low" if it.idle_clock == "low" else "")
260
308
  lock = (f" lock {int.from_bytes(it.lock[1], 'little'):08x}:{int.from_bytes(it.lock[2], 'little'):08x}"
261
309
  if it.lock else "")
262
310
  live = ""
263
311
  if s:
264
- tried = "never tried" if s.last_try_ms is None else f"tried {s.last_try_ms} ms ago"
312
+ tried = "never tried" if s.last_try_at_ns is None else f"tried at {s.last_try_at_ns / 1e9:.3f} s"
265
313
  tid = f" target_id {s.target_id[::-1].hex()}" if s.target_id else ""
266
314
  live = f" -> {s.state}" + (f" (connection {s.connection})" if s.connection else f" ({tried})") + tid
267
315
  print(f" {it.slot} {it.name}: fn {it.wire_fn} pins {pins} {it.attach}{retry} {it.mechanism}{lock}{live}")
268
316
  by_port = {b.port: b for b in st.binds}
269
- print(f"binds (modes: {', '.join(st.bind_modes) or '-'}):")
317
+ print(f"binds (modes: {', '.join(decl.bind_modes) or '-'}):")
270
318
  names = {it.slot: it.name for it in items if isinstance(it, config.Slot)}
271
319
  for it in items:
272
320
  if isinstance(it, config.Bind):
oep_client/arm.py CHANGED
@@ -29,17 +29,19 @@ class SwdWire(WireBase):
29
29
  self.existing = False
30
30
 
31
31
  def attach(self, targetsel: int | None = None, max_speed: int | None = None,
32
- pins: tuple[int, int] | None = None) -> tuple[int, int, bool]:
33
- """-> (connection, DPIDR, woke from dormant). targetsel (multidrop) and max_speed go as critical TLVs: a probe
34
- that cannot honour them refuses. self.existing: the wire was attached already (its connection returned)."""
35
- body = self._speed_tlv(max_speed) + self._pins_tlv(pins)
32
+ pins: tuple[int, int] | None = None, reset: tuple[int, int] | None = None) -> tuple[int, int, bool]:
33
+ """-> (connection, DPIDR, woke from dormant). The request is method 0 (swd has no halting attach) and TLVs:
34
+ max_speed (required, critical; None: the wire's declared max_clock_hz), targetsel (multidrop, critical), pins,
35
+ reset = (channel, hold_ms) (critical). self.existing: the wire was attached already (its connection returned).
36
+ self.flags: the attach_flags byte."""
37
+ body = bytes([0]) + self._speed_tlv(self._speed_or_default(max_speed)) + self._pins_tlv(pins) + self._reset_tlv(reset)
36
38
  if targetsel is not None:
37
39
  body += m.tlv(self.TAG_TARGETSEL, struct.pack("<I", targetsel), critical=True)
38
40
  rd = m.Reader(self._call(self.ATTACH, body).payload)
39
- conn, dpidr, flags, self.speed_hz = rd.take("HIBI")
40
- self.existing = bool(flags & 2)
41
+ conn, dpidr, self.flags, self.speed_hz = rd.take("HIBI")
42
+ self.existing = bool(self.flags & self.FLAGS["existing"])
41
43
  rd.tail()
42
- return conn, dpidr, bool(flags & 1)
44
+ return conn, dpidr, bool(self.flags & self.FLAGS["dormant_woken"])
43
45
 
44
46
 
45
47
  class AdiError(TargetError):
@@ -82,8 +84,8 @@ class ArmAdi(Interface):
82
84
  reads = transfer_reads(steps)
83
85
  r = self._request(self.TRANSFER, struct.pack("<H", len(reads)) + steps)
84
86
  rd = ran(r)
85
- done, status, self.last_ack = rd.take("HBB")
86
- values = rd.words(sum(reads[:done]))
87
+ done, status, self.last_ack, nvals = rd.take("HBBH") # nvals: the values the answer carries (debug §6)
88
+ values = rd.words(nvals)
87
89
  rd.tail()
88
90
  if status != OK or not r.succeeded or done != len(reads):
89
91
  raise AdiError(f"transfer (ack {self.last_ack:#x})", status, r, done=done, values=values)