Skip to content

About

Shared Amtraker API client for the Live Track passenger-rail Home Assistant integrations (Amtrak, VIA Rail, Brightline). Pure Python, no HA dependency.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

45 Commits

Folders and files

Repository files navigation

saw-livetrack

Shared code for the Live Track Home Assistant integrations.

Separate submodules, deliberately.

saw_livetrack.track — source-agnostic

Holds the last two distinct fixes per object and emits the previous_* segment fields, so a consumer can animate between two observed positions rather than extrapolating toward a projection.

The bug it prevents is not specific to any feed. Any integration whose poll interval sits near its source's publish interval receives duplicate fixes, and storing a duplicate as the previous fix collapses the segment to zero length — which renders as a stutter on the map. Every individual update looks correct in isolation; only the sequence is wrong.

The timestamp extractor is injected, because only the source knows what its own timestamp means.

from saw_livetrack.track import FixTracker

t = FixTracker()                      # or FixTracker(observed_at=my_extractor)
t.update("vehicle-1", record)
attrs = t.segment_fields("vehicle-1") # previous_latitude, course_deg, ...

Keys that cannot be supplied honestly are omitted, never set to None or a sentinel. A stationary object has no course, and that absence is correct.

saw_livetrack.amtraker — the Amtraker API client

For Live Track Amtrak, Live Track VIA Rail and Live Track Brightline. One cached fetch serves all three providers, since the endpoint takes no parameters and returns every train of every provider.

What it refuses to do, and why — each refusal came from a measurement:

Behaviour Reason
course_deg() always returns None the feed's direction is an eight-value octant string; converting it invents precision the feed never carried. A real course comes from two observed positions instead
speed_kmh() returns None for Brightline even when non-zero measured 0.0 on every instance while trains covered 76–133 km at 44–77 mph. The field is unpopulated, not stationary
observed_at() returns None for Brightline its timestamp advances every 30 s while the position changes every 60 s — a feed job clock, not an observation time
observed_at() returns None for Predeparture trains that field then carries a scheduled future departure
a finished train is handled by type the API returns a bare [], not the documented keyed object
HTTP 429 raises RateLimited a non-200 must never be recorded as "no data"
an empty User-Agent raises at construction the server blocks such requests

amtraker imports from track. Never the reverse.

saw_livetrack.gtfs — GTFS and GTFS-Realtime

Moved here from Live Track Commuter Rail when Live Track Intercity Rail became its second consumer. No Home Assistant code: storage, coordinators, entities and config flows stay in each integration.

Module Holds
realtime Obs, one vehicle in one poll; vehicle_time and position_of, the readers every adapter shares; decode
schedule a GTFS static zip distilled to stations, their coordinates, line names, scheduled times for the trips running yesterday, today and tomorrow, and (where the realtime feed names stops only by sequence) stop times; the next station, its estimated arrival, and the delay there (stop_delays: the feed's own delay, else predicted minus scheduled) from TripUpdates; the dataset's feed_version from feed_info.txt when present; and trip_coverage, how many of a TripUpdates feed's trips the cached timetable knows (a low share means it is out of date: Metra's 2026 GTFS static change renames every trip id)
fleet one feed's vehicles across polls: motion, the hold for equipment not in service, held course at rest, grace for a vehicle missing from the feed, and each published record, with delay_min (whole minutes toward zero) and stalled (in service and standing: for REST_S when farther than STALL_M from every stop, for PLATFORM_HOLD_S when at a stop); with an on_rail check, a train in service whose fix is not on rail is held at its last fix on rail for at most REFUSED_HOLD_S, then hidden

What a given feed actually carries is not here. Each integration keeps one small adapter per railroad that turns that feed into Obs records and states only what the feed really populates.

pip install "saw-livetrack[gtfs]"

The gtfs extra brings the protobuf bindings, which only decode needs, and tzdata, without which schedule cannot read a timetable's times on a system with no timezone database and publishes no next station. import saw_livetrack does not import this module, so the Amtraker consumers neither load it nor need the extra.

