oep-client-python 0.0.37__py3-none-any.whl → 0.0.39__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.37"
5
+ __version__ = "0.0.39"
oep_client/__main__.py CHANGED
@@ -1,9 +1,9 @@
1
1
  """The oep command: what a probe offers (dump) and its settings (settings, core §13).
2
2
 
3
3
  oep dump --port /run/board-identify/by-id/<probe> oep dump --virtual p4-x035 --prefix oep.target --json
4
- oep settings show <probe> (every value with its origin, the storage state; lock-free)
4
+ oep settings show <probe> (every value, the storage state; lock-free)
5
5
  oep settings items <probe> (what each owner declares: names, forms, choices)
6
- oep settings set <probe> core channel.GPIO23.label=nrst channel.GPIO20.idle=pull-up [--save]
6
+ oep settings set <probe> core channel.GPIO23.label=v003.nrst channel.GPIO20.idle=pull-up [--save]
7
7
  oep settings unset <probe> core channel.GPIO20.idle oep settings unset --all <probe> --save (empties the storage)
8
8
  oep settings save <probe> oep settings state <probe> oep settings network <probe>
9
9
  oep settings export <probe> jig.oep-settings.json oep settings import <probe> jig.oep-settings.json [--save]
@@ -336,18 +336,18 @@ def _settings_parser(sub) -> None:
336
336
  c = sub.add_parser("settings", help="the probe's settings (core §13): show, set / unset by name, save, export / "
337
337
  "import a settings file, the network")
338
338
  cs = c.add_subparsers(dest="action", required=True)
339
- show = cs.add_parser("show", help="every value (category, name, value, origin) and the storage state (lock-free)")
339
+ show = cs.add_parser("show", help="every value (category, name, value) and the storage state (lock-free)")
340
340
  show.add_argument("probe")
341
341
  show.add_argument("--json", action="store_true")
342
342
  items = cs.add_parser("items", help="what the probe declares: each category's items, forms and choices")
343
343
  items.add_argument("probe")
344
344
  items.add_argument("--json", action="store_true")
345
- st = cs.add_parser("set", help="values by name in one category: NAME=VALUE ... (core channel.GPIO2.label=nrst)")
345
+ st = cs.add_parser("set", help="values by name in one category: NAME=VALUE ... (core channel.GPIO2.label=v003.nrst)")
346
346
  st.add_argument("probe")
347
347
  st.add_argument("category", help="core, or an interface's name/instance (oep.fixture.uart/0)")
348
348
  st.add_argument("values", nargs="+", metavar="NAME=VALUE")
349
349
  st.add_argument("--save", action="store_true")
350
- un = cs.add_parser("unset", help="values by name back to their default (or none)")
350
+ un = cs.add_parser("unset", help="remove values by name (the item has none after)")
351
351
  un.add_argument("probe")
352
352
  un.add_argument("category", nargs="?")
353
353
  un.add_argument("names", nargs="*")
@@ -360,10 +360,10 @@ def _settings_parser(sub) -> None:
360
360
  net = cs.add_parser("network", help="the network now: the TCP port, each netif's state and address (lock-free)")
361
361
  net.add_argument("probe")
362
362
  net.add_argument("--json", action="store_true")
363
- ex = cs.add_parser("export", help="write the user's values to a settings file (JSON; secrets left out)")
363
+ ex = cs.add_parser("export", help="write every value to a settings file (JSON; a secret as present only)")
364
364
  ex.add_argument("probe")
365
365
  ex.add_argument("file", help="the file to write, - for stdout")
366
- im = cs.add_parser("import", help="make the probe's user values a settings file's (shows what changes first)")
366
+ im = cs.add_parser("import", help="make the probe's values a settings file's (shows what changes first)")
367
367
  im.add_argument("probe")
368
368
  im.add_argument("file")
369
369
  im.add_argument("--yes", action="store_true", help="apply without asking")
@@ -520,16 +520,16 @@ def _show(st, as_json: bool) -> int:
520
520
  state = st.state() if st.offers("state") else None
521
521
  if as_json:
522
522
  print(json.dumps({"generation": gen, "state": vars(state) if state else None,
523
- "values": [dict(category=c, name=n, value=v, origin=o) for c, n, v, o in rows]}, indent=2))
523
+ "values": [dict(category=c, name=n, value=v) for c, n, v in rows]}, indent=2))
524
524
  return 0
525
525
  if state is not None:
526
526
  why = f" ({state.reason})" if state.reason else ""
527
527
  print(f"storage: {state.storage}{why}, saved generation {state.saved_generation}; now {gen}")
528
528
  else:
529
529
  print(f"no storage; generation {gen}")
530
- for c, n, v, o in rows:
530
+ for c, n, v in rows:
531
531
  shown = "(secret)" if isinstance(v, dict) else json.dumps(v)
532
- print(f" {c} {n} = {shown}" + ("" if o == "user" else f" ({o})"))
532
+ print(f" {c} {n} = {shown}")
533
533
  return 0
534
534
 
535
535
 
oep_client/core.py CHANGED
@@ -170,23 +170,6 @@ def require(hst: h.Host, fn: int, op: int) -> None:
170
170
  raise not_offered(fn, op)
171
171
 
172
172
 
173
- def firmware_labels(hst: h.Host) -> list[tuple[int, str]]:
174
- """The labels the firmware fixes (channel.{}.label of origin fixed, core §13.4) as (channel, text), in get's order.
175
- A probe without the settings: none."""
176
- from . import settings
177
- if not offers(hst, 0, settings.OP["get"]):
178
- return []
179
- st = settings.Settings(hst)
180
- cat = st.catalog()
181
- return [(v.key, m.shown(v.data)) for v in st.get()[1] if v.fn == 0 and v.origin == "fixed"
182
- and (cat.item(0, v.number) or settings.Item(0, 0, "", "", "", 0, 0, 0)).name == settings.LABEL]
183
-
184
-
185
- def probe_labels(hst: h.Host) -> dict[str, int]:
186
- """The firmware's fixed channel labels: {"NRST": 23, ...}."""
187
- return {text: ch for ch, text in firmware_labels(hst)}
188
-
189
-
190
173
  def max_op_ms(hst: h.Host) -> int:
