diff --git a/README.md b/README.md index 7f786ca..25c4d53 100644 --- a/README.md +++ b/README.md @@ -162,9 +162,9 @@ The fields of the USB configuration are identical across devices, but the defaul ```py @dataclasses.dataclass class UsbConfiguration: - buffer_size: serde.type.uint64 = 131072 # size of each buffer in the ring, in bytes - ring_size: serde.type.uint64 = 4096 # number of buffers in the ring, the total size is ring_size * buffer_size - transfer_queue_size: serde.type.uint64 = 32 # number of libusb transfers submitted in parallel + buffer_length: serde.type.uint64 = 131072 # size of each buffer in the ring, in bytes + ring_length: serde.type.uint64 = 4096 # number of buffers in the ring, the total size is ring_length * buffer_length + transfer_queue_length: serde.type.uint64 = 32 # number of libusb transfers submitted in parallel allow_dma: bool = False # whether to enable Direct Memory Access ``` @@ -172,7 +172,7 @@ class UsbConfiguration: See _python/examples_ for different usage examples. -_python/examples/any_display.py_ implements a live event viewer with exponential decays caculated by the GPU. It requires vispy and glfw (`pip install vispy glfw`). +_python/examples/any_display.py_ implements a live event viewer with exponential decays caculated by the GPU. It requires vispy and glfw (`pip install vispy glfw pyopengl`). _python/examples/evk4_plot_hot_pixels_ generates plots and require Plotly (`pip install plotly pandas kaleido`). diff --git a/drivers/src/device.rs b/drivers/src/device.rs index 864efd5..0c040ab 100644 --- a/drivers/src/device.rs +++ b/drivers/src/device.rs @@ -1,4 +1,4 @@ -use crate::error; +use crate::flag; use crate::usb; use rusb::UsbContext; @@ -31,15 +31,16 @@ pub trait Usb: Sized { fn update_configuration(&self, configuration: Self::Configuration); - fn open( + fn open( serial: &Option<&str>, configuration: Self::Configuration, usb_configuration: &usb::Configuration, event_loop: std::sync::Arc, - error_flag: error::Flag, + flag: flag::Flag, ) -> Result where - IntoError: From + Clone + Send + 'static; + IntoError: From + Clone + Send + 'static, + IntoWarning: From + Clone + Send + 'static; fn next_with_timeout(&self, timeout: &std::time::Duration) -> Option; diff --git a/drivers/src/devices.rs b/drivers/src/devices.rs index 9939aff..4059494 100644 --- a/drivers/src/devices.rs +++ b/drivers/src/devices.rs @@ -1,7 +1,7 @@ use crate::adapters; use crate::device::TemperatureCelsius; use crate::device::Usb; -use crate::error; +use crate::flag; use crate::usb; use rusb::UsbContext; @@ -115,7 +115,7 @@ macro_rules! register { configuration: Option, usb_configuration: Option, event_loop: std::sync::Arc, - error_flag: error::Flag, + flag: flag::Flag, ) -> Result { match configuration { @@ -130,7 +130,7 @@ macro_rules! register { .as_ref() .unwrap_or(&$module::Device::DEFAULT_USB_CONFIGURATION), event_loop.clone(), - error_flag.clone(), + flag.clone(), ) .map(|device| paste::paste! {Device::[<$module:camel>](device)}) .map_err(|error| Error::from(error).unpack())? @@ -147,7 +147,7 @@ macro_rules! register { .as_ref() .unwrap_or(&$module::Device::DEFAULT_USB_CONFIGURATION), event_loop.clone(), - error_flag.clone(), + flag.clone(), ) { Ok(device) => return Ok(Device::[<$module:camel>](device)), Err(error) => match Error::from(error).unpack() { diff --git a/drivers/src/devices/prophesee_evk3_hd.rs b/drivers/src/devices/prophesee_evk3_hd.rs index 8f59d89..fcddef1 100644 --- a/drivers/src/devices/prophesee_evk3_hd.rs +++ b/drivers/src/devices/prophesee_evk3_hd.rs @@ -1,10 +1,12 @@ use crate::adapters; use crate::configuration; use crate::device; -use crate::error; +use crate::flag; use crate::properties; use crate::usb; +use device::Usb; + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] pub struct Biases { pub pr: u8, @@ -64,6 +66,23 @@ pub struct Device { serial: String, } +pub const PROPERTIES: properties::Camera = Device::PROPERTIES; +pub const DEFAULT_CONFIGURATION: Configuration = Device::PROPERTIES.default_configuration; +pub const DEFAULT_USB_CONFIGURATION: usb::Configuration = Device::DEFAULT_USB_CONFIGURATION; +pub fn open( + serial: &Option<&str>, + configuration: Configuration, + usb_configuration: &usb::Configuration, + event_loop: std::sync::Arc, + flag: flag::Flag, +) -> Result +where + IntoError: From + Clone + Send + 'static, + IntoWarning: From + Clone + Send + 'static, +{ + Device::open(serial, configuration, usb_configuration, event_loop, flag) +} + impl device::Usb for Device { type Adapter = adapters::evt3::Adapter; @@ -102,9 +121,9 @@ impl device::Usb for Device { }; const DEFAULT_USB_CONFIGURATION: usb::Configuration = usb::Configuration { - buffer_size: 1 << 17, - ring_size: 1 << 12, - transfer_queue_size: 1 << 5, + buffer_length: 1 << 17, + ring_length: 1 << 12, + transfer_queue_length: 1 << 5, allow_dma: false, }; @@ -127,15 +146,16 @@ impl device::Usb for Device { self.configuration_updater.update(configuration); } - fn open( + fn open( serial: &Option<&str>, configuration: Self::Configuration, usb_configuration: &usb::Configuration, event_loop: std::sync::Arc, - error_flag: error::Flag, + flag: flag::Flag, ) -> Result where IntoError: From + Clone + Send + 'static, + IntoWarning: From + Clone + Send + 'static, { let (handle, serial) = Self::handle_from_serial(event_loop.context(), serial)?; std::thread::sleep(std::time::Duration::from_millis(150)); @@ -337,14 +357,18 @@ impl device::Usb for Device { .write(&handle)?; let handle = std::sync::Arc::new(handle); - let ring_error_flag = error_flag.clone(); + let error_flag = flag.clone(); + let warning_flag = flag.clone(); Ok(Device { handle: handle.clone(), ring: usb::Ring::new( handle.clone(), usb_configuration, move |usb_error| { - ring_error_flag.store_if_not_set(Self::Error::from(usb_error)); + error_flag.store_error_if_not_set(Self::Error::from(usb_error)); + }, + move |overflow| { + warning_flag.store_warning_if_not_set(overflow); }, event_loop, usb::TransferType::Bulk { @@ -354,14 +378,14 @@ impl device::Usb for Device { )?, configuration_updater: configuration::Updater::new( configuration, - ConfigurationUpdaterContext { handle, error_flag }, + ConfigurationUpdaterContext { handle, flag }, |context, previous_configuration, configuration| { if let Err(error) = update_configuration( &context.handle, Some(previous_configuration), configuration, ) { - context.error_flag.store_if_not_set(error); + context.flag.store_error_if_not_set(error); } context }, @@ -763,12 +787,13 @@ fn update_configuration( Ok(()) } -struct ConfigurationUpdaterContext +struct ConfigurationUpdaterContext where IntoError: From + Clone + Send, + IntoWarning: From + Clone + Send, { handle: std::sync::Arc>, - error_flag: error::Flag, + flag: flag::Flag, } struct RuntimeRegister { diff --git a/drivers/src/devices/prophesee_evk4.rs b/drivers/src/devices/prophesee_evk4.rs index a2b6f55..b0296e0 100644 --- a/drivers/src/devices/prophesee_evk4.rs +++ b/drivers/src/devices/prophesee_evk4.rs @@ -1,10 +1,12 @@ use crate::adapters; use crate::configuration; use crate::device; -use crate::error; +use crate::flag; use crate::properties; use crate::usb; +use device::Usb; + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] pub struct Biases { pub pr: u8, @@ -87,6 +89,23 @@ impl From for Error { } } +pub const PROPERTIES: properties::Camera = Device::PROPERTIES; +pub const DEFAULT_CONFIGURATION: Configuration = Device::PROPERTIES.default_configuration; +pub const DEFAULT_USB_CONFIGURATION: usb::Configuration = Device::DEFAULT_USB_CONFIGURATION; +pub fn open( + serial: &Option<&str>, + configuration: Configuration, + usb_configuration: &usb::Configuration, + event_loop: std::sync::Arc, + flag: flag::Flag, +) -> Result +where + IntoError: From + Clone + Send + 'static, + IntoWarning: From + Clone + Send + 'static, +{ + Device::open(serial, configuration, usb_configuration, event_loop, flag) +} + impl device::Usb for Device { type Adapter = adapters::evt3::Adapter; @@ -132,9 +151,9 @@ impl device::Usb for Device { }; const DEFAULT_USB_CONFIGURATION: usb::Configuration = usb::Configuration { - buffer_size: 1 << 17, - ring_size: 1 << 12, - transfer_queue_size: 1 << 5, + buffer_length: 1 << 17, + ring_length: 1 << 12, + transfer_queue_length: 1 << 5, allow_dma: false, }; @@ -157,15 +176,16 @@ impl device::Usb for Device { self.configuration_updater.update(configuration); } - fn open( + fn open( serial: &Option<&str>, configuration: Self::Configuration, usb_configuration: &usb::Configuration, event_loop: std::sync::Arc, - error_flag: error::Flag, + flag: flag::Flag, ) -> Result where IntoError: From + Clone + Send + 'static, + IntoWarning: From + Clone + Send + 'static, { let (handle, serial) = Self::handle_from_serial(event_loop.context(), serial)?; usb::assert_control_transfer( @@ -688,7 +708,7 @@ impl device::Usb for Device { } .write(&handle)?; loop { - let mut buffer = vec![0u8; Self::DEFAULT_USB_CONFIGURATION.buffer_size]; + let mut buffer = vec![0u8; Self::DEFAULT_USB_CONFIGURATION.buffer_length]; match handle.read_bulk(0x81, &mut buffer, TIMEOUT) { Ok(size) => { if size == 0 { @@ -786,7 +806,8 @@ impl device::Usb for Device { // } let handle = std::sync::Arc::new(handle); - let ring_error_flag = error_flag.clone(); + let error_flag = flag.clone(); + let warning_flag = flag.clone(); let register_mutex = std::sync::Arc::new(std::sync::Mutex::new(())); Ok(Device { handle: handle.clone(), @@ -794,7 +815,10 @@ impl device::Usb for Device { handle.clone(), usb_configuration, move |usb_error| { - ring_error_flag.store_if_not_set(Self::Error::from(usb_error)); + error_flag.store_error_if_not_set(Self::Error::from(usb_error)); + }, + move |overflow| { + warning_flag.store_warning_if_not_set(overflow); }, event_loop, usb::TransferType::Bulk { @@ -806,7 +830,7 @@ impl device::Usb for Device { configuration, ConfigurationUpdaterContext { handle, - error_flag, + flag, register_mutex: register_mutex.clone(), }, |context, previous_configuration, configuration| { @@ -822,7 +846,7 @@ impl device::Usb for Device { ) }; if let Err(error) = result { - context.error_flag.store_if_not_set(error); + context.flag.store_error_if_not_set(error); } context }, @@ -1231,12 +1255,13 @@ fn update_configuration( Ok(()) } -struct ConfigurationUpdaterContext +struct ConfigurationUpdaterContext where IntoError: From + Clone + Send, + IntoWarning: From + Clone + Send, { handle: std::sync::Arc>, - error_flag: error::Flag, + flag: flag::Flag, register_mutex: std::sync::Arc>, } diff --git a/drivers/src/error.rs b/drivers/src/error.rs deleted file mode 100644 index 4c9d0ba..0000000 --- a/drivers/src/error.rs +++ /dev/null @@ -1,35 +0,0 @@ -#[derive(Debug, Clone)] -pub struct Flag(std::sync::Arc>>) -where - IntoError: Clone + Send; - -impl Flag -where - IntoError: Clone + Send, -{ - pub fn new() -> Self { - Self(std::sync::Arc::new(std::sync::Mutex::new(None))) - } - - pub fn store_if_not_set(&self, error: Error) - where - Error: Into, - { - // unwrap: mutex is not poisoned - self.0.lock().unwrap().get_or_insert(error.into()); - } - - pub fn load(&self) -> Option { - // unwrap: mutex is not poisoned - self.0.lock().unwrap().clone() - } -} - -impl Default for Flag -where - IntoError: Clone + Send, -{ - fn default() -> Self { - Self::new() - } -} diff --git a/drivers/src/flag.rs b/drivers/src/flag.rs new file mode 100644 index 0000000..57cac6b --- /dev/null +++ b/drivers/src/flag.rs @@ -0,0 +1,76 @@ +#[derive(Debug, Clone)] +pub struct Inner +where + IntoError: Clone + Send, + IntoWarning: Clone + Send, +{ + pub error: Option, + pub warning: Option, +} + +#[derive(Debug, Clone)] +pub struct Flag( + std::sync::Arc>>, +) +where + IntoError: Clone + Send, + IntoWarning: Clone + Send; + +impl Flag +where + IntoError: Clone + Send, + IntoWarning: Clone + Send, +{ + pub fn new() -> Self { + Self(std::sync::Arc::new(std::sync::Mutex::new(Inner { + error: None, + warning: None, + }))) + } + + pub fn store_error_if_not_set(&self, error: Error) + where + Error: Into, + { + self.0 + .lock() + .expect("mutex is not poisoned") + .error + .get_or_insert(error.into()); + } + + pub fn store_warning_if_not_set(&self, warning: Warning) + where + Warning: Into, + { + self.0 + .lock() + .expect("mutex is not poisoned") + .warning + .get_or_insert(warning.into()); + } + + pub fn load_error(&self) -> Result<(), IntoError> { + match self.0.lock().expect("mutex is not poisoned").error.take() { + Some(error) => Err(error), + None => Ok(()), + } + } + + pub fn load_warning(&self) -> Option { + self.0.lock().expect("mutex is not poisoned").warning.take() + } +} + +impl Default for Flag +where + IntoError: Clone + Send, + IntoWarning: Clone + Send, +{ + fn default() -> Self { + Self(std::sync::Arc::new(std::sync::Mutex::new(Inner { + error: None, + warning: None, + }))) + } +} diff --git a/drivers/src/lib.rs b/drivers/src/lib.rs index 997e4e0..8f8cf36 100644 --- a/drivers/src/lib.rs +++ b/drivers/src/lib.rs @@ -2,22 +2,38 @@ pub mod adapters; pub mod configuration; pub mod device; pub mod devices; -pub mod error; +pub mod flag; pub mod properties; pub mod usb; -pub use crate::adapters::Adapter; -pub use crate::devices::list_devices; -pub use crate::devices::open; -pub use crate::devices::Configuration; -pub use crate::devices::Device; -pub use crate::devices::Error; -pub use crate::devices::Properties; -pub use crate::devices::Type; -pub use crate::usb::Configuration as UsbConfiguration; +pub use adapters::Adapter; +pub use device::Usb as UsbDevice; +pub use devices::list_devices; +pub use devices::open; +pub use devices::Configuration; +pub use devices::Device; +pub use devices::Error; +pub use devices::Properties; +pub use devices::Type; +pub use flag::Flag; +pub use usb::Configuration as UsbConfiguration; +pub use usb::Overflow as UsbOverflow; + +pub use devices::prophesee_evk3_hd; +pub use devices::prophesee_evk4; pub use bincode; pub use libc; pub use libusb1_sys; pub use neuromorphic_types as types; pub use rusb; + +pub fn event_loop_and_flag( +) -> Result<(Flag, std::sync::Arc), usb::Error> { + let flag = Flag::new(); + let event_loop = std::sync::Arc::new(usb::EventLoop::new( + std::time::Duration::from_millis(100), + flag.clone(), + )?); + Ok((flag, event_loop)) +} diff --git a/drivers/src/usb.rs b/drivers/src/usb.rs index 57471dd..3da408d 100644 --- a/drivers/src/usb.rs +++ b/drivers/src/usb.rs @@ -1,11 +1,11 @@ -use crate::error; +use crate::flag; use rusb::UsbContext; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct Configuration { - pub buffer_size: usize, - pub ring_size: usize, - pub transfer_queue_size: usize, + pub buffer_length: usize, + pub ring_length: usize, + pub transfer_queue_length: usize, pub allow_dma: bool, } @@ -39,7 +39,7 @@ pub enum Error { Busy, } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] pub enum Speed { Unknown, Low, @@ -124,6 +124,7 @@ impl BufferData { struct Buffer { instant: std::time::Instant, + first_after_overflow: bool, data: BufferData, length: usize, capacity: usize, @@ -136,13 +137,17 @@ pub struct EventLoop { thread: Option>, } +#[derive(Debug, Clone, Copy)] +pub struct Overflow(()); + impl EventLoop { - pub fn new( + pub fn new( timeout: std::time::Duration, - error_flag: error::Flag, + flag: flag::Flag, ) -> Result where IntoError: From + Clone + Send + 'static, + IntoWarning: From + Clone + Send + 'static, { let context = rusb::Context::new()?; let running = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true)); @@ -153,7 +158,7 @@ impl EventLoop { thread: Some(std::thread::spawn(move || { while thread_running.load(std::sync::atomic::Ordering::Acquire) { if let Err(handle_events_error) = thread_context.handle_events(Some(timeout)) { - error_flag.store_if_not_set(Error::from(handle_events_error)); + flag.store_error_if_not_set(Error::from(handle_events_error)); } } })), @@ -183,15 +188,41 @@ enum TransferStatus { Deallocated, } +#[derive(Clone)] +pub struct WriteRange { + pub start: usize, + pub end: usize, + pub ring_length: usize, +} + +impl WriteRange { + fn increment_start(&mut self) { + self.start = (self.start + 1) % self.ring_length; + } + + fn increment_end(&mut self) { + self.end = (self.end + 1) % self.ring_length; + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Clutch { + Disengaged, + Engaged, +} + struct RingContext { read: usize, - write_range: (usize, usize), + write_range: WriteRange, transfer_statuses: Vec, buffers: Vec, + freewheel_buffers: Vec, + clutch: Clutch, } struct SharedRingContext { on_error: Box, + on_overflow: Box, shared: std::sync::Mutex, shared_condvar: std::sync::Condvar, } @@ -249,9 +280,16 @@ pub struct TransferProperties { pub timeout: std::time::Duration, } +enum TransferClutch { + Disengaged, + DisengagedFirst, + Engaged, +} + struct TransferContext { ring: std::sync::Arc, transfer_index: usize, + clutch: TransferClutch, } #[no_mangle] @@ -276,39 +314,58 @@ extern "system" fn usb_transfer_callback(transfer_pointer: *mut libusb1_sys::lib TransferStatus::Active => match transfer.status { libusb1_sys::constants::LIBUSB_TRANSFER_COMPLETED | libusb1_sys::constants::LIBUSB_TRANSFER_TIMED_OUT => { - if shared.write_range.1 == shared.read { - error = Some(Error::Overflow); - shared.transfer_statuses[context.transfer_index] = - TransferStatus::Complete; - } else { - let active_buffer = shared.write_range.0; + if !matches!(context.clutch, TransferClutch::Engaged) { + let active_buffer = shared.write_range.start; shared.buffers[active_buffer].instant = now; + shared.buffers[active_buffer].first_after_overflow = + matches!(context.clutch, TransferClutch::DisengagedFirst); shared.buffers[active_buffer].length = transfer.actual_length as usize; - transfer.buffer = shared.buffers[shared.write_range.1].data.as_ptr(); - transfer.length = shared.buffers[shared.write_range.1].capacity as i32; - resubmit = true; - shared.write_range.0 = - (shared.write_range.0 + 1) % shared.buffers.len(); - shared.write_range.1 = - (shared.write_range.1 + 1) % shared.buffers.len(); + shared.write_range.increment_start(); context.ring.shared_condvar.notify_one(); } + if shared.write_range.end == shared.read { + if matches!(shared.clutch, Clutch::Disengaged) { + shared.clutch = Clutch::Engaged; + (context.ring.on_overflow)(Overflow(())); + } + context.clutch = TransferClutch::Engaged; + transfer.buffer = shared.freewheel_buffers[context.transfer_index] + .data + .as_ptr(); + transfer.length = + shared.freewheel_buffers[context.transfer_index].capacity as i32; + } else { + match shared.clutch { + Clutch::Disengaged => { + context.clutch = TransferClutch::Disengaged; + } + Clutch::Engaged => { + shared.clutch = Clutch::Disengaged; + context.clutch = TransferClutch::DisengagedFirst; + } + } + transfer.buffer = shared.buffers[shared.write_range.end].data.as_ptr(); + transfer.length = + shared.buffers[shared.write_range.end].capacity as i32; + shared.write_range.increment_end(); + } + resubmit = true; } status @ (libusb1_sys::constants::LIBUSB_TRANSFER_ERROR | libusb1_sys::constants::LIBUSB_TRANSFER_CANCELLED | libusb1_sys::constants::LIBUSB_TRANSFER_STALL | libusb1_sys::constants::LIBUSB_TRANSFER_NO_DEVICE | libusb1_sys::constants::LIBUSB_TRANSFER_OVERFLOW) => { - if shared.write_range.1 != shared.read { - let active_buffer = shared.write_range.0; + if !matches!(context.clutch, TransferClutch::Engaged) { + let active_buffer = shared.write_range.start; shared.buffers[active_buffer].instant = now; shared.buffers[active_buffer].length = transfer.actual_length as usize; - shared.write_range.0 = - (shared.write_range.0 + 1) % shared.buffers.len(); - shared.write_range.1 = - (shared.write_range.1 + 1) % shared.buffers.len(); + shared.write_range.increment_start(); context.ring.shared_condvar.notify_one(); } + // set clutch to report a packet drop + shared.clutch = Clutch::Engaged; + context.clutch = TransferClutch::Disengaged; shared.transfer_statuses[context.transfer_index] = TransferStatus::Complete; error = Some( match status { @@ -339,16 +396,16 @@ extern "system" fn usb_transfer_callback(transfer_pointer: *mut libusb1_sys::lib | libusb1_sys::constants::LIBUSB_TRANSFER_CANCELLED | libusb1_sys::constants::LIBUSB_TRANSFER_STALL | libusb1_sys::constants::LIBUSB_TRANSFER_NO_DEVICE => { - if shared.write_range.1 != shared.read { - let active_buffer = shared.write_range.0; + if !matches!(context.clutch, TransferClutch::Engaged) { + let active_buffer = shared.write_range.start; shared.buffers[active_buffer].instant = now; shared.buffers[active_buffer].length = transfer.actual_length as usize; - shared.write_range.0 = - (shared.write_range.0 + 1) % shared.buffers.len(); - shared.write_range.1 = - (shared.write_range.1 + 1) % shared.buffers.len(); + shared.write_range.increment_start(); context.ring.shared_condvar.notify_one(); } + // set clutch to report a packet drop + shared.clutch = Clutch::Engaged; + context.clutch = TransferClutch::Disengaged; shared.transfer_statuses[context.transfer_index] = TransferStatus::Complete; } unknown_transfer_status => { @@ -409,50 +466,60 @@ extern "system" fn usb_transfer_callback(transfer_pointer: *mut libusb1_sys::lib } impl Ring { - pub fn new( + pub fn new( handle: std::sync::Arc>, configuration: &Configuration, on_error: OnError, + on_overflow: OnOverflow, event_loop: std::sync::Arc, transfer_type: TransferType, ) -> Result where OnError: Fn(Error) + Send + Sync + 'static, + OnOverflow: Fn(Overflow) + Send + Sync + 'static, { assert!( handle.context() == event_loop.context(), "handle and event_loop must have the same context" ); - if configuration.ring_size <= configuration.transfer_queue_size { + if configuration.ring_length <= configuration.transfer_queue_length { return Err(Error::ConfigurationSizes); } let mut buffers = Vec::new(); - buffers.reserve_exact(configuration.ring_size); - for _ in 0..configuration.ring_size { + buffers.reserve_exact(configuration.ring_length); + let mut freewheel_buffers = Vec::new(); + freewheel_buffers.reserve_exact(configuration.transfer_queue_length); + for index in 0..configuration.ring_length + configuration.transfer_queue_length { let dma_buffer = if configuration.allow_dma { // unsafe: libusb wrapper unsafe { libusb_dev_mem_alloc( handle.as_raw(), - configuration.buffer_size as libc::ssize_t, + configuration.buffer_length as libc::ssize_t, ) } } else { std::ptr::null_mut() }; if dma_buffer.is_null() { - buffers.push(Buffer { + (if index < configuration.ring_length { + &mut buffers + } else { + &mut freewheel_buffers + }) + .push(Buffer { instant: std::time::Instant::now(), + first_after_overflow: false, data: BufferData( std::ptr::NonNull::new( // unsafe: alloc wrapper - // std::alloc::Layout::from_size_align_unchecked + // std::alloc::Layout::from_length_align_unchecked // - align must not be zero // - align must be a power of two // - size, when rounded up to the nearest multiple of align, must not overflow isize unsafe { std::alloc::alloc(std::alloc::Layout::from_size_align_unchecked( - configuration.buffer_size, + configuration.buffer_length, 1, )) }, @@ -460,43 +527,56 @@ impl Ring { .ok_or(rusb::Error::NoMem)?, ), length: 0, - capacity: configuration.buffer_size, + capacity: configuration.buffer_length, dma: false, }); } else { - buffers.push(Buffer { + (if index < configuration.ring_length { + &mut buffers + } else { + &mut freewheel_buffers + }) + .push(Buffer { instant: std::time::Instant::now(), + first_after_overflow: false, // unsafe: dma_buffer is not null data: BufferData(unsafe { std::ptr::NonNull::new_unchecked(dma_buffer) }), length: 0, - capacity: configuration.buffer_size, + capacity: configuration.buffer_length, dma: true, }); } } let mut transfer_statuses = Vec::new(); - transfer_statuses.reserve_exact(configuration.transfer_queue_size); - for _ in 0..configuration.transfer_queue_size { + transfer_statuses.reserve_exact(configuration.transfer_queue_length); + for _ in 0..configuration.transfer_queue_length { transfer_statuses.push(TransferStatus::Active); } let context = std::sync::Arc::new(SharedRingContext { on_error: Box::new(on_error), + on_overflow: Box::new(on_overflow), shared: std::sync::Mutex::new(RingContext { read: buffers.len() - 1, - write_range: (0, configuration.transfer_queue_size), + write_range: WriteRange { + start: 0, + end: configuration.transfer_queue_length, + ring_length: configuration.ring_length, + }, transfer_statuses, buffers, + freewheel_buffers, + clutch: Clutch::Disengaged, }), shared_condvar: std::sync::Condvar::new(), }); let mut transfers: Vec = Vec::new(); - transfers.reserve_exact(configuration.transfer_queue_size); + transfers.reserve_exact(configuration.transfer_queue_length); { let shared = context .shared .lock() .expect("ring context's lock is not poisoned"); - for index in 0..configuration.transfer_queue_size { + for index in 0..configuration.transfer_queue_length { // unsafe: libusb1_sys wrapper let mut transfer = match std::ptr::NonNull::new(unsafe { libusb1_sys::libusb_alloc_transfer(0) @@ -519,6 +599,7 @@ impl Ring { let transfer_context = Box::new(TransferContext { ring: context.clone(), transfer_index: index, + clutch: TransferClutch::Disengaged, }); let transfer_context_pointer = Box::into_raw(transfer_context); match transfer_type { @@ -661,16 +742,23 @@ impl Ring { pub struct BufferView<'a> { pub instant: std::time::Instant, + pub first_after_overflow: bool, pub slice: &'a [u8], pub read: usize, - pub write_range: (usize, usize), - pub ring_length: usize, + pub write_range: WriteRange, + pub clutch: Clutch, active: std::sync::Arc, } impl BufferView<'_> { pub fn backlog(&self) -> usize { - (self.write_range.0 + self.ring_length - 1 - self.read) % self.ring_length + let result = (self.write_range.start + self.write_range.ring_length - 1 - self.read) + % self.write_range.ring_length; + if matches!(self.clutch, Clutch::Engaged) && result == 0 { + self.write_range.ring_length + } else { + result + } } pub fn delay(&self) -> std::time::Duration { @@ -692,7 +780,7 @@ impl Ring { .shared .lock() .expect("ring context's lock is not poisoned"); - (shared.write_range.0 + shared.buffers.len() - 1 - shared.read) % shared.buffers.len() + (shared.write_range.start + shared.buffers.len() - 1 - shared.read) % shared.buffers.len() } pub fn next_with_timeout(&self, duration: &std::time::Duration) -> Option { @@ -702,7 +790,7 @@ impl Ring { { panic!("the buffer returned by a previous call of next_with_timeout must be dropped before calling next_with_timeout again"); } - let (instant, slice, read, write_range, ring_length) = { + let (instant, first_after_overflow, slice, read, write_range, clutch) = { let start = std::time::Instant::now(); let mut shared = self .context @@ -711,7 +799,7 @@ impl Ring { .expect("ring context's lock is not poisoned"); loop { shared.read = (shared.read + 1) % shared.buffers.len(); - while (shared.write_range.1 + shared.buffers.len() - 1 - shared.read) + while (shared.write_range.end + shared.buffers.len() - 1 - shared.read) % shared.buffers.len() < shared.transfer_statuses.len() { @@ -719,7 +807,8 @@ impl Ring { if ellapsed >= *duration { self.active_buffer_view .store(false, std::sync::atomic::Ordering::Release); - shared.read = (shared.read + shared.buffers.len() - 1) % shared.buffers.len(); + shared.read = + (shared.read + shared.buffers.len() - 1) % shared.buffers.len(); return None; } shared = self @@ -735,6 +824,7 @@ impl Ring { } ( shared.buffers[shared.read].instant, + shared.buffers[shared.read].first_after_overflow, // unsafe: data validity guaranteed by read / write_range in shared unsafe { std::slice::from_raw_parts( @@ -743,16 +833,17 @@ impl Ring { ) }, shared.read, - shared.write_range, - shared.buffers.len(), + shared.write_range.clone(), + shared.clutch, ) }; Some(BufferView { instant, + first_after_overflow, slice, read, write_range, - ring_length, + clutch, active: self.active_buffer_view.clone(), }) } diff --git a/drivers/tests/evk4.rs b/drivers/tests/evk4.rs index 25b4087..54b3bc4 100644 --- a/drivers/tests/evk4.rs +++ b/drivers/tests/evk4.rs @@ -1,20 +1,14 @@ -use neuromorphic_drivers::device::Usb; +use neuromorphic_drivers::UsbDevice; #[test] -fn read() -> Result<(), neuromorphic_drivers::devices::prophesee_evk4::Error> { - let error_flag: neuromorphic_drivers::error::Flag< - neuromorphic_drivers::devices::prophesee_evk4::Error, - > = neuromorphic_drivers::error::Flag::new(); - let event_loop = std::sync::Arc::new(neuromorphic_drivers::usb::EventLoop::new( - std::time::Duration::from_millis(100), - error_flag.clone(), - )?); - let device = neuromorphic_drivers::devices::prophesee_evk4::Device::open( +fn read() -> Result<(), neuromorphic_drivers::Error> { + let (flag, event_loop) = neuromorphic_drivers::event_loop_and_flag()?; + let device = neuromorphic_drivers::prophesee_evk4::open( &None, - neuromorphic_drivers::devices::prophesee_evk4::Device::PROPERTIES.default_configuration, - &neuromorphic_drivers::devices::prophesee_evk4::Device::DEFAULT_USB_CONFIGURATION, + neuromorphic_drivers::prophesee_evk4::DEFAULT_CONFIGURATION, + &neuromorphic_drivers::prophesee_evk4::DEFAULT_USB_CONFIGURATION, event_loop, - error_flag.clone(), + flag.clone(), )?; let start = std::time::Instant::now(); let mut previous = std::time::Instant::now(); @@ -31,6 +25,10 @@ fn read() -> Result<(), neuromorphic_drivers::devices::prophesee_evk4::Error> { ); previous = now; } + flag.load_error()?; + if flag.load_warning().is_some() { + eprintln!("USB circular buffer overflow"); + } } Ok(()) } diff --git a/python/Cargo.toml b/python/Cargo.toml index 0ee2756..fd5abcd 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -8,17 +8,17 @@ name = "neuromorphic_drivers" crate-type = ["cdylib"] [dependencies] -neuromorphic-drivers = "0.12.0" -numpy = "0.19" +neuromorphic-drivers = {path = "../drivers"} +numpy = "0.21.0" paste = "1.0" -pyo3 = {version = "0.19", features = ["extension-module"]} +pyo3 = {version = "0.21", features = ["extension-module"]} [build-dependencies] cc = "1.0" -neuromorphic-drivers = "0.12.0" +neuromorphic-drivers = {path = "../drivers"} paste = "1.0" reflect = {path = "../reflect"} serde = {version = "1.0", features = ["derive"]} serde_json = "1.0" serde-generate = "0.25.1" -toml = {version = "0.7", features = ["parse"]} +toml = {version = "0.8", features = ["parse"]} diff --git a/python/build.rs b/python/build.rs index 7afbe64..81a9d02 100644 --- a/python/build.rs +++ b/python/build.rs @@ -498,13 +498,13 @@ macro_rules! generate { new_root_name: Some("Properties".into()), }, ); - for (class_name, iter_data_right) in [ - ("Device", "dict[str, numpy.ndarray[typing.Any, numpy.dtype[numpy.void]]]"), - ("DeviceRaw", "bytes"), + for (class_name, iter_data_left_prefix, iter_data_right) in [ + ("Device", "status.", "dict[str, numpy.ndarray[typing.Any, numpy.dtype[numpy.void]]]"), + ("DeviceRaw", "status.Raw", "bytes"), ] { for (class_suffix, iter_data_left, iter_data_right_prefix, iter_data_right_suffix) in [ - ("", "status.StatusNonOptional", "", ""), - ("Optional", "status.Status", "typing.Optional[", "]"), + ("", "StatusNonOptional", "", ""), + ("Optional", "Status", "typing.Optional[", "]"), ] { writeln!( writer, @@ -512,8 +512,7 @@ macro_rules! generate { "\n", "\n", "class {}{}(typing.Protocol):\n", - " def __enter__(self) -> \"{}{}\":\n", - " ...\n", + " def __enter__(self) -> \"{}{}\": ...\n", "\n", " def __exit__(\n", " self,\n", @@ -523,38 +522,29 @@ macro_rules! generate { " ) -> bool:\n", " ...\n", "\n", - " def __iter__(self) -> \"{}{}\":\n", - " ...\n", + " def __iter__(self) -> \"{}{}\": ...\n", "\n", - " def __next__(self) -> tuple[{}, {}{}{}]:\n", - " ...\n", + " def __next__(self) -> tuple[{}{}, {}{}{}]: ...\n", "\n", - " def backlog(self) -> int:\n", - " ...\n", + " def backlog(self) -> int: ...\n", "\n", - " def clear_backlog(self, until: int):\n", - " ...\n", + " def clear_backlog(self, until: int): ...\n", "\n", - " def name(self) -> typing.Literal[enums.Name.{}]:\n", - " ...\n", + " def overflow(self) -> bool: ...\n", "\n", - " def properties(self) -> Properties:\n", - " ...\n", + " def name(self) -> typing.Literal[enums.Name.{}]: ...\n", "\n", - " def serial(self) -> str:\n", - " ...\n", + " def properties(self) -> Properties: ...\n", "\n", - " def chip_firmware_configuration(self) -> Configuration:\n", - " ...\n", + " def serial(self) -> str: ...\n", "\n", - " def speed(self) -> enums.Speed:\n", - " ...\n", + " def chip_firmware_configuration(self) -> Configuration: ...\n", "\n", - " def temperature_celsius(self) -> float:\n", - " ...\n", + " def speed(self) -> enums.Speed: ...\n", + "\n", + " def temperature_celsius(self) -> float: ...\n", "\n", - " def update_configuration(self, configuration: Configuration):\n", - " ...", + " def update_configuration(self, configuration: Configuration): ...", ), class_name, class_suffix, @@ -562,6 +552,7 @@ macro_rules! generate { class_suffix, class_name, class_suffix, + iter_data_left_prefix, iter_data_left, iter_data_right_prefix, iter_data_right, @@ -572,8 +563,8 @@ macro_rules! generate { writeln!( writer, concat!( - " def illuminance(self) -> int:\n", - " ...", + "\n", + " def illuminance(self) -> int: ...", ) ).unwrap(); } @@ -687,13 +678,13 @@ macro_rules! generate { "from .unions import *\n", ), ).unwrap(); - for (class_name, iter_data_right) in [ - ("GenericDevice", "dict[str, numpy.ndarray[typing.Any, numpy.dtype[numpy.void]]]"), - ("GenericDeviceRaw", "bytes"), + for (class_name, iter_data_left_prefix, iter_data_right) in [ + ("GenericDevice", "status.", "dict[str, numpy.ndarray[typing.Any, numpy.dtype[numpy.void]]]"), + ("GenericDeviceRaw", "status.Raw", "bytes"), ] { for (class_suffix, iter_data_left, iter_data_right_prefix, iter_data_right_suffix) in [ - ("", "status.StatusNonOptional", "", ""), - ("Optional", "status.Status", "typing.Optional[", "]"), + ("", "StatusNonOptional", "", ""), + ("Optional", "Status", "typing.Optional[", "]"), ] { writeln!( writer, @@ -701,8 +692,7 @@ macro_rules! generate { "\n", "\n", "class {}{}(typing.Protocol):\n", - " def __enter__(self) -> \"{}{}\":\n", - " ...\n", + " def __enter__(self) -> \"{}{}\": ...\n", "\n", " def __exit__(\n", " self,\n", @@ -712,38 +702,29 @@ macro_rules! generate { " ) -> bool:\n", " ...\n", "\n", - " def __iter__(self) -> \"{}{}\":\n", - " ...\n", + " def __iter__(self) -> \"{}{}\": ...\n", "\n", - " def __next__(self) -> tuple[{}, {}{}{}]:\n", - " ...\n", + " def __next__(self) -> tuple[{}{}, {}{}{}]: ...\n", "\n", - " def backlog(self) -> int:\n", - " ...\n", + " def backlog(self) -> int: ...\n", "\n", - " def clear_backlog(self, until: int):\n", - " ...\n", + " def clear_backlog(self, until: int): ...\n", "\n", - " def name(self) -> Name:\n", - " ...\n", + " def overflow(self) -> bool: ...\n", "\n", - " def properties(self) -> Properties:\n", - " ...\n", + " def name(self) -> Name: ...\n", "\n", - " def serial(self) -> str:\n", - " ...\n", + " def properties(self) -> Properties: ...\n", "\n", - " def chip_firmware_configuration(self) -> Configuration:\n", - " ...\n", + " def serial(self) -> str: ...\n", "\n", - " def speed(self) -> Speed:\n", - " ...\n", + " def chip_firmware_configuration(self) -> Configuration: ...\n", "\n", - " def temperature_celsius(self) -> float:\n", - " ...\n", + " def speed(self) -> Speed: ...\n", + "\n", + " def temperature_celsius(self) -> float: ...\n", "\n", - " def update_configuration(self, configuration: Configuration):\n", - " ...", + " def update_configuration(self, configuration: Configuration): ...\n", ), class_name, class_suffix, @@ -751,6 +732,7 @@ macro_rules! generate { class_suffix, class_name, class_suffix, + iter_data_left_prefix, iter_data_left, iter_data_right_prefix, iter_data_right, @@ -843,9 +825,8 @@ macro_rules! generate { " device.Device,\n", " raw,\n", " iterator_maximum_raw_packets,\n", - " None\n", - " if configuration is None\n", - " else (configuration.type(), configuration.serialize()),\n", + " None if configuration is None else configuration.type(),\n", + " None if configuration is None else configuration.serialize(),\n", " serial,\n", " None if usb_configuration is None else usb_configuration.serialize(),\n", " iterator_timeout,\n", diff --git a/python/examples/any_display.py b/python/examples/any_display.py index 2566598..baf8a9c 100644 --- a/python/examples/any_display.py +++ b/python/examples/any_display.py @@ -1,6 +1,3 @@ -import threading -import typing - import neuromorphic_drivers as nd import numpy as np import vispy.app @@ -84,7 +81,7 @@ def __init__( self.program["u_projection"] = self.projection self.program["u_texture"] = self.texture self.program["u_t"] = 0 - self.program["u_tau"] = 200000 + self.program["u_tau"] = 100000 self.coordinates = np.zeros( 4, dtype=[("a_position", np.float32, 2), ("a_texcoord", np.float32, 2)], @@ -147,15 +144,12 @@ def on_draw(self, event): ) elif status.ring is not None and status.ring.current_t is not None: self.program["u_t"] = np.float32(status.ring.current_t) - if status.ring is not None and status.ring.backlog() > 1000: - device.clear_backlog(until=0) self.texture.set_data(self.ts_and_ons) self.program.draw("triangle_strip") if __name__ == "__main__": nd.print_device_list() - camera_thread: typing.Optional[threading.Thread] = None with nd.open(iterator_timeout=FRAME_DURATION) as device: print(device.serial(), device.properties()) canvas = Canvas( diff --git a/python/examples/any_monitor_backlog.py b/python/examples/any_monitor_backlog.py new file mode 100644 index 0000000..afaf16b --- /dev/null +++ b/python/examples/any_monitor_backlog.py @@ -0,0 +1,13 @@ +import time + +import neuromorphic_drivers as nd + +nd.print_device_list() + +with nd.open() as device: + print(device.serial(), device.properties()) + for status, packet in device: + if "dvs_events_overflow_indices" in packet: + print(status, packet["dvs_events_overflow_indices"]) + else: + print(status) diff --git a/python/examples/any_read_many.py b/python/examples/any_read_many.py index 4eb85c7..a09884c 100644 --- a/python/examples/any_read_many.py +++ b/python/examples/any_read_many.py @@ -13,7 +13,6 @@ while True: index = np.argmax(backlogs) status, packet = devices[index].__next__() - backlog = status.ring.backlog() - print(f"{index}: {round(status.delay() * 1e6)} µs, backlog: {backlog}") + print(f"{index}: {round(status.delay() * 1e6)} µs, backlog: {status.ring.backlog}") backlogs[:] += 1 - backlogs[index] = backlog + backlogs[index] = status.ring.backlog diff --git a/python/examples/any_read_one.py b/python/examples/any_read_one.py index 02d3482..073bf4e 100644 --- a/python/examples/any_read_one.py +++ b/python/examples/any_read_one.py @@ -5,6 +5,6 @@ with nd.open() as device: print(device.serial(), device.properties()) for status, packet in device: - print(f"{round(status.delay() * 1e6)} µs, backlog: {status.ring.backlog()}") - if status.ring.backlog() > 1000: + print(f"{round(status.delay() * 1e6)} µs, backlog: {status.ring.backlog}") + if status.ring.backlog > 1000: device.clear_backlog(until=0) diff --git a/python/examples/any_record_raw.py b/python/examples/any_record_raw.py index 9a5926c..bdaefc8 100644 --- a/python/examples/any_record_raw.py +++ b/python/examples/any_record_raw.py @@ -17,5 +17,5 @@ output.flush() total += len(packet) print( - f"{total / 1e6:.1f} MB (backlog: {status.ring.backlog()}, raw: {status.ring.raw_packets()})" + f"{total / 1e6:.1f} MB (backlog: {status.ring.backlog}, raw: {status.ring.raw_packets})" ) diff --git a/python/examples/evk4_dual_display.py b/python/examples/evk4_dual_display.py index 12446fe..769e99e 100644 --- a/python/examples/evk4_dual_display.py +++ b/python/examples/evk4_dual_display.py @@ -180,9 +180,8 @@ def on_draw(self, event): self.backlogs[index] = 0 else: assert packet is not None - backlog = status.ring.backlog() self.backlogs[:] += 1 - self.backlogs[index] = backlog + self.backlogs[index] = status.ring.backlog if "dvs_events" in packet: assert status.ring is not None and status.ring.current_t is not None self.encoders[index].write(packet["dvs_events"]) diff --git a/python/examples/evk4_external_synchronization.py b/python/examples/evk4_external_synchronization.py index 131d9ff..2a4a869 100644 --- a/python/examples/evk4_external_synchronization.py +++ b/python/examples/evk4_external_synchronization.py @@ -57,16 +57,17 @@ backlogs[:] += 1 backlogs[index] = 0 else: - backlog = status.ring.backlog() backlogs[:] += 1 - backlogs[index] = backlog + backlogs[index] = status.ring.backlog delay = status.delay() if delay is not None: if packet is not None: print( - f"{index}: {round(delay * 1e6)} µs, backlog: {backlog}, bytes: {len(packet)}" + f"{index}: {round(delay * 1e6)} µs, backlog: {status.ring.backlog}, bytes: {len(packet)}" ) outputs[index].write(packet) outputs[index].flush() else: - print(f"{index}: {round(delay * 1e6)} µs, backlog: {backlog}") + print( + f"{index}: {round(delay * 1e6)} µs, backlog: {status.ring.backlog}" + ) diff --git a/python/examples/evk4_temperature_illuminance.py b/python/examples/evk4_temperature_illuminance.py index da735fb..7b2e44d 100644 --- a/python/examples/evk4_temperature_illuminance.py +++ b/python/examples/evk4_temperature_illuminance.py @@ -9,9 +9,5 @@ next = time.monotonic() + 1.0 for status, packet in device: if time.monotonic() >= next: - try: - print(f"{device.temperature_celsius()}ºC") - print(f"{device.illuminance()}") - except: - pass + print(f"{device.temperature_celsius()}ºC") next += 1.0 diff --git a/python/python/neuromorphic_drivers/device.py b/python/python/neuromorphic_drivers/device.py index 5fcd282..27b6f73 100644 --- a/python/python/neuromorphic_drivers/device.py +++ b/python/python/neuromorphic_drivers/device.py @@ -27,15 +27,33 @@ def __iter__(self): def __next__(self): system_time, ring_status, packet = super().__next__() + if ring_status[5] is None: + return ( + status.RawStatus( + system_time=system_time, + ring=( + None + if ring_status is None + else status.RawRingStatus(*ring_status[0:5]) + ), + ), + packet, + ) return ( status.Status( system_time=system_time, - ring=None if ring_status is None else status.RingStatus(*ring_status), + ring=( + None + if ring_status is None + else status.RingStatus(*(ring_status[0:4] + ring_status[5:6])) + ), ), packet, ) def update_configuration(self, configuration: unions.Configuration) -> None: return ExtensionDevice.update_configuration( - self, (configuration.type(), configuration.serialize()) + self, + configuration.type(), + configuration.serialize(), ) diff --git a/python/python/neuromorphic_drivers/generated/devices/prophesee_evk3_hd.py b/python/python/neuromorphic_drivers/generated/devices/prophesee_evk3_hd.py index 844af72..fecba47 100644 --- a/python/python/neuromorphic_drivers/generated/devices/prophesee_evk3_hd.py +++ b/python/python/neuromorphic_drivers/generated/devices/prophesee_evk3_hd.py @@ -90,9 +90,9 @@ def type() -> str: @dataclasses.dataclass class UsbConfiguration: - buffer_size: serde.type.uint64 = 131072 - ring_size: serde.type.uint64 = 4096 - transfer_queue_size: serde.type.uint64 = 32 + buffer_length: serde.type.uint64 = 131072 + ring_length: serde.type.uint64 = 4096 + transfer_queue_length: serde.type.uint64 = 32 allow_dma: bool = False def serialize(self) -> bytes: @@ -106,8 +106,7 @@ class Properties: class Device(typing.Protocol): - def __enter__(self) -> "Device": - ... + def __enter__(self) -> "Device": ... def __exit__( self, @@ -117,43 +116,33 @@ def __exit__( ) -> bool: ... - def __iter__(self) -> "Device": - ... + def __iter__(self) -> "Device": ... - def __next__(self) -> tuple[status.StatusNonOptional, dict[str, numpy.ndarray[typing.Any, numpy.dtype[numpy.void]]]]: - ... + def __next__(self) -> tuple[status.StatusNonOptional, dict[str, numpy.ndarray[typing.Any, numpy.dtype[numpy.void]]]]: ... - def backlog(self) -> int: - ... + def backlog(self) -> int: ... - def clear_backlog(self, until: int): - ... + def clear_backlog(self, until: int): ... - def name(self) -> typing.Literal[enums.Name.PROPHESEE_EVK3_HD]: - ... + def overflow(self) -> bool: ... - def properties(self) -> Properties: - ... + def name(self) -> typing.Literal[enums.Name.PROPHESEE_EVK3_HD]: ... - def serial(self) -> str: - ... + def properties(self) -> Properties: ... - def chip_firmware_configuration(self) -> Configuration: - ... + def serial(self) -> str: ... - def speed(self) -> enums.Speed: - ... + def chip_firmware_configuration(self) -> Configuration: ... - def temperature_celsius(self) -> float: - ... + def speed(self) -> enums.Speed: ... - def update_configuration(self, configuration: Configuration): - ... + def temperature_celsius(self) -> float: ... + + def update_configuration(self, configuration: Configuration): ... class DeviceOptional(typing.Protocol): - def __enter__(self) -> "DeviceOptional": - ... + def __enter__(self) -> "DeviceOptional": ... def __exit__( self, @@ -163,43 +152,33 @@ def __exit__( ) -> bool: ... - def __iter__(self) -> "DeviceOptional": - ... + def __iter__(self) -> "DeviceOptional": ... - def __next__(self) -> tuple[status.Status, typing.Optional[dict[str, numpy.ndarray[typing.Any, numpy.dtype[numpy.void]]]]]: - ... + def __next__(self) -> tuple[status.Status, typing.Optional[dict[str, numpy.ndarray[typing.Any, numpy.dtype[numpy.void]]]]]: ... - def backlog(self) -> int: - ... + def backlog(self) -> int: ... - def clear_backlog(self, until: int): - ... + def clear_backlog(self, until: int): ... - def name(self) -> typing.Literal[enums.Name.PROPHESEE_EVK3_HD]: - ... + def overflow(self) -> bool: ... - def properties(self) -> Properties: - ... + def name(self) -> typing.Literal[enums.Name.PROPHESEE_EVK3_HD]: ... - def serial(self) -> str: - ... + def properties(self) -> Properties: ... - def chip_firmware_configuration(self) -> Configuration: - ... + def serial(self) -> str: ... - def speed(self) -> enums.Speed: - ... + def chip_firmware_configuration(self) -> Configuration: ... - def temperature_celsius(self) -> float: - ... + def speed(self) -> enums.Speed: ... - def update_configuration(self, configuration: Configuration): - ... + def temperature_celsius(self) -> float: ... + + def update_configuration(self, configuration: Configuration): ... class DeviceRaw(typing.Protocol): - def __enter__(self) -> "DeviceRaw": - ... + def __enter__(self) -> "DeviceRaw": ... def __exit__( self, @@ -209,43 +188,33 @@ def __exit__( ) -> bool: ... - def __iter__(self) -> "DeviceRaw": - ... + def __iter__(self) -> "DeviceRaw": ... - def __next__(self) -> tuple[status.StatusNonOptional, bytes]: - ... + def __next__(self) -> tuple[status.RawStatusNonOptional, bytes]: ... - def backlog(self) -> int: - ... + def backlog(self) -> int: ... - def clear_backlog(self, until: int): - ... + def clear_backlog(self, until: int): ... - def name(self) -> typing.Literal[enums.Name.PROPHESEE_EVK3_HD]: - ... + def overflow(self) -> bool: ... - def properties(self) -> Properties: - ... + def name(self) -> typing.Literal[enums.Name.PROPHESEE_EVK3_HD]: ... - def serial(self) -> str: - ... + def properties(self) -> Properties: ... - def chip_firmware_configuration(self) -> Configuration: - ... + def serial(self) -> str: ... - def speed(self) -> enums.Speed: - ... + def chip_firmware_configuration(self) -> Configuration: ... - def temperature_celsius(self) -> float: - ... + def speed(self) -> enums.Speed: ... - def update_configuration(self, configuration: Configuration): - ... + def temperature_celsius(self) -> float: ... + + def update_configuration(self, configuration: Configuration): ... class DeviceRawOptional(typing.Protocol): - def __enter__(self) -> "DeviceRawOptional": - ... + def __enter__(self) -> "DeviceRawOptional": ... def __exit__( self, @@ -255,35 +224,26 @@ def __exit__( ) -> bool: ... - def __iter__(self) -> "DeviceRawOptional": - ... + def __iter__(self) -> "DeviceRawOptional": ... - def __next__(self) -> tuple[status.Status, typing.Optional[bytes]]: - ... + def __next__(self) -> tuple[status.RawStatus, typing.Optional[bytes]]: ... - def backlog(self) -> int: - ... + def backlog(self) -> int: ... - def clear_backlog(self, until: int): - ... + def clear_backlog(self, until: int): ... - def name(self) -> typing.Literal[enums.Name.PROPHESEE_EVK3_HD]: - ... + def overflow(self) -> bool: ... - def properties(self) -> Properties: - ... + def name(self) -> typing.Literal[enums.Name.PROPHESEE_EVK3_HD]: ... - def serial(self) -> str: - ... + def properties(self) -> Properties: ... - def chip_firmware_configuration(self) -> Configuration: - ... + def serial(self) -> str: ... - def speed(self) -> enums.Speed: - ... + def chip_firmware_configuration(self) -> Configuration: ... - def temperature_celsius(self) -> float: - ... + def speed(self) -> enums.Speed: ... - def update_configuration(self, configuration: Configuration): - ... + def temperature_celsius(self) -> float: ... + + def update_configuration(self, configuration: Configuration): ... diff --git a/python/python/neuromorphic_drivers/generated/devices/prophesee_evk4.py b/python/python/neuromorphic_drivers/generated/devices/prophesee_evk4.py index 5796831..14762ca 100644 --- a/python/python/neuromorphic_drivers/generated/devices/prophesee_evk4.py +++ b/python/python/neuromorphic_drivers/generated/devices/prophesee_evk4.py @@ -128,9 +128,9 @@ def type() -> str: @dataclasses.dataclass class UsbConfiguration: - buffer_size: serde.type.uint64 = 131072 - ring_size: serde.type.uint64 = 4096 - transfer_queue_size: serde.type.uint64 = 32 + buffer_length: serde.type.uint64 = 131072 + ring_length: serde.type.uint64 = 4096 + transfer_queue_length: serde.type.uint64 = 32 allow_dma: bool = False def serialize(self) -> bytes: @@ -144,8 +144,7 @@ class Properties: class Device(typing.Protocol): - def __enter__(self) -> "Device": - ... + def __enter__(self) -> "Device": ... def __exit__( self, @@ -155,45 +154,35 @@ def __exit__( ) -> bool: ... - def __iter__(self) -> "Device": - ... + def __iter__(self) -> "Device": ... - def __next__(self) -> tuple[status.StatusNonOptional, dict[str, numpy.ndarray[typing.Any, numpy.dtype[numpy.void]]]]: - ... + def __next__(self) -> tuple[status.StatusNonOptional, dict[str, numpy.ndarray[typing.Any, numpy.dtype[numpy.void]]]]: ... - def backlog(self) -> int: - ... + def backlog(self) -> int: ... - def clear_backlog(self, until: int): - ... + def clear_backlog(self, until: int): ... - def name(self) -> typing.Literal[enums.Name.PROPHESEE_EVK4]: - ... + def overflow(self) -> bool: ... - def properties(self) -> Properties: - ... + def name(self) -> typing.Literal[enums.Name.PROPHESEE_EVK4]: ... - def serial(self) -> str: - ... + def properties(self) -> Properties: ... - def chip_firmware_configuration(self) -> Configuration: - ... + def serial(self) -> str: ... - def speed(self) -> enums.Speed: - ... + def chip_firmware_configuration(self) -> Configuration: ... - def temperature_celsius(self) -> float: - ... + def speed(self) -> enums.Speed: ... - def update_configuration(self, configuration: Configuration): - ... - def illuminance(self) -> int: - ... + def temperature_celsius(self) -> float: ... + + def update_configuration(self, configuration: Configuration): ... + + def illuminance(self) -> int: ... class DeviceOptional(typing.Protocol): - def __enter__(self) -> "DeviceOptional": - ... + def __enter__(self) -> "DeviceOptional": ... def __exit__( self, @@ -203,45 +192,35 @@ def __exit__( ) -> bool: ... - def __iter__(self) -> "DeviceOptional": - ... + def __iter__(self) -> "DeviceOptional": ... - def __next__(self) -> tuple[status.Status, typing.Optional[dict[str, numpy.ndarray[typing.Any, numpy.dtype[numpy.void]]]]]: - ... + def __next__(self) -> tuple[status.Status, typing.Optional[dict[str, numpy.ndarray[typing.Any, numpy.dtype[numpy.void]]]]]: ... - def backlog(self) -> int: - ... + def backlog(self) -> int: ... - def clear_backlog(self, until: int): - ... + def clear_backlog(self, until: int): ... - def name(self) -> typing.Literal[enums.Name.PROPHESEE_EVK4]: - ... + def overflow(self) -> bool: ... - def properties(self) -> Properties: - ... + def name(self) -> typing.Literal[enums.Name.PROPHESEE_EVK4]: ... - def serial(self) -> str: - ... + def properties(self) -> Properties: ... - def chip_firmware_configuration(self) -> Configuration: - ... + def serial(self) -> str: ... - def speed(self) -> enums.Speed: - ... + def chip_firmware_configuration(self) -> Configuration: ... - def temperature_celsius(self) -> float: - ... + def speed(self) -> enums.Speed: ... - def update_configuration(self, configuration: Configuration): - ... - def illuminance(self) -> int: - ... + def temperature_celsius(self) -> float: ... + + def update_configuration(self, configuration: Configuration): ... + + def illuminance(self) -> int: ... class DeviceRaw(typing.Protocol): - def __enter__(self) -> "DeviceRaw": - ... + def __enter__(self) -> "DeviceRaw": ... def __exit__( self, @@ -251,45 +230,35 @@ def __exit__( ) -> bool: ... - def __iter__(self) -> "DeviceRaw": - ... + def __iter__(self) -> "DeviceRaw": ... - def __next__(self) -> tuple[status.StatusNonOptional, bytes]: - ... + def __next__(self) -> tuple[status.RawStatusNonOptional, bytes]: ... - def backlog(self) -> int: - ... + def backlog(self) -> int: ... - def clear_backlog(self, until: int): - ... + def clear_backlog(self, until: int): ... - def name(self) -> typing.Literal[enums.Name.PROPHESEE_EVK4]: - ... + def overflow(self) -> bool: ... - def properties(self) -> Properties: - ... + def name(self) -> typing.Literal[enums.Name.PROPHESEE_EVK4]: ... - def serial(self) -> str: - ... + def properties(self) -> Properties: ... - def chip_firmware_configuration(self) -> Configuration: - ... + def serial(self) -> str: ... - def speed(self) -> enums.Speed: - ... + def chip_firmware_configuration(self) -> Configuration: ... - def temperature_celsius(self) -> float: - ... + def speed(self) -> enums.Speed: ... - def update_configuration(self, configuration: Configuration): - ... - def illuminance(self) -> int: - ... + def temperature_celsius(self) -> float: ... + + def update_configuration(self, configuration: Configuration): ... + + def illuminance(self) -> int: ... class DeviceRawOptional(typing.Protocol): - def __enter__(self) -> "DeviceRawOptional": - ... + def __enter__(self) -> "DeviceRawOptional": ... def __exit__( self, @@ -299,37 +268,28 @@ def __exit__( ) -> bool: ... - def __iter__(self) -> "DeviceRawOptional": - ... + def __iter__(self) -> "DeviceRawOptional": ... - def __next__(self) -> tuple[status.Status, typing.Optional[bytes]]: - ... + def __next__(self) -> tuple[status.RawStatus, typing.Optional[bytes]]: ... - def backlog(self) -> int: - ... + def backlog(self) -> int: ... - def clear_backlog(self, until: int): - ... + def clear_backlog(self, until: int): ... - def name(self) -> typing.Literal[enums.Name.PROPHESEE_EVK4]: - ... + def overflow(self) -> bool: ... - def properties(self) -> Properties: - ... + def name(self) -> typing.Literal[enums.Name.PROPHESEE_EVK4]: ... - def serial(self) -> str: - ... + def properties(self) -> Properties: ... - def chip_firmware_configuration(self) -> Configuration: - ... + def serial(self) -> str: ... - def speed(self) -> enums.Speed: - ... + def chip_firmware_configuration(self) -> Configuration: ... - def temperature_celsius(self) -> float: - ... + def speed(self) -> enums.Speed: ... - def update_configuration(self, configuration: Configuration): - ... - def illuminance(self) -> int: - ... + def temperature_celsius(self) -> float: ... + + def update_configuration(self, configuration: Configuration): ... + + def illuminance(self) -> int: ... diff --git a/python/python/neuromorphic_drivers/generated/devices_types.py b/python/python/neuromorphic_drivers/generated/devices_types.py index 381cef5..4da6ad8 100644 --- a/python/python/neuromorphic_drivers/generated/devices_types.py +++ b/python/python/neuromorphic_drivers/generated/devices_types.py @@ -15,8 +15,7 @@ class GenericDevice(typing.Protocol): - def __enter__(self) -> "GenericDevice": - ... + def __enter__(self) -> "GenericDevice": ... def __exit__( self, @@ -26,43 +25,34 @@ def __exit__( ) -> bool: ... - def __iter__(self) -> "GenericDevice": - ... + def __iter__(self) -> "GenericDevice": ... - def __next__(self) -> tuple[status.StatusNonOptional, dict[str, numpy.ndarray[typing.Any, numpy.dtype[numpy.void]]]]: - ... + def __next__(self) -> tuple[status.StatusNonOptional, dict[str, numpy.ndarray[typing.Any, numpy.dtype[numpy.void]]]]: ... - def backlog(self) -> int: - ... + def backlog(self) -> int: ... - def clear_backlog(self, until: int): - ... + def clear_backlog(self, until: int): ... - def name(self) -> Name: - ... + def overflow(self) -> bool: ... - def properties(self) -> Properties: - ... + def name(self) -> Name: ... - def serial(self) -> str: - ... + def properties(self) -> Properties: ... - def chip_firmware_configuration(self) -> Configuration: - ... + def serial(self) -> str: ... - def speed(self) -> Speed: - ... + def chip_firmware_configuration(self) -> Configuration: ... - def temperature_celsius(self) -> float: - ... + def speed(self) -> Speed: ... + + def temperature_celsius(self) -> float: ... + + def update_configuration(self, configuration: Configuration): ... - def update_configuration(self, configuration: Configuration): - ... class GenericDeviceOptional(typing.Protocol): - def __enter__(self) -> "GenericDeviceOptional": - ... + def __enter__(self) -> "GenericDeviceOptional": ... def __exit__( self, @@ -72,43 +62,34 @@ def __exit__( ) -> bool: ... - def __iter__(self) -> "GenericDeviceOptional": - ... + def __iter__(self) -> "GenericDeviceOptional": ... - def __next__(self) -> tuple[status.Status, typing.Optional[dict[str, numpy.ndarray[typing.Any, numpy.dtype[numpy.void]]]]]: - ... + def __next__(self) -> tuple[status.Status, typing.Optional[dict[str, numpy.ndarray[typing.Any, numpy.dtype[numpy.void]]]]]: ... - def backlog(self) -> int: - ... + def backlog(self) -> int: ... - def clear_backlog(self, until: int): - ... + def clear_backlog(self, until: int): ... - def name(self) -> Name: - ... + def overflow(self) -> bool: ... - def properties(self) -> Properties: - ... + def name(self) -> Name: ... - def serial(self) -> str: - ... + def properties(self) -> Properties: ... - def chip_firmware_configuration(self) -> Configuration: - ... + def serial(self) -> str: ... - def speed(self) -> Speed: - ... + def chip_firmware_configuration(self) -> Configuration: ... - def temperature_celsius(self) -> float: - ... + def speed(self) -> Speed: ... + + def temperature_celsius(self) -> float: ... + + def update_configuration(self, configuration: Configuration): ... - def update_configuration(self, configuration: Configuration): - ... class GenericDeviceRaw(typing.Protocol): - def __enter__(self) -> "GenericDeviceRaw": - ... + def __enter__(self) -> "GenericDeviceRaw": ... def __exit__( self, @@ -118,43 +99,34 @@ def __exit__( ) -> bool: ... - def __iter__(self) -> "GenericDeviceRaw": - ... + def __iter__(self) -> "GenericDeviceRaw": ... - def __next__(self) -> tuple[status.StatusNonOptional, bytes]: - ... + def __next__(self) -> tuple[status.RawStatusNonOptional, bytes]: ... - def backlog(self) -> int: - ... + def backlog(self) -> int: ... - def clear_backlog(self, until: int): - ... + def clear_backlog(self, until: int): ... - def name(self) -> Name: - ... + def overflow(self) -> bool: ... - def properties(self) -> Properties: - ... + def name(self) -> Name: ... - def serial(self) -> str: - ... + def properties(self) -> Properties: ... - def chip_firmware_configuration(self) -> Configuration: - ... + def serial(self) -> str: ... - def speed(self) -> Speed: - ... + def chip_firmware_configuration(self) -> Configuration: ... - def temperature_celsius(self) -> float: - ... + def speed(self) -> Speed: ... + + def temperature_celsius(self) -> float: ... + + def update_configuration(self, configuration: Configuration): ... - def update_configuration(self, configuration: Configuration): - ... class GenericDeviceRawOptional(typing.Protocol): - def __enter__(self) -> "GenericDeviceRawOptional": - ... + def __enter__(self) -> "GenericDeviceRawOptional": ... def __exit__( self, @@ -164,38 +136,30 @@ def __exit__( ) -> bool: ... - def __iter__(self) -> "GenericDeviceRawOptional": - ... + def __iter__(self) -> "GenericDeviceRawOptional": ... - def __next__(self) -> tuple[status.Status, typing.Optional[bytes]]: - ... + def __next__(self) -> tuple[status.RawStatus, typing.Optional[bytes]]: ... - def backlog(self) -> int: - ... + def backlog(self) -> int: ... - def clear_backlog(self, until: int): - ... + def clear_backlog(self, until: int): ... - def name(self) -> Name: - ... + def overflow(self) -> bool: ... - def properties(self) -> Properties: - ... + def name(self) -> Name: ... - def serial(self) -> str: - ... + def properties(self) -> Properties: ... - def chip_firmware_configuration(self) -> Configuration: - ... + def serial(self) -> str: ... - def speed(self) -> Speed: - ... + def chip_firmware_configuration(self) -> Configuration: ... - def temperature_celsius(self) -> float: - ... + def speed(self) -> Speed: ... + + def temperature_celsius(self) -> float: ... + + def update_configuration(self, configuration: Configuration): ... - def update_configuration(self, configuration: Configuration): - ... @typing.overload @@ -354,9 +318,8 @@ def open( device.Device, raw, iterator_maximum_raw_packets, - None - if configuration is None - else (configuration.type(), configuration.serialize()), + None if configuration is None else configuration.type(), + None if configuration is None else configuration.serialize(), serial, None if usb_configuration is None else usb_configuration.serialize(), iterator_timeout, diff --git a/python/python/neuromorphic_drivers/status.py b/python/python/neuromorphic_drivers/status.py index 70103a8..0bdb90e 100644 --- a/python/python/neuromorphic_drivers/status.py +++ b/python/python/neuromorphic_drivers/status.py @@ -4,6 +4,19 @@ import typing +@dataclasses.dataclass +class RawRingStatus: + system_time: float + """ + Seconds since the UNIX epoch (1970-01-01 00:00:00 UTC) measured when the USB packet was received. + """ + + backlog: int + raw_packets: int + clutch_engaged: bool + overflow_indices: typing.Optional[list[int]] + + @dataclasses.dataclass class RingStatus: system_time: float @@ -11,21 +24,49 @@ class RingStatus: Seconds since the UNIX epoch (1970-01-01 00:00:00 UTC) measured when the USB packet was received. """ - read_range: tuple[int, int] - write_range: tuple[int, int] - ring_length: int - # current_t is None if the mode is raw - current_t: typing.Optional[int] + backlog: int + raw_packets: int + clutch_engaged: bool + current_t: int - def backlog(self) -> int: - return ( - self.write_range[0] + self.ring_length - self.read_range[1] - ) % self.ring_length - def raw_packets(self) -> int: - return ( - self.read_range[1] + self.ring_length - self.read_range[0] - ) % self.ring_length +@dataclasses.dataclass +class RawStatus: + system_time: float + """ + Seconds since the UNIX epoch (1970-01-01 00:00:00 UTC) measured when the user consumed the packet. + + Status.ring.system_time is always smaller than status.system_time. + The difference between Status.ring.system_time and Status.system_time is a measurement + of software latency (status.system_time - status.ring.system_time). + """ + + ring: typing.Optional[RawRingStatus] + """ + ring is None if no data became available before iterator_timeout + ring may only be None if iterator_timeout is not None + """ + + def delay(self) -> typing.Optional[float]: + if self.ring is None: + return None + return self.system_time - self.ring.system_time + + +@dataclasses.dataclass +class RawStatusNonOptional: + system_time: float + """ + Seconds since the UNIX epoch (1970-01-01 00:00:00 UTC) measured when the user consumed the packet. + + Status.ring.system_time is always smaller than status.system_time. + The difference between Status.ring.system_time and Status.system_time is a measurement + of software latency (status.system_time - status.ring.system_time). + """ + + ring: RawRingStatus + + def delay(self) -> float: ... @dataclasses.dataclass @@ -54,6 +95,14 @@ def delay(self) -> typing.Optional[float]: @dataclasses.dataclass class StatusNonOptional: system_time: float + """ + Seconds since the UNIX epoch (1970-01-01 00:00:00 UTC) measured when the user consumed the packet. + + Status.ring.system_time is always smaller than status.system_time. + The difference between Status.ring.system_time and Status.system_time is a measurement + of software latency (status.system_time - status.ring.system_time). + """ + ring: RingStatus def delay(self) -> float: ... diff --git a/python/src/adapters.rs b/python/src/adapters.rs index 4fcd7b3..81d56c6 100644 --- a/python/src/adapters.rs +++ b/python/src/adapters.rs @@ -2,6 +2,7 @@ use neuromorphic_drivers::types::SliceView; use numpy::IntoPyArray; use crate::structured_array; +use pyo3::prelude::PyDictMethods; use pyo3::IntoPy; pub enum Adapter { @@ -9,6 +10,8 @@ pub enum Adapter { inner: neuromorphic_drivers_rs::adapters::evt3::Adapter, dvs_events: Vec, trigger_events: Vec, + dvs_events_overflow_indices: Vec, + trigger_events_overflow_indices: Vec, }, } @@ -25,13 +28,21 @@ impl Adapter { } } - pub fn push(&mut self, slice: &[u8]) { + pub fn push(&mut self, first_after_overflow: bool, slice: &[u8]) { match self { Adapter::Evt3 { inner, dvs_events, trigger_events, + dvs_events_overflow_indices, + trigger_events_overflow_indices, } => { + if first_after_overflow { + dvs_events_overflow_indices + .push(dvs_events.len() / structured_array::DVS_EVENTS_DTYPE.size()); + trigger_events_overflow_indices + .push(dvs_events.len() / structured_array::TRIGGER_EVENTS_DTYPE.size()); + } let events_lengths = inner.events_lengths(slice); dvs_events.reserve_exact(events_lengths.dvs); trigger_events.reserve_exact(events_lengths.trigger); @@ -54,45 +65,82 @@ impl Adapter { inner: _, dvs_events, trigger_events, + dvs_events_overflow_indices, + trigger_events_overflow_indices, } => { - let dict = pyo3::types::PyDict::new(python); + let dict = pyo3::types::PyDict::new_bound(python); if !dvs_events.is_empty() { let dvs_events_array = { let mut taken_dvs_events = Vec::new(); std::mem::swap(dvs_events, &mut taken_dvs_events); - taken_dvs_events.into_pyarray(python) + taken_dvs_events.into_pyarray_bound(python) }; let description = structured_array::DVS_EVENTS_DTYPE.into_py(python); - let dvs_events_array_pointer = dvs_events_array.as_array_ptr(); - unsafe { - *(*dvs_events_array_pointer).dimensions /= - structured_array::DVS_EVENTS_DTYPE.size() as isize; - *(*dvs_events_array_pointer).strides = - structured_array::DVS_EVENTS_DTYPE.size() as isize; - let previous_description = (*dvs_events_array_pointer).descr; - (*dvs_events_array_pointer).descr = description; - pyo3::ffi::Py_DECREF(previous_description as *mut pyo3::ffi::PyObject); + use numpy::prelude::PyUntypedArrayMethods; + { + let dvs_events_array_pointer = dvs_events_array.as_array_ptr(); + unsafe { + *(*dvs_events_array_pointer).dimensions /= + structured_array::DVS_EVENTS_DTYPE.size() as isize; + *(*dvs_events_array_pointer).strides = + structured_array::DVS_EVENTS_DTYPE.size() as isize; + let previous_description = (*dvs_events_array_pointer).descr; + (*dvs_events_array_pointer).descr = description; + pyo3::ffi::Py_DECREF(previous_description as *mut pyo3::ffi::PyObject); + } } dict.set_item("dvs_events", dvs_events_array)?; + if !dvs_events_overflow_indices.is_empty() { + let dvs_events_overflow_indices_array = { + let mut taken_dvs_events_overflow_indices = Vec::new(); + std::mem::swap( + dvs_events_overflow_indices, + &mut taken_dvs_events_overflow_indices, + ); + taken_dvs_events_overflow_indices.into_pyarray_bound(python) + }; + dict.set_item( + "dvs_events_overflow_indices", + dvs_events_overflow_indices_array, + )?; + } } if !trigger_events.is_empty() { let trigger_events_array = { let mut taken_trigger_events = Vec::new(); std::mem::swap(trigger_events, &mut taken_trigger_events); - taken_trigger_events.into_pyarray(python) + taken_trigger_events.into_pyarray_bound(python) }; let description = structured_array::TRIGGER_EVENTS_DTYPE.into_py(python); - let trigger_events_array_pointer = trigger_events_array.as_array_ptr(); - unsafe { - *(*trigger_events_array_pointer).dimensions /= - structured_array::TRIGGER_EVENTS_DTYPE.size() as isize; - *(*trigger_events_array_pointer).strides = - structured_array::TRIGGER_EVENTS_DTYPE.size() as isize; - let previous_description = (*trigger_events_array_pointer).descr; - (*trigger_events_array_pointer).descr = description; - pyo3::ffi::Py_DECREF(previous_description as *mut pyo3::ffi::PyObject); + use numpy::prelude::PyUntypedArrayMethods; + { + let trigger_events_array_pointer = trigger_events_array.as_array_ptr(); + unsafe { + *(*trigger_events_array_pointer).dimensions /= + structured_array::TRIGGER_EVENTS_DTYPE.size() as isize; + *(*trigger_events_array_pointer).strides = + structured_array::TRIGGER_EVENTS_DTYPE.size() as isize; + let previous_description = (*trigger_events_array_pointer).descr; + (*trigger_events_array_pointer).descr = description; + pyo3::ffi::Py_DECREF(previous_description as *mut pyo3::ffi::PyObject); + } } + dict.set_item("trigger_events", trigger_events_array)?; + if !trigger_events_overflow_indices.is_empty() { + let trigger_events_overflow_indices_array = { + let mut taken_trigger_events_overflow_indices = Vec::new(); + std::mem::swap( + trigger_events_overflow_indices, + &mut taken_trigger_events_overflow_indices, + ); + taken_trigger_events_overflow_indices.into_pyarray_bound(python) + }; + dict.set_item( + "trigger_events_overflow_indices", + trigger_events_overflow_indices_array, + )?; + } } Ok(dict.into()) } @@ -107,6 +155,8 @@ impl From for Adapter { inner, dvs_events: Vec::new(), trigger_events: Vec::new(), + dvs_events_overflow_indices: Vec::new(), + trigger_events_overflow_indices: Vec::new(), }, } } diff --git a/python/src/bytes.rs b/python/src/bytes.rs index 5d0a3be..8051b7d 100644 --- a/python/src/bytes.rs +++ b/python/src/bytes.rs @@ -9,6 +9,14 @@ impl Bytes { } } + pub fn length(&self) -> usize { + if self.inner.is_null() { + 0 + } else { + unsafe { pyo3::ffi::PyBytes_Size(self.inner) as usize } + } + } + pub fn extend_from_slice(&mut self, _python: pyo3::Python, slice: &[u8]) { if self.inner.is_null() { self.inner = unsafe { @@ -36,11 +44,11 @@ impl Bytes { } } - pub fn take<'p>(&mut self, python: pyo3::Python<'p>) -> Option<&'p pyo3::types::PyBytes> { + pub fn take<'p>(&mut self, python: pyo3::Python<'p>) -> Option> { if self.inner.is_null() { None } else { - let py_bytes = Some(unsafe { python.from_owned_ptr(self.inner) }); + let py_bytes = Some(unsafe { pyo3::Bound::from_owned_ptr(python, self.inner) }); self.inner = std::ptr::null_mut(); py_bytes } diff --git a/python/src/lib.rs b/python/src/lib.rs index 1e1d6d2..6535aa9 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -33,7 +33,10 @@ struct Device { adapter: Option>, iterator_timeout: Option, iterator_maximum_raw_packets: usize, - error_flag: neuromorphic_drivers_rs::error::Flag, + flag: neuromorphic_drivers_rs::Flag< + neuromorphic_drivers_rs::Error, + neuromorphic_drivers_rs::UsbOverflow, + >, } // unsafe workaround until auto traits are stabilized @@ -49,9 +52,10 @@ unsafe impl Send for Buffer<'_> {} struct Status { instant: std::time::Instant, - read_range: (usize, usize), - write_range: (usize, usize), - ring_length: usize, + backlog: usize, + raw_packets: usize, + clutch_engaged: bool, + overflow_indices: Vec, current_t: Option, } @@ -61,34 +65,34 @@ impl Device { fn new( raw: bool, iterator_maximum_raw_packets: usize, - type_and_configuration: Option<(&str, &[u8])>, + device_type: Option<&str>, + configuration: Option<&[u8]>, serial: Option<&str>, usb_configuration: Option<&[u8]>, iterator_timeout: Option, ) -> pyo3::PyResult { - let error_flag = neuromorphic_drivers_rs::error::Flag::new(); - let event_loop = std::sync::Arc::new( - neuromorphic_drivers_rs::usb::EventLoop::new( - std::time::Duration::from_millis(100), - error_flag.clone(), - ) - .map_err(|error| pyo3::exceptions::PyRuntimeError::new_err(format!("{error}")))?, - ); + let (flag, event_loop) = neuromorphic_drivers_rs::event_loop_and_flag() + .map_err(|error| pyo3::exceptions::PyRuntimeError::new_err(format!("{error}")))?; let device = neuromorphic_drivers_rs::open( serial, - match type_and_configuration { - Some((device_type, configuration)) => Some( - neuromorphic_drivers_rs::Configuration::deserialize_bincode( - device_type.parse().map_err(|error| { + if let Some(device_type) = device_type { + if let Some(configuration) = configuration { + Some( + neuromorphic_drivers_rs::Configuration::deserialize_bincode( + device_type.parse().map_err(|error| { + pyo3::exceptions::PyRuntimeError::new_err(format!("{error}")) + })?, + configuration, + ) + .map_err(|error| { pyo3::exceptions::PyRuntimeError::new_err(format!("{error}")) })?, - configuration, ) - .map_err(|error| { - pyo3::exceptions::PyRuntimeError::new_err(format!("{error}")) - })?, - ), - None => None, + } else { + None + } + } else { + None }, if let Some(usb_configuration) = usb_configuration { Some( @@ -103,7 +107,7 @@ impl Device { None }, event_loop, - error_flag.clone(), + flag.clone(), ) .map_err(|error| pyo3::exceptions::PyRuntimeError::new_err(format!("{error}")))?; let adapter = if raw { @@ -127,7 +131,7 @@ impl Device { None => None, }, iterator_maximum_raw_packets, - error_flag, + flag, }) } @@ -137,9 +141,9 @@ impl Device { fn __exit__( &mut self, - _exception_type: Option<&pyo3::types::PyType>, - _value: Option<&pyo3::types::PyAny>, - _traceback: Option<&pyo3::types::PyAny>, + _exception_type: Option<&pyo3::Bound<'_, pyo3::types::PyType>>, + _value: Option<&pyo3::Bound<'_, pyo3::types::PyAny>>, + _traceback: Option<&pyo3::Bound<'_, pyo3::types::PyAny>>, ) { self.device = None; } @@ -153,7 +157,7 @@ impl Device { python: pyo3::Python, ) -> pyo3::PyResult> { let start = std::time::Instant::now(); - let error_flag = slf.error_flag.clone(); + let flag = slf.flag.clone(); let iterator_timeout = slf.iterator_timeout; let iterator_maximum_raw_packets = slf.iterator_maximum_raw_packets; let device = DeviceReference(slf.device.as_ref().ok_or( @@ -172,49 +176,67 @@ impl Device { |adapter| Buffer::Adapter(adapter.deref_mut()), ); python.allow_threads(|| -> pyo3::PyResult> { - let mut status = None; - let mut raw_packets = 0; + let mut status: Option = None; let mut available_raw_packets = None; let buffer_timeout = iterator_timeout.unwrap_or(std::time::Duration::from_millis(100)); loop { - if let Some(error) = error_flag.load() { - return Err(pyo3::exceptions::PyRuntimeError::new_err(format!( - "{error:?}" - ))); - } + flag.load_error().map_err(|error| { + pyo3::exceptions::PyRuntimeError::new_err(format!("{error:?}")) + })?; if let Some(buffer_view) = device.0.next_with_timeout(&buffer_timeout) { - let current_status = status.get_or_insert(Status { - instant: buffer_view.instant, - read_range: (buffer_view.read, buffer_view.read + 1), - write_range: buffer_view.write_range, - ring_length: buffer_view.ring_length, - current_t: None, - }); - current_status.read_range = (current_status.read_range.0, buffer_view.read + 1); - current_status.write_range = buffer_view.write_range; + if let Some(status) = status.as_mut() { + status.raw_packets += 1; + status.backlog = buffer_view.backlog(); + status.clutch_engaged = matches!( + buffer_view.clutch, + neuromorphic_drivers_rs::usb::Clutch::Engaged + ); + } else { + status = Some(Status { + instant: buffer_view.instant, + backlog: buffer_view.backlog(), + raw_packets: 1, + clutch_engaged: matches!( + buffer_view.clutch, + neuromorphic_drivers_rs::usb::Clutch::Engaged + ), + overflow_indices: Vec::new(), + current_t: None, + }); + } let _ = available_raw_packets.get_or_insert_with(|| { + let available_now = buffer_view.backlog() + 1; if iterator_maximum_raw_packets == 0 { - buffer_view.backlog() + 1 + available_now } else { - iterator_maximum_raw_packets.min(buffer_view.backlog() + 1) + iterator_maximum_raw_packets.min(available_now) } }); - raw_packets += 1; match &mut buffer { Buffer::Adapter(adapter) => { - adapter.push(buffer_view.slice); - current_status.current_t = Some(adapter.current_t()); + adapter.push(buffer_view.first_after_overflow, buffer_view.slice); + if let Some(status) = status.as_mut() { + status.current_t = Some(adapter.current_t()); + } } Buffer::Bytes(bytes) => pyo3::Python::with_gil(|python| { + if buffer_view.first_after_overflow { + status + .as_mut() + .expect("status is always Some here") + .overflow_indices + .push(bytes.length()); + } bytes.extend_from_slice(python, buffer_view.slice); }), } } - if iterator_timeout.map_or_else(|| false, |timeout| start.elapsed() >= timeout) - || available_raw_packets.map_or_else( - || false, - |available_raw_packets| raw_packets >= available_raw_packets, - ) + if iterator_timeout.map_or(false, |timeout| start.elapsed() >= timeout) + || available_raw_packets.map_or(false, |available_raw_packets| { + status + .as_ref() + .map_or(false, |status| status.raw_packets >= available_raw_packets) + }) { return pyo3::Python::with_gil(|python| { let packet = match &mut buffer { @@ -231,9 +253,10 @@ impl Device { status.map(|status| { ( (status.instant.elapsed() + duration_since_epoch).as_secs_f64(), - status.read_range, - status.write_range, - status.ring_length, + status.backlog, + status.raw_packets, + status.clutch_engaged, + status.overflow_indices, status.current_t, ) }), @@ -262,7 +285,7 @@ impl Device { python: pyo3::Python, until: usize, ) -> pyo3::PyResult<()> { - let error_flag = slf.error_flag.clone(); + let flag = slf.flag.clone(); let device = DeviceReference(slf.device.as_ref().ok_or( pyo3::exceptions::PyRuntimeError::new_err("__next__ called after __exit__"), )?); @@ -279,11 +302,8 @@ impl Device { |adapter| Buffer::Adapter(adapter.deref_mut()), ); python.allow_threads(|| loop { - if let Some(error) = error_flag.load() { - return Err(pyo3::exceptions::PyRuntimeError::new_err(format!( - "{error:?}" - ))); - } + flag.load_error() + .map_err(|error| pyo3::exceptions::PyRuntimeError::new_err(format!("{error:?}")))?; if let Some(buffer_view) = device .0 .next_with_timeout(&std::time::Duration::from_millis(0)) @@ -300,6 +320,10 @@ impl Device { }) } + fn overflow(slf: pyo3::PyRef) -> bool { + slf.flag.load_warning().is_some() + } + fn name(slf: pyo3::PyRef) -> pyo3::PyResult { Ok(slf .device @@ -325,7 +349,7 @@ impl Device { slf: pyo3::PyRef, python: pyo3::Python, ) -> pyo3::PyResult { - Ok(pyo3::types::PyBytes::new( + Ok(pyo3::types::PyBytes::new_bound( python, &slf.device .as_ref() @@ -380,14 +404,14 @@ impl Device { fn update_configuration( slf: pyo3::PyRef, - type_and_configuration: (&str, &[u8]), + device_type: &str, + configuration: &[u8], ) -> pyo3::PyResult<()> { let configuration = neuromorphic_drivers_rs::Configuration::deserialize_bincode( - type_and_configuration - .0 + device_type .parse() .map_err(|error| pyo3::exceptions::PyRuntimeError::new_err(format!("{error}")))?, - type_and_configuration.1, + configuration, ) .map_err(|error| pyo3::exceptions::PyRuntimeError::new_err(format!("{error}")))?; slf.device @@ -405,10 +429,7 @@ impl Device { } #[pyo3::pymodule] -fn neuromorphic_drivers( - _py: pyo3::Python<'_>, - module: &pyo3::types::PyModule, -) -> pyo3::PyResult<()> { +fn neuromorphic_drivers(module: &pyo3::Bound<'_, pyo3::types::PyModule>) -> pyo3::PyResult<()> { module.add_class::()?; module.add_function(pyo3::wrap_pyfunction!(list_devices, module)?)?; Ok(()) diff --git a/python/src/structured_array.rs b/python/src/structured_array.rs index 27731be..aa9a105 100644 --- a/python/src/structured_array.rs +++ b/python/src/structured_array.rs @@ -13,9 +13,10 @@ macro_rules! dtype_base { impl pyo3::IntoPy for DtypeBase { fn into_py(self, python: pyo3::Python) -> core::ffi::c_int { + use numpy::prelude::PyArrayDescrMethods; match self { $( - Self::[<$type:camel>] => $type::get_dtype(python).num(), + Self::[<$type:camel>] => $type::get_dtype_bound(python).num(), )+ } }