saw_livetrack.rail — is this position on a railway?

RailIndex(lines) indexes rail lines, each a sequence of (lat, lon), on a 0.01 degree grid; distance_m(lat, lon, within_m) and near(lat, lon, within_m) answer from it. No dependencies. The lines are the caller's: an integration ships them as data (Live Track Commuter Rail builds one file per railroad from the FRA North American Rail Network, public domain). Fleet(on_rail=...) takes such a check.

saw_livetrack.zones — the time zone at any coordinate

zone_at(lat, lon) gives the IANA zone name ("America/Chicago"), anywhere on Earth; open sea gives a nautical zone ("Etc/GMT+5"). local_time(when, lat, lon) and utc_offset_min(when, lat, lon) give the local clock there for an aware datetime. Install the zones extra (pip install saw-livetrack[zones]): it brings tzfpy and tzdata. Without tzfpy, zone_at raises ImportError. A feed's own stated zone is not trusted: Amtrak's GTFS puts Arizona on Denver time and the Mississippi Gulf Coast on New York time; the coordinate puts them right.

saw_livetrack.digitraffic_rail — Finland's trains (Fintraffic Digitraffic)

One poller for every Live Track integration that draws Finnish trains. It joins train-locations/latest (every train with a fix, passenger or not) to the train list (trains/<date>, then trains?version= for only what changed), and routes each train to ONE consumer by trainCategory: Long-distance to intercity, Commuter to commuter, every other category (cargo, locomotives, shunting, track machines, test runs) to freight. One train TYPE overrides its category: MV, empty passenger stock, which Digitraffic files under Long-distance, goes to freight (it carries no passengers), and so does V, a depot or yard train (varikkojuna). next_stop(train) gives the next station, its ETA and the delay in whole minutes, looking only after the last row with an actual time: Digitraffic can leave a stop the train made without one (0.2.25). Sends the Accept-Encoding: gzip Digitraffic requires and a Digitraffic-User header. No extra dependencies. Share one client per Home Assistant with reference/shared_digitraffic_rail.py: each integration registers the consumer it draws, and the client keeps timetables only for registered consumers, so an integration installed alone costs only its own trains (0.2.7). Every fetch and table change is under one lock; register() and unregister() never wait on it. Each joined record carries its train.

saw_livetrack.renfe - Spain's commuter trains (Renfe)

