From 734a7371f36db43c7e12867c9fa50da405bfa6c2 Mon Sep 17 00:00:00 2001 From: Mike Lang Date: Thu, 9 Nov 2023 05:02:14 +1100 Subject: [PATCH] Add bus_data DB table and have thrimshim able to query it for latest odo reading --- postgres/setup.sh | 18 ++++++++++++ thrimshim/setup.py | 1 + thrimshim/thrimshim/main.py | 55 +++++++++++++++++++++++++++++++++++-- 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/postgres/setup.sh b/postgres/setup.sh index 0decea6..793e0b2 100644 --- a/postgres/setup.sh +++ b/postgres/setup.sh @@ -149,6 +149,24 @@ CREATE TABLE playlists ( tags TEXT[] NOT NULL, show_in_description BOOLEAN NOT NULL ); + +-- This table records time series data gleaned from the bus cam (right now, just the odometer). +-- Each record indicates a timestamp and value, as well as the channel/segment file it was sourced from. +-- Note the values are nullable and NULL indicates the value was indeterminate at that time. +-- The "error" column records a free-form human readable message about why a value could not +-- be determined. +-- The odometer column is in miles. The game shows the odometer to the 1/10th mile precision. +CREATE TABLE bus_data ( + timestamp TIMESTAMP NOT NULL, + channel TEXT NOT NULL, + segment TEXT, + error TEXT, + odometer DOUBLE PRECISION, +); + +-- Range index on timestamp as we will often want the closest timestamp to a requested point. +-- Note btree is the default anyway but we use it explicitly here as we want the range behaviour. +CREATE INDEX bus_data_timestamp ON bus_data USING btree (timestamp); EOSQL if [ -a /mnt/wubloader/nodes.csv ]; then diff --git a/thrimshim/setup.py b/thrimshim/setup.py index 07bf5e9..fedad28 100644 --- a/thrimshim/setup.py +++ b/thrimshim/setup.py @@ -11,6 +11,7 @@ setup( "google-auth", "psycogreen", "psycopg2", + "python-dateutil", "requests", "wubloader-common", ], diff --git a/thrimshim/thrimshim/main.py b/thrimshim/thrimshim/main.py index 9d341ec..cecddf3 100644 --- a/thrimshim/thrimshim/main.py +++ b/thrimshim/thrimshim/main.py @@ -16,7 +16,7 @@ import psycopg2 from psycopg2 import sql import common -from common import database +from common import database, dateutil from common.flask_stats import request_stats, after_request import google.oauth2.id_token @@ -517,8 +517,57 @@ def reset_row(ident, editor=None): if results.rowcount != 1: return 'Row id = {} not found or not in cancellable state'.format(ident), 404 logging.info("Row {} reset to 'UNEDITED'".format(ident)) - return '' - + return '' + + +@app.route('/thrimshim/odometer/') +@request_stats +def get_odometer(channel): + """Not directly thrimbletrimmer related but easiest to put here as we have DB access. + Checks DB for the most recent odometer reading as of `time` param (default now). + However, won't consider readings older than `range` param (default 1 minute) + You can also pass `extrapolate`=`true` to try to extrapolate the reading at your requested time + based on the last known time. Note it will still only look within `range` for the last known time. + If it can't find a reading, returns 0. + """ + time = flask.request.args.get("time") + if time is None: + time = datetime.datetime.utcnow() + else: + time = dateutil.parse(time) + + range = int(flask.request.args.get("range", "60")) + range = datetime.timedelta(seconds=range) + + extrapolate = (flask.request.args.get("extrapolate") == "true") + + start = time - range + end = time + + conn = app.db_manager.get_conn() + # Get newest non-errored row within time range + results = database.query(conn, """ + SELECT timestamp, odometer + FROM bus_data + WHERE odometer IS NOT NULL + AND channel = %(channel)s + AND timestamp > %(start)s + AND timestamp <= %(end)s + ORDER BY timestamp DESC + LIMIT 1 + """, channel, start, end) + result = results.fetchone() + if result is None: + # By Sokar's request, we want to return an invalid value rather than an error response. + return "0" + if not extrapolate: + return str(result.odometer) + # Current extrapolate strategy is very simple: presume we're going at full speed (45mph). + SPEED = 45. / 3600 # in miles per second + delta_t = time - timestamp + delta_odo = delta_t * SPEED + return str(result.odometer + delta_odo) + @argh.arg('--host', help='Address or socket server will listen to. Default is 0.0.0.0 (everything on the local machine).') @argh.arg('--port', help='Port server will listen on. Default is 8004.')