1#![cfg_attr(docsrs, feature(doc_cfg))]
2pub use viva_genapi as genapi;
73pub use viva_gencp as gencp;
74pub use viva_gige as gige;
75pub use viva_pfnc as pfnc;
76pub use viva_sfnc as sfnc;
77#[cfg(feature = "u3v")]
78#[cfg_attr(docsrs, doc(cfg(feature = "u3v")))]
79pub use viva_u3v as u3v;
80
81pub mod chunks;
82pub mod events;
83pub mod frame;
84pub mod stream;
85pub mod time;
86
87use std::net::{IpAddr, Ipv4Addr};
88use std::sync::{Arc, Mutex, MutexGuard};
89use std::time::{Duration, Instant, SystemTime};
90
91use crate::events::{
92 bind_socket as bind_event_socket_internal,
93 configure_message_channel_raw as configure_message_channel_fallback,
94 enable_event_raw as enable_event_fallback, parse_event_id,
95};
96use crate::genapi::{GenApiError, Node, NodeMap, RegisterIo, SkOutput};
97use gige::GigeDevice;
98use gige::gvcp::consts as gvcp_consts;
99use thiserror::Error;
100use tokio::time::sleep;
101use tracing::{debug, info, warn};
102
103pub use chunks::{ChunkKind, ChunkMap, ChunkValue, parse_chunk_bytes};
104pub use events::{Event, EventStream};
105pub use frame::Frame;
106pub use gige::action::{AckSummary, ActionParams};
107pub use stream::{FrameStream, Stream, StreamBuilder, StreamDest};
108#[cfg(feature = "u3v")]
109#[cfg_attr(docsrs, doc(cfg(feature = "u3v")))]
110pub use stream::{U3vFrameStream, U3vStreamBuilder};
111pub use time::TimeSync;
112
113#[derive(Debug, Error)]
115#[non_exhaustive]
116pub enum GenicamError {
117 #[error(transparent)]
119 GenApi(#[from] GenApiError),
120 #[error("transport: {0}")]
122 Transport(String),
123 #[error("parse error: {0}")]
125 Parse(String),
126 #[error("chunk feature '{0}' not found; verify camera supports chunk data")]
128 MissingChunkFeature(String),
129 #[error("unsupported pixel format: {0}")]
131 UnsupportedPixelFormat(viva_pfnc::PixelFormat),
132}
133
134impl GenicamError {
135 fn parse<S: Into<String>>(msg: S) -> Self {
136 GenicamError::Parse(msg.into())
137 }
138
139 fn transport<S: Into<String>>(msg: S) -> Self {
140 GenicamError::Transport(msg.into())
141 }
142}
143
144#[derive(Debug)]
146pub struct Camera<T: RegisterIo> {
147 transport: T,
148 nodemap: NodeMap,
149 time_sync: TimeSync,
150}
151
152impl<T: RegisterIo> Camera<T> {
153 pub fn new(transport: T, nodemap: NodeMap) -> Self {
155 Self {
156 transport,
157 nodemap,
158 time_sync: TimeSync::with_capacity(64),
159 }
160 }
161
162 #[inline]
163 fn with_map<R>(&mut self, f: impl FnOnce(&mut NodeMap, &T) -> R) -> R {
164 let transport = &self.transport;
165 let nodemap = &mut self.nodemap;
166 f(nodemap, transport)
167 }
168
169 pub fn transport(&self) -> &T {
171 &self.transport
172 }
173
174 pub fn transport_mut(&mut self) -> &mut T {
176 &mut self.transport
177 }
178
179 pub fn nodemap(&self) -> &NodeMap {
181 &self.nodemap
182 }
183
184 pub fn nodemap_mut(&mut self) -> &mut NodeMap {
186 &mut self.nodemap
187 }
188
189 pub fn enum_entries(&self, name: &str) -> Result<Vec<String>, GenicamError> {
191 self.nodemap.enum_entries(name).map_err(Into::into)
192 }
193
194 pub fn get(&self, name: &str) -> Result<String, GenicamError> {
196 match self.nodemap.node(name) {
197 Some(Node::Integer(_)) => {
198 Ok(self.nodemap.get_integer(name, &self.transport)?.to_string())
199 }
200 Some(Node::Float(_)) => Ok(self.nodemap.get_float(name, &self.transport)?.to_string()),
201 Some(Node::Enum(_)) => self
202 .nodemap
203 .get_enum(name, &self.transport)
204 .map_err(Into::into),
205 Some(Node::Boolean(_)) => Ok(self.nodemap.get_bool(name, &self.transport)?.to_string()),
206 Some(Node::SwissKnife(sk)) => match sk.output {
207 SkOutput::Float => Ok(self.nodemap.get_float(name, &self.transport)?.to_string()),
208 SkOutput::Integer => {
209 Ok(self.nodemap.get_integer(name, &self.transport)?.to_string())
210 }
211 },
212 Some(Node::Converter(conv)) => match conv.output {
213 SkOutput::Float => Ok(self
214 .nodemap
215 .get_converter(name, &self.transport)?
216 .to_string()),
217 SkOutput::Integer => {
218 Ok((self.nodemap.get_converter(name, &self.transport)? as i64).to_string())
219 }
220 },
221 Some(Node::IntConverter(_)) => Ok(self
222 .nodemap
223 .get_int_converter(name, &self.transport)?
224 .to_string()),
225 Some(Node::String(_)) => self
226 .nodemap
227 .get_string(name, &self.transport)
228 .map_err(Into::into),
229 Some(Node::Command(_)) => {
230 Err(GenicamError::GenApi(GenApiError::Type(name.to_string())))
231 }
232 Some(Node::Category(_)) => Ok(String::new()),
233 None => Err(GenApiError::NodeNotFound(name.to_string()).into()),
234 }
235 }
236
237 pub fn set(&mut self, name: &str, value: &str) -> Result<(), GenicamError> {
239 match self.nodemap.node(name) {
240 Some(Node::Integer(_)) => {
241 let parsed: i64 = value
242 .parse()
243 .map_err(|_| GenicamError::parse(format!("invalid integer for {name}")))?;
244 self.nodemap
245 .set_integer(name, parsed, &self.transport)
246 .map_err(Into::into)
247 }
248 Some(Node::Float(_)) => {
249 let parsed: f64 = value
250 .parse()
251 .map_err(|_| GenicamError::parse(format!("invalid float for {name}")))?;
252 self.nodemap
253 .set_float(name, parsed, &self.transport)
254 .map_err(Into::into)
255 }
256 Some(Node::Enum(_)) => self
257 .nodemap
258 .set_enum(name, value, &self.transport)
259 .map_err(Into::into),
260 Some(Node::Boolean(_)) => {
261 let parsed = parse_bool(value).ok_or_else(|| {
262 GenicamError::parse(format!("invalid boolean for {name}: {value}"))
263 })?;
264 self.nodemap
265 .set_bool(name, parsed, &self.transport)
266 .map_err(Into::into)
267 }
268 Some(Node::SwissKnife(_)) => Err(GenApiError::Type(name.to_string()).into()),
269 Some(Node::Converter(_)) => {
270 Err(GenApiError::Type(name.to_string()).into())
273 }
274 Some(Node::IntConverter(_)) => Err(GenApiError::Type(name.to_string()).into()),
275 Some(Node::String(_)) => self
276 .nodemap
277 .set_string(name, value, &self.transport)
278 .map_err(Into::into),
279 Some(Node::Command(_)) => self
280 .nodemap
281 .exec_command(name, &self.transport)
282 .map_err(Into::into),
283 Some(Node::Category(_)) => Err(GenApiError::Type(name.to_string()).into()),
284 None => Err(GenApiError::NodeNotFound(name.to_string()).into()),
285 }
286 }
287
288 pub fn set_exposure_time_us(&mut self, value: f64) -> Result<(), GenicamError> {
290 self.set_float_feature("ExposureTime", value)
292 }
293
294 pub fn set_gain_db(&mut self, value: f64) -> Result<(), GenicamError> {
296 self.set_float_feature("Gain", value)
297 }
298
299 fn set_float_feature(&mut self, name: &str, value: f64) -> Result<(), GenicamError> {
300 match self.nodemap.node(name) {
301 Some(Node::Float(_)) => self
302 .nodemap
303 .set_float(name, value, &self.transport)
304 .map_err(Into::into),
305 Some(_) => Err(GenApiError::Type(name.to_string()).into()),
306 None => Err(GenApiError::NodeNotFound(name.to_string()).into()),
307 }
308 }
309
310 pub async fn time_calibrate(
312 &mut self,
313 samples: usize,
314 interval_ms: u64,
315 ) -> Result<(), GenicamError> {
316 if samples < 2 {
317 return Err(GenicamError::transport(
318 "time calibration requires at least two samples",
319 ));
320 }
321
322 let cap = samples.max(self.time_sync.capacity());
323 self.time_sync = TimeSync::with_capacity(cap);
324
325 let latch_cmd = self.find_alias(viva_sfnc::TS_LATCH_CMDS);
326 let value_node = self
327 .find_alias(viva_sfnc::TS_VALUE_NODES)
328 .ok_or_else(|| GenApiError::NodeNotFound("TimestampValue".into()))?;
329
330 let mut freq_hz = if let Some(name) = self.find_alias(viva_sfnc::TS_FREQ_NODES) {
331 match self.nodemap.get_integer(name, &self.transport) {
332 Ok(value) if value > 0 => Some(value as f64),
333 Ok(_) => None,
334 Err(err) => {
335 debug!(node = name, error = %err, "failed to read timestamp frequency");
336 None
337 }
338 }
339 } else {
340 None
341 };
342
343 info!(samples, interval_ms, "starting time calibration");
344 let mut first_sample: Option<(u64, Instant)> = None;
345 let mut last_sample: Option<(u64, Instant)> = None;
346
347 for idx in 0..samples {
348 if let Some(cmd) = latch_cmd {
349 self.nodemap
350 .exec_command(cmd, &self.transport)
351 .map_err(GenicamError::from)?;
352 }
353
354 let raw_ticks = self
355 .nodemap
356 .get_integer(value_node, &self.transport)
357 .map_err(GenicamError::from)?;
358 let dev_ticks = u64::try_from(raw_ticks).map_err(|_| {
359 GenicamError::transport("timestamp value is negative; unsupported camera")
360 })?;
361 let host = Instant::now();
362 self.time_sync.update(dev_ticks, host);
363 if idx == 0 {
364 first_sample = Some((dev_ticks, host));
365 }
366 last_sample = Some((dev_ticks, host));
367 if let Some(origin) = self.time_sync.origin_instant() {
368 let ns = host.duration_since(origin).as_nanos();
369 debug!(
370 sample = idx,
371 ticks = dev_ticks,
372 host_ns = ns,
373 "timestamp sample"
374 );
375 } else {
376 debug!(sample = idx, ticks = dev_ticks, "timestamp sample");
377 }
378
379 if interval_ms > 0 && idx + 1 < samples {
380 sleep(Duration::from_millis(interval_ms)).await;
381 }
382 }
383
384 if freq_hz.is_none()
385 && let (Some((first_ticks, first_host)), Some((last_ticks, last_host))) =
386 (first_sample, last_sample)
387 && last_ticks > first_ticks
388 && let Some(delta) = last_host.checked_duration_since(first_host)
389 {
390 let secs = delta.as_secs_f64();
391 if secs > 0.0 {
392 freq_hz = Some((last_ticks - first_ticks) as f64 / secs);
393 }
394 }
395
396 let (a, b) = self
397 .time_sync
398 .fit(freq_hz)
399 .ok_or_else(|| GenicamError::transport("insufficient samples for timestamp fit"))?;
400
401 if let Some(freq) = freq_hz {
402 info!(freq_hz = freq, a, b, "time calibration complete");
403 } else {
404 info!(a, b, "time calibration complete");
405 }
406
407 Ok(())
408 }
409
410 pub fn map_dev_ts(&self, dev_ticks: u64) -> SystemTime {
412 self.time_sync.to_host_time(dev_ticks)
413 }
414
415 pub fn time_sync(&self) -> &TimeSync {
417 &self.time_sync
418 }
419
420 pub fn time_reset(&mut self) -> Result<(), GenicamError> {
422 if let Some(cmd) = self.find_alias(viva_sfnc::TS_RESET_CMDS) {
423 self.nodemap
424 .exec_command(cmd, &self.transport)
425 .map_err(GenicamError::from)?;
426 self.time_sync = TimeSync::with_capacity(self.time_sync.capacity());
427 info!(command = cmd, "timestamp counter reset");
428 }
429 Ok(())
430 }
431
432 pub fn acquisition_start(&mut self) -> Result<(), GenicamError> {
434 self.nodemap
435 .exec_command("AcquisitionStart", &self.transport)
436 .map_err(Into::into)
437 }
438
439 pub fn acquisition_stop(&mut self) -> Result<(), GenicamError> {
441 self.nodemap
442 .exec_command("AcquisitionStop", &self.transport)
443 .map_err(Into::into)
444 }
445
446 pub fn configure_chunks(&mut self, cfg: &ChunkConfig) -> Result<(), GenicamError> {
448 self.ensure_chunk_feature(viva_sfnc::CHUNK_MODE_ACTIVE)?;
449 self.ensure_chunk_feature(viva_sfnc::CHUNK_SELECTOR)?;
450 self.ensure_chunk_feature(viva_sfnc::CHUNK_ENABLE)?;
451
452 self.with_map(|nm, tr| {
454 nm.set_bool(viva_sfnc::CHUNK_MODE_ACTIVE, cfg.active, tr)?;
455 for s in &cfg.selectors {
456 nm.set_enum(viva_sfnc::CHUNK_SELECTOR, s, tr)?;
457 nm.set_bool(viva_sfnc::CHUNK_ENABLE, cfg.active, tr)?;
458 }
459 Ok(())
460 })
461 }
462
463 pub async fn configure_events(
465 &mut self,
466 local_ip: Ipv4Addr,
467 port: u16,
468 enable_ids: &[&str],
469 ) -> Result<(), GenicamError> {
470 info!(%local_ip, port, "configuring GVCP events");
471 let msg_sel = self.find_alias(viva_sfnc::MSG_SEL);
473 let msg_ip = self.find_alias(viva_sfnc::MSG_IP);
474 let msg_port = self.find_alias(viva_sfnc::MSG_PORT);
475 let msg_en = self.find_alias(viva_sfnc::MSG_EN);
476
477 let channel_configured = self.with_map(|nodemap, transport| {
478 let mut ok = true;
479
480 if let Some(selector) = msg_sel {
481 match nodemap.enum_entries(selector) {
482 Ok(entries) => {
483 if let Some(entry) = entries.into_iter().next() {
484 if let Err(err) = nodemap.set_enum(selector, &entry, transport) {
485 warn!(node = selector, error = %err, "failed to set message selector");
486 ok = false;
487 }
488 } else {
489 warn!(node = selector, "message selector missing entries");
490 ok = false;
491 }
492 }
493 Err(err) => {
494 warn!(feature = selector, error = %err, "failed to query message selector");
495 ok = false;
496 }
497 }
498 } else {
499 ok = false;
500 }
501
502 if let Some(node) = msg_ip {
503 let value = u32::from(local_ip) as i64;
504 if let Err(err) = nodemap.set_integer(node, value, transport) {
505 warn!(feature = node, error = %err, "failed to write message IP");
506 ok = false;
507 }
508 } else {
509 ok = false;
510 }
511
512 if let Some(node) = msg_port {
513 if let Err(err) = nodemap.set_integer(node, port as i64, transport) {
514 warn!(feature = node, error = %err, "failed to write message port");
515 ok = false;
516 }
517 } else {
518 ok = false;
519 }
520
521 if let Some(node) = msg_en {
522 if let Err(err) = nodemap.set_bool(node, true, transport) {
523 warn!(feature = node, error = %err, "failed to enable message channel");
524 ok = false;
525 }
526 } else {
527 ok = false;
528 }
529
530 ok
531 });
532
533 if !channel_configured {
534 configure_message_channel_fallback(&self.transport, local_ip, port)?;
535 }
536
537 let mut used_sfnc = self.nodemap.node(viva_sfnc::EVENT_SELECTOR).is_some()
538 && self.nodemap.node(viva_sfnc::EVENT_NOTIFICATION).is_some();
539
540 used_sfnc = self.with_map(|nodemap, transport| {
541 if !used_sfnc {
542 return false;
543 }
544 for &name in enable_ids {
545 if let Err(err) = nodemap.set_enum(viva_sfnc::EVENT_SELECTOR, name, transport) {
546 warn!(event = name, error = %err, "failed to select event via SFNC");
547 return false;
548 }
549 if let Err(err) = nodemap.set_enum(
550 viva_sfnc::EVENT_NOTIFICATION,
551 viva_sfnc::EVENT_NOTIF_ON,
552 transport,
553 ) {
554 warn!(event = name, error = %err, "failed to enable event via SFNC");
555 return false;
556 }
557 }
558 true
559 });
560
561 if !used_sfnc {
562 for &name in enable_ids {
563 let Some(event_id) = parse_event_id(name) else {
564 return Err(GenicamError::transport(format!(
565 "event '{name}' missing from nodemap and not numeric"
566 )));
567 };
568 enable_event_fallback(&self.transport, event_id, true)?;
569 }
570 }
571
572 Ok(())
573 }
574
575 pub fn configure_stream_multicast(
577 &mut self,
578 stream_idx: u32,
579 group: Ipv4Addr,
580 port: u16,
581 ) -> Result<(), GenicamError> {
582 if (group.octets()[0] & 0xF0) != 0xE0 {
583 return Err(GenicamError::transport(
584 "multicast group must be within 224.0.0.0/4",
585 ));
586 }
587 info!(stream_idx, %group, port, "configuring multicast stream");
588
589 let dest_addr_node = self.find_alias(viva_sfnc::SCP_DEST_ADDR);
591 let host_port_node = self.find_alias(viva_sfnc::SCP_HOST_PORT);
592 let mcast_en_node = self.find_alias(viva_sfnc::MULTICAST_ENABLE);
593
594 let mut used_sfnc = true;
595 self.with_map(|nm, tr| {
596 if nm.node(viva_sfnc::STREAM_CH_SELECTOR).is_some() {
597 if let Err(err) =
598 nm.set_integer(viva_sfnc::STREAM_CH_SELECTOR, stream_idx as i64, tr)
599 {
600 warn!(
601 channel = stream_idx,
602 error = %err,
603 "failed to select stream channel via SFNC"
604 );
605 used_sfnc = false;
606 }
607 } else {
608 used_sfnc = false;
609 }
610
611 if let Some(node) = dest_addr_node {
612 if let Err(err) = nm.set_integer(node, u32::from(group) as i64, tr) {
613 warn!(feature = node, error = %err, "failed to write multicast address");
614 used_sfnc = false;
615 }
616 } else {
617 used_sfnc = false;
618 }
619
620 if let Some(node) = host_port_node {
621 if let Err(err) = nm.set_integer(node, port as i64, tr) {
622 warn!(feature = node, error = %err, "failed to write multicast port");
623 used_sfnc = false;
624 }
625 } else {
626 used_sfnc = false;
627 }
628
629 if let Some(node) = mcast_en_node {
630 let _ = nm.set_bool(node, true, tr);
631 }
632 });
633
634 if !used_sfnc {
635 let base = gvcp_consts::STREAM_CHANNEL_BASE
636 + stream_idx as u64 * gvcp_consts::STREAM_CHANNEL_STRIDE;
637 let addr_reg = base + gvcp_consts::STREAM_DESTINATION_ADDRESS;
638 self.transport
639 .write(addr_reg, &group.octets())
640 .map_err(|err| GenicamError::transport(format!("write multicast addr: {err}")))?;
641 let port_reg = base + gvcp_consts::STREAM_DESTINATION_PORT;
642 self.transport
643 .write(port_reg, &port.to_be_bytes())
644 .map_err(|err| GenicamError::transport(format!("write multicast port: {err}")))?;
645 info!(
646 stream_idx,
647 %group,
648 port,
649 "configured multicast destination via raw registers"
650 );
651 } else {
652 info!(
653 stream_idx,
654 %group,
655 port,
656 "configured multicast destination via SFNC"
657 );
658 }
659
660 Ok(())
661 }
662
663 pub async fn open_event_stream(
665 &self,
666 local_ip: Ipv4Addr,
667 port: u16,
668 ) -> Result<EventStream, GenicamError> {
669 let socket = bind_event_socket_internal(IpAddr::V4(local_ip), port).await?;
670 let time_sync = if !self.time_sync.is_empty() {
671 Some(Arc::new(self.time_sync.clone()))
672 } else {
673 None
674 };
675 Ok(EventStream::new(socket, time_sync))
676 }
677
678 fn ensure_chunk_feature(&self, name: &str) -> Result<(), GenicamError> {
679 if self.nodemap.node(name).is_none() {
680 return Err(GenicamError::MissingChunkFeature(name.to_string()));
681 }
682 Ok(())
683 }
684
685 fn find_alias(&self, names: &[&'static str]) -> Option<&'static str> {
686 names
687 .iter()
688 .copied()
689 .find(|name| self.nodemap.node(name).is_some())
690 }
691}
692
693#[derive(Debug, Clone, Default)]
695pub struct ChunkConfig {
696 pub selectors: Vec<String>,
698 pub active: bool,
700}
701
702pub struct GigeRegisterIo {
714 handle: tokio::runtime::Handle,
715 device: Mutex<GigeDevice>,
716}
717
718impl GigeRegisterIo {
719 pub fn new(handle: tokio::runtime::Handle, device: GigeDevice) -> Self {
721 Self {
722 handle,
723 device: Mutex::new(device),
724 }
725 }
726
727 pub fn lock_device(&self) -> Result<MutexGuard<'_, GigeDevice>, GenicamError> {
732 self.device
733 .lock()
734 .map_err(|_| GenicamError::transport("gige device mutex poisoned"))
735 }
736
737 fn lock(&self) -> Result<MutexGuard<'_, GigeDevice>, GenApiError> {
738 self.device
739 .lock()
740 .map_err(|_| GenApiError::Io("gige device mutex poisoned".into()))
741 }
742}
743
744impl RegisterIo for GigeRegisterIo {
745 fn read(&self, addr: u64, len: usize) -> Result<Vec<u8>, GenApiError> {
746 let mut device = self.lock()?;
747 let fut = device.read_mem(addr, len);
748 if tokio::runtime::Handle::try_current().is_ok() {
749 tokio::task::block_in_place(|| self.handle.block_on(fut))
750 } else {
751 self.handle.block_on(fut)
752 }
753 .map_err(|err| GenApiError::Io(err.to_string()))
754 }
755
756 fn write(&self, addr: u64, data: &[u8]) -> Result<(), GenApiError> {
757 let mut device = self.lock()?;
758 let fut = device.write_mem(addr, data);
759 if tokio::runtime::Handle::try_current().is_ok() {
760 tokio::task::block_in_place(|| self.handle.block_on(fut))
761 } else {
762 self.handle.block_on(fut)
763 }
764 .map_err(|err| GenApiError::Io(err.to_string()))
765 }
766}
767
768pub async fn connect_gige(
788 device: &gige::DeviceInfo,
789) -> Result<Camera<GigeRegisterIo>, GenicamError> {
790 let (camera, _xml) = connect_gige_with_xml(device).await?;
791 Ok(camera)
792}
793
794pub async fn connect_gige_with_xml(
800 device: &gige::DeviceInfo,
801) -> Result<(Camera<GigeRegisterIo>, String), GenicamError> {
802 use std::net::{IpAddr, SocketAddr};
803 use std::sync::Arc;
804 use tokio::sync::Mutex as AsyncMutex;
805
806 let control_addr = SocketAddr::new(IpAddr::V4(device.ip), gige::GVCP_PORT);
807 info!(%control_addr, "connecting to GigE Vision camera");
808
809 let mut device = gige::GigeDevice::open(control_addr)
810 .await
811 .map_err(|e| GenicamError::transport(e.to_string()))?;
812
813 device
815 .claim_control()
816 .await
817 .map_err(|e| GenicamError::transport(e.to_string()))?;
818
819 let control = Arc::new(AsyncMutex::new(device));
820
821 let xml = viva_genapi_xml::fetch_and_load_xml({
823 let control = control.clone();
824 move |address, length| {
825 let control = control.clone();
826 async move {
827 let mut dev = control.lock().await;
828 dev.read_mem(address, length)
829 .await
830 .map_err(|err| viva_genapi_xml::XmlError::Transport(err.to_string()))
831 }
832 }
833 })
834 .await
835 .map_err(|e| GenicamError::transport(e.to_string()))?;
836
837 let model = viva_genapi_xml::parse(&xml).map_err(|e| GenicamError::transport(e.to_string()))?;
838 let nodemap = genapi::NodeMap::from(model);
839
840 let handle = tokio::runtime::Handle::current();
842 let control_device = Arc::try_unwrap(control)
843 .map_err(|_| GenicamError::transport("control connection still in use"))?
844 .into_inner();
845 let transport = GigeRegisterIo::new(handle, control_device);
846
847 info!("GigE camera connected successfully");
848 Ok((Camera::new(transport, nodemap), xml))
849}
850
851#[cfg(feature = "u3v")]
862#[cfg_attr(docsrs, doc(cfg(feature = "u3v")))]
863pub struct U3vRegisterIo<T: u3v::usb::UsbTransfer + 'static> {
864 device: Mutex<u3v::device::U3vDevice<T>>,
865}
866
867#[cfg(feature = "u3v")]
868impl<T: u3v::usb::UsbTransfer + 'static> U3vRegisterIo<T> {
869 pub fn new(device: u3v::device::U3vDevice<T>) -> Self {
871 Self {
872 device: Mutex::new(device),
873 }
874 }
875
876 pub fn lock_device(&self) -> Result<MutexGuard<'_, u3v::device::U3vDevice<T>>, GenicamError> {
878 self.device
879 .lock()
880 .map_err(|_| GenicamError::transport("u3v device mutex poisoned"))
881 }
882
883 fn lock(&self) -> Result<MutexGuard<'_, u3v::device::U3vDevice<T>>, GenApiError> {
884 self.device
885 .lock()
886 .map_err(|_| GenApiError::Io("u3v device mutex poisoned".into()))
887 }
888}
889
890#[cfg(feature = "u3v")]
891impl<T: u3v::usb::UsbTransfer + 'static> RegisterIo for U3vRegisterIo<T> {
892 fn read(&self, addr: u64, len: usize) -> Result<Vec<u8>, GenApiError> {
893 let mut device = self.lock()?;
894 device
895 .read_mem(addr, len)
896 .map_err(|e| GenApiError::Io(e.to_string()))
897 }
898
899 fn write(&self, addr: u64, data: &[u8]) -> Result<(), GenApiError> {
900 let mut device = self.lock()?;
901 device
902 .write_mem(addr, data)
903 .map_err(|e| GenApiError::Io(e.to_string()))
904 }
905}
906
907#[cfg(feature = "u3v-usb")]
926#[cfg_attr(docsrs, doc(cfg(feature = "u3v-usb")))]
927pub fn connect_u3v(
928 device: &u3v::discovery::U3vDeviceInfo,
929) -> Result<Camera<U3vRegisterIo<u3v::usb::RusbTransfer>>, GenicamError> {
930 let (camera, _xml) = connect_u3v_with_xml(device)?;
931 Ok(camera)
932}
933
934#[cfg(feature = "u3v-usb")]
937#[cfg_attr(docsrs, doc(cfg(feature = "u3v-usb")))]
938pub fn connect_u3v_with_xml(
939 device_info: &u3v::discovery::U3vDeviceInfo,
940) -> Result<(Camera<U3vRegisterIo<u3v::usb::RusbTransfer>>, String), GenicamError> {
941 info!(
942 vendor_id = device_info.vendor_id,
943 product_id = device_info.product_id,
944 "connecting to USB3 Vision camera"
945 );
946
947 let mut device = u3v::device::U3vDevice::open_device(device_info)
948 .map_err(|e| GenicamError::transport(e.to_string()))?;
949
950 let xml = device
951 .fetch_xml()
952 .map_err(|e| GenicamError::transport(e.to_string()))?;
953
954 let model = viva_genapi_xml::parse(&xml).map_err(|e| GenicamError::transport(e.to_string()))?;
955 let nodemap = genapi::NodeMap::from(model);
956 let transport = U3vRegisterIo::new(device);
957
958 info!("USB3 Vision camera connected successfully");
959 Ok((Camera::new(transport, nodemap), xml))
960}
961
962#[cfg(feature = "u3v")]
969#[cfg_attr(docsrs, doc(cfg(feature = "u3v")))]
970pub fn open_u3v_device<T: u3v::usb::UsbTransfer + 'static>(
971 mut device: u3v::device::U3vDevice<T>,
972) -> Result<(Camera<U3vRegisterIo<T>>, String), GenicamError> {
973 let xml = device
974 .fetch_xml()
975 .map_err(|e| GenicamError::transport(e.to_string()))?;
976 let model = viva_genapi_xml::parse(&xml).map_err(|e| GenicamError::transport(e.to_string()))?;
977 let nodemap = genapi::NodeMap::from(model);
978 let transport = U3vRegisterIo::new(device);
979 Ok((Camera::new(transport, nodemap), xml))
980}
981
982fn parse_bool(value: &str) -> Option<bool> {
983 match value.trim().to_ascii_lowercase().as_str() {
984 "1" | "true" => Some(true),
985 "0" | "false" => Some(false),
986 _ => None,
987 }
988}