This commit is contained in:
Sophia S 2026-06-17 10:05:42 +02:00
commit e66d33eefa
3 changed files with 144 additions and 58 deletions

110
README.md
View file

@ -1,23 +1,95 @@
# hafas-terminal-app # hafas-terminal-app
## Description A Python-based terminal application for accessing public transport information via the HAFAS API. This tool provides station-to-station route planning and real-time arrival/departure information for public transport networks.
The goal is to access the Hafas API to retrieve Data about the public transport network. Most important is to achieve Station to Station Routeplanning and Station Arrival/Departure Information.
## Dependencies ## Features
- Python
- prompt_toolkit (tui)
- kivy (mobile-ui) ((optional!!!))
- requests
- json
- Hafas API Endpoint
- Git
## Timetable - **Route Planning**: Find optimal routes between stations
- ~~22.04.2026 - Short project presentation: 3min per Group, incl. UseCase, GitProject, Issues~~ - **Station Information**: View real-time arrivals and departures
- ~~29.04.2026 - Scrum-Round mia/hafas-terminal-app#1~~ - **Terminal UI**: User-friendly command-line interface using prompt_toolkit
- ~~06.05.2026 - Scrum-Round mia/hafas-terminal-app#2, optionally mia/hafas-terminal-app#3 and mia/hafas-terminal-app#4~~ - **Multiple Transport Methods**: Support for various public transport modes
- 13.05.2026 - Scrum-Round #3, #4, #5
- 20.05.2026 - Scrum-Round ## Requirements
- 27.05.2026 - Scrum-Round
- 03.06.2026 - Scrum-Round ### Core Dependencies
- 10.06.2026 - Presentations - **Python 3.7+**: Programming language
- **Hafas API Endpoint**: Access to a HAFAS public transport API
### Python Packages
- `prompt_toolkit`: Terminal user interface (TUI)
- `requests`: HTTP library for API communication
- `kivy` (optional): Mobile UI support for future mobile versions
### Tools
- `Git`: Version control
## Installation
### Option 1: Using Nix (Recommended)
1. Clone the repository:
```bash
git clone https://git.miaig.dev/mia/hafas-terminal-app.git
cd hafas-terminal-app
```
2. Enter the Nix development environment:
```bash
nix develop # Modern Nix with flakes
# or
nix-shell # Traditional Nix
```
All dependencies will be automatically configured.
### Option 2: Using pip
1. Clone the repository:
```bash
git clone https://git.miaig.dev/mia/hafas-terminal-app.git
cd hafas-terminal-app
```
2. Create a virtual environment:
```bash
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```
3. Install dependencies:
```bash
pip install -r requirements.txt
```
## Usage
Run the application:
```bash
python main.py
```
### Features
- Navigate between stations and search for routes
- View departure and arrival times
- Display detailed journey information
## Project Structure
```
hafas-terminal-app/
├── main.py # Application entry point
├── backend.py # Backend API communication logic
├── route_planning.py # Route planning algorithms
├── station_monitor.py # Station monitoring functionality
├── flake.nix # Nix environment configuration
├── README.md # This file
└── docs/ # Documentation
```
## License
This project is provided under the terms specified in the [LICENSE](./LICENSE) file.
## Contributing
Contributions are welcome! Please feel free to submit issues and pull requests to help improve the application.

View file

