2 puntos por GN⁺ 2024-10-25 | 1 comentarios | Compartir por WhatsApp
  • En una vida sin auto en San Francisco, resulta más práctico tener un dispositivo que muestre de inmediato la próxima hora de llegada en la parada cercana antes de salir de casa
  • Se hizo jailbreak a un Kindle viejo para convertirlo en un panel mural de tinta electrónica, y al principio mostraba periódicamente capturas de la página web de BART
  • Al sumar MUNI, la combinación de varias pestañas del navegador, la carga de memoria en una Raspberry Pi y los HTTP 429 hizo que la arquitectura basada en capturas de pantalla perdiera confiabilidad
  • La versión reescrita en Rust obtiene los datos desde la Stop Monitoring API de 511.org y genera directamente PNG adaptados a la resolución del Kindle y a la profundidad de color de 8 bits con skia-safe
  • Al eliminar el motor del navegador y añadir caché, visualización de errores y ajustes de layout, el resultado fue un panel de transporte público en Kindle realmente usable

La información necesaria al salir de casa es simple

  • Al moverse por San Francisco sin automóvil, se usa con frecuencia transporte público como autobuses, trenes, tranvías, trolebuses, teleféricos y ferris
  • Apps como CityMapper son útiles para ir a un destino, pero frente a casa importa más la próxima hora de llegada de cada línea que “a dónde ir”
  • Si ya se conocen la parada y la línea que se van a usar, el modelo de ubicación actual y destino de una app de navegación genérica resulta más complejo de lo necesario
  • La idea surgió de el texto de Matt Healy sobre usar un Kindle viejo como pantalla de hogar inteligente y el texto de Ben Borgers sobre usar un Nook viejo como marco de fotos de iCloud

Hasta mostrar imágenes en el Kindle

  • El primer paso fue hacer jailbreak al Kindle para activar USBNet y permitir acceso por SSH
    • Después, se usó una tarea de cron para refrescar la pantalla periódicamente
    • Sirvieron como referencia los materiales de hacking de Kindle en la wiki y los foros de mobileread
  • La página Real Time Departure de BART mostraba los próximos trenes de la estación deseada y ofrecía autoactualización, así que fue una buena fuente inicial de datos
  • Se intentó mostrar PNG con eips, la CLI del Kindle, pero al principio las imágenes salían muy estiradas o recortadas y no servían
  • La causa era que la herramienta de visualización del Kindle no procesaba bien los PNG RGB de 24 bits y los leía como si fueran imágenes de 8 bits
    • Al aplicar -colorspace gray -depth 8 en ImageMagick convert, comenzaron a mostrarse correctamente
    • Los requisitos clave pasaron a ser una imagen del tamaño de la pantalla del Kindle y una profundidad de color de 8 bits

Primera implementación: Node.js, Puppeteer y Raspberry Pi

  • El servidor inicial usaba Node.js y Puppeteer para capturar el área necesaria de la página web de BART
  • La imagen capturada se convertía al tamaño de la pantalla del Kindle y a escala de grises de 8 bits, y luego se devolvía desde un endpoint HTTP
  • El servidor corría en una Raspberry Pi, y el Kindle obtenía la imagen cada minuto mediante cron para mostrarla con eips
  • Como la propia página de BART ya soportaba autoactualización, al iniciar el servidor se dejaba abierta una sola vez y no se recargaba en cada solicitud
  • Esta versión funcionó bien por un tiempo, pero a veces el refresco se detenía o el servidor necesitaba reiniciarse por falta de memoria

Límites que aparecieron al ampliar a MUNI

  • En el área de la bahía de San Francisco hay 27 operadores de transporte público, y la primera implementación solo cubría BART, el tren regional rápido
  • Después surgió la necesidad de ver también los horarios de llegada de autobuses y tren ligero de MUNI
  • Como la página de MUNI también mostraba llegadas por parada, al inicio se intentó capturar varias páginas de paradas con Puppeteer y unirlas con ImageMagick
  • Las 7 secciones de la pantalla correspondían cada una a una pestaña del navegador que Puppeteer debía mantener
    • La Raspberry Pi no tenía memoria suficiente y Chrome no era liviano
    • La página de MUNI devolvía HTTP 429 si se la consultaba con demasiada frecuencia
  • La pantalla que mostraba BART y MUNI juntos era mucho más inestable que la de solo BART, y un tablero de consulta rápida necesitaba una arquitectura más confiable

