Phalos-Berlin-ZZA/run.py

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