Skip to content
......@@ -3,88 +3,56 @@
'''REST API for IP camera related operations'''
# System modules
import time
# Third-party modules
from astropy.time import Time
from flask import Response, make_response, render_template, request
from flask_restx import Namespace, Resource, fields
# Custom modules
import devices
from flask import request
############################
# REST API
############################
from .baseresource import ResourceDev
api = Namespace('webcam', description='Dome Webcam related operations')
@api.route("/position")
class Pointing(Resource):
# @api.route("/position")
class Pointing(ResourceDev):
"""Position of the webcam."""
def get(self):
"""Retrieve the alt az coordinates of the webcam."""
res = {
"response": devices.ipcam.altaz,
"error": devices.ipcam.error,
"response": self.dev.altaz,
"error": self.dev.error,
"timestamp": self.timestamp,
}
return res
def put(self):
"""Set new alt az coordinates of the webcam."""
target = api.payload
devices.ipcam.altaz = target
target = request.json
self.dev.altaz = target
res = {
"response": devices.ipcam.altaz,
"error": devices.ipcam.error,
"response": self.dev.altaz,
"error": self.dev.error,
"timestamp": self.timestamp,
}
return res
@api.route("/snapshot")
class Snapshot(Resource):
# @api.route("/snapshot")
class Snapshot(ResourceDev):
"""Image from the webcam."""
def __init__(self, *args, **kwargs):
'''Constructor.'''
super().__init__(self)
self.last = None
def get(self):
"""Retrieve a raw base/64 image from the webcam."""
img = devices.ipcam.image
img = self.dev.image
if not img:
code = 401
elif devices.ipcam.error:
elif self.dev.error:
code = 501
else:
code = 200
res = {
"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
# -*- coding: utf-8 -*-
"""API server, with a web client for free"""
# System modules
import configparser
import logging
import time
# Third-party modules
from flask import Flask
from flask import Flask, request
from flask_cors import CORS
# Custom modules
from api.scheduler import scheduler
from api import api_blueprint as api
from api import web_blueprint as web
from web.server import instance
app = Flask(__name__)
app.register_blueprint(api, url_prefix='/api')
app.threaded = True
cors = CORS(app)
# scheduler.api_enabled = True
scheduler.init_app(app)
# Load the config file
ends = configparser.ConfigParser()
ends.read('./config/api.ini')
app.register_blueprint(api, url_prefix='/api')
app.register_blueprint(web, url_prefix='/web')
@app.route('/all/<string:namespace>')
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__":
# System modules
import socket
import sys
with_web = True
try:
port = sys.argv[1]
hostname = sys.argv[2]
except IndexError:
arg = sys.argv[1]
if arg.startswith("--noweb"):
with_web = False
port = 5533
else:
port = arg
except IndexError as e:
port = 5533
oarpaf_ip = "10.185.119.108"
this_ip = socket.getfqdn()
# hostname = "fork.orsa.unige.net"
# hostname = oarpaf_ip if this_ip==oarpaf_ip else this_ip
hostname = oarpaf_ip
print(f"hostname {hostname}")
app.run(host=hostname, port=port, threaded=True, debug=False)
hostname = "0.0.0.0"
print(f"hostname {hostname}")
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
class = Rotator
node = CABINET
[tel_temp]
module = astelco
class = Sensor
node = CABINET
outlet1 = 1
outlet2 = 2
[dom]
module = alpaca
class = Dome
......@@ -93,7 +100,7 @@ outlet2 = 6
[ipcam]
module = ipcam
class = DlinkDCSCamera
class = Webcam
node = IPCAM
[met]
......
......@@ -76,4 +76,3 @@ password = BigBang2021
ip = 10.185.119.106
hostname = ipcam.orsa.unige.net
port = 80
......@@ -10,6 +10,9 @@ import importlib
import configparser
import sys
# Custom modules
from utils.url_stuff import build_url
this_module = sys.modules[__name__]
nodes = configparser.ConfigParser()
......@@ -19,12 +22,12 @@ devs.read('./config/devices.ini')
def dynamic_import(this, dev):
"""Dynamically import into this module the devices
from the config files.
from the nodes.ini and devices.ini files.
Old way:
# from config.addresses import ASCOM_REMOTE
# 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):
cls = getattr(module, devs.get(dev, "class"))
node = devs.get(dev, "node")
itn = dict(nodes.items(node))
user = itn.get("user") or ""
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
itn = dict(nodes.items(node))
url = build_url(itn)
instance_name = dev
# Extract additional parameters based on device type
......
......@@ -24,7 +24,7 @@ class OpenTSI(BaseDevice):
self.url = url.split(":")[0] or url
self.port = url.split(":")[1] or 22
self.timeout = 3
self.connection = None
self.connection = True
@check.telnet_errors
def _connect(self):
......@@ -200,15 +200,6 @@ class Telescope(OpenTSI):
res = self.get(message)
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
def status(self):
'''need to check exit and error from get function'''
......@@ -485,7 +476,6 @@ class Rotator(OpenTSI):
@property
def position(self):
'''Get Relative rotator position from telnet'''
message = "POSITION.INSTRUMENTAL.DEROTATOR[2].OFFSET"
res = self.get(message)
self._position = res
......@@ -509,3 +499,25 @@ class Rotator(OpenTSI):
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
# Third-party modules
import requests
from astropy.time import Time
# Custom modules
from devices.basedevice import BaseDevice
from utils import check
from utils.logger import log
class DlinkDCSCamera:
class DlinkDCSCamera(BaseDevice):
'''Base wrapper class for Dlink DCS 5020l cameras.'''
def __init__(self, url):
......@@ -60,11 +60,14 @@ class DlinkDCSCamera:
return value
def time_to_string(self, time):
'''Convert a datetime into the HH:MM:SS string format.'''
return datetime.strftime(time, '%H:%M:%S')
# def time_to_string(self, time):
# '''Convert a datetime into the HH:MM:SS string format.'''
# return datetime.strftime(time, '%H:%M:%S')
# OK
class Webcam(DlinkDCSCamera):
'''Implementation of the Telescope commands mocking my Alpaca
Telescope wrapper.'''
@property
def description(self):
......
......@@ -18,7 +18,8 @@ class Meteo:
def __init__(self, url):
self.url = url
self.error = None
self.station = None
@check.telnet_errors
def connect(self):
self.station = VantagePro2.from_url(self.url)
......
......@@ -7,27 +7,43 @@ Interface with a SBIG STX camera device
# System modules
from urllib.parse import urlencode
from datetime import datetime
# Third-party modules
import requests
from astropy.time import Time
# Custom modules
from devices.basedevice import BaseDevice
from config.constants import temp_fits
from utils import check
from utils.logger import log
class Camera:
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.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
def get(self, method, params=[]):
'''Send a HTTP GET request to the device address.'''
......@@ -35,7 +51,7 @@ class Camera:
res = requests.get(f"{self.addr}/{method}.cgi",
params="&".join(params),
timeout=self.timeout, verify=False)
# res.raise_for_status()
value = res.text.split("\r\n")
if not value[-1]:
value = value[:-1]
......@@ -68,6 +84,13 @@ class Camera:
text = text[0]
return text
class Camera(STX):
'''Camera device based on STX cameras.'''
def abort(self):
self.put("ImagerAbortExposure")
@check.request_errors
def abort2(self):
# https://github.com/dkirkby/STXLDriver/blob/master/stxldriver/camera.py
......@@ -84,14 +107,11 @@ class Camera:
text = text[0]
return text
def start(self, duration, frametype, datetime=Time.now().isot):
def start(self, duration, frametype, datetime=datetime.utcnow().isoformat()):
params = {"Duration": duration,
"FrameType": frametype,
"DateTime": datetime}
self.put("ImagerStartExposure", params=params)
def abort(self):
self.put("ImagerAbortExposure")
def download(self):
res = requests.get(f"{self.addr}/Imager.FIT")
......@@ -158,8 +178,8 @@ class Camera:
try:
ambient, setpoint, temperature, cool, fan, binx, biny, camx, camy, startx, starty, numx, numy = list(map(float,res[:13]))
except TypeError 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]
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
......
[
{
"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": {
}
}
]