One poller for every Live Track integration that draws Renfe's vehicles. Renfe publishes ONE GTFS-Realtime feed of positions and one of trip updates for all its Cercanías networks and Rodalies de Catalunya, keyless, about every 20 s, and the feed carries more than commuter trains: rail-replacement and feeder buses (route_type 3 in Renfe's own timetable) and Catalonia's regional lines (R11-R17). Each vehicle goes to ONE consumer: bus for a bus, intercity for a regional line, commuter for every other train. for_consumer(name) returns that consumer's positions and trip updates as two GTFS-Realtime feeds, cleaned once for everyone: the trip id is the identity (Renfe's entity ids repeat across networks), a trip appearing twice is kept once, 0,0 is dropped, and a position that jumps straight back to where the train was two fixes earlier is held. The timetable (16 MB, for telling the buses from the trains) loads on its own thread and then daily; until the first load ends every feed is empty, so a bus is never drawn as a train, and static_zip() hands the same bytes to a consumer that distills them. parse_trip_id() reads the network, train number and line out of a trip id. Needs the gtfs extra. Share one client per Home Assistant with reference/shared_renfe.py; each integration registers the consumer it draws (0.2.9). Renfe leaves a whole network out of about one batch in four; such a network is served from the batch it was last in for up to a minute, positions and trip updates both (0.2.10).

saw_livetrack.tfnsw - Transport for NSW's nswtrains feed

One poller for every Live Track integration that draws TfNSW's regional vehicles, each user with their own key (Authorization: apikey <key>; each config entry passes its own, and the client polls with the oldest). Each vehicle goes to ONE consumer: intercity for route type 106, commuter for 100 (4T.T.BMT, HUN, SCO, SHL), bus for 204 and 205 (4T.C.), by the route_id prefix until a timetable is on disk and by its route_type then. for_consumer(name) returns that consumer's positions and trip updates as two GTFS-Realtime feeds, cleaned once for everyone: a trip with a position keeps only that run's update, since one trip id runs on consecutive dates and its updates overlap; the position's stop_id, current_stop_sequence, current_status and speed are cleared, since they mislead. The timetable (about 40 MB, no Range, no ETag) goes to .storage/saw_tfnsw_nswtrains.zip on its own thread, checked by HEAD at most daily; static_version() and static_path() let each consumer distill its own cut. A key is given up on after 10 minutes of HTTP 401 or 403 from both feeds with no success between, then AuthRefused when none is left: one or two 401s are normal here. Share one client per Home Assistant with reference/shared_tfnsw.py (0.2.23). Needs the gtfs extra.

saw_livetrack.entur - Norway's trains (Entur)

One poller for every Live Track integration that draws Norwegian trains, keyless (Entur asks each consumer for an ET-Client-Name, sent as one fixed name). Polled per CODESPACE, the railroad (VYG Vy, GOA Go-Ahead Norge, FLT Flytoget; SJ Nord publishes no positions), and only the codespaces some consumer registered: register(consumer, codespace). Every train is route type 100, so each vehicle goes to ONE consumer by its line's letter: intercity for F, RE, RX and R lines, commuter for L lines and all of Flytoget, bus for rail-replacement buses (trip ids BUS-<n> and B<n>, which ride on rail lines). The line is the route's short name from the timetable, or a route id that carries it (VYG:Line:R13); anything else goes to nobody until the timetable is on disk. for_consumer(consumer, codespace) returns that consumer's positions and trip updates, cleaned once for everyone: start_date rewritten to YYYYMMDD (the trip updates often send YYYY-MM-DD), and a trip with a position keeps only that run's updates. Each codespace's timetable (Vy's is 66 MB) goes to .storage/saw_entur_<codespace>.zip on its own thread, checked by HEAD at most daily; static_version(codespace), static_path(codespace) and routes(codespace) let each consumer distill its own cut. Share one client per Home Assistant with reference/shared_entur.py (0.2.27). Needs the gtfs extra.

saw_livetrack.trafikverket - Sweden's trains (Trafikverket)

One poller for every Live Track integration that draws Swedish trains, with each user's own free key (register(consumer, brand, key); the oldest key is used, a refused one given up on after 10 minutes, as the other keyed clients). Each poll reads the positions as changeid deltas, the announcements of each run newly seen whole (a positioned train's run is its advertised number and run date), and the rows of the claimed runs modified since the last answer: a day's announcements (about 69,000 rows) are never read whole. Each run goes to ONE consumer and brand by its traffic type and product: Tåg to intercity under its brand (sj, oresundstag, ...), Pendeltåg and Arlanda Express to commuter, Buss to transit; Vy's own trains to nobody (Norway's). for_consumer(consumer, brand) returns that brand's positioned runs with the calls of the trip each train is on; stations() the stations, read daily to .storage/saw_trafikverket_stations.json. Share one client per Home Assistant with reference/shared_trafikverket.py (0.2.31). No extra needed.

saw_livetrack.irish_rail - Ireland's trains (Irish Rail)

Irish Rail's keyless realtime API (getHaconTrainsXML, undocumented; the documented getCurrentTrainsXML when it fails), read once a minute for every consumer and split by the train code's prefix: A and B to intercity, P and D to commuter, E (DART) and BUS to transit. Positions of 0,0 are no position; times are Irish local time. Also the stations (weekly, .storage/saw_irish_rail_stations.json) and the National Transport Authority's Irish Rail GTFS (daily HEAD, .storage/saw_nta_gtfs_irish_rail.zip), each kept on disk. Share one client per Home Assistant with reference/shared_irish_rail.py (0.2.32). No extra needed (gtfs brings tzdata where the OS has none; without it Ireland's own summer-time rule is used).

