From 73c3152e2166397bea9ef1b6b333d191564d2f56 Mon Sep 17 00:00:00 2001 From: Ruben van de Ven Date: Tue, 23 Oct 2018 22:07:28 +0200 Subject: [PATCH] Initial server --- .gitignore | 5 +++ bin/server | 9 +++++ heartbeat/__init__.py | 0 heartbeat/export.py | 30 +++++++++++++++ heartbeat/frontend.py | 25 ++++++++++++ heartbeat/server.py | 38 +++++++++++++++++++ heartbeat/ws.py | 64 +++++++++++++++++++++++++++++++ views/plot.html | 88 +++++++++++++++++++++++++++++++++++++++++++ views/sockets.html | 77 +++++++++++++++++++++++++++++++++++++ 9 files changed, 336 insertions(+) create mode 100644 .gitignore create mode 100755 bin/server create mode 100644 heartbeat/__init__.py create mode 100644 heartbeat/export.py create mode 100644 heartbeat/frontend.py create mode 100644 heartbeat/server.py create mode 100644 heartbeat/ws.py create mode 100644 views/plot.html create mode 100644 views/sockets.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0c71c05 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +heartbeat.db +venv/ +*.pyc + + diff --git a/bin/server b/bin/server new file mode 100755 index 0000000..80a2a62 --- /dev/null +++ b/bin/server @@ -0,0 +1,9 @@ +#!/usr/bin/python3 +import sys, os +sys.path.append(os.path.dirname(os.path.realpath(__file__)) + '/..') + +from heartbeat import server + +if __name__ == '__main__': + args = {} + server.start(args) diff --git a/heartbeat/__init__.py b/heartbeat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/heartbeat/export.py b/heartbeat/export.py new file mode 100644 index 0000000..63915bd --- /dev/null +++ b/heartbeat/export.py @@ -0,0 +1,30 @@ +# TODO: web frontend +import os +import tornado.web + +class ExportHandler(tornado.web.RequestHandler): + + def initialize(self, conn): + """ + conn: sqlite3 connection + """ + self.conn = conn + + def get(self, range): + # TODO: userid + start time + c = self.conn.cursor() + self.set_header("Content-Type", 'text/csv') + ranges = { + '1h': '-1 hour', + '24h': '-1 day', + 'week': '-7 days', + } + if range not in ranges: + # Not found + raise tornado.web.HTTPError(404) + + c.execute("SELECT * FROM beats WHERE createdAt > date('now', ?) ORDER BY createdAt ASC", (ranges[range],)) + self.write("id,bpm,timestamp\n") + for row in c: + self.write("{},{},{}\n".format(row['id'], row['bpm'], row['createdAt'])) + self.flush() diff --git a/heartbeat/frontend.py b/heartbeat/frontend.py new file mode 100644 index 0000000..b32f963 --- /dev/null +++ b/heartbeat/frontend.py @@ -0,0 +1,25 @@ +# TODO: web frontend +import os +import tornado.web +import tornado.template + + +viewdir = os.path.dirname(os.path.realpath(__file__)) + '/../views' +loader = tornado.template.Loader(viewdir) + +class FrontendHandler(tornado.web.RequestHandler): + + def initialize(self, conn): + """ + conn: sqlite3 connection + """ + self.conn = conn + + def get(self): + # c = self.conn.cursor() + # c.execute("SELECT * FROM beats ORDER BY createdAt DESC LIMIT 20") + # beats = [row for row in c] + # print(beats) + # html = loader.load("sockets.html").generate(beats=beats) + html = loader.load("plot.html").generate() + self.write(html) diff --git a/heartbeat/server.py b/heartbeat/server.py new file mode 100644 index 0000000..5a23556 --- /dev/null +++ b/heartbeat/server.py @@ -0,0 +1,38 @@ +# import the libraries +import tornado.web +import tornado.ioloop +from .ws import WebSocketHandler +from .frontend import FrontendHandler +from .export import ExportHandler +import sqlite3 +import os + +def start(args): + basedir = os.path.dirname(os.path.realpath(__file__)) +'/../' + conn = sqlite3.connect(basedir + 'heartbeat.db') + + conn.cursor() + conn.row_factory = sqlite3.Row + c = conn.cursor() + c.execute(""" + CREATE TABLE IF NOT EXISTS beats ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + bpm INTEGER, + beatcount INTEGER, + beattime REAL, + createdAt TIMESTAMP DEFAULT (datetime('now','localtime')) + ); + """) + conn.commit() + + # start a new WebSocket Application + # use "/" as the root, and the + # WebSocketHandler as our handler + application = tornado.web.Application([ + (r"/", FrontendHandler, {"conn": conn}), + (r"/ws", WebSocketHandler, {"conn": conn}), + (r"/(.*).csv", ExportHandler, {"conn": conn}), + ],debug=True) + + application.listen(8888) + tornado.ioloop.IOLoop.instance().start() diff --git a/heartbeat/ws.py b/heartbeat/ws.py new file mode 100644 index 0000000..c95b091 --- /dev/null +++ b/heartbeat/ws.py @@ -0,0 +1,64 @@ +import tornado.websocket +import json + + +# This is our WebSocketHandler - it handles the messages +# from the tornado server +class WebSocketHandler(tornado.websocket.WebSocketHandler): + connections = set() + + def initialize(self, conn): + """ + conn: sqlite3 connection + """ + self.conn = conn + + # the client connected + def open(self): + self.connections.add(self) + print ("New client connected") + + # the client sent the message + def on_message(self, message): + # {"rate":"77*", "count":"4", "time":"14.44"} + try: + beat = json.loads(message) + except Exception as e: + print("Probably no valid JSON") + [con.write_message(message) for con in self.connections] + return False + + if beat['rate'].endswith('*'): + beat['rate'] = beat['rate'][:-1] + beat['rate'] = beat['rate'].strip() + if beat['count'].endswith('*'): + beat['count'] = beat['count'][:-1] + beat['count'] = beat['count'].strip() + if beat['time'].endswith('*'): + beat['time'] = beat['time'][:-1] + beat['time'] = beat['time'].strip() + + c = self.conn.cursor() + c.execute(""" + INSERT INTO beats (bpm,beatcount,beattime) + VALUES (:rate, :count, :time) + """, beat) + insertid = c.lastrowid + self.conn.commit() + + c.execute("SELECT createdAt FROM beats WHERE id = ?", (insertid,)) + now = c.fetchone()[0] + + beat['bpm'] = int(beat['rate']) + beat['timestamp'] = now + # actively close connection :-) + [con.write_message(json.dumps(beat)) for con in self.connections] + self.close() + + + + + # client disconnected + def on_close(self): + self.connections.remove(self) + print ("Client disconnected") diff --git a/views/plot.html b/views/plot.html new file mode 100644 index 0000000..b55776a --- /dev/null +++ b/views/plot.html @@ -0,0 +1,88 @@ + + + WebSocket Test + + + + +

current bpm: ...

+
+ Claim data + Last hour as CSV. + Last day as CSV. + Last week as CSV. +
+
+ +
+ diff --git a/views/sockets.html b/views/sockets.html new file mode 100644 index 0000000..3863a60 --- /dev/null +++ b/views/sockets.html @@ -0,0 +1,77 @@ + + + WebSocket Test + + +

WebSocket Test

+ + {% for beat in beats %} +
  • {{beat['id'] }}: {{beat['bpm'] }} / {{beat['beatcount'] }} / {{beat['beattime'] }} / {{beat['createdAt'] }}
  • + {% end %} + + + +