Pico Transit is a friendly little companion for getting around on public transit. Real schedules, real-time arrivals, live connections at any stop, and a live map that shows exactly where your ride actually is β no ads, no clutter, no infinite scroll. Just "where's my bus," answered nicely. πβ¨
Right now Pico Transit knows its way around MBTA, RIPTA, RTD Denver (plus Bustang, Colorado's statewide coach service), LTC Ontario (London, Ontario), STM MontrΓ©al, CTA, Metra, Pace, NYC Subway, LIRR, Metro-North, LA Metro, and around 50 SF Bay Area operators (BART, Muni, AC Transit, Caltrain, VTA, and dozens more via 511.org's regional feed) β with more agencies hopefully hopping aboard down the road. It's built on the Light SDK for the Light Phone III, so it stays just as calm and un-distracting as the rest of your Light experience.
Pico Transit can be used alongside the light phone's directions tool for more context on your commutes, or standalone, covering buses, commuter rail.subway systems, and transit stations.
- π Stations list opens straight to arrivals now β tapping a station in the Stations list used to always open its platform map; now it jumps straight to that station's live upcoming arrivals instead (the map's still one tap away from there). Tap-and-hold does the opposite, opening the platform map. On by default, and scoped to the Stations list only β nothing else changes. The Map screen's own top bar now also reminds you what double-tap and tap-and-hold do there, but only for whichever of those two gestures you actually have on in Settings.
- π Select Run, for CTA 'L' trains and MBTA Green Line β both show up on Trip Detail as a "Closest match," Pico Transit's own best guess at which live vehicle is yours. Select Run lets you confirm or correct that guess yourself: open it to see the whole trip's stop list with each live vehicle shown right on its current stop and tap the one you're on, or use the quick Next/Previous stepper next to "Select Run" to nudge the pick one vehicle at a time. Both are optional in Settings (on by default; the stepper is a nested toggle under it, off by default).
- π Fixed a "running 1400+ minutes early" bug on trips that cross midnight β a trip that starts before midnight and is still running into the early morning uses GTFS's own "transit day" convention (hour values past 24:00, staying on the service day it started on) β several shared schedule queries only ever checked whether a trip's service was active today, missing that case entirely. Fixed by also checking yesterday's service for a trip still plausibly in progress.
- π§ Fixed routes showing more "directions" than they actually have β MBTA's Franklin/Foxboro Line, for example, was showing 4 separate directions instead of 2, because every distinct destination sign (a short-turn train to Readville, one continuing to South Station) counted as its own direction. Directions are now grouped by GTFS's real
direction_id(never more than 2), with a proper Inbound/Outbound header wherever an agency publishes that data (MBTA does, viadirections.txt) β every real destination stays individually pickable, none get hidden or merged away. - π No more "phantom stops" on short-turn trips β picking "Toward Readville" on that same Franklin/Foxboro Line used to still list every stop all the way to South Station, since a short-turn train and the full-length one share a direction. The stop list is now scoped to exactly what the picked destination actually reaches, and also skips any stop with no departures left today, so tapping in never dead-ends on an empty screen.
- β±οΈ Departures now match the direction you picked β by default, a stop shared by a short-turn trip and a longer one (e.g. Readville vs. South Station) shows both, since either gets you at least as far as the shorter one promises β but never the other way around, so picking the longer destination never shows you a train that stops short of it. A new Settings toggle ("Include longer trips in departures") switches to an exact match only, for anyone who'd rather not see the extra trips at all.
- π Bustang live tracking β Bustang, Colorado's statewide intercity coach service, now merges right into RTD Denver: its own routes and stops show up alongside RTD's own in schedules and connections, and its live vehicles now track on the map too, not just its static timetable.
- β΄οΈ Ferries now have their own icon β SF Bay Ferry, Golden Gate Ferry, Angel Island Tiburon Ferry, and Treasure Island Ferry show up on the map with a proper ferry icon instead of being lumped in with buses, matching dark/light map theming like every other mode.
- π Fixed a stuck home screen progress bar β This was noticed in agencies that never populate
current_stop_sequencein their live vehicle feed left the home screen's trip progress bar frozen in place, even though the same trip's own Trip Detail screen showed it moving. The home screen now falls back to the same GPS-proximity and TripUpdate-based matching Trip Detail already used. - π A real onboarding step, and a clock on the home screen β first launch (or picking "Transit Agency" again in Settings) now opens a dedicated welcome screen to choose your agency, instead of the home screen doubling as an always-visible picker. Once an agency's picked, the home screen shows a ticking clock in that agency's own timezone, its name, and a loading indicator while its schedule downloads β and the clock stays put once a trip's boarded too, right alongside your live ETA and progress bar.
- π Searching a much longer agency list β with the SF Bay Area rollout the picker jumped from a handful of agencies to over 50, so search now kicks in automatically once the list passes 10 entries: an inline live-filter keyboard docks right into the picker, same pattern used everywhere else in the app.
- π Comment and documentation audit β every code comment across the tool module got reviewed for accuracy against current source and rewritten in plainer, more durable language β less session-specific narration and dated verification detail, more of what a future reader actually needs to know. Caught (and fixed) several comments that had quietly drifted from what the code actually does.
- π More agencies coming soon β thanks to community support, Pico Transit's agency list keeps growing beyond where it started.
- π NYC Subway realtime β still blocked on an architecture decision: the feed is split across 8 separate live sources with non-overlapping trip_id ranges, which doesn't fit the one-URL-per-agency model every other realtime source uses today. Needs either a new merge mechanism or accepting the extra polling cost before it can be wired in.
β οΈ Service alerts β surfacing GTFS-RT's Alerts feed (detours, delays, service changes) isn't wired in yet; today Pico Transit only reads TripUpdates and VehiclePositions. Planned so riders get a heads-up on disruptions, not just a vehicle that's simply running late.
-
π Pick your agency β MBTA, RIPTA, RTD Denver, LTC Ontario, STM MontrΓ©al, and dozens more (see the full list above) β and Pico Transit downloads their schedule right onto your phone. First launch (or switching agencies later from Settings) opens this as its own welcome screen; once picked, the home screen shows a ticking clock, your agency's name, and a loading indicator until its schedule is ready.
-
βοΈ Settings β switch agencies any time (reopens the same welcome picker), light/dark map tiles, and on/off toggles (tap-and-hold a stop to jump to its arrivals β the same gesture also jumps from a Station map's own name to the main map centered on it; double-tap a station to zoom into its platforms; track tapped-open stops' own vehicles on the map; the home screen's trip progress bar; the home screen's daily message; "See Everything," a map mode covered below; and "Only download over Wi-Fi," on by default, so a schedule refresh never surprises you with cellular data usage). A "Clear schedule cache" action frees up space by deleting every agency's downloaded schedule, then re-downloads your current one right away.
-
π Schedules β browse by Subway π, Commuter Rail π, or Bus π, pick a route, a direction, and a stop, and see every departure today. New: tap the header on the Departures screen to plan ahead with tomorrow's schedule instead.

