oep-client-python 0.0.46__py3-none-any.whl → 0.0.47__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.46"
5
+ __version__ = "0.0.47"
oep_client/acquire.py CHANGED
@@ -11,14 +11,25 @@ probe's facts out.
11
11
 
12
12
  Logic channels go to oep.fixture.logic, analog ones to oep.fixture.analog; with both, oep.fixture.capture-group
13
13
  starts them together, a trigger on either track as the group's trigger source (capture §4.4). A line is named as a
14
- script keeps it - the probe's channel name (fn 0's channel_name, core §7.5) or a port's line label (S.N, host guide
15
- §5.3) - and looked up on the session (a channel number holds for one boot only); a track's channels are its plan roles
14
+ script keeps it - the probe's channel name (fn 0's channel_name, core §7.5) or a port's line label (host guide §5.3:
15
+ S.N by its role, S@P by the target pin) - and looked up on the session (a channel number holds for one boot only); a track's channels are its plan roles
16
16
  0..C-1 in the order given. The plans are released afterwards.
17
17
 
18
18
  Modes: "oneshot" (the probe's memory), "repeat" (`segments` acquisitions read and released as they come: with a
19
19
  trigger one part each, layout "segments"; without, a timeline), "stream" (for `seconds`, sent as taken,
20
20
  put together on disk while it runs: a timeline, cut where data was lost).
21
21
 
22
+ A stream can run while the script does its steps on the same session (marking sections, sending console commands):
23
+ `during=steps` in place of `seconds` streams until steps(wait) returns, `wait(seconds)` collecting while it waits.
24
+ Steps should call wait often (the probe holds only its stream buffer meanwhile); a loss with on_loss "stop" raises
25
+ StreamStopped out of wait, and the stream ends there.
26
+
27
+ def steps(wait):
28
+ for duty in (64, 128):
29
+ with rec.section(2, f"duty={duty}"):
30
+ console.write(f"PWM {duty}\n".encode()); wait(0.5)
31
+ res = acquire.capture(hst, acquire.Request(chans, 1_000_000, 0, mode="stream", during=steps))
32
+
22
33
  Times: tick 0 of a part is its first logic sample, `start_ns` on the probe's clock; an analog channel starts at