Una arquitectura rediseñada en Rust

  • La versión reescrita eliminó Puppeteer y pasó a una estructura donde un servidor HTTP en Rust genera directamente los PNG
  • La interfaz pública se mantuvo parecida a la del servidor en Node.js, pero la implementación interna cambió por completo
  • Los componentes principales son tres
    • Obtener las próximas llegadas desde 511.org
    • Renderizar esas llegadas como imágenes PNG de 8 bits
    • Devolver el PNG generado desde un servidor HTTP
  • Para el servidor HTTP se usó Axum
  • Para renderizar PNG se usó rust-skia
    • Skia también es la librería gráfica base de Chrome, así que permitía aprovechar tecnología de renderizado con menos carga que Puppeteer
  • El código final del servidor tenía unas 1,200 líneas, y solo se resumieron las partes importantes

Obtener datos desde la Stop Monitoring API de 511.org

  • La Stop Monitoring API de 511.org entrega horas estimadas de llegada y salida para paradas del área de la bahía en XML y JSON
  • El endpoint usado tenía la forma http://api.511.org/transit/StopMonitoring/…]
  • agency especifica uno de los 27 operadores de transporte del área de la bahía, y el código de SF MUNI es SF
  • La API solo permite obtener datos de un operador a la vez
    • Devuelve las llegadas de una parada específica o los datos de todas las paradas de ese operador
    • No se encontró forma de pedir de una sola vez un subconjunto de varias paradas
  • El límite de la API de 511.org es de 60 solicitudes por hora, así que hacer varias solicitudes por parada no era adecuado para una pantalla en tiempo real
  • Al pedir todos los datos de SF MUNI, la solicitud HTTP tardaba unos 5 segundos y devolvía unos 27 MiB de datos, y el JSON formateado superaba el millón de líneas
  • La respuesta completa tenía unas 26k entradas MonitoredStopVisit, pero en la pantalla solo hacían falta LineRef, DirectionRef, DestinationName, StopPointRef, ExpectedArrivalTime y DestinationDisplay para paradas y líneas específicas

Detalles problemáticos al procesar datos en Rust

  • Se usaron reqwest, serde, serde_json, chrono, tokio y eyre para solicitudes HTTP, parsing de JSON, cálculo de tiempo, ejecución asíncrona y manejo de errores
  • Los IDs de las paradas de MUNI pueden verse en las páginas de parada del sitio de SF MUNI
    • Por ejemplo, Persia Ave & Paris St tiene 15918 para inbound y 15919 para outbound
    • Las paradas de MUNI tienen IDs separados por dirección
  • La respuesta de 511.org venía con un UTF-8 Byte Order Mark al inicio del JSON, y al parsearla directamente con .json() aparecía el error expected value at line 1 column 1
  • Al leer la respuesta con .text() como cadena UTF-8, el BOM se eliminaba y podía parsearse con serde_json::from_str
  • Después del filtrado, quedaban solo unas 20 llegadas de las paradas de interés, mucho más manejables que las 26k iniciales
  • Luego se agrupaban por LineRef y DirectionRef, se ordenaban por ExpectedArrivalTime y se convertían los timestamps UTC al formato “en cuántos minutos”

Generar PNG para Kindle con Skia

  • El renderizado PNG se construyó con skia-safe
  • Se creaba un bitmap Gray8 del tamaño de pantalla del Kindle, 1024x758, se rellenaba con fondo blanco y luego se dibujaban texto y líneas
  • Primero se generó una imagen PNG8 con “Hello World” para verificar el pipeline de renderizado
  • Después se creó una imagen tipo tabla con dos columnas, inbound y outbound, usando datos falsos de horas de llegada
  • Al combinarlo con datos reales de la API, hubo más variedad en destinos y horarios que en los datos de prueba, y el layout fijo empezó a producir superposición de texto
  • Para iterar rápido y evitar los límites de la API, se añadió código temporal para guardar la respuesta en caché en data.json

