If you have ever owned more than one debug probe, you know the problem. Each probe speaks its own dialect to its own host tool. A new probe means new host support. A host application ends up bound to a particular product, board, firmware revision or connection method, and the same capability gets reimplemented in mutually incompatible ways.
Open Embedded Probe (OEP) is an attempt to specify that away. Its README is one sentence: “Open Embedded Probe (OEP) is a protocol for interoperable embedded-development probes and hosts.”
The repository has 660 commits and zero stars.
What it is actually trying to fix
The project’s concept document lists the fragmentation costs explicitly: every new probe implementation or feature needs individual host-side support; host applications become dependent on specific products, boards, firmware or connection methods; equivalent capabilities get reimplemented incompatibly; a probe offering several functions has no common convention for exposing them coherently; and using the same capability over a non-USB connection usually means redesigning the mechanism from scratch.
The document is careful about one thing. USB device identity is named as one concrete example where the problem surfaces — and the project explicitly states that using USB, or a shared PID, is not the goal of the project.
The goal it does state: a probe can publish the capabilities it offers, a host can understand which capabilities exist and what they mean, the host can use them and the probe can report results and state with shared meaning — all without either side knowing the other’s specific implementation in advance.
The two-layer structure
| Layer | Contents | Naming | Versioning |
|---|---|---|---|
| Core | What every probe and host implements regardless of features: transports and framing, messages, sessions and exclusivity, discovery (confirm / list / describe), probe clock, channel and resource lifetime rules, notification mechanism, extension rules | No name — addressed as fn 0, not listed | Protocol revision |
| Interfaces | Named capabilities defined purely in terms of core mechanisms. Anything core does not mandate lives here | Reverse-DNS names; the project’s own use short oep. names | Per-interface revision |
Capabilities are addressed by fn (a u16 interface number, stable for the life of a boot_id) and op (a u8 operation number within an interface). fn 0 is the core itself, which has no name and does not appear in list.
The single source of truth for every number — ops, tags, reject reasons, statuses, enums — is registry/oep-v1.toml. The spec states that the tables in the documents are copies of the registry, and that where they disagree the registry is correct and the document gets fixed. That is an unusually disciplined choice for a hobby-scale project, and it is the kind of decision that makes a spec implementable by someone who did not write it.
Explicitly out of scope for core: the probe’s own firmware-update procedure (DFU, mass storage) and USB descriptor minutiae. Self-update and authentication can be defined as optional interfaces, but core will not gain mandatory update or authentication operations.
Framing, which is where specs usually get vague
It does not get vague. Transports split into two kinds — serial ports (UART bridge, USB CDC, built-in USB serial), which the OS sees as serial devices and which carry OEP frames only, and everything else (USB vendor bulk, HID, TCP), which carries OEP only.
| Transport | Framing |
|---|---|
| Serial ports | COBS + CRC-16, delimited by 0x00 |
| USB vendor bulk, TCP | length(u16) message, no CRC; length 0 reserved as keepalive and skipped |
| USB HID (vendor-defined report) | The bulk byte stream carried in reports: count(u16), then count bytes, then padding |
The CRC is specified down to the parameters — CRC-16/CCITT-FALSE, polynomial 0x1021, initial value 0xFFFF, non-reflected, with the standard "123456789" → 0x29B1 check value — appended little-endian before COBS encoding, and the frame is wrapped in 0x00 on both sides, with neither probe nor host permitted to omit the leading delimiter.
There is a genuinely thoughtful detail in how a host recovers synchronisation. Because bytes sent before the port was opened may mean the leading 0x00 never arrives, the host treats both the span from port-open to the first 0x00 and each 0x00-to-0x00 span as candidate frames, discarding candidates that fail to decode, fail CRC, or carry the wrong role or correlation value. Mid-frame stalls are handled by a 200 ms gap timeout (probe_frame_gap_ms), after which the partial frame is dropped.
The USB identity question, answered narrowly
The project’s USB VID:PID is 1209:4F45 — VID 0x1209 being pid.codes, the community-allocated vendor ID for open-source hardware, and PID 0x4F45 spelling OE in ASCII.
The rule is deliberately minimal: the VID:PID answers only the question “is this an OEP probe?”, so that host discovery can enumerate candidates without opening every port. What the probe can actually do is read after opening, via confirm / list / describe. The spec is emphatic that VID:PID must not become a capability table, and that iProduct strings and interface class/subclass/protocol values must not be used for identification, since they can coincide with unrelated products.
Hosts are told to remember probes by unit_id — the USB serial number — rather than by VID:PID or port name, and udev rules or usbipd binds are written against 1209:4F45 with individual probes located by serial number. Probes that cannot choose their own VID:PID and serial number, such as USB-UART bridges, are simply not enumerated under the project VID:PID; the user picks the port and describe on fn 0 reports the unit_id afterwards.
Seven repositories, one month, almost no stars
The spec does not stand alone. The organisation currently holds:
| Repository | Created | Stars |
|---|---|---|
oep-spec | 17 Sep 2026 | 0 |
oep-probe-arduino | 17 Sep 2026 | 1 |
oep-client-python | 17 Sep 2026 | 0 |
oep-client-js | 30 Sep 2026 | 0 |
wireskein | 26 Sep 2026 | 0 |
pytest-embedded-wireskein | 29 Sep 2026 | 0 |
wireskein-web | 30 Sep 2026 | 0 |
A normative spec, a reference probe firmware for Arduino, clients in Python and JavaScript, and a test-harness family — assembled since mid-September, all pushed within the last three weeks.
The language policy, which is the catch
OEP v1 is not frozen. Until it is, the maintained and authoritative text is Japanese, and the entry point is README.ja.md. Every document in docs/ carries a .ja.md suffix: oep-core, oep-transports, usb-identity, conformance, security, versioning, glossary, the host, probe and extension development guides, and more.
English normative documents will be produced at the freeze. The project states that stale translations are intentionally not kept in the repository — which is the right call for correctness and an obvious barrier to adoption in the meantime. The spec also marks each document as either normative or a guide, and states that where a guide and the core document disagree, core wins.
For now, then: a carefully constructed interoperability standard with a conformance document, a number registry and three reference implementations, readable by anyone who reads Japanese, and effectively invisible to everyone else.
Sources
- Open-Embedded-Probe/oep-spec — GitHub
- OEP core — プロトコル本体 v1
- OEP transports — 経路とフレーム v1
- An Open Embedded Probe specification — Adafruit
- pid.codes — the open-source USB VID registry