From 8a2b52cb5fa2d97f15017146d3ed6b1fd66d256d Mon Sep 17 00:00:00 2001 From: ccolin Date: Tue, 3 Dec 2024 04:24:50 -0600 Subject: [PATCH 01/11] rotator docs --- pyscope/observatory/ascom_rotator.py | 17 +++++++ pyscope/observatory/rotator.py | 76 ++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/pyscope/observatory/ascom_rotator.py b/pyscope/observatory/ascom_rotator.py index 45d18eb0..8948a234 100644 --- a/pyscope/observatory/ascom_rotator.py +++ b/pyscope/observatory/ascom_rotator.py @@ -8,6 +8,23 @@ class ASCOMRotator(ASCOMDevice, Rotator): def __init__(self, identifier, alpaca=False, device_number=0, protocol="http"): + """ + ASCOM implementation of the Rotator base class. + + The class provides an interface to control an ASCOM-compatible rotator device, including methods for + moving, halting, and properties for determining the current position and movement state of the rotator. + + Parameters + ---------- + identifier : `str` + The device identifier. + alpaca : `bool`, default : `False`, optional + Whether the device is an Alpaca device. + device_number : `int`, default : 0, optional + The device number. + protocol : `str`, default : "http", optional + The protocol to use for communication with the device. + """ super().__init__( identifier, alpaca=alpaca, diff --git a/pyscope/observatory/rotator.py b/pyscope/observatory/rotator.py index 79655dec..24e3aea5 100644 --- a/pyscope/observatory/rotator.py +++ b/pyscope/observatory/rotator.py @@ -6,51 +6,120 @@ class Rotator(ABC, metaclass=_DocstringInheritee): @abstractmethod def __init__(self, *args, **kwargs): + """ + Abstract base class for a rotator device. + + This class defines the interface for a rotator device, including methods for + moving the rotator, halting movement, and properties for determining the current + position and movement state of the rotator. Subclasses must implement the abstract + methods and properties in this class. + + Parameters + ---------- + *args : `tuple` + Variable length argument list. + **kwargs : `dict` + Arbitrary keyword arguments. + """ pass @abstractmethod def Halt(self): + """Immediately stop any movement of the rotator due to a `Move` or `MoveAbsolute` call.""" pass @abstractmethod def Move(self, Position): + """ + Move the rotator `Position` degrees from the current position. + + Calling will change `TargetPosition` to sum of the current angular position and `Position` module 360 degrees. + + Parameters + ---------- + Position : `float` + Relative position to move from current `Position`, in degrees + """ pass @abstractmethod def MoveAbsolute(self, Position): + """ + Move the rotator to the absolute `Position` degrees. + + Calling will change `TargetPosition` to `Position`. + + Parameters + ---------- + Position : `float` + Absolute position to move to, in degrees + """ pass @abstractmethod def MoveMechanical(self, Position): + """ + Move the rotator to the mechanical `Position` angle. + + This move is independent of any sync offsets, and is to be used for circumstances + where physical rotaion angle is needed (i.e. taking sky flats). + For instances such as imaging, `MoveAbsolute` should be preferred. + + Parameters + ---------- + Position : `float` + Mechanical position to move to, in degrees + """ pass @abstractmethod def Sync(self, Position): + """ + Synchronize the rotator to the `Position` angle without movement. + + Once set, along with determining sync offsets, `MoveAbsolute` and `Position` must function + in terms of synced coordinates rather than mechanical. + Implementations should make sure offsets persist across driver starts and device reboots. + + Parameters + ---------- + Position : `float` + Sync position to set, in degrees + """ pass @property @abstractmethod def CanReverse(self): + """Whether the rotator supports the `Reverse` method. (`bool`)""" pass @property @abstractmethod def IsMoving(self): + """Whether the rotator is currently moving. (`bool`)""" pass @property @abstractmethod def MechanicalPosition(self): + """The current mechanical position of the rotator, in degrees. (`float`)""" pass @property @abstractmethod def Position(self): + """The current angular position of the rotator accounting offsets, in degrees. (`float`)""" pass @property @abstractmethod def Reverse(self): + """ + Whether the rotator is in reverse mode. (`bool`) + + If `True`, the rotation and angular direction must be reversed for optics. + """ pass @Reverse.setter @@ -61,9 +130,16 @@ def Reverse(self, value): @property @abstractmethod def StepSize(self): + """The minimum step size of the rotator, in degrees. (`float`)""" pass @property @abstractmethod def TargetPosition(self): + """ + The target angular position of the rotator, in degrees. (`float`) + + Upon a `Move` or `MoveAbsolute` call, this property is set to the position angle to which + the rotator is moving. Value persists until another move call. + """ pass From 0c5fe2722b3335e7397e0b6c9afed95713038a05 Mon Sep 17 00:00:00 2001 From: ccolin Date: Tue, 3 Dec 2024 04:59:41 -0600 Subject: [PATCH 02/11] switch docs --- pyscope/observatory/ascom_switch.py | 17 ++++ pyscope/observatory/switch.py | 117 ++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/pyscope/observatory/ascom_switch.py b/pyscope/observatory/ascom_switch.py index f4fac71b..dfcb9ee0 100644 --- a/pyscope/observatory/ascom_switch.py +++ b/pyscope/observatory/ascom_switch.py @@ -8,6 +8,23 @@ class ASCOMSwitch(ASCOMDevice, Switch): def __init__(self, identifier, alpaca=False, device_number=0, protocol="http"): + """ + ASCOM implementation of the Switch base class. + + The class provides an interface to ASCOM-compatible switch devices, including methods for + getting and setting switch values, and getting switch names and descriptions. + + Parameters + ---------- + identifier : `str` + The device identifier. + alpaca : `bool`, default : `False`, optional + Whether the device is an Alpaca device. + device_number : `int`, default : 0, optional + The device number. + protocol : `str`, default : "http", optional + The protocol to use for communication with the device. + """ super().__init__( identifier, alpaca=alpaca, diff --git a/pyscope/observatory/switch.py b/pyscope/observatory/switch.py index eb5c0b53..980982a8 100644 --- a/pyscope/observatory/switch.py +++ b/pyscope/observatory/switch.py @@ -6,49 +6,166 @@ class Switch(ABC, metaclass=_DocstringInheritee): @abstractmethod def __init__(self, *args, **kwargs): + """ + Abstract class for switch devices. + + This class defines the common interface for switch devices, including methods for + getting and setting switch values, and getting switch names and descriptions. + Subclasses must implement the abstract methods and properties in this class. + + Parameters + ---------- + *args : `tuple` + Variable length argument list. + **kwargs : `dict` + Arbitrary keyword arguments. + """ pass @abstractmethod def CanWrite(self, ID): + """ + Whether the switch can be written to. (`bool`) + + Most devices will return `True` for this property, but some switches are + meant to be read-only, such as limit switches or sensors. + + Parameters + ---------- + ID : `int` + The switch ID number. + """ pass @abstractmethod def GetSwitch(self, ID): + """ + The current state of the switch as a boolean. (`bool`) + + This is a mandatory property for all switch devices. For multi-state switches, + this should be `True` for when the device is at the maximum value, `False` if at minimum, + and either `True` or `False` for intermediate values; specification up to driver author. + + Parameters + ---------- + ID : `int` + The switch ID number. + """ pass @abstractmethod def GetSwitchDescription(self, ID): + """ + The detailed description of the switch, to be used for features such as tooltips. (`str`) + + Parameters + ---------- + ID : `int` + The switch ID number. + """ pass @abstractmethod def GetSwitchName(self, ID): + """ + The name of the switch, to be used for display purposes. (`str`) + + Parameters + ---------- + ID : `int` + The switch ID number. + """ pass @abstractmethod def MaxSwitchValue(self, ID): + """ + The maximum value of the switch. (`float`) + + Must be greater than `MinSwitchValue`. Two-state devices should return 1.0, and only + multi-state devices should return values greater than 1.0. + + Parameters + ---------- + ID : `int` + The switch ID number. + """ pass @abstractmethod def MinSwitchValue(self, ID): + """ + The minimum value of the switch. (`float`) + + Must be less than `MaxSwitchValue`. Two-state devices should return 0.0. + """ pass @abstractmethod def SetSwitch(self, ID, State): + """ + Sets the state of the switch controller device. + + `GetSwitchValue` will also be set, to `MaxSwitchValue` if `State` is `True`, and `MinSwitchValue` if `State` is `False`. + + Parameters + ---------- + ID : `int` + The switch ID number. + State : `bool` + The state to set the switch to. + """ pass @abstractmethod def SetSwitchName(self, ID, Name): + """ + Sets the name of the switch device. + + Parameters + ---------- + ID : `int` + The switch ID number. + Name : `str` + The new name to set the switch to. + """ pass @abstractmethod def SetSwitchValue(self, ID, Value): + """ + Sets the value of the switch device. + + Values should be between `MinSwitchValue` and `MaxSwitchValue`. If the value is + intermediate in relation to `SwitchStep`, it will be set to an achievable value. + How this achieved value is determined is up to the driver author. + + Parameters + ---------- + ID : `int` + The switch ID number. + Value : `float` + The value to set the switch to. + """ pass @abstractmethod def SwitchStep(self, ID): + """ + The step size that the switch device supports. (`float`) + + Has to be greater than zero. This is the smallest increment that the switch device's value can be set to. + For two-state devices, this should be 1.0, and anything else other than 0 for multi-state devices. + + Parameters + ---------- + ID : `int` + The switch ID number. + """ pass @property @abstractmethod def MaxSwitch(self): + """The number of switch devices managed by this driver. (`int`)""" pass From dcc98224ecf5f1b7446707fe32fe6b97a50b2d43 Mon Sep 17 00:00:00 2001 From: ccolin Date: Tue, 3 Dec 2024 08:08:59 -0600 Subject: [PATCH 03/11] azimuth reference clarification --- pyscope/observatory/dome.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyscope/observatory/dome.py b/pyscope/observatory/dome.py index 17ecbb1f..c3188d40 100644 --- a/pyscope/observatory/dome.py +++ b/pyscope/observatory/dome.py @@ -92,7 +92,7 @@ def SlewToAzimuth(self, Azimuth): Parameters ---------- Azimuth : `float` - The azimuth to slew to in degrees. + The azimuth to slew to in degrees, North-referenced, CW. """ pass @@ -104,7 +104,7 @@ def SyncToAzimuth(self, Azimuth): Parameters ---------- Azimuth : `float` - The azimuth to synchronize to in degrees. + The azimuth to synchronize to in degrees, North-referenced, CW. """ pass From e6ff16e0bd0726b38c3db708c24e0d531676416a Mon Sep 17 00:00:00 2001 From: ccolin Date: Tue, 3 Dec 2024 08:09:16 -0600 Subject: [PATCH 04/11] telescope docs --- pyscope/observatory/ascom_telescope.py | 185 ++++++++++ pyscope/observatory/telescope.py | 453 +++++++++++++++++++++++++ 2 files changed, 638 insertions(+) diff --git a/pyscope/observatory/ascom_telescope.py b/pyscope/observatory/ascom_telescope.py index 8de648e2..68dcbdbc 100644 --- a/pyscope/observatory/ascom_telescope.py +++ b/pyscope/observatory/ascom_telescope.py @@ -8,6 +8,24 @@ class ASCOMTelescope(ASCOMDevice, Telescope): def __init__(self, identifier, alpaca=False, device_number=0, protocol="http"): + """ + ASCOM implementation of the Telescope base class. + + This class provides an interface for an ASCOM-compatible telescope. Methods include + slewing the telescope, halting movement, and properties for determining the current + position and movement state of the telescope. + + Parameters + ---------- + identifier : `str` + The unique device identifier. This can be the ProgID for COM devices or the device number for Alpaca devices. + alpaca : `bool`, default : `False`, optional + Whether to use the Alpaca protocol, for Alpaca devices. If `False`, the COM protocol is used. + device_number : `int`, default : 0, optional + The device number. + protocol : `str`, default : "http", optional + The communication protocol to use. + """ super().__init__( identifier, alpaca=alpaca, @@ -21,14 +39,74 @@ def AbortSlew(self): self._device.AbortSlew() def AxisRates(self, Axis): + """ + Set of rates at which the telescope may be moved about the specified axis. + + See `MoveAxis`'s description for more information. Should return an empty set + if `MoveAxis` is not supported. + + Parameters + ---------- + Axis : `TelescopeAxes `_ + The axis about which the telescope may be moved. See below for ASCOM standard. + * 0 : Primary axis, Right Ascension or Azimuth. + * 1 : Secondary axis, Declination or Altitude. + * 2 : Tertiary axis, imager rotators. + + Returns + ------- + `IAxisRates `_ + While it may look intimidating, this return is but an object representing a + collection of rates, including properties for both the number of rates, and the + actual rates, and methods for returning an enumerator for the rates, and for + disposing of the object as a whole. + + Notes + ----- + Rates must be absolute non-negative values only. Determining direction + of motion should be done by the application by applying the appropriate + sign to the rate. This is to not have the driver present a duplicate + set of positive and negative rates, therefore decluttering. + """ logger.debug(f"ASCOMTelescope.AxisRates({Axis}) called") return self._device.AxisRates(Axis) def CanMoveAxis(self, Axis): + """ + Whether the telescope can move about the specified axis with `MoveAxis`. (`bool`) + + See `MoveAxis`'s description for more information. + + Parameters + ---------- + Axis : `TelescopeAxes `_ + The axis about which the telescope may be moved. See below for ASCOM standard. + * 0 : Primary axis, Right Ascension or Azimuth. + * 1 : Secondary axis, Declination or Altitude. + * 2 : Tertiary axis, imager rotators. + """ logger.debug(f"ASCOMTelescope.CanMoveAxis({Axis}) called") return self._device.CanMoveAxis(Axis) def DestinationSideOfPier(self, RightAscension, Declination): + """ + For German equatorial mounts, prediction of which side of the pier the telescope will be on after slewing to the specified equatorial coordinates at the current instant of time. + + Parameters + ---------- + RightAscension : `float` + Right ascension coordinate (hours) of destination, not current, at current instant of time. + Declination : `float` + Declination coordinate (degrees) of destination, not current, at current instant of time. + + Returns + ------- + `PierSide `_ + The side of the pier on which the telescope will be after slewing to the specified coordinates. See below for ASCOM standard. + * 0 : Normal pointing state, mount on East side of pier, looking West. + * 1 : Through the pole pointing state, mount on West side of pier, looking East. + * -1 : Unknown or indeterminate. + """ logger.debug( f"ASCOMTelescope.DestinationSideOfPier({RightAscension}, {Declination}) called" ) @@ -39,6 +117,34 @@ def FindHome(self): self._device.FindHome() def MoveAxis(self, Axis, Rate): + """ + Move the telescope at the given rate about the specified axis. + + The rate is a value between 0.0 and the maximum value returned by `AxisRates`. + This movement will continue indefinitely until `MoveAxis` is called again with a rate of 0.0, + at which point the telescope will restore rate to the one set by `Tracking`. Tracking motion + is disabled during this movement about axis. The method can be called for each axis independently, + with each axis moving at its own rate. + + Parameters + ---------- + Axis : `TelescopeAxes `_ + The axis about which the telescope may be moved. See below for ASCOM standard. + * 0 : Primary axis, Right Ascension or Azimuth. + * 1 : Secondary axis, Declination or Altitude. + * 2 : Tertiary axis, imager rotators. + Rate : `float` + Rate of motion in degrees per second. Positive values indicate motion in one direction, + negative values in the opposite direction, and 0.0 stops motion by this method and resumes tracking motion. + + Notes + ----- + Rate must be within the values returned by `AxisRates`. Note that those values are absolute, + and the direction of motion is determined by the sign of the rate given here. This sets `Slewing` + much like `SlewToAltAz` or `SlewToCoordinates` would, and is therefore affected by `AbortSlew`. + Depending on `Tracking` state, a setting rate of 0.0 will reset the scope to the previous `TrackingRate`, + or to no movement at all. + """ logger.debug(f"ASCOMTelescope.MoveAxis({Axis}, {Rate}) called") self._device.MoveAxis(Axis, Rate) @@ -59,6 +165,25 @@ def Park(self): logger.error(f"Error in executing or accessing SetPark: {e}") def PulseGuide(self, Direction, Duration): + """ + Move the telescope in the specified direction for the specified time. + + Rate at which the telescope moves is set by `GuideRateRightAscension` and `GuideRateDeclination`. + Depending on driver implementation and the mount's capabilities, these two rates may be tied together. + For hardware capable of dual-axis movement, the method returns immediately, otherwise it returns only + after completion of the guide pulse. + + Parameters + ---------- + Direction : `GuideDirections `_ + Direction in which to move the telescope. See below for ASCOM standard. + * 0 : North or up. + * 1 : South or down. + * 2 : East or right. + * 3 : West or left. + Duration : `int` + Time in milliseconds for which to pulse the guide. Must be a positive non-zero value. + """ logger.debug(f"ASCOMTelescope.PulseGuide({Direction}, {Duration}) called") self._device.PulseGuide(Direction, Duration) @@ -126,6 +251,14 @@ def Unpark(self): @property def AlignmentMode(self): + """ + The alignment mode of the telescope. (`AlignmentModes `_) + + See below for ASCOM standard. + * 0 : Altitude-Azimuth alignment. + * 1 : Polar (equatorial) mount alignment, NOT German. + * 2 : German equatorial mount alignment. + """ logger.debug("ASCOMTelescope.AlignmentMode property accessed") return self._device.AlignmentMode @@ -273,6 +406,16 @@ def DoesRefraction(self, value): @property def EquatorialSystem(self): + """ + Equatorial coordinate system used by the telescope. (`EquatorialCoordinateType `_) + + See below for ASCOM standard. + * 0 : Custom/unknown equinox and/or reference frame. + * 1 : Topocentric coordinates. Coordinates at the current date, allowing for annual aberration and precession-nutation. Most common for amateur telescopes. + * 2 : J2000 equator and equinox. Coordinates at the J2000 epoch, ICRS reference frame. Most common for professional telescopes. + * 3 : J2050 equator and equinox. Coordinates at the J2050 epoch, ICRS reference frame. + * 4 : B1950 equinox. Coordinates at the B1950 epoch, FK4 reference frame. + """ logger.debug("ASCOMTelescope.EquatorialSystem property accessed") return self._device.EquatorialSystem @@ -323,6 +466,19 @@ def RightAscensionRate(self, value): @property def SideOfPier(self): + """ + The pointing state of the mount. (`PierSide `_) + + See below for ASCOM standard. + * 0 : Normal pointing state, mount on East side of pier, looking West. + * 1 : Through the pole pointing state, mount on West side of pier, looking East. + * -1 : Unknown or indeterminate. + + .. warning:: + The name of this property is misleading and does not reflect the true meaning of the property. + The name will not change out of preservation of compatibility. + For more information, see the 'Remarks' section of `this page `_. + """ logger.debug("ASCOMTelescope.SideOfPier property accessed") return self._device.SideOfPier @@ -413,6 +569,21 @@ def Tracking(self, value): @property def TrackingRate(self): + """ + Current tracking rate of the telescope's sidereal drive. (`DriveRates `_) + + Supported rates are contained in `TrackingRates`, and the property's rate + is guaranteed to be one of those. + If the mount's current tracking rate cannot be read, the driver should force + and report a default rate on connection. + In this circumstance, a default of `Sidereal` rate is preferred. + + See below for ASCOM standard. + * 0 : Sidereal tracking, 15.041 arcseconds per second. + * 1 : Lunar tracking, 14.685 arcseconds per second. + * 2 : Solar tracking, 15.0 arcseconds per second. + * 3 : King tracking, 15.0369 arcseconds per second. + """ logger.debug("ASCOMTelescope.TrackingRate property accessed") return self._device.TrackingRate @@ -423,6 +594,20 @@ def TrackingRate(self, value): @property def TrackingRates(self): + """ + Collection of supported tracking rates for the telescope's sidereal drive. (`TrackingRates `_) + + This collection contains all supported tracking rates. At a minimum, it will contain + an item for the default `Sidereal` rate. The collection is an iterable, and includes + properties for the number of rates, and the actual rates, and methods for returning + an enumerator for the rates, and for disposing of the object as a whole. + + See below for ASCOM standard expected collection. + * 0 : Sidereal tracking, 15.041 arcseconds per second. + * 1 : Lunar tracking, 14.685 arcseconds per second. + * 2 : Solar tracking, 15.0 arcseconds per second. + * 3 : King tracking, 15.0369 arcseconds per second + """ logger.debug("ASCOMTelescope.TrackingRates property accessed") return self._device.TrackingRates diff --git a/pyscope/observatory/telescope.py b/pyscope/observatory/telescope.py index 7825e406..3b0147ce 100644 --- a/pyscope/observatory/telescope.py +++ b/pyscope/observatory/telescope.py @@ -6,87 +6,336 @@ class Telescope(ABC, metaclass=_DocstringInheritee): @abstractmethod def __init__(self, *args, **kwargs): + """ + Abstract base class for a telescope device. + + This class defines the interface for a telescope device, including methods for + slewing the telescope, halting movement, and properties for determining the current + position and movement state of the telescope. Subclasses must implement the abstract + methods and properties in this class. + + Parameters + ---------- + *args : `tuple` + Variable length argument list. + **kwargs : `dict` + Arbitrary keyword arguments. + """ pass @abstractmethod def AbortSlew(self): + """ + Immediately stop any movement of the telescope due to any of the SlewTo*** calls. + + Does nothing if the telescope is not slewing. Tracking will return to its pre-slew state. + """ pass @abstractmethod def AxisRates(self, Axis): + """ + Set of rates at which the telescope may be moved about the specified axis. + + See `MoveAxis`'s description for more information. Should return an empty set + if `MoveAxis` is not supported. + + Parameters + ---------- + Axis : `enum` + The axis about which the telescope may be moved. See below for example values. + * 0 : Primary axis, usually corresponding to Right Ascension or Azimuth. + * 1 : Secondary axis, usually corresponding to Declination or Altitude. + * 2 : Tertiary axis, usually corresponding to imager rotators. + + Returns + ------- + `object` + This object should be an iterable collection, including properties for both + the number of rates, and the actual rates, and methods for returning an + enumerator for the rates, and for disposing of the object as a whole. + + Notes + ----- + Rates must be absolute non-negative values only. Determining direction + of motion should be done by the application by applying the appropriate + sign to the rate. This is to not have the driver present a duplicate + set of positive and negative rates, therefore decluttering. + """ pass @abstractmethod def CanMoveAxis(self, Axis): + """ + Whether the telescope can move about the specified axis with `MoveAxis`. (`bool`) + + See `MoveAxis`'s description for more information. + + Parameters + ---------- + Axis : `enum` + The axis about which the telescope may be moved. See below for example values. + * 0 : Primary axis, usually corresponding to Right Ascension or Azimuth. + * 1 : Secondary axis, usually corresponding to Declination or Altitude. + * 2 : Tertiary axis, usually corresponding to imager rotators. + """ pass @abstractmethod def DestinationSideOfPier(self, RightAscension, Declination): + """ + For German equatorial mounts, prediction of which side of the pier the telescope will be on after slewing to the specified equatorial coordinates at the current instant of time. + + Parameters + ---------- + RightAscension : `float` + Right ascension coordinate (hours) of destination, not current, at current instant of time. + Declination : `float` + Declination coordinate (degrees) of destination, not current, at current instant of time. + + Returns + ------- + `enum` + The side of the pier on which the telescope will be after slewing to the specified equatorial coordinates at the current instant of time. See below for example values. + * 0 : Normal pointing state, mount on East side of pier, looking West. + * 1 : Through the pole pointing state, mount on West side of pier, looking East. + * -1 : Unknown or indeterminate. + """ pass @abstractmethod def FindHome(self): + """ + Synchronously locate the telescope's home position. + + Returns only once the home position has been located. After locating, + `AtHome` will set to `True`. + """ pass @abstractmethod def MoveAxis(self, Axis, Rate): + """ + Move the telescope at the given rate about the specified axis. + + The rate is a value between 0.0 and the maximum value returned by `AxisRates`. + This movement will continue indefinitely until `MoveAxis` is called again with a rate of 0.0, + at which point the telescope will restore rate to the one set by `Tracking`. Tracking motion + is disabled during this movement about axis. The method can be called for each axis independently, + with each axis moving at its own rate. + + Parameters + ---------- + Axis : `enum` + The axis about which the telescope may be moved. See below for example values. + * 0 : Primary axis, usually corresponding to Right Ascension or Azimuth. + * 1 : Secondary axis, usually corresponding to Declination or Altitude. + * 2 : Tertiary axis, usually corresponding to imager rotators. + Rate : `float` + Rate of motion in degrees per second. Positive values indicate motion in one direction, + negative values in the opposite direction, and 0.0 stops motion by this method and resumes tracking motion. + + Notes + ----- + Rate must be within the values returned by `AxisRates`. Note that those values are absolute, + and the direction of motion is determined by the sign of the rate given here. This sets `Slewing` + much like `SlewToAltAz` or `SlewToCoordinates` would, and is therefore affected by `AbortSlew`. + Depending on `Tracking` state, a setting rate of 0.0 will reset the scope to the previous `TrackingRate`, + or to no movement at all. + """ pass @abstractmethod def Park(self): + """ + Moves the telescope to the park position. + + To achieve this, it stops all motion (alternatively just restrict it to a small safe range), + park, then set `AtPark` to `True`. Calling it with `AtPark` already `True` is safe to do. + """ pass @abstractmethod def PulseGuide(self, Direction, Duration): + """ + Move the telescope in the specified direction for the specified time. + + Rate at which the telescope moves is set by `GuideRateRightAscension` and `GuideRateDeclination`. + Depending on driver implementation and the mount's capabilities, these two rates may be tied together. + For hardware capable of dual-axis movement, the method returns immediately, otherwise it returns only + after completion of the guide pulse. + + Parameters + ---------- + Direction : `enum` + Direction in which to move the telescope. See below for example values. + * 0 : North or up. + * 1 : South or down. + * 2 : East or right. + * 3 : West or left. + Duration : `int` + Time in milliseconds for which to pulse the guide. Must be a positive non-zero value. + """ pass @abstractmethod def SetPark(self): + """Sets the telescope's park position to the current position.""" pass @abstractmethod def SlewToAltAz(self, Azimuth, Altitude): + """ + Move the telescope to the specified Alt/Az coordinates. + + Slew may fail if target is beyond limits imposed within the driver component. These limits + could be mechanical constraints by the mount or instruments, safety restrictions from the + building or dome enclosure, etc. + `TargetRightAscension` and `TargetDeclination` will be unchanged by this method. + + Parameters + ---------- + Azimuth : `float` + Azimuth coordinate in degrees, North-referenced, CW. + Altitude : `float` + Altitude coordinate in degrees. + """ pass @abstractmethod def SlewToAltAzAsync(self, Azimuth, Altitude): + """ + Move the telescope to the specified Alt/Az coordinates asynchronously. + + Method should only be implemented if the telescope is capable of reading `Altitude`, `Azimuth`, `RightAscension`, and `Declination` + while slewing. Returns immediately after starting the slew. + Slew may fail if target is beyond limits imposed within the driver component. These limits + could be mechanical constraints by the mount or instruments, safety restrictions from the + building or dome enclosure, etc. + `TargetRightAscension` and `TargetDeclination` will be unchanged by this method. + + Parameters + ---------- + Azimuth : `float` + Azimuth coordinate in degrees, North-referenced, CW. + Altitude : `float` + Altitude coordinate in degrees. + """ pass @abstractmethod def SlewToCoordinates(self, RightAscension, Declination): + """ + Move the telescope to the specified equatorial coordinates. + + Slew may fail if target is beyond limits imposed within the driver component. These limits + could be mechanical constraints by the mount or instruments, safety restrictions from the + building or dome enclosure, etc. + `TargetRightAscension` and `TargetDeclination` will set to the specified coordinates, irrelevant of + whether the slew was successful. + + Parameters + ---------- + RightAscension : `float` + Right ascension coordinate in hours. + Declination : `float` + Declination coordinate in degrees. + """ pass @abstractmethod def SlewToCoordinatesAsync(self, RightAscension, Declination): + """ + Move the telescope to the specified equatorial coordinates asynchronously. + + Returns immediately after starting the slew. Slew may fail if target is beyond limits imposed within the driver component. + These limits could be mechanical constraints by the mount or instruments, safety restrictions from the building or dome enclosure, etc. + `TargetRightAscension` and `TargetDeclination` will set to the specified coordinates, irrelevant of whether the slew was successful. + + Parameters + ---------- + RightAscension : `float` + Right ascension coordinate in hours. + Declination : `float` + Declination coordinate in degrees. + """ pass @abstractmethod def SlewToTarget(self): + """ + Move the telescope to the `TargetRightAscension` and `TargetDeclination` coordinates. + + Slew may fail if target is beyond limits imposed within the driver component. These limits + could be mechanical constraints by the mount or instruments, safety restrictions from the + building or dome enclosure, etc. + """ pass @abstractmethod def SlewToTargetAsync(self): + """ + Move the telescope to the `TargetRightAscension` and `TargetDeclination` coordinates asynchronously. + + Returns immediately after starting the slew. Slew may fail if target is beyond limits imposed within the driver component. + These limits could be mechanical constraints by the mount or instruments, safety restrictions from the building or dome enclosure, etc. + """ pass @abstractmethod def SyncToAltAz(self, Azimuth, Altitude): + """ + Synchronizes the telescope's current local horizontal coordinates to the specified Alt/Az coordinates. + + Parameters + ---------- + Azimuth : `float` + Azimuth coordinate in degrees, North-referenced, CW. + Altitude : `float` + Altitude coordinate in degrees. + """ pass @abstractmethod def SyncToCoordinates(self, RightAscension, Declination): + """ + Synchronizes the telescope's current equatorial coordinates to the specified RA/Dec coordinates. + + Parameters + ---------- + RightAscension : `float` + Right ascension coordinate in hours. + Declination : `float` + Declination coordinate in degrees. + """ pass @abstractmethod def SyncToTarget(self): + """ + Synchronizes the telescope's current equatorial coordinates to the `TargetRightAscension` and `TargetDeclination` coordinates. + + This method should only be used to improve the pointing accuracy of the telescope for positions close + to the position at which the sync is done, since Sync is mount-dependent. + """ pass @abstractmethod def Unpark(self): + """Takes the telescope out of the `Parked` state.""" pass @property @abstractmethod def AlignmentMode(self): + """ + The alignment mode of the telescope. (`enum`) + + See below for example values. + * 0 : Altitude-Azimuth alignment. + * 1 : Polar (equatorial) mount alignment, NOT German. + * 2 : German equatorial mount alignment. + """ pass @AlignmentMode.setter @@ -97,116 +346,174 @@ def AlignmentMode(self, value): @property @abstractmethod def Altitude(self): + """Altitude above the horizon of the telescope's current position in degrees. (`float`)""" pass @property @abstractmethod def ApertureArea(self): + """Area of the telescope's aperture in square meters, accounting for obstructions. (`float`)""" pass @property @abstractmethod def ApertureDiameter(self): + """Effective diameter of the telescope's aperture in meters. (`float`)""" pass @property @abstractmethod def AtHome(self): + """ + Whether the telescope is at the home position. (`bool`) + + Only `True` after a successful `FindHome` call, and `False` after any slewing operation. + Alternatively, `False` if the telescope is not capable of homing. + """ pass @property @abstractmethod def AtPark(self): + """ + Whether the telescope is in the `Parked` state. (`bool`) + + See `Park` and `Unpark` for more information. + Setting to `True` or `False` should only be done by those methods respectively. + While `True`, no movement commands should be accepted except `Unpark`, and an attempt + to do so should raise an error. + """ pass @property @abstractmethod def Azimuth(self): + """Azimuth of the telescope's current position in degrees, North-referenced, CW. (`float`)""" pass @property @abstractmethod def CanFindHome(self): + """Whether the telescope is capable of finding its home position with `FindHome`. (`bool`)""" pass @property @abstractmethod def CanPark(self): + """Whether the telescope is capable of parking with `Park`. (`bool`)""" pass @property @abstractmethod def CanPulseGuide(self): + """Whether the telescope is capable of pulse guiding with `PulseGuide`. (`bool`)""" pass @property @abstractmethod def CanSetDeclinationRate(self): + """Whether the telescope is capable of setting the declination rate with `DeclinationRate` for offset tracking. (`bool`)""" pass @property @abstractmethod def CanSetGuideRates(self): + """Whether the telescope is capable of setting the guide rates with `GuideRateRightAscension` and `GuideRateDeclination`. (`bool`)""" pass @property @abstractmethod def CanSetPark(self): + """Whether the telescope is capable of setting the park position with `SetPark`. (`bool`)""" pass @property @abstractmethod def CanSetPierSide(self): + """ + Whether the telescope is capable of setting the side of the pier with `SideOfPier`, i.e. the mount can be forced to flip. (`bool`) + + This is only relevant for German equatorial mounts, as non-Germans do not have to be flipped + and should therefore have `CansetPierSide` `False`. + """ pass @property @abstractmethod def CanSetRightAscensionRate(self): + """Whether the telescope is capable of setting the right ascension rate with `RightAscensionRate` for offset tracking. (`bool`)""" pass @property @abstractmethod def CanSetTracking(self): + """Whether the telescope is capable of setting the tracking state with `Tracking`. (`bool`)""" pass @property @abstractmethod def CanSlew(self): + """ + Whether the telescope is capable of slewing with `SlewToCoordinates`, or `SlewToTarget`. (`bool`) + + A `True` only guarantees that the synchronous slews are possible. + Asynchronous slew capabilies are determined by `CanSlewAsync`. + """ pass @property @abstractmethod def CanSlewAltAz(self): + """ + Whether the telescope is capable of slewing to Alt/Az coordinates with `SlewToAltAz`. (`bool`) + + A `True` only guarantees that the synchronous local horizontal slews are possible. + Asynchronous slew capabilies are determined by `CanSlewAltAzAsync`. + """ pass @property @abstractmethod def CanSlewAltAzAsync(self): + """ + Whether the telescope is capable of slewing to Alt/Az coordinates asynchronously with `SlewToAltAzAsync`. (`bool`) + + A `True` also guarantees that the synchronous local horizontal slews are possible. + """ pass @property @abstractmethod def CanSlewAsync(self): + """ + Whether the telescope is capable of slewing asynchronously with `SlewToCoordinatesAsync`, `SlewToAltAzAsync`, or `SlewToTargetAsync`. (`bool`) + + A `True` also guarantees that the synchronous slews are possible. + """ pass @property @abstractmethod def CanSync(self): + """Whether the telescope is capable of syncing with `SyncToCoordinates` or `SyncToTarget`. (`bool`)""" pass @property @abstractmethod def CanSyncAltAz(self): + """Whether the telescope is capable of syncing to Alt/Az coordinates with `SyncToAltAz`. (`bool`)""" pass @property @abstractmethod def CanUnpark(self): + """Whether the telescope is capable of unparking with `Unpark`. (`bool`)""" pass @property @abstractmethod def Declination(self): + """Declination of the telescope's current position, in the coordinate system given by `EquatorialSystem`, in degrees. (`float`)""" pass @Declination.setter @@ -217,6 +524,14 @@ def Declination(self, value): @property @abstractmethod def DeclinationRate(self): + """ + Declination tracking rate in arcseconds per second. (`float`) + + This, in conjunction with `RightAscensionRate`, is primarily used for offset tracking, + tracking objects that move relatively slowly against the equatorial coordinate system. + The supported range is telescope-dependent, but it can be expected to be a range + sufficient for guiding error corrections. + """ pass @DeclinationRate.setter @@ -227,6 +542,7 @@ def DeclinationRate(self, value): @property @abstractmethod def DoesRefraction(self): + """Whether the telescope applies atmospheric refraction to coordinates. (`bool`)""" pass @DoesRefraction.setter @@ -237,16 +553,42 @@ def DoesRefraction(self, value): @property @abstractmethod def EquatorialSystem(self): + """ + Equatorial coordinate system used by the telescope. (`enum`) + + See below for example values. + * 0 : Custom/unknown equinox and/or reference frame. + * 1 : Topocentric coordinates. Coordinates at the current date, allowing for annual aberration and precession-nutation. Most common for amateur telescopes. + * 2 : J2000 equator and equinox. Coordinates at the J2000 epoch, ICRS reference frame. Most common for professional telescopes. + * 3 : J2050 equator and equinox. Coordinates at the J2050 epoch, ICRS reference frame. + * 4 : B1950 equinox. Coordinates at the B1950 epoch, FK4 reference frame. + """ pass @property @abstractmethod def FocalLength(self): + """ + Focal length of the telescope in meters. (`float`) + + May be used by clients to calculate the field of view and plate scale of the telescope + in combination with detector pixel size and geometry. + """ pass @property @abstractmethod def GuideRateDeclination(self): + """ + Current declination movement rate offset in degrees per second. (`float`) + + This is the rate for both hardware guiding and the `PulseGuide` method. + The supported range is telescope-dependent, but it can be expected to be a range + sufficient for guiding error corrections. + If the telescope is incapable of separate guide rates for RA and Dec, this property + and `GuideRateRightAscension` may be tied together; changing one property will change the other. + Mounts must start up with a default rate, and this property must return that rate until changed. + """ pass @GuideRateDeclination.setter @@ -257,6 +599,16 @@ def GuideRateDeclination(self, value): @property @abstractmethod def GuideRateRightAscension(self): + """ + Current right ascension movement rate offset in degrees per second. (`float`) + + This is the rate for both hardware guiding and the `PulseGuide` method. + The supported range is telescope-dependent, but it can be expected to be a range + sufficient for guiding error corrections. + If the telescope is incapable of separate guide rates for RA and Dec, this property + and `GuideRateDeclination` may be tied together; changing one property will change the other. + Mounts must start up with a default rate, and this property must return that rate until changed. + """ pass @GuideRateRightAscension.setter @@ -267,16 +619,30 @@ def GuideRateRightAscension(self, value): @property @abstractmethod def IsPulseGuiding(self): + """Whether the telescope is currently pulse guiding using `PulseGuide`. (`bool`)""" pass @property @abstractmethod def RightAscension(self): + """Right ascension of the telescope's current position, in the coordinate system given by `EquatorialSystem`, in hours. (`float`)""" pass @property @abstractmethod def RightAscensionRate(self): + """ + Right ascension tracking rate in seconds per sidereal second. (`float`) + + This, in conjunction with `DeclinationRate`, is primarily used for offset tracking, + tracking objects that move relatively slowly against the equatorial coordinate system. + The supported range is telescope-dependent, but it can be expected to be a range + sufficient for guiding error corrections. + + Notes + ----- + To convet to sidereal seconds per UTC second, multiply by 0.9972695677. + """ pass @RightAscensionRate.setter @@ -287,6 +653,19 @@ def RightAscensionRate(self, value): @property @abstractmethod def SideOfPier(self): + """ + The pointing state of the mount. (`enum`) + + See below for example values. + * 0 : Normal pointing state, mount on East side of pier, looking West. + * 1 : Through the pole pointing state, mount on West side of pier, looking East. + * -1 : Unknown or indeterminate. + + .. warning:: + The name of this property is misleading and does not reflect the true meaning of the property. + The name will not change out of preservation of compatibility. + For more information, see the 'Remarks' section of `this page `_. + """ pass @SideOfPier.setter @@ -297,11 +676,18 @@ def SideOfPier(self, value): @property @abstractmethod def SiderealTime(self): + """ + Local apparent sidereal time from the telescope's internal clock in hours. (`float`) + + If the telesope has no sidereal time capability, this property should be calculated from the system + clock by the driver. + """ pass @property @abstractmethod def SiteElevation(self): + """Elevation of the telescope's site above sea level in meters. (`float`)""" pass @SiteElevation.setter @@ -312,6 +698,7 @@ def SiteElevation(self, value): @property @abstractmethod def SiteLatitude(self): + """Latitude (geodetic) of the telescope's site in degrees. (`float`)""" pass @SiteLatitude.setter @@ -322,16 +709,32 @@ def SiteLatitude(self, value): @property @abstractmethod def SiteLongitude(self): + """Longitude (geodetic) of the telescope's site in degrees. (`float`)""" pass @property @abstractmethod def Slewing(self): + """ + Whether the telescope is currently slewing due to one of the Slew methods or `MoveAxis`. (`bool`) + + Telescopes incapable of asynchronous slewing will always have this property be `False`. + Slewing for the purpose of this property excludes motion by sidereal tracking, pulse guiding, + `RightAscensionRate`, and `DeclinationRate`. + Only Slew commands, flipping due to `SideOfPier`, and `MoveAxis` should set this property to `True`. + """ pass @property @abstractmethod def SlewSettleTime(self): + """ + A set post-slew settling time in seconds. (`int`) + + This adds additional time to the end of all slew operations. + In practice, slew methods will not return, and `Slewing` will not be `False`, until the + operation has ended plus this time has elapsed. + """ pass @SlewSettleTime.setter @@ -342,6 +745,7 @@ def SlewSettleTime(self, value): @property @abstractmethod def TargetDeclination(self): + """Declination coordinate in degrees of the telescope's current slew or sync target. (`float`)""" pass @TargetDeclination.setter @@ -352,6 +756,7 @@ def TargetDeclination(self, value): @property @abstractmethod def TargetRightAscension(self): + """Right ascension coordinate in hours of the telescope's current slew or sync target. (`float`)""" pass @TargetRightAscension.setter @@ -362,6 +767,13 @@ def TargetRightAscension(self, value): @property @abstractmethod def Tracking(self): + """ + State of the telescope's sidereal tracking drive. (`bool`) + + Changing of this property will turn sidereal drive on and off. + Some telescopes may not support changing of this property, and thus + may not support turning tracking on and off. See `CanSetTracking`. + """ pass @Tracking.setter @@ -372,6 +784,21 @@ def Tracking(self, value): @property @abstractmethod def TrackingRate(self): + """ + Current tracking rate of the telescope's sidereal drive. (`enum`) + + Supported rates are contained in `TrackingRates`, and the property's rate + is guaranteed to be one of those. + If the mount's current tracking rate cannot be read, the driver should force + and report a default rate on connection. + In this circumstance, a default of `Sidereal` rate is preferred. + + See below for example values. + * 0 : Sidereal tracking, 15.041 arcseconds per second. + * 1 : Lunar tracking, 14.685 arcseconds per second. + * 2 : Solar tracking, 15.0 arcseconds per second. + * 3 : King tracking, 15.0369 arcseconds per second. + """ pass @TrackingRate.setter @@ -382,11 +809,37 @@ def TrackingRate(self, value): @property @abstractmethod def TrackingRates(self): + """ + Collection of supported tracking rates for the telescope's sidereal drive. (`object`) + + This collection should contain all supported tracking rates. At a minimum, it should contain + an item for the default `Sidereal` rate. The collection should be an iterable, and should + include properties for the number of rates, and the actual rates, and methods for returning + an enumerator for the rates, and for disposing of the object as a whole. + + See below for an example collection. + * 0 : Sidereal tracking, 15.041 arcseconds per second. + * 1 : Lunar tracking, 14.685 arcseconds per second. + * 2 : Solar tracking, 15.0 arcseconds per second. + * 3 : King tracking, 15.0369 arcseconds per second. + """ pass @property @abstractmethod def UTCDate(self): + """ + UTC date and time of the telescope's internal clock. (`datetime`) + + If the telescope has no UTC time capability, this property should be calculated from the system + clock by the driver. + If the telescope does have UTC measuring capability, changing of its internal UTC clock is permitted, + for instance for allowing clients to adjust for accuracy. + + .. warning:: + If the telescope has no UTC time capability, do NOT under any circumstances implement the property + to be writeable, as it would change the system clock. + """ pass @UTCDate.setter From d301b4d44eadbb06ce45f209bd494191cb5ee32c Mon Sep 17 00:00:00 2001 From: ccolin Date: Tue, 3 Dec 2024 10:29:51 -0600 Subject: [PATCH 05/11] autofocus docs --- pyscope/observatory/autofocus.py | 41 +++++++++++++++++++++++++++ pyscope/observatory/maxim.py | 27 ++++++++++++++++++ pyscope/observatory/pwi4_autofocus.py | 31 ++++++++++++++++++++ pyscope/observatory/pwi_autofocus.py | 24 ++++++++++++++++ 4 files changed, 123 insertions(+) diff --git a/pyscope/observatory/autofocus.py b/pyscope/observatory/autofocus.py index 1876f538..f8f60411 100644 --- a/pyscope/observatory/autofocus.py +++ b/pyscope/observatory/autofocus.py @@ -6,12 +6,53 @@ class Autofocus(ABC, metaclass=_DocstringInheritee): @abstractmethod def __init__(self, *args, **kwargs): + """ + Abstract base class for autofocus functionality on different utility platforms. + + This class provides a common interface for autofocus functionality, including the + bare minimum for any platform such as initialization, running the autofocus routine, + and aborting the autofocus routine. + Example platforms include Maxim, PWI, PWI4, etc. + Args and kwargs provide needed parameters to the platform-specific autofocus routine, + such as hosting protocol and port. + + Parameters + ---------- + *args : `tuple` + Variable length argument list. + **kwargs : `dict` + Arbitrary keyword arguments. + """ pass @abstractmethod def Run(self, *args, **kwargs): + """ + Run the autofocus routine on the given platform. + Args and kwargs provide needed parameters to the platform-specific autofocus routine, + such as exposure time and timeout. + + Parameters + ---------- + *args : `tuple` + Variable length argument list. + **kwargs : `dict` + Arbitrary keyword arguments. + """ pass @abstractmethod def Abort(self, *args, **kwargs): + """ + Abort the autofocus routine on the given platform. + Whether aborting is immediate or has a gracious exit process is platform-specific. + Args and kwargs usually should not be needed for an abort. + + Parameters + ---------- + *args : `tuple` + Variable length argument list. + **kwargs : `dict` + Arbitrary keyword arguments. + """ pass diff --git a/pyscope/observatory/maxim.py b/pyscope/observatory/maxim.py index 80ce82e1..77eb3479 100755 --- a/pyscope/observatory/maxim.py +++ b/pyscope/observatory/maxim.py @@ -66,10 +66,34 @@ def camera(self): class _MaximAutofocus(Autofocus): def __init__(self, maxim): + """ + Autofocus class for Maxim DL. + + This class provides an interface for running and aborting the autofocus routine in Maxim DL. + + Parameters + ---------- + maxim : `Maxim` + The Maxim DL object. + """ logger.debug("_MaximAutofocus.MaximAutofocus __init__ called") self.maxim = maxim def Run(self, exposure=10): + """ + Run the autofocus routine in Maxim DL. + Only returns once the autofocus routine is complete. + + Parameters + ---------- + exposure : `int`, default : 10, optional + The exposure time in seconds for the autofocus routine. + + Returns + ------- + `bool` + `True` if the autofocus routine was successful, `False` if it failed. + """ logger.debug(f"Run called with exposure={exposure}") self.maxim.Autofocus(exposure) @@ -82,6 +106,9 @@ def Run(self, exposure=10): return False def Abort(self): + """ + Abort the autofocus routine in Maxim DL. + """ logger.debug("_MaximAutofocus.Abort called") raise NotImplementedError diff --git a/pyscope/observatory/pwi4_autofocus.py b/pyscope/observatory/pwi4_autofocus.py index fad2feef..8325bac0 100644 --- a/pyscope/observatory/pwi4_autofocus.py +++ b/pyscope/observatory/pwi4_autofocus.py @@ -9,11 +9,39 @@ class PWI4Autofocus(Autofocus): def __init__(self, host="localhost", port=8220): + """ + Autofocus class for the PlaneWave Interface 4 (PWI4) utility platform. + + This class provides an interface for autofocus running, and aborting on the PWI4 platform. + + Parameters + ---------- + host : `str`, default : "localhost", optional + The host of the PWI4 server. + port : `int`, default : 8220, optional + The port of the PWI4 server. + """ self._host = host self._port = port self._app = _PWI4(host=self._host, port=self._port) def Run(self, *args, **kwargs): + """ + Run the autofocus routine on the PWI4 platform. + Only returns once the autofocus routine is complete. + + Parameters + ---------- + *args : `tuple` + Variable length argument list. + **kwargs : `dict` + Arbitrary keyword arguments. + + Returns + ------- + `float` + The best focuser position found by the autofocus routine. + """ logger.debug("Starting autofocus in PWI4Autofocus") self._app.request("/autofocus/start") logger.info("Autofocus started") @@ -30,5 +58,8 @@ def Run(self, *args, **kwargs): return self._app.status().autofocus.best_position def Abort(self): + """ + Abort the autofocus routine on the PWI4 platform. + """ logger.debug("Aborting autofocus in PWI4Autofocus") _ = self._app.focuser_stop() diff --git a/pyscope/observatory/pwi_autofocus.py b/pyscope/observatory/pwi_autofocus.py index db86e486..11d28896 100755 --- a/pyscope/observatory/pwi_autofocus.py +++ b/pyscope/observatory/pwi_autofocus.py @@ -9,6 +9,11 @@ class PWIAutofocus(Autofocus): def __init__(self): + """ + Autofocus class for PlaneWave Instruments focuser. + + This class provides an interface for autofocus running, and aborting on the PlaneWave Instruments focuser. + """ logger.debug("PWIAutofocus.__init__ called") if platform.system() != "Windows": raise Exception("This class is only available on Windows.") @@ -33,6 +38,22 @@ def __init__(self): self._com_object.PreventFilterChange = True def Run(self, exposure=10, timeout=120): + """ + Run the autofocus routine on the PlaneWave Instruments focuser. + Only returns once the autofocus routine has completed. + + Parameters + ---------- + exposure : `float`, default : 10, optional + Exposure time in seconds for the autofocus routine. + timeout : `float`, default : 120, optional + Timeout in seconds for the autofocus routine to complete. + + Returns + ------- + `float` or `None` + The best position found by the autofocus routine, or None if the autofocus routine failed. + """ logger.debug( f"PWIAutofocus.Run called with args: exposure={exposure}, timeout={timeout}" ) @@ -61,6 +82,9 @@ def Run(self, exposure=10, timeout=120): return None def Abort(self): + """ + Abort the autofocus routine on the PlaneWave Instruments focuser. + """ logger.debug("PWIAutofocus.Abort called") self._com_object.StopAutofocus From 15a4a3feed87148c3ba40aa3ecfd68134dce380e Mon Sep 17 00:00:00 2001 From: ccolin Date: Tue, 3 Dec 2024 10:46:00 -0600 Subject: [PATCH 06/11] abstract doc sentence consistency fix --- pyscope/observatory/camera.py | 2 +- pyscope/observatory/switch.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyscope/observatory/camera.py b/pyscope/observatory/camera.py index 51c36b35..af9530db 100644 --- a/pyscope/observatory/camera.py +++ b/pyscope/observatory/camera.py @@ -7,7 +7,7 @@ class Camera(ABC, metaclass=_DocstringInheritee): @abstractmethod def __init__(self, *args, **kwargs): """ - Abstract class for camera devices. + Abstract base class for camera devices. The class defines the interface for camera devices, including methods for controlling exposures, guiding, and retrieving camera properties. Subclasses must implement diff --git a/pyscope/observatory/switch.py b/pyscope/observatory/switch.py index 980982a8..6978e03c 100644 --- a/pyscope/observatory/switch.py +++ b/pyscope/observatory/switch.py @@ -7,7 +7,7 @@ class Switch(ABC, metaclass=_DocstringInheritee): @abstractmethod def __init__(self, *args, **kwargs): """ - Abstract class for switch devices. + Abstract base class for switch devices. This class defines the common interface for switch devices, including methods for getting and setting switch values, and getting switch names and descriptions. From 9362156ea46b1da9692735fed9df2ee3fa287c26 Mon Sep 17 00:00:00 2001 From: ccolin Date: Tue, 3 Dec 2024 11:10:56 -0600 Subject: [PATCH 07/11] safety monitor docs --- pyscope/observatory/ascom_safety_monitor.py | 19 +++++++++++++++ pyscope/observatory/html_safety_monitor.py | 27 +++++++++++++++++++++ pyscope/observatory/safety_monitor.py | 15 ++++++++++++ 3 files changed, 61 insertions(+) diff --git a/pyscope/observatory/ascom_safety_monitor.py b/pyscope/observatory/ascom_safety_monitor.py index edae555d..90e46787 100644 --- a/pyscope/observatory/ascom_safety_monitor.py +++ b/pyscope/observatory/ascom_safety_monitor.py @@ -8,6 +8,25 @@ class ASCOMSafetyMonitor(ASCOMDevice, SafetyMonitor): def __init__(self, identifier, alpaca=False, device_number=0, protocol="http"): + """ + ASCOM implementation of the SafetyMonitor base class. + + This class provides an interface to ASCOM-compatible safety monitors, + allowing the observatory to check if weather, power, and other + observatory-specific conditions allow safe usage of observatory equipment, + such as opening the roof or dome. + + Parameters + ---------- + identifier : `str` + The device identifier. + alpaca : `bool`, default : `False`, optional + Whether the device is an Alpaca device. + device_number : `int`, default : 0, optional + The device number. + protocol : `str`, default : "http", optional + The device communication protocol. + """ super().__init__( identifier, alpaca=alpaca, diff --git a/pyscope/observatory/html_safety_monitor.py b/pyscope/observatory/html_safety_monitor.py index 1921dab1..a0d13ada 100755 --- a/pyscope/observatory/html_safety_monitor.py +++ b/pyscope/observatory/html_safety_monitor.py @@ -9,6 +9,24 @@ class HTMLSafetyMonitor(SafetyMonitor): def __init__(self, url, check_phrase=b"ROOFPOSITION=OPEN"): + """ + HTML implementation of the SafetyMonitor base class. + + This class provides an interface to access safety monitors, + doing so using data fetched from a URL. This allows the observatory + to check if weather, power, and other observatory-specific conditions + allow safe usage of observatory equipment, such as opening the roof or dome. + Other than the method `IsSafe`, this class also provides the properties + for information about the driver, about the interface, description, and name. + + Parameters + ---------- + url : `str` + The URL to fetch data from. + check_phrase : `bytes`, default : b"ROOFPOSITION=OPEN", optional + The phrase to check for in the data fetched from the URL. + In the default case, we would check if it is safe to open the roof. + """ logger.debug( f"""HTMLSafetyMonitor.__init__( {url}, check_phrase={check_phrase}) called""" @@ -19,6 +37,9 @@ def __init__(self, url, check_phrase=b"ROOFPOSITION=OPEN"): @property def IsSafe(self): + """ + Whether the observatory equipment/action specified in the constructor's `check_phrase` is safe to use. (`bool`) + """ logger.debug(f"""HTMLSafetyMonitor.IsSafe property called""") safe = None @@ -47,30 +68,36 @@ def IsSafe(self): @property def DriverVersion(self): + """Version of the driver. (`str`)""" logger.debug(f"""HTMLSafetyMonitor.DriverVersion property called""") return "1.0" @property def DriverInfo(self): + """Information about the driver. (`str`)""" logger.debug(f"""HTMLSafetyMonitor.DriverInfo property called""") return "HTML Safety Monitor" @property def InterfaceVersion(self): + """Version of the interface. (`str`)""" logger.debug(f"""HTMLSafetyMonitor.InterfaceVersion property called""") return "1.0" @property def Description(self): + """Description of the driver. (`str`)""" logger.debug(f"""HTMLSafetyMonitor.Description property called""") return "HTML Safety Monitor" @property def SupportedActions(self): + """List of supported actions. (`list`)""" logger.debug(f"""HTMLSafetyMonitor.SupportedActions property called""") return [] @property def Name(self): + """Name of the driver/url. (`str`)""" logger.debug(f"""HTMLSafetyMonitor.Name property called""") return self._url diff --git a/pyscope/observatory/safety_monitor.py b/pyscope/observatory/safety_monitor.py index cef470b9..94e91453 100644 --- a/pyscope/observatory/safety_monitor.py +++ b/pyscope/observatory/safety_monitor.py @@ -6,9 +6,24 @@ class SafetyMonitor(ABC, metaclass=_DocstringInheritee): @abstractmethod def __init__(self, *args, **kwargs): + """ + Abstract base class for safety monitors. + + This class defines the common interface for safety monitors, a way to check if + weather, power, and other observatory-specific conditions allow safe usage of + observatory equipment. Subclasses must implement the abstract method in this class. + + Parameters + ---------- + *args : `tuple` + Variable length argument list. + **kwargs : `dict` + Arbitrary keyword arguments. + """ pass @property @abstractmethod def IsSafe(self): + """Whether the observatory is safe for use. (`bool`)""" pass From ae4025992d6122b005c1da4c4a8c9889130ca634 Mon Sep 17 00:00:00 2001 From: ccolin Date: Tue, 3 Dec 2024 11:26:39 -0600 Subject: [PATCH 08/11] simulator server docs --- pyscope/observatory/simulator_server.py | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pyscope/observatory/simulator_server.py b/pyscope/observatory/simulator_server.py index b93f7f95..abc59750 100644 --- a/pyscope/observatory/simulator_server.py +++ b/pyscope/observatory/simulator_server.py @@ -12,6 +12,33 @@ class SimulatorServer: def __init__(self, force_update=False): + """ + Class for starting the ASCOM Alpaca Simulators server. + + This classhandles downloading, extracting, and launching the ASCOM Alpaca Simulators + server executable appropriate for the host's operating system and architecture. + Ensures correct version is downloaded and forces updating if specified. + + Parameters + ---------- + force_update : bool, default : `False`, optional + If `True`, forces download of the ASCOM Alpaca Simulators server executable. + If `False`, checks if the executable exists and skips download if it does. + + Raises + ------ + Exception + If the host's operating system is not supported. + + Notes + ----- + The server executable is downloaded from the ASCOM Initiative GitHub repository found `here `_, + and is from the latest release version of v0.3.1. + Currently supported operating systems are: + - macOS (Darwin) + - Linux (x86_64, armhf, aarch64) + - Windows (x86, x64) + """ if platform.system() == "Darwin": sys_name = "macos-x64" zip_type = ".zip" @@ -86,6 +113,9 @@ def __init__(self, force_update=False): os.chdir(current_dir) def __del__(self): + """ + Automatically kills the server process when the object is deleted. + """ if platform.system() == "Darwin" or platform.system() == "Linux": # self.process.kill() # doesn't work since sudo is needed subprocess.Popen( From aae198f5b962dada4441970c0b132eeeaed3c278 Mon Sep 17 00:00:00 2001 From: ccolin Date: Tue, 3 Dec 2024 17:34:48 -0600 Subject: [PATCH 09/11] sphinx bulletpoint format fix --- pyscope/observatory/ascom_telescope.py | 90 +++++++++++++------------- pyscope/observatory/telescope.py | 90 +++++++++++++------------- 2 files changed, 90 insertions(+), 90 deletions(-) diff --git a/pyscope/observatory/ascom_telescope.py b/pyscope/observatory/ascom_telescope.py index 68dcbdbc..7ff1b5ae 100644 --- a/pyscope/observatory/ascom_telescope.py +++ b/pyscope/observatory/ascom_telescope.py @@ -48,10 +48,10 @@ def AxisRates(self, Axis): Parameters ---------- Axis : `TelescopeAxes `_ - The axis about which the telescope may be moved. See below for ASCOM standard. - * 0 : Primary axis, Right Ascension or Azimuth. - * 1 : Secondary axis, Declination or Altitude. - * 2 : Tertiary axis, imager rotators. + The axis about which the telescope may be moved. See below for ASCOM standard: + * 0 : Primary axis, Right Ascension or Azimuth. + * 1 : Secondary axis, Declination or Altitude. + * 2 : Tertiary axis, imager rotators. Returns ------- @@ -80,10 +80,10 @@ def CanMoveAxis(self, Axis): Parameters ---------- Axis : `TelescopeAxes `_ - The axis about which the telescope may be moved. See below for ASCOM standard. - * 0 : Primary axis, Right Ascension or Azimuth. - * 1 : Secondary axis, Declination or Altitude. - * 2 : Tertiary axis, imager rotators. + The axis about which the telescope may be moved. See below for ASCOM standard: + * 0 : Primary axis, Right Ascension or Azimuth. + * 1 : Secondary axis, Declination or Altitude. + * 2 : Tertiary axis, imager rotators. """ logger.debug(f"ASCOMTelescope.CanMoveAxis({Axis}) called") return self._device.CanMoveAxis(Axis) @@ -102,10 +102,10 @@ def DestinationSideOfPier(self, RightAscension, Declination): Returns ------- `PierSide `_ - The side of the pier on which the telescope will be after slewing to the specified coordinates. See below for ASCOM standard. - * 0 : Normal pointing state, mount on East side of pier, looking West. - * 1 : Through the pole pointing state, mount on West side of pier, looking East. - * -1 : Unknown or indeterminate. + The side of the pier on which the telescope will be after slewing to the specified coordinates. See below for ASCOM standard: + * 0 : Normal pointing state, mount on East side of pier, looking West. + * 1 : Through the pole pointing state, mount on West side of pier, looking East. + * -1 : Unknown or indeterminate. """ logger.debug( f"ASCOMTelescope.DestinationSideOfPier({RightAscension}, {Declination}) called" @@ -129,10 +129,10 @@ def MoveAxis(self, Axis, Rate): Parameters ---------- Axis : `TelescopeAxes `_ - The axis about which the telescope may be moved. See below for ASCOM standard. - * 0 : Primary axis, Right Ascension or Azimuth. - * 1 : Secondary axis, Declination or Altitude. - * 2 : Tertiary axis, imager rotators. + The axis about which the telescope may be moved. See below for ASCOM standard: + * 0 : Primary axis, Right Ascension or Azimuth. + * 1 : Secondary axis, Declination or Altitude. + * 2 : Tertiary axis, imager rotators. Rate : `float` Rate of motion in degrees per second. Positive values indicate motion in one direction, negative values in the opposite direction, and 0.0 stops motion by this method and resumes tracking motion. @@ -176,11 +176,11 @@ def PulseGuide(self, Direction, Duration): Parameters ---------- Direction : `GuideDirections `_ - Direction in which to move the telescope. See below for ASCOM standard. - * 0 : North or up. - * 1 : South or down. - * 2 : East or right. - * 3 : West or left. + Direction in which to move the telescope. See below for ASCOM standard: + * 0 : North or up. + * 1 : South or down. + * 2 : East or right. + * 3 : West or left. Duration : `int` Time in milliseconds for which to pulse the guide. Must be a positive non-zero value. """ @@ -254,10 +254,10 @@ def AlignmentMode(self): """ The alignment mode of the telescope. (`AlignmentModes `_) - See below for ASCOM standard. - * 0 : Altitude-Azimuth alignment. - * 1 : Polar (equatorial) mount alignment, NOT German. - * 2 : German equatorial mount alignment. + See below for ASCOM standard: + * 0 : Altitude-Azimuth alignment. + * 1 : Polar (equatorial) mount alignment, NOT German. + * 2 : German equatorial mount alignment. """ logger.debug("ASCOMTelescope.AlignmentMode property accessed") return self._device.AlignmentMode @@ -409,12 +409,12 @@ def EquatorialSystem(self): """ Equatorial coordinate system used by the telescope. (`EquatorialCoordinateType `_) - See below for ASCOM standard. - * 0 : Custom/unknown equinox and/or reference frame. - * 1 : Topocentric coordinates. Coordinates at the current date, allowing for annual aberration and precession-nutation. Most common for amateur telescopes. - * 2 : J2000 equator and equinox. Coordinates at the J2000 epoch, ICRS reference frame. Most common for professional telescopes. - * 3 : J2050 equator and equinox. Coordinates at the J2050 epoch, ICRS reference frame. - * 4 : B1950 equinox. Coordinates at the B1950 epoch, FK4 reference frame. + See below for ASCOM standard: + * 0 : Custom/unknown equinox and/or reference frame. + * 1 : Topocentric coordinates. Coordinates at the current date, allowing for annual aberration and precession-nutation. Most common for amateur telescopes. + * 2 : J2000 equator and equinox. Coordinates at the J2000 epoch, ICRS reference frame. Most common for professional telescopes. + * 3 : J2050 equator and equinox. Coordinates at the J2050 epoch, ICRS reference frame. + * 4 : B1950 equinox. Coordinates at the B1950 epoch, FK4 reference frame. """ logger.debug("ASCOMTelescope.EquatorialSystem property accessed") return self._device.EquatorialSystem @@ -469,10 +469,10 @@ def SideOfPier(self): """ The pointing state of the mount. (`PierSide `_) - See below for ASCOM standard. - * 0 : Normal pointing state, mount on East side of pier, looking West. - * 1 : Through the pole pointing state, mount on West side of pier, looking East. - * -1 : Unknown or indeterminate. + See below for ASCOM standard: + * 0 : Normal pointing state, mount on East side of pier, looking West. + * 1 : Through the pole pointing state, mount on West side of pier, looking East. + * -1 : Unknown or indeterminate. .. warning:: The name of this property is misleading and does not reflect the true meaning of the property. @@ -578,11 +578,11 @@ def TrackingRate(self): and report a default rate on connection. In this circumstance, a default of `Sidereal` rate is preferred. - See below for ASCOM standard. - * 0 : Sidereal tracking, 15.041 arcseconds per second. - * 1 : Lunar tracking, 14.685 arcseconds per second. - * 2 : Solar tracking, 15.0 arcseconds per second. - * 3 : King tracking, 15.0369 arcseconds per second. + See below for ASCOM standard: + * 0 : Sidereal tracking, 15.041 arcseconds per second. + * 1 : Lunar tracking, 14.685 arcseconds per second. + * 2 : Solar tracking, 15.0 arcseconds per second. + * 3 : King tracking, 15.0369 arcseconds per second. """ logger.debug("ASCOMTelescope.TrackingRate property accessed") return self._device.TrackingRate @@ -602,11 +602,11 @@ def TrackingRates(self): properties for the number of rates, and the actual rates, and methods for returning an enumerator for the rates, and for disposing of the object as a whole. - See below for ASCOM standard expected collection. - * 0 : Sidereal tracking, 15.041 arcseconds per second. - * 1 : Lunar tracking, 14.685 arcseconds per second. - * 2 : Solar tracking, 15.0 arcseconds per second. - * 3 : King tracking, 15.0369 arcseconds per second + See below for ASCOM standard expected collection: + * 0 : Sidereal tracking, 15.041 arcseconds per second. + * 1 : Lunar tracking, 14.685 arcseconds per second. + * 2 : Solar tracking, 15.0 arcseconds per second. + * 3 : King tracking, 15.0369 arcseconds per second """ logger.debug("ASCOMTelescope.TrackingRates property accessed") return self._device.TrackingRates diff --git a/pyscope/observatory/telescope.py b/pyscope/observatory/telescope.py index 3b0147ce..33e57673 100644 --- a/pyscope/observatory/telescope.py +++ b/pyscope/observatory/telescope.py @@ -43,10 +43,10 @@ def AxisRates(self, Axis): Parameters ---------- Axis : `enum` - The axis about which the telescope may be moved. See below for example values. - * 0 : Primary axis, usually corresponding to Right Ascension or Azimuth. - * 1 : Secondary axis, usually corresponding to Declination or Altitude. - * 2 : Tertiary axis, usually corresponding to imager rotators. + The axis about which the telescope may be moved. See below for example values: + * 0 : Primary axis, usually corresponding to Right Ascension or Azimuth. + * 1 : Secondary axis, usually corresponding to Declination or Altitude. + * 2 : Tertiary axis, usually corresponding to imager rotators. Returns ------- @@ -74,10 +74,10 @@ def CanMoveAxis(self, Axis): Parameters ---------- Axis : `enum` - The axis about which the telescope may be moved. See below for example values. - * 0 : Primary axis, usually corresponding to Right Ascension or Azimuth. - * 1 : Secondary axis, usually corresponding to Declination or Altitude. - * 2 : Tertiary axis, usually corresponding to imager rotators. + The axis about which the telescope may be moved. See below for example values: + * 0 : Primary axis, usually corresponding to Right Ascension or Azimuth. + * 1 : Secondary axis, usually corresponding to Declination or Altitude. + * 2 : Tertiary axis, usually corresponding to imager rotators. """ pass @@ -96,10 +96,10 @@ def DestinationSideOfPier(self, RightAscension, Declination): Returns ------- `enum` - The side of the pier on which the telescope will be after slewing to the specified equatorial coordinates at the current instant of time. See below for example values. - * 0 : Normal pointing state, mount on East side of pier, looking West. - * 1 : Through the pole pointing state, mount on West side of pier, looking East. - * -1 : Unknown or indeterminate. + The side of the pier on which the telescope will be after slewing to the specified equatorial coordinates at the current instant of time. See below for example values: + * 0 : Normal pointing state, mount on East side of pier, looking West. + * 1 : Through the pole pointing state, mount on West side of pier, looking East. + * -1 : Unknown or indeterminate. """ pass @@ -127,10 +127,10 @@ def MoveAxis(self, Axis, Rate): Parameters ---------- Axis : `enum` - The axis about which the telescope may be moved. See below for example values. - * 0 : Primary axis, usually corresponding to Right Ascension or Azimuth. - * 1 : Secondary axis, usually corresponding to Declination or Altitude. - * 2 : Tertiary axis, usually corresponding to imager rotators. + The axis about which the telescope may be moved. See below for example values: + * 0 : Primary axis, usually corresponding to Right Ascension or Azimuth. + * 1 : Secondary axis, usually corresponding to Declination or Altitude. + * 2 : Tertiary axis, usually corresponding to imager rotators. Rate : `float` Rate of motion in degrees per second. Positive values indicate motion in one direction, negative values in the opposite direction, and 0.0 stops motion by this method and resumes tracking motion. @@ -168,11 +168,11 @@ def PulseGuide(self, Direction, Duration): Parameters ---------- Direction : `enum` - Direction in which to move the telescope. See below for example values. - * 0 : North or up. - * 1 : South or down. - * 2 : East or right. - * 3 : West or left. + Direction in which to move the telescope. See below for example values: + * 0 : North or up. + * 1 : South or down. + * 2 : East or right. + * 3 : West or left. Duration : `int` Time in milliseconds for which to pulse the guide. Must be a positive non-zero value. """ @@ -331,10 +331,10 @@ def AlignmentMode(self): """ The alignment mode of the telescope. (`enum`) - See below for example values. - * 0 : Altitude-Azimuth alignment. - * 1 : Polar (equatorial) mount alignment, NOT German. - * 2 : German equatorial mount alignment. + See below for example values: + * 0 : Altitude-Azimuth alignment. + * 1 : Polar (equatorial) mount alignment, NOT German. + * 2 : German equatorial mount alignment. """ pass @@ -556,12 +556,12 @@ def EquatorialSystem(self): """ Equatorial coordinate system used by the telescope. (`enum`) - See below for example values. - * 0 : Custom/unknown equinox and/or reference frame. - * 1 : Topocentric coordinates. Coordinates at the current date, allowing for annual aberration and precession-nutation. Most common for amateur telescopes. - * 2 : J2000 equator and equinox. Coordinates at the J2000 epoch, ICRS reference frame. Most common for professional telescopes. - * 3 : J2050 equator and equinox. Coordinates at the J2050 epoch, ICRS reference frame. - * 4 : B1950 equinox. Coordinates at the B1950 epoch, FK4 reference frame. + See below for example values: + * 0 : Custom/unknown equinox and/or reference frame. + * 1 : Topocentric coordinates. Coordinates at the current date, allowing for annual aberration and precession-nutation. Most common for amateur telescopes. + * 2 : J2000 equator and equinox. Coordinates at the J2000 epoch, ICRS reference frame. Most common for professional telescopes. + * 3 : J2050 equator and equinox. Coordinates at the J2050 epoch, ICRS reference frame. + * 4 : B1950 equinox. Coordinates at the B1950 epoch, FK4 reference frame. """ pass @@ -656,10 +656,10 @@ def SideOfPier(self): """ The pointing state of the mount. (`enum`) - See below for example values. - * 0 : Normal pointing state, mount on East side of pier, looking West. - * 1 : Through the pole pointing state, mount on West side of pier, looking East. - * -1 : Unknown or indeterminate. + See below for example values: + * 0 : Normal pointing state, mount on East side of pier, looking West. + * 1 : Through the pole pointing state, mount on West side of pier, looking East. + * -1 : Unknown or indeterminate. .. warning:: The name of this property is misleading and does not reflect the true meaning of the property. @@ -793,11 +793,11 @@ def TrackingRate(self): and report a default rate on connection. In this circumstance, a default of `Sidereal` rate is preferred. - See below for example values. - * 0 : Sidereal tracking, 15.041 arcseconds per second. - * 1 : Lunar tracking, 14.685 arcseconds per second. - * 2 : Solar tracking, 15.0 arcseconds per second. - * 3 : King tracking, 15.0369 arcseconds per second. + See below for example values: + * 0 : Sidereal tracking, 15.041 arcseconds per second. + * 1 : Lunar tracking, 14.685 arcseconds per second. + * 2 : Solar tracking, 15.0 arcseconds per second. + * 3 : King tracking, 15.0369 arcseconds per second. """ pass @@ -817,11 +817,11 @@ def TrackingRates(self): include properties for the number of rates, and the actual rates, and methods for returning an enumerator for the rates, and for disposing of the object as a whole. - See below for an example collection. - * 0 : Sidereal tracking, 15.041 arcseconds per second. - * 1 : Lunar tracking, 14.685 arcseconds per second. - * 2 : Solar tracking, 15.0 arcseconds per second. - * 3 : King tracking, 15.0369 arcseconds per second. + See below for an example collection: + * 0 : Sidereal tracking, 15.041 arcseconds per second. + * 1 : Lunar tracking, 14.685 arcseconds per second. + * 2 : Solar tracking, 15.0 arcseconds per second. + * 3 : King tracking, 15.0369 arcseconds per second. """ pass From 94ca83ce5c1c33cccbcb7ef233de42159d7fd9d7 Mon Sep 17 00:00:00 2001 From: ccolin Date: Tue, 3 Dec 2024 18:15:14 -0600 Subject: [PATCH 10/11] missing docs cleanup, currently only for classes visible to readthedocs --- pyscope/observatory/ascom_camera.py | 1 + pyscope/observatory/ascom_telescope.py | 10 ++ .../observatory/collect_calibration_set.py | 70 ++++++------ .../observatory/html_observing_conditions.py | 101 ++++++++++++++++++ pyscope/observatory/ip_cover_calibrator.py | 15 +++ pyscope/observatory/maxim.py | 8 ++ pyscope/observatory/pwi4_focuser.py | 21 ++++ 7 files changed, 191 insertions(+), 35 deletions(-) diff --git a/pyscope/observatory/ascom_camera.py b/pyscope/observatory/ascom_camera.py index 97cc61dc..5399688f 100644 --- a/pyscope/observatory/ascom_camera.py +++ b/pyscope/observatory/ascom_camera.py @@ -398,6 +398,7 @@ def LastExposureStartTime(self): @property def LastInputExposureDuration(self): + """The duration of the last exposure in seconds. (`float`)""" logger.debug(f"ASCOMCamera.LastInputExposureDuration property called") return self._last_exposure_duration diff --git a/pyscope/observatory/ascom_telescope.py b/pyscope/observatory/ascom_telescope.py index 7ff1b5ae..be65fa34 100644 --- a/pyscope/observatory/ascom_telescope.py +++ b/pyscope/observatory/ascom_telescope.py @@ -193,6 +193,8 @@ def SetPark(self): def SlewToAltAz(self, Azimuth, Altitude): # pragma: no cover """ + Deprecated + .. deprecated:: 0.1.1 ASCOM is deprecating this method. """ @@ -205,6 +207,8 @@ def SlewToAltAzAsync(self, Azimuth, Altitude): def SlewToCoordinates(self, RightAscension, Declination): # pragma: no cover """ + Deprecated + .. deprecated:: 0.1.1 ASCOM is deprecating this method. """ @@ -221,6 +225,8 @@ def SlewToCoordinatesAsync(self, RightAscension, Declination): def SlewToTarget(self): # pragma: no cover """ + Deprecated + .. deprecated:: 0.1.1 ASCOM is deprecating this method. """ @@ -339,6 +345,8 @@ def CanSetTracking(self): @property def CanSlew(self): # pragma: no cover """ + Deprecated + .. deprecated:: 0.1.1 ASCOM is deprecating this property. """ @@ -348,6 +356,8 @@ def CanSlew(self): # pragma: no cover @property def CanSlewAltAz(self): # pragma: no cover """ + Deprecated + .. deprecated:: 0.1.1 ASCOM is deprecating this property. """ diff --git a/pyscope/observatory/collect_calibration_set.py b/pyscope/observatory/collect_calibration_set.py index 9d9727bf..6b8df852 100644 --- a/pyscope/observatory/collect_calibration_set.py +++ b/pyscope/observatory/collect_calibration_set.py @@ -119,47 +119,47 @@ def collect_calibration_set_cli( verbose=0, ): """ - Collects a calibration set for the observatory.\b + Collects a calibration set for the observatory. .. warning:: The filter_exposures and filter_brightnesses must be of equal length. Parameters ---------- - observatory : `str` - - camera : `str`, default="ccd" - - readouts : `list`, default=[`None`] - - binnings : `list`, default=[`None`] - - repeat : `int`, default=1 - - dark_exposures : `list`, default=[] - - filters : `list`, default=[] - - filter_exposures : `list`, default=[] - - filter_brightness : `list`, default=`None` - - home_telescope : `bool`, default=`False` - - target_counts : `int`, default=`None` - - check_cooler : `bool`, default=`True` - - tracking : `bool`, default=`True` - - dither_radius : `float`, default=0 - - save_path : `str`, default="./temp/" - - new_dir : `bool`, default=`True` - - verbose : `int`, default=0 - + observatory : `str` or :py:class:`pyscope.observatory.Observatory` + The name of the observatory, or the observatory object itself, to connect to. + camera : `str`, default : "ccd" + The type of camera to collect calibration data for, such as ccd or cmos. + readouts : `list`, default : [`None`] + The indices of readout modes to iterate through. + binnings : `list`, default : [`None`] + The binnings to iterate through. + repeat : `int`, default : 1 + The number of times to repeat each exposure. + dark_exposures : `list`, default : [] + The dark exposure times. + filters : `list`, default : [] + The filters to collect flats for. + filter_exposures : `list`, default : [] + The flat exposure times for each filter. + filter_brightness : `list`, default : `None` + The intensity of the calibrator [if present] for each filter. + home_telescope : `bool`, default : `False` + Whether to return the telescope to its home position after taking flats. + target_counts : `int`, default : `None` + The target counts for the flats. + check_cooler : `bool`, default : `True` + Whether to check if the cooler is on before taking flats. + tracking : `bool`, default : `True` + Whether to track the telescope while taking flats. + dither_radius : `float`, default : 0 + The radius to dither the telescope by while taking flats. + save_path : `str`, default : "./temp/" + The path to save the calibration set. + new_dir : `bool`, default : `True` + Whether to create a new directory for the calibration set. + verbose : `int`, default : 0 + The verbosity of the output. """ if type(observatory) == str: diff --git a/pyscope/observatory/html_observing_conditions.py b/pyscope/observatory/html_observing_conditions.py index 80419ab0..4bda46a0 100755 --- a/pyscope/observatory/html_observing_conditions.py +++ b/pyscope/observatory/html_observing_conditions.py @@ -54,6 +54,101 @@ def __init__( last_updated_units="", last_updated_numeric=True, ): + """ + This class provides an interface to gathering observing condition data via HTML. + + The class is designed to be used with an HTML page that contains observing conditions data, + sensor descriptions, and time since last update. + + Parameters + ---------- + url : `str` + The URL of the HTML page that contains the observing conditions data. + cloud_cover_keyword : `str`, default : "CLOUDCOVER", optional + The keyword that identifies the cloud cover data. + cloud_cover_units : `str`, default : "%", optional + The units of the cloud cover data. + cloud_cover_numeric : `bool`, default : `True`, optional + Whether the cloud cover data is numeric. + dew_point_keyword : `str`, default : "DEWPOINT", optional + The keyword that identifies the dew point data. + dew_point_units : `str`, default : "F", optional + The units of the dew point data. + dew_point_numeric : `bool`, default : `True`, optional + Whether the dew point data is numeric. + humidity_keyword : `str`, default : "HUMIDITY", optional + The keyword that identifies the humidity data. + humidity_units : `str`, default : "%", optional + The units of the humidity data. + humidity_numeric : `bool`, default : `True`, optional + Whether the humidity data is numeric. + pressure_keyword : `str`, default : "PRESSURE", optional + The keyword that identifies the pressure data. + pressure_units : `str`, default : "inHg", optional + The units of the pressure data. + pressure_numeric : `bool`, default : `True`, optional + Whether the pressure data is numeric. + rain_rate_keyword : `str`, default : "RAINRATE", optional + The keyword that identifies the rain rate data. + rain_rate_units : `str`, default : "inhr", optional + The units of the rain rate data. + rain_rate_numeric : `bool`, default : `True`, optional + Whether the rain rate data is numeric. + sky_brightness_keyword : `str`, default : "SKYBRIGHTNESS", optional + The keyword that identifies the sky brightness data. + sky_brightness_units : `str`, default : "magdeg2", optional + The units of the sky brightness data. + sky_brightness_numeric : `bool`, default : `True`, optional + Whether the sky brightness data is numeric. + sky_quality_keyword : `str`, default : "SKYQUALITY", optional + The keyword that identifies the sky quality data. + sky_quality_units : `str`, default : "", optional + The units of the sky quality data. + sky_quality_numeric : `bool`, default : `True`, optional + Whether the sky quality data is numeric. + sky_temperature_keyword : `str`, default : "SKYTEMPERATURE", optional + The keyword that identifies the sky temperature data. + sky_temperature_units : `str`, default : "F", optional + The units of the sky temperature data. + sky_temperature_numeric : `bool`, default : `True`, optional + Whether the sky temperature data is numeric. + star_fwhm_keyword : `str`, default : "STARFWHM", optional + The keyword that identifies the star FWHM data. + star_fwhm_units : `str`, default : "arcsec", optional + The units of the star FWHM data. + star_fwhm_numeric : `bool`, default : `True`, optional + Whether the star FWHM data is numeric. + temperature_keyword : `str`, default : "TEMPERATURE", optional + The keyword that identifies the temperature data. + temperature_units : `str`, default : "F", optional + The units of the temperature data. + temperature_numeric : `bool`, default : `True`, optional + Whether the temperature data is numeric. + wind_direction_keyword : `str`, default : "WINDDIRECTION", optional + The keyword that identifies the wind direction data. + wind_direction_units : `str`, default : "EofN", optional + The units of the wind direction data. + wind_direction_numeric : `bool`, default : `True`, optional + Whether the wind direction data is numeric. + wind_gust_keyword : `str`, default : "WINDGUST", optional + The keyword that identifies the wind gust data. + wind_gust_units : `str`, default : "mph", optional + The units of the wind gust data. + wind_gust_numeric : `bool`, default : `True`, optional + Whether the wind gust data is numeric. + wind_speed_keyword : `str`, default : "WINDSPEED", optional + The keyword that identifies the wind speed data. + wind_speed_units : `str`, default : "mph", optional + The units of the wind speed data. + wind_speed_numeric : `bool`, default : `True`, optional + Whether the wind speed data is numeric. + last_updated_keyword : `str`, default : "LASTUPDATED", optional + The keyword that identifies the last updated data. + last_updated_units : `str`, default : "", optional + The units of the last updated data. + last_updated_numeric : `bool`, default : `True`, optional + Whether the last updated data is numeric. + """ logger.debug( f"""HTMLObservingConditions.__init__( {url}, @@ -320,16 +415,19 @@ def CloudCover(self): @property def Description(self): + """Description of the driver. (`str`)""" logger.debug("HTMLObservingConditions.Description property called") return "HTML Observing Conditions Driver" @property def DriverVersion(self): + """Version of the driver. (`str`)""" logger.debug("HTMLObservingConditions.DriverVersion property called") return None @property def DriverInfo(self): + """Provides information about the driver. (`str`)""" logger.debug("HTMLObservingConditions.DriverInfo property called") return "HTML Observing Conditions Driver" @@ -345,11 +443,13 @@ def Humidity(self): @property def InterfaceVersion(self): + """Version of the interface supported by the driver. (`int`)""" logger.debug("HTMLObservingConditions.InterfaceVersion property called") return 1 @property def Name(self): + """Name/url of the driver. (`str`)""" logger.debug("HTMLObservingConditions.Name property called") return self._url @@ -405,5 +505,6 @@ def WindSpeed(self): @property def LastUpdated(self): + """Time of last update of conditions. (`str`)""" logger.debug("HTMLObservingConditions.LastUpdated property called") return self._last_updated diff --git a/pyscope/observatory/ip_cover_calibrator.py b/pyscope/observatory/ip_cover_calibrator.py index 0cc1cd1a..12462a8b 100644 --- a/pyscope/observatory/ip_cover_calibrator.py +++ b/pyscope/observatory/ip_cover_calibrator.py @@ -11,6 +11,18 @@ class IPCoverCalibrator(CoverCalibrator): def __init__(self, tcp_ip, tcp_port, buffer_size): + """ + Implements the CoverCalibrator interface for a cover calibrator that is controlled via a TCP/IP connection. + + Parameters + ---------- + tcp_ip : `str` + The IP address of the cover calibrator. + tcp_port : `int` + The port number of the cover calibrator. + buffer_size : `int` + The size of the buffer to use when sending and receiving data. + """ self._tcp_ip = tcp_ip self._tcp_port = tcp_port self._buffer_size = buffer_size @@ -80,15 +92,18 @@ def _send_packet(self, intensity): @property def tcp_ip(self): + """The IP address of the cover calibrator. (`str`)""" logger.debug("IPCoverCalibrator.tcp_ip called") return self._tcp_ip @property def tcp_port(self): + """The port number of the cover calibrator. (`int`)""" logger.debug("IPCoverCalibrator.tcp_port called") return self._tcp_port @property def buffer_size(self): + """The size of the buffer to use when sending and receiving data. (`int`)""" logger.debug("IPCoverCalibrator.buffer_size called") return self._buffer_size diff --git a/pyscope/observatory/maxim.py b/pyscope/observatory/maxim.py index 77eb3479..778b2897 100755 --- a/pyscope/observatory/maxim.py +++ b/pyscope/observatory/maxim.py @@ -14,6 +14,11 @@ class Maxim(Device): def __init__(self): + """ + This class provides an interface to Maxim DL, and its camera, filter wheel, and autofocus routines. + + This class is only available on Windows. + """ logger.debug("Maxim.Maxim __init__ called") if platform.system() != "Windows": raise Exception("This class is only available on Windows.") @@ -50,16 +55,19 @@ def Name(self): @property def app(self): + """The Maxim DL application object. (`win32com.client.CDispatch`)""" logger.debug("Maxim.app called") return self._app @property def autofocus(self): + """The autofocus object. (`_MaximAutofocus`)""" logger.debug("Maxim.autofocus called") return self._autofocus @property def camera(self): + """The camera object. (`_MaximCamera`)""" logger.debug("Maxim.camera called") return self._camera diff --git a/pyscope/observatory/pwi4_focuser.py b/pyscope/observatory/pwi4_focuser.py index 3e0fd0f7..f502593f 100644 --- a/pyscope/observatory/pwi4_focuser.py +++ b/pyscope/observatory/pwi4_focuser.py @@ -9,6 +9,20 @@ class PWI4Focuser(Focuser): def __init__(self, host="localhost", port=8220): + """ + Focuser class for the PWI4 software platform. + + This class provides an interface to the PWI4 Focuser, and enables the user to access properties of + the focuser such as position, temperature, and whether the focuser is moving, and methods to move the + focuser, enable/disable the focuser, and check if the focuser is connected. + + Parameters + ---------- + host : `str`, default : "localhost", optional + The IP address of the host computer running the PWI4 software. + port : `int`, default : 8220, optional + The port number of the host computer running the PWI4 software. + """ self._host = host self._port = port self._app = _PWI4(host=self._host, port=self._port) @@ -46,18 +60,22 @@ def Absolute(self, value): @property def Description(self): + """A short description of the device. (`str`)""" return "PWI4 Focuser" @property def DriverInfo(self): + """A short description of the driver. (`str`)""" return "PWI4Driver" @property def DriverVersion(self): + """The version of the driver. (`str`)""" return "PWI4Driver" @property def InterfaceVersion(self): + """The version of the ASCOM interface. (`int`)""" return "PWI4Interface" @property @@ -75,6 +93,7 @@ def MaxStep(self): @property def Name(self): + """The name of the device. (`str`)""" return "PWI4 Focuser" @property @@ -108,6 +127,7 @@ def Temperature(self, value): @property def Connected(self): + """Whether the focuser is connected and enabled. (`bool`)""" logger.debug("PWI4Focuser.Connected() called") if self._app.status().focuser.exists: return ( @@ -119,6 +139,7 @@ def Connected(self): @property def Enabled(self): + """Whether the focuser is enabled. (`bool`)""" logger.debug("PWI4Focuser.Enabled() called") return self._app.status().focuser.is_enabled From 5f999542e8e4b8673b0a514d379fd2ee1373ad31 Mon Sep 17 00:00:00 2001 From: ccolin Date: Tue, 3 Dec 2024 21:54:19 -0600 Subject: [PATCH 11/11] pwi4 summary docs, rest left to actual devs --- pyscope/observatory/_pwi4.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pyscope/observatory/_pwi4.py b/pyscope/observatory/_pwi4.py index b31e6e1e..266f6a16 100644 --- a/pyscope/observatory/_pwi4.py +++ b/pyscope/observatory/_pwi4.py @@ -27,11 +27,17 @@ class _PWI4: - """ - Client to the PWI4 telescope control application. - """ - def __init__(self, host="localhost", port=8220): + """ + Client to the PWI4 telescope control application. + + Parameters + ---------- + host : `str`, default : "localhost", optional + The hostname or IP address of the computer running PWI4. + port : `int`, default : 8220, optional + The port number on which PWI4 is listening for HTTP requests. + """ self.host = host self.port = port self.comm = _PWI4HttpCommunicator(host, port)