Afinando el layout de visualización

  • La primera pantalla con datos reales tenía poca legibilidad porque la cantidad de información por fila variaba
  • Las mejoras fueron las siguientes
    • Alinear las horas de llegada a la derecha para reducir espacio vacío al final de la tabla
    • Eliminar min después de cada tiempo y dejarlo solo al final de la lista
    • Mostrar solo los próximos 3 vehículos
    • Mover el nombre del destino hacia la derecha para considerar nombres de línea largos
  • Después, para mostrar el ID de la línea en una burbuja gris redondeada, se calcularon los límites del texto con measure_str
  • La cadena del destino se colocó 15 píxeles a la derecha del extremo de la burbuja del ID de línea
  • En la parte superior se añadió un encabezado gris claro con Muni Inbound y Muni Outbound centrados

Entregar PNG con Axum

  • El servidor HTTP se hizo con Axum 0.7 y devolvía el PNG en la ruta /stops.png
  • El encabezado de respuesta se configuró como Content-Type: image/png
  • Al principio se comprobó HTTP 200 con un cuerpo vacío y luego se conectó la función de generación de imagen para devolver directamente los bytes del PNG desde memoria
  • Al descargar la imagen con wget en el Kindle y mostrarla con eips, al principio aparecía rotada y recortada
  • Como el Kindle asume uso en orientación vertical, para que se viera como pantalla horizontal hubo que aplicar una matriz de rotación antes de renderizar
    • Se invirtió el tamaño de la imagen a (758, 1024)
    • Se rotó el canvas 90.0 grados

Funciones añadidas a la versión de uso real

  • La versión de uso real añadió las siguientes funciones
    • Un sistema de caché que sigue obteniendo datos de la API en segundo plano para que el servidor HTTP responda rápido
    • La hora en la esquina inferior izquierda y el estado de la caché de datos en la esquina inferior derecha
    • Antialiasing
    • Recorte de nombres de destino para evitar que se encimen con el área de horarios de llegada
    • Uso de distintos tonos de gris por línea para distinguirlas rápidamente
    • Configuración basada en archivo de configuración
    • Si ocurre un error, renderizar el error y la cadena de .source() como PNG para poder verlo directamente en el Kindle
  • El código del servidor simplificado que se hizo junto con el proyecto está en transit-kindle-simplified
  • El código del servidor que hace funcionar el Kindle montado en la pared está en transit-kindle
  • La versión basada en capturas de BART funcionaba alrededor de febrero de 2023, y el ajuste final de la UI de la reescritura en Rust se completó en enero de 2024