Since 0.2.34 the timetable carries every train and Irish Rail's reports correct it (its positions are signalling points, minutes apart, and some lines are not tracked at all): read_timetable(zip, consumer, today) gives a consumer's trips, calls and service dates from the NTA's zip; join_stations matches the API's stations to the timetable's stops; RunTracker().step(trains, now, table, stations) returns each run drawn this poll, keyed <code>-<day>, with its last reported delay and where it is (standing at, or running from, a call). A report counts only when it is that run's (origin time, not a ghost, not an hour early); its last location is a floor. Direction, look and what is published stay with each consumer.

saw_livetrack.timetable - where a train is by its timetable

For a railroad whose positions are scarce or absent (0.2.34). call_times(calls) gives each call one arrival and one departure: its actual, else expected, else aimed time plus the delay last known before it, refusing an arrival faster than 0.3 of the scheduled stretch; place(times, now) says whether the train is standing at a call or running from it toward the next, or not out; service_base(day, tz) is the GTFS service day's zero. No dependencies.

saw_livetrack.ns - the Netherlands (NS, with OVapi)

NS's Virtual Train positions (/virtual-train-api/vehicle, each user's own key from apiportal.ns.nl, header Ocp-Apim-Subscription-Key), read once for every consumer and routed by the feed's type: IC and the regional ARR trains to intercity, SPR to commuter. The feed has no fix time: observed_at is the poll that first saw a CHANGED position, flagged observed_at_source: "first_seen", never re-stamped while it stands. The track is NS's SpoorKaart (station-to-station lines, daily, .storage/saw_ns_spoorkaart.json); the timetable OVapi's gtfs-nl.zip (weekly, conditional, cut to rail as it is read, .storage/saw_ovapi_nl_rail.json.gz), which names each train's trip by its number; delays OVapi's trainUpdates.pb every five minutes, conditional, backing off on 429 (one server for both). A regional train's company (arriva, keolis) is NS's DVS vervoerder, else its brand's route. for_consumer(name, operator) gives each train with its trip, calls (expected times from the updates) and path (SpoorKaart stations); pair_lines() the encoded lines; next_call(calls, now) the next stop and delay. Share one client per Home Assistant with reference/shared_ns.py (0.2.35). The updates need the gtfs extra.

saw_livetrack.transport_vic - Transport Victoria

The GTFS Schedule is one zip of numbered folders, and a consumer takes only its own (intercity folder 1, bus folder 5) by HTTP Range. The address is read from the portal's CKAN API each time, because it changes. Checked daily, fetched when the ETag changes, the ETag held on every response and the CRC at the end; a 200 to a Range request is abandoned unread. static_path(consumer) is the folder as a zip at .storage/saw_transport_vic_<folder>.zip; no key needed. Realtime, V/Line's only so far: for_consumer("intercity"), as published, with the user's own key (KeyId). No V/Line rule lives here. Share one client per Home Assistant with reference/shared_transport_vic.py (0.2.23). Realtime needs the gtfs extra.

saw_livetrack.map_card - one map card, many integrations

Every Live Track integration ships the released Live Track Map Card, serves it and offers it; the instance loads the highest version, from exactly one dashboard resource row. This module decides: it reads the version stamped on the bundle's first line, orders versions by SemVer precedence (build metadata ignored, ties keep what is registered), builds and parses the resource URL (?v= percent-encoded), and plans the change to the row. It never touches a row under /hacsfiles/, and it updates rows in place rather than adding them. No dependencies, no Home Assistant import. Each integration copies reference/shared_map_card.py verbatim for the Home Assistant calls (0.2.8).

saw_livetrack.aircraft_icons - aircraft marker icons

The marker icons of the Live Track aircraft integrations, so each draws an aircraft the same way: the ICAO type designator to icon table, the SVG artwork, render() (fill by owner class, a dark edge, gentle shading, police lights, a size ramp by ADS-B emitter category), and ART_REV, the picture revision a consumer puts in its URLs. Each integration serves the pictures from its own view; picture_query() gives the query string. No dependencies, no Home Assistant import (0.2.11). Since 0.2.12 the lights take a mode: True pairs them with the fill (the blue wig-wag on the police white, the red/blue glow on any other), "glow" and "wigwag" say which, and the query carries it as l=g or l=w; lights_from_query() reads it back, the old l=1 included. Since 0.2.16 "charter" (LIGHTS_CHARTER, l=c) draws a steady glow, blue inside and red outside, for an ICE Air charter airframe.

