oep-client-python 0.0.1__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 +5 -0
- oep_client/v1/__init__.py +1 -0
- oep_client/v1/__main__.py +40 -0
- oep_client/v1/arm.py +269 -0
- oep_client/v1/capture.py +365 -0
- oep_client/v1/catalog.py +181 -0
- oep_client/v1/ch32_flash.py +254 -0
- oep_client/v1/cobs.py +73 -0
- oep_client/v1/console.py +165 -0
- oep_client/v1/core.py +172 -0
- oep_client/v1/decode.py +65 -0
- oep_client/v1/dump.py +157 -0
- oep_client/v1/endpoint.py +1479 -0
- oep_client/v1/esp32_targets.py +137 -0
- oep_client/v1/fake.py +233 -0
- oep_client/v1/fake_serial.py +105 -0
- oep_client/v1/fake_serve.py +299 -0
- oep_client/v1/fixture.py +121 -0
- oep_client/v1/frames.py +91 -0
- oep_client/v1/hid_stream.py +257 -0
- oep_client/v1/host.py +321 -0
- oep_client/v1/interfaces.py +119 -0
- oep_client/v1/link.py +414 -0
- oep_client/v1/message.py +235 -0
- oep_client/v1/names.py +86 -0
- oep_client/v1/registry.py +59 -0
- oep_client/v1/riscv.py +430 -0
- oep_client/v1/rp2350.py +85 -0
- oep_client/v1/target.py +16 -0
- oep_client/v1/uiapduino.py +121 -0
- oep_client/v1/usb_stream.py +209 -0
- oep_client_python-0.0.1.dist-info/METADATA +106 -0
- oep_client_python-0.0.1.dist-info/RECORD +36 -0
- oep_client_python-0.0.1.dist-info/WHEEL +4 -0
- oep_client_python-0.0.1.dist-info/entry_points.txt +2 -0
- oep_client_python-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""A USB vendor bulk endpoint pair shaped like the part of a pyserial port the v1 link uses (read with a timeout,
|
|
2
|
+
in_waiting, write, reset_input_buffer), so length-prefixed frames run over it unchanged. pyusb is imported only here.
|
|
3
|
+
|
|
4
|
+
IN is drained on a thread: a bulk write blocks until the device takes the data, and the device stops taking data
|
|
5
|
+
once its answers fill its FIFO and nobody reads them (E160).
|
|
6
|
+
|
|
7
|
+
Each IN read asks for READ_SIZE bytes. A read completes when that much has arrived or a short packet ends the
|
|
8
|
+
transfer; if its timeout hits first, pyusb raises and the bytes already received are lost. Under a continuous stream
|
|
9
|
+
(12.5 MB/s, 2026-09-25) a 64 KiB read waited for more than its 50 ms and dropped whole answers, so reads stay small."""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import threading
|
|
14
|
+
import time
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UsbBulkStream:
|
|
18
|
+
READ_SIZE = 16384
|
|
19
|
+
|
|
20
|
+
def __init__(self, device, endpoint_in, endpoint_out, interface: int):
|
|
21
|
+
self.device, self.ep_in, self.ep_out, self.interface = device, endpoint_in, endpoint_out, interface
|
|
22
|
+
self.timeout = 0.05
|
|
23
|
+
self._buffer = bytearray()
|
|
24
|
+
self._cond = threading.Condition()
|
|
25
|
+
self._closed = False
|
|
26
|
+
self._reader = threading.Thread(target=self._drain, daemon=True)
|
|
27
|
+
self._reader.start()
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def open(cls, vid: int, pid: int, serial: str | None = None) -> "UsbBulkStream":
|
|
31
|
+
import usb.core
|
|
32
|
+
import usb.util
|
|
33
|
+
match = (lambda d: usb.util.get_string(d, d.iSerialNumber).lower() == serial.lower()) if serial else None
|
|
34
|
+
dev = usb.core.find(idVendor=vid, idProduct=pid, custom_match=match)
|
|
35
|
+
if dev is None:
|
|
36
|
+
raise FileNotFoundError(f"no USB device {vid:04x}:{pid:04x}" + (f" serial {serial}" if serial else ""))
|
|
37
|
+
cfg = dev.get_active_configuration()
|
|
38
|
+
for intf in cfg:
|
|
39
|
+
eps = [e for e in intf if usb.util.endpoint_type(e.bmAttributes) == usb.util.ENDPOINT_TYPE_BULK]
|
|
40
|
+
ins = [e for e in eps if usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN]
|
|
41
|
+
outs = [e for e in eps if usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_OUT]
|
|
42
|
+
if ins and outs:
|
|
43
|
+
usb.util.claim_interface(dev, intf.bInterfaceNumber)
|
|
44
|
+
return cls(dev, ins[0], outs[0], intf.bInterfaceNumber)
|
|
45
|
+
raise FileNotFoundError("no bulk IN/OUT pair on the device")
|
|
46
|
+
|
|
47
|
+
def _drain(self) -> None:
|
|
48
|
+
import usb.core
|
|
49
|
+
while not self._closed:
|
|
50
|
+
try:
|
|
51
|
+
data = self.ep_in.read(self.READ_SIZE, timeout=200)
|
|
52
|
+
except usb.core.USBTimeoutError:
|
|
53
|
+
continue
|
|
54
|
+
except usb.core.USBError:
|
|
55
|
+
if self._closed:
|
|
56
|
+
return
|
|
57
|
+
time.sleep(0.01)
|
|
58
|
+
continue
|
|
59
|
+
if len(data):
|
|
60
|
+
with self._cond:
|
|
61
|
+
self._buffer += bytes(data)
|
|
62
|
+
self._cond.notify_all()
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def in_waiting(self) -> int:
|
|
66
|
+
with self._cond:
|
|
67
|
+
return len(self._buffer)
|
|
68
|
+
|
|
69
|
+
def read(self, n: int = 1) -> bytes:
|
|
70
|
+
deadline = time.monotonic() + (self.timeout or 0)
|
|
71
|
+
with self._cond:
|
|
72
|
+
while not self._buffer:
|
|
73
|
+
left = deadline - time.monotonic()
|
|
74
|
+
if left <= 0:
|
|
75
|
+
return b""
|
|
76
|
+
self._cond.wait(left)
|
|
77
|
+
out = bytes(self._buffer[:n])
|
|
78
|
+
del self._buffer[:n]
|
|
79
|
+
return out
|
|
80
|
+
|
|
81
|
+
def write(self, data: bytes) -> int:
|
|
82
|
+
n = self.ep_out.write(data, timeout=2000)
|
|
83
|
+
if data and len(data) % self.ep_out.wMaxPacketSize == 0: # a zero-length packet ends the probe's transfer
|
|
84
|
+
self.ep_out.write(b"", timeout=2000)
|
|
85
|
+
return n
|
|
86
|
+
|
|
87
|
+
def reset_input_buffer(self) -> None:
|
|
88
|
+
with self._cond:
|
|
89
|
+
self._buffer.clear()
|
|
90
|
+
|
|
91
|
+
def close(self) -> None:
|
|
92
|
+
import usb.util
|
|
93
|
+
self._closed = True
|
|
94
|
+
self._reader.join(timeout=0.5)
|
|
95
|
+
usb.util.release_interface(self.device, self.interface)
|
|
96
|
+
usb.util.dispose_resources(self.device)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class UsbAsyncStream:
|
|
100
|
+
"""The same shape as UsbBulkStream on python-libusb1 (`usb1`): IN runs as DEPTH asynchronous transfers of URB_SIZE
|
|
101
|
+
kept queued by an event thread, so a continuous stream near the HS ceiling (wch-protocols E116: 1 MiB x 8 reaches
|
|
102
|
+
~47 MB/s over usbipd; 64 KiB x 32 carried 2 x 160 Msps here) is taken without gaps. A transfer completes when full or on a short packet; the probe ends a
|
|
103
|
+
transfer whose length is a whole number of packets with a zero-length packet, so small answers never wait."""
|
|
104
|
+
|
|
105
|
+
# 64 KiB x 32: a probe streaming whole-packet frames (16 KiB) completes a read every four frames, so the latency at
|
|
106
|
+
# a low rate stays short (1 MiB reads waited 0.8 s at 1.25 MB/s), and 2 MiB stay queued for the host's hiccups
|
|
107
|
+
URB_SIZE = 64 * 1024
|
|
108
|
+
DEPTH = 32
|
|
109
|
+
|
|
110
|
+
def __init__(self, context, handle, endpoint_in: int, endpoint_out: int, interface: int):
|
|
111
|
+
import usb1
|
|
112
|
+
self._usb1 = usb1
|
|
113
|
+
self.context, self.handle, self.ep_in, self.ep_out, self.interface = context, handle, endpoint_in, endpoint_out, interface
|
|
114
|
+
self.timeout = 0.05
|
|
115
|
+
self._buffer = bytearray()
|
|
116
|
+
self._cond = threading.Condition()
|
|
117
|
+
self._closed = False
|
|
118
|
+
self._transfers = []
|
|
119
|
+
for _ in range(self.DEPTH):
|
|
120
|
+
t = handle.getTransfer()
|
|
121
|
+
t.setBulk(endpoint_in, self.URB_SIZE, callback=self._done, timeout=0)
|
|
122
|
+
t.submit()
|
|
123
|
+
self._transfers.append(t)
|
|
124
|
+
self._events = threading.Thread(target=self._run, daemon=True)
|
|
125
|
+
self._events.start()
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def open(cls, vid: int, pid: int, serial: str | None = None) -> "UsbAsyncStream":
|
|
129
|
+
import usb1
|
|
130
|
+
context = usb1.USBContext()
|
|
131
|
+
context.open()
|
|
132
|
+
for dev in context.getDeviceIterator(skip_on_error=True):
|
|
133
|
+
if dev.getVendorID() != vid or dev.getProductID() != pid:
|
|
134
|
+
continue
|
|
135
|
+
handle = dev.open()
|
|
136
|
+
if serial and (handle.getSerialNumber() or "").lower() != serial.lower():
|
|
137
|
+
handle.close()
|
|
138
|
+
continue
|
|
139
|
+
for setting in dev.iterSettings():
|
|
140
|
+
eps = [(e.getAddress(), e.getAttributes()) for e in setting]
|
|
141
|
+
ins = [a for a, attr in eps if attr & 3 == 2 and a & 0x80]
|
|
142
|
+
outs = [a for a, attr in eps if attr & 3 == 2 and not a & 0x80]
|
|
143
|
+
if ins and outs:
|
|
144
|
+
handle.claimInterface(setting.getNumber())
|
|
145
|
+
return cls(context, handle, ins[0], outs[0], setting.getNumber())
|
|
146
|
+
handle.close()
|
|
147
|
+
context.close()
|
|
148
|
+
raise FileNotFoundError(f"no USB device {vid:04x}:{pid:04x}" + (f" serial {serial}" if serial else ""))
|
|
149
|
+
|
|
150
|
+
def _done(self, transfer) -> None:
|
|
151
|
+
usb1 = self._usb1
|
|
152
|
+
status = transfer.getStatus()
|
|
153
|
+
if status == usb1.TRANSFER_COMPLETED:
|
|
154
|
+
n = transfer.getActualLength()
|
|
155
|
+
if n:
|
|
156
|
+
data = transfer.getBuffer()[:n]
|
|
157
|
+
with self._cond:
|
|
158
|
+
self._buffer += data
|
|
159
|
+
self._cond.notify_all()
|
|
160
|
+
if not self._closed and status in (usb1.TRANSFER_COMPLETED, usb1.TRANSFER_TIMED_OUT):
|
|
161
|
+
transfer.submit()
|
|
162
|
+
|
|
163
|
+
def _run(self) -> None:
|
|
164
|
+
while not self._closed:
|
|
165
|
+
try:
|
|
166
|
+
self.context.handleEventsTimeout(0.1)
|
|
167
|
+
except self._usb1.USBErrorInterrupted:
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def in_waiting(self) -> int:
|
|
172
|
+
with self._cond:
|
|
173
|
+
return len(self._buffer)
|
|
174
|
+
|
|
175
|
+
def read(self, n: int = 1) -> bytes:
|
|
176
|
+
deadline = time.monotonic() + (self.timeout or 0)
|
|
177
|
+
with self._cond:
|
|
178
|
+
while not self._buffer:
|
|
179
|
+
left = deadline - time.monotonic()
|
|
180
|
+
if left <= 0:
|
|
181
|
+
return b""
|
|
182
|
+
self._cond.wait(left)
|
|
183
|
+
out = bytes(self._buffer[:n])
|
|
184
|
+
del self._buffer[:n]
|
|
185
|
+
return out
|
|
186
|
+
|
|
187
|
+
def write(self, data: bytes) -> int:
|
|
188
|
+
n = self.handle.bulkWrite(self.ep_out, data, timeout=2000)
|
|
189
|
+
if data and len(data) % 512 == 0: # end the probe's receive transfer (a probe reading large transfers needs it)
|
|
190
|
+
self.handle.bulkWrite(self.ep_out, b"", timeout=2000)
|
|
191
|
+
return n
|
|
192
|
+
|
|
193
|
+
def reset_input_buffer(self) -> None:
|
|
194
|
+
with self._cond:
|
|
195
|
+
self._buffer.clear()
|
|
196
|
+
|
|
197
|
+
def close(self) -> None:
|
|
198
|
+
self._closed = True
|
|
199
|
+
for t in self._transfers:
|
|
200
|
+
try:
|
|
201
|
+
t.cancel()
|
|
202
|
+
except self._usb1.USBError:
|
|
203
|
+
pass
|
|
204
|
+
self._events.join(timeout=0.5)
|
|
205
|
+
try:
|
|
206
|
+
self.handle.releaseInterface(self.interface)
|
|
207
|
+
finally:
|
|
208
|
+
self.handle.close()
|
|
209
|
+
self.context.close()
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: oep-client-python
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Open Embedded Probe (OEP) v1 host: serial / USB / TCP transports, the session rules, the standard interfaces, and a fake probe
|
|
5
|
+
Project-URL: Homepage, https://github.com/Open-Embedded-Probe/oep-client-python
|
|
6
|
+
Project-URL: Repository, https://github.com/Open-Embedded-Probe/oep-client-python
|
|
7
|
+
Author: TANAKA Masayuki
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
13
|
+
Classifier: Topic :: Software Development :: Testing
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Requires-Dist: pyserial>=3.5
|
|
16
|
+
Requires-Dist: pyusb>=1.3
|
|
17
|
+
Provides-Extra: hid
|
|
18
|
+
Requires-Dist: hidapi>=0.14; extra == 'hid'
|
|
19
|
+
Provides-Extra: usb-async
|
|
20
|
+
Requires-Dist: libusb1>=3; extra == 'usb-async'
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# OEP Python client
|
|
24
|
+
|
|
25
|
+
Open Embedded Probe の host 側。v1(oep-spec の `docs/oep-core.ja.md` と `docs/oep-if-*.ja.md`、固める候補の形)を話す。番号は oep-spec の
|
|
26
|
+
`generated/oep-v1/oep_v1_registry.py` をそのまま写した `oep_client.v1.registry` から取る。破壊的変更を前提とする
|
|
27
|
+
実験段階で、互換 API は約束しない。OEP を初めて読む人は oep-spec の `docs/review-guide.ja.md`(どこに何が書いてあるか)から。
|
|
28
|
+
|
|
29
|
+
target の知識は host にある、という OEP の分担に従う。probe は線と DMI / DP・AP の転送しか知らず、CH32 の flash
|
|
30
|
+
コントローラ、RAM ローダー、RP2350 の boot ROM、Cortex-M の debug レジスタなどはここに置く。
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
pip install oep-client-python # PyPI (import oep_client); a checkout: pip install -e <checkout>
|
|
34
|
+
uv run pytest # in a checkout
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`import oep_client.v1` だけで使える(`sys.path` に `src/` を足す使い方は不要になった)。番号の表は `tools/sync_registry.sh` で
|
|
38
|
+
oep-spec から写す。PyPI の `oep-client` は別のプロジェクトなので、配布名は `oep-client-python`。
|
|
39
|
+
|
|
40
|
+
リリースは GitHub Actions の Release(workflow_dispatch、version = X.Y.Z か X.Y.ZbN): `tools/prepare_release.py` が
|
|
41
|
+
pyproject.toml と `oep_client.__version__` を書き換え、CHANGELOG.md の Unreleased をその版にし、試験と build の後に commit と tag、
|
|
42
|
+
GitHub Release、PyPI(Trusted Publishing)へ出す。変更は CHANGELOG.md の Unreleased に (EN) / (JA) で書き足しておく。
|
|
43
|
+
|
|
44
|
+
## モジュール(`oep_client.v1`)
|
|
45
|
+
|
|
46
|
+
| モジュール | 中身 |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `host` | 要求と結果、session_id とロック、`call()`(失敗なら例外)、pipeline、エラーの階層(`OepError` / `Rejected` / `Failed`) |
|
|
49
|
+
| `link` | transport: シリアルの口(常に COBS + CRC、`0x00 <COBS> 0x00`、フレームの外は雑音として捨てる、排他で開く)、USB vendor bulk / HID と TCP(長さつきフレーム、§5.1 の立て直し)、corr による照合と送り直し、`open_host(target)` |
|
|
50
|
+
| `core` | インターフェースを名前で探す(キャッシュつき)、confirm、probe の describe(ラベル、transport の一覧)、ロックの取り方(`take`)、ピンの割り当て(plan)、`Interface` の土台 |
|
|
51
|
+
| `riscv` | `oep.wire.rvswd` / `oep.wire.swio`、`oep.target.riscv-dm`、リセット線の探索、GPIO 経由の attach |
|
|
52
|
+
| `console` | `oep.target.console`(位置つきのストリーム)と、バイト列として読む `ConsoleIO` |
|
|
53
|
+
| `fixture` | `oep.fixture.gpio` / `uart`(revision 1) |
|
|
54
|
+
| `capture` | `oep.fixture.capture`(revision 1、oep-spec の oep-if-capture)。読んだ区画は `Host.on_capture` の callback に `CaptureRecord` で渡る(記録の受け口。wireskein には依存しない) |
|
|
55
|
+
| `esp32_targets` | 独自インターフェース `io.github.ch32-riscv-ug.esp32.i2c-target` / `spi-target`(oep-probe-arduino の ESP32 の I2C / SPI の target) |
|
|
56
|
+
| `decode` | キャプチャのチャネルの復号(I2C) |
|
|
57
|
+
| `registry` | oep-spec の番号の表から生成したモジュール(編集しない。oep-spec から写し直す) |
|
|
58
|
+
| `arm` | `oep.wire.swd`、`oep.target.arm-adi`、MEM-AP、Cortex-M の停止と関数呼び出し |
|
|
59
|
+
| `ch32_flash` | CH32 の書き込み(RAM ローダー、ページ単位の書き直し) |
|
|
60
|
+
| `rp2350` | RP2350 の boot ROM 経由の flash と reboot |
|
|
61
|
+
| `uiapduino` | UIAPduino のブートローダへの出入り |
|
|
62
|
+
| `catalog` / `names` / `interfaces` / `dump` | 能力の一覧と describe の形、表示 |
|
|
63
|
+
| `fake` / `endpoint` / `fake_serial` / `fake_serve` | 偽の probe(下の「偽の probe」) |
|
|
64
|
+
| `target` | 上の主なものを 1 か所から import する入口(最初の版に合わせて書いた呼び出し側のため) |
|
|
65
|
+
|
|
66
|
+
## 使い方の例
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from oep_client.v1 import core, link, riscv, ch32_flash
|
|
70
|
+
|
|
71
|
+
hst = link.open_host("/run/board-identify/by-id/esp32-series-30eda0e31108") # pipelining つき
|
|
72
|
+
# a serial port (always COBS), "tcp://127.0.0.1:PORT" (a broker), "usb" / "usb:303a:0002[:SERIAL]" (vendor, then HID)
|
|
73
|
+
core.take(hst, 30000, owner="flash script") # the only way in: force; else wait out the lease, name the holder
|
|
74
|
+
wire = riscv.Wire(hst, "oep.wire.rvswd")
|
|
75
|
+
conn, _ = wire.attach(halt=True)
|
|
76
|
+
dm = riscv.RiscvDm(hst, conn)
|
|
77
|
+
dm.reset_halt()
|
|
78
|
+
result = ch32_flash.program(hst, dm, open("sketch.bin", "rb").read(), ch32_flash.PROFILES["x035"])
|
|
79
|
+
dm.reset(confirm=True)
|
|
80
|
+
wire.detach(conn)
|
|
81
|
+
hst.end()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
能力の一覧は `uv run python -m oep_client.v1 dump --port <probe>`(`--fake p4-x035` でハードウェアなし)。
|
|
85
|
+
実機での一通りの確認は ArduinoCore-CH32 の `tests/manual/oep_smoke/`(`oep_smoke.py`、`oep_probe_checks.py`)。
|
|
86
|
+
|
|
87
|
+
## 偽の probe(動く spec)
|
|
88
|
+
|
|
89
|
+
`endpoint.Endpoint` は oep-spec の規範どおりに答える偽の probe で、ch32rv・この client・probe の firmware を突き合わせる
|
|
90
|
+
「動く spec」として使う(spec が変わったら、probe の firmware より先にここを合わせる)。`fake` は宣言の例(profile:
|
|
91
|
+
`p4-x035`、`esp32-v003`、`p4-bench` = スロット 3 か所と席 2 つの架空の治具)、`fake_serial` はシリアルの口のバイトの側(COBS の
|
|
92
|
+
候補、生のバイトと bind、セッション中の停止と再開)。
|
|
93
|
+
|
|
94
|
+
外のプログラムの試験には `fake_serve` を子プロセスで使う:
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
uv run python -m oep_client.v1.fake_serve --pty --profile p4-bench --slot x035 --bind last-reset \
|
|
98
|
+
--console 'uptime %d\r\n' --every 100
|
|
99
|
+
# 最初の行: PTY /dev/pts/N(--tcp 0 なら PORT n)。stdin を閉じると終わる
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
pty がシリアルの口(host が TIOCEXCL を掛けて開く)、`--tcp PORT` は `--framing cobs`(シリアルの口)か `--framing length`
|
|
103
|
+
(vendor bulk / TCP の形)。故障の注入は `--drop N`(N 番目の答えを 1 回出さない。要求は実行済みなので送り直しは覚えた答えを
|
|
104
|
+
受ける)、`--noise TEXT`(答えの前に雑音)、`--corrupt N`(N 番目の答えの CRC を 1 回壊す)。ほかは `--help`。
|
|
105
|
+
|
|
106
|
+
v0 の client(`oep_client.v0`)は 2026-09-26 に消した(git の履歴に残る)。v0 を話す probe はもう無い。
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
oep_client/__init__.py,sha256=f5QEx-7qtfSSRnMCtAflZ2y0GvfX6lXa_AVjdc4SQfg,166
|
|
2
|
+
oep_client/v1/__init__.py,sha256=Aghb5g5wouqnDIUFv9S4dkimnzs5YZcWx9gzgxi7GPM,90
|
|
3
|
+
oep_client/v1/__main__.py,sha256=CytzDl1qI-sqXPS5ZqtvV07Riz2agQ7uK-Hlwmyz8fw,1747
|
|
4
|
+
oep_client/v1/arm.py,sha256=JLyUhLSPcmsk73schZcaJYoOKuHzrvnU8kPmfrEvVuo,12331
|
|
5
|
+
oep_client/v1/capture.py,sha256=RZgDnWqr8Aqy49U7PqQpnKIQEL18djVuDJFydxrmv-c,17996
|
|
6
|
+
oep_client/v1/catalog.py,sha256=EqAQnSgHFxGbCO2GQ_Ee-uheJKbBBUUpL1-0qHfNMXU,6574
|
|
7
|
+
oep_client/v1/ch32_flash.py,sha256=Fc-6jEj15cQtMSKmfC5dmDetMXJ8hAdAuJ9d9odwrsA,12651
|
|
8
|
+
oep_client/v1/cobs.py,sha256=hqddQI_X7rsBvBvcP_MBE3bXPyB13-Azjqp_9JfdQ1g,2288
|
|
9
|
+
oep_client/v1/console.py,sha256=YYcuc9Qxe3kihla8wynYbDaLVr7wBLEvswrM-cMK3Tk,6859
|
|
10
|
+
oep_client/v1/core.py,sha256=z0-mplQVLOmPyXj1lHc-U7HYl3Gd6o-b5YJhpQTvqKU,8789
|
|
11
|
+
oep_client/v1/decode.py,sha256=M-bRARrA2qGZLoJn0BdXeTb4WjL_2Hb2nk_g1IGXIw4,2382
|
|
12
|
+
oep_client/v1/dump.py,sha256=GAnmu2abuDmGmbWcbbEfC3EUuPp5Hady9vQVNcQ-724,6743
|
|
13
|
+
oep_client/v1/endpoint.py,sha256=ay767evyw0QgM6oXOUcl94b_wxTHtXHxsc8sAUMi6u0,72014
|
|
14
|
+
oep_client/v1/esp32_targets.py,sha256=b8tNH-nb2KC-XgNfWg5l3TO0cyhZOF-gJajaVxNVSGQ,4988
|
|
15
|
+
oep_client/v1/fake.py,sha256=UzpB-A42Ma2QqJj0l-JmwCJkMn6DPVHHN77fcemT59U,13104
|
|
16
|
+
oep_client/v1/fake_serial.py,sha256=6fhtrit9kWix-D83yYfUKFLHJ11pEiMDrlKNeJKvCfU,4314
|
|
17
|
+
oep_client/v1/fake_serve.py,sha256=B93k_fV0j0KpO8Qtyx8dQfffxn2EeRsURsdX_ScKEP0,12423
|
|
18
|
+
oep_client/v1/fixture.py,sha256=653hCD-Iz0Lfs8dT4Hdg3QyubbbOGs1_xjq_3nb8foU,5564
|
|
19
|
+
oep_client/v1/frames.py,sha256=xjgjnXmXUqNJZ5m0h91r4Cy48WoVK1mEMW4gRg-mlMI,3991
|
|
20
|
+
oep_client/v1/hid_stream.py,sha256=99Dsm4Io1keukuETG_UNqjBGiVYZZeb__Pv4ytaKg9k,10695
|
|
21
|
+
oep_client/v1/host.py,sha256=Y5oqE-pw5Ja9cG1dzdy8svIEgR4Kqhki0wuyrpP_xH8,15563
|
|
22
|
+
oep_client/v1/interfaces.py,sha256=P4ehrIj4j-9eg4NjaVdV15pawI2vOqucJwVklSdR-As,5472
|
|
23
|
+
oep_client/v1/link.py,sha256=J9VtBWfTgNoIgUQuj8SsH9hHesQM2uEN545IoxKVS8E,19406
|
|
24
|
+
oep_client/v1/message.py,sha256=MEM1UT6KFM7AsT56jvBLt_FJK_6SJ7O_nGAiipW0_zo,9912
|
|
25
|
+
oep_client/v1/names.py,sha256=ztCZFWs__aR-a5G14TnJP-Q4YfLhbyrzvOVOMJuG93k,3653
|
|
26
|
+
oep_client/v1/registry.py,sha256=nRM-sQiAMs1OzuSZIajdsBfdFckYo3OfwgmcLkHzxqw,9867
|
|
27
|
+
oep_client/v1/riscv.py,sha256=E0_C-4mBsrp9FUoZpMiqSH9e8sC8Q4EO5HRjFKE6_pc,21392
|
|
28
|
+
oep_client/v1/rp2350.py,sha256=7OMyj270k3AY1e3-ljicPwOjf9jOlokbT6QNkA0b0yY,4227
|
|
29
|
+
oep_client/v1/target.py,sha256=R8BvYfFfYNhZF0MysJkNb8b69YgBlmeGXNX8ReZIe5k,1068
|
|
30
|
+
oep_client/v1/uiapduino.py,sha256=IEPG3LbZPksi8g3wmAf6T0CRWSfYKW1_qrH_GBVQdxY,6090
|
|
31
|
+
oep_client/v1/usb_stream.py,sha256=1nvuRNLQkpp8054eG-JutBKh9y0F_8PHGDAfG0ZSbaY,8819
|
|
32
|
+
oep_client_python-0.0.1.dist-info/METADATA,sha256=PdTZlP4XrPcU_zZZZYofEqST_flj7AeoXHWF9iC06LQ,7312
|
|
33
|
+
oep_client_python-0.0.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
34
|
+
oep_client_python-0.0.1.dist-info/entry_points.txt,sha256=F9_WK-5S92fBuWSeCR0GXzmA5OXY6y0xvYpHseqY2R8,52
|
|
35
|
+
oep_client_python-0.0.1.dist-info/licenses/LICENSE,sha256=ntlalztb3L1OaNAxmQdHlubu5fVhjMc9oRZi3PeQhXg,1076
|
|
36
|
+
oep_client_python-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Open Embedded Probe
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|