Improved API & added web version
This commit is contained in:
parent
a26666071a
commit
1067333518
|
@ -1,5 +1,6 @@
|
||||||
from bottle import route, template, error, run, ServerAdapter
|
from bottle import route, template, error, run, get, ServerAdapter, static_file
|
||||||
from departures import Departure
|
from departures import Departure
|
||||||
|
from operator import itemgetter
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
class Server(ServerAdapter):
|
class Server(ServerAdapter):
|
||||||
|
@ -16,15 +17,20 @@ class Server(ServerAdapter):
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.server.shutdown()
|
self.server.shutdown()
|
||||||
|
|
||||||
server = Server(port=8080)
|
server = Server(host="0.0.0.0", port=8080)
|
||||||
|
|
||||||
@route('/departures')
|
@route('/departures')
|
||||||
def index():
|
def index():
|
||||||
resp = []
|
resp = []
|
||||||
for d in Departure.storage:
|
for d in Departure.storage:
|
||||||
resp.append(Departure.storage[d].json())
|
resp.append(Departure.storage[d].json())
|
||||||
|
resp.sort(key=itemgetter("departure"))
|
||||||
return {'departures': resp}
|
return {'departures': resp}
|
||||||
|
|
||||||
|
@get("/")
|
||||||
|
def static():
|
||||||
|
return static_file("index.html", root="static")
|
||||||
|
|
||||||
@error(404)
|
@error(404)
|
||||||
def error404(error):
|
def error404(error):
|
||||||
return ''
|
return ''
|
||||||
|
|
|
@ -91,7 +91,7 @@ class Departure:
|
||||||
self.delay = delay
|
self.delay = delay
|
||||||
|
|
||||||
def get_departure(self):
|
def get_departure(self):
|
||||||
departure = round(self.get_accurate_departure()*10)
|
departure = floor(self.get_accurate_departure()*10)
|
||||||
return departure
|
return departure
|
||||||
|
|
||||||
def get_accurate_departure(self):
|
def get_accurate_departure(self):
|
||||||
|
@ -106,5 +106,6 @@ class Departure:
|
||||||
'line': self.line,
|
'line': self.line,
|
||||||
'type': self.type,
|
'type': self.type,
|
||||||
'last_stop': self.last_stop,
|
'last_stop': self.last_stop,
|
||||||
'departure': round(self.get_accurate_departure())
|
'departure': floor(self.get_accurate_departure()),
|
||||||
|
'delay': self.delay
|
||||||
}
|
}
|
|
@ -57,7 +57,7 @@ class LoraController:
|
||||||
while True:
|
while True:
|
||||||
if not len(self.message_pool):
|
if not len(self.message_pool):
|
||||||
break
|
break
|
||||||
data = self.message_pool.pop()
|
data = self.message_pool.pop(0)
|
||||||
url = f"https://lora.plzen.eu/api/v2/nodes/{data[0]:0>16x}/queue"
|
url = f"https://lora.plzen.eu/api/v2/nodes/{data[0]:0>16x}/queue"
|
||||||
string = data[2]()
|
string = data[2]()
|
||||||
headers = CaseInsensitiveDict()
|
headers = CaseInsensitiveDict()
|
||||||
|
|
|
@ -40,30 +40,35 @@ class MainLoop:
|
||||||
sleep(1)
|
sleep(1)
|
||||||
if self.ended:
|
if self.ended:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
print("Stopping...")
|
||||||
|
server.stop()
|
||||||
|
self.ended = True
|
||||||
|
self.controller.message_pool = []
|
||||||
|
|
||||||
def input_loop(self):
|
def input_loop(self):
|
||||||
while not self.ended:
|
try:
|
||||||
print("> ", end="")
|
while not self.ended:
|
||||||
command = input()
|
print("> ", end="")
|
||||||
|
command = input()
|
||||||
|
|
||||||
if command in ["d", "dep", "departures"]:
|
if command in ["d", "dep", "departures"]:
|
||||||
for did in Departure.storage:
|
for did in Departure.storage:
|
||||||
print(Departure.storage[did])
|
print(Departure.storage[did])
|
||||||
if len(Departure.storage) == 0:
|
if len(Departure.storage) == 0:
|
||||||
print("No loaded departures")
|
print("No loaded departures")
|
||||||
|
|
||||||
if command in ["q", "queue"]:
|
if command in ["q", "queue"]:
|
||||||
for m in self.controller.message_pool:
|
for m in self.controller.message_pool:
|
||||||
print(m)
|
print(m)
|
||||||
if len(self.controller.message_pool) == 0:
|
if len(self.controller.message_pool) == 0:
|
||||||
print("No messages in queue")
|
print("No messages in queue")
|
||||||
|
|
||||||
if command in ["s", "stop"]:
|
if command in ["s", "stop"]:
|
||||||
print("Stopping...")
|
self.stop()
|
||||||
server.stop()
|
except KeyboardInterrupt:
|
||||||
self.ended = True
|
self.stop()
|
||||||
self.controller.message_pool = []
|
|
||||||
|
|
||||||
main_loop = MainLoop("40", lora_controller)
|
main_loop = MainLoop("40", lora_controller)
|
||||||
main_loop.input_loop()
|
main_loop.input_loop()
|
95
server/static/index.html
Normal file
95
server/static/index.html
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Odjezdová tabule</title>
|
||||||
|
<style>
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--site-bg: #1C1C1C;
|
||||||
|
--site-alt-bg: #272727;
|
||||||
|
}
|
||||||
|
body {background-color: var(--site-bg); color: #fffd; margin: 0; margin-top: 35px; font-family: sans-serif; box-sizing: border-box;}
|
||||||
|
|
||||||
|
.container {max-width: 700px; padding: 25px; margin: auto;}
|
||||||
|
.departure-grid {display: grid; grid-template-columns: 30px 1fr max-content max-content; gap: 10px 20px; line-height: 31px;}
|
||||||
|
|
||||||
|
.header {line-height: 100%; opacity: .5; margin-bottom: 10px; font-size: 90%;}
|
||||||
|
|
||||||
|
.line {background-color: var(--site-alt-bg); border-radius: 3px; box-shadow: 0 0 5px 0 #0004; text-align: center; font-weight: 700; width: 30px; height: 30px; color: #fff; text-shadow: 0 0 15px #000;}
|
||||||
|
.last-stop {font-weight: 700;}
|
||||||
|
.departure {text-align: right;}
|
||||||
|
.delay {text-align: right; color: #ff6e6e; font-size: 80%;}
|
||||||
|
|
||||||
|
.line.type1 {background-color: #F0BE32;}
|
||||||
|
.line.type2 {background-color: #1E9641;}
|
||||||
|
.line.type3 {background-color: #CD2837;}
|
||||||
|
|
||||||
|
h3 {color: #fff;}
|
||||||
|
|
||||||
|
.nav {position: fixed; top: 0; left: 0; width: 100%; z-index: 10;}
|
||||||
|
.nav .inner {background: linear-gradient(to bottom, var(--site-bg) 70%, transparent 100%); padding: 5px 25px; margin: auto; max-width: 700px; padding-bottom: 25px; transition: padding .1s;}
|
||||||
|
.nav.top .inner {padding-bottom: 0px;}
|
||||||
|
.nav .inner::after {content: ""; position: fixed; bottom: 0; left: 0; height: 20px; width: 100%; background: linear-gradient(to top, var(--site-bg) 0%, transparent 100%)}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container" id="app">
|
||||||
|
<div class="nav" :class="{'top': top == 0}">
|
||||||
|
<div class="inner">
|
||||||
|
<h3>Borský park</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="departure-grid">
|
||||||
|
<div class="header">spoj</div>
|
||||||
|
<div class="header">poslední zastávka</div>
|
||||||
|
<div class="header">zpoždění</div>
|
||||||
|
<div class="header">odjezd</div>
|
||||||
|
<template v-for="d in departures">
|
||||||
|
<template v-if="d.departure >= 0">
|
||||||
|
<div class="line" :class="'type'+d.type">{{ d.line }}</div>
|
||||||
|
<div class="last-stop">{{ d.last_stop }}</div>
|
||||||
|
<div class="delay">{{ d.delay == 0 ? '' : '+'+d.delay }}</div>
|
||||||
|
<div class="departure">{{ d.departure == 0 ? '<1' : d.departure }}</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
const { createApp } = Vue;
|
||||||
|
let app = createApp({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
top: 0,
|
||||||
|
departures: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).mount('#app');
|
||||||
|
async function api() {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
if(this.readyState !== 4) return;
|
||||||
|
if(!this.responseText) reject(this);
|
||||||
|
try {
|
||||||
|
resolve(JSON.parse(this.responseText));
|
||||||
|
} catch(e) {
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.open("GET", "/departures", true);
|
||||||
|
xhr.send();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
window.addEventListener("scroll", () => {
|
||||||
|
app.$data.top = document.querySelector('html').scrollTop;
|
||||||
|
})
|
||||||
|
async function update() {
|
||||||
|
app.$data.departures = (await api()).departures;
|
||||||
|
}
|
||||||
|
setInterval(update, 5000);
|
||||||
|
update();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in a new issue