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