@ -9,7 +9,8 @@ The application will provide routing information and station departures/arrivals
import requests import requests
import json import json
from datetime import datetime import re
from datetime import date, datetime
from prompt_toolkit import Application from prompt_toolkit import Application
from prompt_toolkit.completion import WordCompleter from prompt_toolkit.completion import WordCompleter
@ -43,6 +44,13 @@ class HafasClient:
headers=self.headers) headers=self.headers)
return json.loads(res.text) return json.loads(res.text)
def journeyRequest(self, origin, destination, via=None):
time = datetime.now()
res = self.session.post(self.baseUrl,
data=json.dumps({"svcReqL": [{"req": {"arrLocL": [{"lid": f"A=1@L={destination}@"}], "viaLocL": via or [], "depLocL": [{"lid": f"A=1@L={origin}@"}], "outDate": time.strftime("%Y%m%d"), "outTime": time.strftime("%H%M%S"), "jnyFltrL": [{"type": "PROD", "mode": "INC", "value": "4087"}], "minChgTime": 0, "maxChg": -1, "numF": 1}, "meth": "TripSearch"}], "client": self.clientInfo, "ver": self.version, "lang": self.language, "auth": self.auth}),
headers=self.headers)
return json.loads(res.text)
def getStationNames(self, stationString): def getStationNames(self, stationString):
@ -75,20 +83,13 @@ class HafasClient:
# - Departure/arrival time: departure["stbStop"]["dTimeS"] or ["aTimeS"] # - Departure/arrival time: departure["stbStop"]["dTimeS"] or ["aTimeS"]
departures.append({ departures.append({
"name": departure.get("dirTxt"), "name": departure.get("dirTxt"),
"line": ( "line": re.search(r'#ZB#([^#]+)', departure.get("jid")).group(1).strip(),
product.get("nameS")
or product.get("number")
or product.get("name")
or product.get("prodCtx", {}).get("line")
or product.get("prodCtx", {}).get("name")
or product.get("prodCtx", {}).get("nameS")
),
"departure_time": self._format_time(stop.get(time_key)), "departure_time": self._format_time(stop.get(time_key)),
"departure_time_raw": stop.get(time_key), "departure_time_raw": stop.get(time_key),
"departure_time_real": self._format_time(stop.get(time_key_real)), "departure_time_real": self._format_time(stop.get(time_key_real)),
"departure_time_real_raw": stop.get(time_key_real), "departure_time_real_raw": stop.get(time_key_real),
"jid": departure.get("jid"), "jid": departure.get("jid"),
"raw": departure, # "raw": departure,
}) })
return departures return departures
@ -114,13 +115,25 @@ class HafasClient:
trip = self.getTrip(departures[0]["jid"]) trip = self.getTrip(departures[0]["jid"])
__import__('pprint').pprint(trip) __import__('pprint').pprint(trip)
def moniterTest(self): def monitorTest(self):
stationInput = input("Enter Station Name: ") stationInput = input("Enter Station Name: ")
station = self.getStationNames(stationInput) station = self.getStationNames(stationInput)
print(station) print(station)
# departures = self.getArrDep(station[0][1], arrdep="DEP", count=1) # departures = self.getArrDep(station[0][1], arrdep="DEP", count=1)
departures = self.arrDepRequest(station[0][1], arrdep="DEP", count=1) print("\n)------------------\n")
print(departures) departures = self.getArrDep(station[0][1], arrdep="DEP", count=2)
__import__('pprint').pprint(departures)
print("\n)------------------\n")
arrivals = self.getArrDep(station[0][1], arrdep="ARR", count=2)
__import__('pprint').pprint(arrivals)
#arrivalsRaw = self.arrDepRequest(station[0][1], arrdep="ARR", count=1)
#__import__('pprint').pprint(arrivalsRaw)
def journeyTest(self):
origin = self.getStationNames(input("Origin: "))[0][1]
destination = self.getStationNames(input("Destination: "))[0][1]
journeys = self.journeyRequest(origin, destination)
__import__('pprint').pprint(journeys)
# streq = self.stationRequest(station)["svcResL"][0]["res"]["match"]["locL"] # streq = self.stationRequest(station)["svcResL"][0]["res"]["match"]["locL"]
@ -140,4 +153,5 @@ class HafasClient:
if __name__ == '__main__': if __name__ == '__main__':
client = HafasClient() client = HafasClient()
# client.runTests() # client.runTests()
client.moniterTest() # client.monitorTest()
client.journeyTest()

View file

@ -15,7 +15,6 @@ from prompt_toolkit.layout.containers import Window, VSplit, HSplit
from prompt_toolkit.layout.controls import BufferControl from prompt_toolkit.layout.controls import BufferControl
from prompt_toolkit.key_binding import KeyBindings from prompt_toolkit.key_binding import KeyBindings
import threading import threading
import time
def run_station_monitor(hafas: backend.HafasClient | None = None): def run_station_monitor(hafas: backend.HafasClient | None = None):
@ -69,37 +68,38 @@ def run_station_monitor(hafas: backend.HafasClient | None = None):
def handle_enter(event): def handle_enter(event):
global app_state global app_state
user_input = userinputBuffer.text.strip() user_input = userinputBuffer.text.strip()
if not user_input:
app_state = "MONITOR"
return
if user_input:
app_state = "RESULTS" app_state = "RESULTS"
results = hafas.getStationNames(user_input) results = hafas.getStationNames(user_input)
if not results: if not results:
arrivalBuffer.text = f"Keine Stationen gefunden für: {user_input}" arrivalBuffer.text = f"Keine Stationen gefunden für: {user_input}"
departureBuffer.text= "" departureBuffer.text = ""
userinputBuffer.text=""
app_state = "MONITOR"
else:
station_name = results[0][1]
station_id = results[0][0]
arrivals = hafas.getArrDep(station_id, arrdep="ARR", count=3)
arrivalBuffer.text = f"Ankünfte für {station_name}:\n"
for arrival in arrivals:
arrivalBuffer.insert_text(f"\n {arrival}")
departures = hafas.getArrDep(station_id, arrdep="DEP", count=3)
departureBuffer.text = f"Abfahrten für {station_name}:\n"
for departure in departures:
departureBuffer.insert_text(f"\n {departure}")
userinputBuffer.text = "" userinputBuffer.text = ""
app_state = "MONITOR"
return
else: station_name = results[0][0]
app_state = "MONITOR" station_id = results[0][1]
arrivals = hafas.getArrDep(station_id, arrdep="ARR", count=5)
arrivalBuffer.text = (
f"Ankünfte für {station_name}:\n"
+ "\n".join(f" {arrival["departure_time_real"]} {arrival["line"]} <-- {arrival["name"]}" for arrival in arrivals)
)
departures = hafas.getArrDep(station_id, arrdep="DEP", count=5)
departureBuffer.text = (
f"Abfahrten für {station_name}:\n"
+ "\n".join(f" {departure["departure_time_real"]} {departure["line"]} --> {departure["name"]}" for departure in departures)
)
userinputBuffer.text = ""
@keyB.add("c-q") @keyB.add("c-q")