351 lines
12 KiB
Python
351 lines
12 KiB
Python
import datetime
|
|
import hashlib
|
|
import json
|
|
import time
|
|
import traceback
|
|
|
|
from db_infoscreen import DBInfoscreen
|
|
from splitflap.krone import Krone8200Controller
|
|
from splitflap.display import SplitFlapDisplay, CustomMapField
|
|
from splitflap.exceptions import CommunicationError
|
|
|
|
from util import timeout
|
|
|
|
RX_ADDRESS = (0x41, 0x41)
|
|
|
|
MAP_ROUTE = {
|
|
0: "Halensee, Schöneberg, Südkreuz, Neukölln",
|
|
1: "Schöneberg, Südkreuz, Neukölln, Baumschulenweg",
|
|
2: "Schöneberg, Südkreuz, Neukölln, Schöneweide",
|
|
3: "Schöneberg, Neukölln, Schöneweide, Grünau",
|
|
4: "Gesundbrunnen, Schönhauser Allee, Ostkreuz, Treptower Park",
|
|
5: "Gesundbrunnen, Schönhauser Allee, Ostkreuz, Neukölln",
|
|
6: "Gesundbrunnen, Ostkreuz, Neukölln, Südkreuz",
|
|
7: "Gesundbrunnen, Ostkreuz, Neukölln, Schöneberg",
|
|
8: "Westend",
|
|
9: "Westend, Jungfernheide",
|
|
10: "Westend, Jungfernheide, Westhafen",
|
|
11: "Westend, Jungfernheide, Westhafen, Gesundbrunnen",
|
|
12: "Westend, Jungfernheide, Gesundbrunnen, Schönhauser Allee",
|
|
13: "Jungfernheide, Gesundbrunnen, Schönhauser Allee, Ostkreuz",
|
|
14: "Gesundbrunnen, Ostkreuz, Schöneberg",
|
|
15: "Halensee, Schöneberg, Südkreuz, Neukölln",
|
|
16: "Schöneberg, Südkreuz, Neukölln, Ostkreuz",
|
|
17: "Schöneberg, Neukölln, Ostkreuz, Gesundbrunnen",
|
|
18: "Halensee",
|
|
19: "Halensee, Bundesplatz",
|
|
20: "Halensee, Bundesplatz, Schöneberg",
|
|
21: "Halensee, Bundesplatz, Schöneberg, Südkreuz",
|
|
22: "Schöneberg, Ostkreuz, Gesundbrunnen",
|
|
23: "weiter als S2 über Buch",
|
|
24: "Jungfernheide, Gesundbrunnen",
|
|
25: "Jungfernheide, Gesundbrunnen, Bornholmer Straße, Schönholz",
|
|
26: "Westkreuz, Schöneberg, Südkreuz, Neukölln",
|
|
27: "Westkreuz, Schöneberg, Neukölln, Baumschulenweg",
|
|
28: "Westkreuz, Schöneberg, Neukölln, Schöneweide",
|
|
29: "Westkreuz, Schöneberg, Schöneweide, Grünau",
|
|
30: "Gesundbrunnen, Ostkreuz, Schöneberg, Westkreuz",
|
|
31: "Westkreuz, Schöneberg, Neukölln, Ostkreuz",
|
|
32: "Westkreuz, Neukölln, Ostkreuz, Gesundbrunnen",
|
|
33: "Westkreuz",
|
|
34: "Westkreuz, Halensee",
|
|
35: "Westkreuz, Halensee, Bundesplatz",
|
|
36: "Westkreuz, Halensee, Bundesplatz, Schöneberg",
|
|
37: "Westkreuz, Halensee, Schöneberg, Südkreuz",
|
|
38: "Westkreuz, Schöneberg, Ostkreuz, Gesundbrunnen",
|
|
39: "Jungfernheide",
|
|
40: "Jungfernheide, Westhafen",
|
|
41: "Jungfernheide, Westhafen, Gesundbrunnen",
|
|
42: "Jungfernheide, Westhafen, Gesundbrunnen, Schönhauser Allee",
|
|
43: "Jungfernheide, Gesundbrunnen, Bornholmer Straße",
|
|
44: "Schöneberg, Neukölln Kurzzug",
|
|
45: "Neukölln, Schöneweide Kurzzug",
|
|
46: "Gesundbrunnen, Ostkreuz Kurzzug",
|
|
47: "Westend Kurzzug",
|
|
48: "Westend, Jungfernheide Kurzzug",
|
|
49: "Jungfernheide, Gesundbrunnen Kurzzug",
|
|
50: "Schöneberg, Neukölln Kurzzug",
|
|
51: "Neukölln, Ostkreuz Kurzzug",
|
|
52: "Halensee Kurzzug",
|
|
53: "Halensee, Bundesplatz Kurzzug",
|
|
54: "Halensee, Schöneberg Kurzzug",
|
|
55: "Schöneberg, Ostkreuz Kurzzug",
|
|
56: "Westend, Gesundbrunnen Kurzzug",
|
|
57: "Gesundbrunnen, Bornholmer Straße Kurzzug",
|
|
58: "Gesundbrunnen, Schönholz Kurzzug",
|
|
59: "Westkreuz, Neukölln Kurzzug",
|
|
60: "Westkreuz, Schöneweide Kurzzug",
|
|
61: "Ostkreuz, Gesundbrunnen Kurzzug",
|
|
62: "Westkreuz Kurzzug",
|
|
63: "Westkreuz, Halensee Kurzzug",
|
|
64: "Westkreuz, Schöneberg Kurzzug",
|
|
65: "Westkreuz, Ostkreuz Kurzzug",
|
|
66: "Jungfernheide Kurzzug",
|
|
67: "Jungfernheide, Gesundbrunnen Kurzzug",
|
|
68: "Gesundbrunnen, Ostkreuz, Südkreuz, Westkreuz",
|
|
69: "Westkreuz, Südkreuz, Ostkreuz, Gesundbrunnen",
|
|
70: "Gesundbrunnen, Schönhauser Allee Kurzzug",
|
|
71: "Messe Nord/ICC (Witzleben)",
|
|
72: "In die letzten 4 Wagen bitte nicht einsteigen",
|
|
73: "Zug fährt später",
|
|
74: "Ansage beachten",
|
|
75: "Kurzzug",
|
|
76: "Ersatzverkehr",
|
|
77: "Zugverkehr unterbrochen",
|
|
78: "Zugverkehr unregelmäßig"
|
|
}
|
|
|
|
MAP_LINE = {
|
|
1: "S1",
|
|
3: "S2",
|
|
5: "S3",
|
|
7: "S4",
|
|
9: "S5",
|
|
11: "S6",
|
|
13: "S7",
|
|
15: "S8",
|
|
17: "S9",
|
|
19: "S10",
|
|
21: "S75",
|
|
23: "S85",
|
|
25: "S86",
|
|
27: "S25",
|
|
29: "S45",
|
|
31: "S46",
|
|
33: "S26",
|
|
35: "S41",
|
|
37: "S42",
|
|
39: "S47"
|
|
}
|
|
|
|
MAP_DESTINATION = {
|
|
0: "Alexanderplatz",
|
|
1: "Ahrensfelde",
|
|
2: "Anhalter Bahnhof",
|
|
3: "Baumschulenweg",
|
|
4: "Bernau",
|
|
5: "Birkenwerder",
|
|
6: "Blankenburg",
|
|
7: "Blankenfelde",
|
|
8: "Buch",
|
|
9: "Bundesplatz",
|
|
10: "Charlottenburg",
|
|
11: "Erkner",
|
|
12: "Falkensee",
|
|
13: "Friedrichshagen",
|
|
14: "Friedrichstraße",
|
|
15: "Schönefeld Flughafen",
|
|
16: "Frohnau",
|
|
17: "Gesundbrunnen",
|
|
18: "Greifswalder Straße",
|
|
19: "Grunewald",
|
|
20: "Grünau",
|
|
21: "Halensee",
|
|
22: "Ostbahnhof",
|
|
23: "Hermannstraße",
|
|
24: "Hermsdorf",
|
|
25: "Henningsdorf",
|
|
26: "Hoppegarten (Markt)",
|
|
27: "Karlshorst",
|
|
28: "Königs Wusterhausen",
|
|
29: "Köpenik",
|
|
30: "Lichtenberg",
|
|
31: "Lichtenrade",
|
|
32: "Lichterfeld Süd",
|
|
33: "Mahlow",
|
|
34: "Mahlsdorf",
|
|
35: "Marienfelde",
|
|
36: "Nordbahnhof",
|
|
37: "Olympiastadion",
|
|
38: "Oranienburg",
|
|
39: "Ostkreuz",
|
|
40: "Südkreuz",
|
|
41: "Pichelsberg",
|
|
42: "Potsdam Hbf",
|
|
43: "Potsdamer Platz",
|
|
44: "Westhafen",
|
|
45: "Rangsdorf",
|
|
46: "Spandau",
|
|
47: "Schöneberg",
|
|
48: "Schöneweide",
|
|
49: "Schönholz",
|
|
50: "Sellheimbrücke",
|
|
51: "Spindlersfeld",
|
|
52: "Rathaus Steglitz",
|
|
53: "Strausberg",
|
|
54: "Strausberg Nord",
|
|
55: "Streslow",
|
|
56: "Tegel",
|
|
57: "Tempelhof",
|
|
58: "Wannsee",
|
|
59: "Warschauer Straße",
|
|
60: "Westend",
|
|
61: "Westkreuz",
|
|
62: "Zehlendorf",
|
|
63: "Zoologischer Garten",
|
|
64: "Zeuthen",
|
|
65: "Berlin Hbf",
|
|
66: "Landsberger Allee",
|
|
67: "Marzahn",
|
|
68: "Neukölln",
|
|
69: "Pankow",
|
|
70: "Schönhauser Allee",
|
|
71: "Springpfuhl",
|
|
72: "Treptower Park",
|
|
73: "Ring ↻",
|
|
74: "Ring ↺",
|
|
75: "Gesundbrunnen/Ring",
|
|
76: "Beusselstraße",
|
|
77: "Wedding",
|
|
78: "Nicht einsteigen"
|
|
}
|
|
|
|
|
|
class BerlinSplitFlapDisplay(SplitFlapDisplay):
|
|
route = CustomMapField(MAP_ROUTE, start_address=1, x=0, y=0, module_width=20, module_height=1, home_pos=99)
|
|
line = CustomMapField(MAP_LINE, start_address=2, x=0, y=1, module_width=4, module_height=1, home_pos=99)
|
|
destination = CustomMapField(MAP_DESTINATION, start_address=3, x=4, y=1, module_width=16, module_height=1, home_pos=99)
|
|
|
|
|
|
@timeout(30)
|
|
def get_trains(dbi, station):
|
|
return dbi.get_trains(station)
|
|
|
|
|
|
def main():
|
|
running = True
|
|
while running:
|
|
controller = Krone8200Controller("/dev/serial/by-path/platform-3f980000.usb-usb-0:1.5:1.0-port0", RX_ADDRESS, debug=True)
|
|
display = BerlinSplitFlapDisplay(controller)
|
|
dbi = DBInfoscreen("dbf.finalrewind.org")
|
|
|
|
last_update = 0
|
|
last_heartbeat = 0
|
|
while True:
|
|
now = time.time()
|
|
|
|
if now - last_heartbeat >= 10:
|
|
print("\n" + "=" * 60 + "\n")
|
|
print("Sending heartbeat")
|
|
controller.send_end_comm()
|
|
last_heartbeat = now
|
|
continue
|
|
|
|
if now - last_update < 60:
|
|
time.sleep(1)
|
|
continue
|
|
|
|
print("\n" + "=" * 60 + "\n")
|
|
|
|
try:
|
|
trains = get_trains(dbi, "Berlin Westkreuz")
|
|
trains = dbi.calc_real_times(trains)
|
|
display.clear()
|
|
line = ""
|
|
dest = ""
|
|
route = ""
|
|
|
|
trains.sort(key=dbi.time_sort)
|
|
for train in trains:
|
|
# Only show trains within the next 3 minutes
|
|
now_dt = datetime.datetime.now()
|
|
departure_time = datetime.datetime.strptime(train['actualDeparture'] or train['actualArrival'], "%H:%M").time()
|
|
departure_date = (now_dt.date() + datetime.timedelta(days=1)) if (now_dt.time().hour - departure_time.hour) >= 2 else now_dt.date()
|
|
departure_dt = datetime.datetime.combine(departure_date, departure_time)
|
|
delta = departure_dt - now_dt
|
|
if delta.total_seconds() / 60 > 3:
|
|
continue
|
|
|
|
line = "".join(train['train'].split())
|
|
if line == "S41":
|
|
dest = "Ring ↻"
|
|
elif line == "S42":
|
|
dest = "Ring ↺"
|
|
else:
|
|
dest = train['destination'].replace("Berlin-", "").replace("Berlin", "").replace("(S)", "").strip()
|
|
if dest == "Westkreuz":
|
|
dest = "Nicht einsteigen"
|
|
line = ""
|
|
if dest not in MAP_DESTINATION.values():
|
|
continue
|
|
display.line.set(line)
|
|
display.destination.set(dest)
|
|
|
|
delay = train['delayDeparture'] or train['delayArrival'] or 0
|
|
|
|
route = ""
|
|
if train['train'] == "Bus SEV":
|
|
route = "Ersatzverkehr"
|
|
elif all(map(lambda t: t['isCancelled'], trains[:20])):
|
|
route = "Zugverkehr unterbrochen"
|
|
elif all(map(lambda t: t['isCancelled'], trains[:5])):
|
|
route = "Zugverkehr unregelmäßig"
|
|
elif train['isCancelled'] or delay > 60:
|
|
route = "Ansage beachten"
|
|
elif delay > 2:
|
|
route = "Zug fährt später"
|
|
elif line == "S41":
|
|
route = "Gesundbrunnen, Schönhauser Allee, Ostkreuz, Neukölln"
|
|
elif line == "S42":
|
|
route = "Schöneberg, Neukölln, Ostkreuz, Gesundbrunnen"
|
|
elif line == "S46" and dest == "Königs Wusterhausen":
|
|
route = "Schöneberg, Südkreuz, Neukölln, Baumschulenweg"
|
|
elif line == "S46" and dest == "Westend":
|
|
route = "Messe Nord/ICC (Witzleben)"
|
|
elif dest == "":
|
|
route = "Ansage beachten"
|
|
display.route.set(route)
|
|
break
|
|
|
|
print(display.render_ascii())
|
|
|
|
try:
|
|
with open("status.json", 'r') as f:
|
|
status = json.load(f)
|
|
except (FileNotFoundError, ValueError):
|
|
status = {'displayHash': None}
|
|
|
|
display_hash = hashlib.sha256(bytes(line + dest + route, 'utf-8')).hexdigest()
|
|
|
|
update = True
|
|
if status['displayHash'] == display_hash:
|
|
print("No need to update display.")
|
|
update = False
|
|
else:
|
|
status['displayHash'] = display_hash
|
|
|
|
if update:
|
|
num_tries = 10
|
|
success = False
|
|
for i in range(num_tries):
|
|
try:
|
|
display.update()
|
|
success = True
|
|
break
|
|
except CommunicationError:
|
|
print("Communication error! Retrying... {}/{}".format(i+1, num_tries))
|
|
time.sleep(5)
|
|
except OSError:
|
|
# e.g. USB UART disconnected - handled by outer loop
|
|
raise
|
|
if success:
|
|
with open("status.json", 'w') as f:
|
|
json.dump(status, f)
|
|
except OSError:
|
|
print("Communication error with USB UART! Restarting in 10 seconds...")
|
|
time.sleep(10)
|
|
break
|
|
except KeyboardInterrupt:
|
|
print("Exiting")
|
|
running = False
|
|
break
|
|
except:
|
|
traceback.print_exc()
|
|
time.sleep(10)
|
|
|
|
last_update = now
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|