diff options
author | Andy Wortman <ixineeringeverywhere@gmail.com> | 2019-07-27 20:07:05 -0700 |
---|---|---|
committer | Andy Wortman <ixineeringeverywhere@gmail.com> | 2019-07-27 20:07:05 -0700 |
commit | 144854e4b1a7ac38c5ddd2b0d45c495f491aa5dc (patch) | |
tree | 55306a36cfe8fef91a38496510acf34a771e582e /src/qhyccd | |
parent | 0d887779b8dee042999d04a29d4a095a3df93a6c (diff) |
significant rewrite for threaded qhy operation
Diffstat (limited to 'src/qhyccd')
-rw-r--r-- | src/qhyccd/QHYCCDCam.rs | 70 | ||||
-rw-r--r-- | src/qhyccd/mod.rs | 388 |
2 files changed, 406 insertions, 52 deletions
diff --git a/src/qhyccd/QHYCCDCam.rs b/src/qhyccd/QHYCCDCam.rs index c0a0775..2f6b4fb 100644 --- a/src/qhyccd/QHYCCDCam.rs +++ b/src/qhyccd/QHYCCDCam.rs @@ -24,7 +24,7 @@ impl From<u32> for QHYResult { } #[repr(u32)] -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Control { Brightness = 0, // !< image brightness Contrast = 1, //1 image contrast @@ -84,6 +84,74 @@ pub enum Control { OutputDataAlignment = 55 //55 } +impl Control { + pub fn requires_reset(&self) -> bool { + match self { + Control::Brightness + | Control::Contrast + | Control::CONTROL_WBR + | Control::CONTROL_WBB + | Control::CONTROL_WBG + | Control::Gamma + | Control::Gain + | Control::Offset + | Control::Exposure + | Control::Speed + | Control::TransferBit + | Control::Channels + | Control::Bin1x1Mode + | Control::Bin2x2Mode + | Control::Bin3x3Mode + | Control::Bin4x4Mode + | Control::USBTraffic => { + true + } + | Control::CurTemp + | Control::CurPWM + | Control::ManulPwm + | Control::CFWPort + | Control::Cooler + | Control::St4port + | Control::Color + | Control::CAM_MECHANICALSHUTTER + | Control::CAM_TRIGER_INTERFACE + | Control::CAM_TECOVERPROTECT_INTERFACE + | Control::CAM_SINGNALCLAMP_INTERFACE + | Control::CAM_FINETONE_INTERFACE + | Control::CAM_SHUTTERMOTORHEATING_INTERFACE + | Control::CAM_CALIBRATEFPN_INTERFACE + | Control::CAM_CHIPTEMPERATURESENSOR_INTERFACE + | Control::CAM_USBREADOUTSLOWEST_INTERFACE + | Control::CAM_8BITS + | Control::CAM_16BITS + | Control::CAM_GPS + | Control::CAM_IGNOREOVERSCAN_INTERFACE + | Control::QHYCCD_3A_AUTOBALANCE + | Control::QHYCCD_3A_AUTOEXPOSURE + | Control::QHYCCD_3A_AUTOFOCUS + | Control::CONTROL_AMPV + | Control::CONTROL_VCAM + | Control::CAM_VIEW_MODE + | Control::CONTROL_CFWSLOTSNUM + | Control::IS_EXPOSING_DONE + | Control::ScreenStretchB + | Control::ScreenStretchW + | Control::CONTROL_DDR + | Control::CAM_LIGHT_PERFORMANCE_MODE + | Control::CAM_QHY5II_GUIDE_MODE + | Control::DDR_BUFFER_CAPACITY + | Control::DDR_BUFFER_READ_THRESHOLD + | Control::DefaultOffset + | Control::OutputDataActualBits + | Control::OutputDataAlignment => { + false + } + _ => { false } + } + } +} + + #[repr(u32)] #[derive(Copy, Clone, Debug)] pub enum Bayer diff --git a/src/qhyccd/mod.rs b/src/qhyccd/mod.rs index b50ba3a..12630d6 100644 --- a/src/qhyccd/mod.rs +++ b/src/qhyccd/mod.rs @@ -12,17 +12,83 @@ use std::fs::File; use std::io::BufWriter; use std::path::Path; +use std::time::Duration; +use crossbeam_channel::unbounded; +use crossbeam_channel::{Sender, Receiver, TryRecvError}; +use crossbeam_channel::select; + use png::HasParameters; -unsafe impl Send for Camera { } +use crate::Dimensions; + +// unsafe impl Send for Camera { } #[derive(Debug)] pub struct Camera { - imagew: u32, - imageh: u32, + buffer: Vec<Vec<u8>>, + frame_size: usize, + handle: *mut os::raw::c_void, + settings: Settings, +} + +#[derive(Debug, Default, Copy, Clone)] +pub struct Settings { + exposure: u64, + brightness: u32, + contrast: u32, + control_wbr: u32, + control_wbb: u32, + control_wbg: u32, + gamma: u32, + gain: u32, + offset: u32, + speed: u32, + transfer_bit: u32, + usb_traffic: u32, + row_noise_re: bool, + cur_temp: f64, + cur_pwm: f64, + manual_pwm: f64, + cfw_port: f64, + cooler: bool, + st4_port: bool, + color: bool, + bin: u8, bpp: u8, channels: u8, - bin: u8, - handle: *mut os::raw::c_void + // width, height + image_roi: (u32, u32), + image_xy: (u32, u32), +} + +impl Settings { + pub fn frame_size(&self) -> usize { + let pixels = (self.image_roi.0 as usize) * (self.image_roi.1 as usize); + let bytes_per_pixel = (self.bpp as usize / 8) * (self.channels as usize); + pixels * bytes_per_pixel + } + + pub fn update_param(&mut self, control: Control, value: f64) { + match control { + Control::Brightness => { self.brightness = value as u32; } + Control::Contrast => { self.contrast = value as u32; } + Control::CONTROL_WBR => { self.control_wbr = value as u32; } + Control::CONTROL_WBB => { self.control_wbb = value as u32; } + Control::CONTROL_WBG => { self.control_wbg = value as u32; } + Control::Gamma => { self.gamma = value as u32; } + Control::Gain => { self.gain = value as u32; } + Control::Offset => { self.offset = value as u32; } + Control::Exposure => { self.exposure = value as u64; } + Control::TransferBit => { self.transfer_bit = value as u32; } + Control::Channels => { self.channels = value as u8; } + Control::USBTraffic => { self.usb_traffic = value as u32; } + Control::RowNoiseRe => { self.row_noise_re = value as u32 == 1; } + Control::CurTemp => { self.cur_temp = value; } + Control::CurPWM => { self.cur_pwm = value; } + Control::ManulPwm => { self.manual_pwm = value; } + Control::Cooler => { self.cooler = value as u32 == 1; } + _ => { } + } + } } #[derive(Debug, Copy, Clone)] @@ -45,7 +111,7 @@ fn check(result: os::raw::c_int) -> Result<()> { static mut INITIALIZED: bool = false; -fn fix_channels(dataslice: &mut [u8]) { +pub fn fix_channels_and_endianness(dataslice: &mut [u8]) { for i in 0..(dataslice.len() / 6) { let (b_low, b_high) = (dataslice[i * 6], dataslice[i * 6 + 1]); let tmp2 = dataslice[i * 6 + 1]; @@ -60,7 +126,139 @@ fn fix_channels(dataslice: &mut [u8]) { } } -pub fn acquire(camera_idx: i32) -> Result<Camera> { +pub fn fix_endianness(dataslice: &mut [u8]) { + for i in 0..(dataslice.len() / 2) { + let (low, high) = (dataslice[i * 2], dataslice[i * 2 + 1]); + dataslice[i * 2 + 0] = high; + dataslice[i * 2 + 1] = low; + } +} + + +pub fn connect(camera_idx: i32) -> Result<(Receiver<QHYResponse>, Sender<QHYMessage>)> { + let (response_sender, response_receiver) = unbounded(); + let (message_sender, message_receiver) = unbounded(); + + std::thread::spawn(move || { + let mut camera = match acquire(camera_idx) { + Ok(camera) => camera, + Err(e) => { + response_sender.send(QHYResponse::InitializationError).unwrap(); + return; + }, + }; + + camera.set_defaults().unwrap(); + + // sleep for 2ms between waiting for messages and reading frames. This introduces an + // artificial cap of 500fps, but I don't own a camera that can hit that. + let SLEEP_TIME = 2u64; + let mut exposing = false; + let mut counter = 0u64; + + loop { + match message_receiver.try_recv() { + Ok(QHYMessage::Shutdown) => { + println!("Got shutdown request, closing camera..."); + return; + } + Ok(QHYMessage::FrameAvailable(data)) => { + if data.len() == camera.settings.frame_size() { + camera.buffer.push(data); + } else { + // otherwise the writer finished handling a frame from an old size, + // and the array is incorrectly sized for current settings. + // so drop the buffer and free that memory. + drop(data); + } + } + Ok(QHYMessage::BeginCapture) => { + counter = camera.settings.exposure; + println!("Beginning capture"); + camera.begin_exposure().expect("can begin exposures"); + exposing = true; + } + Ok(QHYMessage::StopCapture) => { + counter = 0; + camera.cancel_exposure().expect("can cancel exposures"); + exposing = false; + } + Ok(QHYMessage::SetControl(control, value)) => { + if control == Control::Color { + // overload Control::Color to signal if we want to switch to + // debayer-disabled "mono" + let mono_mode = value == 0.0; + camera.set_color_mode(mono_mode); + // yes, this requires reset. + counter = 0; + camera.cancel_exposure().expect("can cancel exposures"); + exposing = false; + } else { + if control.requires_reset() { + counter = 0; + camera.cancel_exposure().expect("can cancel exposures"); + exposing = false; + } + camera.set_control(control, value).expect("can set camera controls"); + } + response_sender.send(QHYResponse::UpdatedSettings(camera.settings.clone())).expect("can send responses to main thread"); + } + Ok(QHYMessage::QueryControl(control)) => { + let value = camera.get_control(control); + camera.settings.update_param(control, value); + response_sender.send(QHYResponse::CurrentControlValue(control, value)).expect("can send control value"); + } + Err(TryRecvError::Empty) => { + // this is fine. nothing new to do. + }, + Err(TryRecvError::Disconnected) => { + // uh oh. main thread crashed? all we can do is exit. + return; + } + } + + if counter == 0 && exposing { + if let Some(data) = camera.get_frame() { + let (data, width, height, bpp, channels) = camera.read_frame(data).expect("can read frames from camera"); + counter = camera.settings.exposure; + camera.begin_exposure().expect("can begin exposures"); + response_sender.send(QHYResponse::Data(data, Dimensions::new(width, height, bpp, channels))).unwrap(); + } else { + // no frame ready in the buffer! we can't actually read the image... + counter = camera.settings.exposure; + camera.begin_exposure().expect("can begin exposures"); + response_sender.send(QHYResponse::DroppedFrame).unwrap(); + } + } + + counter = counter.saturating_sub(SLEEP_TIME * 1000); + std::thread::sleep(Duration::from_millis(SLEEP_TIME)); + } + + }); + + Ok((response_receiver, message_sender)) +} + +pub enum QHYResponse { + InitializationError, + Data(Vec<u8>, Dimensions), + DroppedFrame, + Shutdown, + UpdatedSettings(Settings), + CurrentControlValue(Control, f64), +} + +pub enum QHYMessage { + FrameAvailable(Vec<u8>), + BeginCapture, + StopCapture, + QueryControl(Control), + SetControl(Control, f64), + Shutdown, +} + +fn acquire(camera_idx: i32) -> Result<Camera> { unsafe { if !INITIALIZED { println!("Initializing QHYCCDResource"); @@ -87,29 +285,35 @@ pub fn acquire(camera_idx: i32) -> Result<Camera> { check(QHYCCDCam::InitQHYCCD(handle))?; check(QHYCCDCam::CancelQHYCCDExposingAndReadout(handle))?; Ok(Camera { - imagew: 0, - imageh: 0, - bpp: 16, - bin: 1, - channels: 3, - handle: handle + buffer: vec![], + frame_size: 0, + handle: handle, + settings: Settings::default() }) } } impl Camera { + pub fn get_frame(&mut self) -> Option<Vec<u8>> { + if self.frame_size != self.image_buf_size() { + // something has happened that requires a new buffer + self.resize_buffer(); + } + + self.buffer.pop() + } pub fn image_buf_size(&self) -> usize { - (self.imagew as usize) * (self.imageh as usize) * (self.bpp as usize / 8) * (self.channels as usize) + self.settings.frame_size() } - pub fn set_exposure_ms(&self, ms: u32) -> Result<()> { - self.set_param(Control::Exposure, (ms as f64) * 1000.0) + pub fn set_exposure_ms(&mut self, ms: u32) -> Result<()> { + self.set_control(Control::Exposure, (ms as f64) * 1000.0) } pub fn set_target_temp(&self, temp: f64) -> Result<()> { unsafe { check(QHYCCDCam::ControlQHYCCDTemp(self.handle, temp)) } } - pub fn has_param(&self, control: Control) -> bool { + pub fn has_control(&self, control: Control) -> bool { unsafe { match QHYResult::from(QHYCCDCam::IsQHYCCDControlAvailable(self.handle, control as i32) as u32) { QHYResult::QHYCCD_ERROR => { @@ -124,19 +328,21 @@ impl Camera { } } } - pub fn set_param(&self, control: Control, value: f64) -> Result<()> { + pub fn set_control(&mut self, control: Control, value: f64) -> Result<()> { unsafe { - if self.has_param(control) { - check(QHYCCDCam::SetQHYCCDParam(self.handle, control as i32, value)) + if self.has_control(control) { + check(QHYCCDCam::SetQHYCCDParam(self.handle, control as i32, value))?; + self.settings.update_param(control, value); + Ok(()) } else { println!("Cannot set control: {:?}", control); Ok(()) } } } - pub fn get_param_limits(&self, control: Control) -> Result<(f64, f64, f64)> { + pub fn get_control_limits(&self, control: Control) -> Result<(f64, f64, f64)> { unsafe { - if self.has_param(control) { + if self.has_control(control) { let mut min = 0f64; let mut max = 0f64; let mut step = 0f64; @@ -154,7 +360,7 @@ impl Camera { } } - pub fn get_param(&self, control: Control) -> f64 { + pub fn get_control(&self, control: Control) -> f64 { unsafe { QHYCCDCam::GetQHYCCDParam(self.handle, control as i32) } @@ -164,16 +370,34 @@ impl Camera { check(QHYCCDCam::CloseQHYCCD(self.handle)) } } + pub fn set_color_mode(&mut self, mono: bool) -> Result<()> { + // TODO: handle mono cameras correctly + if mono { + unsafe { + check(QHYCCDCam::SetQHYCCDDebayerOnOff(self.handle, 0))?; + } + self.set_control(Control::CONTROL_WBR, 1000.0)?; + self.set_control(Control::CONTROL_WBG, 1000.0)?; + self.set_control(Control::CONTROL_WBB, 1000.0)?; + self.settings.channels = 1; + } else { + unsafe { + check(QHYCCDCam::SetQHYCCDDebayerOnOff(self.handle, 1))?; + } + self.set_control(Control::CONTROL_WBR, 1000.0)?; + self.set_control(Control::CONTROL_WBG, 1000.0)?; + self.set_control(Control::CONTROL_WBB, 1000.0)?; + self.settings.channels = 3; + } + Ok(()) + } pub fn set_defaults(&mut self) -> Result<()> { unsafe { println!("Hey wait gotta get dimensions first"); let ((chipw, chiph), (imagew, imageh), (pixelw, pixelh), bpp) = self.get_dimensions()?; match QHYCCDCam::IsQHYCCDControlAvailable(self.handle, Control::Color as i32) { 1 | 2 | 3 | 4 => { - check(QHYCCDCam::SetQHYCCDDebayerOnOff(self.handle, 1))?; - self.set_param(Control::CONTROL_WBR, 1000.0)?; - self.set_param(Control::CONTROL_WBG, 1000.0)?; - self.set_param(Control::CONTROL_WBB, 1000.0)?; + self.set_color_mode(false)?; }, a @ _ => { println!("unexpected response when querying color setting: {}", a); @@ -182,34 +406,35 @@ impl Camera { } self.set_roi(0, 0, imagew, imageh)?; self.set_bin_mode(1)?; - if self.has_param(Control::TransferBit) { + if self.has_control(Control::TransferBit) { check(QHYCCDCam::SetQHYCCDBitsMode(self.handle, 16))?; - self.bpp = 16; + self.settings.bpp = 16; } + println!("roi set to {} x {} ???", imagew, imageh); Ok(()) } } pub fn set_roi(&mut self, x: u32, y: u32, w: u32, h: u32) -> Result<()> { unsafe { - check(QHYCCDCam::SetQHYCCDResolution(self.handle, x, y, w, h)); - self.imageh = h; - self.imagew = w; + check(QHYCCDCam::SetQHYCCDResolution(self.handle, x, y, w, h))?; + self.settings.image_roi = (w, h); + self.settings.image_xy = (x, y); Ok(()) } } pub fn set_bin_mode(&mut self, bin: u8) -> Result<()> { match bin { - 1 => if !self.has_param(Control::Bin1x1Mode) { return Err(CameraError::InvalidControl); } - 2 => if !self.has_param(Control::Bin2x2Mode) { return Err(CameraError::InvalidControl); } - 3 => if !self.has_param(Control::Bin3x3Mode) { return Err(CameraError::InvalidControl); } - 4 => if !self.has_param(Control::Bin4x4Mode) { return Err(CameraError::InvalidControl); } + 1 => if !self.has_control(Control::Bin1x1Mode) { return Err(CameraError::InvalidControl); } + 2 => if !self.has_control(Control::Bin2x2Mode) { return Err(CameraError::InvalidControl); } + 3 => if !self.has_control(Control::Bin3x3Mode) { return Err(CameraError::InvalidControl); } + 4 => if !self.has_control(Control::Bin4x4Mode) { return Err(CameraError::InvalidControl); } _ => { return Err(CameraError::InvalidControl); } } unsafe { check(QHYCCDCam::SetQHYCCDBinMode(self.handle, bin as i32, bin as i32))?; - self.bin = bin; + self.settings.bin = bin; Ok(()) } } @@ -238,9 +463,65 @@ impl Camera { Ok(()) } + pub fn cancel_exposure(&self) -> Result<()> { + unsafe { + check(QHYCCDCam::CancelQHYCCDExposingAndReadout(self.handle)) + } + } + + pub fn begin_exposure(&self) -> Result<()> { + let result = unsafe { QHYCCDCam::ExpQHYCCDSingleFrame(self.handle) }; + match QHYCCDCam::QHYResult::from(result as u32) { + QHYResult::QHYCCD_SUCCESS => { +// println!("Didn't expect this result..."); + Ok(()) + }, + QHYResult::QHYCCD_READ_DIRECTLY => { +// println!("Exp complete, example sleeps so i'll sleep too"); + Ok(()) + }, + QHYResult::QHYCCD_DELAY_200MS => { + println!("Sleeping 200ms... but not actually, bout to have a bug :):)))"); + Ok(()) + }, + a @ _ =>{ + println!("exp err: {:?}", a); + return Err(CameraError::QHYError); + } + } + } + + fn resize_buffer(&mut self) { + println!("Resizing buffer to 3x{}", self.settings.frame_size()); + self.frame_size = self.settings.frame_size(); + self.buffer = vec![ + vec![0u8; self.frame_size], + vec![0u8; self.frame_size], + vec![0u8; self.frame_size], + ]; + } + + pub fn read_frame(&self, mut buf: Vec<u8>) -> Result<(Vec<u8>, u32, u32, u8, u8)> { + let mut castediw = 0i32; + let mut castedih = 0i32; + let mut castedbpp = 0i32; + let mut channels = 0i32; + println!("Getting data..."); + unsafe { + use std::time::{SystemTime, UNIX_EPOCH}; + let start = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let start = start.as_secs() * 1000 + (start.subsec_nanos() as u64) / 1000000; + check(QHYCCDCam::GetQHYCCDSingleFrame(self.handle, &mut castediw, &mut castedih, &mut castedbpp, &mut channels, buf.as_mut_ptr()))?; + let end = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let end = end.as_secs() * 1000 + (end.subsec_nanos() as u64) / 1000000; + println!("camera record time: {}ms", end - start); + } + Ok((buf, castediw as u32, castedih as u32, castedbpp as u8, channels as u8)) + } + /* pub fn take_image_live(&self, path: &str) -> Result<()> { unsafe { - let exposure_duration = self.get_param(Control::Exposure); + let exposure_duration = self.get_control(Control::Exposure); let exposure_ms = exposure_duration / 1000.0; println!("Exposure duration: {}", exposure_ms); @@ -260,19 +541,19 @@ impl Camera { let mut bpp: i32 = self.bpp as i32; let mut channels: i32 = self.channels as i32; + let frame = match frame_rx.recv() { + Ok(frame) => frame, + Err(signal) => { + return Err(CollectionError::BufferClosed); + } + }; // println!("w {} h {} bpp {} channels {}", imagew, imageh, bpp, channels); - match check(QHYCCDCam::GetQHYCCDLiveFrame(self.handle, &mut imagew, &mut imageh, &mut bpp, &mut channels, data)) { + match check(QHYCCDCam::GetQHYCCDLiveFrame(self.handle, &mut imagew, &mut imageh, &mut bpp, &mut channels, data.as_mut_ptr())) { Ok(()) => { + processor.send(Ok(frame)) + } println!("Ok, guess we got it?"); - let mut dataslice: &mut [u8] = - unsafe { - std::slice::from_raw_parts_mut( - data, - bufsize as usize - ) - }; - //fix_channels(dataslice); let dest = Path::new(path); @@ -303,12 +584,16 @@ impl Camera { dealloc(data as *mut u8, data_layout); Ok(()) + + pub fn begin_exposure(&self) -> Result<()> { + + } } } pub fn take_image(&self, path: &str) -> Result<()> { unsafe { - let exposure_duration = self.get_param(Control::Exposure); + let exposure_duration = self.get_control(Control::Exposure); let exposure_ms = exposure_duration / 1000.0; println!("Exposure duration: {}", exposure_ms); let result = QHYCCDCam::ExpQHYCCDSingleFrame(self.handle); @@ -362,13 +647,13 @@ impl Camera { let data_layout = Layout::from_size_align(bufsize as usize, 8).unwrap(); let data = alloc(data_layout); - let mut counter: i64 = (self.get_param(Control::Exposure) as u64 / 1000) as i64; + let mut counter: i64 = (self.get_control(Control::Exposure) as u64 / 1000) as i64; while counter > 0 { println!("I think there's about {}ms remaining", counter); println!("You think there's about {}ms remaining", self.get_exposure_remaining()); std::thread::sleep(std::time::Duration::from_millis(500)); - println!("Camera temp is currently: {}", self.get_param(Control::CurTemp)); + println!("Camera temp is currently: {}", self.get_control(Control::CurTemp)); counter -= 500; } @@ -390,7 +675,7 @@ impl Camera { encoder.set(png::ColorType::RGB).set(png::BitDepth::Sixteen); // crazy theory, endianness might be wrong... // so flip the bytes first - let mut dataslice: &mut [u8] = + let dataslice: &mut [u8] = unsafe { std::slice::from_raw_parts_mut( data, @@ -418,6 +703,7 @@ impl Camera { Ok(()) } } +*/ pub fn get_overscan_area(&self) -> Result<(u32, u32, u32, u32)> { unsafe { let mut startX: i32 = 0; |