#!/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 #@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()