23
34
  t0_ticks = (its segment's start_ns - that) plus its skew_ns, in ticks. These are the probe's estimates; each track's
24
35
  uncertainty is kept. ADC values are kept raw with the probe's linear conversion; the input range, reference, Vrefint
@@ -37,13 +48,14 @@ import array
37
48
  import math
38
49
  import struct
39
50
  import sys
51
+ from collections.abc import Callable
40
52
  from dataclasses import dataclass, field
41
53
  from fractions import Fraction
42
54
  from pathlib import Path
43
55
 
44
56
  from . import registry as _reg
45
57
 
46
- __all__ = ["Analog", "Bits", "Changes", "OnDisk", "Part", "Request", "Result", "capture", "capture_at"]
58
+ __all__ = ["Analog", "Bits", "Changes", "OnDisk", "Part", "Request", "Result", "StreamStopped", "capture", "capture_at"]
47
59
 
48
60
  TRIGGERS = ("high", "low", "rise", "fall", "both")
49
61
  ANALOG_TRIGGERS = ("high", "low", "rise", "fall")
@@ -52,7 +64,8 @@ ANALOG_TRIGGERS = ("high", "low", "rise", "fall")
52
64
  @dataclass
53
65
  class Request:
54
66
  channels: list[tuple[str, str]] # logic: (name to record, the line): the probe's channel name (fn 0's
55
- # channel_name, GPIO47) or a port's line label (S.N, x035.swdio), in order
67
+ # channel_name, GPIO47) or a port's line label (S.N, x035.swdio; S@P,
68
+ # x035@pb9), in order
56
69
  rate: int # logic samples per second asked for (the probe may answer another)
57
70
  samples: int
58
71
  trigger: tuple | None = None # (logic channel, one of TRIGGERS) or (analog channel, rise / fall / high /
@@ -69,6 +82,8 @@ class Request:
69
82
  segments: int | None = None # repeat: how many acquisitions
70
83
  on_loss: str = "stop" # stream: "stop" at the first loss, or "continue" to the end
71
84
  spool_dir: str | None = None # stream: where its channels are put together on disk while it runs
85
+ during: Callable[[Callable[[float], None]], None] | None = None # stream: instead of `seconds`, runs until this
86
+ # returns - called with wait(seconds), which collects while it waits
72
87
 
73
88
  def __post_init__(self):
74
89
  names = [n for n, _ in self.channels] + [n for n, *_ in self.analog]
@@ -92,8 +107,10 @@ class Request:
92
107
  raise ValueError("repeat: logic channels only")
93
108
  elif self.segments is not None:
94
109
  raise ValueError("segments is for repeat only")
95
- if self.mode == "stream" and not (self.seconds and self.seconds > 0):
96
- raise ValueError("a stream needs seconds (how long)")
110
+ if self.during is not None and (self.mode != "stream" or self.seconds is not None):
111
+ raise ValueError("during is for a stream, in place of seconds")
112
+ if self.mode == "stream" and self.during is None and not (self.seconds and self.seconds > 0):
113
+ raise ValueError("a stream needs seconds (how long) or during")
97
114
  if self.arm not in ("pretrigger", "now"):
98
115
  raise ValueError(f"arm {self.arm!r}: pretrigger or now")
99
116
  logic = {n for n, _ in self.channels}
@@ -567,7 +584,8 @@ def _describe(core, hst, fn: int) -> list[tuple[int, bytes]]:
567
584
 
568
585
  def _channels(core, hst, lines: list[str]) -> list[int]:
569
586
  """The probe's channel numbers (valid for this boot) of lines named as kept in scripts: a channel name (fn 0's
570
- channel_name, core §7.5; GPIO47) or a port's line label (S.N, host guide §5.3; x035.swdio). ASCII case aside."""
587
+ channel_name, core §7.5; GPIO47) or a port's line label (host guide §5.3: S.N by its role, x035.swdio; S@P by the target pin, x035@pb9). ASCII
588
+ case aside."""
571
589
  if not lines:
572
590
  return []
573
591
  from . import registry, settings
@@ -581,11 +599,13 @@ def _channels(core, hst, lines: list[str]) -> list[int]:
581
599
  labels = None
582
600
  out = []
583
601
  for line in lines:
584
- port_line = settings.port_line(line)
585
- if port_line is not None:
602
+ label = settings.parse_label(line)
603
+ if label is not None:
586
604
  if labels is None:
587
605
  labels = settings.labels(hst)
588
- ch = settings.line_from_labels(labels, port_line[1], port_line[0])
606
+ port, role, pin = label
607
+ ch = (settings.line_from_labels(labels, role, port) if role
608
+ else settings.pin_from_labels(labels, pin, port))
589
609
  if ch is None:
590
610
  raise ValueError(f"{line}: no channel carries that label (host guide §5.3)")
591
611
  else:
@@ -1133,6 +1153,35 @@ STREAM_MAX_DELAY_MS = 20 # ... or after this long. With (0, 0) the P4X sent eac
1133
1153
  # quiet lines) as a transfer of its own, 2000 a second, and dropped 35 % it could not send
1134
1154
 
1135
1155
 
1156
+ class StreamStopped(Exception):
1157
+ """Raised by a stream's `wait` (Request.during) when data was lost and on_loss is "stop": the stream stops there."""
1158
+
1159
+
1160
+ def _drive(req: Request, poll, lost) -> None:
1161
+ """Collect a started stream: for req.seconds, or while req.during(wait) runs - its `wait(seconds)` collecting
1162
+ meanwhile. poll(seconds) takes what comes, lost() says whether anything was lost; with on_loss "stop" a loss ends
1163
+ it (a StreamStopped out of wait, caught here)."""
1164
+ import time
1165
+
1166
+ def wait(seconds: float) -> None:
1167
+ deadline = time.monotonic() + seconds
1168
+ while True:
1169
+ now = time.monotonic()
1170
+ if now >= deadline:
1171
+ return
1172
+ poll(min(STREAM_POLL_S, deadline - now))
1173
+ if req.on_loss == "stop" and lost():
1174
+ raise StreamStopped("data was lost while streaming")
1175
+
1176
+ try:
1177
+ if req.during is None:
1178
+ wait(req.seconds)
1179
+ else:
1180
+ req.during(wait)
1181
+ except StreamStopped:
1182
+ pass
1183
+
1184
+
1136
1185
  def _collect_group(group, tracks, link, req: Request, oc, spool=None):
