oep-client-python 0.0.31__py3-none-any.whl → 0.0.32__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.
Files changed (263) hide show
  1. oep_client/__init__.py +1 -1
  2. oep_client/__main__.py +158 -321
  3. oep_client/arm.py +63 -17
  4. oep_client/capture.py +209 -208
  5. oep_client/catalog.py +59 -33
  6. oep_client/ch32_flash.py +2 -2
  7. oep_client/config.py +437 -472
  8. oep_client/conformance.py +1083 -0
  9. oep_client/conformance_allocator.py +88 -0
  10. oep_client/conformance_allocator_sample.py +16 -0
  11. oep_client/conformance_analog.py +142 -0
  12. oep_client/conformance_analog_payload.py +84 -0
  13. oep_client/conformance_analog_sample.py +15 -0
  14. oep_client/conformance_arm_adi.py +141 -0
  15. oep_client/conformance_arm_adi_blocks.py +87 -0
  16. oep_client/conformance_arm_adi_sample.py +14 -0
  17. oep_client/conformance_attach.py +198 -0
  18. oep_client/conformance_attach_sample.py +16 -0
  19. oep_client/conformance_block_sample.py +12 -0
  20. oep_client/conformance_blocks.py +132 -0
  21. oep_client/conformance_capture_group.py +276 -0
  22. oep_client/conformance_capture_group_geometry.py +76 -0
  23. oep_client/conformance_capture_group_sample.py +17 -0
  24. oep_client/conformance_capture_metadata.py +42 -0
  25. oep_client/conformance_capture_serial.py +103 -0
  26. oep_client/conformance_capture_serial_sample.py +61 -0
  27. oep_client/conformance_capture_timeline.py +261 -0
  28. oep_client/conformance_capture_timeline_lifecycle.py +175 -0
  29. oep_client/conformance_capture_timeline_sample.py +15 -0
  30. oep_client/conformance_clock.py +82 -0
  31. oep_client/conformance_clock_sample.py +13 -0
  32. oep_client/conformance_config.py +139 -0
  33. oep_client/conformance_config_execution.py +84 -0
  34. oep_client/conformance_config_execution_sample.py +13 -0
  35. oep_client/conformance_config_fallback.py +72 -0
  36. oep_client/conformance_config_fallback_sample.py +25 -0
  37. oep_client/conformance_config_paging.py +87 -0
  38. oep_client/conformance_config_paging_sample.py +29 -0
  39. oep_client/conformance_config_power_loss.py +89 -0
  40. oep_client/conformance_config_power_loss_sample.py +15 -0
  41. oep_client/conformance_config_sample.py +13 -0
  42. oep_client/conformance_config_storage.py +79 -0
  43. oep_client/conformance_config_storage_sample.py +15 -0
  44. oep_client/conformance_core_fixed_fields.py +100 -0
  45. oep_client/conformance_core_smoke.py +193 -0
  46. oep_client/conformance_corr.py +111 -0
  47. oep_client/conformance_cut.py +83 -0
  48. oep_client/conformance_cut_sample.py +10 -0
  49. oep_client/conformance_data.py +315 -0
  50. oep_client/conformance_data_sample.py +15 -0
  51. oep_client/conformance_debug.py +177 -0
  52. oep_client/conformance_debug_sample.py +12 -0
  53. oep_client/conformance_dependencies.py +261 -0
  54. oep_client/conformance_dependency_sample.py +17 -0
  55. oep_client/conformance_discovery.py +135 -0
  56. oep_client/conformance_dmseq.py +226 -0
  57. oep_client/conformance_dmseq_sample.py +9 -0
  58. oep_client/conformance_dmseq_target.py +137 -0
  59. oep_client/conformance_dmseq_target_sample.py +21 -0
  60. oep_client/conformance_execution_order.py +101 -0
  61. oep_client/conformance_execution_order_sample.py +9 -0
  62. oep_client/conformance_exhausted.py +112 -0
  63. oep_client/conformance_exhausted_sample.py +59 -0
  64. oep_client/conformance_fixture_metadata.py +56 -0
  65. oep_client/conformance_gpio.py +97 -0
  66. oep_client/conformance_gpio_lifecycle.py +69 -0
  67. oep_client/conformance_gpio_lifecycle_sample.py +8 -0
  68. oep_client/conformance_gpio_sample.py +12 -0
  69. oep_client/conformance_host_core.py +152 -0
  70. oep_client/conformance_host_core_sample.py +65 -0
  71. oep_client/conformance_host_deadline.py +96 -0
  72. oep_client/conformance_host_deadline_sample.py +89 -0
  73. oep_client/conformance_host_notifications.py +161 -0
  74. oep_client/conformance_host_notifications_sample.py +39 -0
  75. oep_client/conformance_host_request_sample.py +27 -0
  76. oep_client/conformance_host_requests.py +157 -0
  77. oep_client/conformance_host_resend.py +61 -0
  78. oep_client/conformance_host_resend_sample.py +66 -0
  79. oep_client/conformance_host_tlv.py +119 -0
  80. oep_client/conformance_host_tlv_sample.py +22 -0
  81. oep_client/conformance_host_values.py +95 -0
  82. oep_client/conformance_host_values_sample.py +36 -0
  83. oep_client/conformance_host_wait.py +70 -0
  84. oep_client/conformance_host_wait_sample.py +65 -0
  85. oep_client/conformance_i2c.py +141 -0
  86. oep_client/conformance_i2c_sample.py +14 -0
  87. oep_client/conformance_interface_smoke.py +79 -0
  88. oep_client/conformance_interfaces.py +111 -0
  89. oep_client/conformance_lease.py +168 -0
  90. oep_client/conformance_lease_sample.py +32 -0
  91. oep_client/conformance_link_routes.py +111 -0
  92. oep_client/conformance_link_routes_sample.py +15 -0
  93. oep_client/conformance_link_transfer_sample.py +24 -0
  94. oep_client/conformance_link_transfers.py +110 -0
  95. oep_client/conformance_logic.py +182 -0
  96. oep_client/conformance_logic_sample.py +14 -0
  97. oep_client/conformance_mailbox.py +156 -0
  98. oep_client/conformance_mailbox_sample.py +11 -0
  99. oep_client/conformance_notification_retention.py +44 -0
  100. oep_client/conformance_notification_retention_sample.py +41 -0
  101. oep_client/conformance_notification_sequence.py +214 -0
  102. oep_client/conformance_notification_sequence_sample.py +11 -0
  103. oep_client/conformance_oep_declarations.py +98 -0
  104. oep_client/conformance_oep_smoke.py +202 -0
  105. oep_client/conformance_outcome_sample.py +12 -0
  106. oep_client/conformance_outcomes.py +147 -0
  107. oep_client/conformance_pipeline.py +147 -0
  108. oep_client/conformance_pipeline_sample.py +9 -0
  109. oep_client/conformance_port_speed.py +208 -0
  110. oep_client/conformance_port_speed_execution.py +99 -0
  111. oep_client/conformance_port_speed_execution_sample.py +6 -0
  112. oep_client/conformance_port_speed_lifecycle.py +88 -0
  113. oep_client/conformance_port_speed_sample.py +17 -0
  114. oep_client/conformance_probe_input.py +84 -0
  115. oep_client/conformance_probe_input_sample.py +29 -0
  116. oep_client/conformance_recovery.py +131 -0
  117. oep_client/conformance_recovery_race_sample.py +133 -0
  118. oep_client/conformance_recovery_races.py +158 -0
  119. oep_client/conformance_recovery_sample.py +86 -0
  120. oep_client/conformance_refusal_order.py +73 -0
  121. oep_client/conformance_refusal_order_sample.py +33 -0
  122. oep_client/conformance_registered_ops.py +96 -0
  123. oep_client/conformance_replay_pressure.py +184 -0
  124. oep_client/conformance_reset.py +114 -0
  125. oep_client/conformance_reset_sample.py +8 -0
  126. oep_client/conformance_resource_sample.py +40 -0
  127. oep_client/conformance_resources.py +261 -0
  128. oep_client/conformance_response_race.py +76 -0
  129. oep_client/conformance_response_race_sample.py +57 -0
  130. oep_client/conformance_restart.py +81 -0
  131. oep_client/conformance_restart_sample.py +25 -0
  132. oep_client/conformance_retention.py +391 -0
  133. oep_client/conformance_retention_lifecycle.py +135 -0
  134. oep_client/conformance_retention_lifecycle_sample.py +11 -0
  135. oep_client/conformance_retention_sample.py +7 -0
  136. oep_client/conformance_retry.py +78 -0
  137. oep_client/conformance_retry_sample.py +10 -0
  138. oep_client/conformance_route_sample.py +66 -0
  139. oep_client/conformance_routes.py +263 -0
  140. oep_client/conformance_run.py +120 -0
  141. oep_client/conformance_run_sample.py +9 -0
  142. oep_client/conformance_scan.py +122 -0
  143. oep_client/conformance_scan_options.py +128 -0
  144. oep_client/conformance_scan_options_sample.py +9 -0
  145. oep_client/conformance_scan_sample.py +10 -0
  146. oep_client/conformance_selected_metadata.py +43 -0
  147. oep_client/conformance_serial.py +118 -0
  148. oep_client/conformance_session_loss.py +94 -0
  149. oep_client/conformance_session_loss_sample.py +62 -0
  150. oep_client/conformance_session_smoke.py +149 -0
  151. oep_client/conformance_spi.py +161 -0
  152. oep_client/conformance_spi_sample.py +16 -0
  153. oep_client/conformance_step.py +100 -0
  154. oep_client/conformance_step_sample.py +9 -0
  155. oep_client/conformance_stream.py +238 -0
  156. oep_client/conformance_stream_sample.py +14 -0
  157. oep_client/conformance_subscription_sample.py +25 -0
  158. oep_client/conformance_subscriptions.py +294 -0
  159. oep_client/conformance_tcp.py +94 -0
  160. oep_client/conformance_tcp_peers.py +206 -0
  161. oep_client/conformance_tcp_recovery.py +109 -0
  162. oep_client/conformance_tcp_recovery_sample.py +168 -0
  163. oep_client/conformance_tlv_requests.py +127 -0
  164. oep_client/conformance_uart.py +141 -0
  165. oep_client/conformance_uart_sample.py +15 -0
  166. oep_client/conformance_uart_stream.py +37 -0
  167. oep_client/conformance_uart_stream_sample.py +11 -0
  168. oep_client/conformance_usb.py +162 -0
  169. oep_client/conformance_usb_cases.py +103 -0
  170. oep_client/conformance_usb_model.py +43 -0
  171. oep_client/conformance_wire_options.py +145 -0
  172. oep_client/conformance_wire_options_sample.py +11 -0
  173. oep_client/conformance_writer_lease.py +77 -0
  174. oep_client/conformance_writer_lease_sample.py +9 -0
  175. oep_client/console.py +51 -46
  176. oep_client/core.py +72 -48
  177. oep_client/dump.py +9 -8
  178. oep_client/endpoint.py +1285 -1383
  179. oep_client/fixture.py +31 -103
  180. oep_client/hardware.py +587 -0
  181. oep_client/hid_stream.py +74 -12
  182. oep_client/host.py +178 -78
  183. oep_client/interfaces.py +67 -52
  184. oep_client/kept_session.py +36 -11
  185. oep_client/link.py +244 -99
  186. oep_client/linktest.py +14 -6
  187. oep_client/message.py +22 -17
  188. oep_client/names.py +12 -26
  189. oep_client/pins.py +70 -63
  190. oep_client/pytest_conformance.py +60 -0
  191. oep_client/registry.py +60 -59
  192. oep_client/riscv.py +287 -145
  193. oep_client/uiapduino.py +2 -0
  194. oep_client/usb_stream.py +5 -3
  195. oep_client/virtual_allocator_model.py +33 -0
  196. oep_client/virtual_analog_model.py +193 -0
  197. oep_client/virtual_arm_adi_model.py +139 -0
  198. oep_client/virtual_attach_model.py +146 -0
  199. oep_client/virtual_bench.py +131 -115
  200. oep_client/virtual_bench_capture.py +409 -249
  201. oep_client/virtual_bench_serial.py +60 -22
  202. oep_client/virtual_bench_serve.py +31 -99
  203. oep_client/virtual_bench_usb.py +94 -0
  204. oep_client/virtual_block_model.py +80 -0
  205. oep_client/virtual_capture_group_geometry_model.py +43 -0
  206. oep_client/virtual_capture_group_model.py +245 -0
  207. oep_client/virtual_capture_metadata_model.py +24 -0
  208. oep_client/virtual_capture_timeline_model.py +390 -0
  209. oep_client/virtual_clock_model.py +42 -0
  210. oep_client/virtual_config_execution_model.py +68 -0
  211. oep_client/virtual_config_model.py +176 -0
  212. oep_client/virtual_config_paging_model.py +12 -0
  213. oep_client/virtual_config_power_loss_model.py +77 -0
  214. oep_client/virtual_config_storage_model.py +20 -0
  215. oep_client/virtual_core.py +229 -0
  216. oep_client/virtual_cut_model.py +83 -0
  217. oep_client/virtual_data_model.py +97 -0
  218. oep_client/virtual_debug_model.py +149 -0
  219. oep_client/virtual_declaration_model.py +39 -0
  220. oep_client/virtual_dependency_model.py +109 -0
  221. oep_client/virtual_dmseq_model.py +66 -0
  222. oep_client/virtual_dmseq_target_model.py +66 -0
  223. oep_client/virtual_fixture_metadata_model.py +22 -0
  224. oep_client/virtual_gpio_lifecycle_model.py +15 -0
  225. oep_client/virtual_gpio_model.py +99 -0
  226. oep_client/virtual_i2c_model.py +134 -0
  227. oep_client/virtual_link_routes_model.py +42 -0
  228. oep_client/virtual_link_transfer_model.py +62 -0
  229. oep_client/virtual_logic_model.py +199 -0
  230. oep_client/virtual_mailbox_model.py +55 -0
  231. oep_client/virtual_names.py +29 -0
  232. oep_client/virtual_notification_model.py +81 -0
  233. oep_client/virtual_oep_declaration_model.py +55 -0
  234. oep_client/virtual_outcome_model.py +78 -0
  235. oep_client/virtual_pipeline_model.py +94 -0
  236. oep_client/virtual_port_speed_execution_model.py +36 -0
  237. oep_client/virtual_port_speed_model.py +148 -0
  238. oep_client/virtual_registered_ops_model.py +48 -0
  239. oep_client/virtual_reset_model.py +68 -0
  240. oep_client/virtual_resource_model.py +86 -0
  241. oep_client/virtual_restart_model.py +77 -0
  242. oep_client/virtual_retention_model.py +44 -0
  243. oep_client/virtual_retry_model.py +92 -0
  244. oep_client/virtual_route_model.py +187 -0
  245. oep_client/virtual_run_model.py +84 -0
  246. oep_client/virtual_scan_model.py +75 -0
  247. oep_client/virtual_scan_options_model.py +83 -0
  248. oep_client/virtual_selected_metadata_model.py +17 -0
  249. oep_client/virtual_spi_model.py +154 -0
  250. oep_client/virtual_step_model.py +60 -0
  251. oep_client/virtual_stream_model.py +99 -0
  252. oep_client/virtual_tlv_request_model.py +74 -0
  253. oep_client/virtual_uart_model.py +135 -0
  254. oep_client/virtual_wire_options_model.py +125 -0
  255. oep_client/virtual_writer_lease_model.py +33 -0
  256. {oep_client_python-0.0.31.dist-info → oep_client_python-0.0.32.dist-info}/METADATA +149 -192
  257. oep_client_python-0.0.32.dist-info/RECORD +268 -0
  258. oep_client_python-0.0.32.dist-info/entry_points.txt +4 -0
  259. oep_client/multirate.py +0 -254
  260. oep_client_python-0.0.31.dist-info/RECORD +0 -43
  261. oep_client_python-0.0.31.dist-info/entry_points.txt +0 -2
  262. {oep_client_python-0.0.31.dist-info → oep_client_python-0.0.32.dist-info}/WHEEL +0 -0
  263. {oep_client_python-0.0.31.dist-info → oep_client_python-0.0.32.dist-info}/licenses/LICENSE +0 -0
