Source code for noctua.devices.stx

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

"""
Interface with a SBIG STX camera device
"""

# System modules
import time
from datetime import datetime
from urllib.parse import urlencode

# Third-party modules
import requests

# Other templates
from ..config.constants import temp_fits
from ..utils import check
from .basedevice import BaseDevice


[docs] class STX(BaseDevice): '''Base wrapper class for SBIG STX cameras.''' def __init__(self, url): '''Constructor.''' super().__init__(url) self.url = url self.addr = self.url self.timeout = 3 self._last_command_time = 0 self._command_interval = 0.05 # 50 milliseconds def _wait_if_needed(self): """ Ensures the 50ms command interval is respected between commands. """ elapsed = time.time() - self._last_command_time if elapsed < self._command_interval: time.sleep(self._command_interval - elapsed) self._last_command_time = time.time() @property def connection(self): '''Setup a telnet connection to the cabinet. No decorator to silence errors. If error, not connected. ''' try: method = "VersionNumbers" res = requests.get(f"{self.addr}/{method}.cgi", timeout=self.timeout) return True except Exception as e: self.error = ["No ping"] return False @check.request_errors def get(self, method, params=[]): '''Send a HTTP GET request to the device address.''' self._wait_if_needed() # Wait before sending command res = requests.get(f"{self.addr}/{method}.cgi", params="&".join(params), timeout=self.timeout, verify=False) value = res.text.split("\r\n") if not value[-1]: value = value[:-1] if len(value) == 1: value = value[0] try: int(value) return int(value) except BaseException: try: float(value) return float(value) except BaseException: return value @check.request_errors def put(self, method, params={}): '''Send a HTTP GET request to the device address.''' self._wait_if_needed() # Wait before sending command res = requests.get(f"{self.addr}/{method}.cgi", params=urlencode(params), timeout=self.timeout) res.raise_for_status() text = res.text.split("\r\n") if not text[-1]: text = text[:-1] if len(text) == 1: text = text[0] return text
[docs] class Camera(STX): '''Camera device based on STX cameras.'''
[docs] def abort(self): self.put("ImagerAbortExposure")
@check.request_errors def abort2(self): # https://github.com/dkirkby/STXLDriver/blob/master/stxldriver/camera.py # Maybe /exposure.html?Abort res = requests.get(f"exposure.html", params=urlencode("Abort"), timeout=self.timeout) res.raise_for_status() text = res.text.split("\r\n") if not text[-1]: text = text[:-1] if len(text) == 1: text = text[0] return text
[docs] def start( self, duration, frametype, datetime=datetime.utcnow().isoformat()): params = {"Duration": duration, "FrameType": frametype, "DateTime": datetime} self.put("ImagerStartExposure", params=params)
[docs] def download(self): res = requests.get(f"{self.addr}/Imager.FIT") with open(temp_fits, 'wb') as f: f.write(res.content)
# return fits.ImageHDU.fromstring(res.content)
[docs] def full_frame(self): maxx, maxy = self.max_range self.xrange = [0, maxx] self.yrange = [0, maxy] self.xystart = [0, 0] self.xyend = [maxx, maxy] return {"xrange": self.xrange, "yrange": self.yrange}
[docs] def half_frame(self): maxx, maxy = self.max_range self.xrange = [maxx // 4 * 1, maxx // 4 * 3] self.yrange = [maxy // 4 * 1, maxy // 4 * 3] self.xystart = [maxx // 4, maxy // 4] self.xyend = [maxx // 4 * 3, maxy // 4 * 3] return {"xrange": self.xrange, "yrange": self.yrange}
[docs] def small_frame(self): maxx, maxy = self.max_range self.xrange = [maxx // 20 * 9, maxx // 20 * 11] self.yrange = [maxy // 20 * 9, maxy // 20 * 11] self.xystart = [maxx // 20 * 9, maxy // 20 * 9] self.xyend = [maxx // 20 * 11, maxy // 20 * 11] return {"xrange": self.xrange, "yrange": self.yrange}
@property def version(self): res = self.get("VersionNumbers") return res @property def description(self): res = self.get("Description") return res @property def state(self): res = self.get("ImagerState") return res @property def ready(self): res = self.get("ImagerImageReady") return res @property def all(self): params = ["AmbientTemperature", # 0 "CCDTemperatureSetpoint", # 1 "CCDTemperature", # 2 "CoolerState", # 3 "CoolerPower", # 4 "BinX", "BinY", # 5 #6 "CameraXSize", "CameraYSize", # 7 #8 "StartX", "StartY", # 9 #10 "NumX", "NumY"] # 11 #12 res = self.get("ImagerGetSettings", params=params) try: ambient, setpoint, temperature, cool, fan, binx, biny, camx, camy, startx, starty, numx, numy = list( map(float, res[:13])) except (TypeError, ValueError) as e: ambient, setpoint, temperature, cool, fan, binx, biny, camx, camy, startx, starty, numx, numy = [ 999, 999, 999, 0, 0, 999, 999, 0, 0, 0, 0, 0, 0] cooler = True if cool else False x_start = startx // binx y_start = starty // biny x_end = (startx + numx) // binx y_end = (starty + numy) // biny centx = camx // binx // 2 centy = camy // biny // 2 all_dict = {"ambient": round(ambient, 1), "setpoint": round(setpoint, 1), "temperature": round(temperature, 1), "cooler": cooler, "fan": round(fan, 0), "binning": [binx, biny], "max_range": [camx // binx, camy // biny], "xystart": [x_start, y_start], "xyend": [x_end, y_end], "xrange": [x_start, x_end], "yrange": [y_start, y_end], "center": [centx, centy]} return all_dict @property def ambient(self): params = ["AmbientTemperature"] res = self.get("ImagerGetSettings", params=params) return round(res, 1) @property def max_range(self): params = ["CameraXSize", "CameraYSize"] res = self.get("ImagerGetSettings", params=params) if self.error: return None, None res = list(map(int, res)) binning = self.binning return [r // b for r, b in zip(res, binning)] @property def is_moving(self): res = self.get("FilterState") self._is_moving = res return self._is_moving @property def filter(self): params = ["CurrentFilter"] # , "CurrentFilterName" res = self.get("GetFilterSetting", params=params) self._filter = res return self._filter @filter.setter def filter(self, n): params = {"NewPosition": n} res = self.put("ChangeFilter", params=params) self._filter = self.filter @property def binning(self): params = ["BinX", "BinY"] res = self.get("ImagerGetSettings", params=params) if self.error: return None self.binning = list(map(int, res)) return self._binning @binning.setter def binning(self, b): params = {"BinX": b[0], "BinY": b[1]} self.put("ImagerSetSettings", params=params) if self.error: return None self._binning = b @property def setpoint(self): params = ["CCDTemperatureSetpoint"] res = self.get("ImagerGetSettings", params=params) return res @property def temperature(self): params = ["CCDTemperature"] res = self.get("ImagerGetSettings", params=params) return round(res, 1) @temperature.setter def temperature(self, t): params = {"CCDTemperatureSetpoint": t} res = self.put("ImagerSetSettings", params=params) return self.setpoint @property def cooler(self): params = ["CoolerState"] res = self.get("ImagerGetSettings", params=params) self.cooler = True if res else False return self._cooler @cooler.setter def cooler(self, b): s = "1" if b else "0" params = {"CoolerState": s} res = self.put("ImagerSetSettings", params=params) self._cooler = b @property def fan(self): params = ["CoolerPower"] res = self.get("ImagerGetSettings", params=params) return round(res) @property def xrange(self): params = ["StartX", "NumX", "BinX"] # start=20, width=100 (binning=1) res = self.get("ImagerGetSettings", params=params) if self.error: return None startx, numx, binx = list(map(int, res)) # binx = self.binning[0] # binning is 2 x_start = startx // binx x_end = numx // binx + x_start self.xrange = [x_start, x_end] # start=10, end=60 return self._xrange @xrange.setter def xrange(self, b): # start=10, end=60 binx = self.binning[0] # binning is 2 startx = b[0] * binx # start: 10*2=20 numx = b[1] * binx - startx # width: 60*2-startx=100 params = {"StartX": startx, "NumX": numx} res = self.put("ImagerSetSettings", params=params) self._xrange = b @property def yrange(self): params = ["StartY", "NumY", "BinY"] # start=20, height=100 (binning=1) res = self.get("ImagerGetSettings", params=params) if self.error: return None starty, numy, biny = list(map(int, res)) # biny = self.binning[1] # binning is 2 y_start = starty // biny y_end = numy // biny + y_start self.yrange = [y_start, y_end] # start=10, end=60 return self._yrange @yrange.setter def yrange(self, b): # start=10, end=60 biny = self.binning[1] # binning is 2 starty = b[0] * biny # start: 10*2=20 numy = b[1] * biny - starty # height: 60*2-starty=100 params = {"StartY": starty, "NumY": numy} res = self.put("ImagerSetSettings", params=params) self._yrange = b @property def xystart(self): params = ["StartX", "StartY", "BinX", "BinY"] res = self.get("ImagerGetSettings", params=params) if self.error: return None startx, starty, binx, biny = list(map(int, res)) x_start = startx // binx y_start = starty // biny self.xystart = [x_start, y_start] return self._xystart @xystart.setter def xystart(self, b): params = ["StartX", "StartY", "NumX", "NumY", "BinX", "BinY"] res = self.get("ImagerGetSettings", params=params) if self.error: return None startx, starty, numx, numy, binx, biny = list(map(int, res)) numx = numx + startx - b[0] * binx numy = numy + starty - b[1] * biny startx = int(b[0]) * binx starty = int(b[1]) * biny params = {"StartX": startx, "StartY": starty, "NumX": numx, "NumY": numy} res = self.put("ImagerSetSettings", params=params) self._xystart = b @property def xyend(self): params = ["StartX", "StartY", "NumX", "NumY", "BinX", "BinY"] res = self.get("ImagerGetSettings", params=params) if self.error: return None startx, starty, numx, numy, binx, biny = list(map(int, res)) x_end = (startx + numx) // binx y_end = (starty + numy) // biny return [x_end, y_end] @xyend.setter def xyend(self, b): params = ["StartX", "StartY", "BinX", "BinY"] res = self.get("ImagerGetSettings", params=params) if self.error: return None startx, starty, binx, biny = list(map(int, res)) numx = int(b[0]) * binx - startx numy = int(b[1]) * biny - starty params = {"NumX": numx, "NumY": numy} res = self.put("ImagerSetSettings", params=params) self._xyend = b @property def center(self): params = ["CameraXSize", "CameraYSize", "BinX", "BinY"] res = self.get("ImagerGetSettings", params=params) if self.error: return None camx, camy, binx, biny = list(map(int, res)) centx = camx // binx // 2 centy = camy // biny // 2 return [centx, centy]