Source code for noctua.devices.alpaca

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# System modules
import time
from urllib.parse import urlencode

# Third-party modules
import requests

# Other templates
from ..config.constants import dome_park_position
from ..utils import check
from ..utils.logger import log
from .basedevice import BaseDevice


[docs] class AlpacaDevice(BaseDevice): '''Base wrapper class for ASCOM Alpaca devices''' def __init__(self, url, dev=None): '''Constructor.''' super().__init__(url) self._dev = dev # switch, dome, telescope etc. self.sim = 0 self.timeout = 10 @property def addr(self): '''Get or set the device URL address.''' return f"{self.url}/{self._dev}/{self.sim}" @addr.setter def addr(self, a): '''Setter for the device URL address.''' self._addr = a @check.request_errors def get(self, method, params={}, timeout=None): '''Send a HTTP GET request to the device address.''' if not timeout: timeout = self.timeout res = requests.get(f"{self.addr}/{method}", params=urlencode(params), timeout=timeout) res.raise_for_status() resj = res.json() if resj["ErrorNumber"] != 0: msg = f'ASCOM {resj["ErrorNumber"]}: {resj["ErrorMessage"]}' log.error(msg) self.error.append(msg) else: self.error = [] value = resj["Value"] return value @check.request_errors def put(self, method, data={}, timeout=None): '''Send a HTTP PUT request to the device address.''' if not timeout: timeout = self.timeout res = requests.put(f"{self.addr}/{method}", data=data, timeout=timeout) res.raise_for_status() resj = res.json() if resj["ErrorNumber"] != 0: msg = f'ASCOM {resj["ErrorNumber"]}: {resj["ErrorMessage"]}' log.error(msg) self.error.append(msg) else: if "Value" in resj: try: value = resj["Value"] self.error = [] return value except AttributeError as e: log.error(e) self.error.append(e) return res except KeyError as e: log.error(e) self.error.append(e) return res @property def actions(self): '''List the actions supported by the device.''' res = self.get("supportedactions") return res @property def name(self): '''Get the name of the device.''' res = self.get("name") return res @property def connection(self): '''Get or set the connection status of the device.''' res = self.get("connected") self._connection = res return self._connection @connection.setter def connection(self, b): '''Setter for the connection status of the device.''' data = {"Connected": b} res = self.put("connected", data=data) self._connection = self.connection
[docs] def command(self, val, cmd="commandstring"): data = {"Command": val, "Raw": False} res = self.put(cmd, data=data) value = res if type(value) in [bool, int]: return value else: try: float(value) return float(value) except BaseException: return value
[docs] def commandblind(self, val): res = self.command(val, cmd="commandblind") return res
[docs] class Switch(AlpacaDevice): '''Wrapper for ASCOM Alpaca devices of "switch" type.''' def __init__(self, url, id=3): '''Constructor.''' super().__init__(url, dev="switch") self.id = id @property def description(self): '''Describe the switch.''' params = {"Id": self.id} res = self.get("getswitchname", params=params) return res @property def state(self): '''Get or set the switch state.''' params = {"Id": self.id} res = self.get("getswitch", params=params) self._state = True if res else False return self._state @state.setter def state(self, s: bool): '''Setter method for the switch state.''' data = {"Id": self.id, "State": s} res = self.put("setswitch", data=data) self._state = self.state
[docs] class Dome(AlpacaDevice): '''Wrapper for ASCOM Alpaca devices of "dome" type.''' def __init__(self, url): '''Constructor.''' super().__init__(url, dev="dome") self.PARK = dome_park_position self.TOLERANCE = 1.0 # deg tolerance for park
[docs] def park(self): '''Put the dome in its park position.''' self.slave = False self.azimuth = self.PARK
[docs] def sync(self, az): '''Force the value of the dome azimuth to assume a specific value.''' self.slave = False data = {"Azimuth": az} res = self.put("synctoazimuth", data=data)
[docs] def abort(self): '''Abort the motion of the dome.''' self.slave = False res = self.put("abortslew")
@property def is_parked(self): '''Check if the dome is at its park position.''' try: res = abs(self.PARK - self.azimuth) < self.TOLERANCE except TypeError as e: res = None self._is_parked = res return self._is_parked @property def is_moving(self): '''Check if the dome is slewing.''' res = self.get("slewing") self._is_moving = res return self._is_moving @property def azimuth(self): '''Get or set the dome azimuth.''' res = self.get("azimuth") self._azimuth = res return self._azimuth @azimuth.setter def azimuth(self, a): '''Setter method for the dome azimuth.''' data = {"Azimuth": a} res = self.put("slewtoazimuth", data=data) self._azimuth = res @property def shutter(self): '''Get the status of the dome shutter.''' res = self.get("shutterstatus") return res @property def open(self): '''Get the dome shutter position, or open/close it.''' res = self.shutter if res == 0: self._open = True elif res == 1: self._open = False else: self._open = None return self._open @open.setter def open(self, b): '''Setter method to open/close the dome shutter.''' if b: res = self.put("openshutter") else: res = self.put("closeshutter") self._open = self.open @property def slave(self): '''Check if the dome position is slaved to the telescope, or slave/unslave it. ''' res = self.get("slaved") self._slave = res return self._slave @slave.setter def slave(self, b): '''Setter method to slave/unslave the dome to the telescope.''' data = {"Slaved": b} res = self.put("slaved", data=data) self._slave = self.slave
[docs] class Telescope(AlpacaDevice): def __init__(self, url): super().__init__(url, dev="telescope")
[docs] def track(self): self.tracking = True res = self.put("slewtotargetasync")
[docs] def abort(self): res = self.put("abortslew")
@property def is_moving(self): res = self.get("slewing") self._is_moving = res return self._is_moving @property def temperature(self): res1 = self.command("AUXILIARY.SENSOR[2].VALUE") res2 = self.command("AUXILIARY.SENSOR[3].VALUE") res3 = self.command("AUXILIARY.SENSOR[4].VALUE") return [round(float(res1), 1), round(float(res2), 1), round(float(res3), 1)] @property def status(self): res = self.command("TELESCOPE.STATUS.LIST") return res @property def state(self): res = self.command("TELESCOPE.STATUS.GLOBAL") return res
[docs] def clear(self, n): res = self.command(f"TELESCOPE.STATUS.CLEAR_ERROR={n}") return res
@property def clock(self): res = self.command("POSITION.LOCAL.UTC") # try: # time = Time(res, format="unix") # return time.isot # except ValueError: # return res return res @property def cover(self): res = self.command("AUXILIARY.COVER.REALPOS") self._cover = res return self._cover @property def open(self): res = self.cover if res == 1.0: self._open = True elif res == 0.0: self._open = False else: self._open = None return self._open @open.setter def open(self, b): pos = 1.0 if b else 0.0 res = self.commandblind(f"AUXILIARY.COVER.TARGETPOS={pos}") time.sleep(0.2) status = self.command("TELESCOPE.STATUS.GLOBAL") log.error(status) self._open = self.open @property def park(self): res = self.get("atpark") self._park = True if res else False return self._park @park.setter def park(self, b, timeout=90): if b: res = self.put("park", timeout=timeout) else: res = self.put("unpark", timeout=timeout) # res = self._park # why I put this? self._park = self.park @property def altaz(self): alt = self.get("altitude") az = self.get("azimuth") self._altaz = [alt, az] return self._altaz @altaz.setter def altaz(self, a): self.tracking = False data = {"Altitude": a[0], "Azimuth": a[1]} res = self.put("slewtoaltazasync", data=data) self._altaz = self.altaz @property def targetradec(self): ra = self.get("targetrightascension") dec = self.get("targetdeclination") self._targetradec = [ra, dec] return self._targetradec @targetradec.setter def targetradec(self, a): data = {"TargetRightAscension": a[0]} ra = self.put("targetrightascension", data=data) data = {"TargetDeclination": a[1]} dec = self.put("targetdeclination", data=data) self._targetradec = self.targetradec @property def radec(self): ra = self.get("rightascension") dec = self.get("declination") # ra = self.command("POSITION.EQUATORIAL.RA_J2000") # dec = self.command("POSITION.EQUATORIAL.DEC_J2000") self._radec = [ra, dec] return self._radec @radec.setter def radec(self, a): self.tracking = True data = {"RightAscension": a[0], "Declination": a[1]} res = self.put("slewtocoordinatesasync", data=data) self._radec = self.radec @property def offset(self): zd = self.command("POSITION.INSTRUMENTAL.ZD.OFFSET") az = self.command("POSITION.INSTRUMENTAL.AZ.OFFSET") self._offset = [zd, az] return self._offset @offset.setter def offset(self, a): zd = a[0] # in degrees! az = a[1] # in degrees! if abs(zd) > 0.3: # 18 arcmin log.error(f"zd {zd} too large. Maybe arcsec instead of deg?") return if abs(az) > 0.3: # 18 arcmin log.error(f"az {az} too large. Maybe arcsec instead of deg?") return zd = self.commandblind(f"POSITION.INSTRUMENTAL.ZD.OFFSET={a[0]}") az = self.commandblind(f"POSITION.INSTRUMENTAL.AZ.OFFSET={a[1]}") self._offset = self.offset @property def tracking(self): res = self.get("tracking") self._tracking = res return self._tracking @tracking.setter def tracking(self, b): data = {"Tracking": b} res = self.put("tracking", data=data) self._tracking = self.tracking
[docs] class Focuser(AlpacaDevice): def __init__(self, url): super().__init__(url, dev="focuser") @property def is_moving(self): res = self.get("ismoving") self._moving = res return self._moving @property def position(self): res = self.get("position") self._position = res return self._position @position.setter def position(self, s): # 0-34500=micron? data = {"Position": s} res = self.put("move", data=data) self._position = self.position
[docs] class Rotator(AlpacaDevice): def __init__(self, url): super().__init__(url, dev="rotator") @property def is_moving(self): res = self.get("ismoving") self._moving = res return self._moving @property def position(self): res = self.get("position") self._position = res return self._position @position.setter def position(self, s): # 0-270 deg? data = {"Position": s} res = self.put("moveabsolute", data=data) self._position = self.position