Routes for each agency Pico Transit currently supports:
-
π Connections β tap any stop along a trip to see what else comes through there next, across every platform of a station, not just the one your trip happened to use. Great for planning a transfer on the fly.
-
π Explore/Leave now β type where you are (or let Pico Transit take a quick IP-based guess π°οΈ) and get the closest stops, nearest first β it remembers your last search, so ducking into a stop's arrivals and back doesn't make you search all over again. Need something more precise, search for an address or landmark and find the closest stops in feet to it, then find how soon the next trip will arrive at your stop.
-
β±οΈ Live ETAs β real-time predictions with On Time / Late / Early badges, whenever the agency's live feed is playing along nicely.
-
πΊοΈ Map β your stop, pinned on a live map, with nearby stops you can tap to reveal their names. Live vehicles show up right where they actually are, with a matching icon for their mode (subway/light rail, commuter rail, bus, ferry). Flip on "See Everything" (Settings) to drop the usual "just this stop's own vehicles" filter and plot every live vehicle in view instead, labeled with just its route until you tap it; narrow it back down by tapping a stop ("Filter by stop" β tags each vehicle TO/FROM/AT that stop) or by mode (Bus/Subway/Commuter Rail).
-
π Gestures β tap and hold a stop or station's name β on the Map, in Schedules while picking where to board, or in the Stations list β to jump straight to its live upcoming arrivals instead of whatever a plain tap would open there. On the Map itself, double-tap a multi-platform station to zoom into a view of just its own platforms, then double-tap its name again to zoom back out to the main map and see what else is nearby. On Trip Detail, tap and hold works a little differently depending on whether that trip's boarded: not boarded, a plain tap opens a stop's connections and tap-and-hold jumps to its live arrivals instead; boarded, a plain tap sets (or clears) that stop as where you're getting off, and tap-and-hold still opens its connections.
-
π Stations β browse every real multi-platform station an agency has, and open a zoomed-in map of just that station's own real platforms and gates (elevators, entrances, and escalators are filtered out). For MBTA commuter rail, once a specific track is assigned β usually 10-15 minutes before departure β its vehicle shows up right on that track's own platform.
-
βΆοΈ Board a trip β from any Trip Detail screen, tap Play to make it your current trip. Keep track of which stop you're closest to from the vehicle icon.Tap a stop along the way to mark where you're getting off β reach it, and Pico Transit throws a little "You've reached your stop! π" celebration and jumps you to that stop's upcoming arrivals, whether you were looking at the trip or just sitting on the home screen. -
π¦ Home screen trip status β while a trip is boarded, the home screen swaps its usual heading for your route, live ETA, and stops remaining, plus an optional progress bar with a little vehicle marker crawling from your boarding stop toward your alight stop β the clock stays put through all of it, right above your route.
-
β©οΈ Jump back anytime β a Play icon shows up in the corner of every screen while a trip's boarded, one tap from wherever you are back to its live tracking; a plain circle in the footer does the same for the home screen itself.
-
βΉοΈ About β a full legend of every icon and mode Pico Transit uses, reachable right from the home screen.
Pico Transit lives inside the light-sdk monorepo β check the root README first for one-time setup (GitHub token, Android Studio, etc). Once that's done:
- Open the whole
light-sdkproject in Android Studio. - Run the
:toolmodule on an emulator, or better yet, the LightOS emulator β that's this app! π - Tap an agency, grab a coffee β while it downloads the schedule, and you're off.
Light's official "build it, sign it, share it" pipeline for community tools isn't quite ready yet β vetting is expected around August/September 2026, with the full sharing platform following in October. So for now, sideloading via ADB is the way, and Light's own docs say that's totally fine for the adventurous! π€
- In
lighttool.toml, pointserverPackageat the real LightOS package instead of the emulator:serverPackage = "com.lightos"
- Build a debug APK:
./gradlew :tool:assembleDebug
- Turn on Developer Options + USB debugging on your Light Phone III (same as any Android device), plug it in, then:
adb install -r tool/build/outputs/apk/debug/tool-debug.apk
- On the phone, allow "Any tools" in LightOS's tool settings β it'll warn you this one isn't Light-vetted yet, which is expected for a homemade build like this. π§
That's it β happy transit-ing! πππ
- Every realtime/static feed request goes through
pico-transit-proxy, a small Cloudflare Worker (its own separate repo, not part of this codebase) β a fixed allowlist of routes to fixed upstream URLs, never taking a target from the incoming request. It's what makes the HTTPβHTTPS redirects below possible, keeps every agency's API keys as worker-side secrets that never ship in the app itself, and is the one place the 511 regional-feed sharing described below actually happens. - RIPTA's and LTC's live feeds are HTTP-only at the origin (no HTTPS). Both now resolve through the proxy's redirect to HTTPS, so the app itself never connects over cleartext, and the
:netconfigmodule's cleartext exception is no longer needed. - No device GPS is used anywhere β the SDK doesn't expose it to tools yet. Nearby-stop and location search are powered by Nominatim (OpenStreetMap) and IP-based geolocation instead. Be kind to their free APIs! π
- Stations are deduplicated using GTFS's
parent_stationβ a big station with several platforms (subway entrances, commuter rail tracks, etc.) shows up as one marker/entry, not one per platform, while still resolving to the right platform'sstop_idunder the hood for schedule lookups. Only real platforms and boarding areas count as "member platforms" for this β GTFS also links entrances, elevators, and escalator nodes to the same parent station, and those are filtered out so a big hub's map isn't cluttered with dozens of non-boardable points. - Boarding a trip is a saved reference, not a background tracker β Pico Transit never polls a live feed while the app itself isn't open. "You've reached your stop" detection only runs while Trip Detail or the home screen is actually visible and polling, the same way every other bit of live tracking in the app works.
- Commuter rail track assignments come from MBTA's V3 API, not GTFS-RT β GTFS-RT never publishes which specific track a commuter rail trip will use, and MBTA's own dispatch system usually doesn't decide until 10-15 minutes before departure. Pico Transit polls the V3 API (
api-v3.mbta.com) for this and for commuter rail's own live vehicle positions, falling back to the standard GTFS-RT feed if a trip has no V3 match yet. CTA's own Bus Tracker API is wired the same way for buses, matching a live vehicle back to a scheduled trip by its route + scheduled departure time rather than a GTFS-RT trip_id. - CTA's 'L' trains and MBTA's Green Line have no reliable bridge from their live feed back to a real scheduled trip_id β Train Tracker identifies a train by run number, and most currently-running Green Line vehicles are synthetic GTFS-RT "ADDED" trips with no static trip underneath at all (confirmed live: ~96% of a given sample). Both are handled the same way:
FuzzyRunTripsranks live runs against scheduled trips ordinally (soonest live paired with soonest scheduled, and so on) to find the closest real trip to attach to, surfaced everywhere as "Closest match" rather than a certain live ETA. It's necessarily an approximation, so Select Run (see above) exists for a rider to confirm or correct the guess directly. Live ETAs (Upcoming Arrivals) and boarded-trip tracking (Trip Detail, the home screen progress bar) both work fully off this "Closest match" today β what's not there yet is plotting these live vehicles on the map itself, since a live run with no confirmed trip has no fixed position to anchor a marker to. - CARTO's map tiles now require an API key β like every other agency key in this app, it's a worker-side secret injected server-side by
pico-transit-proxy, never shipped in the app. The app never talks to CARTO directly. - NYC Subway's realtime is split across 8 separate MTA feeds by line group, not one combined feed β unlike LIRR/Metro-North's single URL.
MultiGtfsFeed(the same mechanism Bustang uses to merge into RTD Denver) now covers this case too: with no static feed of its own (feedUrlleft null), it's just another realtime source layered onto the primary agency's own already-ingested schedule, unioned in with no id-prefixing since the trip_ids already match directly β versus Bustang's case, a genuinely separate static+realtime feed that does need prefixing to avoid colliding with RTD's own ids. - ~50 SF Bay Area agencies share one upstream realtime fetch, not 50 β 511.org publishes one combined feed per region covering every agency it integrates, with each agency's own trips distinguished by a code prefix (e.g.
BA:1965572for a BART trip_id). The proxy worker fetches and caches that one shared feed per cache window, then filters and re-prefixes it server-side into each agency's own slice on request β so plugging in dozens more agencies from the same aggregator never multiplies the number of live requests being made.
Thanks to Jose Briones for his continued support integrating RTD Denver and Colorado transit into Pico Transit.
Thanks to Guy Dupont on the Light team for adding LightConnectivity to light-sdk, the network-state API that made the "Only download over Wi-Fi" setting possible β and, more broadly, to the whole Light Phone team for all the work that's gone into making this SDK a genuine pleasure to build on, and the LightOS developer community such a positive, supported, and all-around amazing place to be.
Thanks to Claude (Anthropic) for extensive collaboration throughout Pico Transit's development β including learning Kotlin from scratch, implementing the "Only download over Wi-Fi" setting, and working through a pass on the codebase's comments and documentation together.
The tool/ directory (Pico Transit itself) is licensed separately from the rest of the monorepo β see LICENSE-TRANSIT (MIT, Β© Christian Ferreira / CJFData). The rest of light-sdk remains under its own LICENSE (MIT, Β© The Light Phone).























