use std::net::{IpAddr, Ipv4Addr};
use std::sync::Arc;
use std::time::SystemTime;
use bytes::Bytes;
use tracing::{debug, info, warn};
use viva_gige::gvcp::consts as gvcp_consts;
use viva_gige::message::{EventPacket, EventSocket};
use crate::GenicamError;
use crate::time::TimeSync;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Event {
pub id: u16,
pub ts_dev: u64,
pub ts_host: SystemTime,
pub data: Bytes,
}
pub struct EventStream {
socket: EventSocket,
time_sync: Option<Arc<TimeSync>>,
}
impl EventStream {
pub(crate) fn new(socket: EventSocket, time_sync: Option<Arc<TimeSync>>) -> Self {
Self { socket, time_sync }
}
pub async fn next(&self) -> Result<Event, GenicamError> {
let packet = self
.socket
.recv()
.await
.map_err(|err| GenicamError::transport(format!("gvcp message recv: {err}")))?;
debug!(
event_id = packet.event_id,
ts_dev = packet.timestamp_dev,
"event received"
);
Ok(Self::map_packet(packet, self.time_sync.clone()))
}
pub fn local_addr(&self) -> Result<std::net::SocketAddr, GenicamError> {
self.socket
.local_addr()
.map_err(|err| GenicamError::transport(format!("gvcp local addr: {err}")))
}
fn map_packet(packet: EventPacket, sync: Option<Arc<TimeSync>>) -> Event {
let ts_host = match sync {
Some(sync) if sync.len() > 1 => sync.to_host_time(packet.timestamp_dev),
Some(sync) => {
warn!("insufficient time sync samples; using current system time");
let _ = sync; SystemTime::now()
}
None => SystemTime::now(),
};
Event {
id: packet.event_id,
ts_dev: packet.timestamp_dev,
ts_host,
data: packet.payload,
}
}
}
pub(crate) fn configure_message_channel_raw<T: crate::genapi::RegisterIo>(
transport: &T,
ip: Ipv4Addr,
port: u16,
) -> Result<(), GenicamError> {
let addr = gvcp_consts::MESSAGE_DESTINATION_ADDRESS;
transport
.write(addr, &ip.octets())
.map_err(|err| GenicamError::transport(format!("write message addr: {err}")))?;
transport
.write(gvcp_consts::MESSAGE_DESTINATION_PORT, &port.to_be_bytes())
.map_err(|err| GenicamError::transport(format!("write message port: {err}")))?;
info!(%ip, port, "configured message channel via raw registers");
Ok(())
}
pub(crate) fn enable_event_raw<T: crate::genapi::RegisterIo>(
transport: &T,
event_id: u16,
on: bool,
) -> Result<(), GenicamError> {
let index = (event_id / 32) as u64;
let bit = 1u32 << (event_id % 32);
let addr =
gvcp_consts::EVENT_NOTIFICATION_BASE + index * gvcp_consts::EVENT_NOTIFICATION_STRIDE;
let current = transport
.read(addr, 4)
.map_err(|err| GenicamError::transport(format!("read event mask: {err}")))?;
if current.len() != 4 {
return Err(GenicamError::transport("event mask length mismatch"));
}
let mut bytes = [0u8; 4];
bytes.copy_from_slice(¤t);
let mut value = u32::from_be_bytes(bytes);
if on {
value |= bit;
} else {
value &= !bit;
}
transport
.write(addr, &value.to_be_bytes())
.map_err(|err| GenicamError::transport(format!("write event mask: {err}")))?;
info!(event_id, enabled = on, "updated event notification mask");
Ok(())
}
pub(crate) fn parse_event_id(text: &str) -> Option<u16> {
if let Some(stripped) = text.strip_prefix("0x") {
u16::from_str_radix(stripped, 16).ok()
} else if let Some(stripped) = text.strip_prefix("0X") {
u16::from_str_radix(stripped, 16).ok()
} else {
text.parse::<u16>().ok()
}
}
pub(crate) async fn bind_socket(ip: IpAddr, port: u16) -> Result<EventSocket, GenicamError> {
EventSocket::bind(ip, port)
.await
.map_err(|err| GenicamError::transport(format!("bind event socket: {err}")))
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::SocketAddr;
#[test]
fn parse_numeric_event_ids() {
assert_eq!(parse_event_id("1234"), Some(1234));
assert_eq!(parse_event_id("0x00AF"), Some(0x00AF));
assert_eq!(parse_event_id("0XFF10"), Some(0xFF10));
assert_eq!(parse_event_id("not-a-number"), None);
}
#[test]
fn map_packet_without_sync() {
let packet = EventPacket {
src: SocketAddr::from(([127, 0, 0, 1], 4000)),
event_id: 0x1000,
timestamp_dev: 42,
stream_channel: 0,
block_id: 0,
payload: Bytes::from_static(b"abcd"),
};
let event = EventStream::map_packet(packet.clone(), None);
assert_eq!(event.id, packet.event_id);
assert_eq!(event.ts_dev, packet.timestamp_dev);
assert_eq!(event.data, packet.payload);
}
}