oep_client/__init__.py CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.0.31"
5
+ __version__ = "0.0.32"
oep_client/__main__.py CHANGED
@@ -2,18 +2,10 @@
2
2
 
3
3
  oep dump --port /run/board-identify/by-id/<probe> oep dump --virtual p4-x035 --prefix oep.target --json
4
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)
6
- oep config slot <probe> --name x035 --wire rvswd --pins 2,54 --attach at-boot --retry 1 --mechanism dmseq
7
- oep config bind <probe> --port 1 --stream slot:x035 (the one stream a serial port carries)
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
10
- oep config disable <probe> 3 4 (channels the probe never uses or touches; remove disable CH re-enables)
11
- oep config remove <probe> bind 1 oep config save <probe> oep config erase <probe>
12
- oep config wifi <probe> --index 0 --ssid LAB --pass-prompt [--save] (a network the probe joins, probe.config §1.4;
13
- the passphrase is asked without echo or read from --pass-env VAR, never
14
- printed; without either the entry keeps its passphrase)
15
- oep config wifi <probe> --from-env [--save] (OEP_WIFI_SSID_<n> / OEP_WIFI_PASS_<n>, n = the index)
16
- oep config wifi-unset <probe> --index 0 [--save]
5
+ oep config state <probe> (the storage state; lock-free)
6
+ oep config label <probe> 23 nrst oep config idle <probe> 20 pull-up
7
+ oep config disable <probe> 3 4 (channels the probe never drives or sets up; remove disable CH re-enables)
8
+ oep config remove <probe> idle 20 oep config save <probe> oep config erase <probe>
17
9
  oep find [--timeout 2] [--json] [--no-verify] (probes announcing _oep._tcp by DNS-SD over mDNS: unit_id, host,
18
10
  port, IP; each verified first by confirm + describe's unit_id, host guide
19
11
  §4.1 - one that fails is dropped with a warning)
