Raw injection
raw sends bytes verbatim on one cloned endpoint, named by number and direction: IN emits toward the game PC, OUT writes to the real device. It bypasses the semantic model and never merges with native motion.
The write is stateless: the next native report overwrites it. raw bypasses the rewrite rules and the clip's packet triggers. A raw IN report takes a poll to itself, since no native report carries it. If the real device reports on that endpoint at every poll, the raw report is emitted within two native reports.
real device the box (host chip | device chip = the clone) game PC
HID report ---IN---> [ HID_IN ]--> renderer --> [ EMIT ]---interrupt-IN----> reads report
^
+-- raw(n, IN) <== a report toward the PC
relayed <--OUT--- [ HID_OUT ]<-- relay <--------------- interrupt-OUT <-- writes report
(VEND_INTR / VEND_BULK) ^
+-- raw(n, OUT) <== a report toward the real device
control <-- EP0 -> [ CONTROL ]<-- proxy ------------------- EP0 <--------> class, vendor requests
enumerate descriptor patches overwrite what the clone presentsThe advanced control layer needs allow_imperfect_clones. With it off, the box drops a raw frame with no reply, so the call still returns Ok.
rawOne report on one endpoint, fire-and-forget
fn raw(&self, ep: u8, direction: Direction, bytes: &[u8]) -> Result<()>
Fire-and-forget
| Parameter | Type | Description |
|---|---|---|
ep | u8 | Cloned endpoint number, 0-15. |
direction | Direction | IN emits toward the game PC, OUT writes to the real device. Any other value returns Error::RawDirection. |
bytes | &[u8] | The report, sent as given, ≤510 bytes. An interrupt report must fit one packet. A bulk payload is split per packet size. |
use medius::{Device, Direction};
let device = Device::find()?;
device.allow_imperfect_clones(true)?;
device.raw(1, Direction::IN, &[0x00, 0x01, 0x00, 0x00])?; // one report on interrupt-IN endpoint 1Packet size Bounded by the endpoint
| Endpoint type | Over the limit |
|---|---|
| Interrupt | Dropped by the box if longer than one packet (the endpoint's wMaxPacketSize), IN or OUT. On a HID IN endpoint whose largest Input report is longer than wMaxPacketSize, a full packet gets a zero-length packet after it (as a native report of that length does), so the PC's read ends there. |
| Bulk | Split at wMaxPacketSize in either direction. A short packet ends the payload, or a zero-length packet if the payload is an exact multiple of wMaxPacketSize. |
An advanced control layer frame carries ≤512 payload bytes (the frame limit). The C and Python buffers (MEDIUS_MAX_DEV_PAYLOAD) are the same size. raw spends two on endpoint and direction, leaving 510. The wire layout is on the native RAW command.
Raw vs injection Bytes or semantic input
For a standard input in the native report, use inject and move_rel. They describe an input that the box renders into a faithful report. raw sends bytes untouched.
| Aspect | inject / move_rel | raw |
|---|---|---|
| Addresses | An axis or usage, by semantic id | An endpoint, by number and direction |
| On the wire | Merged into the native report, clamped to the field width, paced to the native report rate | The bytes as given, one report |
| State | Held until cleared; rides the native stream | Stateless; the next native report overwrites it |
| Rewrite rules, packet triggers | Apply | Bypassed |
| Gate | Always available | Imperfect-clone opt-in |
raw is for a vendor packet no HID field describes, a byte sequence the real device expects on an OUT endpoint, or a report shape the semantic core does not model.
Imperfect-clone gate One opt-in enables the whole layer
set_rewrite and apply_patch read the opt-in before sending. raw runs per report, so it sends without reading the opt-in.
| Error | Returned on |
|---|---|
ImperfectRequired | set_rewrite or apply_patch, when the box reports the opt-in off. Turn it on with allow_imperfect_clones(true). |
The opt-in is a persistent box option, read back with query_imperfect. The native OPTION(IMPERFECT) sets the same gate.
On AsyncDevice raw is a future over the same send
AsyncDevice makes raw a future. The send stays fire-and-forget.
use futures::executor::block_on;
use medius::{AsyncDevice, Direction};
let device = AsyncDevice::open("/dev/ttyACM0")?;
device.allow_imperfect_clones(true)?;
block_on(device.raw(1, Direction::IN, &[0x00, 0x01, 0x00, 0x00]))?;