From 344cfbe60284b40057e525e71bb49e75a229a5f9 Mon Sep 17 00:00:00 2001 From: seanlu99 Date: Wed, 10 Jul 2019 18:28:07 -0700 Subject: [PATCH] Issue #107 - Implemented data sending with slider, created more accurate timer --- ait/gui/__init__.py | 129 ++++++++++---------------- ait/gui/static/js/ait/gui/Playback.js | 103 ++++++++++---------- 2 files changed, 102 insertions(+), 130 deletions(-) diff --git a/ait/gui/__init__.py b/ait/gui/__init__.py index e4a94c44..6b33b6fd 100644 --- a/ait/gui/__init__.py +++ b/ait/gui/__init__.py @@ -26,6 +26,7 @@ from ait.core.server.plugin import Plugin import copy from datetime import datetime +from Queue import Queue class Session (object): """Session @@ -986,6 +987,12 @@ def handle(): PromptResponse = json.loads(bottle.request.body.read()) +# Global variables for playback +playback_query = {} +playback_queue = Queue() +playback_wsock = None + + def playback_connect(): # Get datastore from config @@ -1036,111 +1043,69 @@ def handle(): return json.dumps(ranges) -def playback_query(packet, start_time, end_time): +@App.route('/playback/query', method='POST') +def handle(): + global playback_query + global playback_queue + playback_query = {} + playback_queue = Queue() dbconn = playback_connect() + tlm_dict = tlm.getDefaultDict() + + # Get values from form + packet = bottle.request.forms.get('packet') + start_time = bottle.request.forms.get('startTime') + end_time = bottle.request.forms.get('endTime') + uid = tlm_dict[packet].uid # Query from database point_query = 'SELECT * FROM "{}" WHERE time >= \'{}\' AND time <= \'{}\''.format(packet, start_time, end_time) points = list(dbconn.query(point_query).get_points()) - # Sort points - sorted_points = [] - tlm_dict = tlm.getDefaultDict() + # Put query into {timestamp: data} and sort according to tlm dictionary pkt = tlm_dict[packet] fields = pkt.fields - key_names = ["time"] + field_names = [] for i in range(len(fields)): - key_names.append(fields[i].name) + field_names.append(fields[i].name) for i in range(len(points)): - sorted_points.append([]) - for j in range(len(key_names)): - sorted_points[i].append(points[i][key_names[j]]) - - return sorted_points + timestamp = str(points[i]['time'][:21]) + data = bytearray(1) + struct.pack('>I', uid) + for j in range(len(field_names)): + data += struct.pack('>H', points[i][field_names[j]]) + if playback_query.has_key(timestamp): + playback_query[timestamp].append(data) + else: + playback_query[timestamp] = [data] -@App.route('/playback/query', method='POST') +@App.route('/playback/send', method='POST') def handle(): - packet = bottle.request.forms.get('packet') - start_time = bottle.request.forms.get('startTime') - end_time = bottle.request.forms.get('endTime') - bottle.response.set_cookie('form', (packet, start_time, end_time), secret='secretkey') - - # points = playback_query(packet, start_time, end_time) - - d1 = datetime.strptime(start_time, '%Y-%m-%dT%H:%M:%SZ') - d2 = datetime.strptime(end_time, '%Y-%m-%dT%H:%M:%SZ') - query_range = (d2 - d1).total_seconds() - - return json.dumps(query_range) - - -playback_wsock = None -playback_paused = False + global playback_query + global playback_queue + timestamp = bottle.request.forms.get('timestamp') + if (playback_query.has_key(timestamp)): + playback_queue.put(timestamp) @App.route('/playback/playback') def handle(): + global playback_query + global playback_queue global playback_wsock - global playback_paused - playback_paused = False - form = bottle.request.get_cookie('form', secret='secretkey') - packet = form[0] - start_time = form[1] - end_time = form[2] - points = playback_query(packet, start_time, end_time) - uid = tlm.getDefaultDict()[packet].uid - - - # Playback query as if it were realtime to client - # A null-byte pad ensures wsock is treated as binary. - pad = bytearray(1) + playback_wsock = bottle.request.environ.get('wsgi.websocket') if not playback_wsock: bottle.abort(400, 'Expected WebSocket request.') - # List of time intervals to wait - wait_intervals = [] - d_start = datetime.strptime(start_time, '%Y-%m-%dT%H:%M:%SZ') - d_first = datetime.strptime(points[0][0], '%Y-%m-%dT%H:%M:%S.%fZ') - wait_intervals.append((d_first - d_start).total_seconds()) - for i in range(len(points) - 1): - d1 = datetime.strptime(points[i][0], '%Y-%m-%dT%H:%M:%S.%fZ') - d2 = datetime.strptime(points[i + 1][0], '%Y-%m-%dT%H:%M:%S.%fZ') - wait_intervals.append((d2 - d1).total_seconds()) - d_last = datetime.strptime(points[len(points) - 1][0], '%Y-%m-%dT%H:%M:%S.%fZ') - d_end = datetime.strptime(end_time, '%Y-%m-%dT%H:%M:%SZ') - wait_intervals.append((d_end - d_last).total_seconds()) - - # Send data to socket - time.sleep(wait_intervals[0]) - for i in range(len(points)): - # Check if playback is paused at 100ms intervals - while playback_paused: - time.sleep(0.100) - - data = '' - for j in range(1, len(points[i])): - data += struct.pack('>H', points[i][j]) - try: - playback_wsock.send(pad + struct.pack('>I', uid) + data) - except geventwebsocket.WebSocketError: - pass - time.sleep(wait_intervals[i + 1]) - playback_wsock.closed = True - - -@App.route('/playback/play', method='PUT') -def handle(): - global playback_paused - playback_paused = False - - -@App.route('/playback/pause', method='PUT') -def handle(): - global playback_paused - playback_paused = True + # Send data from queue to socket + while playback_wsock.closed == False: + timestamp = playback_queue.get() + if (timestamp != None): + data_list = playback_query[timestamp] + for i in range(len(data_list)): + playback_wsock.send(data_list[i]) @App.route('/playback/abort', method='PUT') diff --git a/ait/gui/static/js/ait/gui/Playback.js b/ait/gui/static/js/ait/gui/Playback.js index 5ab6c961..849ecd6f 100644 --- a/ait/gui/static/js/ait/gui/Playback.js +++ b/ait/gui/static/js/ait/gui/Playback.js @@ -10,10 +10,10 @@ const Playback = { _end_time: null, _validation_errors: {}, _timeline: null, + _slider: null, _play: null, _pause: null, _abort: null, - _slider: null, _timer: null, oninit(vnode) { @@ -91,21 +91,11 @@ const Playback = { m('div', {class:'timeline-end'}, this._end_time) ]) - function move_right() { - ++vnode.dom.getElementsByClassName('slider')[0].value - } - this._play = m('button', {class: 'btn btn-success pull-right', onclick: (e) => { - if (!this._timer) - this._timer = setInterval(move_right, 1000) - - m.request({ - url: '/playback/play', - method: 'PUT' - }) + this.start_slider(vnode) }, style: 'display:none', id: 'playback-control' @@ -116,13 +106,7 @@ const Playback = { m('button', {class: 'btn btn-success pull-right', onclick: (e) => { - clearInterval(this._timer) - this._timer = null - - m.request({ - url: '/playback/pause', - method: 'PUT' - }) + this.stop_slider() }, style: 'display:none', id: 'playback-control' @@ -134,11 +118,10 @@ const Playback = { {class: 'btn btn-success pull-right', onclick: (e) => { vnode.dom.getElementsByClassName('timeline')[0].style.display = 'none' - clearInterval(this._timer) - this._timer = null + this.stop_slider() let buttons = vnode.dom.getElementsByClassName('btn btn-success pull-right') - for (var i = 0; i < buttons.length; ++i) { + for (let i = 0; i < buttons.length; ++i) { if (buttons[i].id == 'playback-control') buttons[i].style.display = 'none' if (buttons[i].id == 'playback-query') @@ -171,41 +154,38 @@ const Playback = { data.append('startTime', this._start_time) data.append('endTime', this._end_time) - vnode.dom.getElementsByClassName('slider')[0].min = Date.parse(this._start_time) / 1000 - vnode.dom.getElementsByClassName('slider')[0].max = Date.parse(this._end_time) / 1000 - vnode.dom.getElementsByClassName('slider')[0].value = 0 - vnode.dom.getElementsByClassName('timeline')[0].style.display = 'block' - if (!this._timer) - this._timer = setInterval(move_right, 1000) - - let buttons = vnode.dom.getElementsByClassName('btn btn-success pull-right') - for (var i = 0; i < buttons.length; ++i) { - if (buttons[i].id == 'playback-control') - buttons[i].style.display = 'block' - if (buttons[i].id == 'playback-query') - buttons[i].style.display = 'none' - } - m.request({ url: '/playback/query', method: 'POST', data: data - }).then((q) => { + }) - ait.tlm = {dict: {}} - ait.tlm.promise = m.request({ url: '/tlm/dict' }) - ait.tlm.promise.then((dict) => { - const proto = location.protocol === 'https:' ? 'wss' : 'ws' - const url = proto + '://' + location.host + '/playback/playback' + ait.tlm = {dict: {}} + ait.tlm.promise = m.request({ url: '/tlm/dict' }) + ait.tlm.promise.then((dict) => { + const proto = location.protocol === 'https:' ? 'wss' : 'ws' + const url = proto + '://' + location.host + '/playback/playback' - ait.tlm.dict = TelemetryDictionary.parse(dict) - ait.tlm.stream = new TelemetryStream(url, ait.tlm.dict) + ait.tlm.dict = TelemetryDictionary.parse(dict) + ait.tlm.stream = new TelemetryStream(url, ait.tlm.dict) - ait.events.on('ait:tlm:packet', () => { - m.redraw() - }) + ait.events.on('ait:tlm:packet', () => { + m.redraw() }) }) + + vnode.dom.getElementsByClassName('slider')[0].min = Date.parse(this._start_time) / 100 + vnode.dom.getElementsByClassName('slider')[0].max = Date.parse(this._end_time) / 100 + vnode.dom.getElementsByClassName('slider')[0].value = 0 + vnode.dom.getElementsByClassName('timeline')[0].style.display = 'block' + + let buttons = vnode.dom.getElementsByClassName('btn btn-success pull-right') + for (let i = 0; i < buttons.length; ++i) { + if (buttons[i].id == 'playback-control') + buttons[i].style.display = 'block' + if (buttons[i].id == 'playback-query') + buttons[i].style.display = 'none' + } }, }, [ m('h3', 'Query data from database'), @@ -220,6 +200,33 @@ const Playback = { ]) }, + start_slider(vnode) { + if (this._timer) return + let start = Date.now() + let difference = 0 + + this._timer = setInterval(function() { + let delta = Math.floor((Date.now() - start) / 100) + if (delta > difference) { + difference = delta + let current_time = ++vnode.dom.getElementsByClassName('slider')[0].value + let formatted_time = new Date(current_time * 100).toISOString().substring(0, 21) + let data = new FormData() + data.append('timestamp', formatted_time) + m.request({ + url: '/playback/send', + method: 'POST', + data: data + }) + } + },10) + }, + + stop_slider() { + clearInterval(this._timer) + this._timer = null + }, + _validate_form(form) { this._validation_errors = {}