@@ -22,8 +14,8 @@
22
14
  500000 - a faster rate only when named, after its 1 s verify each way)
23
15
  oep linktest <probe> --rates now,921600 --patterns in,out,duplex --inflight 1,2 --sizes 128,496 --frames 300
24
16
  oep pins <probe> --power 5 --wire swio (find the target's debug pins and reset line: classify the channels, scan,
25
- identify, hold-low + attach under reset; prints a slot, --save writes it)
26
- oep clock <probe> [-n 8] [--json] (fn 0's clock, core §7.7: the probe's uptime_ns and boot_id against this
17
+ identify, hold-low + attach under reset; prints the labels, --save writes them)
18
+ oep clock <probe> [-n 8] [--json] (fn 0's clock, core §7.6: the probe's uptime_ns and boot_id against this
27
19
  host's time - the reading with the shortest round trip of n)
28
20
  oep restart <probe> [--reopen-s S] (oep.probe.restart: the probe restarts; waits until it is back, its new boot_id)
29
21
 
@@ -37,7 +29,9 @@ from __future__ import annotations
37
29
  import argparse
38
30
  import sys
39
31
 
32
+ import getpass
40
33
  import json
34
+ import os
41
35
  import struct
42
36
 
43
37
  from . import catalog, config, core, dump, virtual_bench, host, link
@@ -63,7 +57,7 @@ def main(argv=None) -> int:
63
57
  parser = argparse.ArgumentParser(prog="oep", description="Open Embedded Probe: what a probe offers, its settings")
64
58
  sub = parser.add_subparsers(dest="command", required=True)
65
59
  _config_parser(sub)
66
- sp = sub.add_parser("speed", help="port_speed (oep-if-link §3, host guide §17): try faster rates on a UART bridge, "
60
+ sp = sub.add_parser("speed", help="port_speed (oep-if-link §3; this client's procedure): try faster rates on a UART bridge, "
67
61
  "print the report")
68
62
  sp.add_argument("probe", help="the probe's serial port (a UART bridge this host opens)")
69
63
  sp.add_argument("rates", nargs="?", default="", help="the candidates, comma-separated, in order of preference "
@@ -102,19 +96,19 @@ def main(argv=None) -> int:
102
96
  lt.add_argument("--low-latency", choices=("on", "off"), default="on", help="the serial driver's low-latency mode")
103
97
  lt.add_argument("--json", action="store_true", help="one JSON object per cell")
104
98
  pn = sub.add_parser("pins", help="find where a target is wired: classify the channels, scan the wire, find the "
105
- "reset line, suggest a slot (writes nothing without --save)")
99
+ "reset line, suggest labels (writes nothing without --save)")
106
100
  pn.add_argument("probe", help="a probe: a serial port, tcp://HOST:PORT or usb[:VID:PID[:SERIAL]]")
107
101
  pn.add_argument("--power", type=int, help="the gpio channel that powers the target: switched off, then on, to "
108
102
  "see which channels follow it (never touched without this)")
109
103
  pn.add_argument("--exclude", default="", help="channels never to read, scan or hold, comma-separated")
110
104
  pn.add_argument("--wire", choices=("swio", "rvswd", "swd"), default="swio")
111
- pn.add_argument("--steps", default="classify,hold,scan,identify,reset,slot",
112
- help="the steps to run, comma-separated (default all: classify, hold, scan, identify, reset, slot)")
113
- pn.add_argument("--save", action="store_true", help="write the suggested slot (config set, then save)")
114
- pn.add_argument("--slot", type=int, default=0, help="--save: the slot number (default 0)")
115
- pn.add_argument("--name", help="--save: the slot's name (default: the target family, else 'target')")
105
+ pn.add_argument("--steps", default="classify,hold,scan,identify,reset,labels",
106
+ help="the steps to run, comma-separated (default all: classify, hold, scan, identify, reset, labels)")
107
+ pn.add_argument("--save", action="store_true", help="write the suggested labels (config set, then save)")
108
+ pn.add_argument("--name", help="the host's connection profile name: labels <name>.nrst / <name>.power_hi (a probe "
109
+ "serving several targets; default: nrst / power_hi)")
116
110
  pn.add_argument("--json", action="store_true", help="the report as JSON (the steps' lines go to stderr)")
117
- ck = sub.add_parser("clock", help="the probe's clock (fn 0 clock, core §7.7) against this host's: lock-free")
111
+ ck = sub.add_parser("clock", help="the probe's clock (fn 0 clock, core §7.6) against this host's: lock-free")
118
112
  ck.add_argument("probe", help="a probe: a serial port, tcp://HOST:PORT or usb[:VID:PID[:SERIAL]]")
119
113
  ck.add_argument("-n", type=int, default=8, help="readings; the one with the shortest round trip is kept (default 8)")
120
114
  ck.add_argument("--json", action="store_true")
@@ -246,7 +240,7 @@ def _pins_cmd(args) -> int:
246
240
  try:
247
241
  core.take(hst, 10000, owner="oep pins")
248
242
  finder = pins.PinFinder(hst, wire=args.wire, power=args.power, exclude=exclude, say=say, probe=args.probe,
249
- save=args.save, slot=args.slot, name=args.name)
243
+ save=args.save, name=args.name)
250
244
  report = finder.run(steps=tuple(s for s in args.steps.split(",") if s))
251
245
  say(f"done in {report.elapsed_s:.1f} s")
252
246
  if args.json:
@@ -260,7 +254,7 @@ def _pins_cmd(args) -> int:
260
254
  return 2
261
255
  finally:
262
256
  hst.link.close()
263
- return 0 if report.found else 1
257
+ return 0 if report.resolution == 'unique-candidate' else 1
264
258
 
265
259
 
266
260
  # ---- oep linktest -------------------------------------------------------------------------------------------------
@@ -340,40 +334,16 @@ def _speed(args) -> int:
340
334
  # ---- oep config ----------------------------------------------------------------------------------------------------
341
335
 
342
336
  def _config_parser(sub) -> None:
343
- c = sub.add_parser("config", help="the probe's settings (oep.probe.config): slots, binds, save")
337
+ c = sub.add_parser("config", help="the probe's settings (oep.probe.config): labels, idle, disable, pass-throughs, "
338
+ "Wi-Fi / IPv4, its own settings, save")
344
339
  cs = c.add_subparsers(dest="action", required=True)
345
- show = cs.add_parser("show", help="the settings, what the probe declares, and the live slot / bind state")
340
+ show = cs.add_parser("show", help="the settings, what the probe declares, and the storage state")
346
341
  show.add_argument("probe")
347
342
  show.add_argument("--json", action="store_true")
348
- state = cs.add_parser("state", help="the live slot / bind / storage state (op state, lock-free)")
343
+ state = cs.add_parser("state", help="the storage state (op state, lock-free)")
349
344
  state.add_argument("probe")
350
345
  state.add_argument("--json", action="store_true")
351
- slot = cs.add_parser("slot", help="register a slot (a place a target is wired to)")
352
- slot.add_argument("probe")
353
- slot.add_argument("--slot", type=int, default=0, help="the slot number (default 0)")
354
- slot.add_argument("--name", required=True, help="1-32 of a-z 0-9 - _ (the oep://<probe>/<name> address)")
355
- slot.add_argument("--wire", help="rvswd or swio (or an fn); default: the probe's only wire")
356
- slot.add_argument("--pins", help="swdio,swclk (one pin on swio); default: the wire's only pin set")
357
- slot.add_argument("--attach", choices=sorted(config.ATTACH), default="host")
358
- slot.add_argument("--retry", type=float, default=0, help="at-boot: try again every N s while absent (0: never)")
359
- slot.add_argument("--mechanism", choices=sorted(config.MECHANISM), default="dmseq",
360
- help="the console's mechanism, or none (no console on this slot)")
361
- slot.add_argument("--max-speed", type=int, default=0,
362
- help="the line's ceiling in Hz for the probe's own attach (0: none); the target's, e.g. 1000000")
363
- slot.add_argument("--idle-clock", choices=sorted(config.IDLE_CLOCK), default="high",
364
- help="rvswd: SWCLK while the line rests (the target's: low on CH32L103 / V203)")
365
- slot.add_argument("--save", action="store_true", help="save after the change")
366
- bind = cs.add_parser("bind", help="the one stream a serial port carries (set it again to change it)")
367
- bind.add_argument("probe")
368
- bind.add_argument("--port", type=int, required=True, help="the serial port (its transport index, see show)")
369
- bind.add_argument("--stream", required=True, help="slot:NAME, slot:N or uart:FN")
370
- bind.add_argument("--save", action="store_true")
371
- plan = cs.add_parser("plan", help="the pins an interface keeps (its whole plan, kept as a setting)")
372
- plan.add_argument("probe")
373
- plan.add_argument("fn", help="an fn, or name#instance (the list instance, 0 = the first; #0 may be left out, core §7.2)")
374
- plan.add_argument("roles", nargs="+", help="ROLE=CHANNEL, ROLE a number or the interface's role name (rx, tx, line...)")
375
- plan.add_argument("--save", action="store_true")
376
- label = cs.add_parser("label", help="name a channel (a setting, read with config get)")
346
+ label = cs.add_parser("label", help="name a channel (a setting, read with config show)")
377
347
  label.add_argument("probe")
378
348
  label.add_argument("channel", type=int)
379
349
  label.add_argument("text")
@@ -382,119 +352,73 @@ def _config_parser(sub) -> None:
382
352
  idle.add_argument("probe")
383
353
  idle.add_argument("channel", type=int)
384
354
  idle.add_argument("mode", choices=sorted(config.IDLE))
385
- drive = idle.add_mutually_exclusive_group()
386
- drive.add_argument("--drive-ma", type=int, help="output modes: the strongest drive level of about this many mA or "
387
- "less, picked here from the gpio's describe drive_levels")
388
- drive.add_argument("--drive-level", type=int, help="output modes: a drive level number of this probe")
389
355
  idle.add_argument("--save", action="store_true")
390
- dis = cs.add_parser("disable", help="channels the probe never uses or touches (not on this board)")
356
+ dis = cs.add_parser("disable", help="channels the probe never drives or sets up (not on this board)")
391
357
  dis.add_argument("probe")
392
358
  dis.add_argument("channels", type=int, nargs="+")
393
359
  dis.add_argument("--save", action="store_true")
394
- uart = cs.add_parser("uart", help="a fixture UART's baud / format, applied whenever its plan has RX or TX")
395
- uart.add_argument("probe")
396
- uart.add_argument("fn", help="an fn, or name#instance of the oep.fixture.uart")
397
- uart.add_argument("baud", type=int)
398
- uart.add_argument("--format", default="8N1", help="data bits, parity, stop bits: 8N1 (default), 8E1, 7O2 ...")
399
- uart.add_argument("--save", action="store_true")
400
- rm = cs.add_parser("remove", help="remove one item (unset): slot N, bind PORT, plan FN, label CH, idle CH, uart FN, disable CH")
360
+ rm = cs.add_parser("remove", help="remove one item (unset): label CH, idle CH, disable CH")
401
361
  rm.add_argument("probe")
402
- rm.add_argument("kind", choices=sorted(config.ITEM))
403
- rm.add_argument("key", type=int)
362
+ rm.add_argument("kind", help="the item's name: label, idle, disable, passthrough, wifi, ... or a probe's own")
363
+ rm.add_argument("channel", type=int)
404
364
  rm.add_argument("--save", action="store_true")
405
- wifi = cs.add_parser("wifi", help="a Wi-Fi network the probe joins (the wifi item; the passphrase is never printed)")
365
+ wifi = cs.add_parser("wifi", help="a Wi-Fi network the probe tries, by entry (the passphrase asked, never shown)")
406
366
  wifi.add_argument("probe")
407
- wifi.add_argument("--index", type=int, help="the entry (tried in index order; below the probe's wifi_max)")
408
- wifi.add_argument("--ssid", help="the network's name (1-32 bytes)")
367
+ wifi.add_argument("entry", type=int)
368
+ wifi.add_argument("ssid")
409
369
  pw = wifi.add_mutually_exclusive_group()
410
- pw.add_argument("--pass-prompt", action="store_true", help="ask for the passphrase (no echo)")
411
- pw.add_argument("--pass-env", metavar="VAR", help="read the passphrase from environment variable VAR")
412
- pw.add_argument("--open", action="store_true", help="no passphrase: an open network")
413
- pw.add_argument("--from-env", action="store_true", help="every entry from OEP_WIFI_SSID_<n> / OEP_WIFI_PASS_<n> "
414
- "(n = the index; no PASS: open); entries whose SSID and passphrase presence already match are not "
415
- "sent again (a passphrase cannot be compared: --force sends them)")
416
- wifi.add_argument("--force", action="store_true", help="--from-env: send every entry, passphrases included")
370
+ pw.add_argument("--open", action="store_true", help="a network without a passphrase")
371
+ pw.add_argument("--keep", action="store_true", help="keep the entry's passphrase (pass_len 0xFF)")
372
+ pw.add_argument("--passphrase-env", metavar="VAR", help="take the passphrase from this environment variable")
417
373
  wifi.add_argument("--save", action="store_true")
418
- wun = cs.add_parser("wifi-unset", help="remove Wi-Fi entries (the wifi item of these indexes)")
419
- wun.add_argument("probe")
420
- wun.add_argument("--index", type=int, action="append", required=True, help="an entry's index (repeatable)")
421
- wun.add_argument("--save", action="store_true")
374
+ ip = cs.add_parser("ipv4", help="how a netif gets its IPv4 address: dhcp, or a.b.c.d/prefix")
375
+ ip.add_argument("probe")
376
+ ip.add_argument("address", help="dhcp, or a.b.c.d/prefix")
377
+ ip.add_argument("--netif", default="wifi", help="the netif's name (describe's netif list; default wifi)")
378
+ ip.add_argument("--gateway")
379
+ ip.add_argument("--dns")
380
+ ip.add_argument("--save", action="store_true")
381
+ net = cs.add_parser("network", help="the network now: the TCP port, each netif's state and address (lock-free)")
382
+ net.add_argument("probe")
383
+ net.add_argument("--json", action="store_true")
384
+ pt = cs.add_parser("passthrough", help="a serial port carries a fixture UART's receive outside the OEP frames")
385
+ pt.add_argument("probe")
386
+ pt.add_argument("port", type=int, help="fn 0's transport index of a serial port")
387
+ pt.add_argument("uart", help="the fixture UART: fn number or name#instance")
388
+ pt.add_argument("rx", type=int, help="the RX channel")
389
+ pt.add_argument("--baud", type=int, default=115200)
390
+ pt.add_argument("--format", type=int, default=0)
391
+ pt.add_argument("--save", action="store_true")
392
+ cp = cs.add_parser("console-passthrough", help="a serial port carries the target's debug console outside the frames")
393
+ cp.add_argument("probe")
394
+ cp.add_argument("port", type=int, help="fn 0's transport index of a serial port")
395
+ cp.add_argument("wire", help="the wire: fn number or name#instance (oep.wire.rvswd / oep.wire.swio)")
396
+ cp.add_argument("swdio", type=int)
397
+ cp.add_argument("swclk", type=int, nargs="?", default=0xFFFF)
398
+ cp.add_argument("--console", required=True, help="the console: fn number or name#instance")
399
+ cp.add_argument("--mechanism", required=True, help="the console's mechanism by name (sdi, dmdata, dmseq, ...)")
400
+ cp.add_argument("--max-speed", type=int, default=1_000_000)
401
+ cp.add_argument("--save", action="store_true")
402
+ own = cs.add_parser("set", help="a probe's own setting by its name (reverse-DNS), its form from describe")
403
+ own.add_argument("probe")
404
+ own.add_argument("name")
405
+ own.add_argument("value", help="text, a number (u32), 0 / 1 (bool), a.b.c.d (ipv4) or hex (bytes)")
406
+ own.add_argument("--key", type=int, default=0)
407
+ own.add_argument("--save", action="store_true")
422
408
  for name in ("save", "erase"):
423
409
  cs.add_parser(name, help=f"{name} the stored settings").add_argument("probe")
424
410
 
425
411
 
426
- def _pins(hst, fn: int, text: str | None, wire: str) -> tuple[int, int]:
427
- if text:
428
- parts = [int(x, 0) for x in text.split(",")]
429
- return (parts[0], parts[1] if len(parts) > 1 else 0xFFFF)
430
- groups = []
431
- for tag, v in core.describe(hst, fn):
432
- if tag & 0x7F == catalog.CHANNEL_GROUP:
433
- roles = dict(catalog.unpack_channel_group(v)[1])
434
- groups.append((roles.get(1, 0xFFFF), roles.get(2, 0xFFFF)))
435
- if len(groups) != 1:
436
- raise SystemExit(f"{wire}: {len(groups)} pin sets on this probe - name one with --pins")
437
- return groups[0]
438
-
439
-
440
- def _plan_fn(hst, spec: str) -> tuple[int, dict[str, int]]:
441
- """fn and its role names (from the registry) for `spec`: an fn number, or name[#instance] (the list's instance,
442
- 0-based, core §7.2)."""
443
- from . import registry as reg
444
- if spec.isdigit():
445
- fn = int(spec)
446
- name = next((e.name for e in core.list_entries(hst) if e.fn == fn), "")
447
- else:
448
- name, _, k = spec.partition("#")
449
- entries = [e for e in core.list_entries(hst, name, exact=True)]
450
- if not entries:
451
- raise SystemExit(f"the probe offers no {name}")
452
- instance = int(k) if k else 0
453
- fn = next((e.fn for e in entries if e.instance == instance), None)
454
- if fn is None:
455
- raise SystemExit(f"{name}: instances {sorted(e.instance for e in entries)}, not {instance}")
456
- iface = reg.INTERFACES.get(name)
457
- roles = dict(iface.enum.get("role", {})) if iface else {}
458
- return fn, roles
459
-
460
-
461
- def _wire_fn(hst, wire: str | None) -> int:
462
- if wire is None:
463
- wires = [e for e in core.list_entries(hst, "oep.wire") if e.name in ("oep.wire.rvswd", "oep.wire.swio")]
464
- if len(wires) != 1:
465
- raise SystemExit("this probe has " + (", ".join(f"{e.name} (fn {e.fn})" for e in wires) or "no RISC-V wire")
466
- + ": name one with --wire")
467
- return wires[0].fn
468
- name = wire if wire.startswith("oep.") else f"oep.wire.{wire}"
469
- if wire.isdigit():
470
- return int(wire)
471
- try:
472
- return core.find(hst, name)
473
- except LookupError:
474
- raise SystemExit(f"this probe does not offer {name}") from None
475
-
476
-
477
- def _stream(spec: str, slots: dict[str, int]) -> tuple[str, int]:
478
- kind, _, key = spec.partition(":")
479
- if kind not in config.STREAM or not key:
480
- raise SystemExit(f"--stream {spec}: want slot:NAME, slot:N or uart:FN")
481
- if kind == "slot" and not key.isdigit():
482
- if key not in slots:
483
- raise SystemExit(f"--stream {spec}: no slot named {key} (see oep config show)")
484
- return kind, slots[key]
485
- return kind, int(key)
486
-
487
-
488
412
  def _change(hst, cfg, items, save: bool) -> None:
489
413
  core.take(hst, 3000, owner="oep config")
490
414
  try:
491
- h = cfg.set(items)
492
- print(f"set: hash 0x{h:08x}")
415
+ gen = cfg.set(items)
416
+ print(f"set: generation {gen}")
493
417
  if save:
494
418
  if cfg.needs_save():
495
- print(f"saved: hash 0x{cfg.save():08x}")
419
+ print(f"saved: generation {cfg.save()}")
496
420
  else:
497
- print("saved already (storage_hash is the settings' hash): not written")
421
+ print("saved already (saved_generation is the current generation): not written")
498
422
  finally:
499
423
  hst.end()
500
424
 
@@ -507,63 +431,50 @@ def _config(args) -> int:
507
431
  return _show(hst, cfg, args.json)
508
432
  if args.action == "state":
509
433
  return _state(cfg, args.json)
510
- if args.action == "slot":
511
- fn = _wire_fn(hst, args.wire)
512
- args.wire = args.wire or str(fn)
513
- it = config.Slot(slot=args.slot, wire_fn=fn, pins=_pins(hst, fn, args.pins, args.wire), name=args.name,
514
- attach=args.attach, retry_s=args.retry, mechanism=args.mechanism,
515
- max_speed=args.max_speed, idle_clock=args.idle_clock)
516
- _change(hst, cfg, [it], args.save)
517
- elif args.action == "bind":
518
- slots = {it.name: it.slot for it in cfg.items() if isinstance(it, config.Slot)}
519
- _change(hst, cfg, [config.Bind(port=args.port, stream=_stream(args.stream, slots))], args.save)
520
- elif args.action == "plan":
521
- fn, roles = _plan_fn(hst, args.fn)
522
- items = []
523
- for spec in args.roles:
524
- role, _, ch = spec.partition("=")
525
- if not ch:
526
- raise SystemExit(f"{spec}: want ROLE=CHANNEL")
527
- number = int(role) if role.isdigit() else roles.get(role.lower())
528
- if number is None:
529
- raise SystemExit(f"{role}: not a role of fn {fn} (roles: {', '.join(sorted(roles)) or 'numbers only'})")
530
- items.append(config.Plan(fn=fn, role=number, channel=int(ch, 0)))
531
- _change(hst, cfg, items, args.save)
532
- elif args.action == "uart":
533
- fn, _ = _plan_fn(hst, args.fn)
534
- from .fixture import FixtureUart
535
- fmt = args.format.upper()
536
- try:
537
- byte = FixtureUart.format_byte(int(fmt[0]), fmt[1], int(fmt[2]))
538
- except (KeyError, ValueError, IndexError):
539
- raise SystemExit(f"--format {args.format}: want <data bits><parity><stop bits>, e.g. 8N1, 8E2, 7O1") from None
540
- _change(hst, cfg, [config.Uart(fn=fn, baud=args.baud, format=byte)], args.save)
541
- elif args.action == "label":
434
+ if args.action == "label":
542
435
  _change(hst, cfg, [config.Label(channel=args.channel, text=args.text)], args.save)
543
436
  elif args.action == "idle":
544
- from .fixture import Drive, Gpio
545
- drive = Drive.level(args.drive_level) if args.drive_level is not None else None
546
- if args.drive_ma is not None:
547
- levels = Gpio(hst).drive_levels()
548
- if levels is None:
549
- raise SystemExit("--drive-ma: this probe declares no drive_levels (its strength cannot be chosen)")
550
- drive = levels.at_most(args.drive_ma)
551
- if drive is not None and args.mode not in ("output-low", "output-high"):
552
- raise SystemExit(f"--drive-ma / --drive-level go with output-low / output-high, not {args.mode}")
553
- _change(hst, cfg, [config.Idle(channel=args.channel, mode=args.mode, drive=drive)], args.save)
437
+ _change(hst, cfg, [config.Idle(channel=args.channel, mode=args.mode)], args.save)
554
438
  elif args.action == "disable":
555
439
  _change(hst, cfg, [config.Disable(channel=ch) for ch in args.channels], args.save)
556
440
  elif args.action == "remove":
557
- _change(hst, cfg, [config.remove(args.kind, args.key)], args.save)
441
+ _change(hst, cfg, [config.remove(args.kind, args.channel)], args.save)
558
442
  elif args.action == "wifi":
559
- _wifi_set(hst, cfg, args)
560
- elif args.action == "wifi-unset":
561
- _change(hst, cfg, [config.remove("wifi", i) for i in args.index], args.save)
443
+ if args.keep:
444
+ passphrase = None
445
+ elif args.open:
446
+ passphrase = ""
447
+ elif args.passphrase_env:
448
+ passphrase = os.environ[args.passphrase_env]
449
+ else:
450
+ passphrase = getpass.getpass(f"passphrase for {args.ssid}: ")
451
+ _change(hst, cfg, [config.Wifi(entry=args.entry, ssid=args.ssid, passphrase=passphrase)], args.save)
452
+ elif args.action == "ipv4":
453
+ address = None if args.address == "dhcp" else args.address
454
+ _change(hst, cfg, [config.Ipv4(netif=args.netif, address=address, gateway=args.gateway, dns=args.dns)],
455
+ args.save)
456
+ elif args.action == "network":
457
+ return _network(cfg, args.json)
458
+ elif args.action == "passthrough":
459
+ _change(hst, cfg, [config.Passthrough(port=args.port, fn=_fn(hst, args.uart), rx=args.rx, baud=args.baud,
460
+ format=args.format)], args.save)
461
+ elif args.action == "console-passthrough":
462
+ from . import console as _console
463
+ cfn = _fn(hst, args.console)
464
+ names = [v.decode() for t, v in core.describe(hst, cfn) if t == _console.Console.TAG_MECHANISM]
465
+ if args.mechanism not in names:
466
+ raise SystemExit(f"the console declares no mechanism {args.mechanism} ({', '.join(names)})")
467
+ mech = names.index(args.mechanism) + 1 # the place in describe's list, from 1 (core §7.7)
468
+ _change(hst, cfg, [config.ConsolePassthrough(port=args.port, wire_fn=_fn(hst, args.wire), swdio=args.swdio,
469
+ swclk=args.swclk, max_speed=args.max_speed, console_fn=cfn,
470
+ mechanism=mech)], args.save)
471
+ elif args.action == "set":
472
+ _change(hst, cfg, [config.Own(args.name, _own_value(cfg, args.name, args.value), args.key)], args.save)
562
473
  else:
563
474
  core.take(hst, 3000, owner="oep config")
564
475
  try:
565
476
  if args.action == "save":
566
- print(f"saved: hash 0x{cfg.save():08x}")
477
+ print(f"saved: generation {cfg.save()}")
567
478
  else:
568
479
  cfg.erase()
569
480
  print("erased (the settings now in effect stay until the probe restarts)")
@@ -577,154 +488,80 @@ def _config(args) -> int:
577
488
  hst.link.close()
578
489
 
579
490
 
580
- def _wifi_set(hst, cfg, args) -> None:
581
- """oep config wifi (host guide §15.1): the passphrase from a prompt without echo or an environment variable, never
582
- from the command line, never printed; without one the entry keeps its passphrase (pass_len 0xFF) - an entry that
583
- does not exist yet needs --pass-prompt, --pass-env or --open."""
584
- import os
585
- decl = cfg.describe()
586
- if config.ITEM["wifi"] not in decl.items:
587
- raise SystemExit("this probe has no wifi item (describe's items)")
588
- if args.from_env:
589
- wanted = config.wifi_from_env(count=decl.wifi_max)
590
- if not wanted:
591
- raise SystemExit("--from-env: no OEP_WIFI_SSID_<n> set (n = 0 .. wifi_max - 1)")
592
- have = {it.index: it for it in cfg.items() if isinstance(it, config.Wifi)}
593
- send = [w for w in wanted if args.force or not config.same_items([have[w.index]] if w.index in have else [], [w])]
594
- for w in wanted:
595
- print(f"wifi {w.index}: " + ("sent" if w in send else "unchanged (ssid and passphrase presence match)"))
596
- if send:
597
- _change(hst, cfg, send, args.save)
598
- elif args.save and cfg.needs_save():
599
- core.take(hst, 3000, owner="oep config")
600
- try:
601
- print(f"saved: hash 0x{cfg.save():08x}")
602
- finally:
603
- hst.end()
604
- return
605
- if args.index is None or args.ssid is None:
606
- raise SystemExit("oep config wifi: --index and --ssid (or --from-env)")
607
- if not 0 <= args.index < max(decl.wifi_max, 1):
608
- raise SystemExit(f"--index {args.index}: 0 to {decl.wifi_max - 1} (the probe's wifi_max is {decl.wifi_max})")
609
- if args.pass_prompt:
610
- import getpass
611
- passphrase = getpass.getpass(f"passphrase for {args.ssid!r} (not shown): ")
612
- elif args.pass_env:
613
- passphrase = os.environ.get(args.pass_env)
614
- if passphrase is None:
615
- raise SystemExit(f"--pass-env {args.pass_env}: not set")
616
- elif args.open:
617
- passphrase = None
618
- else:
619
- if not any(isinstance(it, config.Wifi) and it.index == args.index for it in cfg.items()):
620
- raise SystemExit(f"wifi entry {args.index} does not exist yet: give --pass-prompt, --pass-env VAR or --open")
621
- passphrase = config.KEEP
622
- it = config.Wifi(index=args.index, ssid=args.ssid, passphrase=passphrase)
623
- try:
624
- it.value()
625
- except ValueError as e:
626
- raise SystemExit(str(e)) from None
627
- _change(hst, cfg, [it], args.save)
628
- print(f"wifi {args.index}: {it.shown()['ssid']!r}, passphrase {it.shown()['passphrase']}")
491
+ def _fn(hst, ref: str) -> int:
492
+ """A fn by number, or by `name#instance` (host guide §5.3; #0 may be left out) resolved through list."""
493
+ if ref.isdigit():
494
+ return int(ref)
495
+ name, _, instance = ref.partition("#")
496
+ for e in core.list_entries(hst, name, exact=True):
497
+ if e.instance == int(instance or 0):
498
+ return e.fn
499
+ raise SystemExit(f"no {ref} on this probe")
500
+
501
+
502
+ def _own_value(cfg, name: str, text: str):
503
+ """A probe's own setting's value from the command line, by the form its describe declares (probe.config §1.6)."""
504
+ form = cfg.describe().by_name(name).form
505
+ if form == config.FORM["u32"]:
506
+ return int(text, 0)
507
+ if form == config.FORM["bool"]:
508
+ return text.lower() in ("1", "true", "on", "yes")
509
+ if form == config.FORM["bytes"]:
510
+ return bytes.fromhex(text)
511
+ return text
512
+
513
+
514
+ def _network(cfg, as_json: bool) -> int:
515
+ """op network (lock-free): the TCP port and each netif's state."""
516
+ net = cfg.network()
517
+ if as_json:
518
+ print(json.dumps({"tcp_port": net.tcp_port, "netifs": [vars(n) for n in net.netifs]}, indent=2))
519
+ return 0
520
+ print(f"tcp port: {net.tcp_port or 'not listening'}")
521
+ for n in net.netifs:
522
+ where = f" {n.address} gateway {n.gateway} dns {n.dns}" if n.address else ""
523
+ entry = f" entry {n.entry}" if n.entry is not None else ""
524
+ rssi = f" {n.rssi} dBm" if n.state == "up" and n.netif == "wifi" else ""
525
+ why = f" ({n.reason})" if n.reason != "none" else ""
526
+ print(f" {n.netif}: {n.state}{why}{entry}{rssi}{where}")
527
+ return 0
629
528
 
630
529
 
631
530
  def _plain(o):
632
- if hasattr(o, "shown"):
633
- return o.shown() # a wifi item: never its passphrase
634
531
  return {k: (v.hex() if isinstance(v, bytes) else v) for k, v in vars(o).items()} if hasattr(o, "__dict__") \
635
532
  else list(o)
636
533
 
637
534
 
638
- def _state_dict(st) -> dict:
639
- return {**{k: v for k, v in vars(st).items() if k not in ("slots", "binds", "wifi")},
640
- "slots": [_plain(s) for s in st.slots], "binds": [_plain(b) for b in st.binds],
641
- **({"wifi": vars(st.wifi)} if st.wifi is not None else {})}
642
-
643
-
644
535
  def _state(cfg, as_json: bool) -> int:
645
- """The live state alone (op state, lock-free): what a monitor polls."""
536
+ """The storage state alone (op state, lock-free)."""
646
537
  st = cfg.state()
647
538
  if as_json:
648
- print(json.dumps(_state_dict(st), indent=2, default=lambda o: o.hex() if isinstance(o, bytes) else str(o)))
539
+ print(json.dumps(vars(st), indent=2))
649
540
  return 0
650
- why = f" ({st.unreadable})" if st.unreadable else ""
651
- print(f"storage: {st.storage}{why}, saved hash 0x{st.saved_hash:08x}")
652
- for s in st.slots:
653
- print(f" slot {s.slot}: {s.state}" + (f", connection {s.connection}" if s.connection else "")
654
- + (f", tried at {s.last_try_at_ns / 1e9:.3f} s" if s.last_try_at_ns is not None else ""))
655
- for b in st.binds:
656
- print(f" port {b.port}: {b.flow}")
657
- if st.wifi is not None:
658
- print(f"wifi: {st.wifi.text()}")
541
+ why = f" ({st.reason})" if st.reason else ""
542
+ print(f"storage: {st.storage}{why}, saved generation {st.saved_generation}")
659
543
  return 0
660
544
 
661
545
 
662
- def _tids(hst) -> dict[int, str]:
663
- """connection -> its target_id as hex, from every wire's connections (the slot's target is the host's to check,
664
- probe.config §1.1, debug §2.1); best effort."""
665
- from . import riscv
666
- out = {}
667
- for e in core.list_entries(hst):
668
- if e.name in ("oep.wire.rvswd", "oep.wire.swio"):
669
- try:
670
- for c in riscv.Wire(hst, e.name).connections():
671
- if c.target_id:
672
- out[c.connection] = c.target_id[1][::-1].hex()
673
- except host.OepError:
674
- pass
675
- return out
676
-
677
-
678
546
  def _show(hst, cfg, as_json: bool) -> int:
679
- h, _ = cfg.get()
547
+ gen, _ = cfg.get()
680
548
  items = cfg.items()
681
549
  decl = cfg.describe()
682
- st = cfg.state()
683
- kinds = {i: _name(k) for i, k, _ in core.transports(hst)}
550
+ st = cfg.state() if cfg.offers(cfg.STATE) else None
684
551
  if as_json:
685
- out = {"hash": h, "items": [dict(type=type(i).__name__, **_plain(i)) if hasattr(i, "__dict__") else _plain(i)
686
- for i in items],
687
- "declared": _plain(decl), "state": _state_dict(st), "transports": kinds}
552
+ out = {"generation": gen, "items": [dict(type=type(i).__name__, **_plain(i)) if hasattr(i, "__dict__")
553
+ else _plain(i) for i in items],
554
+ "declared": _plain(decl), "state": vars(st) if st else None}
688
555
  print(json.dumps(out, indent=2, default=lambda o: o.hex() if isinstance(o, bytes) else str(o)))
689
556
  return 0
690
- why = f" ({st.unreadable})" if st.unreadable else ""
691
- print(f"storage: {st.storage}{why} ({decl.storage_bytes} bytes), saved hash 0x{st.saved_hash:08x}; now 0x{h:08x}")
692
- print("transports: " + ", ".join(f"{i} {k}" for i, k in kinds.items()))
693
- by_slot = {s.slot: s for s in st.slots}
694
- tids = _tids(hst) if any(s.connection for s in st.slots) else {}
695
- print(f"slots (up to {decl.slots_max}):")
696
- for it in items:
697
- if isinstance(it, config.Slot):
698
- s = by_slot.get(it.slot)
699
- pins = f"{it.pins[0]}" if it.pins[1] == 0xFFFF else f"{it.pins[0]},{it.pins[1]}"
700
- retry = f" retry {it.retry_s:g} s" if it.attach == "at-boot" else ""
701
- retry += (f" max {it.max_speed} Hz" if it.max_speed else "") + (" idle-low" if it.idle_clock == "low" else "")
702
- live = ""
703
- if s:
704
- tried = "never tried" if s.last_try_at_ns is None else f"tried at {s.last_try_at_ns / 1e9:.3f} s"
705
- tid = f" target_id {tids[s.connection]}" if s.connection in tids else ""
706
- live = f" -> {s.state}" + (f" (connection {s.connection})" if s.connection else f" ({tried})") + tid
707
- print(f" {it.slot} {it.name}: fn {it.wire_fn} pins {pins} {it.attach}{retry} {it.mechanism}{live}")
708
- by_port = {b.port: b for b in st.binds}
709
- print("binds:")
710
- names = {it.slot: it.name for it in items if isinstance(it, config.Slot)}
711
- for it in items:
712
- if isinstance(it, config.Bind):
713
- b = by_port.get(it.port)
714
- k, i = it.stream
715
- stream = f"slot:{names.get(i, i)}" if k == "slot" else f"{k}:{i}"
716
- live = f" -> {b.flow}" if b else ""
717
- print(f" port {it.port} ({kinds.get(it.port, '?')}): {stream}{live}")
718
- if config.ITEM["wifi"] in decl.items:
719
- print(f"wifi (up to {decl.wifi_max}): " + (st.wifi.text() if st.wifi is not None else "no state"))
720
- for it in items:
721
- if isinstance(it, config.Wifi):
722
- s = it.shown()
723
- use = " <- in use" if st.wifi is not None and st.wifi.entry == it.index else ""
724
- print(f" {s['index']} {s['ssid']!r}: passphrase {s['passphrase']}{use}")
557
+ if st is not None:
558
+ why = f" ({st.reason})" if st.reason else ""
559
+ print(f"storage: {st.storage}{why} ({decl.storage_bytes} bytes), saved generation {st.saved_generation}; "
560
+ f"now {gen}")
561
+ else:
562
+ print(f"no storage; generation {gen}")
725
563
  for it in items:
726
- if not isinstance(it, (config.Slot, config.Bind, config.Wifi)):
727
- print(f" {it}")
564
+ print(f" {it}")
728
565
  return 0
729
566
 
730
567