1 comentarios

 
GN⁺ 2024-10-25
Opiniones de Hacker News
  • Trabajo en la organización sin fines de lucro detrás de OneBusAway. OneBusAway es un proyecto de código abierto que millones de personas usan todos los días para consultar la ubicación y los horarios de llegada de buses, trenes, tranvías y funiculares.
    Si tu ciudad ya tiene un servidor de OneBusAway, puedes crear una app personalizada con el nuevo SDK: https://github.com/onebusAway/?q=sdk&type=all&language=&sort...
    Si tu ciudad no tiene servidor, con la imagen de Docker y la configuración de OpenTofu que creamos este año puedes convertir feeds GTFS/GTFS-RT en una API REST.
    BART también ofrece GTFS y GTFS-RT: https://mobilitydatabase.org/feeds/mdb-53
    Otras agencias de transporte público en EE. UU. también están en una situación en la que deben publicar al menos datos de horarios estáticos en GTFS debido a nuevas reglas federales.
    Siempre necesitamos desarrolladores que contribuyan al software relacionado y, en particular, nos hace mucha falta un desarrollador iOS que ayude a mejorar la experiencia de 250 mil usuarios diarios. Contacto: aaron@onebusaway.org
    Docker: https://github.com/OneBusAway/onebusaway-docker / OpenTofu: https://github.com/OneBusAway/onebusaway-deployment

    • Actualmente solo hay 9 ciudades compatibles en todo el mundo, y 2 de ellas están en beta, así que parece que tendría que expandirse más para que también sea más fácil contribuir al código abierto.
      También me gustaría agregar mi ciudad si el proceso fuera sencillo. Parece que últimamente dedicaron mucho tiempo de ingeniería al soporte de bibliotecas y SDK, pero creo que sería mejor invertir en una ruta sencilla de experiencia para desarrolladores para que a nuevos colaboradores les valga la pena dedicar tiempo.
      Por ejemplo, esta documentación es bastante compleja: https://github.com/OneBusAway/onebusaway/wiki/Multi-Region
    • Me pregunto dónde se puede encontrar la lista de ciudades donde OneBusAway está desplegado. Si busco “onebusaway cities”, aparece https://onebusaway.org/onebusaway-deployments/, pero no estaban ciudades como Adelaide que se mencionaron ahí.
    • Si es de código abierto, me pregunto por qué lo publican solo en Play Store y no lo distribuyen en F-Droid.
    • Me pregunto por qué las apps de seguimiento de buses, no solo OneBusAway sino también Google Maps, son tan imprecisas.
      Dice “llega en 4 minutos” y el bus se va frente a tus ojos, o dice “5 minutos de retraso” y termina llegando 2 minutos antes. A veces el bus directamente no aparece.
      Supongo que es un problema de los datos de origen, pero me gustaría conocer la interna de qué hace que sea tan poco confiable. Como referencia, hablo de Seattle.
  • Tanto BART como MUNI soportan General Transit Feed Specification, y hay una forma estándar de obtener estos datos.
    https://gtfs.org/documentation/overview/#gtfs-realtime

    • Me pregunto qué tan confiable es la información en tiempo real. Google Maps dice “el bus llega en 4 minutos”, pero lo estoy viendo irse, o dice “5 minutos de retraso” y en realidad llega 2 minutos antes con bastante frecuencia.
  • Me encanta. Hace tiempo, cuando vivía en Boston sin auto, armé por mi cuenta algo mucho más rudimentario para manejar casos en los que había varias formas de ir de A a B y la “mejor” dependía de la hora o de los lugares por los que pasara en el medio.
    Por ejemplo, antes de ir al trabajo llevaba a mi hijo a la escuela y, a veces, desayunábamos después de dejarlo. Tener una función de ver próximas salidas me habría permitido decidir con mucha más flexibilidad en situaciones no determinísticas, como caminar con un niño de 4 años por lugares interesantes, o si convenía apurarse para tomar un tren que, si lo perdíamos, dejaba un hueco grande en la frecuencia.

  • Me sorprende que todavía no se haya mencionado https://oeffi.schildbach.de/index.html. Hace exactamente lo que se quiere aquí.

  • Si estás en el Reino Unido, puedes comprar un dispositivo de tablero de salidas que imita los paneles de salidas de las estaciones
    https://ukdepartureboards.co.uk/store/product/desktop-depart...

  • Yo también vivo en SF y, para resolver el mismo problema, hice una app de iOS llamada A Better Ride
    Solo muestra los horarios de salida del transporte público en las paradas cercanas. El objetivo es reducir el estrés haciendo que el transporte público sea predecible y fácil de explorar. Es un proyecto personal que hago en mi tiempo libre, sin monetización

    • La app es realmente excelente. Se diferencia de otras apps de transporte público en que solo muestra las rutas cercanas y hacia dónde van
      Permite explorar lugares a los que puedes llegar sin preocuparte por los transbordos
    • La app está muy buena. El mapa en tiempo real me gusta especialmente
      Yo también estoy construyendo hardware en este ámbito y últimamente he trabajado mucho con GTFS, así que se nota de inmediato cuánto trabajo hubo para parsear y mostrar los datos de transporte
      Si te parece bien, me gustaría hablar sobre las decisiones de UX para resumir y agrupar los viajes posibles desde cada parada, y sobre el backend
    • Está muy buena. Tiene muchos detalles cuidados, como mostrar de qué lado de la calle está la parada y cuál es la dirección general del viaje
  • No hace falta hacerle jailbreak al Kindle ni renderizar imágenes
    Basta con abrir una página web propia en el navegador del Kindle y desactivar con comandos de depuración los anuncios de “protector de pantalla” o la pantalla de ahorro de energía del Kindle
    La barra del navegador queda en un borde del Kindle, pero puedes rotar la orientación del dispositivo para dejarla abajo o a la derecha. A cambio, puedes crear una pantalla de clima, transporte y noticias con HTML/CSS/JS sencillo y el lenguaje de backend que quieras, y correrla en algo como una instancia barata de DigitalOcean de 4 dólares
    https://blog.notfaqs.com/2018/06/kindle-e-reader-disable-scr...
    https://www.mobileread.com/forums/showthread.php?t=198334

    • Sería genial que Amazon fomentara y agregara un modo kiosco al navegador del Kindle para este tipo de hacks de pantalla
    • Hice esto con un inkBOOK que corre Android. Le cargué el APK de Chrome y abrí una página web propia
      Con document.documentElement.requestFullscreen() entró en pantalla completa real y ni siquiera se veía la barra de estado. Como la API de transporte público local era accesible directamente desde la página web, ni siquiera hizo falta backend
    • Investigué con un Kindle de 4.ª generación y parece que casi no puede usar sitios HTTPS porque los certificados no son válidos
      Aun así, probablemente estaría bien si se lo hace comunicarse con un servidor en la red local
    • Es casi seguro que en Kindles posteriores se eliminó la opción de desactivar el protector de pantalla
  • Creo que no entendiste bien este caso de uso. No quiero “tomar el tren”, quiero ir de A a B
    Si el tren está averiado, hay huelga, la línea está bloqueada de 1 a 4 p. m., o no está en horario de servicio, quiero alternativas. Por eso necesito lo que Google Maps ofrece, o intenta ofrecerme
    Si voy del Ferry Building a Oakland y BART está averiado, quizá convenga más el ferry; si es tarde, puede que solo quede un taxi o Uber
    Si en Tokyo voy de Shibuya a Azabujuban, hay opciones como Ginza Line→Namboku Line, Hanzomon Line→Oedo Line, Yamanote Line→Namboku Line, el autobús 6, taxi, etc.
    Entre las estaciones y las paradas de autobús de cada ruta también hay caminatas de 3 a 8 minutos, así que qué tan cerca estoy ahora influye en la elección. También importa si voy tarde o si llevo equipaje grande o pesado

    • Google Maps parece asumir con mucha fuerza que caminar es algo muy malo, algo que nadie quiere
      Por ejemplo, para ir a cierto lugar puedo tomar el autobús 39 y caminar 15 minutos, pero si le pregunto a Google me da una ruta con 2 transbordos. Con suerte, tarda más o menos lo mismo que un autobús más caminata
      Así que, si quiero saber cuándo llega el autobús 39, tengo que decirle deliberadamente otro destino
      Además, donde vivo los datos en tiempo real de Google me generan dudas, y los datos del operador o de algunos terceros son más confiables
    • El caso de uso es el desplazamiento cotidiano. Mucha gente, la mayor parte del tiempo, realmente quiere “tomar el tren”
      Hay situaciones en las que hay que hacer una planificación sofisticada en tiempo real, y está bueno tener herramientas para eso, pero los traslados cotidianos se cubren bastante bien con herramientas mucho más simples
      El ejemplo de Tokyo que das también es interesante, pero según Google la mayoría de las opciones de transporte público tardan entre 30 y 40 minutos, así que para una planificación aproximada son casi equivalentes. El taxi es más rápido y la bicicleta tarda casi lo mismo
    • En mi viaje diario al trabajo, realmente solo quiero “tomar el tren”
      Si el tren está averiado o hay huelga, aparece como un hueco inesperado en la lista de trenes en tiempo real
      Cuando me muevo por la ciudad abro Google Maps y pongo el destino, pero cuando salgo de casa hacia un lugar al que ya fui 500 veces, no me molesto en abrirlo
    • La experiencia de transporte público en el centro de Tokyo no se puede generalizar al mundo entero
      Es tan poco significativo como hablar basándose en la experiencia de comprar en tiendas o comer solo dentro de Disneyland. Incluso pasar la Suica es varias veces más rápido que en otros lugares, y en algunos ni siquiera está soportada
    • Quizá te guste https://citymapper.com
      Tiene muchas funciones, pero no sé qué tan bien está soportado Tokyo. En London, si le pides que te lleve a casa, muestra varias formas optimizadas por costo, distancia a pie, rapidez, transbordos, accesibilidad, etc.
  • Estoy usando algo parecido con una tablet/dispositivo para salas de reuniones Lenovo ThinkSmart View reutilizado, integrado con Home Assistant y la agencia de transporte local.
    La ventaja es que está mucho más listo para usarse de inmediato. Claro que le instalé una ROM personalizada de Android al Lenovo, pero puede mostrar cualquier cosa que quiera y también controlar cosas como las luces o la aspiradora robot.

  • También hay un producto hermoso que convierte el mapa de BART en un letrero físico con LED: https://www.designrules.co/