191
174
  """The longest one request may take on this probe (fn 0's describe max_op_ms, core §7.5): the ceiling of run's
192
175
  timeout_ms, a dmi list's waits, an attach's hold_ms, and the argument time of attach, scan and riscv-dm's reset
oep_client/endpoint.py CHANGED
@@ -77,7 +77,8 @@ define (oep-spec a09fa45):
77
77
  oep.fixture.spi-target (§4: configure, arm, read_rx, status; errors at most 1 a transfer; `spi_transfer`),
78
78
  oep.fixture.logic / analog / capture-group (`virtual_bench_capture`)
79
79
  - the settings (core §13), fn 0's get / set / unset / save / state / network: each fn's items from its describe, the
80
- user's values by (fn, item, key), the firmware's fixed / default ones beside them (`firm`); a set checked whole (forms
80
+ values by (fn, item, key) with no origin, the initial values put in on a boot without a save (`initial`, core §13.4),
81
+ the labels unique but for ASCII case (§13.8); a set checked whole (forms
81
82
  malformed, what this probe cannot take unsupported with the value's item index, the whole against what runs); the
82
83
  generation counts the changes of one boot from 1; the save by category and item name, applied at boot one value at a
83
84
  time (disable first, then idle): what fails is left out (partial, reason 2); a disabled channel is refused everywhere
@@ -118,7 +119,7 @@ READ_FROM = reg.COMMON.enum["read_from"] # position, oldest, no
118
119
  STEP_LEFT = _RV.tlv["step_answer"]["step_left"] # step's answer: the hart could not be halted again (§4.2)
119
120
  # the settings (core §13): fn 0's ops, the items' forms, key kinds and origins, the core's and the wires' choices
120
121
  _SET_OP = reg.CORE.op
121
- FORM, KEY_KIND, ORIGIN = reg.CORE.enum["setting_form"], reg.CORE.enum["setting_key_kind"], reg.CORE.enum["setting_origin"]
122
+ FORM, KEY_KIND = reg.CORE.enum["setting_form"], reg.CORE.enum["setting_key_kind"]
122
123
  SETTING_SECRET = reg.CORE.enum["setting_flag"]["secret"]
123
124
  ITEM_TLV = reg.CORE.tlv["unsupported_payload"]["item"]
124
125
  IDLE_MODE = reg.CORE.enum["channel_idle"]
@@ -736,10 +737,10 @@ class Endpoint:
736
737
  # i2c-target internal pull-ups (fixture §3): features bit2, else none
737
738
  self.i2c_pullups = {fn: bool(self.target_decl[fn][1] & I2C_FEATURES["internal_pullups"])
738
739
  for fn, name in self.names.items() if name == _I2C.name}
739
- # the settings (core §13): each fn's items (its describe), the firmware's values (fixed / default), the netifs
740
+ # the settings (core §13): each fn's items (its describe), the firmware's initial values (§13.4), the netifs
740
741
  self.setting_items = {fn: setting_items(decl) for fn, decl in self.decl.items() if setting_items(decl)}
741
- self.firm: dict[tuple[int, str, int], tuple[int, bytes]] = {
742
- (fn, name, key): (origin, bytes(d)) for fn, name, key, origin, d in self.inner.get(0, {}).get("firmware_values", ())}
742
+ self.initial: dict[tuple[int, str, int], bytes] = {
743
+ (fn, name, key): bytes(d) for fn, name, key, d in self.inner.get(0, {}).get("initial_values", ())}
743
744
  # the network (transports §7): the netifs, a named list (numbers from 1); around the bench: Wi-Fi access points
744
745
  # (ssid -> passphrase), the Ethernet cable, what DHCP gives; every drop and reconnect of a netif (a value in use changed)
745
746
  self.netif_names = [v.decode() for tag, v in self.decl.get(0, ()) if tag == virtual_bench.NETIF]
@@ -858,8 +859,10 @@ class Endpoint:
858
859
  self.speed_frame_busy = False # TEST HOOK: a frame on the raised port is not finished yet
859
860
  if self.saved is not None:
860
861
  self._apply_saved() # disable before idle (core §13.7)
861
- elif self.saved_unreadable:
862
- self.storage_state, self.storage_reason = 2, 1 # nothing applied: state 2, reason 1 at the next boot (§3)
862
+ else:
863
+ if self.saved_unreadable:
864
+ self.storage_state, self.storage_reason = 2, 1 # nothing applied: state 2, reason 1 at the next boot (§3)
865
+ self._initial_values() # no save to use: the initial values (core §13.4)
863
866
  self._park(self._boot_channels()) # every free pin's idle state before the first answer (core §8)
864
867
 
865
868
  def _own(self, fn: int, tag: int, fmt: str, default: int) -> int:
@@ -3263,8 +3266,8 @@ class Endpoint:
3263
3266
  return (tx + bytes(n))[:n]
3264
3267
 
3265
3268
  # ---- the settings (core §13) ----------------------------------------------------------------------
3266
- # The items each fn's describe declares (setting, setting_choice); the user's values as (fn, item, key) -> data,
3267
- # the firmware's (fixed / default) beside them; fn 0's ops get / set / unset / save / state / network.
3269
+ # The items each fn's describe declares (setting, setting_choice); the values as (fn, item, key) -> data (no origin,
3270
+ # §13.4: the initial values go in on a boot without a save); fn 0's ops get / set / unset / save / state / network.
3268
3271
  def _items(self, fn: int) -> list[SettingItem]:
3269
3272
  return self.setting_items.get(fn, [])
3270
3273
 
@@ -3273,18 +3276,43 @@ class Endpoint:
3273
3276
  return next((k for k, it in enumerate(self._items(fn), 1) if it.name == name), 0)
3274
3277
 
3275
3278
  def _value(self, fn: int, name: str, key: int) -> bytes | None:
3276
- """The value now of an item (the user's, else the firmware's)."""
3277
- v = self.config.get((fn, name, key))
3278
- if v is None and (fn, name, key) in self.firm:
3279
- v = self.firm[(fn, name, key)][1]
3280
- return v
3279
+ """The value now of an item."""
3280
+ return self.config.get((fn, name, key))
3281
3281
 
3282
3282
  def _values_of(self, name: str, fn: int | None = None, config: dict | None = None) -> dict[tuple[int, int], bytes]:
3283
- """Every value of item `name` (of fn `fn`, else of any fn): (fn, key) -> data, the user's over the firmware's."""
3284
- out = {(f, k): v for (f, n, k), (_, v) in self.firm.items() if n == name and fn in (None, f)}
3285
- out.update({(f, k): v for (f, n, k), v in (self.config if config is None else config).items()
3286
- if n == name and fn in (None, f)})
3287
- return out
3283
+ """Every value of item `name` (of fn `fn`, else of any fn): (fn, key) -> data."""
3284
+ return {(f, k): v for (f, n, k), v in (self.config if config is None else config).items() if n == name and fn in (None, f)}
3285
+
3286
+ @staticmethod
3287
+ def _fold(d: bytes) -> bytes:
3288
+ return d.translate(bytes.maketrans(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ", b"abcdefghijklmnopqrstuvwxyz"))
3289
+
3290
+ def _label_clash(self, config: dict) -> int | None:
3291
+ """The later channel of two whose labels are equal but for ASCII case (core §13.8), else None."""
3292
+ seen: dict[bytes, int] = {}
3293
+ for (_, ch), d in sorted(self._values_of("channel.{}.label", 0, config).items()):
3294
+ if self._fold(d) in seen:
3295
+ return ch
3296
+ seen[self._fold(d)] = ch
3297
+ return None
3298
+
3299
+ def _initial_values(self) -> None:
3300
+ """A boot without a save (core §13.4): the initial values, checked as a set's; ones that do not pass (the
3301
+ firmware's mistake) stay out."""
3302
+ values = {}
3303
+ for (fn, name, key), d in self.initial.items():
3304
+ number = self._item_number(fn, name)
3305
+ if not number:
3306
+ continue
3307
+ it = self._items(fn)[number - 1]
3308
+ try:
3309
+ self._value_form(it, key, fn, d)
3310
+ except Reject:
3311
+ continue
3312
+ if not self._value_refused(fn, it, key, d):
3313
+ values[(fn, name, key)] = d
3314
+ if self._label_clash(values) is None:
3315
+ self.config = values
3288
3316
 
3289
3317
  def _key_ok(self, fn: int, it: SettingItem, key: int) -> bool:
3290
3318
  kind = it.key_kind
@@ -3325,12 +3353,10 @@ class Endpoint:
3325
3353
  raise Reject(m.MALFORMED)
3326
3354
 
3327
3355
  def _value_refused(self, fn: int, it: SettingItem, key: int, d: bytes) -> bool:
3328
- """Whether this probe cannot take the value (unsupported, core §13.3): a key it has no value for, a fixed value
3329
- (§13.4), a choice it does not declare, a channel or fn it does not have, and what the item's owner refuses."""
3356
+ """Whether this probe cannot take the value (unsupported, core §13.3): a key it has no value for, a choice it does
3357
+ not declare, a channel or fn it does not have, and what the item's owner refuses."""
3330
3358
  if not self._key_ok(fn, it, key):
3331
3359
  return True
3332
- if (fn, it.name, key) in self.firm and self.firm[(fn, it.name, key)][0] == ORIGIN["fixed"]:
3333
- return True
3334
3360
  if not d:
3335
3361
  return False # a secret kept
3336
3362
  if it.form == FORM["enum"] and d[0] not in it.choices:
@@ -3390,14 +3416,9 @@ class Endpoint:
3390
3416
  rows = []
3391
3417
  for fn in sorted(self.setting_items):
3392
3418
  for number, it in enumerate(self._items(fn), 1):
3393
- keys = sorted({k for (f, n, k) in self.config if f == fn and n == it.name}
3394
- | {k for (f, n, k) in self.firm if f == fn and n == it.name})
3395
- for k in keys:
3396
- user = self.config.get((fn, it.name, k))
3397
- origin, d = (ORIGIN["user"], user) if user is not None else self.firm[(fn, it.name, k)]
3398
- if it.flags & SETTING_SECRET:
3399
- d = b"" # a secret is never read (§13.5)
3400
- rows.append(struct.pack("<HBHBH", fn, number, k, origin, len(d)) + d)
3419
+ for k in sorted(k for (f, n, k) in self.config if f == fn and n == it.name):
3420
+ d = b"" if it.flags & SETTING_SECRET else self.config[(fn, it.name, k)] # a secret is never read
3421
+ rows.append(struct.pack("<HBHH", fn, number, k, len(d)) + d) # fn number key len data
3401
3422
  budget = self.probe.max_frame - m.RESULT_HEADER - 6
3402
3423
  out, sent = b"", 0
3403
3424
  for row in rows[first:]:
@@ -3432,7 +3453,7 @@ class Endpoint:
3432
3453
  values = self._resolve(self._read_values(t, t.take("B"), False))
3433
3454
  t.tail()
3434
3455
  for i, (fn, it, key, _) in enumerate(values):
3435
- if it is None or ((fn, it.name, key) in self.firm and self.firm[(fn, it.name, key)][0] == ORIGIN["fixed"]):
3456
+ if it is None:
3436
3457
  raise self._unsupported_item(i)
3437
3458
  new = dict(self.config)
3438
3459
  for fn, it, key, _ in values:
@@ -3553,6 +3574,9 @@ class Endpoint:
3553
3574
  return
3554
3575
  if self.generation == 0xFFFFFFFF:
3555
3576
  raise unavailable("limit") # no generation left before a restart (§13.5)
3577
+ clash = self._label_clash(new)
3578
+ if clash is not None:
3579
+ raise unavailable("held_by_settings", clash) # two labels equal but for case (§13.8)
3556
3580
  disabled = {k for (f, k), v in self._values_of("channel.{}.disable", 0, new).items() if v == b"\x01"}
3557
3581
  idles = self._values_of("channel.{}.idle", 0, new)
3558
3582
  outputs = (IDLE_MODE["low"], IDLE_MODE["high"])
@@ -3604,7 +3628,8 @@ class Endpoint:
3604
3628
 
3605
3629
  def _apply_saved(self) -> None:
3606
3630
  """At boot (core §13.7): every saved value, by name, checked by itself against this firmware - disable first,
3607
- then idle (a channel with both keeps the disable), then the rest; one it cannot take (a category or item it does
3631
+ then idle (a channel with both keeps the disable), then the rest; of two labels equal but for case the lower
3632
+ channel's; one it cannot take (a category or item it does
3608
3633
  not have, a form or value it refuses) is left out and the rest applied (storage_state 3 partial, reason 2)."""
