기차를 놓치지 않기 위한 Rust 프로젝트
(lilymara.xyz)- 샌프란시스코에서 차 없이 이동하는 생활에서는 집을 나서기 전 가까운 정류장의 다음 도착 시간만 바로 확인하는 장치가 더 실용적임
- 오래된 Kindle을 탈옥해 벽걸이 전자잉크 안내판으로 바꾸고, 처음에는 BART 웹페이지 캡처 이미지를 주기적으로 표시함
- MUNI까지 붙이자 여러 브라우저 탭, Raspberry Pi 메모리 부담, HTTP 429가 겹쳐 스크린샷 기반 구조의 신뢰성이 떨어짐
- Rust 재작성 버전은 511.org Stop Monitoring API에서 데이터를 가져오고,
skia-safe로 Kindle 해상도와 8-bit 색상 깊이에 맞는 PNG를 직접 생성함 - 브라우저 엔진을 없애고 캐싱, 오류 표시, 레이아웃 조정까지 더해 실사용 가능한 Kindle 대중교통 표시판으로 완성됨
집을 나설 때 필요한 정보는 단순함
- 샌프란시스코에서 자동차 없이 이동하며 버스, 기차, 트램, 트롤리버스, 케이블카, 페리 같은 대중교통을 자주 이용함
- CityMapper 같은 앱은 목적지 안내에는 유용하지만, 집 앞에서는 “어디로 갈지”보다 각 노선의 다음 도착 시간이 더 중요함
- 이미 이용할 정류장과 노선을 알고 있다면 범용 내비게이션 앱의 현재 위치·목적지 모델은 필요보다 복잡함
- 오래된 Kindle을 스마트 홈 디스플레이로 쓴 Matt Healy의 글과 오래된 Nook을 iCloud 포토 프레임으로 쓴 Ben Borgers의 글에서 아이디어를 얻음
Kindle에 이미지를 표시하기까지
- 첫 단계는 Kindle을 탈옥해 USBNet을 활성화하고 SSH 접속을 가능하게 만드는 것이었음
- 이후 cron 작업으로 디스플레이를 주기적으로 갱신함
- mobileread 위키와 포럼의 Kindle 해킹 자료가 참고 자료로 쓰임
- BART의 Real Time Departure 페이지는 원하는 역의 다음 열차와 자동 새로고침을 제공해 초기 데이터 소스로 적합했음
- Kindle CLI의
eips로 PNG를 표시하려 했지만, 처음에는 이미지가 심하게 늘어나거나 잘려 사용할 수 없었음 - 원인은 Kindle 표시 도구가 24-bit RGB PNG를 제대로 처리하지 못하고 8-bit 이미지처럼 읽는 데 있었음
- ImageMagick
convert에서-colorspace gray -depth 8을 적용하자 정상 표시됨 - Kindle 화면 크기에 맞는 이미지와 8-bit 색상 깊이가 핵심 조건이 됨
- ImageMagick
첫 구현: Node.js, Puppeteer, Raspberry Pi
- 초기 서버는 Node.js와 Puppeteer로 BART 웹페이지의 필요한 영역을 캡처함
- 캡처 이미지는 Kindle 화면 크기와 8-bit 회색조에 맞게 변환한 뒤 HTTP 엔드포인트로 반환함
- Raspberry Pi에서 서버를 돌리고, Kindle은 cron으로 매분 이미지를 가져와
eips로 표시함 - BART 페이지 자체가 자동 새로고침을 지원했기 때문에 서버 시작 시 페이지를 한 번 열어두고 요청마다 새로 로드하지 않는 방식으로 구현함
- 이 버전은 한동안 잘 동작했지만, 새로고침이 가끔 멈추거나 서버가 메모리 부족으로 재시작을 요구함
MUNI 확장으로 드러난 한계
- 샌프란시스코 베이 지역에는 27개 대중교통 운영사가 있으며, 첫 구현은 지역 고속철도인 BART만 다룸
- 이후 MUNI의 버스와 경전철 정류장 도착 시간도 함께 보고 싶어짐
- MUNI 웹페이지도 정류장별 도착 시간을 제공해, 처음에는 여러 정류장 페이지를 Puppeteer로 캡처한 뒤 ImageMagick으로 합치는 방식을 시도함
- 화면의 7개 섹션은 각각 Puppeteer가 유지해야 하는 브라우저 탭에 해당함
- Raspberry Pi에는 충분한 메모리가 없었고 Chrome은 가볍지 않았음
- MUNI 페이지는 자주 요청하면 HTTP 429를 반환함
- BART와 MUNI를 함께 표시하는 화면은 BART 단독 화면보다 훨씬 불안정했고, 한눈에 보는 안내판에는 더 신뢰할 수 있는 구조가 필요했음
Rust로 다시 설계한 구조
- 재작성 버전은 Puppeteer를 제거하고 Rust HTTP 서버가 PNG를 직접 생성하는 구조로 바뀜
- 공개 인터페이스는 Node.js 서버와 비슷하게 유지하되 내부 구현은 완전히 달라짐
- 주요 구성 요소는 세 가지임
- 511.org에서 다음 도착 시간 가져오기
- 도착 시간을 8-bit PNG 이미지로 렌더링하기
- 생성한 PNG를 HTTP 서버로 반환하기
- HTTP 서버에는 Axum을 사용함
- PNG 렌더링에는 rust-skia를 사용함
- Skia는 Chrome의 기반 그래픽 라이브러리이기도 해, Puppeteer보다 적은 부담으로 렌더링 기술을 활용할 수 있음
- 최종 서버 코드는 약 1,200줄이었고, 중요한 부분만 정리됨
511.org Stop Monitoring API에서 데이터 가져오기
- 511.org의 Stop Monitoring API는 베이 지역 정류장의 예상 도착·출발 시간을 XML과 JSON으로 제공함
- 사용한 엔드포인트는
http://api.511.org/transit/StopMonitoring/…]형태임 agency는 27개 베이 지역 대중교통 운영사 중 하나를 지정하며, SF MUNI의 코드는SF임- API는 한 번에 한 운영사 데이터만 가져올 수 있음
- 특정 정류장 하나의 도착 시간 또는 해당 운영사의 전체 정류장 데이터를 반환함
- 여러 정류장을 부분 집합으로 한 번에 가져오는 방법은 찾지 못함
- 511.org API 제한은 시간당 60 요청이라, 정류장별로 여러 번 요청하는 방식은 실시간 표시에 적합하지 않음
- SF MUNI 전체 데이터를 요청했을 때 HTTP 요청은 약 5초가 걸렸고 약 27 MiB 데이터를 반환했으며, 포맷한 JSON은 100만 줄을 넘음
- 전체 응답에는 약 26k개의
MonitoredStopVisit항목이 있었지만, 실제 화면에는 특정 정류장과 노선의LineRef,DirectionRef,DestinationName,StopPointRef,ExpectedArrivalTime,DestinationDisplay만 필요했음
Rust 데이터 처리에서 만난 세부 이슈
reqwest,serde,serde_json,chrono,tokio,eyre로 HTTP 요청, JSON 파싱, 시간 계산, 비동기 실행, 오류 처리를 구성함- MUNI 정류장 ID는 SF MUNI 웹사이트의 정류장 페이지에서 확인할 수 있음
- 예를 들어 Persia Ave & Paris St에는 inbound용
15918, outbound용15919가 따로 있음 - MUNI 정류장은 방향별로 별도 ID를 가짐
- 예를 들어 Persia Ave & Paris St에는 inbound용
- 511.org 응답은 JSON 앞에 UTF-8 Byte Order Mark를 붙여 반환했고, 바로
.json()으로 파싱하면expected value at line 1 column 1오류가 발생함 .text()로 응답을 UTF-8 문자열로 읽으면 BOM이 제거되어serde_json::from_str로 파싱할 수 있었음- 필터링 후에는 관심 있는 정류장의 약 20개 도착 정보만 남아, 처음의 26k개보다 훨씬 다루기 쉬워짐
LineRef와DirectionRef기준으로 묶고ExpectedArrivalTime기준으로 정렬한 뒤, UTC 타임스탬프를 “몇 분 뒤” 형식으로 변환함
Skia로 Kindle용 PNG 생성
- PNG 렌더링은
skia-safe로 구성함 - Kindle 화면 크기인 1024x758에 맞는
Gray8비트맵을 만들고, 배경을 흰색으로 채운 뒤 텍스트와 선을 그림 - 먼저 “Hello World”를 출력하는 PNG8 이미지를 만들어 렌더링 파이프라인을 확인함
- 이후 가짜 도착 시간 데이터로 inbound와 outbound 두 열을 가진 표 형태 이미지를 생성함
- 실제 API 데이터와 결합하자 테스트 데이터보다 목적지와 도착 시간이 다양해져, 고정 레이아웃에서는 문자열 겹침이 발생함
- 빠른 반복과 API 제한 회피를 위해 응답을
data.json에 캐시하는 임시 코드를 추가함
표시 레이아웃 다듬기
- 초기 실제 데이터 화면은 행마다 표시할 데이터 양이 달라 가독성이 떨어졌음
- 개선한 내용은 다음과 같음
- 도착 시간을 오른쪽 정렬해 표 끝 공간을 줄임
- 각 시간 뒤의
min을 제거하고 목록 끝에만min을 붙임 - 다음 3대 차량만 표시함
- 긴 노선 이름을 고려해 목적지 이름을 오른쪽으로 이동함
- 이후 노선 ID를 둥근 회색 말풍선 형태로 표시하기 위해
measure_str로 텍스트 경계를 계산함 - 목적지 문자열은 노선 ID 말풍선의 오른쪽 끝에서 15픽셀 떨어진 위치에 배치함
- 상단에는 연한 회색 헤더를 추가하고
Muni Inbound,Muni Outbound를 중앙 정렬로 표시함
Axum으로 PNG 제공하기
- HTTP 서버는
Axum 0.7로 만들고/stops.png라우트에서 PNG를 반환함 - 응답 헤더는
Content-Type: image/png로 설정함 - 처음에는 빈 본문으로 HTTP 200을 확인한 뒤, 이미지 생성 함수를 연결해 PNG 바이트를 메모리에서 바로 반환하도록 바꿈
- Kindle에서
wget으로 이미지를 받아eips로 표시하면 처음에는 이미지가 회전되고 잘리는 문제가 있었음 - Kindle은 세로 방향 사용을 기본으로 하므로, 가로 화면으로 보이게 하려면 렌더링 전에 회전 행렬을 적용해야 했음
- 이미지 크기를
(758, 1024)로 뒤집음 - 캔버스를
90.0도 회전함
- 이미지 크기를
실사용 버전에 더해진 기능
- 실제 사용 버전에는 다음 기능이 추가됨
- API 데이터를 백그라운드에서 계속 가져와 HTTP 서버가 빠르게 응답하는 캐싱 시스템
- 왼쪽 아래의 시간 표시와 오른쪽 아래의 데이터 캐시 상태
- 안티앨리어싱
- 도착 시간 영역과 겹치지 않도록 목적지 이름을 자르는 처리
- 노선별로 다른 회색을 써서 빠르게 구분할 수 있게 하는 처리
- 설정 파일 기반 구성
- 오류가 발생하면 오류와
.source()체인을 PNG로 렌더링해 Kindle에서 바로 볼 수 있게 하는 처리
- 함께 만든 단순화된 서버 코드는 transit-kindle-simplified에 있음
- 실제 벽걸이 Kindle을 구동하는 서버 코드는 transit-kindle에 있음
- BART 스크린샷 기반 버전은 2023년 2월쯤 동작했고, Rust 재작성의 최종 UI 다듬기는 2024년 1월에 완료됨
댓글과 토론
Hacker News 의견들
-
OneBusAway 뒤의 비영리단체에서 일하고 있음. OneBusAway는 매일 수백만 명이 버스·기차·트램·푸니쿨라 위치와 도착 시간을 확인하는 데 쓰는 오픈소스 프로젝트임
이미 OneBusAway 서버가 있는 도시라면 새 SDK로 맞춤 앱을 만들 수 있음: https://github.com/onebusAway/?q=sdk&type=all&language=&sort...
서버가 없는 도시라면 올해 만든 Docker 이미지와 OpenTofu 설정으로 GTFS/GTFS-RT 피드를 REST API로 바꿀 수 있음
BART도 GTFS와 GTFS-RT를 제공함: https://mobilitydatabase.org/feeds/mdb-53
미국의 다른 대중교통 기관도 새 연방 규칙 때문에 최소한 정적 시간표 데이터는 GTFS로 공개해야 하는 상황임
관련 소프트웨어에 기여할 개발자는 언제든 필요하고, 특히 하루 25만 사용자의 개선을 도울 iOS 개발자가 절실함. 연락처는 aaron@onebusaway.org
Docker: https://github.com/OneBusAway/onebusaway-docker / OpenTofu: https://github.com/OneBusAway/onebusaway-deployment- 현재 전 세계 지원 도시는 9곳뿐이고 그중 2곳은 베타라서, 더 넓게 퍼져야 오픈소스 기여도 쉬워질 듯함
내 도시도 절차가 간단하면 추가하고 싶음. 최근 라이브러리와 SDK 지원에 엔지니어링 시간을 많이 쓴 것 같은데, 새 기여자가 시간을 들이기 좋게 개발자 경험의 쉬운 경로에 투자하는 편이 좋아 보임
예를 들어 이 문서는 꽤 복잡함: https://github.com/OneBusAway/onebusaway/wiki/Multi-Region - OneBusAway가 배포된 도시 목록을 어디서 찾는지 궁금함. “onebusaway cities”로 검색하면 https://onebusaway.org/onebusaway-deployments/가 나오지만, 거기에 언급한 Adelaide 같은 도시는 없었음
- 오픈소스라면 왜 Play Store에만 올리고 F-Droid에는 배포하지 않는지 궁금함
- OneBusAway뿐 아니라 Google Maps 등 버스 추적 앱들이 왜 이렇게 부정확한지 궁금함
“4분 후 도착”이라고 뜨는데 버스가 눈앞에서 떠나거나, “5분 지연”이라더니 2분 일찍 오는 식임. 버스가 아예 안 나타나기도 함
원천 데이터 문제일 것 같지만, 무엇 때문에 이렇게 신뢰성이 낮아지는지 내부 사정을 알고 싶음. 참고로 Seattle 기준임
- 현재 전 세계 지원 도시는 9곳뿐이고 그중 2곳은 베타라서, 더 넓게 퍼져야 오픈소스 기여도 쉬워질 듯함
-
BART와 MUNI는 모두 General Transit Feed Specification을 지원하고, 이 데이터를 얻는 표준 방식이 있음
https://gtfs.org/documentation/overview/#gtfs-realtime- 실시간 정보가 얼마나 믿을 만한지 궁금함. Google Maps가 “버스 4분 후 도착”이라고 하는데 버스가 떠나는 걸 보고 있거나, “5분 지연”이라더니 실제로는 2분 일찍 오는 일이 자주 있음
-
정말 마음에 듦. 예전에 Boston에서 차 없이 살 때, A에서 B로 가는 방법이 여러 개이고 “최선”이 시간이나 중간에 들르는 곳에 따라 달라지는 경우를 처리하려고 훨씬 조악한 걸 직접 엮어 만든 적이 있음
예를 들어 출근 전에 아들을 학교에 데려다주고, 가끔은 등원 후 아침을 먹었음. 다음 출발 시간 보기가 있으면 흥미로운 장소에서 4살 아이와 걷는 비결정적인 상황이나, 놓치면 배차 공백이 생기는 열차를 타려고 서둘러야 할지 같은 판단을 훨씬 유연하게 할 수 있었음 -
아직 https://oeffi.schildbach.de/index.html가 언급되지 않은 게 의외임. 여기서 원하는 일을 정확히 해줌
- 비슷한 걸 해주는 앱은 꽤 많지만, 물론 벽에 붙인 Kindle에서 돌아가는 경우는 드묾
- 이것들도 언급할 만함
https://play.google.com/store/apps/details?id=nl.skywave.ovi... / https://apps.apple.com/nl/app/ovinfo/id1144468923
https://drgl.nl/
https://www.rijdendetreinen.nl/
https://spoorkaart.mwnn.nl/ - 북스웨덴 깊은 숲에서도 이런 게 실시간 갱신으로 동작할 줄은 몰랐음
공식 앱보다 훨씬 좋고, 가장 필요한 정보인 가까운 버스 정류장과 다음 버스의 실시간 시간을 바로 보여줌
-
영국이라면 역 출발 안내판을 흉내 낸 출발 안내판 장치를 살 수 있음
https://ukdepartureboards.co.uk/store/product/desktop-depart...- 일부 역에서 쓰는 출발 안내판을 흉내 낸 https://departureboards.mobi/departures/BTH도 있음
- 좋긴 한데, 충동구매로 정당화하기엔 꽤 비쌈
- 스위스에는 트램 안내판을 흉내 낸 비슷한 제품도 있음
https://tramli.ch/en
-
나도 SF에 살고 있고, 같은 문제를 풀려고 A Better Ride라는 iOS 앱을 만들었음
주변 정류장의 대중교통 출발 시간만 보여줌. 목표는 대중교통을 예측 가능하고 탐색하기 쉽게 만들어 스트레스를 줄이는 것임. 수익화 없이 여가 시간에 만드는 개인 프로젝트임- 이 앱 정말 훌륭함. 주변 노선과 그 노선이 어디로 가는지만 보여준다는 점에서 다른 대중교통 앱과 다름
환승을 신경 쓰지 않고 갈 수 있는 장소들을 탐색할 수 있게 해줌 - 앱이 정말 멋짐. 실시간 지도가 특히 좋음
나도 이 분야의 하드웨어를 만들고 있고 최근 GTFS를 많이 다뤘는데, 교통 데이터를 파싱하고 보여주는 데 얼마나 많은 작업이 들어갔는지 바로 보임
괜찮다면 각 정류장에서 가능한 이동을 요약하고 묶는 UX 결정, 그리고 백엔드에 대해 이야기해보고 싶음 - 정말 좋음. 길의 어느 쪽인지, 전체 이동 방향이 어디인지 보여주는 식의 세심한 디테일이 많음
- 이 앱 정말 훌륭함. 주변 노선과 그 노선이 어디로 가는지만 보여준다는 점에서 다른 대중교통 앱과 다름
-
Kindle을 탈옥하거나 이미지를 렌더링할 필요는 없음
직접 만든 웹페이지를 Kindle 브라우저로 열고, 디버그 명령으로 Kindle의 “화면 보호기” 광고나 절전 화면을 끄면 됨
브라우저 바가 Kindle 한쪽 가장자리에 남긴 하지만, 기기 방향을 돌려 아래나 오른쪽에 둘 수 있음. 대신 날씨·교통·뉴스 화면을 쉬운 HTML/CSS/JS와 원하는 백엔드 언어로 만들고, 저렴한 DigitalOcean 4달러 인스턴스 같은 곳에서 돌릴 수 있음
https://blog.notfaqs.com/2018/06/kindle-e-reader-disable-scr...
https://www.mobileread.com/forums/showthread.php?t=198334- Amazon이 이런 디스플레이 해킹을 위해 Kindle 브라우저에 키오스크 모드를 장려하고 만들어주면 더 멋질 듯함
- Android가 돌아가는 inkBOOK으로 이렇게 해봤음. Chrome APK를 올리고 직접 만든 웹페이지를 열었음
document.documentElement.requestFullscreen()로 진짜 전체화면에 들어가서 상태 표시줄도 안 보임. 지역 대중교통 API가 웹페이지에서 직접 접근 가능해서 백엔드도 필요 없었음 - 4세대 Kindle로 알아봤는데 인증서가 유효하지 않아 HTTPS 사이트를 거의 못 쓰는 듯함
그래도 로컬 네트워크의 서버와 통신하게 만들면 괜찮을 것 같음 - 이후 Kindle에서는 화면 보호기 비활성화 기능이 제거됐을 가능성이 거의 확실함
-
이 사용 사례를 잘 이해하지 못한 것 같음. “기차를 타고” 싶은 게 아니라 A에서 B로 가고 싶음
기차가 고장났거나, 파업 중이거나, 오후 1~4시에 노선이 막혔거나, 운행 시간이 아니라면 대안을 원함. 그래서 내게는 Google Maps가 주는 것, 혹은 주려고 하는 것이 필요함
Ferry Building에서 Oakland로 가려는데 BART가 고장났다면 페리가 나을 수 있고, 늦은 시간이라면 택시나 Uber밖에 없을 수도 있음
Tokyo에서 Shibuya에서 Azabujuban으로 간다면 Ginza Line→Namboku Line, Hanzomon Line→Oedo Line, Yamanote Line→Namboku Line, 6번 버스, 택시 등 선택지가 있음
각 노선의 역과 버스 정류장 사이도 3~8분씩 걸어서, 지금 어디에 가까운지가 선택에 영향을 줌. 늦었는지, 짐이 크거나 무거운지도 중요함- Google Maps는 걷는 것을 매우 나쁜 것으로, 아무도 원하지 않는 것으로 강하게 가정하는 듯함
예를 들어 어떤 장소에 갈 때 39번 버스를 타고 15분 걸으면 되는데, Google에 물어보면 환승 2번짜리 경로를 줌. 운이 좋으면 버스 한 번+도보와 시간이 비슷한 정도임
그래서 39번 버스가 언제 오는지 알고 싶으면 목적지를 일부러 다르게 말해야 함
또 내가 있는 곳에서는 Google의 실시간 데이터가 의심스럽고, 운영사나 일부 제3자 데이터가 더 신뢰할 만함 - 사용 사례는 일상적인 이동임. 많은 사람은 대부분의 시간에 정말로 “기차를 타고” 싶어함
실시간으로 대단한 계획을 세워야 하는 상황도 있고 그런 도구가 있으면 좋지만, 일상적인 이동은 훨씬 단순한 도구로도 충분히 커버됨
제시한 Tokyo 예시도 흥미롭지만, Google 기준으로 대부분의 대중교통 선택지가 30~40분이라 대략적인 계획에서는 거의 동등함. 택시는 더 빠르고 자전거도 거의 비슷함 - 매일 통근할 때는 정말로 “기차를 타고” 싶을 뿐임
기차가 고장났거나 파업 중이면 실시간 열차 목록에 예상치 못한 공백으로 드러남
도시를 돌아다닐 때는 Google Maps를 열고 목적지를 넣지만, 500번 가본 곳으로 집에서 나갈 때는 굳이 열지 않음 - Tokyo 중심부의 대중교통 경험은 전 세계에 일반화할 수 없음
Disneyland 안에서만의 매장 쇼핑이나 식사 경험을 기준으로 이야기하는 것만큼 의미가 없음. Suica를 찍는 것만 해도 다른 곳보다 몇 배는 빠르고, 애초에 지원되지 않는 곳도 있음 - https://citymapper.com가 마음에 들 수도 있음
기능이 많지만 Tokyo에서 얼마나 지원되는지는 모르겠음. London에서는 집으로 가달라고 하면 비용, 도보 거리, 속도, 환승, 접근성 등을 기준으로 최적화한 여러 방법을 보여줌
- Google Maps는 걷는 것을 매우 나쁜 것으로, 아무도 원하지 않는 것으로 강하게 가정하는 듯함
-
재활용한 Lenovo ThinkSmart View 태블릿/회의실 장치에 Home Assistant와 지역 교통기관 연동을 붙여 비슷한 걸 쓰고 있음
장점은 훨씬 바로 쓸 수 있다는 점임. 물론 Lenovo에 커스텀 Android ROM을 올리긴 했지만, 원하는 걸 무엇이든 보여줄 수 있고 조명이나 로봇청소기 같은 것도 제어할 수 있음 -
BART 지도를 물리적인 LED 표지판으로 만든 아름다운 제품도 있음: https://www.designrules.co/