1use 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#[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
78fn 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
102pub 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
124pub 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
151pub fn run_stream(index: Option<usize>, save: usize, rgb: bool, duration_s: u64) -> Result<()> {
156 let mut camera = open_usb_camera(index)?;
157
158 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 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 drop(device); 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 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}