3609
3634
  def rank(kv):
3610
3635
  (ident, name, key), _ = kv
@@ -3626,6 +3651,8 @@ class Endpoint:
3626
3651
  ok = False
3627
3652
  if ok and name == "channel.{}.idle" and applied.get((0, "channel.{}.disable", key)) == b"\x01":
3628
3653
  ok = False # disable wins over idle on one channel
3654
+ if ok and name == "channel.{}.label" and self._label_clash({**applied, (fn, name, key): d}) is not None:
3655
+ ok = False # labels in key order: the lower channel's stays
3629
3656
  if ok:
3630
3657
  applied[(fn, name, key)] = d
3631
3658
  bad |= not ok
oep_client/pins.py CHANGED
@@ -21,8 +21,8 @@ Steps, each optional and each saying what it did:
21
21
  5. reset line, confirmed: attach under reset through each channel that stopped the activity (or, with nothing
22
22
  active, through each candidate) - the real line stops the hart at the reset vector.
23
23
  6. print the connection profile the host keeps (wire, pins, line settings: host guide §5.3 - the probe keeps no
24
- wiring) and the labels that name the lines found: `nrst` on the reset line (5) and `power_hi` on --power, or
25
- `<profile>.nrst` / `<profile>.power_hi` with --name (a probe serving several targets); --save writes the labels
24
+ wiring) and the labels that name the lines found: `<port>.nrst` on the reset line (5) and `<port>.power_hi` on
25
+ --power, the port being --name or the one port the probe's labels name; --save writes the labels
26
26
  (settings set + save). Nothing is written otherwise.
27
27
 
28
28
  Everything is bounded in time (the whole run under a minute) and every plan is released at the end. A plan_apply
@@ -562,10 +562,16 @@ class PinFinder:
562
562
  r.profile = ", ".join(parts)
563
563
  self.say(f"profile{' ' + self.name if self.name else ''}: {r.profile} (the host's to keep: the probe keeps "
564
564
  f"no wiring)")
565
- # the lines found are named by label (host guide §5.3): `nrst` - the reset line a host's attach under reset
566
- # looks up - and `power_hi`; `<profile>.<line>` when the probe serves several targets (--name)
567
- prefix = f"{self.name}." if self.name else ""
565
+ # the lines found are named by label (host guide §5.3): `<port>.nrst` - the reset line a host's attach under
566
+ # reset looks up - and `<port>.power_hi`; the port is --name, else the one port the probe's labels name
567
+ port = self.name or self._only_port()
568
568
  labels = []
569
+ if port is None:
570
+ if r.reset_channel is not None or self.power is not None:
571
+ self.note("labels: name the port with --name (a line is named <port>.<line>, host guide §5.3)")
572
+ r.labels = labels
573
+ return
574
+ prefix = f"{port}."
569
575
  if r.reset_channel is not None:
570
576
  labels.append((r.reset_channel, f"{prefix}nrst"))
571
577
  if self.power is not None:
@@ -585,6 +591,14 @@ class PinFinder:
585
591
  r.saved = True
586
592
  self.say(" labels written and saved")
587
593
 
594
+ def _only_port(self) -> str | None:
595
+ """The one port the probe's labels name (None: none or several)."""
596
+ try:
597
+ ports = settings.ports_named(settings.labels(self.hst))
598
+ except (LookupError, h.OepError):
599
+ return None
600
+ return ports[0] if len(ports) == 1 else None
601
+
588
602
  def _name(self, channel: int) -> str:
589
603
  """A channel's name (fn 0's channel_name, core §7.5); its number when the probe names none."""
590
604
  try:
@@ -603,8 +617,8 @@ class PinFinder:
603
617
  relabelled = {ch for ch, _ in labels}
604
618
  after = [(ch, t) for ch, t in have if ch not in relabelled] + list(labels)
605
619
  for ch, text in labels:
606
- line = text.rpartition(".")[2]
607
- found = settings.find_line(after, line, self.name, single=self.name is None)
620
+ port, _, line = text.partition(".")
621
+ found = settings.find_line(after, line, port)
608
622
  if found != ch:
609
623
  clash = sorted(c for c, t in after if c != ch and settings.fold_name(t) == settings.fold_name(text))
