#!/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