From 4cbdd39dd79d632d86c1bbcb749897a02f99e843 Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 9 Feb 2021 12:52:24 +1100 Subject: [PATCH 1/3] warn not raise --- src/panoptes/pocs/camera/zwo.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/panoptes/pocs/camera/zwo.py b/src/panoptes/pocs/camera/zwo.py index 877d677d4..b85df46cc 100644 --- a/src/panoptes/pocs/camera/zwo.py +++ b/src/panoptes/pocs/camera/zwo.py @@ -334,20 +334,17 @@ def _control_setter(self, control_type, value): # Check limits. max_value = self._control_info[control_type]['max_value'] if value > max_value: - msg = "Cannot set {} to {}, clipping to max value {}".format( - control_name, value, max_value) + self.logger.warning(f"Cannot set {control_name} to {value}, clipping to max value:" + f" {max_value}.") Camera._driver.set_control_value(self._handle, control_type, max_value) - raise error.IllegalValue(msg) min_value = self._control_info[control_type]['min_value'] if value < min_value: - msg = "Cannot set {} to {}, clipping to min value {}".format( - control_name, value, min_value) + self.logger.warning(f"Cannot set {control_name} to {value}, clipping to min value:" + f" {min_value}.") Camera._driver.set_control_value(self._handle, control_type, min_value) - raise error.IllegalValue(msg) else: if not self._control_info[control_type]['is_auto_supported']: - msg = "{} cannot set {} to AUTO".format(self.model, control_name) - raise error.IllegalValue(msg) + raise error.IllegalValue(f"{self.model} cannot set {control_name} to AUTO") Camera._driver.set_control_value(self._handle, control_type, value) From e38e28482ac680cce148db8d82174f1e6fabf909 Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 9 Feb 2021 13:47:38 +1100 Subject: [PATCH 2/3] add return statements --- src/panoptes/pocs/camera/zwo.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/panoptes/pocs/camera/zwo.py b/src/panoptes/pocs/camera/zwo.py index b85df46cc..ee6743447 100644 --- a/src/panoptes/pocs/camera/zwo.py +++ b/src/panoptes/pocs/camera/zwo.py @@ -337,12 +337,14 @@ def _control_setter(self, control_type, value): self.logger.warning(f"Cannot set {control_name} to {value}, clipping to max value:" f" {max_value}.") Camera._driver.set_control_value(self._handle, control_type, max_value) + return min_value = self._control_info[control_type]['min_value'] if value < min_value: self.logger.warning(f"Cannot set {control_name} to {value}, clipping to min value:" f" {min_value}.") Camera._driver.set_control_value(self._handle, control_type, min_value) + return else: if not self._control_info[control_type]['is_auto_supported']: raise error.IllegalValue(f"{self.model} cannot set {control_name} to AUTO") From 5d0da24ed445a57ab8992998b62787ead6aef580 Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 10 Feb 2021 11:07:27 +1100 Subject: [PATCH 3/3] initial --- setup.cfg | 1 + src/panoptes/pocs/camera/zwo.py | 54 ++++++++++++++++++++++++--------- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/setup.cfg b/setup.cfg index d2728b7a4..a5a8a75b8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -42,6 +42,7 @@ install_requires = panoptes-utils[config]>=0.2.30 pyserial transitions + pyusb>=1.1.1 # The usage of test_requires is discouraged, see `Dependency Management` docs # tests_require = pytest; pytest-cov # Require a specific Python version, e.g. Python 2.7 or >= 3.4 diff --git a/src/panoptes/pocs/camera/zwo.py b/src/panoptes/pocs/camera/zwo.py index ee6743447..b6f01dc7a 100644 --- a/src/panoptes/pocs/camera/zwo.py +++ b/src/panoptes/pocs/camera/zwo.py @@ -1,21 +1,25 @@ import threading import time from contextlib import suppress +from usb.core import find as finddev import numpy as np from astropy import units as u from astropy.time import Time -from panoptes.pocs.camera.libasi import ASIDriver -from panoptes.pocs.camera.sdk import AbstractSDKCamera + from panoptes.utils import error from panoptes.utils.images import fits as fits_utils from panoptes.utils.utils import get_quantity_value +from panoptes.pocs.camera.libasi import ASIDriver +from panoptes.pocs.camera.sdk import AbstractSDKCamera + class Camera(AbstractSDKCamera): _driver = None # Class variable to store the ASI driver interface _cameras = [] # Cache of camera string IDs _assigned_cameras = set() # Camera string IDs already in use. + _usb_vendor_id = 0x03c3 # Fixed for ZWO cameras def __init__(self, name='ZWO ASI Camera', @@ -24,7 +28,6 @@ def __init__(self, *args, **kwargs): """ ZWO ASI Camera class - Args: serial_number (str): camera serial number or user set ID (up to 8 bytes). See notes. gain (int, optional): gain setting, using camera's internal units. If not given @@ -33,7 +36,6 @@ def __init__(self, or 'Y8'). Default is to use 'RAW16' if supported by the camera, otherwise the camera's own default will be used. *args, **kwargs: additional arguments to be passed to the parent classes. - Notes: ZWO ASI cameras don't have a 'port', they only have a non-deterministic integer camera_ID and, probably, an 8 byte serial number. Optionally they also have an @@ -105,7 +107,6 @@ def temperature(self): @AbstractSDKCamera.target_temperature.getter def target_temperature(self): """ Current value of the target temperature for the camera's image sensor cooling control. - Can be set by assigning an astropy.units.Quantity """ return self._control_getter('TARGET_TEMP')[0] @@ -123,7 +124,6 @@ def cooling_power(self): @property def gain(self): """ Current value of the camera's gain setting in internal units. - See `egain` for the corresponding electrons / ADU value. """ return self._control_getter('GAIN')[0] @@ -148,7 +148,6 @@ def is_exposing(self): def connect(self): """ Connect to ZWO ASI camera. - Gets 'camera_ID' (needed for all driver commands), camera properties and details of available camera commands/parameters. """ @@ -169,6 +168,12 @@ def connect(self): Camera._driver.disable_dark_subtract(self._handle) self._connected = True + def reconnect(self): + """ Reconnect to the camera. """ + Camera._driver.close_camera(self._handle) + self._reset_usb() + return self.connect() + def start_video(self, seconds, filename_root, max_frames, image_type=None): if not isinstance(seconds, u.Quantity): seconds = seconds * u.second @@ -298,7 +303,13 @@ def _readout(self, filename, width, height, header): header=header, filename=filename) elif exposure_status == 'FAILED': - raise error.PanError("Exposure failed on {}".format(self)) + + # Reconnect to the camera so it can still be used + self.logger.warning(f"Exposure failed on {self}. Reconnecting camera.") + self.reconnect() + + raise error.PanError(f"Exposure failed on {self}") + elif exposure_status == 'IDLE': raise error.PanError("Exposure missing on {}".format(self)) else: @@ -334,19 +345,32 @@ def _control_setter(self, control_type, value): # Check limits. max_value = self._control_info[control_type]['max_value'] if value > max_value: - self.logger.warning(f"Cannot set {control_name} to {value}, clipping to max value:" - f" {max_value}.") + msg = "Cannot set {} to {}, clipping to max value {}".format( + control_name, value, max_value) Camera._driver.set_control_value(self._handle, control_type, max_value) - return + raise error.IllegalValue(msg) min_value = self._control_info[control_type]['min_value'] if value < min_value: - self.logger.warning(f"Cannot set {control_name} to {value}, clipping to min value:" - f" {min_value}.") + msg = "Cannot set {} to {}, clipping to min value {}".format( + control_name, value, min_value) Camera._driver.set_control_value(self._handle, control_type, min_value) - return + raise error.IllegalValue(msg) else: if not self._control_info[control_type]['is_auto_supported']: - raise error.IllegalValue(f"{self.model} cannot set {control_name} to AUTO") + msg = "{} cannot set {} to AUTO".format(self.model, control_name) + raise error.IllegalValue(msg) Camera._driver.set_control_value(self._handle, control_type, value) + + def _reset_usb(self): + """ Reset the USB device. """ + self.logger.warning(f"Resetting USB for {self}.") + for product_id in Camera._driver.get_product_ids(): + dev = finddev(idVendor=self._usb_vendor_id, idProduct=product_id) + if dev: + self.logger.debug(f"Identified USB product ID: {product_id}.") + break + if not dev: + raise RuntimeError(f"Unable to determine USB product ID for {self}.") + dev.reset()