Skip to content
...@@ -3,88 +3,56 @@ ...@@ -3,88 +3,56 @@
'''REST API for IP camera related operations''' '''REST API for IP camera related operations'''
# System modules
import time
# Third-party modules # Third-party modules
from astropy.time import Time from flask import request
from flask import Response, make_response, render_template, request
from flask_restx import Namespace, Resource, fields
# Custom modules
import devices
############################ from .baseresource import ResourceDev
# REST API
############################
api = Namespace('webcam', description='Dome Webcam related operations')
# @api.route("/position")
@api.route("/position") class Pointing(ResourceDev):
class Pointing(Resource):
"""Position of the webcam.""" """Position of the webcam."""
def get(self): def get(self):
"""Retrieve the alt az coordinates of the webcam.""" """Retrieve the alt az coordinates of the webcam."""
res = { res = {
"response": devices.ipcam.altaz, "response": self.dev.altaz,
"error": devices.ipcam.error, "error": self.dev.error,
"timestamp": self.timestamp,
} }
return res return res
def put(self): def put(self):
"""Set new alt az coordinates of the webcam.""" """Set new alt az coordinates of the webcam."""
target = api.payload target = request.json
devices.ipcam.altaz = target self.dev.altaz = target
res = { res = {
"response": devices.ipcam.altaz, "response": self.dev.altaz,
"error": devices.ipcam.error, "error": self.dev.error,
"timestamp": self.timestamp,
} }
return res return res
@api.route("/snapshot") # @api.route("/snapshot")
class Snapshot(Resource): class Snapshot(ResourceDev):
"""Image from the webcam.""" """Image from the webcam."""
def __init__(self, *args, **kwargs):
'''Constructor.'''
super().__init__(self)
self.last = None
def get(self): def get(self):
"""Retrieve a raw base/64 image from the webcam.""" """Retrieve a raw base/64 image from the webcam."""
img = devices.ipcam.image img = self.dev.image
if not img: if not img:
code = 401 code = 401
elif devices.ipcam.error: elif self.dev.error:
code = 501 code = 501
else: else:
code = 200 code = 200
res = { res = {
"response": img.decode("ISO-8859-1") if img else img, "response": img.decode("ISO-8859-1") if img else img,
"error": devices.ipcam.error, "error": self.dev.error,
"timestamp": self.timestamp,
} }
self.last = img
return res, code
############################
# WEB VIEW
############################
web = Namespace('webcam', description='Web webcam interface')
@web.route("/")
class Init(Resource):
def get(self):
data = {}
return make_response(render_template("webcam.html", data=data))
return res, code
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""API server, with a web client for free"""
# System modules
import configparser
import logging
import time
# Third-party modules # Third-party modules
from flask import Flask from flask import Flask, request
from flask_cors import CORS from flask_cors import CORS
# Custom modules # Custom modules
from api.scheduler import scheduler
from api import api_blueprint as api from api import api_blueprint as api
from api import web_blueprint as web from web.server import instance
app = Flask(__name__) app = Flask(__name__)
app.register_blueprint(api, url_prefix='/api')
app.threaded = True
cors = CORS(app) cors = CORS(app)
# scheduler.api_enabled = True # Load the config file
scheduler.init_app(app) ends = configparser.ConfigParser()
ends.read('./config/api.ini')
app.register_blueprint(api, url_prefix='/api') @app.route('/all/<string:namespace>')
app.register_blueprint(web, url_prefix='/web') def get_all(namespace):
# '''Build a global status for a given namespace'''
wait = float(request.args.get('wait', 0))
endpoints = {}
sections = []
# List of get-priorities, if any.
for section in ends.sections():
if section.startswith("/"+namespace):
priority = ends[section].getint('get-priority', float('inf'))
sections.append((priority, section))
# Sorting by priority
sections.sort()
# GET starting from prioritized. If prioritized fails, break.
for pri, sec in sections:
time.sleep(wait)
with app.test_client() as client:
cli = client.get(f"/api{sec}")
if cli.status_code != 405: # 405 Method Not Allowed
res = cli.get_json()
name = "-".join(sec.split("/")[1:]) # "telescope-power"
endpoints[name] = res
if pri != float('inf'):
if "raw" in res:
if not res["raw"]:
break
elif "response" in res: # just for telesope/clock
if (res["response"] == "FAILED 2"):
break
return endpoints
# @app.route('/all/<string:namespace>')
# def get_status(namespace):
# '''Build a global status for a given namespace'''
# wait = float(request.args.get('wait', 0))
# endpoints = {}
# # Check for ping before going to all others
# power = f"/api/{namespace}/ping"
# with app.test_client() as client:
# res = client.get(power)
# name = "-".join(power.split("/")[2:]) # "dome-ping"
# endpoints[name] = res.get_json()
# if "raw" in res.get_json():
# if not res.get_json()["raw"]:
# return endpoints
# for rule in app.url_map.iter_rules():
# if rule.rule.startswith(f"/api/{namespace}/"): # "/api/dome/shutter/movement"
# if "GET" in rule.methods:
# with app.test_client() as client:
# res = client.get(rule.rule)
# name = "-".join(rule.rule.split("/")[2:]) # "dome-shutter-movement"
# endpoints[name] = res.get_json()
# time.sleep(wait)
# return endpoints
app.template_folder = "./web/pages"
app.static_folder = "./web/static"
scheduler.start()
if __name__ == "__main__": if __name__ == "__main__":
# System modules # System modules
import socket
import sys import sys
with_web = True
try: try:
port = sys.argv[1] arg = sys.argv[1]
hostname = sys.argv[2] if arg.startswith("--noweb"):
except IndexError: with_web = False
port = 5533
else:
port = arg
except IndexError as e:
port = 5533 port = 5533
oarpaf_ip = "10.185.119.108"
this_ip = socket.getfqdn() hostname = "0.0.0.0"
# hostname = "fork.orsa.unige.net"
# hostname = oarpaf_ip if this_ip==oarpaf_ip else this_ip print(f"hostname {hostname}")
hostname = oarpaf_ip
print(f"hostname {hostname}")
app.run(host=hostname, port=port, threaded=True, debug=False)
if with_web:
socketio = instance.enable(app)
socketio.run(app, host=hostname, port=port)
else:
app.run(host=hostname, port=port)
# #####################################################
# [/my/endpoint/name] # You choose
# resource = class implementing a CRUD operation
# device = You defined it in devices.ini
# get-priority = defines a GET hierarchy.
# #####################################################
##############
# dome
##############
[/dome/connection]
resource = Connection
device = dom
get-priority = 1
[/dome/light]
resource = State
device = light
[/dome/shutter]
resource = Shutter
device = dom
[/dome/shutter/movement]
resource = ShutterMovement
device = dom
[/dome/position]
resource = Position
device = dom
[/dome/position/movement]
resource = PositionMovement
device = dom
[/dome/position/movement/park]
resource = PositionMovementPark
device = dom
[/dome/position/movement/azimuth]
resource = PositionMovementAzimuth
device = dom
[/dome/position/slaved]
resource = PositionSlaved
device = dom
[/dome/position/sync]
resource = PositionSync
device = dom
##############
# telescope
##############
[/telescope/power]
resource = State
device = cab
get-priority = 1
[/telescope/clock]
resource = Clock
device = tel
get-priority = 2
[/telescope/lamp]
resource = State
device = lamp
[/telescope/cover]
resource = Cover
device = tel
[/telescope/cover/movement]
resource = CoverMovement
device = tel
[/telescope/coordinates]
resource = Coordinates
device = tel
[/telescope/coordinates/movement]
resource = CoordinatesMovement
device = tel
[/telescope/coordinates/movement/radec]
resource = CoordinatesMovementRadec
device = tel
[/telescope/coordinates/movement/altaz]
resource = CoordinatesMovementAltaz
device = tel
[/telescope/coordinates/movement/atpark]
resource = CoordinatesMovementAtpark
device = tel
[/telescope/coordinates/movement/park]
resource = CoordinatesMovementPark
device = tel
[/telescope/coordinates/movement/unpark]
resource = CoordinatesMovementUnpark
device = tel
[/telescope/coordinates/offset]
resource = CoordinatesOffset
device = tel
[/telescope/coordinates/tracking]
resource = CoordinatesTracking
device = tel
[/telescope/connection]
resource = Connection
device = tel
[/telescope/error]
resource = Error
device = tel
[/telescope/error/details]
resource = ErrorDetails
device = tel
[/telescope/focuser]
resource = Focuser
device = foc
[/telescope/focuser/movement]
resource = FocuserMovement
device = foc
[/telescope/rotator]
resource = Rotator
device = rot
[/telescope/rotator/movement]
resource = RotatorMovement
device = rot
##############
# camera
##############
[/camera/power]
resource = State
device = sof
get-priority = 1
[/camera/frame/binning]
resource = FrameBinning
device = cam
[/camera/cooler]
resource = Cooler
device = cam
# [/camera/cooler/fan]
# resource = CoolerFan
# device = cam
# [/camera/cooler/temperature]
# resource = CoolerTemperature
# device = cam
[/camera/cooler/temperature/setpoint]
resource = CoolerTemperatureSetpoint
device = cam
[/camera/filters]
resource = Filters
device = cam
[/camera/filter]
resource = Filter
device = cam
[/camera/filter/movement]
resource = FilterMovement
device = cam
# [/camera/frame]
# resource = Frame
# device = cam
[/camera/frame/custom]
resource = FrameCustom
device = cam
[/camera/frame/full]
resource = FrameFull
device = cam
[/camera/frame/half]
resource = FrameHalf
device = cam
[/camera/frame/small]
resource = FrameSmall
device = cam
# [/camera/frame/temperature]
# resource = FrameTemperature
# device = cam
[/camera/snapshot]
resource = Snapshot
device = cam
[/camera/snapshot/state]
resource = SnapshotState
device = cam
[/camera/snapshot/acquisition]
resource = SnapshotAcquisition
device = cam
[/camera/snapshot/recenter]
resource = SnapshotRecenter
device = cam
[/camera/snapshot/domeslewing]
resource = SnapshotDomeslewing
device = cam
[/camera/cooler/warmup]
resource = CoolerWarmup
device = cam
[/camera/settings]
resource = Settings
device = cam
# [/camera/status]
# resource = Status
# device = cam
##############
# webcam
##############
[/webcam/snapshot]
resource = Snapshot
device = ipcam
[/webcam/position]
resource = Pointing
device = ipcam
##############
# environment
##############
# [/environment/external/telescope]
# resource = Temperature
# device = tel_temp
# [/environment/internal/telescope]
# resource = Temperature
# device = tel_temp
# [/environment/internal/fork]
# resource = Temperature
# device = fork
# [/environment/internal/rack]
# resource = Temperature
# device = fork
# [/environment/internal/reception]
# resource = Temperature
# device = rec
...@@ -36,6 +36,13 @@ module = astelco ...@@ -36,6 +36,13 @@ module = astelco
class = Rotator class = Rotator
node = CABINET node = CABINET
[tel_temp]
module = astelco
class = Sensor
node = CABINET
outlet1 = 1
outlet2 = 2
[dom] [dom]
module = alpaca module = alpaca
class = Dome class = Dome
...@@ -93,7 +100,7 @@ outlet2 = 6 ...@@ -93,7 +100,7 @@ outlet2 = 6
[ipcam] [ipcam]
module = ipcam module = ipcam
class = DlinkDCSCamera class = Webcam
node = IPCAM node = IPCAM
[met] [met]
......
...@@ -76,4 +76,3 @@ password = BigBang2021 ...@@ -76,4 +76,3 @@ password = BigBang2021
ip = 10.185.119.106 ip = 10.185.119.106
hostname = ipcam.orsa.unige.net hostname = ipcam.orsa.unige.net
port = 80 port = 80
...@@ -10,6 +10,9 @@ import importlib ...@@ -10,6 +10,9 @@ import importlib
import configparser import configparser
import sys import sys
# Custom modules
from utils.url_stuff import build_url
this_module = sys.modules[__name__] this_module = sys.modules[__name__]
nodes = configparser.ConfigParser() nodes = configparser.ConfigParser()
...@@ -19,12 +22,12 @@ devs.read('./config/devices.ini') ...@@ -19,12 +22,12 @@ devs.read('./config/devices.ini')
def dynamic_import(this, dev): def dynamic_import(this, dev):
"""Dynamically import into this module the devices """Dynamically import into this module the devices
from the config files. from the nodes.ini and devices.ini files.
Old way: Old way:
# from config.addresses import ASCOM_REMOTE # from config.addresses import ASCOM_REMOTE
# from devices import ascom # from devices import ascom
# lamp = ascom.Switch(ASCOM_REMOTE, 2) # lamp = ascom.Switch(ASCOM_REMOTE, 2)
""" """
...@@ -32,23 +35,11 @@ def dynamic_import(this, dev): ...@@ -32,23 +35,11 @@ def dynamic_import(this, dev):
cls = getattr(module, devs.get(dev, "class")) cls = getattr(module, devs.get(dev, "class"))
node = devs.get(dev, "node") node = devs.get(dev, "node")
itn = dict(nodes.items(node))
user = itn.get("user") or "" itn = dict(nodes.items(node))
pwd = ":"+itn["password"]+"@" if itn.get("password") else ""
if itn.get("protocol"):
if itn["protocol"] == "tcp":
prot = itn["protocol"]+":"
else:
prot = itn["protocol"]+"://"
else:
prot = ""
ip = itn["ip"]
endp = itn.get("endpoint") or ""
port = ":"+itn["port"] if itn.get("port") else ""
url = prot + user + pwd + ip + port + endp
url = build_url(itn)
instance_name = dev instance_name = dev
# Extract additional parameters based on device type # Extract additional parameters based on device type
......
...@@ -24,7 +24,7 @@ class OpenTSI(BaseDevice): ...@@ -24,7 +24,7 @@ class OpenTSI(BaseDevice):
self.url = url.split(":")[0] or url self.url = url.split(":")[0] or url
self.port = url.split(":")[1] or 22 self.port = url.split(":")[1] or 22
self.timeout = 3 self.timeout = 3
self.connection = None self.connection = True
@check.telnet_errors @check.telnet_errors
def _connect(self): def _connect(self):
...@@ -200,15 +200,6 @@ class Telescope(OpenTSI): ...@@ -200,15 +200,6 @@ class Telescope(OpenTSI):
res = self.get(message) res = self.get(message)
return res return res
@property
def temperature(self):
'''Mirrors temperature'''
message = ["AUXILIARY.SENSOR[2].VALUE",
"AUXILIARY.SENSOR[3].VALUE",
"AUXILIARY.SENSOR[4].VALUE"]
res = self.get(message)
return res
@property @property
def status(self): def status(self):
'''need to check exit and error from get function''' '''need to check exit and error from get function'''
...@@ -485,7 +476,6 @@ class Rotator(OpenTSI): ...@@ -485,7 +476,6 @@ class Rotator(OpenTSI):
@property @property
def position(self): def position(self):
'''Get Relative rotator position from telnet''' '''Get Relative rotator position from telnet'''
message = "POSITION.INSTRUMENTAL.DEROTATOR[2].OFFSET" message = "POSITION.INSTRUMENTAL.DEROTATOR[2].OFFSET"
res = self.get(message) res = self.get(message)
self._position = res self._position = res
...@@ -509,3 +499,25 @@ class Rotator(OpenTSI): ...@@ -509,3 +499,25 @@ class Rotator(OpenTSI):
return self._absolute return self._absolute
class Sensor(OpenTSI):
'''Implementation of a Sensor class.'''
def __init__(self, url, temp_id, hum_id):
'''Constructor.'''
super().__init__(url)
self.temp_id = temp_id # recycle for last_update
self.hum_id = hum_id
@property
def temperature(self):
'''Mirrors temperature'''
message = ["AUXILIARY.SENSOR[2].VALUE",
"AUXILIARY.SENSOR[3].VALUE",
"AUXILIARY.SENSOR[4].VALUE"]
res = self.get(message)
return res
@property
def humidity(self):
'''Does it have humidity sensors?'''
...@@ -7,14 +7,14 @@ from urllib.parse import urlencode ...@@ -7,14 +7,14 @@ from urllib.parse import urlencode
# Third-party modules # Third-party modules
import requests import requests
from astropy.time import Time
# Custom modules # Custom modules
from devices.basedevice import BaseDevice
from utils import check from utils import check
from utils.logger import log from utils.logger import log
class DlinkDCSCamera: class DlinkDCSCamera(BaseDevice):
'''Base wrapper class for Dlink DCS 5020l cameras.''' '''Base wrapper class for Dlink DCS 5020l cameras.'''
def __init__(self, url): def __init__(self, url):
...@@ -60,11 +60,14 @@ class DlinkDCSCamera: ...@@ -60,11 +60,14 @@ class DlinkDCSCamera:
return value return value
def time_to_string(self, time): # def time_to_string(self, time):
'''Convert a datetime into the HH:MM:SS string format.''' # '''Convert a datetime into the HH:MM:SS string format.'''
return datetime.strftime(time, '%H:%M:%S') # return datetime.strftime(time, '%H:%M:%S')
# OK
class Webcam(DlinkDCSCamera):
'''Implementation of the Telescope commands mocking my Alpaca
Telescope wrapper.'''
@property @property
def description(self): def description(self):
......
...@@ -18,7 +18,8 @@ class Meteo: ...@@ -18,7 +18,8 @@ class Meteo:
def __init__(self, url): def __init__(self, url):
self.url = url self.url = url
self.error = None self.error = None
self.station = None
@check.telnet_errors @check.telnet_errors
def connect(self): def connect(self):
self.station = VantagePro2.from_url(self.url) self.station = VantagePro2.from_url(self.url)
......
...@@ -7,27 +7,43 @@ Interface with a SBIG STX camera device ...@@ -7,27 +7,43 @@ Interface with a SBIG STX camera device
# System modules # System modules
from urllib.parse import urlencode from urllib.parse import urlencode
from datetime import datetime
# Third-party modules # Third-party modules
import requests import requests
from astropy.time import Time
# Custom modules # Custom modules
from devices.basedevice import BaseDevice
from config.constants import temp_fits from config.constants import temp_fits
from utils import check from utils import check
from utils.logger import log
class STX(BaseDevice):
class Camera:
'''Base wrapper class for SBIG STX cameras.''' '''Base wrapper class for SBIG STX cameras.'''
def __init__(self, url): def __init__(self, url):
'''Constructor.''' '''Constructor.'''
super().__init__(url)
self.url = url self.url = url
self.addr = self.url self.addr = self.url
self.timeout = 3 self.timeout = 3
self.error = []
@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 @check.request_errors
def get(self, method, params=[]): def get(self, method, params=[]):
'''Send a HTTP GET request to the device address.''' '''Send a HTTP GET request to the device address.'''
...@@ -35,7 +51,7 @@ class Camera: ...@@ -35,7 +51,7 @@ class Camera:
res = requests.get(f"{self.addr}/{method}.cgi", res = requests.get(f"{self.addr}/{method}.cgi",
params="&".join(params), params="&".join(params),
timeout=self.timeout, verify=False) timeout=self.timeout, verify=False)
# res.raise_for_status()
value = res.text.split("\r\n") value = res.text.split("\r\n")
if not value[-1]: if not value[-1]:
value = value[:-1] value = value[:-1]
...@@ -68,6 +84,13 @@ class Camera: ...@@ -68,6 +84,13 @@ class Camera:
text = text[0] text = text[0]
return text return text
class Camera(STX):
'''Camera device based on STX cameras.'''
def abort(self):
self.put("ImagerAbortExposure")
@check.request_errors @check.request_errors
def abort2(self): def abort2(self):
# https://github.com/dkirkby/STXLDriver/blob/master/stxldriver/camera.py # https://github.com/dkirkby/STXLDriver/blob/master/stxldriver/camera.py
...@@ -84,14 +107,11 @@ class Camera: ...@@ -84,14 +107,11 @@ class Camera:
text = text[0] text = text[0]
return text return text
def start(self, duration, frametype, datetime=Time.now().isot): def start(self, duration, frametype, datetime=datetime.utcnow().isoformat()):
params = {"Duration": duration, params = {"Duration": duration,
"FrameType": frametype, "FrameType": frametype,
"DateTime": datetime} "DateTime": datetime}
self.put("ImagerStartExposure", params=params) self.put("ImagerStartExposure", params=params)
def abort(self):
self.put("ImagerAbortExposure")
def download(self): def download(self):
res = requests.get(f"{self.addr}/Imager.FIT") res = requests.get(f"{self.addr}/Imager.FIT")
...@@ -158,8 +178,8 @@ class Camera: ...@@ -158,8 +178,8 @@ class Camera:
try: try:
ambient, setpoint, temperature, cool, fan, binx, biny, camx, camy, startx, starty, numx, numy = list(map(float,res[:13])) ambient, setpoint, temperature, cool, fan, binx, biny, camx, camy, startx, starty, numx, numy = list(map(float,res[:13]))
except TypeError as e: except (TypeError,ValueError) as e:
ambient, setpoint, temperature, cool, fan, binx, biny, camx, camy, startx, starty, numx, numy = [999,999,999,0,0,0,0,0,0,0,0,0,0] 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 cooler = True if cool else False
......
[
{
"template": "acquisition",
"params": {
"radec": [
11.81,
14.57
]
}
}
]
\ No newline at end of file
[
{
"template": "acquisition",
"params": {
"radec": [ 13.21, 18.16 ],
"offset": [ 0, 0 ]
}
},
{
"template": "observation",
"params": {
"objname": "M53",
"binning": 1,
"filter": "V",
"exptime": 600,
"repeat": 3
}
}
]
[
{
"template": "observation",
"params": {
"objname": "M92 test b",
"binning": 2,
"filter": "V",
"exptime": 3,
"repeat": 3,
"frametype": "Light",
"xystart": [
500,
500
],
"xyend": [
1250,
1250
]
}
}
]
\ No newline at end of file
[
{
"template": "observation",
"params": {
"objname": "Orma Fossile",
"binning": 1,
"filter": "V",
"exptime": 300,
"repeat": 3,
"frametype": "Light",
"xystart": [
0,
0
],
"xyend": [
4145,
4126
]
}
}
]
\ No newline at end of file
[
{
"template": "lampsoff",
"params": {}
}
]
\ No newline at end of file
[
{
"template" : "testpause",
"params": {
}
}
]