1137
1186
  """_collect for a group's streaming tracks (capture §4.1: each subscribed, started at once): -> ({fn: Received},
1138
1187
  seconds). The first track's Received goes to the spool as it comes; a loss in any track stops it with "stop"."""
@@ -1141,20 +1190,22 @@ def _collect_group(group, tracks, link, req: Request, oc, spool=None):
1141
1190
  t.subscribe(STREAM_MIN_BYTES, STREAM_MAX_DELAY_MS)
1142
1191
  group.start(tracks)
1143
1192
  t_read = time.monotonic()
1144
- deadline, got = t_read + req.seconds, {t.fn: oc.Received() for t in tracks}
1193
+ got = {t.fn: oc.Received() for t in tracks}
1145
1194
  lead = tracks[0].fn
1146
- while True:
1147
- now = time.monotonic()
1148
- if now >= deadline:
1149
- break
1150
- got = group.stream(link, tracks, seconds=min(STREAM_POLL_S, deadline - now), into=got)
1195
+
1196
+ def poll(seconds):
1197
+ group.stream(link, tracks, seconds=seconds, into=got)
1151
1198
  if spool is not None:
1152
1199
  spool.take(got[lead])
1153
- lost = (spool is not None and spool.lost) or any(
1200
+
1201
+ def lost():
1202
+ return (spool is not None and spool.lost) or any(
1154
1203
  g.gaps or (g.start or 0) > 0 for fn, g in got.items() if spool is None or fn != lead)
1155
- if req.on_loss == "stop" and lost:
1156
- break
1157
- group.stop()
1204
+
1205
+ try:
1206
+ _drive(req, poll, lost)
1207
+ finally:
1208
+ group.stop() # also when the caller's `during` raised: the probe is left stopped
1158
1209
  got = group.finish(link, tracks, got)
1159
1210
  return got, time.monotonic() - t_read
1160
1211
 
@@ -1182,25 +1233,26 @@ def _analog_runs(an, acfg, got) -> tuple[list, list]:
1182
1233
 
1183
1234
 
1184
1235
  def _collect(track, link, req: Request, oc, spool=None):
1185
- """Subscribe, start, collect for req.seconds, stop and collect the rest -> (Received, seconds). A loss stops it
1186
- at once with on_loss "stop"."""
1236
+ """Subscribe, start, collect for req.seconds (or while req.during runs), stop and collect the rest -> (Received,
1237
+ seconds). A loss stops it at once with on_loss "stop"."""
1187
1238
  import time
1188
1239
  track.subscribe(STREAM_MIN_BYTES, STREAM_MAX_DELAY_MS) # batched: a capture wants bandwidth, not a frame per block
1189
1240
  track.start()
1190
1241
  t_read = time.monotonic()
1191
- deadline, got = t_read + req.seconds, oc.Received()
1192
- while True: # stream() renews the lease across calls on the same Received
1193
- now = time.monotonic()
1194
- if now >= deadline:
1195
- break
1196
- got = track.stream(link, seconds=min(STREAM_POLL_S, deadline - now), into=got)
1242
+ got = oc.Received()
1243
+
1244
+ def poll(seconds): # stream() renews the lease across calls on the same Received
1245
+ track.stream(link, seconds=seconds, into=got)
1197
1246
  if spool is not None: # put together as it comes: memory holds a poll's worth, not the stream
1198
1247
  spool.take(got)
1199
- if req.on_loss == "stop" and spool.lost:
1200
- break
1201
- elif req.on_loss == "stop" and (got.gaps or (got.start or 0) > 0):
1202
- break
1203
- track.stop()
1248
+
1249
+ def lost():
1250
+ return spool.lost if spool is not None else bool(got.gaps or (got.start or 0) > 0)
1251
+
1252
+ try:
1253
+ _drive(req, poll, lost)
1254
+ finally:
1255
+ track.stop()
1204
1256
  got = track.finish(link, got)
1205
1257
  return got, time.monotonic() - t_read
1206
1258
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: oep-client-python
3
- Version: 0.0.46
3
+ Version: 0.0.47
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
@@ -1,6 +1,6 @@
1
- oep_client/__init__.py,sha256=0u8trdN_O8C6tby9PUqF3CWE3Y-4kdFOWpKRJPQp2Jw,164
1
+ oep_client/__init__.py,sha256=SJtIdmZwlVWpTrZFXfUmNy4LSawSb5TuRDhPAnsFzcI,164
2
2
  oep_client/__main__.py,sha256=kbXBtEmFdhRXbVqXEbWCO-_5aDa-KyqMcW2HLZb5fjc,31510
3
- oep_client/acquire.py,sha256=O4jmz4llIlfBkw2bGb-5yRUe5IB4TLb9BnZtScqedIQ,100251
3
+ oep_client/acquire.py,sha256=js4hRYX2kG7JVfQuLgO6fveRF9t1nOjz7PozuRbkUU0,102529
4
4
  oep_client/arm.py,sha256=SszSLae8zErD4T-a1uQ6s6o3Lz7pe2SR8d3Qy-wi5rY,15633
5
5
  oep_client/capture.py,sha256=fvLqs4Ltot4pyOdQiCWIzf_v1j_iduCxU4rrX6oWfg8,76245
6
6
  oep_client/capture_blocks.py,sha256=dPrrZ8K-ISF80w6UumKVst5XNt0IjUk9lxbVvFnUEqg,20836
@@ -255,8 +255,8 @@ oep_client/conformance/wire_options_sample.py,sha256=dog-yvUcu693M8b51tA5TnhAnKr
255
255
  oep_client/conformance/writer_lease.py,sha256=5wDd4XIJWFPFlRY91wHlJs5U7ANp3xPpGgU2Eki7e8g,7331
256
256
  oep_client/conformance/writer_lease_model.py,sha256=FxhDqqzdFOa_SSHSvjdQFh49Wn3YuZ98ISSlRRxrq9E,1721
257
257
  oep_client/conformance/writer_lease_sample.py,sha256=oowRwYUNAE4f6rEE2HmYlSS9dDWE0TGegkcsqVH8Nak,455
258
- oep_client_python-0.0.46.dist-info/METADATA,sha256=PCFgn-UCTe7dtMwpEdnFtKQwT2eHSEFwjyvAcHuJ9CA,55269
259
- oep_client_python-0.0.46.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
260
- oep_client_python-0.0.46.dist-info/entry_points.txt,sha256=XEt39Jz7oQyU9Hfg_tV6eDGofNxWYrjSfslxKP-fh70,135
261
- oep_client_python-0.0.46.dist-info/licenses/LICENSE,sha256=ntlalztb3L1OaNAxmQdHlubu5fVhjMc9oRZi3PeQhXg,1076
262
- oep_client_python-0.0.46.dist-info/RECORD,,
258
+ oep_client_python-0.0.47.dist-info/METADATA,sha256=e38LWsb7vSB_jpzlmyLWSQeBrHvSb9dREoKRv8V0nKQ,55269
259
+ oep_client_python-0.0.47.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
260
+ oep_client_python-0.0.47.dist-info/entry_points.txt,sha256=XEt39Jz7oQyU9Hfg_tV6eDGofNxWYrjSfslxKP-fh70,135
261
+ oep_client_python-0.0.47.dist-info/licenses/LICENSE,sha256=ntlalztb3L1OaNAxmQdHlubu5fVhjMc9oRZi3PeQhXg,1076
262
+ oep_client_python-0.0.47.dist-info/RECORD,,