saw_livetrack.enforcement_list - the EnforcementAircraft list

Reads the public EnforcementAircraft list (release data) once per Home Assistant instance: shared(hass.data, cache_path) gives the one reader, refresh(session) checks SHA256SUMS first and fetches the list only when it changed, installing it only when its bytes match. A failure keeps the cached copy (.storage/saw_enforcementaircraft.json), which never expires. Lookups by Mode S hex: status(), is_sourced(), hexes(*statuses). Statuses: SOURCED, DETECTED, CHARTER (ice_air_charter, 0.2.16: an airliner that flies ICE Air charters and other work too) and CLEARED; WATCHED is every one but cleared, and CHARTER_LABEL ("ICE Air Charter?") is the label every consumer prints for a charter. ice_air_callsign(callsign, hex) (0.2.18) says whether THIS flight broadcasts ICE Air's call sign: "VELUX", "GXAUX" or "UX" (each + 2 digits, the last two 0.2.22), "TYSON" (TYSON + 2 digits) or "TYS" (TYS + 1-4 digits), on a US Mode S address only (A00000-AFFFFF) and never for AA3410, else None; a carrier's own designator (GXA, BBQ) says nothing either way. While it does, a consumer prints CHARTER_FLIGHT_LABEL ("ICE Air Charter"), without the question mark. Per flight only: it says nothing about the airframe's next flight. A status the reader does not know is installed as given, so a newer list never breaks an older reader. The consumer passes its own aiohttp session, made with SESSION_KWARGS (GitHub's release redirect carries a header over aiohttp's default 8,190-byte limit). No Home Assistant import, no aiohttp import (0.2.12).

Most of the icons are by ADS-B Radar, from its SVG aircraft icon set, used under the author's terms, which ask for a backlink to ADS-B Radar. They are not covered by this package's MIT licence, which covers the code; the icons drawn for this project were donated to ADS-B Radar and are under the same terms. Which is which, and what was changed: src/saw_livetrack/aircraft_icons/ATTRIBUTION.md.

saw_livetrack.adsb_poll - one ADS-B poll per instance

The aircraft integrations ask adsb.fi through one poller per Home Assistant instance (shared(hass.data)), because a free feed's rate limit is counted per IP address, not per integration. Each consumer names the Mode S hexes it wants (set_hexes) and one request a minute asks for all of them (hex_round); a circle (circle) asked again within a few seconds by anyone is answered from the same request; a consumer's circle lying wholly inside another consumer's recent answer is answered from that answer, filtered by distance, with no request at all (0.2.14); every request to one source is spaced a second apart; and one HTTP 429 backoff covers every consumer. Each consumer says which aircraft it draws (claim) so another can leave them alone (claimed_by_others). The circle's sources are the caller's choice; the hex request goes to adsb.fi, and to adsb.lol only while adsb.fi is failing (0.2.24). A source that keeps failing in any other way (a timeout, a broken connection, another status) gets a skip window like a 429's, so one hung feed does not stall every consumer, and the log carries one WARNING when a source starts failing and one when it has answered again. No Home Assistant import, no aiohttp import (0.2.13).

saw_livetrack.ais and saw_livetrack.aisstream - AIS, once per instance

saw_livetrack.ais is the shape every AIS source yields (Observation, units in the field names, None for anything a transmitter left unavailable), the rate-of-turn decode (rot_to_dps), what a transmitter is (object_kind: vessel, net buoy, aid to navigation, base station, SAR aircraft, distress beacon) and the MMSI join table for static data (StaticCache). Moved from Live Track Ships in 0.2.19.

