Skip to main content

viva_camctl/
cmd_usb.rs

1//! USB3 Vision CLI commands.
2
3use std::path::PathBuf;
4
5use anyhow::{Result, anyhow};
6use serde::Serialize;
7use tracing::info;
8
9use viva_genicam::{Camera, U3vRegisterIo, connect_u3v};
10use viva_pfnc::PixelFormat;
11use viva_u3v::discovery::{U3vDeviceInfo, discover};
12use viva_u3v::usb::RusbTransfer;
13
14use crate::common;
15
16// ---------------------------------------------------------------------------
17// list-usb
18// ---------------------------------------------------------------------------
19
20#[derive(Serialize)]
21struct UsbDeviceEntry {
22    index: usize,
23    vendor_id: String,
24    product_id: String,
25    manufacturer: Option<String>,
26    model: Option<String>,
27    serial: Option<String>,
28    bus: u8,
29    address: u8,
30}
31
32pub fn run_list(json: bool) -> Result<()> {
33    let devices = discover().map_err(|e| anyhow!("USB discovery failed: {e}"))?;
34    info!(count = devices.len(), "discovered USB3 Vision cameras");
35
36    if json {
37        let entries: Vec<UsbDeviceEntry> = devices
38            .iter()
39            .enumerate()
40            .map(|(idx, dev)| UsbDeviceEntry {
41                index: idx,
42                vendor_id: format!("{:04x}", dev.vendor_id),
43                product_id: format!("{:04x}", dev.product_id),
44                manufacturer: dev.manufacturer.clone(),
45                model: dev.model.clone(),
46                serial: dev.serial.clone(),
47                bus: dev.bus,
48                address: dev.address,
49            })
50            .collect();
51        common::print_json(&entries)?;
52        return Ok(());
53    }
54
55    if devices.is_empty() {
56        println!("No USB3 Vision cameras discovered.");
57        return Ok(());
58    }
59
60    println!(
61        "{:<6} {:<10} {:<20} {:<20} Serial",
62        "INDEX", "VID:PID", "Manufacturer", "Model"
63    );
64    for (idx, dev) in devices.iter().enumerate() {
65        println!(
66            "{idx:<6} {:04x}:{:04x}  {:<20} {:<20} {}",
67            dev.vendor_id,
68            dev.product_id,
69            dev.manufacturer.as_deref().unwrap_or("-"),
70            dev.model.as_deref().unwrap_or("-"),
71            dev.serial.as_deref().unwrap_or("-"),
72        );
73    }
74
75    Ok(())
76}
77
78// ---------------------------------------------------------------------------
79// get-usb / set-usb helpers
80// ---------------------------------------------------------------------------
81
82fn select_usb_device(index: Option<usize>) -> Result<U3vDeviceInfo> {
83    let devices = discover().map_err(|e| anyhow!("USB discovery failed: {e}"))?;
84
85    let idx = index.unwrap_or(0);
86    devices
87        .into_iter()
88        .nth(idx)
89        .ok_or_else(|| anyhow!("no USB3 Vision device at index {idx}"))
90}
91
92fn open_usb_camera(index: Option<usize>) -> Result<Camera<U3vRegisterIo<RusbTransfer>>> {
93    let dev = select_usb_device(index)?;
94    info!(
95        vid = format!("{:04x}", dev.vendor_id),
96        pid = format!("{:04x}", dev.product_id),
97        "connecting to USB3 Vision camera"
98    );
99    connect_u3v(&dev).map_err(|e| anyhow!("connect failed: {e}"))
100}
101
102// ---------------------------------------------------------------------------
103// get-usb
104// ---------------------------------------------------------------------------
105
106pub fn run_get(index: Option<usize>, name: String, json: bool) -> Result<()> {
107    let camera = open_usb_camera(index)?;
108    let value = camera.get(&name)?;
109
110    if json {
111        #[derive(Serialize)]
112        struct GetResult {
113            name: String,
114            value: String,
115        }
116        common::print_json(&GetResult { name, value })?;
117    } else {
118        println!("{value}");
119    }
120
121    Ok(())
122}
123
124// ---------------------------------------------------------------------------
125// set-usb
126// ---------------------------------------------------------------------------
127
128pub fn run_set(index: Option<usize>, name: String, value: String, json: bool) -> Result<()> {
129    let mut camera = open_usb_camera(index)?;
130    camera.set(&name, &value)?;
131
132    if json {
133        #[derive(Serialize)]
134        struct SetResult {
135            name: String,
136            value: String,
137            ok: bool,
138        }
139        common::print_json(&SetResult {
140            name,
141            value,
142            ok: true,
143        })?;
144    } else {
145        println!("OK");
146    }
147
148    Ok(())
149}
150
151// ---------------------------------------------------------------------------
152// stream-usb
153// ---------------------------------------------------------------------------
154
155pub fn run_stream(index: Option<usize>, save: usize, rgb: bool, duration_s: u64) -> Result<()> {
156    let mut camera = open_usb_camera(index)?;
157
158    // Read image dimensions from camera features.
159    let width: u32 = camera.get("Width")?.parse().unwrap_or(640);
160    let height: u32 = camera.get("Height")?.parse().unwrap_or(480);
161    let pf_str = camera
162        .get("PixelFormat")
163        .unwrap_or_else(|_| "Mono8".to_string());
164    let bpp: usize = PixelFormat::from_name(&pf_str)
165        .bytes_per_pixel()
166        .unwrap_or(1);
167    let payload_size = (width as usize) * (height as usize) * bpp;
168
169    info!(
170        width,
171        height,
172        pixel_format = pf_str,
173        payload_size,
174        "opening U3V stream"
175    );
176
177    // Open stream through the transport.
178    let mut device = camera.transport().lock_device()?;
179    let mut stream = device
180        .open_stream(payload_size as u64)
181        .map_err(|e| anyhow!("open stream failed: {e}"))?;
182
183    // Start acquisition.
184    drop(device); // Release lock before camera feature access.
185    camera.set("AcquisitionStart", "1")?;
186
187    let deadline = if duration_s > 0 {
188        Some(std::time::Instant::now() + std::time::Duration::from_secs(duration_s))
189    } else {
190        None
191    };
192
193    let mut frame_count: u64 = 0;
194    let mut saved: usize = 0;
195    let t0 = std::time::Instant::now();
196
197    loop {
198        if let Some(deadline) = deadline
199            && std::time::Instant::now() >= deadline
200        {
201            break;
202        }
203
204        let raw = match stream.next_frame() {
205            Ok(f) => f,
206            Err(e) => {
207                eprintln!("stream error: {e}");
208                break;
209            }
210        };
211
212        frame_count += 1;
213        let elapsed = t0.elapsed().as_secs_f64();
214        let fps = frame_count as f64 / elapsed.max(0.001);
215
216        let pixel_format = PixelFormat::from_code(raw.leader.pixel_format);
217        println!(
218            "frame #{frame_count}: {}x{} {pixel_format} block={} ({fps:.1} fps)",
219            raw.leader.width, raw.leader.height, raw.trailer.block_id,
220        );
221
222        // Save frames if requested.
223        if saved < save {
224            let payload = raw.payload.as_ref();
225            let path = if rgb {
226                let frame = viva_genicam::frame::Frame {
227                    payload: raw.payload.clone(),
228                    width: raw.leader.width,
229                    height: raw.leader.height,
230                    pixel_format,
231                    chunks: None,
232                    ts_dev: None,
233                    ts_host: None,
234                };
235                let rgb_data = frame
236                    .to_rgb8()
237                    .map_err(|e| anyhow!("RGB conversion: {e}"))?;
238                let path = PathBuf::from(format!("frame_{saved}.ppm"));
239                let encoded = common::encode_ppm(raw.leader.width, raw.leader.height, &rgb_data)?;
240                common::save_image(&encoded, &path)?;
241                path
242            } else {
243                let path = PathBuf::from(format!("frame_{saved}.pgm"));
244                let encoded = common::encode_pgm(raw.leader.width, raw.leader.height, payload)?;
245                common::save_image(&encoded, &path)?;
246                path
247            };
248            println!("  saved -> {}", path.display());
249            saved += 1;
250        }
251    }
252
253    println!(
254        "\n{frame_count} frames in {:.2}s ({:.1} fps)",
255        t0.elapsed().as_secs_f64(),
256        frame_count as f64 / t0.elapsed().as_secs_f64().max(0.001),
257    );
258
259    Ok(())
260}