Add station departure display
This commit is contained in:
parent
7d6adcd1ec
commit
56695e7874
13 changed files with 457 additions and 88 deletions
|
@ -1,76 +0,0 @@
|
|||
import logging
|
||||
from datetime import datetime
|
||||
from threading import Thread
|
||||
from time import sleep
|
||||
|
||||
from buba.BubaCmd import BubaCmd
|
||||
|
||||
|
||||
class BubaAnimation:
|
||||
def __init__(self, buba: BubaCmd):
|
||||
self.buba = buba
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{type(self).__name__}>"
|
||||
|
||||
|
||||
class BubaTime(BubaAnimation):
|
||||
def __init__(self, buba: BubaCmd):
|
||||
super().__init__(buba)
|
||||
|
||||
def run(self):
|
||||
self.buba.simple_text(page=0, row=0, col=0, text="Buba")
|
||||
self.buba.simple_text(page=0, row=1, col=0, text="Choas Computer Club")
|
||||
self.buba.simple_text(page=0, row=2, col=0, text="Hansestadt Hamburg")
|
||||
self.buba.simple_text(page=0, row=3, col=0, text="Hello, world!")
|
||||
self.buba.set_page(0)
|
||||
|
||||
for i in range(5):
|
||||
self.buba.simple_text(page=0, row=0, col=93, text=datetime.now().strftime("%H:%M:%S"))
|
||||
sleep(1)
|
||||
|
||||
|
||||
class BubaCharset(BubaAnimation):
|
||||
def __init__(self, buba: BubaCmd, single=None):
|
||||
super().__init__(buba)
|
||||
self.charset = bytes(range(256)).decode("CP437")
|
||||
self.single = single
|
||||
|
||||
def run(self):
|
||||
if self.single is not None:
|
||||
while True:
|
||||
self.render(self.single)
|
||||
sleep(1)
|
||||
else:
|
||||
for page in range(16):
|
||||
self.render(page)
|
||||
sleep(5)
|
||||
|
||||
def render(self, page):
|
||||
for row in range(4):
|
||||
start = ((page + row) * 16) % 256
|
||||
self.buba.simple_text(page=0, row=row, col=0, text=f"${start:02x}: {self.charset[start:start + 16]}")
|
||||
|
||||
class BubaAnimator:
|
||||
def __init__(self, buba: BubaCmd):
|
||||
self.log = logging.getLogger(__name__)
|
||||
self.buba = buba
|
||||
self.animations = []
|
||||
Thread(target=self.run, daemon=True).start()
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
if len(self.animations) == 0:
|
||||
a = BubaTime(self.buba)
|
||||
a.run()
|
||||
else:
|
||||
for a in self.animations:
|
||||
self.log.debug(f"Starting animation: {a}")
|
||||
a.run()
|
||||
|
||||
def add(self, animation, *args, **kwargs):
|
||||
self.animations.append(animation(self.buba, *args, **kwargs))
|
|
@ -5,9 +5,12 @@ from bottle_log import LoggingPlugin
|
|||
from bottle_websocket import websocket, GeventWebSocketServer
|
||||
from geventwebsocket.websocket import WebSocket
|
||||
|
||||
from buba.AppConfig import AppConfig
|
||||
from buba.BubaAnimator import BubaAnimator, BubaTime, BubaCharset
|
||||
from buba.BubaCmd import BubaCmd
|
||||
from buba.appconfig import AppConfig
|
||||
from buba.bubaanimator import BubaAnimator
|
||||
from buba.animations.time import BubaTime
|
||||
from buba.bubacmd import BubaCmd
|
||||
|
||||
from buba.animations.dbf import DBFAnimation
|
||||
from buba.websocketcomm import WebSocketClients
|
||||
|
||||
config = AppConfig()
|
||||
|
@ -25,10 +28,10 @@ TEMPLATE_PATH.insert(0, config.templatepath)
|
|||
websocket_clients = WebSocketClients()
|
||||
buba = BubaCmd(config.serial, websocket_clients.send)
|
||||
animator = BubaAnimator(buba)
|
||||
animator.add(BubaCharset) #, single=12)
|
||||
# animator.add(BubaCharset) #, single=12)
|
||||
animator.add(BubaTime)
|
||||
# bottle_helpers = BottleHelpers(auth, group=config.requires_group, allowed=config.allowed)
|
||||
# update_poller = UpdatePoller(websocket_clients, ccujack, 1 if config.debug else 0.1)
|
||||
animator.add(DBFAnimation, ds100="AHST", station="Holstenstraße")
|
||||
animator.add(DBFAnimation, ds100="AHS", station="Altona", count=9)
|
||||
|
||||
|
||||
@app.route("/static/<filepath>")
|
||||
|
|
0
buba/animations/__init__.py
Normal file
0
buba/animations/__init__.py
Normal file
26
buba/animations/charset.py
Normal file
26
buba/animations/charset.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
from time import sleep
|
||||
|
||||
from buba.bubaanimator import BubaAnimation
|
||||
from buba.bubacmd import BubaCmd
|
||||
|
||||
|
||||
class BubaCharset(BubaAnimation):
|
||||
def __init__(self, buba: BubaCmd, single=None):
|
||||
super().__init__(buba)
|
||||
self.charset = bytes(range(256)).decode("CP437")
|
||||
self.single = single
|
||||
|
||||
def run(self):
|
||||
if self.single is not None:
|
||||
while True:
|
||||
self.render(self.single)
|
||||
sleep(1)
|
||||
else:
|
||||
for page in range(16):
|
||||
self.render(page)
|
||||
sleep(5)
|
||||
|
||||
def render(self, page):
|
||||
for row in range(4):
|
||||
start = ((page + row) * 16) % 256
|
||||
self.buba.simple_text(page=0, row=row, col=0, text=f"${start:02x}: {self.charset[start:start + 16]}")
|
123
buba/animations/dbf.py
Normal file
123
buba/animations/dbf.py
Normal file
|
@ -0,0 +1,123 @@
|
|||
"""
|
||||
Pull departure info from https://trains.xatlabs.com and display.
|
||||
|
||||
See also https://github.com/derf/db-fakedisplay/blob/main/README.md
|
||||
"""
|
||||
import datetime
|
||||
from itertools import islice
|
||||
from threading import Thread
|
||||
from time import sleep
|
||||
|
||||
from deutschebahn.db_infoscreen import DBInfoscreen
|
||||
|
||||
from buba.bubaanimator import BubaAnimation
|
||||
from buba.bubacmd import BubaCmd
|
||||
|
||||
|
||||
class DBFAnimation(BubaAnimation):
|
||||
def __init__(self, buba: BubaCmd, ds100="AHST", station="Holstenstraße", count=3):
|
||||
super().__init__(buba)
|
||||
self.ds100 = ds100
|
||||
self.station = station
|
||||
self.trains = []
|
||||
self.count = count
|
||||
Thread(target=self.update, daemon=True).start()
|
||||
|
||||
def fetch(self):
|
||||
dbi = DBInfoscreen("trains.xatlabs.com")
|
||||
trains = dbi.calc_real_times(dbi.get_trains(self.ds100)) # Station
|
||||
trains = [t for t in trains] # if t['platform'] == "1"] # platform gleis
|
||||
trains.sort(key=dbi.time_sort)
|
||||
self.trains = trains
|
||||
|
||||
def update(self):
|
||||
self.fetch()
|
||||
sleep(60)
|
||||
|
||||
@staticmethod
|
||||
def countdown(train):
|
||||
now = datetime.datetime.now()
|
||||
try:
|
||||
dep_time = datetime.datetime.strptime(train['actualDeparture'], "%H:%M").time()
|
||||
except TypeError as e:
|
||||
return -1
|
||||
|
||||
# First, assume all departure times are on the current day
|
||||
dep_date = now.date()
|
||||
|
||||
# Calculate timedelta under the above assumption
|
||||
dep_td = datetime.datetime.combine(dep_date, dep_time) - now
|
||||
|
||||
# If the calculated timedelta is more than one hour in the past,
|
||||
# assume that the day should actually be the next day
|
||||
# (This will be the case e.g. when a train departs at 00:15 and it's currently 23:50)
|
||||
if dep_td.total_seconds() <= -3600:
|
||||
dep_date += datetime.timedelta(days=1)
|
||||
|
||||
# If the calculated timedelta is more than 23 hours in the future,
|
||||
# assume that the day should actually be the previous day.
|
||||
# (This will be the case e.g. when a train should have departed at 23:50 but it's already 00:15)
|
||||
if dep_td.total_seconds() >= 3600 * 23:
|
||||
dep_date -= datetime.timedelta(days=1)
|
||||
|
||||
# Recalculate the timedelta
|
||||
dep_td = datetime.datetime.combine(dep_date, dep_time) - now
|
||||
return round(dep_td.total_seconds() / 60)
|
||||
|
||||
@staticmethod
|
||||
def short_station(station: str) -> str:
|
||||
station = station.strip()
|
||||
if station.startswith("Hamburg-") or station.startswith("Hamburg "):
|
||||
station = station[8:]
|
||||
if station.endswith("(S)"):
|
||||
station = station[:-3]
|
||||
if station.endswith("(S-Bahn)"):
|
||||
station = station[:-8]
|
||||
if station == "Hbf":
|
||||
station = "Hauptbahnhof"
|
||||
return station
|
||||
|
||||
@staticmethod
|
||||
def short_train(train: str) -> str:
|
||||
if train.startswith("ICE"):
|
||||
train = "ICE"
|
||||
if train.startswith("ME"):
|
||||
train = "ME"
|
||||
if train.startswith("RE"):
|
||||
train = "RE"
|
||||
train = train.replace(" ", "")
|
||||
return train
|
||||
|
||||
@staticmethod
|
||||
def chunk(it, size):
|
||||
it = iter(it)
|
||||
return iter(lambda: tuple(islice(it, size)), ())
|
||||
|
||||
def run(self):
|
||||
all = self.trains[:self.count]
|
||||
all_len = int((len(all)+1)/3)
|
||||
|
||||
if len(self.trains) == 0:
|
||||
sleep(5)
|
||||
|
||||
for page, trains in enumerate(self.chunk(all, 3)):
|
||||
if all_len == 1:
|
||||
title = self.station
|
||||
else:
|
||||
title = f"{self.station} ({page+1}/{all_len})"
|
||||
self.buba.text(page=0, row=0, col_start=0, col_end=120, text=title, align=BubaCmd.ALIGN_LEFT)
|
||||
for i, train in enumerate(trains):
|
||||
if train['isCancelled']:
|
||||
when = "--"
|
||||
else:
|
||||
when = self.countdown(train)
|
||||
if when < 0:
|
||||
when = 0
|
||||
self.buba.text(page=0, row=i + 1, col_start=0, col_end=120, text=self.short_train(train['train']))
|
||||
self.buba.text(page=0, row=i + 1, col_start=12, col_end=76, text=self.short_station(train['destination']))
|
||||
self.buba.text(page=0, row=i + 1, col_start=105, col_end=120,
|
||||
text=str(when), align=BubaCmd.ALIGN_RIGHT)
|
||||
self.buba.set_page(0)
|
||||
for i in range(10):
|
||||
self.buba.text(page=0, row=0, col_start=93, col_end=120, text=datetime.datetime.now().strftime("%H:%M:%S"), align=BubaCmd.ALIGN_RIGHT)
|
||||
sleep(1)
|
21
buba/animations/time.py
Normal file
21
buba/animations/time.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from datetime import datetime
|
||||
from time import sleep
|
||||
|
||||
from buba.bubaanimator import BubaAnimation
|
||||
from buba.bubacmd import BubaCmd
|
||||
|
||||
|
||||
class BubaTime(BubaAnimation):
|
||||
def __init__(self, buba: BubaCmd):
|
||||
super().__init__(buba)
|
||||
|
||||
def run(self):
|
||||
self.buba.simple_text(page=0, row=0, col=0, text="Bus-Bahn-Anzeige")
|
||||
self.buba.simple_text(page=0, row=1, col=0, text="Chaos Computer Club")
|
||||
self.buba.simple_text(page=0, row=2, col=0, text="Hansestadt Hamburg")
|
||||
self.buba.simple_text(page=0, row=3, col=0, text="Hello, world!")
|
||||
self.buba.set_page(0)
|
||||
|
||||
for i in range(5):
|
||||
self.buba.text(page=0, row=0, col_start=93, col_end=120, text=datetime.now().strftime("%H:%M:%S"), align=BubaCmd.ALIGN_RIGHT)
|
||||
sleep(1)
|
38
buba/bubaanimator.py
Normal file
38
buba/bubaanimator.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
import logging
|
||||
from threading import Thread
|
||||
from time import sleep
|
||||
|
||||
from buba.bubacmd import BubaCmd
|
||||
|
||||
|
||||
class BubaAnimation:
|
||||
def __init__(self, buba: BubaCmd):
|
||||
self.buba = buba
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{type(self).__name__}>"
|
||||
|
||||
|
||||
class BubaAnimator:
|
||||
def __init__(self, buba: BubaCmd):
|
||||
self.log = logging.getLogger(__name__)
|
||||
self.buba = buba
|
||||
self.animations = []
|
||||
Thread(target=self.run, daemon=True).start()
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
if len(self.animations) == 0:
|
||||
self.log.debug("No animations, sleeping...")
|
||||
sleep(2)
|
||||
else:
|
||||
for a in self.animations:
|
||||
self.log.debug(f"Starting animation: {a}")
|
||||
a.run()
|
||||
|
||||
def add(self, animation, *args, **kwargs):
|
||||
self.animations.append(animation(self.buba, *args, **kwargs))
|
|
@ -9,6 +9,11 @@ from pyfis.aegmis import MIS1TextDisplay
|
|||
|
||||
|
||||
class BubaCmd:
|
||||
ALIGN_LEFT = 0x00
|
||||
ALIGN_RIGHT = 0x01
|
||||
ALIGN_CENTER = 0x02
|
||||
ALIGN_SCROLL = 0x03 # apparently not supported
|
||||
|
||||
def __init__(self, serial: str, send: Callable):
|
||||
self.log = logging.getLogger(__name__)
|
||||
self.serial = serial
|
|
@ -119,15 +119,44 @@ export default class {
|
|||
return col;
|
||||
}
|
||||
|
||||
text_length(text) {
|
||||
let l = 0
|
||||
for (let c of text) {
|
||||
let f = this.font[c];
|
||||
if (f === undefined)
|
||||
continue;
|
||||
l += f.length;
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
col_start_for_alignment(col_start, col_end, text, align) {
|
||||
let l = this.text_length(text)
|
||||
switch (align) {
|
||||
case 0: // ALIGN_LEFT
|
||||
return col_start;
|
||||
case 1: // ALIGN_RIGHT
|
||||
return col_end - l;
|
||||
case 2:
|
||||
return Math.floor((col_end - col_start - l) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
set_pages(pages) {
|
||||
console.log("set pages", pages);
|
||||
// ignore for now
|
||||
}
|
||||
|
||||
text(page, row, col_start, col_end, text, align) {
|
||||
let s = this.col_start_for_alignment(col_start, col_end, text, align);
|
||||
if (s < 0)
|
||||
console.log("Out of alignment")
|
||||
this.eraseCols(row, col_start, col_end);
|
||||
this.applyText(row, s, text);
|
||||
}
|
||||
|
||||
simple_text(page, row, col, text, align) {
|
||||
// console.log("simple text", page, row, col, text, align);
|
||||
this.eraseCols(row, col, this.config.cols);
|
||||
this.applyText(row, col, text);
|
||||
this.text(page, row, col, this.config.cols, text, align);
|
||||
}
|
||||
|
||||
defineFont() {
|
||||
|
@ -333,6 +362,8 @@ export default class {
|
|||
// 0x20-0x2f
|
||||
defs['\u0020'] = [ // space
|
||||
'...... ...... ...... ......',
|
||||
'...... ...... ...... ......',
|
||||
'...... ...... ...... ......',
|
||||
];
|
||||
defs['\u0021'] = [ // !
|
||||
'###### ###### ##.... ###...',
|
||||
|
|
|
@ -19,7 +19,7 @@ if (container) {
|
|||
display.simple_text(m.page, m.row, m.col, m.text, m.align);
|
||||
break;
|
||||
case "text":
|
||||
console.log("text", m);
|
||||
display.text(m.page, m.row, m.col_start, m.col_end, m.text, m.align);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue