Initial server

This commit is contained in:
Ruben van de Ven 2018-10-23 22:07:28 +02:00
commit 73c3152e21
9 changed files with 336 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
heartbeat.db
venv/
*.pyc

9
bin/server Executable file
View file

@ -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)

0
heartbeat/__init__.py Normal file
View file

30
heartbeat/export.py Normal file
View file

@ -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()

25
heartbeat/frontend.py Normal file
View file

@ -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)

38
heartbeat/server.py Normal file
View file

@ -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()

64
heartbeat/ws.py Normal file
View file

@ -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")

88
views/plot.html Normal file
View file

@ -0,0 +1,88 @@
<!DOCTYPE html>
<meta charset="utf-8" />
<title>WebSocket Test</title>
<style media="screen">
body{
font-family: Helvetica, sans-serif;
}
#graph{
height: calc(100vh - 5em);
}
</style>
<script src="https://cdn.plot.ly/plotly-1.5.0.min.js"></script>
<script language="javascript" type="text/javascript">
var wsUri = window.location.toString().replace('http','ws') + "ws";
console.log(wsUri)
</script>
<h2 id="title">current bpm: <span id='value'>...</span></h2>
<div id='downloads'>
Claim data
<span>Last hour <a href='/1h.csv'>as CSV</a>.</span>
<span>Last day <a href='/24h.csv'>as CSV</a>.</span>
<span>Last week <a href='/week.csv'>as CSV</a>.</span>
</div>
<div id="graph">
</div>
<script type="text/javascript">
var valueEl = document.getElementById('value');
Plotly.d3.csv("/1h.csv", function(err, rows){
console.log(rows);
function unpack(rows, key) {
return rows.map(function(row) { return row[key]; });
}
var trace1 = {
type: "scatter",
mode: "lines",
name: 'BPM',
x: unpack(rows, 'timestamp'),
y: unpack(rows, 'bpm'),
line: {color: '#17BECF'}
}
var data = [trace1];
var layout = {
title: 'Last hour',
};
Plotly.newPlot('graph', data, layout);
var wsUri = window.location.toString().replace('http','ws') + "ws";
var connectSocket = function() {
websocket = new WebSocket(wsUri);
websocket.onopen = function(evt) {
console.log("opened");
};
websocket.onclose = function(evt) {
console.log("closed", evt);
valueEl.innerHTML = '...';
setTimeout(connectSocket, 1000 * 10);
};
websocket.onmessage = function(evt) {
data = JSON.parse(evt.data);
var update = {
x: [[data['timestamp']]],
y: [[data['bpm']]]
}
console.log("update", update)
valueEl.innerHTML = data['bpm'];
Plotly.extendTraces('graph', update, [0])
};
websocket.onerror = function(evt) {
};
}
connectSocket();
});
</script>

77
views/sockets.html Normal file
View file

@ -0,0 +1,77 @@
<!DOCTYPE html>
<meta charset="utf-8" />
<title>WebSocket Test</title>
<script language="javascript" type="text/javascript">
var wsUri = window.location.toString().replace('http','ws') + "ws";
console.log(wsUri)
var output;
function init()
{
output = document.getElementById("output");
testWebSocket();
}
function testWebSocket()
{
websocket = new WebSocket(wsUri);
websocket.onopen = function(evt) { onOpen(evt) };
websocket.onclose = function(evt) { onClose(evt) };
websocket.onmessage = function(evt) { onMessage(evt) };
websocket.onerror = function(evt) { onError(evt) };
}
function onOpen(evt)
{
writeToScreen("CONNECTED");
// doSend("WebSocket rocks");
document.getElementById('send').disabled = false;
document.getElementById('disable').disabled = false;
}
function onClose(evt)
{
writeToScreen("DISCONNECTED");
document.getElementById('send').disabled = true;
document.getElementById('disable').disabled = true;
}
function onMessage(evt)
{
// writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>');
// websocket.close();
}
function onError(evt)
{
// writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
}
function doSend(message)
{
writeToScreen("SENT: " + message);
websocket.send(message);
}
function writeToScreen(message)
{
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
output.appendChild(pre);
}
window.addEventListener("load", init, false);
</script>
<h2>WebSocket Test</h2>
{% for beat in beats %}
<li>{{beat['id'] }}: {{beat['bpm'] }} / {{beat['beatcount'] }} / {{beat['beattime'] }} / {{beat['createdAt'] }}</li>
{% end %}
<input type='text' id='msg' placeholder="message"> <button id='send' disabled onclick='doSend(document.getElementById("msg").value)'>Send message</button>
<button disabled onclick='websocket.close()' id='disable'>close connection</button>
<div id="output"></div>