cccc/server.py
2020-12-05 12:56:56 +01:00

246 lines
6.6 KiB
Python
Executable File

#!/usr/bin/python3
from functools import wraps
from urllib.parse import unquote
import json
import logging
import os
import queue
import shutil
import threading
import time
import sys
import requests
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import StaticFileHandler, Application, HTTPError
import tornado.gen
import tornado.web
from camera import Camera
logger = logging.getLogger(__name__)
STATIC_PATH = 'static'
PORT = 8000
ADDRESS = '127.0.0.1'
ADDRESS = '0.0.0.0'
with open('camera.json') as fd:
CAMERA = json.load(fd)
state = {
'time': {},
'status': 'Idle'
}
def run_async(func):
@wraps(func)
def async_func(*args, **kwargs):
func_hl = Thread(target=func, args=args, kwargs=kwargs)
func_hl.start()
return func_hl
return async_func
def _to_json(python_object):
if isinstance(python_object, datetime.datetime):
if python_object.year < 1900:
tt = python_object.timetuple()
return '%d-%02d-%02dT%02d:%02d%02dZ' % tuple(list(tt)[:6])
return python_object.strftime('%Y-%m-%dT%H:%M:%SZ')
raise TypeError('%s %s is not JSON serializable' % (repr(python_object), type(python_object)))
def json_dumps(obj):
return json.dumps(obj, indent=4, default=_to_json, ensure_ascii=False, sort_keys=True).encode()
class ControlQueue:
shutdown = False
def worker(self):
while True:
item = self.q.get()
if item is None or self.shutdown:
break
state['status'] = 'Active'
self.camera.sequence(**item)
if self.camera.abort:
self.camera.abort = False
state['status'] = 'Idle'
def __init__(self):
self.q = queue.Queue()
self.camera = Camera(**CAMERA)
self._worker = threading.Thread(target=self.worker)
self._worker.start()
def put(self, command):
if self.q.empty():
self.camera.abort = False
self.q.put(command)
def join(self):
self.shutdown = True
self.camera.abort = True
# block until all tasks are done
self.q.join()
self.q.put(None)
self._worker.join()
class API(object):
def __call__(self, method, kwargs):
return getattr(self, method)(**kwargs)
def getPresets(self, **data):
result = {}
result['presets'] = ctl.camera.get_presets(True)
return result
def setPreset(self, **data):
result = {}
print('setPreset', data)
ctl.camera.set_preset(data['id'], data['name'])
return result
def setPresets(self, **data):
result = {}
ctl.camera.set_presets(data['presets'])
result['presets'] = ctl.camera.get_presets(True)
return result
def camera(self, **data):
result = {}
for key, value in data.items():
result[key] = getattr(ctl.camera, key)(**value)
return result
def run(self, **data):
result = {}
ctl.put(data)
with open('last_run.json', 'w') as fd:
json.dump(data, fd, indent=4)
return result
def move(self, **data):
result = {}
if data.get('direction') in (
'LEFT', 'RIGHT', 'UP', 'DOWN',
'LEFT_UP', 'LEFT_DOWN',
'RIGHT_UP', 'RIGHT_DOWN',
'IN', 'OUT',
):
direction = getattr(ctl.camera, data['direction']).copy()
speed = int(data.get('speed', 1))
for key in direction:
direction[key] *= speed
print('move', direction)
ctl.camera.momentary(direction, float(data.get('duration', 1)))
return result
def stop(self, **data):
result = {}
ctl.camera.abort = True
ctl.camera.continuous(ctl.camera.STOP)
return result
def status(self, **data):
result = {}
result['time'] = ctl.camera.segment_times
result['status'] = state['status']
if result['status'] == 'Active':
if ctl.camera.sequence_start:
result['duration'] = int(time.time() - ctl.camera.sequence_start)
else:
result['duration'] = '...'
result['next'] = ctl.camera.next_target
else:
result['duration'] = ctl.camera.sequence_time
result['position'] = ctl.camera.last_position
return result
#@run_async
def api_task(request, callback):
api = API()
response = {
'result': api(request['method'], request.get('params', {}))
}
callback(response)
class RPCHandler(tornado.web.RequestHandler):
def get(self):
self.write(BANNER)
@tornado.gen.coroutine
def post(self):
error = None
request = None
try:
request = json.loads(self.request.body.decode())
if request['method'][0] == '_' or not hasattr(API, request['method']):
raise Exception('unknown method')
except:
error = {'error': {'code': -32700, 'message': 'Parse error'}}
if not error:
try:
response = yield tornado.gen.Task(api_task, request)
except KeyboardInterrupt:
raise
except:
logger.error("ERROR: %s", request, exc_info=True)
error = {'error': {'code': -32000, 'message': 'Server error'}}
if error:
response = error
if request and 'id' in request:
response['id'] = request['id']
response['jsonrpc'] = '2.0'
response = json_dumps(response)
self.write(response)
class MainHandler(tornado.web.RequestHandler):
def get(self, path):
path = os.path.join(STATIC_PATH, 'index.html')
with open(path) as fd:
content = fd.read()
self.set_header('Content-Type', 'text/html')
self.set_header('Content-Length', str(len(content)))
self.set_header('Cache-Control', 'no-cache, no-store, must-revalidate')
self.set_header('Pragma', 'no-cache')
self.set_header('Expires', '0')
self.write(content)
def main():
global ctl
ctl = ControlQueue()
handlers = [
(r'/api/', RPCHandler),
(r'/static/(.*)', StaticFileHandler, {'path': STATIC_PATH}),
(r"(.*)", MainHandler),
]
options = {
'debug': False,
'gzip': True,
}
app = Application(handlers, **options)
print('listening on http://%s:%s/' % (ADDRESS, PORT))
app.listen(PORT, ADDRESS)
main = IOLoop.instance()
try:
main.start()
except:
print('shutting down...')
ctl.join()
if __name__ == '__main__':
main()