Skip to main content

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.

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.

Disclaimer

Independent project. Not affiliated with, endorsed by or connected to Amtrak, VIA Rail Canada or Brightline.

Tests

python tests/test_track.py
python tests/test_amtraker.py
python tests/test_shared_coordinator.py
python -m unittest discover -s tests -p "test_gtfs_*.py"
python -m unittest tests.test_zones

The first three are scripts that exit non-zero on a failed check; run them directly, not through unittest discover.

Licence

MIT.

Metadata

Release files for saw-livetrack 0.2.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for saw-livetrack 0.2.5
File Size Uploaded
saw_livetrack-0.2.5.tar.gz 58.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for saw-livetrack 0.2.5
File Interpreter ABI Platform
saw_livetrack-0.2.5-py3-none-any.whl Python 3 none any Details

Total release size: 94.2 kB

Release files / saw_livetrack-0.2.5.tar.gz

Download URL saw_livetrack-0.2.5.tar.gz
Size 58.6 kB
Tags Source
SHA-256 checksum
How to use checksums
a65942dde485019fb1c7987f144d0508f9c34df8b05f422647c3a261342e9ecc
BLAKE2b-256 checksum
How to use checksums
8450b97ed3aab12a1084401ad340cc504d1efde86226941ce0be836260efc98c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.13

Release files / saw_livetrack-0.2.5-py3-none-any.whl

Download URL saw_livetrack-0.2.5-py3-none-any.whl
Size 35.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
62f2548cafa40f9b9c8bac1d3ba91c3e24ec96b3e505f7453f80ab0744049b5e
BLAKE2b-256 checksum
How to use checksums
18a157371fe6bd8fd15e7eeb2bd4a14ca6600eb44f92e48cec4b21c1070779a4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.13

Release history Release notifications | RSS feed

0.2.7

2 release files

0.2.6

2 release files

This release

0.2.5 This release

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page