diff --git a/README.md b/README.md index af5cfaf..c04ea4c 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,95 @@ # hafas-terminal-app -## Description -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. +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. -## Dependencies -- Python - - prompt_toolkit (tui) - - kivy (mobile-ui) ((optional!!!)) - - requests - - json -- Hafas API Endpoint -- Git +## Features -## Timetable -- ~~22.04.2026 - Short project presentation: 3min per Group, incl. UseCase, GitProject, Issues~~ -- ~~29.04.2026 - Scrum-Round mia/hafas-terminal-app#1~~ -- ~~06.05.2026 - Scrum-Round mia/hafas-terminal-app#2, optionally mia/hafas-terminal-app#3 and mia/hafas-terminal-app#4~~ -- 13.05.2026 - Scrum-Round #3, #4, #5 -- 20.05.2026 - Scrum-Round -- 27.05.2026 - Scrum-Round -- 03.06.2026 - Scrum-Round -- 10.06.2026 - Presentations \ No newline at end of file +- **Route Planning**: Find optimal routes between stations +- **Station Information**: View real-time arrivals and departures +- **Terminal UI**: User-friendly command-line interface using prompt_toolkit +- **Multiple Transport Methods**: Support for various public transport modes + +## Requirements + +### Core Dependencies +- **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. \ No newline at end of file diff --git a/backend.py b/backend.py index 03bc604..fd44e0c 100644 --- a/backend.py +++ b/backend.py @@ -9,7 +9,8 @@ The application will provide routing information and station departures/arrivals import requests import json -from datetime import datetime +import re +from datetime import date, datetime from prompt_toolkit import Application from prompt_toolkit.completion import WordCompleter @@ -43,6 +44,13 @@ class HafasClient: headers=self.headers) 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): @@ -75,20 +83,13 @@ class HafasClient: # - Departure/arrival time: departure["stbStop"]["dTimeS"] or ["aTimeS"] departures.append({ "name": departure.get("dirTxt"), - "line": ( - 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") - ), + "line": re.search(r'#ZB#([^#]+)', departure.get("jid")).group(1).strip(), "departure_time": self._format_time(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_raw": stop.get(time_key_real), "jid": departure.get("jid"), - "raw": departure, + # "raw": departure, }) return departures @@ -114,13 +115,25 @@ class HafasClient: trip = self.getTrip(departures[0]["jid"]) __import__('pprint').pprint(trip) - def moniterTest(self): + def monitorTest(self): stationInput = input("Enter Station Name: ") station = self.getStationNames(stationInput) print(station) # departures = self.getArrDep(station[0][1], arrdep="DEP", count=1) - departures = self.arrDepRequest(station[0][1], arrdep="DEP", count=1) - print(departures) + print("\n)------------------\n") + 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"] @@ -140,4 +153,5 @@ class HafasClient: if __name__ == '__main__': client = HafasClient() # client.runTests() - client.moniterTest() + # client.monitorTest() + client.journeyTest() diff --git a/station_monitor.py b/station_monitor.py index 63d57b2..cf83336 100644 --- a/station_monitor.py +++ b/station_monitor.py @@ -15,7 +15,6 @@ from prompt_toolkit.layout.containers import Window, VSplit, HSplit from prompt_toolkit.layout.controls import BufferControl from prompt_toolkit.key_binding import KeyBindings import threading -import time 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): 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" results = hafas.getStationNames(user_input) - + if not results: arrivalBuffer.text = f"Keine Stationen gefunden für: {user_input}" - 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}") - - + departureBuffer.text = "" userinputBuffer.text = "" + app_state = "MONITOR" + return - else: - app_state = "MONITOR" + station_name = results[0][0] + 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")