Dự án Rust để không bị lỡ chuyến tàu
(lilymara.xyz)- Khi di chuyển không có ô tô ở San Francisco, một thiết bị cho phép kiểm tra ngay thời gian đến tiếp theo của điểm dừng gần nhà trước khi ra khỏi nhà thực tế hữu ích hơn
- Một chiếc Kindle cũ được jailbreak để biến thành bảng chỉ dẫn e-ink treo tường, ban đầu hiển thị định kỳ ảnh chụp trang web BART
- Khi thêm cả MUNI, nhiều tab trình duyệt, gánh nặng bộ nhớ trên Raspberry Pi và HTTP 429 cùng xuất hiện, khiến kiến trúc dựa trên ảnh chụp màn hình trở nên kém tin cậy
- Phiên bản viết lại bằng Rust lấy dữ liệu từ Stop Monitoring API của 511.org và dùng
skia-safeđể tự tạo PNG phù hợp với độ phân giải Kindle và độ sâu màu 8-bit - Sau khi loại bỏ engine trình duyệt và bổ sung caching, hiển thị lỗi, điều chỉnh bố cục, hệ thống đã hoàn thiện thành một bảng hiển thị giao thông công cộng trên Kindle có thể dùng thực tế
Thông tin cần thiết khi ra khỏi nhà rất đơn giản
- Khi di chuyển ở San Francisco mà không có ô tô, tác giả thường xuyên dùng phương tiện công cộng như xe buýt, tàu hỏa, tram, trolleybus, cáp treo và phà
- Các ứng dụng như CityMapper hữu ích cho việc dẫn đường tới đích, nhưng trước cửa nhà thì thời gian đến tiếp theo của từng tuyến quan trọng hơn việc “đi đâu”
- Nếu đã biết điểm dừng và tuyến sẽ đi, mô hình vị trí hiện tại/điểm đến của các ứng dụng điều hướng đa dụng trở nên phức tạp hơn mức cần thiết
- Ý tưởng đến từ bài viết của Matt Healy về việc dùng Kindle cũ làm màn hình nhà thông minh và bài viết của Ben Borgers về việc dùng Nook cũ làm khung ảnh iCloud
Cho đến khi hiển thị được ảnh trên Kindle
- Bước đầu tiên là jailbreak Kindle để bật USBNet và cho phép kết nối SSH
- Sau đó dùng tác vụ cron để làm mới màn hình theo chu kỳ
- Tài liệu tham khảo là các hướng dẫn hack Kindle trên wiki và diễn đàn mobileread
- Trang Real Time Departure của BART cung cấp chuyến tàu tiếp theo tại ga mong muốn và tự động làm mới, nên phù hợp làm nguồn dữ liệu ban đầu
- Tác giả đã thử hiển thị PNG bằng
eipscủa Kindle CLI, nhưng ban đầu ảnh bị kéo giãn mạnh hoặc cắt mất nên không dùng được - Nguyên nhân là công cụ hiển thị của Kindle không xử lý đúng PNG RGB 24-bit mà đọc như ảnh 8-bit
- Khi áp dụng
-colorspace gray -depth 8trong ImageMagickconvertthì ảnh hiển thị bình thường - Ảnh đúng kích thước màn hình Kindle và độ sâu màu 8-bit là điều kiện then chốt
- Khi áp dụng
Bản triển khai đầu tiên: Node.js, Puppeteer, Raspberry Pi
- Máy chủ ban đầu dùng Node.js và Puppeteer để chụp vùng cần thiết trên trang web BART
- Ảnh chụp được chuyển đổi phù hợp với kích thước màn hình Kindle và thang xám 8-bit rồi trả về qua một endpoint HTTP
- Máy chủ chạy trên Raspberry Pi, còn Kindle dùng cron để lấy ảnh mỗi phút và hiển thị bằng
eips - Vì bản thân trang BART hỗ trợ tự động làm mới, nên khi khởi động máy chủ chỉ cần mở trang một lần rồi giữ nguyên, thay vì tải lại cho mỗi yêu cầu
- Phiên bản này hoạt động tốt trong một thời gian, nhưng đôi khi việc làm mới bị dừng hoặc máy chủ phải khởi động lại do thiếu bộ nhớ
Những giới hạn bộc lộ khi mở rộng sang MUNI
- Khu vực Vịnh San Francisco có 27 đơn vị vận hành giao thông công cộng, còn bản triển khai đầu tiên chỉ xử lý BART, hệ thống tàu tốc hành vùng
- Sau đó tác giả muốn xem thêm thời gian đến của các điểm dừng xe buýt và light rail của MUNI
- Trang web MUNI cũng cung cấp thời gian đến theo từng điểm dừng, nên ban đầu tác giả thử chụp nhiều trang điểm dừng bằng Puppeteer rồi ghép lại bằng ImageMagick
- 7 khu vực trên màn hình tương ứng với các tab trình duyệt mà Puppeteer phải duy trì
- Raspberry Pi không có đủ bộ nhớ, và Chrome cũng không hề nhẹ
- Trang MUNI trả về HTTP 429 nếu bị yêu cầu quá thường xuyên
- Màn hình hiển thị đồng thời BART và MUNI kém ổn định hơn nhiều so với màn hình chỉ có BART, nên một bảng thông tin nhìn nhanh cần kiến trúc đáng tin cậy hơn
Thiết kế lại kiến trúc bằng Rust
- Phiên bản viết lại loại bỏ Puppeteer và chuyển sang kiến trúc trong đó máy chủ HTTP Rust tự tạo PNG trực tiếp
- Giao diện công khai được giữ gần giống máy chủ Node.js, nhưng phần triển khai bên trong thay đổi hoàn toàn
- Có ba thành phần chính
- Lấy thời gian đến tiếp theo từ 511.org
- Render thời gian đến thành ảnh PNG 8-bit
- Trả về PNG đã tạo qua máy chủ HTTP
- Máy chủ HTTP dùng Axum
- Việc render PNG dùng rust-skia
- Skia cũng là thư viện đồ họa nền tảng của Chrome, nên có thể tận dụng công nghệ render với chi phí thấp hơn Puppeteer
- Mã máy chủ cuối cùng dài khoảng 1.200 dòng, và bài viết chỉ tóm tắt những phần quan trọng
Lấy dữ liệu từ 511.org Stop Monitoring API
- Stop Monitoring API của 511.org cung cấp thời gian đến/đi dự kiến tại các điểm dừng trong khu vực Vịnh dưới dạng XML và JSON
- Endpoint được dùng có dạng
http://api.511.org/transit/StopMonitoring/…] agencychỉ định một trong 27 đơn vị vận hành giao thông công cộng của khu vực Vịnh, và mã của SF MUNI làSF- API chỉ cho lấy dữ liệu của một đơn vị vận hành trong mỗi lần gọi
- Nó trả về thời gian đến của một điểm dừng cụ thể hoặc dữ liệu toàn bộ các điểm dừng của đơn vị đó
- Tác giả không tìm ra cách lấy cùng lúc một tập con gồm nhiều điểm dừng
- Giới hạn của API 511.org là 60 yêu cầu mỗi giờ, nên cách gọi nhiều lần theo từng điểm dừng không phù hợp cho màn hình thời gian thực
- Khi yêu cầu toàn bộ dữ liệu SF MUNI, HTTP request mất khoảng 5 giây và trả về khoảng 27 MiB dữ liệu; JSON đã format vượt quá 1 triệu dòng
- Toàn bộ phản hồi có khoảng 26k mục
MonitoredStopVisit, nhưng trên màn hình thực tế chỉ cầnLineRef,DirectionRef,DestinationName,StopPointRef,ExpectedArrivalTime,DestinationDisplaycho các điểm dừng và tuyến quan tâm
Các vấn đề chi tiết khi xử lý dữ liệu trong Rust
reqwest,serde,serde_json,chrono,tokio,eyređược dùng cho HTTP request, parse JSON, tính toán thời gian, chạy bất đồng bộ và xử lý lỗi- Có thể xem ID điểm dừng MUNI trên trang điểm dừng của website SF MUNI
- Ví dụ, Persia Ave & Paris St có
15918cho inbound và15919cho outbound - Các điểm dừng MUNI có ID riêng theo từng hướng
- Ví dụ, Persia Ave & Paris St có
- Phản hồi từ 511.org trả về JSON với UTF-8 Byte Order Mark ở đầu, và nếu parse trực tiếp bằng
.json()sẽ gặp lỗiexpected value at line 1 column 1 - Khi đọc phản hồi thành chuỗi UTF-8 bằng
.text(), BOM bị loại bỏ nên có thể parse bằngserde_json::from_str - Sau khi lọc, chỉ còn khoảng 20 thông tin thời gian đến của các điểm dừng quan tâm, dễ xử lý hơn nhiều so với 26k mục ban đầu
- Dữ liệu được nhóm theo
LineRefvàDirectionRef, sắp xếp theoExpectedArrivalTime, rồi chuyển timestamp UTC sang dạng “còn bao nhiêu phút”
Tạo PNG cho Kindle bằng Skia
- Việc render PNG được xây dựng bằng
skia-safe - Một bitmap
Gray8đúng kích thước màn hình Kindle là 1024x758 được tạo ra, nền được tô trắng rồi vẽ chữ và đường kẻ lên trên - Đầu tiên, tác giả tạo một ảnh PNG8 in ra “Hello World” để xác nhận pipeline render
- Sau đó tạo ảnh dạng bảng với hai cột inbound và outbound bằng dữ liệu thời gian đến giả
- Khi kết hợp với dữ liệu API thật, đích đến và thời gian đến đa dạng hơn dữ liệu thử nghiệm nên bố cục cố định làm các chuỗi bị chồng lên nhau
- Để lặp nhanh và tránh giới hạn API, tác giả thêm đoạn mã tạm để cache phản hồi vào
data.json
Tinh chỉnh bố cục hiển thị
- Màn hình dữ liệu thật ban đầu khó đọc vì lượng thông tin hiển thị ở mỗi hàng khác nhau
- Các cải tiến gồm có
- Căn phải thời gian đến để giảm phần trống ở cuối bảng
- Bỏ
minsau từng thời gian và chỉ thêmminở cuối danh sách - Chỉ hiển thị 3 chuyến xe tiếp theo
- Đẩy tên điểm đến sang phải để tính đến tên tuyến dài
- Sau đó tác giả dùng
measure_strđể tính biên chữ nhằm hiển thị ID tuyến dưới dạng bong bóng bo tròn màu xám - Chuỗi điểm đến được đặt cách mép phải của bong bóng ID tuyến 15 pixel
- Phía trên cùng có thêm header màu xám nhạt với
Muni InboundvàMuni Outboundđược căn giữa
Cung cấp PNG bằng Axum
- Máy chủ HTTP được tạo bằng
Axum 0.7và trả về PNG tại route/stops.png - Header phản hồi được đặt là
Content-Type: image/png - Ban đầu, tác giả xác nhận HTTP 200 với phần thân rỗng, rồi nối hàm tạo ảnh để trả trực tiếp byte PNG từ bộ nhớ
- Khi dùng
wgettrên Kindle để lấy ảnh rồi hiển thị bằngeips, ban đầu ảnh bị xoay và cắt mất - Vì Kindle mặc định dùng theo chiều dọc, nên để trông như màn hình ngang phải áp dụng ma trận xoay trước khi render
- Đảo kích thước ảnh thành
(758, 1024) - Xoay canvas
90.0độ
- Đảo kích thước ảnh thành
Các tính năng được thêm vào phiên bản dùng thực tế
- Phiên bản dùng thực tế có thêm các tính năng sau
- Hệ thống cache liên tục lấy dữ liệu API trong nền để máy chủ HTTP phản hồi nhanh
- Hiển thị thời gian ở góc dưới bên trái và trạng thái cache dữ liệu ở góc dưới bên phải
- Anti-aliasing
- Cắt tên điểm đến để không chồng lên vùng thời gian đến
- Dùng các sắc xám khác nhau theo từng tuyến để phân biệt nhanh
- Cấu hình dựa trên file cấu hình
- Khi có lỗi, render lỗi và chuỗi
.source()thành PNG để có thể xem ngay trên Kindle
- Mã máy chủ đã đơn giản hóa được đặt tại transit-kindle-simplified
- Mã máy chủ chạy chiếc Kindle treo tường thực tế được đặt tại transit-kindle
- Phiên bản dựa trên ảnh chụp màn hình BART hoạt động vào khoảng tháng 2 năm 2023, còn phần tinh chỉnh UI cuối cùng của bản viết lại bằng Rust được hoàn tất vào tháng 1 năm 2024
1 bình luận
Các ý kiến trên Hacker News
Tôi đang làm việc tại tổ chức phi lợi nhuận đứng sau OneBusAway. OneBusAway là một dự án mã nguồn mở được hàng triệu người dùng mỗi ngày để kiểm tra vị trí và thời gian đến của xe buýt, tàu hỏa, xe điện và funicular
Nếu thành phố của bạn đã có máy chủ OneBusAway, bạn có thể dùng SDK mới để tạo ứng dụng tùy chỉnh: https://github.com/onebusAway/?q=sdk&type=all&language=&sort...
Nếu thành phố chưa có máy chủ, bạn có thể dùng Docker image và cấu hình OpenTofu mà chúng tôi tạo trong năm nay để chuyển feed GTFS/GTFS-RT thành REST API
BART cũng cung cấp GTFS và GTFS-RT: https://mobilitydatabase.org/feeds/mdb-53
Các cơ quan giao thông công cộng khác ở Mỹ cũng đang ở tình thế phải công khai ít nhất dữ liệu lịch trình tĩnh dưới dạng GTFS do quy định liên bang mới
Chúng tôi luôn cần lập trình viên đóng góp cho phần mềm liên quan, và đặc biệt rất cần lập trình viên iOS để giúp cải thiện trải nghiệm cho 250 nghìn người dùng mỗi ngày. Liên hệ: aaron@onebusaway.org
Docker: https://github.com/OneBusAway/onebusaway-docker / OpenTofu: https://github.com/OneBusAway/onebusaway-deployment
Tôi cũng muốn thêm thành phố của mình nếu quy trình đơn giản. Có vẻ gần đây dự án đã dành khá nhiều thời gian kỹ thuật cho việc hỗ trợ thư viện và SDK, nhưng tôi nghĩ nên đầu tư vào một lộ trình trải nghiệm lập trình viên dễ tiếp cận để người mới đóng góp có thể bỏ thời gian vào thuận lợi hơn
Ví dụ tài liệu này khá phức tạp: https://github.com/OneBusAway/onebusaway/wiki/Multi-Region
Kiểu như hiển thị “đến sau 4 phút” nhưng xe buýt rời đi ngay trước mắt, hoặc nói “trễ 5 phút” nhưng lại đến sớm 2 phút. Có khi xe buýt còn không xuất hiện
Có lẽ là vấn đề ở dữ liệu nguồn, nhưng tôi muốn biết chuyện nội bộ nào khiến độ tin cậy thấp như vậy. Tham chiếu là Seattle
BART và MUNI đều hỗ trợ General Transit Feed Specification, và có cách tiêu chuẩn để lấy dữ liệu này
https://gtfs.org/documentation/overview/#gtfs-realtime
Tôi thật sự thích dự án này. Trước đây khi sống không có ô tô ở Boston, tôi từng tự ghép một thứ thô sơ hơn nhiều để xử lý các trường hợp có nhiều cách đi từ A đến B và “tốt nhất” phụ thuộc vào thời gian hoặc các điểm ghé giữa đường
Ví dụ trước khi đi làm, tôi đưa con trai đến trường, và thỉnh thoảng ăn sáng sau khi đưa con đi học. Nếu có tính năng xem giờ khởi hành tiếp theo, tôi đã có thể linh hoạt hơn rất nhiều khi quyết định trong các tình huống không xác định như đi bộ với một đứa trẻ 4 tuổi ở những nơi thú vị, hoặc liệu có cần vội để kịp chuyến tàu mà nếu lỡ thì sẽ bị khoảng trống lớn giữa các chuyến hay không
Tôi ngạc nhiên là https://oeffi.schildbach.de/index.html vẫn chưa được nhắc đến. Nó làm đúng việc bạn muốn ở đây
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/
Tốt hơn ứng dụng chính thức rất nhiều, và hiển thị ngay thông tin cần nhất: trạm xe buýt gần đó và giờ thời gian thực của chuyến xe buýt tiếp theo
Nếu ở Anh, bạn có thể mua thiết bị bảng thông báo khởi hành mô phỏng bảng khởi hành ở nhà ga
https://ukdepartureboards.co.uk/store/product/desktop-depart...
https://tramli.ch/en
Tôi cũng sống ở SF và đã làm một app iOS tên A Better Ride để giải quyết cùng vấn đề
App chỉ hiển thị giờ khởi hành phương tiện công cộng ở các trạm gần đó. Mục tiêu là giảm căng thẳng bằng cách khiến giao thông công cộng dễ dự đoán và dễ khám phá hơn. Đây là dự án cá nhân làm trong thời gian rảnh, không kiếm tiền
Nó cho phép bạn khám phá những nơi có thể đến mà không phải bận tâm chuyện chuyển tuyến
Tôi cũng đang làm phần cứng trong lĩnh vực này và gần đây xử lý GTFS khá nhiều, nên nhìn là thấy ngay đã tốn bao nhiêu công sức để phân tích và hiển thị dữ liệu giao thông
Nếu được, tôi muốn trao đổi về các quyết định UX trong việc tóm tắt và nhóm các chuyến đi khả dụng ở từng trạm, cũng như về backend
Không cần jailbreak Kindle hay render hình ảnh
Chỉ cần mở trang web tự làm bằng trình duyệt Kindle, rồi dùng lệnh debug để tắt quảng cáo “screensaver” hoặc màn hình tiết kiệm điện của Kindle
Thanh trình duyệt vẫn còn ở một cạnh của Kindle, nhưng bạn có thể xoay thiết bị để đặt nó ở cạnh dưới hoặc bên phải. Đổi lại, bạn có thể làm màn hình thời tiết/giao thông/tin tức bằng HTML/CSS/JS đơn giản cùng ngôn ngữ backend tùy thích, rồi chạy trên nơi như một instance DigitalOcean giá rẻ 4 USD
https://blog.notfaqs.com/2018/06/kindle-e-reader-disable-scr...
https://www.mobileread.com/forums/showthread.php?t=198334
Dùng
document.documentElement.requestFullscreen()để vào toàn màn hình thật sự, nên cả thanh trạng thái cũng không thấy. API giao thông công cộng địa phương có thể truy cập trực tiếp từ trang web, nên cũng không cần backendDù vậy, nếu cho nó giao tiếp với một server trong mạng cục bộ thì có vẻ ổn
Có vẻ tôi chưa hiểu rõ use case này. Không phải là muốn “đi tàu”, mà là muốn đi từ A đến B
Nếu tàu hỏng, đang đình công, tuyến bị tắc từ 1–4 giờ chiều, hoặc không phải giờ chạy, tôi muốn có phương án thay thế. Vì thế thứ tôi cần là điều Google Maps cung cấp, hoặc đang cố cung cấp
Nếu đi từ Ferry Building đến Oakland mà BART bị hỏng thì đi ferry có thể tốt hơn, còn nếu đã muộn thì có khi chỉ còn taxi hoặc Uber
Nếu ở Tokyo đi từ Shibuya đến Azabujuban thì có các lựa chọn như Ginza Line→Namboku Line, Hanzomon Line→Oedo Line, Yamanote Line→Namboku Line, xe buýt số 6, taxi, v.v.
Giữa các ga và trạm xe buýt của từng tuyến cũng phải đi bộ 3–8 phút, nên hiện tại bạn ở gần đâu sẽ ảnh hưởng đến lựa chọn. Việc có bị muộn không, hành lý có to hoặc nặng không cũng quan trọng
Ví dụ, để đến một nơi nào đó, có thể đi xe buýt số 39 rồi đi bộ 15 phút là được, nhưng nếu hỏi Google thì nó đưa tuyến phải chuyển 2 lần. Nếu may thì thời gian cũng chỉ tương đương một chuyến xe buýt + đi bộ
Vì vậy nếu muốn biết xe buýt số 39 đến khi nào, tôi phải cố tình nói điểm đến khác đi
Ngoài ra, ở chỗ tôi, dữ liệu thời gian thực của Google đáng nghi, còn dữ liệu của nhà vận hành hoặc một số bên thứ ba thì đáng tin hơn
Có những tình huống cần lập kế hoạch phức tạp theo thời gian thực, và có công cụ như vậy thì tốt, nhưng việc đi lại hằng ngày có thể được bao phủ đầy đủ bằng các công cụ đơn giản hơn nhiều
Ví dụ Tokyo bạn nêu cũng thú vị, nhưng theo Google thì hầu hết các lựa chọn giao thông công cộng đều mất 30–40 phút, nên trong kế hoạch sơ bộ chúng gần như tương đương. Taxi nhanh hơn, còn xe đạp cũng gần tương tự
Nếu tàu hỏng hoặc đình công, điều đó sẽ hiện ra dưới dạng những khoảng trống bất thường trong danh sách tàu thời gian thực
Khi đi quanh thành phố thì tôi mở Google Maps và nhập điểm đến, nhưng khi rời nhà để đến một nơi đã đi 500 lần thì tôi không cần mở làm gì
Nó cũng vô nghĩa như nói về trải nghiệm mua sắm hay ăn uống trong cửa hàng chỉ dựa trên Disneyland. Chỉ riêng việc chạm Suica thôi đã nhanh hơn nơi khác nhiều lần, và có những nơi thậm chí không hỗ trợ từ đầu
Nó có nhiều tính năng, nhưng tôi không biết Tokyo được hỗ trợ đến mức nào. Ở London, nếu yêu cầu chỉ đường về nhà, nó sẽ hiển thị nhiều cách khác nhau được tối ưu theo chi phí, quãng đường đi bộ, tốc độ, chuyển tuyến, khả năng tiếp cận, v.v.
Tôi đang dùng một thứ tương tự: lấy lại thiết bị máy tính bảng/phòng họp Lenovo ThinkSmart View, gắn Home Assistant và tích hợp với cơ quan giao thông địa phương.
Ưu điểm là có thể dùng ngay dễ hơn nhiều. Tất nhiên tôi có cài ROM Android tùy chỉnh lên Lenovo, nhưng nó có thể hiển thị bất cứ thứ gì mình muốn và cũng điều khiển được những thứ như đèn hay robot hút bụi.
Cũng có một sản phẩm rất đẹp biến bản đồ BART thành bảng LED vật lý: https://www.designrules.co/