2026-04-23 07:00:06 -03:00
|
|
|
fn main() -> Result<()> {
|
|
|
|
|
let (dev, _cfg) = parse_args()?;
|
|
|
|
|
let _ = load_interfaces();
|
|
|
|
|
let _ = UvcConfig::from_env();
|
|
|
|
|
|
|
|
|
|
let _ = open_with_retry(&dev)?;
|
|
|
|
|
anyhow::bail!("coverage harness: control loop disabled");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(coverage)]
|
|
|
|
|
fn parse_args() -> Result<(String, UvcConfig)> {
|
|
|
|
|
let args: Vec<String> = env::args().skip(1).collect();
|
|
|
|
|
let dev = parse_device_arg(&args)
|
|
|
|
|
.or_else(|| env::var("LESAVKA_UVC_DEV").ok())
|
|
|
|
|
.context("missing --device (or LESAVKA_UVC_DEV)")?;
|
|
|
|
|
|
|
|
|
|
Ok((dev, UvcConfig::from_env()))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(coverage)]
|
|
|
|
|
fn parse_device_arg(args: &[String]) -> Option<String> {
|
|
|
|
|
args
|
|
|
|
|
.windows(2)
|
|
|
|
|
.find_map(|pair| (pair[0] == "--device" || pair[0] == "-d").then(|| pair[1].clone()))
|
|
|
|
|
.or_else(|| {
|
|
|
|
|
args.iter()
|
|
|
|
|
.rev()
|
|
|
|
|
.find(|arg| {
|
|
|
|
|
arg.as_str() != "--device" && arg.as_str() != "-d" && arg.starts_with('/')
|
|
|
|
|
})
|
|
|
|
|
.cloned()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(coverage)]
|
|
|
|
|
impl UvcConfig {
|
|
|
|
|
fn from_env() -> Self {
|
|
|
|
|
let width = env_u32("LESAVKA_UVC_WIDTH", 1280);
|
|
|
|
|
let height = env_u32("LESAVKA_UVC_HEIGHT", 720);
|
|
|
|
|
let fps = env_u32("LESAVKA_UVC_FPS", 25).max(1);
|
|
|
|
|
let frame_size = env_u32("LESAVKA_UVC_FRAME_SIZE", width * height * 2);
|
|
|
|
|
let interval = env_u32("LESAVKA_UVC_INTERVAL", 0);
|
|
|
|
|
let bulk = env::var("LESAVKA_UVC_BULK").is_ok();
|
|
|
|
|
let mut max_packet = env_u32("LESAVKA_UVC_MAXPACKET", 1024);
|
|
|
|
|
|
|
|
|
|
if let Some(limit) = compute_payload_cap(bulk).map(|cap| cap.limit) {
|
|
|
|
|
max_packet = max_packet.min(limit);
|
|
|
|
|
}
|
|
|
|
|
max_packet = if bulk {
|
|
|
|
|
max_packet.min(512)
|
|
|
|
|
} else {
|
|
|
|
|
max_packet.min(1024)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let interval = if interval == 0 {
|
|
|
|
|
10_000_000 / fps
|
|
|
|
|
} else {
|
|
|
|
|
interval
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Self {
|
|
|
|
|
width,
|
|
|
|
|
height,
|
|
|
|
|
fps,
|
|
|
|
|
interval,
|
|
|
|
|
max_packet,
|
|
|
|
|
frame_size,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(coverage)]
|
|
|
|
|
impl UvcState {
|
|
|
|
|
fn new(cfg: UvcConfig) -> Self {
|
|
|
|
|
let ctrl_len = stream_ctrl_len();
|
|
|
|
|
let default = build_streaming_control(&cfg, ctrl_len);
|
|
|
|
|
Self {
|
|
|
|
|
cfg,
|
|
|
|
|
ctrl_len,
|
|
|
|
|
default,
|
|
|
|
|
probe: default,
|
|
|
|
|
commit: default,
|
|
|
|
|
cfg_snapshot: None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(coverage)]
|
|
|
|
|
fn load_interfaces() -> UvcInterfaces {
|
|
|
|
|
let control = env_u8("LESAVKA_UVC_CTRL_INTF").unwrap_or(UVC_STRING_CONTROL_IDX);
|
|
|
|
|
let streaming = env_u8("LESAVKA_UVC_STREAM_INTF").unwrap_or(UVC_STRING_STREAMING_IDX);
|
|
|
|
|
UvcInterfaces { control, streaming }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(coverage)]
|
|
|
|
|
fn read_interface(path: &str) -> Option<u8> {
|
|
|
|
|
std::fs::read_to_string(path)
|
|
|
|
|
.ok()
|
|
|
|
|
.and_then(|v| v.trim().parse::<u8>().ok())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(coverage)]
|
|
|
|
|
fn open_with_retry(path: &str) -> Result<std::fs::File> {
|
2026-04-29 01:25:06 -03:00
|
|
|
let read_only = uvc_control_read_only();
|
2026-04-23 07:00:06 -03:00
|
|
|
let mut opts = OpenOptions::new();
|
2026-04-29 01:25:06 -03:00
|
|
|
opts.read(true);
|
|
|
|
|
if !read_only {
|
|
|
|
|
opts.write(true);
|
|
|
|
|
}
|
2026-04-23 07:00:06 -03:00
|
|
|
if env::var("LESAVKA_UVC_BLOCKING").is_err() {
|
|
|
|
|
opts.custom_flags(libc::O_NONBLOCK);
|
|
|
|
|
}
|
|
|
|
|
opts.open(path).with_context(|| format!("open {path}"))
|
|
|
|
|
}
|
2026-04-29 01:25:06 -03:00
|
|
|
|
|
|
|
|
#[cfg(coverage)]
|
|
|
|
|
fn uvc_control_read_only() -> bool {
|
|
|
|
|
env::var("LESAVKA_UVC_CONTROL_READ_ONLY")
|
|
|
|
|
.ok()
|
|
|
|
|
.map(|value| {
|
|
|
|
|
let trimmed = value.trim();
|
|
|
|
|
!(trimmed.eq_ignore_ascii_case("0")
|
|
|
|
|
|| trimmed.eq_ignore_ascii_case("false")
|
|
|
|
|
|| trimmed.eq_ignore_ascii_case("no")
|
|
|
|
|
|| trimmed.eq_ignore_ascii_case("off"))
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or(true)
|
|
|
|
|
}
|