610
624
  self.note(f"label {text}: the settings already name channel(s) {clash} the same way - a host would "
oep_client/registry.py CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  from types import SimpleNamespace as _NS
5
5
 
6
- REGISTRY_HASH = "be9417dc8aa85168"
6
+ REGISTRY_HASH = "2fdee123ed6fb422"
7
7
  SCHEMA = 1
8
8
  PROTOCOL_REVISION = 1
9
9
  CONFIRM_REQUEST_MAGIC = 'OEP?'
@@ -22,12 +22,12 @@ STATUS = {"ok": 0x00, "wait": 0x01, "line": 0x02, "fault": 0x03, "timeout": 0x04
22
22
  DESCRIBE_COMMON = {"role_channels": 0x01, "max_clock_hz": 0x02, "max_length": 0x03, "min_clock_hz": 0x04, "features": 0x05, "channel_group": 0x06, "ops": 0x07, "setting": 0x08, "setting_choice": 0x09}
23
23
  TIMING = {"probe_frame_gap_ms": 0xC8, "uart_bridge_boot_baud": 0x1C200, "port_speed_idle_ms": 0xBB8, "host_wait_add_ms": 0x3E8, "core_quick_op_max_ms": 0x3E8, "notify_pending_max_frames": 0x02, "port_speed_switch_wait_ms": 0x14}
24
24
  USB = {"project_vid": 0x1209, "project_pid": 0x4F45, "vendor_bulk_class": 0xFF, "vendor_bulk_subclass": 0x4F, "vendor_bulk_protocol": 0x45, "hid_usage_page": 0xFF4F, "hid_usage": 0x45}
25
- LIMITS = {"request_header_bytes": 0x0A, "result_header_bytes": 0x05, "corr_max": 0xFFFF, "corr_half_range": 0x8000, "resource_id_bytes": 0x04, "resource_id_max": 0xFFFFFFFF, "lease_min_ms": 0x3E8, "lease_max_ms": 0xEA60, "owner_max_bytes": 0x20, "unit_id_max_bytes": 0x20, "wifi_ssid_max_bytes": 0x20, "wifi_min_max_frame": 0x53, "label_max_bytes": 0x20, "setting_name_max_bytes": 0x30, "setting_choice_name_max_bytes": 0x10, "channel_name_max_bytes": 0x10, "setting_get_overhead_bytes": 0x13, "interface_name_max_bytes": 0x30, "port_speed_tolerance_pct": 0x02, "uart_baud_tolerance_pct": 0x05, "max_op_ms_max": 0x927C0}
25
+ LIMITS = {"request_header_bytes": 0x0A, "result_header_bytes": 0x05, "corr_max": 0xFFFF, "corr_half_range": 0x8000, "resource_id_bytes": 0x04, "resource_id_max": 0xFFFFFFFF, "lease_min_ms": 0x3E8, "lease_max_ms": 0xEA60, "owner_max_bytes": 0x20, "unit_id_max_bytes": 0x20, "wifi_ssid_max_bytes": 0x20, "wifi_min_max_frame": 0x52, "label_max_bytes": 0x20, "setting_name_max_bytes": 0x30, "setting_choice_name_max_bytes": 0x10, "channel_name_max_bytes": 0x10, "setting_get_overhead_bytes": 0x12, "interface_name_max_bytes": 0x30, "port_speed_tolerance_pct": 0x02, "uart_baud_tolerance_pct": 0x05, "max_op_ms_max": 0x927C0}
26
26
  COMMON = _NS(enum={"target_id_scheme": {"dmi_7f": 0x01, "dpidr": 0x02}, "read_from": {"position": 0x00, "oldest": 0x01, "now": 0x02, "last_mark": 0x03}, "read_flags": {"more": 0x01, "gap": 0x02}, "mark_kind": {"lost": 0x01, "clear": 0x02, "host": 0x03}, "mark_detail_lost": {"overflow": 0x01, "framing": 0x02, "parity": 0x03}})
27
27
 
28
28
  INTERFACES = {}
29
29
  CORE = _NS(op={"confirm": 0x01, "list": 0x02, "describe": 0x03, "clock": 0x04, "open": 0x10, "end": 0x11, "keepalive": 0x12, "lock_state": 0x13, "get": 0x20, "set": 0x21, "unset": 0x22, "save": 0x23, "state": 0x24, "network": 0x25}, lock_free={0x01, 0x02, 0x03, 0x04, 0x10, 0x13, 0x20, 0x24, 0x25},
30
- tlv={"confirm_answer": {"transport": 0x01}, "open": {"owner": 0x01}, "lock_state_answer": {"owner": 0x01}, "locked_payload": {"owner": 0x01}, "unavailable_payload": {"cause": 0x01, "channel": 0x02, "item": 0x03, "fn": 0x05}, "unsupported_payload": {"supported": 0x01, "channel": 0x02, "item": 0x03, "fn": 0x05}, "describe": {"firmware": 0x40, "model": 0x41, "unit_id": 0x42, "channels": 0x43, "channel_name": 0x44, "netif": 0x45, "transport": 0x49, "chip": 0x4C, "max_op_ms": 0x4D}}, event={}, enum={"unavailable_cause": {"pin_in_use": 0x01, "limit": 0x02, "storage_full": 0x03, "bound_in_group": 0x04, "held_by_settings": 0x05, "wrong_state": 0x06, "policy_denied": 0x07}, "transport_kind": {"uart_bridge": 0x01, "usb_cdc": 0x02, "usb_serial_jtag": 0x03, "vendor_bulk": 0x04, "hid": 0x05, "tcp": 0x06}, "route_kind": {"bridge": 0x01, "cdc": 0x02, "usj": 0x03, "bulk": 0x04, "hid": 0x05, "tcp": 0x06}, "setting_form": {"bytes": 0x01, "text": 0x02, "u32": 0x03, "ipv4": 0x04, "bool": 0x05, "enum": 0x06, "channel": 0x07, "transport": 0x08, "fn": 0x09}, "setting_key_kind": {"none": 0x00, "channel": 0x01, "index": 0x02, "transport": 0x03, "netif": 0x04}, "setting_flag": {"secret": 0x01, "restart": 0x02}, "setting_origin": {"fixed": 0x01, "default": 0x02, "user": 0x03}, "storage_state": {"none": 0x00, "applied": 0x01, "unreadable": 0x02, "partial": 0x03}, "storage_reason": {"none": 0x00, "form": 0x01, "item": 0x02, "capacity": 0x03}, "channel_idle": {"hi_z": 0x00, "pull_up": 0x01, "pull_down": 0x02, "low": 0x03, "high": 0x04}, "ipv4_mode": {"dhcp": 0x00, "static": 0x01}, "network_state": {"off": 0x00, "connecting": 0x01, "up": 0x02, "waiting": 0x03}, "network_reason": {"none": 0x00, "not_found": 0x01, "auth": 0x02, "no_address": 0x03, "other": 0x04}}, own={},
30
+ tlv={"confirm_answer": {"transport": 0x01}, "open": {"owner": 0x01}, "lock_state_answer": {"owner": 0x01}, "locked_payload": {"owner": 0x01}, "unavailable_payload": {"cause": 0x01, "channel": 0x02, "item": 0x03, "fn": 0x05}, "unsupported_payload": {"supported": 0x01, "channel": 0x02, "item": 0x03, "fn": 0x05}, "describe": {"firmware": 0x40, "model": 0x41, "unit_id": 0x42, "channels": 0x43, "channel_name": 0x44, "netif": 0x45, "transport": 0x49, "chip": 0x4C, "max_op_ms": 0x4D}}, event={}, enum={"unavailable_cause": {"pin_in_use": 0x01, "limit": 0x02, "storage_full": 0x03, "bound_in_group": 0x04, "held_by_settings": 0x05, "wrong_state": 0x06, "policy_denied": 0x07}, "transport_kind": {"uart_bridge": 0x01, "usb_cdc": 0x02, "usb_serial_jtag": 0x03, "vendor_bulk": 0x04, "hid": 0x05, "tcp": 0x06}, "route_kind": {"bridge": 0x01, "cdc": 0x02, "usj": 0x03, "bulk": 0x04, "hid": 0x05, "tcp": 0x06}, "setting_form": {"bytes": 0x01, "text": 0x02, "u32": 0x03, "ipv4": 0x04, "bool": 0x05, "enum": 0x06, "channel": 0x07, "transport": 0x08, "fn": 0x09}, "setting_key_kind": {"none": 0x00, "channel": 0x01, "index": 0x02, "transport": 0x03, "netif": 0x04}, "setting_flag": {"secret": 0x01, "restart": 0x02}, "storage_state": {"none": 0x00, "applied": 0x01, "unreadable": 0x02, "partial": 0x03}, "storage_reason": {"none": 0x00, "form": 0x01, "item": 0x02, "capacity": 0x03}, "channel_idle": {"hi_z": 0x00, "pull_up": 0x01, "pull_down": 0x02, "low": 0x03, "high": 0x04}, "ipv4_mode": {"dhcp": 0x00, "static": 0x01}, "network_state": {"off": 0x00, "connecting": 0x01, "up": 0x02, "waiting": 0x03}, "network_reason": {"none": 0x00, "not_found": 0x01, "auth": 0x02, "no_address": 0x03, "other": 0x04}}, own={},
31
31
  line_names={'swdio': 'the debug data line (2 wires: rvswd, swd) (Host only)', 'swclk': 'the debug clock line (2 wires: rvswd, swd) (Host only)', 'swio': 'the one debug wire (swio) (Host only)', 'rx': 'the fixture UART receive, wired to the target TX (Host only)', 'tx': 'the fixture UART transmit, wired to the target RX (Host only)', 'nrst': 'the target reset line (Host only)', 'power_hi': 'target power on when high (Host only)', 'power_lo': 'target power on when low (Host only)'}, names={'netif': {'wifi': 'Wi-Fi station', 'ethernet': 'wired Ethernet'}}, settings={'channel.{}.label': "text 1-32, channel; the pin's label (NRST)", 'channel.{}.idle': 'enum channel_idle, channel; the idle state (core §8)', 'channel.{}.disable': 'bool, channel; true: the probe neither drives nor configures the pin', 'wifi.{}.ssid': "text 1-32, index (entry); the network's name", 'wifi.{}.passphrase': 'text 8-64, index (entry), secret; 8-63 printable passphrase or 64 hex digits', 'ipv4.{}.mode': 'enum ipv4_mode, netif; dhcp when absent', 'ipv4.{}.address': 'ipv4, netif; static address', 'ipv4.{}.prefix': 'u32 1-32, netif; static prefix length', 'ipv4.{}.gateway': 'ipv4, netif; static gateway, none when absent', 'ipv4.{}.dns': 'ipv4, netif; static DNS server, none when absent'})
32
32
  PROBE_PLAN = _NS(name="oep.probe.plan", revision=1, target=None, op={"plan_apply": 0x10, "plan_release": 0x11}, lock_free=set(),
33
33
  tlv={"plan_apply": {"role_assignment": 0x10}, "describe": {"plan_roles": 0x40}}, event={}, enum={}, own={},
oep_client/settings.py CHANGED
@@ -7,10 +7,10 @@ with storage, network with the network items). A value is (fn, item number, key)
7
7
  and the value with references as names - are what a host keeps, shows and writes to a file.
8
8
 
9
9
  st = settings.Settings(hst)
10
- st.show() # [(category, flat name, value, origin)]
10
+ st.show() # [(category, flat name, value)]
11
11
  st.set_named("core", {"channel.GPIO14.label": "NRST", "channel.GPIO14.idle": "pull-up"})
12
12
  st.save()
13
- doc = st.export() # the settings file (JSON), the user's values only
13
+ doc = st.export() # the settings file (JSON), every value
14
14
  plan = st.plan_import(doc); st.apply_import(plan)
15
15
 
16
16
  Secrets (core §13.5) are never read: get gives no data, the file a ``{"secret": true}``.
@@ -19,6 +19,7 @@ Secrets (core §13.5) are never read: get gives no data, the file a ``{"secret":
19
19
  from __future__ import annotations
20
20
 
21
21
  import ipaddress
22
+ import re
22
23
  import struct
23
24
  from dataclasses import dataclass, field
24
25
 
@@ -28,7 +29,6 @@ _C = reg.CORE
28
29
  OP = {k: _C.op[k] for k in ("get", "set", "unset", "save", "state", "network")}
29
30
  FORM = {v: k for k, v in _C.enum["setting_form"].items()}
30
31
  KEY_KIND = {v: k for k, v in _C.enum["setting_key_kind"].items()}
31
- ORIGIN = {v: k for k, v in _C.enum["setting_origin"].items()}
32
32
  SECRET = _C.enum["setting_flag"]["secret"]
33
33
  STORAGE_STATE = {v: k for k, v in _C.enum["storage_state"].items()}
34
34
  STORAGE_REASON = {v: k for k, v in _C.enum["storage_reason"].items()}
@@ -37,8 +37,7 @@ NET_REASON = {v: k for k, v in _C.enum["network_reason"].items()}
37
37
  ROUTE = {v: k for k, v in _C.enum["route_kind"].items()} # a transport kind's short name (core §13.3)
38
38
  _D = _C.tlv["describe"]
39
39
  SETTING, SETTING_CHOICE = reg.DESCRIBE_COMMON["setting"], reg.DESCRIBE_COMMON["setting_choice"]
40
- VALUE_HEADER = 8 # fn number key origin len (core §13.5)
41
- SET_HEADER = 7 # fn number key len
40
+ VALUE_HEADER = 7 # fn number key len, get's and set's alike (core §13.5)
42
41
  FILE_VERSION = 1 # the settings file's form (oep_settings)
43
42
  CORE_CATEGORY = "core"
44
43
 
@@ -63,11 +62,10 @@ class Item:
63
62
 
64
63
  @dataclass
65
64
  class Value:
66
- """One value as get gives it."""
65
+ """One value as get gives it (no origin: core §13.4)."""
67
66
  fn: int
68
67
  number: int
69
68
  key: int
70
- origin: str
71
69
  data: bytes
72
70
 
73
71
 
@@ -264,22 +262,22 @@ def values_from(payload_pages) -> list[Value]:
264
262
  for raw in payload_pages:
265
263
  at = 0
266
264
  while at + VALUE_HEADER <= len(raw):
267
- fn, number, key, origin, n = struct.unpack_from("<HBHBH", raw, at)
268
- out.append(Value(fn, number, key, ORIGIN.get(origin, str(origin)), raw[at + VALUE_HEADER:at + VALUE_HEADER + n]))
265
+ fn, number, key, n = struct.unpack_from("<HBHH", raw, at)
266
+ out.append(Value(fn, number, key, raw[at + VALUE_HEADER:at + VALUE_HEADER + n]))
269
267
  at += VALUE_HEADER + n
270
268
  return out
271
269
 
272
270
 
273
271
  def split_values(data: bytes, count: int) -> tuple[list[Value], bytes]:
274
- """count values (fn number key origin len data) from get's answer, then the answer's own TLV tail."""
272
+ """count values (fn number key len data) from get's answer, then the answer's own TLV tail."""
275
273
  out, at = [], 0
276
274
  for _ in range(count):
277
275
  if at + VALUE_HEADER > len(data):
278
276
  raise m.ProtocolError("get: a value past the answer")
279
- fn, number, key, origin, n = struct.unpack_from("<HBHBH", data, at)
277
+ fn, number, key, n = struct.unpack_from("<HBHH", data, at)
280
278
  if at + VALUE_HEADER + n > len(data):
281
279
  raise m.ProtocolError("get: a value's data past the answer")
282
- out.append(Value(fn, number, key, ORIGIN.get(origin, str(origin)), data[at + VALUE_HEADER:at + VALUE_HEADER + n]))
280
+ out.append(Value(fn, number, key, data[at + VALUE_HEADER:at + VALUE_HEADER + n]))
283
281
  at += VALUE_HEADER + n
284
282
  return out, data[at:]
285
283
 
@@ -364,7 +362,7 @@ class Settings:
364
362
  return gen
365
363
 
366
364
  def save(self) -> int:
367
- """Save the user's values (a probe with storage). -> the current generation (completed failed raises: `state`
365
+ """Save the values (a probe with storage). -> the current generation (completed failed raises: `state`
368
366
  says what is kept). Its argument time is max_op_ms (core §13.7)."""
369
367
  return self._generation(self._call("save", expect_ms=core.max_op_ms(self.host)))
370
368
 
@@ -394,8 +392,8 @@ class Settings:
394
392
  return Network(port, out)
395
393
 
396
394
  # -- by name --
397
- def show(self) -> list[tuple[str, str, object, str]]:
398
- """Every value as (category, flat name, value, origin); a secret's value is {"secret": True}."""
395
+ def show(self) -> list[tuple[str, str, object]]:
396
+ """Every value as (category, flat name, value); a secret's value is {"secret": True}."""
399
397
  cat = self.catalog()
400
398
  out = []
401
399
  for v in self.get()[1]:
@@ -404,7 +402,7 @@ class Settings:
404
402
  continue
405
403
  flat = cat.flat(it, v.key) or f"{it.name}#{v.key}"
406
404
  out.append((cat.category.get(v.fn, str(v.fn)), flat,
407
- {"secret": True} if it.secret else cat.to_json(it, v.data), v.origin))
405
+ {"secret": True} if it.secret else cat.to_json(it, v.data)))
408
406
  return out
409
407
 
410
408
  def _named(self, cat: Catalog, category: str, flat: str) -> tuple[Item, int]:
@@ -432,10 +430,15 @@ class Settings:
432
430
  keys.append((it.fn, it.number, key))
433
431
  return self.unset(keys)
434
432
 
433
+ def changeable(self) -> bool:
434
+ """Whether the probe lets its values change: set / unset offered (a probe of initial values only has get alone,
435
+ core §13.5)."""
436
+ return self.offers("set") and self.offers("unset")
437
+
435
438
  def unset_all(self) -> int | None:
436
- """Every user value back to its default or none (then save to empty the storage: core §13.7)."""
437
- users = [(v.fn, v.number, v.key) for v in self.get()[1] if v.origin == "user"]
438
- return self.unset(users) if users else None
439
+ """Every value away (then save to empty the storage: core §13.7). None: there was none."""
440
+ keys = [(v.fn, v.number, v.key) for v in self.get()[1]]
441
+ return self.unset(keys) if keys else None
439
442
 
440
443
  # -- the file (settings file §3, §4) --
441
444
  def export(self) -> dict:
@@ -455,11 +458,9 @@ class Settings:
455
458
 
456
459
 
457
460
  def export_file(cat: Catalog, values: list[Value]) -> dict:
458
- """The settings file of the user's values (settings file §3)."""
461
+ """The settings file of every value (settings file §3)."""
459
462
  out: dict = {}
460
463
  for v in values:
461
- if v.origin != "user":
462
- continue # fixed and default stay with the firmware
463
464
  it = cat.item(v.fn, v.number)
464
465
  flat = cat.flat(it, v.key) if it is not None else None
465
466
  if flat is None:
@@ -477,7 +478,7 @@ def check_file(doc) -> None:
477
478
 
478
479
 
479
480
  def plan_import(cat: Catalog, current: list[Value], doc: dict) -> ImportPlan:
480
- """What making the file's user values the probe's takes (settings file §4)."""
481
+ """What making the file's values the probe's takes (settings file §4)."""
481
482
  check_file(doc)
482
483
  plan = ImportPlan()
483
484
  probe = doc.get("probe", {})
@@ -502,55 +503,61 @@ def plan_import(cat: Catalog, current: list[Value], doc: dict) -> ImportPlan:
502
503
  plan.unresolved.append((category, flat))
503
504
  continue
504
505
  now = have.get((it.fn, it.number, key))
505
- if now is None or now.origin != "user" or now.data != data:
506
+ if now is None or now.data != data:
506
507
  plan.sets.append((it.fn, it.number, key, data))
507
- plan.unsets = [(v.fn, v.number, v.key) for v in current if v.origin == "user" and (v.fn, v.number, v.key) not in wanted]
508
+ plan.unsets = [(v.fn, v.number, v.key) for v in current if (v.fn, v.number, v.key) not in wanted]
508
509
  return plan
509
510
 
510
511
 
511
- # ---- the label convention (host guide §5.3) ---------------------------------------------------------------
512
- # The line names: `nrst` a target's reset, `power_hi` high powers it, `power_lo` low powers it. With several targets,
513
- # `<port>.<name>`.
512
+ # ---- ports and lines by label (host guide §5.3) -----------------------------------------------------------
513
+ # A label "S.N" names port S's line N: S 1-16 of letters, digits and '-', a letter first; N letters, digits, '-' and '_';
514
+ # one '.' between. Other labels are a pin's names only. Kept as written, compared ignoring ASCII case; a probe keeps its
515
+ # labels unique that way (core §13.8). Line names: `swdio`, `swclk`, `swio`, `rx`, `tx`, `nrst`, `power_hi`, `power_lo`.
514
516
  LINE_NAMES = tuple(_C.line_names)
515
517
  _ASCII_LOWER = str.maketrans("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz")
516
518
  LABEL = "channel.{}.label"
519
+ _PORT = re.compile(r"[A-Za-z][A-Za-z0-9-]{0,15}")
520
+ _LINE = re.compile(r"[A-Za-z0-9_-]+")
517
521
 
518
522
 
519
523
  def fold_name(text: str) -> str:
520
- """ASCII case folded (only A-Z: the convention compares ignoring ASCII case)."""
524
+ """ASCII case folded (only A-Z): names compare ignoring ASCII case."""
521
525
  return text.translate(_ASCII_LOWER)
522
526
 
523
527
 
528
+ def port_line(text: str) -> tuple[str, str] | None:
529
+ """(S, N) of a label that names a port's line, as written; None for any other label."""
530
+ port, dot, line = text.partition(".")
531
+ if not dot or not _PORT.fullmatch(port) or not _LINE.fullmatch(line):
532
+ return None
533
+ return port, line
534
+
535
+
524
536
  def ports_named(labels) -> list[str]:
525
- """The ports the labels name (host guide §5.3): the distinct S of the "S.N" labels, folded, in order."""
526
- out = []
537
+ """The ports the labels name (host guide §5.3): each S once (ignoring case), as written, in order."""
538
+ out: list[str] = []
527
539
  for _, text in labels:
528
- port, dot, rest = text.partition(".")
529
- if dot and port and rest and fold_name(port) not in out:
530
- out.append(fold_name(port))
540
+ pl = port_line(text)
541
+ if pl and fold_name(pl[0]) not in {fold_name(p) for p in out}:
542
+ out.append(pl[0])
531
543
  return out
532
544
 
533
545
 
534
- def line_from_labels(labels, name: str, profile: str | None = None, single: bool | None = None) -> int | None:
535
- """Host guide §5.3 on bare data: labels as (channel, text) - every channel.{}.label value, whatever its origin. In
536
- order, stopping at the first step that finds exactly one channel: (a) with a port name (`profile`), a label equal
537
- to `<port>.<name>`; (b) only while at most one port is named (`single`; by default from the labels), a label equal
538
- to `name`. A step that finds two or more ends the search with none. Texts compare ignoring ASCII case."""
539
- if single is None:
540
- single = len(ports_named(labels)) <= 1
541
- steps = ([f"{profile}.{name}"] if profile is not None else []) + ([name] if single else [])
542
- for text in steps:
543
- found = {ch for ch, t in labels if fold_name(t) == fold_name(text)}
544
- if len(found) > 1:
546
+ def line_from_labels(labels, name: str, port: str | None = None) -> int | None:
547
+ """The channel of port `port`'s line `name` among labels as (channel, text), or None (host guide §5.3). Without a
548
+ port, the only port the labels name (a tool's shortcut when exactly one is there). Compared ignoring ASCII case."""
549
+ if port is None:
550
+ ports = ports_named(labels)
551
+ if len(ports) != 1:
545
552
  return None
546
- if found:
547
- return found.pop()
548
- return None
553
+ port = ports[0]
554
+ found = {ch for ch, text in labels
555
+ if (pl := port_line(text)) and fold_name(pl[0]) == fold_name(port) and fold_name(pl[1]) == fold_name(name)}
556
+ return found.pop() if len(found) == 1 else None
549
557
 
550
558
 
551
559
  def labels(hst) -> list[tuple[int, str]]:
552
- """Every channel label now (channel.{}.label, fixed / default / user) as (channel, text). A probe without the
553
- settings ops: none."""
560
+ """Every channel label now (channel.{}.label) as (channel, text). A probe without the settings ops: none."""
554
561
  if not core.offers(hst, 0, OP["get"]):
555
562
  return []
556
563
  st = Settings(hst)
@@ -563,8 +570,8 @@ def labels(hst) -> list[tuple[int, str]]:
563
570
  return out
564
571
 
565
572
 
566
- def find_line(source, name: str, profile: str | None = None, single: bool | None = None) -> int | None:
567
- """The channel of the line `name` (LINE_NAMES) by the label convention (host guide §5.3), or None. source: a Host
568
- (its labels read with get, no lock) or labels as (channel, text)."""
573
+ def find_line(source, name: str, port: str | None = None) -> int | None:
574
+ """The channel of the line `name` (LINE_NAMES) of port `port` by the labels (host guide §5.3), or None. source: a
575
+ Host (its labels read with get, no lock) or labels as (channel, text)."""
569
576
  found = list(source) if isinstance(source, (list, tuple)) else labels(source)
570
- return line_from_labels(found, name, profile, single)
577
+ return line_from_labels(found, name, port)
@@ -276,7 +276,6 @@ def _transports(kinds: list[tuple[int, int]]) -> tuple[bytes, ...]:
276
276
  # ---- the settings (core §13) -------------------------------------------------------------------------------
277
277
  SETTING, SETTING_CHOICE = reg.DESCRIBE_COMMON["setting"], reg.DESCRIBE_COMMON["setting_choice"]
278
278
  FORM, KEY_KIND, FLAG = reg.CORE.enum["setting_form"], reg.CORE.enum["setting_key_kind"], reg.CORE.enum["setting_flag"]
279
- ORIGIN = reg.CORE.enum["setting_origin"]
280
279
  SETTINGS_OPS = ("get", "set", "unset") # fn 0's ops with settings; save / state with storage, network with netifs
281
280
 
282
281
 
@@ -345,25 +344,25 @@ def channel_name(channel: int) -> str:
345
344
 
346
345
 
347
346
  def with_settings(offered: list[Offered], *, storage: bool = True, netifs=(), labels: dict[int, str] | None = None,
348
- defaults=()) -> list[Offered]:
347
+ initial=(), changeable: bool = True) -> list[Offered]:
349
348
  """A profile's fns with the settings (core §13): fn 0 offers get / set / unset (save / state with `storage`, network
350
349
  with `netifs`) and declares its channel items (the network's with `netifs`, a named list of netifs); each debug wire
351
- its hold (rvswd its rest too), each fixture UART and console its pass-through. `labels`: channel labels the firmware
352
- fixes; `defaults`: more firmware values as (fn, item, key, data) given by default (core §13.4)."""
350
+ its hold (rvswd its rest too), each fixture UART and console its pass-through. `labels`: channel labels as initial
351
+ values; `initial`: more initial values as (fn, item, key, data) (core §13.4: in on a boot without a save);
352
+ `changeable` False: a probe of initial values only, get alone (§13.5)."""
353
353
  out = []
354
354
  for o in offered:
355
355
  extra: tuple[Item, ...] = ()
356
356
  inner = o.inner
357
357
  if o.fn == CORE_FN:
358
- ops = [reg.CORE.op[k] for k in CORE_REQUIRED + SETTINGS_OPS]
359
- ops += [reg.CORE.op[k] for k in ("save", "state")] if storage else []
358
+ ops = [reg.CORE.op[k] for k in CORE_REQUIRED + (SETTINGS_OPS if changeable else ("get",))]
359
+ ops += [reg.CORE.op[k] for k in ("save", "state")] if storage and changeable else []
360
360
  ops += [reg.CORE.op["network"]] if netifs else []
361
361
  items = CORE_ITEMS + (NETWORK_ITEMS if netifs else ())
362
- firm = tuple((0, "channel.{}.label", ch, ORIGIN["fixed"], n.encode()) for ch, n in (labels or {}).items())
363
- firm += tuple((fn, item, key, ORIGIN["default"], data) for fn, item, key, data in defaults)
362
+ firm = tuple((0, "channel.{}.label", ch, n.encode()) for ch, n in (labels or {}).items()) + tuple(initial)
364
363
  out.append(Offered(o.fn, o.instance, o.name, (catalog.ops_tlv(ops),) + tuple(t for t in o.tlvs if t[0] != catalog.OPS)
365
364
  + tuple(catalog.tlv(NETIF, n.encode()) for n in netifs) + items_tlvs(items), o.revision,
366
- inner + (("firmware_values", firm),)))
365
+ inner + (("initial_values", firm),)))
367
366
  continue
368
367
  if o.name == "oep.wire.rvswd":
369
368
  extra = (REST, HOLD)
@@ -496,7 +495,7 @@ def p4_x035() -> VirtualProbe:
496
495
  _link(13),
497
496
  _plan(14),
498
497
  _restart(15),
499
- ], netifs=("ethernet",), labels={2: "SWDIO", 54: "SWCLK", 51: "LED"}), own_channels=own)
498
+ ], netifs=("ethernet",), labels={2: "x035.swdio", 54: "x035.swclk"}), own_channels=own)
500
499
 
501
500
 
502
501
  def esp32_v003(max_frame: int = 512) -> VirtualProbe:
@@ -532,7 +531,7 @@ def esp32_v003(max_frame: int = 512) -> VirtualProbe:
532
531
  _link(10, port_speed=True),
533
532
  _plan(11),
534
533
  _restart(12),
535
- ], netifs=("wifi",), labels={16: "SWIO", 23: "NRST", 22: "DUT TX", 21: "DUT RX"}), own_channels=own)
534
+ ], netifs=("wifi",), labels={16: "v003.swio", 23: "v003.nrst", 22: "v003.rx", 21: "v003.tx"}), own_channels=own)
536
535
 
537
536
 
538
537
  def esp32_v003_64() -> VirtualProbe:
@@ -562,7 +561,7 @@ def p4_bench() -> VirtualProbe:
562
561
  _link(7),
563
562
  _plan(8),
564
563
  _restart(9),
565
- ], labels={2: "A SWDIO", 3: "A SWCLK", 4: "B SWDIO", 5: "B SWCLK", 6: "C SWDIO", 7: "C SWCLK"}), own_channels=own)
564
+ ], labels={2: "a.swdio", 3: "a.swclk", 4: "b.swdio", 5: "b.swclk", 6: "c.swdio", 7: "c.swclk"}), own_channels=own)
566
565
 
567
566
 
568
567
  def rp2350_pins() -> VirtualProbe:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: oep-client-python
3
- Version: 0.0.37
3
+ Version: 0.0.39
4
4
  Summary: Open Embedded Probe (OEP) v1 host: serial / USB / TCP transports, the session rules, the standard interfaces, and a virtual bench
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
@@ -84,7 +84,7 @@ names starting with `_`, may change. Check probe/client compatibility using the
84
84
  | `pins` | `oep pins`: classify the channels, hold-low search, scan, identify, confirm the reset line, suggest the connection profile and the labels (`PinFinder`) |
85
85
  | `console` | `oep.target.console` (position streams: read answers carry their length; marks carry `time_ns`, the common kinds lost / clear / host and the console's reset 0x40 / restart 0x41; the lock-free `streams()` list of stream, connection and mechanism; `open(conn, mechanism)` always makes a new stream (u32) on the connection - a second on a connection with a live one is unavailable cause 6 -; a stream is the session's - close, its connection's release and the session's end release it with its buffer, and its number answers no_resource after that; a write goes into the stream's send queue - its size is the probe's own, not declared -, accepted = min(count, its free space), 0 only when full; an SDI stream's write is unsupported; marks, clear, mark and write are optional) and `ConsoleIO`, read as bytes and written in chunks of at most one frame, on from each answer's accepted (`stall_s`: TimeoutError when nothing is taken for that long) |
86
86
  | `fixture` | `oep.fixture.gpio` (`set([(ch, mode)])` applied in order in one request, `read(channels)` the levels; the output strength is the probe's own - there is no drive selection) / `uart` (its stream is the plan's; nothing is received until `configure(baud, fmt)`; `status()` = `UartStatus(baud, format)` in force, baud 0 and format 0 while not configured; a configure the probe could not bring up raises `Failed` and leaves it not configured) / `i2c-target` (one form: `configure(address)` - the target made anew -, every controller write with data one frame for `read_rx`, reads answered from `preload_tx(data)` slots or 0xFF, `status()` = `I2cStatus(state, queued, rx_frames, tx_slots, errors)`, `stretch` when the ops offer it, `internal_pullups`: whether it enables pull-ups of its own; the reserved addresses 0x00-0x07 / 0x78-0x7F refused before sending) / `spi-target` (no reset: configure again; `cs_setup_ns`: how long after CS the first bit is sure; `oep dump` shows it) |
87
- | `settings` | the core's settings (oep-spec core §13): fn 0's get / set / unset / save / state / network. `Settings(hst)`: `catalog()` = the names of this boot (each fn's category `core` / `name/instance`, items, choices, channel / route / netif names), `show()` = every value as (category, flat name, value, origin), `set_named(category, {flat: value})` / `unset_named` / `unset_all`, raw `set` / `unset` by (fn, number, key), `save()`, `state()`, `network()`; `export()` / `plan_import(doc)` / `apply_import(plan)` = the settings file (oep-spec docs/oep-settings-file.ja.md; secrets never read); `find_line(source, "nrst", profile=None)` = host guide §5.3's line lookup over every channel label |
87
+ | `settings` | the core's settings (oep-spec core §13): fn 0's get / set / unset / save / state / network. `Settings(hst)`: `catalog()` = the names of this boot (each fn's category `core` / `name/instance`, items, choices, channel / route / netif names), `show()` = every value as (category, flat name, value) (no origin: the firmware's initial values are values like any other, core §13.4), `changeable()` (set / unset offered), `set_named(category, {flat: value})` / `unset_named` / `unset_all`, raw `set` / `unset` by (fn, number, key), `save()`, `state()`, `network()`; `export()` / `plan_import(doc)` / `apply_import(plan)` = the settings file (oep-spec docs/oep-settings-file.ja.md; secrets never read); `find_line(source, "nrst", port=None)` = host guide §5.3's line lookup: port S's line N is the label `S.N` (ignoring ASCII case; without a port the one port named), `ports_named(labels)` the ports as written |
88
88
  | `capture` | `oep.fixture.logic` / `analog` / `capture-group` (revision 1, oep-spec oep-if-capture). configure's TLVs follow core §2.3: a value the probe cannot honour is refused unsupported with the tag as sent (the table's TLVs go without the critical bit); the answer holds the actual values, its samples among them (analog: a scale for every role, a skew left out read as 0). Every `start()` returns a new generation (`LogicCapture.generation`, never re-used within a boot: past 0xFFFFFFFF a start is refused until a reboot) that read and release name - `read_segment(segment)` passes the segment's own - and every event carries (`events()` drops an earlier start's); streaming data frames carry their generation and their segment (segment_info, never crossing a segment: `Received.segments`); `status()` returns a `Status`. A capture-group binds configured tracks with an immediate trigger, all in one mode, and starts them at once (`CaptureGroup.start` -> the group's start_ns; the group's and each track's generation). Every segment read goes to the `Host.on_capture` callbacks as a `CaptureRecord` (the hook for run recorders; no wireskein dependency) |
89
89
  | `decode` | decoding capture channels (I2C) |
90
90
  | `registry` | generated from oep-spec's number table (never edited; copied again from oep-spec). The public way to reach an interface by name is `registry.INTERFACES[name]` (`.revision`, `.op`, `.tlv`, `.enum`, e.g. `INTERFACES["oep.fixture.uart"].enum["role"]`); the module-level names (`FIXTURE_UART`, ...) are the same objects |
@@ -135,12 +135,12 @@ A probe's CDC ports (`/dev/ttyACM*`) need no rule beyond the usual `dialout` gro
135
135
 
136
136
  ```sh
137
137
  oep dump --port <probe> # what the probe offers (--virtual p4-x035: no hardware)
138
- oep settings show <probe> # every value with its origin, and the storage state
138
+ oep settings show <probe> # every value, and the storage state
139
139
  oep settings items <probe> # what each owner declares (names, forms, choices)
140
- oep settings set <probe> core channel.GPIO23.label=nrst channel.GPIO20.idle=pull-up [--save]
140
+ oep settings set <probe> core channel.GPIO23.label=v003.nrst channel.GPIO20.idle=pull-up [--save]
141
141
  oep settings unset <probe> core channel.GPIO20.idle [--save] # or --all (then --save empties the storage)
142
142
  oep settings save <probe> # kept over a restart
143
- oep settings export <probe> jig.oep-settings.json # the user's values by name (secrets left out)
143
+ oep settings export <probe> jig.oep-settings.json # every value by name (secrets as present only)
144
144
  oep settings import <probe> jig.oep-settings.json [--save] # shows the changes, asks, then sets / unsets
145
145
  oep speed <probe> [921600,500000] [--verify] [--flows out:2] # port_speed: raise a UART bridge (default 500000), print the report (below)
146
146
  oep pins <probe> --power 5 --wire swio # where the target is wired: debug pins, reset line, labels (below)
@@ -223,8 +223,8 @@ option-byte reader for the reset line, max_speed / idle_clock).
223
223
  NRST exists (read only, never written); then resume.
224
224
  5. **reset**: attach under reset through each candidate - the real line stops the hart at the reset vector.
225
225
  6. **labels**: the connection profile the host keeps (wire, pins, line settings: the probe keeps no wiring) and the
226
- labels `nrst` / `power_hi` (`<name>.nrst` / `<name>.power_hi` with `--name`, a probe serving several targets),
227
- checked with `config.find_line` against the probe's settings (a note when another channel already carries the name:
226
+ labels `<port>.nrst` / `<port>.power_hi` (the port: `--name`, else the one port the probe's labels name; neither:
227
+ a note asks for `--name`), checked with `settings.find_line` against the probe's settings (a note when another channel already carries the name:
228
228
  the line would not be found); `--save` writes the labels (set + save). Nothing is written without `--save`.
229
229
 
230
230
  Safety: a driven or active channel is never driven, scanned or held; the power channel is touched only with `--power`;
@@ -236,7 +236,7 @@ pulls; its "pulled-up" row (channel 4, the reset line's weak pull-up) is left ou
236
236
  alone that channel reads floating:
237
237
 
238
238
  ```
239
- $ oep pins /run/board-identify/by-id/esp32-series-30eda0ea068b --power 5 --wire swio
239
+ $ oep pins /run/board-identify/by-id/esp32-series-30eda0ea068b --power 5 --wire swio --name v003
240
240
  power: channel 5 low 300 ms (target off: read), then high 400 ms before the reads
241
241
  classify: 52 channels, 16 reads each under pull-up, pull-down
242
242
  floating 0-3,6,9-20,26-34,36-50,52-54
@@ -252,8 +252,8 @@ attach swio 19: target_id 00310510 (WCH DMI 0x7F) -> ch32v00x, halted at dpc 0x1
252
252
  option bytes (read only): RST_MODE 10 (USER 0xf7): NRST on PD7, 12 ms ignore window
253
253
  attach under reset through 4 (held 20 ms): dpc 0x0 -> the reset line
254
254
  profile: wire swio, pins 19 (the host's to keep: the probe keeps no wiring)
255
- label: oep settings set ... core channel.GPIO4.label=nrst (not written)
256
- label: oep settings set ... core channel.GPIO5.label=power_hi (not written)
255
+ label: oep settings set ... core channel.GPIO4.label=v003.nrst (not written)
256
+ label: oep settings set ... core channel.GPIO5.label=v003.power_hi (not written)
257
257
  idle: oep settings set ... core channel.GPIO5.idle=high (keeps the target powered while no plan holds channel 5; not written by this tool)
258
258
  released every plan (channel 5 is back to its idle state: the target is powered only while something drives it)
259
259
  done in 7.3 s
@@ -398,7 +398,8 @@ lines outside through `gpio_world`); uart receives nothing until configured, sta
398
398
  (a write with data one frame, cut at max_length; reads from preload_tx slots, else 0xFF); spi-target without reset.
399
399
  Capture: configure's TLVs follow core §2.3 alone; a bound track's start / configure is unavailable cause 4, a bind short
400
400
  of resources cause 2 with the fn; a read at or past the write position answers write_pos and len 0. The settings (core §13): each fn's items from its
401
- describe, values with their origin (the firmware's fixed / default ones from the profile); a value of another length
401
+ describe, values without origin (the profile's initial values in on a boot without a save; labels unique but for ASCII
402
+ case, a probe of get alone with `changeable=False`); a value of another length
402
403
  malformed; the generation counts the changes of a boot from 1; state is storage_state, saved_generation, reason; at boot
403
404
  the saved values (by category and item name) are checked one by one, disable first, and what fails is left out (partial). port_speed: baud / step / verify_ms on the UART bridge the request came in on, within
404
405
  port_speed_tolerance_pct, phases 0-3 read by port_state; the virtual bench goes back after verify_ms, after
@@ -1,5 +1,5 @@
1
- oep_client/__init__.py,sha256=yjYOK4BdHsZGNYYhMCRuwG62rN9ieP_REO1quvpwd-g,164
2
- oep_client/__main__.py,sha256=vCVs4gnlesGxI4SLM-BvxkOYkXWaUqZV2CVrpD9HiR8,30257
1
+ oep_client/__init__.py,sha256=fPqGYg5bvjixvo-QAD2owR5YvUr0AopjUZEJpXsE1Jo,164
2
+ oep_client/__main__.py,sha256=DwmDPwTT0NpPQLm8KphkjSu5b_l9djUwyOsvYl15srI,30186
3
3
  oep_client/arm.py,sha256=SszSLae8zErD4T-a1uQ6s6o3Lz7pe2SR8d3Qy-wi5rY,15633
4
4
  oep_client/capture.py,sha256=uSzQT5MRvYlnKkxsob7vLtlvzQl_ffM8pbOC_re5ZjY,69259
5
5
  oep_client/capture_blocks.py,sha256=GpENb0c2xIC8WkkPdHb5S1nQBV02v0rzRxN2ak_eQLQ,15283
@@ -162,11 +162,11 @@ oep_client/conformance_wire_options_sample.py,sha256=cWrssQEO6yEz8CdbEUI5HcaK_Mv
162
162
  oep_client/conformance_writer_lease.py,sha256=dZv9AlyKHB--3YtqFQrBWK8wRYu2FetN78FR5OmERbw,7364
163
163
  oep_client/conformance_writer_lease_sample.py,sha256=VBaq3Fk7Pd_6bbHQf9qLUZGBBO3XRYR1ax2b8z3xR60,467
164
164
  oep_client/console.py,sha256=hDxoaXDCzk6KiOo-Y3RqC4yZuLPkCXOVi886_C4xXaE,12910
165
- oep_client/core.py,sha256=mjM5aB85PZab8sLRE8hUrM5v7dQNmLgte2znUsKMyOg,22791
165
+ oep_client/core.py,sha256=qyOCFlujvSzIjbBbZmvA6cMjBn2FEOQYlq1KU2WLdOQ,22040
166
166
  oep_client/decode.py,sha256=6pTFVBT0MLAKedZLVKyd5yT8hjrRw4fdDjnoDiE9XRs,2380
167
167
  oep_client/discovery.py,sha256=5MurWfhiTGIz_bTNpB35Qz6HFe1vY_2HlINdTh3cV7U,20170
168
168
  oep_client/dump.py,sha256=rnPOwbcXKk0ATk2Ka8O8p_XN6lTzmAQAhIHjm-iwT7I,13014
169
- oep_client/endpoint.py,sha256=l6FjrK_c2fvgB4HWi1jPWOaaXy8dtFAxBdeTsRClvws,233833
169
+ oep_client/endpoint.py,sha256=SNYvhIltK2xn2smpz700fJVRBmTny4nrwx76l6vNqDw,234859
170
170
  oep_client/fixture.py,sha256=GbAPXAk9GQ0qG6STL-rY4-EqXftmKfNKVI2xZZUiFzA,18108
171
171
  oep_client/frames.py,sha256=44h0dy4xlW3S3y39k3jwxHoKkI2Ib2fsmHdjlPYF310,4990
172
172
  oep_client/hardware.py,sha256=RboxkRxkQJ3DbNJEXyyiOFYlLFKGxrlcSwHho8zFC1U,30256
@@ -178,12 +178,12 @@ oep_client/link.py,sha256=E_8nnNJCr6O2NpUXl9qSmru-5j49p_mrImM70_ZQ6E4,142023
178
178
  oep_client/linktest.py,sha256=zj9Ek28VkjyuIPsiU3iF-UEZmY_xMnNjNL-vK-jOQ3Q,10258
179
179
  oep_client/message.py,sha256=Kw-PwkMCwa8VGUBd1HWTXe8Epe4FwNcBx8262mWx2rk,14152
180
180
  oep_client/names.py,sha256=ohrRvQNURraYyJ-BtjHRwxGt_GLq-t8IO0aOji9Dhe0,3531
181
- oep_client/pins.py,sha256=1B-oBXIsbepxkaDpgVzXbkMBgo0pEmnWrQ_Ex_2JU90,32691
181
+ oep_client/pins.py,sha256=6fAyJOF724Na-Yx1uxDI_wZ-5s0Qm7SAJ0GyglTmWaI,33256
182
182
  oep_client/pytest_conformance.py,sha256=-bcJU2E1UE4xjQ7HZjaIgtp0l65cIibBTNFnaW2tews,2929
183
- oep_client/registry.py,sha256=RiFRC9Op1LT-l93spCUYXKHZBR0tpCafO1AoXrl7Oyw,21204
183
+ oep_client/registry.py,sha256=2gmzJvCDynq5BmmxrahVOO5frcI21bmCiXHT2yGRAiw,21138
184
184
  oep_client/riscv.py,sha256=ywBg1VvvpJydOO-D1UlpuKgNAVV8w1lvWhKc6JXeDlo,60428
185
185
  oep_client/rp2350.py,sha256=7OMyj270k3AY1e3-ljicPwOjf9jOlokbT6QNkA0b0yY,4227
186
- oep_client/settings.py,sha256=jL89dniGAhoyOztd4dhkXU1Mk-TSbadb8123YkYsh2I,24946
186
+ oep_client/settings.py,sha256=dccTo1m56oN4fca0b69GAORHI2H9L9tic6K-F2OqkTQ,24938
187
187
  oep_client/speed_record.py,sha256=6THUKbN7gQKpfIdUO2551P8mXiAFqbFDky72Xbki7L0,7229
188
188
  oep_client/targets.py,sha256=zFekSr76NVO7tzdgyQsLDHLWRvkgylfwvGNTz_hQGf8,5041
189
189
  oep_client/uiapduino.py,sha256=oafP1SyKPO3cox-2RYbWUZusy4vc6NsEVC7Z-Y1sjWI,6337
@@ -192,7 +192,7 @@ oep_client/virtual_allocator_model.py,sha256=XBrbjG2Zupf0P28gjUhQVDw6UzO2jQXsYZ1
192
192
  oep_client/virtual_analog_model.py,sha256=9ohE6xm3Av0o37VgiKRu4sD9-D0bOqW2rUvwrguCPJU,13853
193
193
  oep_client/virtual_arm_adi_model.py,sha256=xlqhjr_QCF6MNVOnVelTanlNWpmaCJ5uWXf6V9WkqUM,8121
194
194
  oep_client/virtual_attach_model.py,sha256=YhGYz4X2IcdBMsBb0vjkV2UWUst6tSVieZHUwQZ89gA,10433
195
- oep_client/virtual_bench.py,sha256=Ss_IIA0yGTmMFWD96SydOuS2FkGFZI-sShZFYgxX_ys,41606
195
+ oep_client/virtual_bench.py,sha256=4wTcB3Z3Ofv5FbsNJ4LNihcBQyrt_2ALoCO5rhGG2wE,41625
196
196
  oep_client/virtual_bench_capture.py,sha256=SJcsW_dvKAc7k_BvRbY-3PZF60F0rbfrlGholoNyUnw,77412
197
197
  oep_client/virtual_bench_mdns.py,sha256=cnJNBKg5zTduXUcH-wqN_ZBDGl7DyL-tL3CBn2jro0E,13305
198
198
  oep_client/virtual_bench_serial.py,sha256=EvbdOYeGrM6CRti2b90I0PzpmmherfoJRaAg4v7Iq9Y,13205
@@ -246,8 +246,8 @@ oep_client/virtual_tlv_request_model.py,sha256=gp-eiN784NnloQBANG_4TnM6oUCmrj046
246
246
  oep_client/virtual_uart_model.py,sha256=Sd_MWLjl1-6_bemsCuI2at9zpVJRBehFQgZ6By97PdQ,9386
247
247
  oep_client/virtual_wire_options_model.py,sha256=1QNF16hRWmdFt0J7Zd1WKZdEc07vns6osMQLetMBlXw,9206
248
248
  oep_client/virtual_writer_lease_model.py,sha256=E1MAQsGuShqONVdIeLubun7Cop0URNUSk8wicPicbt4,1728
249
- oep_client_python-0.0.37.dist-info/METADATA,sha256=27nY_mpZ96-opNPDVHuIlGvvqq1u5PKzJu_Tgm7UM-I,53307
250
- oep_client_python-0.0.37.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
251
- oep_client_python-0.0.37.dist-info/entry_points.txt,sha256=XEt39Jz7oQyU9Hfg_tV6eDGofNxWYrjSfslxKP-fh70,135
252
- oep_client_python-0.0.37.dist-info/licenses/LICENSE,sha256=ntlalztb3L1OaNAxmQdHlubu5fVhjMc9oRZi3PeQhXg,1076
253
- oep_client_python-0.0.37.dist-info/RECORD,,
249
+ oep_client_python-0.0.39.dist-info/METADATA,sha256=DKEFrj6sNxWzMUlMTosL36BankLLWWsEuioOuS6Ml3c,53653
250
+ oep_client_python-0.0.39.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
251
+ oep_client_python-0.0.39.dist-info/entry_points.txt,sha256=XEt39Jz7oQyU9Hfg_tV6eDGofNxWYrjSfslxKP-fh70,135
252
+ oep_client_python-0.0.39.dist-info/licenses/LICENSE,sha256=ntlalztb3L1OaNAxmQdHlubu5fVhjMc9oRZi3PeQhXg,1076
253
+ oep_client_python-0.0.39.dist-info/RECORD,,