273 lines
7.3 KiB
Python
Executable File
273 lines
7.3 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',
|
|
'sequence': [],
|
|
}
|
|
|
|
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
|
|
|
|
def updateSequence(self, **data):
|
|
print('updateSequence')
|
|
state['sequence'] = data['sequence']
|
|
with open('sequence.json', 'w') as fd:
|
|
json.dump(state['sequence'], fd, indent=4, ensure_ascii=False)
|
|
|
|
def getSequence(self, **data):
|
|
result = {}
|
|
result['sequence'] = state['sequence']
|
|
return result
|
|
|
|
def wipe(self, **data):
|
|
result = {}
|
|
ctl.camera.wipe()
|
|
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 load_sequence():
|
|
if os.path.exists('sequence.json'):
|
|
try:
|
|
with open('sequence.json') as fd:
|
|
data = json.load(fd)
|
|
except:
|
|
data = []
|
|
state['sequence'] = data
|
|
|
|
def main():
|
|
global ctl
|
|
ctl = ControlQueue()
|
|
load_sequence()
|
|
|
|
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()
|