saw_livetrack.aisstream reads aisstream.io through one client per Home Assistant instance (shared(hass.data)). aisstream gives each connection one subscription, and an MMSI filter applies to every box in it, so the client holds at most two connections per API key: an AREA subscription, the union of every area consumer's boxes (add_area), and a VESSELS subscription, the union of every list consumer's MMSIs, at most 200 (add_vessels). Each observation goes to the consumers whose boxes hold it (and whose list names it), labelled with that consumer's domain. A change of consumers replaces the subscription on the open connection, at most once a second; the last consumer of a connection closes it. Static messages feed one StaticCache (client.statics). Each user brings their own aisstream API key. No Home Assistant import; aiohttp only when it connects (0.2.19).

Each consumer says which vessels it draws (claim) so another can leave them alone (claimed_by_others; release, and remove drops a consumer's claims), as on adsb_poll. A VESSELS consumer with no valid MMSI is refused, since aisstream would read an empty MMSI filter as none at all (0.2.20).

saw_livetrack.enforcement_vessels - the EnforcementVessels list

Reads the public EnforcementVessels list (release data) once per Home Assistant instance: shared_list(hass.data, cache_path) gives the one reader, refresh(session) checks SHA256SUMS first and fetches the list only when it changed, installing it only when its bytes match. A failure keeps the cached copy (.storage/saw_enforcementvessels.json), which never expires. Lookups by MMSI: vessel() (status, name, agency, the entry's short form, hull) and watched() (every MMSI not cleared: what a VESSELS subscription asks for); theatre(lat, lon) gives the box where the list says the work happens, if any. A status the reader does not know is installed as given. No Home Assistant import, no aiohttp import (0.2.20; moved from Live Track SALT).

The look of a listed vessel, the same for every integration that draws one (0.2.21; moved from Live Track Ships): listed(reader, mmsi) (the row, unless cleared), label(v) (the entry's short form, else the agency, else "Listed vessel", at most 22 characters), name(v, ais_name) and operator_short(v) for the hover's first line, attributes(v, ais_name) (unknown values omitted), lights(v) (the glow on a sourced row), LIST_TONE ("mil", the Coast Guard's army green in saw_livetrack.aircraft_icons) and attribution(base), the source's credit followed by the list's ODbL line (LIST_ATTRIBUTION). The picture stays the integration's.

saw_livetrack.faa_registry - the FAA registry, once per instance

The FAA's Releasable Aircraft Database, read once per Home Assistant instance for the aircraft integrations (0.2.15): shared(hass.data, storage_dir) gives the one registry, load() reads the caches (.storage/saw_faa_gov_aircraft.json, saw_faa_owners.sqlite), and refresh(session) downloads the archive when the cache is a week old or of an older rule, under one lock, so two integrations never download it twice. A failure keeps the tables in use; the owner table never breaks the government one. lookup_government(hex) gives a government registrant's registration, owner, owner class (categorise: federal law enforcement, law enforcement, Coast Guard, military, fire and EMS, ...) and, for law enforcement, a short name (operator_short: DHS, CBP, ICE, FBI, USMS, DEA, ATF, USSS, USCG, DOJ, or a state or local force shortened to 22 characters). lookup_owner(hex) gives any registration's title holder. from_feed_flags() reads the feed's military and PIA flags. The data comes from the FAA, which withholds what 49 U.S.C. 44114 requires before it publishes; nothing is shipped. No Home Assistant import, no aiohttp import.

saw_livetrack.airlines - the airline behind a callsign

lookup_operator(callsign) gives (designator, operator name) for an airline callsign (UAL308 -> ("UAL", "United Airlines")), (None, "Private") for a US registration. airline_short(callsign) gives the name a person reads on the map's hover, "United" in "United 1678", for a known designator followed by a flight number (7 characters at most): a single-brand regional by its brand (ENY -> American Eagle), a multi-brand one by its operator (SkyWest, Republic). None for a special callsign (TROY329, VELUX55) or a registration. A hand-kept table of about 160 designators; moved here from Live Track Aircraft in 0.2.17 so Live Track SALT reads the same one.

Data attribution

Train data is provided by Amtraker and is licensed under the Open Data Commons Attribution License (ODC-By) v1.0.

Time-zone boundaries (saw_livetrack.zones) are from Timezone Boundary Builder, embedded in tzfpy, and are licensed under the Open Database License (ODbL) v1.0. zones.DATA_VERSION() names the release.

Finnish rail data (saw_livetrack.digitraffic_rail) is from Fintraffic / digitraffic.fi, licensed under Creative Commons Attribution 4.0.

Spanish rail data (saw_livetrack.renfe) is from Renfe, licensed under Creative Commons Attribution 4.0.

Transport for NSW data (saw_livetrack.tfnsw) is from Transport for NSW, and Victorian data (saw_livetrack.transport_vic) from Transport Victoria, both licensed under Creative Commons Attribution 4.0. Each user brings their own key; nothing here redistributes the data.

Norwegian rail data (saw_livetrack.entur): contains data under the Norwegian licence for Open Government data (NLOD) distributed by Entur. Nothing here redistributes the data.

Swedish rail data (saw_livetrack.trafikverket) is from Trafikverket, released under CC0 1.0. Each user brings their own key; nothing here redistributes the data.

The enforcement-aircraft list (saw_livetrack.enforcement_list) contains information from EnforcementAircraft, which is made available under the Open Database License (ODbL) v1.0.

The enforcement-vessels list (saw_livetrack.enforcement_vessels) contains information from EnforcementVessels, which is made available under the Open Database License (ODbL) v1.0.

Aircraft positions (saw_livetrack.adsb_poll) are from adsb.fi, and from adsb.lol (ODbL v1.0) where a consumer asks it and for the hex request while adsb.fi is failing; each consumer credits the feeds it uses.

Vessel positions (saw_livetrack.aisstream) are from aisstream.io, which publishes no terms of use; each consumer credits it.

Irish rail data (saw_livetrack.irish_rail) is from Irish Rail (irishrail.ie), which publishes its realtime API "as is" with no licence stated, and the timetable from the National Transport Authority, CC BY 4.0, provided as is.

Dutch train positions and track (saw_livetrack.ns) are from NS (apiportal.ns.nl), read with each user's own key; the timetable and delays from OVapi, CC0.

Disclaimer

Independent project. Not affiliated with, endorsed by or connected to Amtrak, VIA Rail Canada, Brightline, VR, Fintraffic, Renfe, Transport for NSW, Transport Victoria, Entur, Trafikverket, Irish Rail (Iarnród Éireann), the National Transport Authority, NS, Arriva, Keolis or OVapi.

Tests

python tests/test_track.py
python tests/test_amtraker.py
python tests/test_shared_coordinator.py
python tests/test_digitraffic_rail.py
python tests/test_map_card.py
python tests/test_shared_map_card.py
python tests/test_renfe.py
python tests/test_tfnsw.py
python tests/test_transport_vic.py
python tests/test_aircraft_icons.py
python -m unittest discover -s tests -p "test_gtfs_*.py"
python -m unittest discover -s tests -p "test_enforcement_list.py"
python -m unittest tests.test_adsb_poll
python -m unittest tests.test_aisstream
python -m unittest tests.test_enforcement_vessels
python -m unittest tests.test_zones
python -m unittest tests.test_entur tests.test_shared_entur
python -m unittest tests.test_trafikverket tests.test_shared_trafikverket
python -m unittest tests.test_irish_rail tests.test_shared_irish_rail tests.test_irish_rail_runs
python -m unittest tests.test_timetable
python tests/test_ns.py

The first four, test_aircraft_icons.py and test_ns.py are scripts that exit non-zero on a failed check; run them directly, not through unittest discover.

Licence

MIT, except the ADS-B Radar aircraft icons, which are under their author's terms: see src/saw_livetrack/aircraft_icons/ATTRIBUTION.md.

About

Shared Amtraker API client for the Live Track passenger-rail Home Assistant integrations (Amtrak, VIA Rail, Brightline). Pure Python, no HA dependency.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages