Un projet Rust pour ne pas rater son train
(lilymara.xyz)- À San Francisco, quand on se déplace sans voiture, il est plus pratique d’avoir un dispositif qui affiche directement la prochaine heure d’arrivée à l’arrêt le plus proche avant de quitter la maison
- Une ancienne Kindle a été jailbreakée puis transformée en panneau e-ink mural, affichant au départ à intervalles réguliers des captures de la page web de BART
- En y ajoutant MUNI, l’accumulation d’onglets de navigateur, la charge mémoire sur le Raspberry Pi et les erreurs HTTP 429 ont rendu l’architecture basée sur des captures d’écran moins fiable
- La réécriture en Rust récupère les données depuis l’API Stop Monitoring de 511.org et génère directement des PNG adaptés à la résolution de la Kindle et à sa profondeur de couleur 8 bits avec
skia-safe - En supprimant le moteur de navigateur et en ajoutant cache, affichage des erreurs et ajustements de mise en page, le projet aboutit à un afficheur de transports en commun sur Kindle réellement utilisable
Les informations nécessaires avant de sortir sont simples
- À San Francisco, l’auteur se déplace sans voiture et utilise souvent les transports en commun comme le bus, le train, le tramway, le trolleybus, le cable car et le ferry
- Des apps comme CityMapper sont utiles pour aller à destination, mais devant chez soi, il est plus important de connaître la prochaine heure d’arrivée de chaque ligne que de décider « où aller »
- Si l’on connaît déjà l’arrêt et les lignes à prendre, le modèle position actuelle/destination des apps de navigation généralistes est plus complexe que nécessaire
- L’idée vient d’un article de Matt Healy sur l’usage d’une ancienne Kindle comme écran domotique, et d’un article de Ben Borgers sur l’usage d’une ancienne Nook comme cadre photo iCloud
Jusqu’à l’affichage d’images sur la Kindle
- La première étape a consisté à jailbreaker la Kindle pour activer USBNet et permettre une connexion SSH
- Ensuite, une tâche cron rafraîchissait régulièrement l’écran
- Les ressources de hacking Kindle du wiki et du forum mobileread ont servi de référence
- La page Real Time Departure de BART fournit les prochains trains pour une station donnée et se rafraîchit automatiquement, ce qui en faisait une bonne source de données initiale
- L’auteur a tenté d’afficher un PNG avec
eips, l’outil CLI de la Kindle, mais au départ l’image était fortement étirée ou tronquée, donc inutilisable - Le problème venait du fait que l’outil d’affichage de la Kindle ne gérait pas correctement les PNG RGB 24 bits et les lisait comme des images 8 bits
- En appliquant
-colorspace gray -depth 8avecconvertd’ImageMagick, l’affichage est devenu correct - Une image à la taille de l’écran Kindle et une profondeur de couleur 8 bits sont devenues les conditions essentielles
- En appliquant
Première implémentation : Node.js, Puppeteer, Raspberry Pi
- Le serveur initial utilisait Node.js et Puppeteer pour capturer la zone nécessaire de la page web BART
- L’image capturée était convertie au format adapté à la taille d’écran de la Kindle et au niveau de gris 8 bits, puis renvoyée via un endpoint HTTP
- Le serveur tournait sur un Raspberry Pi, et la Kindle récupérait l’image chaque minute via cron pour l’afficher avec
eips - Comme la page BART prenait déjà en charge l’actualisation automatique, le serveur ouvrait la page une seule fois au démarrage au lieu de la recharger à chaque requête
- Cette version a bien fonctionné pendant un temps, mais le rafraîchissement cessait parfois et le serveur devait parfois redémarrer faute de mémoire
Les limites révélées par l’extension à MUNI
- Dans la baie de San Francisco, il existe 27 opérateurs de transports publics, et la première implémentation ne gérait que BART, le réseau ferroviaire régional
- Ensuite, l’auteur a voulu voir aussi les heures d’arrivée des arrêts de bus et de métro léger de MUNI
- Le site web de MUNI fournissant lui aussi les heures d’arrivée par arrêt, la première tentative a consisté à capturer plusieurs pages d’arrêt avec Puppeteer puis à les assembler avec ImageMagick
- Les 7 sections de l’écran correspondaient chacune à un onglet de navigateur que Puppeteer devait maintenir
- Le Raspberry Pi n’avait pas assez de mémoire, et Chrome n’est pas léger
- Le site de MUNI renvoyait des HTTP 429 en cas de requêtes trop fréquentes
- L’écran combinant BART et MUNI s’est révélé bien plus instable que l’écran BART seul, et un tableau de bord visible d’un coup d’œil demandait une architecture plus fiable
Une architecture repensée en Rust
- La version réécrite supprime Puppeteer et adopte un serveur HTTP Rust qui génère directement les PNG
- L’interface publique est restée proche de celle du serveur Node.js, mais l’implémentation interne a complètement changé
- L’architecture repose sur trois composants principaux
- récupérer les prochaines heures d’arrivée depuis 511.org
- rendre ces heures d’arrivée en image PNG 8 bits
- renvoyer le PNG généré depuis un serveur HTTP
- Le serveur HTTP utilise Axum
- Le rendu PNG utilise rust-skia
- Skia est aussi la bibliothèque graphique de base de Chrome, ce qui permet de réutiliser une technologie de rendu avec moins de surcharge que Puppeteer
- Le code final du serveur faisait environ 1 200 lignes, et seuls les points essentiels sont présentés
Récupérer les données depuis l’API Stop Monitoring de 511.org
- L’API Stop Monitoring de 511.org fournit en XML et JSON les heures prévues d’arrivée et de départ pour les arrêts de la baie
- L’endpoint utilisé avait la forme
http://api.511.org/transit/StopMonitoring/…] agencydésigne l’un des 27 opérateurs de transport de la baie, et le code de SF MUNI estSF- L’API ne permet de récupérer les données que pour un seul opérateur à la fois
- Elle renvoie soit les heures d’arrivée d’un arrêt précis, soit les données de tous les arrêts de cet opérateur
- L’auteur n’a pas trouvé de moyen de récupérer en une seule fois un sous-ensemble de plusieurs arrêts
- La limite de l’API 511.org est de 60 requêtes par heure, ce qui rend peu adaptée une approche par multiples requêtes par arrêt pour un affichage en temps réel
- En demandant l’ensemble des données de SF MUNI, la requête HTTP prenait environ 5 secondes, renvoyait environ 27 MiB de données, et le JSON formaté dépassait le million de lignes
- La réponse complète contenait environ 26k entrées
MonitoredStopVisit, mais l’écran n’avait réellement besoin que deLineRef,DirectionRef,DestinationName,StopPointRef,ExpectedArrivalTimeetDestinationDisplaypour certains arrêts et lignes précis
Les détails rencontrés dans le traitement des données en Rust
reqwest,serde,serde_json,chrono,tokioeteyreont été utilisés pour les requêtes HTTP, le parsing JSON, le calcul du temps, l’exécution asynchrone et la gestion des erreurs- Les identifiants d’arrêt MUNI peuvent être consultés sur les pages d’arrêt du site web de SF MUNI
- Par exemple, Persia Ave & Paris St a
15918pour l’inbound et15919pour l’outbound - Les arrêts MUNI ont des identifiants distincts selon la direction
- Par exemple, Persia Ave & Paris St a
- La réponse de 511.org ajoutait un Byte Order Mark UTF-8 au début du JSON, ce qui provoquait l’erreur
expected value at line 1 column 1en essayant de parser directement avec.json() - En lisant la réponse avec
.text(), le BOM était supprimé et le contenu pouvait ensuite être parsé avecserde_json::from_str - Après filtrage, il ne restait qu’une vingtaine d’informations d’arrivée pour les arrêts d’intérêt, bien plus faciles à manipuler que les 26k entrées initiales
- Les données ont été regroupées par
LineRefetDirectionRef, triées parExpectedArrivalTime, puis les horodatages UTC ont été convertis au format « dans X min »
Générer des PNG pour Kindle avec Skia
- Le rendu PNG a été construit avec
skia-safe - Un bitmap
Gray8correspondant à la résolution de l’écran Kindle, 1024x758, est créé, rempli d’un fond blanc, puis le texte et les lignes y sont dessinés - L’auteur a d’abord généré une image PNG8 « Hello World » pour valider le pipeline de rendu
- Ensuite, une image en forme de tableau avec deux colonnes, inbound et outbound, a été produite à partir de fausses données d’horaires
- Une fois les vraies données d’API branchées, la variété plus grande des destinations et des temps d’arrivée a provoqué des chevauchements de texte dans la mise en page fixe
- Pour itérer rapidement et éviter les limites de l’API, un code temporaire de mise en cache de la réponse dans
data.jsona été ajouté
Affiner la mise en page de l’affichage
- Le premier écran basé sur les données réelles était peu lisible, car la quantité d’informations affichées variait selon les lignes
- Les améliorations apportées ont été les suivantes
- aligner à droite les heures d’arrivée pour réduire l’espace perdu en fin de tableau
- supprimer
minaprès chaque heure et ne l’ajouter qu’à la fin de la liste - n’afficher que les 3 prochains véhicules
- déplacer les noms de destination vers la droite pour tenir compte des noms de ligne plus longs
- Ensuite, les limites du texte ont été calculées avec
measure_strafin d’afficher l’identifiant de ligne dans une bulle grise arrondie - La chaîne de destination est placée à 15 pixels à droite du bord droit de cette bulle d’identifiant
- En haut, un en-tête gris clair a été ajouté avec
Muni InboundetMuni Outboundcentrés
Servir le PNG avec Axum
- Le serveur HTTP a été construit avec
Axum 0.7et renvoie le PNG sur la route/stops.png - L’en-tête de réponse est défini sur
Content-Type: image/png - L’auteur a d’abord vérifié un HTTP 200 avec un corps vide, puis a connecté la fonction de génération d’image pour renvoyer directement les octets PNG depuis la mémoire
- Lors de la récupération de l’image sur la Kindle avec
wgetpuis de son affichage aveceips, l’image était d’abord tournée et tronquée - Comme la Kindle est pensée pour un usage en orientation portrait, il a fallu appliquer une matrice de rotation avant le rendu pour l’afficher en paysage
- les dimensions de l’image ont été inversées en
(758, 1024) - le canvas a été tourné de
90.0degrés
- les dimensions de l’image ont été inversées en
Les fonctions ajoutées à la version réellement utilisée
- La version utilisée au quotidien ajoute les fonctions suivantes
- un système de cache qui continue de récupérer les données API en arrière-plan afin que le serveur HTTP réponde rapidement
- l’affichage de l’heure en bas à gauche et de l’état du cache de données en bas à droite
- l’anti-aliasing
- une logique de troncature des noms de destination pour éviter qu’ils ne chevauchent la zone des heures d’arrivée
- une différenciation rapide des lignes via des niveaux de gris distincts
- une configuration basée sur un fichier de paramètres
- un rendu des erreurs et de la chaîne
.source()directement en PNG pour pouvoir les voir immédiatement sur la Kindle
- Le code simplifié du serveur créé pour l’occasion est disponible dans transit-kindle-simplified
- Le code du serveur qui alimente la Kindle murale réelle est disponible dans transit-kindle
- La version basée sur les captures d’écran BART fonctionnait vers février 2023, et la finalisation de l’interface de la réécriture Rust s’est achevée en janvier 2024
1 commentaires
Avis Hacker News
Je travaille pour l’organisation à but non lucratif derrière OneBusAway. OneBusAway est un projet open source utilisé chaque jour par des millions de personnes pour consulter la position et les horaires d’arrivée des bus, trains, trams et funiculaires.
Si votre ville dispose déjà d’un serveur OneBusAway, vous pouvez créer une app personnalisée avec le nouveau SDK : https://github.com/onebusAway/?q=sdk&type=all&language=&sort...
Si votre ville n’a pas de serveur, les images Docker et la configuration OpenTofu que nous avons créées cette année permettent de transformer des flux GTFS/GTFS-RT en API REST.
BART fournit aussi du GTFS et du GTFS-RT : https://mobilitydatabase.org/feeds/mdb-53
Aux États-Unis, d’autres opérateurs de transport public doivent également publier au moins leurs données d’horaires statiques en GTFS, en raison d’une nouvelle réglementation fédérale.
Nous avons toujours besoin de développeurs pour contribuer aux logiciels associés, et nous recherchons en particulier urgemment des développeurs iOS pour aider à améliorer le service utilisé par 250 000 utilisateurs par jour. Contact : aaron@onebusaway.org
Docker : https://github.com/OneBusAway/onebusaway-docker / OpenTofu : https://github.com/OneBusAway/onebusaway-deployment
J’aimerais aussi ajouter ma ville si la procédure était simple. Il semble que beaucoup de temps d’ingénierie ait récemment été consacré au support des bibliothèques et SDK, mais il vaudrait mieux, à mon avis, investir dans un parcours simple d’expérience développeur afin que les nouveaux contributeurs puissent y consacrer du temps plus facilement.
Par exemple, cette documentation est assez complexe : https://github.com/OneBusAway/onebusaway/wiki/Multi-Region
Elles affichent “arrivée dans 4 minutes” alors que le bus part sous mes yeux, ou bien “5 minutes de retard” alors qu’il arrive en réalité 2 minutes en avance. Il arrive aussi que le bus n’apparaisse pas du tout.
Je suppose que le problème vient des données sources, mais j’aimerais comprendre ce qui, en interne, rend la fiabilité aussi faible. Pour référence, je parle de Seattle.
BART et MUNI prennent tous deux en charge la General Transit Feed Specification, et il existe une méthode standard pour obtenir ces données.
https://gtfs.org/documentation/overview/#gtfs-realtime
J’aime beaucoup. À l’époque où je vivais sans voiture à Boston, j’avais bricolé quelque chose de beaucoup plus rudimentaire pour gérer les cas où il y avait plusieurs façons d’aller de A à B, et où la “meilleure” dépendait de l’heure ou des endroits où l’on s’arrêtait en chemin.
Par exemple, avant d’aller travailler, j’emmenais mon fils à l’école, et parfois nous prenions le petit-déjeuner après l’avoir déposé. Avec une fonctionnalité voir les prochains départs, il aurait été beaucoup plus facile de décider souplement quoi faire dans des situations non déterministes, comme marcher avec un enfant de 4 ans dans un lieu intéressant, ou savoir s’il fallait se dépêcher pour attraper un train après lequel il y aurait un gros trou dans la fréquence.
Je suis surpris que https://oeffi.schildbach.de/index.html n’ait pas encore été mentionné. Il fait exactement ce que l’on veut ici.
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/
C’est bien mieux que l’app officielle, et cela affiche immédiatement l’information la plus utile : l’arrêt de bus le plus proche et les horaires en temps réel des prochains bus.
Au Royaume-Uni, on peut acheter un panneau d’affichage des départs qui imite ceux des gares
https://ukdepartureboards.co.uk/store/product/desktop-depart...
https://tramli.ch/en
Moi aussi, j’habite à SF, et j’ai créé une app iOS appelée A Better Ride pour résoudre le même problème
Elle affiche uniquement les horaires de départ des transports en commun aux arrêts à proximité. L’objectif est de réduire le stress en rendant les transports en commun plus prévisibles et plus faciles à explorer. C’est un projet personnel que je développe sur mon temps libre, sans monétisation
Elle permet d’explorer les endroits où l’on peut aller sans se soucier des correspondances
Je travaille moi aussi sur du matériel dans ce domaine et j’ai beaucoup manipulé GTFS récemment ; on voit tout de suite le travail énorme qu’il a fallu pour parser et afficher les données de transport
Si ça te va, j’aimerais discuter des choix UX pour résumer et regrouper les trajets possibles depuis chaque arrêt, ainsi que du backend
Il n’est pas nécessaire de jailbreaker le Kindle ni de générer des images
Il suffit d’ouvrir une page web que l’on a créée soi-même dans le navigateur du Kindle, puis de désactiver les publicités de « veille » ou l’écran d’économie d’énergie du Kindle avec une commande de debug
La barre du navigateur reste sur un bord du Kindle, mais on peut orienter l’appareil pour la placer en bas ou à droite. En échange, on peut créer un écran météo/transports/actualités en HTML/CSS/JS simple, avec le langage backend de son choix, et le faire tourner sur quelque chose comme une instance DigitalOcean bon marché à 4 dollars
https://blog.notfaqs.com/2018/06/kindle-e-reader-disable-scr...
https://www.mobileread.com/forums/showthread.php?t=198334
Avec
document.documentElement.requestFullscreen(), on passe en vrai plein écran et même la barre d’état disparaît. L’API de transports locale étant accessible directement depuis la page web, je n’avais même pas besoin de backendCela dit, ça devrait aller si on le fait communiquer avec un serveur sur le réseau local
Je crois que je ne comprends pas bien ce cas d’usage. Ce que je veux, ce n’est pas « prendre le train », c’est aller de A à B
Si le train est en panne, en grève, si la ligne est bloquée entre 13 h et 16 h, ou si ce n’est pas dans les horaires de service, je veux une alternative. Donc ce dont j’ai besoin, c’est ce que Google Maps fournit, ou essaie de fournir
Si je veux aller du Ferry Building à Oakland et que le BART est en panne, le ferry peut être préférable ; s’il est tard, il ne reste peut-être que le taxi ou Uber
À Tokyo, pour aller de Shibuya à Azabujuban, les options incluent Ginza Line→Namboku Line, Hanzomon Line→Oedo Line, Yamanote Line→Namboku Line, le bus 6, un taxi, etc.
Il faut aussi marcher 3 à 8 minutes entre les gares et les arrêts de bus de chaque ligne, donc l’endroit précis où l’on se trouve influe sur le choix. Le fait d’être en retard, ou d’avoir de gros bagages lourds, compte aussi
Par exemple, pour aller quelque part, je peux prendre le bus 39 puis marcher 15 minutes, mais si je demande à Google, il me propose un itinéraire avec 2 correspondances. Avec un peu de chance, c’est à peu près aussi long qu’un trajet bus + marche
Du coup, si je veux savoir quand arrive le bus 39, je dois volontairement indiquer une autre destination
Et là où je vis, les données temps réel de Google sont suspectes, tandis que celles de l’opérateur ou de certains tiers sont plus fiables
Il y a des situations où il faut faire une planification sophistiquée en temps réel, et ce serait bien d’avoir ce genre d’outil, mais les déplacements quotidiens sont largement couverts par des outils beaucoup plus simples
L’exemple de Tokyo que tu donnes est intéressant, mais d’après Google, la plupart des options de transports en commun prennent 30 à 40 minutes, donc elles sont presque équivalentes pour une planification approximative. Le taxi est plus rapide, et le vélo presque comparable
Si le train est en panne ou en grève, cela se voit dans la liste des trains en temps réel par un vide inattendu
Quand je me déplace en ville, j’ouvre Google Maps et j’entre ma destination, mais quand je pars de chez moi pour aller à un endroit où je suis déjà allé 500 fois, je ne le fais pas
Ce serait aussi peu pertinent que de parler de shopping ou de restauration en se basant uniquement sur l’expérience à l’intérieur de Disneyland. Rien que le fait de valider avec Suica est plusieurs fois plus rapide qu’ailleurs, et ce n’est même pas pris en charge dans certains endroits
Il a beaucoup de fonctionnalités, mais je ne sais pas à quel point Tokyo est bien pris en charge. À Londres, si on lui demande de nous ramener chez nous, il montre plusieurs options optimisées selon le coût, la distance à pied, la vitesse, les correspondances, l’accessibilité, etc.
J’utilise quelque chose de similaire avec Home Assistant et une intégration aux transports locaux sur une tablette / appareil de salle de réunion Lenovo ThinkSmart View recyclé.
L’avantage, c’est que c’est beaucoup plus prêt à l’emploi. Bien sûr, j’ai quand même installé une ROM Android personnalisée sur le Lenovo, mais on peut afficher à peu près tout ce qu’on veut, et aussi contrôler des choses comme l’éclairage ou un robot aspirateur.
Il existe aussi un superbe produit qui transforme la carte du BART en panneau LED physique : https://www.designrules.co/