2 pontos por GN⁺ 2024-10-25 | 1 comentários | Compartilhar no WhatsApp
  • Para uma vida sem carro em São Francisco, é mais prático ter um dispositivo que mostre imediatamente os próximos horários de chegada das paradas próximas antes de sair de casa
  • Um Kindle antigo foi desbloqueado e transformado em um painel de tinta eletrônica na parede; no início, ele exibia periodicamente imagens capturadas da página do BART
  • Ao adicionar o MUNI, a combinação de várias abas do navegador, pressão de memória no Raspberry Pi e HTTP 429 tornou a arquitetura baseada em screenshots pouco confiável
  • A versão reescrita em Rust busca dados na Stop Monitoring API do 511.org e gera diretamente PNGs com skia-safe, ajustados à resolução do Kindle e à profundidade de cor de 8 bits
  • Ao eliminar o motor do navegador e acrescentar cache, exibição de erros e ajustes de layout, o projeto se tornou um painel Kindle de transporte público utilizável no dia a dia

A informação necessária ao sair de casa é simples

  • Vivendo sem carro em São Francisco, o autor usa frequentemente transporte público, como ônibus, trens, bondes, trólebus, bondinhos e balsas
  • Apps como o CityMapper são úteis para orientar trajetos até um destino, mas, na porta de casa, os próximos horários de chegada de cada linha importam mais do que “para onde ir”
  • Se você já sabe quais paradas e linhas vai usar, o modelo de localização atual e destino dos apps de navegação genéricos é mais complexo do que o necessário
  • A ideia veio de um texto de Matt Healy sobre usar um Kindle antigo como display de casa inteligente e de um texto de Ben Borgers sobre usar um Nook antigo como porta-retratos do iCloud

Até exibir imagens no Kindle

  • O primeiro passo foi desbloquear o Kindle, ativar o USBNet e permitir acesso via SSH
    • Depois disso, um job cron atualizava a tela periodicamente
    • Materiais sobre hacking de Kindle no wiki e nos fóruns do mobileread foram usados como referência
  • A página Real Time Departure do BART oferecia os próximos trens da estação desejada e atualização automática, sendo adequada como fonte inicial de dados
  • A tentativa de exibir PNGs com o eips da CLI do Kindle inicialmente produzia imagens muito esticadas ou cortadas, inutilizáveis
  • A causa era que a ferramenta de exibição do Kindle não lidava corretamente com PNGs RGB de 24 bits, lendo-os como se fossem imagens de 8 bits
    • Ao aplicar -colorspace gray -depth 8 no convert do ImageMagick, a exibição passou a funcionar corretamente
    • Imagens no tamanho da tela do Kindle e com profundidade de cor de 8 bits se tornaram os requisitos essenciais

Primeira implementação: Node.js, Puppeteer, Raspberry Pi

  • O servidor inicial usava Node.js e Puppeteer para capturar a área necessária da página do BART
  • A imagem capturada era convertida para o tamanho da tela do Kindle e para escala de cinza de 8 bits, depois retornada por um endpoint HTTP
  • O servidor rodava em um Raspberry Pi, e o Kindle, via cron, buscava a imagem a cada minuto e a exibia com eips
  • Como a própria página do BART oferecia atualização automática, a implementação abria a página uma vez ao iniciar o servidor e não a recarregava a cada requisição
  • Essa versão funcionou bem por algum tempo, mas às vezes a atualização parava ou o servidor precisava ser reiniciado por falta de memória

Limites revelados pela expansão para o MUNI

  • A região da Baía de São Francisco tem 27 operadoras de transporte público, e a primeira implementação cobria apenas o BART, o trem metropolitano regional
  • Depois surgiu a vontade de ver também os horários de chegada dos ônibus e do VLT do MUNI
  • As páginas do MUNI também ofereciam horários de chegada por parada, então a primeira tentativa foi capturar várias páginas de paradas com Puppeteer e combiná-las com ImageMagick
  • As 7 seções da tela correspondiam, cada uma, a uma aba de navegador que o Puppeteer precisava manter
    • O Raspberry Pi não tinha memória suficiente, e o Chrome não era leve
    • As páginas do MUNI retornavam HTTP 429 quando requisitadas com frequência
  • A tela que exibia BART e MUNI juntos era muito mais instável do que a tela só do BART, e um painel de consulta rápida precisava de uma arquitetura mais confiável

Arquitetura redesenhada em Rust

  • A versão reescrita removeu o Puppeteer e passou a usar um servidor HTTP em Rust que gera PNGs diretamente
  • A interface pública foi mantida parecida com a do servidor em Node.js, mas a implementação interna mudou completamente
  • Os principais componentes são três:
    • Buscar os próximos horários de chegada no 511.org
    • Renderizar os horários de chegada como uma imagem PNG de 8 bits
    • Retornar o PNG gerado por um servidor HTTP
  • O servidor HTTP usa Axum
  • A renderização de PNG usa rust-skia
    • Como Skia também é a biblioteca gráfica base do Chrome, ela permite aproveitar tecnologia de renderização com menos peso que o Puppeteer
  • O código final do servidor tinha cerca de 1.200 linhas, e apenas as partes importantes foram resumidas

Buscando dados na Stop Monitoring API do 511.org

  • A Stop Monitoring API do 511.org fornece, em XML e JSON, horários estimados de chegada e partida das paradas da região da Baía
  • O endpoint usado tem o formato http://api.511.org/transit/StopMonitoring/…]
  • agency especifica uma das 27 operadoras de transporte público da região da Baía; o código do SF MUNI é SF
  • A API só permite buscar dados de uma operadora por vez
    • Ela retorna os horários de chegada de uma parada específica ou dados de todas as paradas daquela operadora
    • Não foi encontrada uma forma de buscar, de uma vez, um subconjunto com várias paradas
  • O limite da API do 511.org é de 60 requisições por hora, então fazer várias requisições por parada não é adequado para exibição em tempo real
  • Ao solicitar todos os dados do SF MUNI, a requisição HTTP levou cerca de 5 segundos e retornou cerca de 27 MiB de dados; o JSON formatado ultrapassava 1 milhão de linhas
  • A resposta completa tinha cerca de 26 mil itens MonitoredStopVisit, mas a tela realmente precisava apenas de LineRef, DirectionRef, DestinationName, StopPointRef, ExpectedArrivalTime e DestinationDisplay para paradas e linhas específicas

Detalhes encontrados no processamento de dados em Rust

  • reqwest, serde, serde_json, chrono, tokio e eyre foram usados para requisições HTTP, parsing de JSON, cálculo de horários, execução assíncrona e tratamento de erros
  • Os IDs de paradas do MUNI podem ser verificados nas páginas de paradas do site do SF MUNI
    • Por exemplo, Persia Ave & Paris St tem IDs separados para inbound, 15918, e outbound, 15919
    • As paradas do MUNI têm IDs separados por direção
  • A resposta do 511.org vinha com uma marca de ordem de bytes UTF-8 antes do JSON; ao tentar fazer parsing direto com .json(), ocorria o erro expected value at line 1 column 1
  • Ao ler a resposta como string UTF-8 com .text(), o BOM era removido, permitindo fazer parsing com serde_json::from_str
  • Depois da filtragem, restavam cerca de 20 informações de chegada das paradas de interesse, muito mais fáceis de manipular do que os 26 mil itens iniciais
  • Os dados eram agrupados por LineRef e DirectionRef, ordenados por ExpectedArrivalTime, e os timestamps UTC eram convertidos para o formato “daqui a quantos minutos”

Gerando PNG para o Kindle com Skia

  • A renderização de PNG foi construída com skia-safe
  • Foi criado um bitmap Gray8 no tamanho da tela do Kindle, 1024x758, preenchido com fundo branco, sobre o qual eram desenhados textos e linhas
  • Primeiro foi criada uma imagem PNG8 exibindo “Hello World” para verificar o pipeline de renderização
  • Depois, com dados falsos de horários de chegada, foi gerada uma imagem em formato de tabela com duas colunas, inbound e outbound
  • Ao combinar com dados reais da API, destinos e horários ficaram mais variados do que nos dados de teste, causando sobreposição de strings no layout fixo
  • Para acelerar as iterações e evitar atingir limites da API, foi adicionado um código temporário que armazenava a resposta em cache em data.json

Refinando o layout de exibição

  • A primeira tela com dados reais tinha baixa legibilidade porque a quantidade de dados exibida variava por linha
  • As melhorias foram:
    • Alinhar os horários de chegada à direita, reduzindo o espaço no fim da tabela
    • Remover o min depois de cada horário e mantê-lo apenas no fim da lista
    • Mostrar apenas os próximos 3 veículos
    • Mover o nome do destino para a direita para acomodar nomes longos de linhas
  • Depois, para exibir os IDs de linha como balões arredondados cinza, os limites do texto foram calculados com measure_str
  • A string do destino foi posicionada 15 pixels à direita do fim do balão do ID da linha
  • No topo, foi adicionado um cabeçalho em cinza claro com Muni Inbound e Muni Outbound centralizados

Servindo PNGs com Axum

  • O servidor HTTP foi criado com Axum 0.7, retornando o PNG na rota /stops.png
  • O cabeçalho da resposta é definido como Content-Type: image/png
  • Primeiro foi verificado o HTTP 200 com um corpo vazio; depois, a função de geração de imagem foi conectada para retornar os bytes do PNG diretamente da memória
  • Ao baixar a imagem no Kindle com wget e exibi-la com eips, inicialmente ela aparecia girada e cortada
  • Como o Kindle usa orientação vertical por padrão, para fazê-la aparecer em paisagem era necessário aplicar uma matriz de rotação antes da renderização
    • O tamanho da imagem foi invertido para (758, 1024)
    • O canvas foi girado em 90.0 graus

Recursos adicionados à versão de uso real

  • A versão usada na prática recebeu os seguintes recursos:
    • Um sistema de cache que busca dados da API continuamente em segundo plano para que o servidor HTTP responda rápido
    • Exibição da hora no canto inferior esquerdo e do status do cache de dados no canto inferior direito
    • Antialiasing
    • Recorte dos nomes de destino para evitar sobreposição com a área dos horários de chegada
    • Uso de diferentes tons de cinza por linha para facilitar a diferenciação rápida
    • Configuração baseada em arquivo
    • Renderização de erros e da cadeia .source() em PNG, para que possam ser vistos diretamente no Kindle
  • O código do servidor simplificado criado em conjunto está em transit-kindle-simplified
  • O código do servidor que alimenta o Kindle real na parede está em transit-kindle
  • A versão baseada em screenshots do BART funcionava por volta de fevereiro de 2023, e os ajustes finais da UI da reescrita em Rust foram concluídos em janeiro de 2024

1 comentários

 
GN⁺ 2024-10-25
Opiniões do Hacker News
  • Trabalho na organização sem fins lucrativos por trás do OneBusAway. OneBusAway é um projeto open source usado todos os dias por milhões de pessoas para verificar a localização e os horários de chegada de ônibus, trens, bondes e funiculares
    Se a sua cidade já tem um servidor OneBusAway, você pode criar um app personalizado com o novo SDK: https://github.com/onebusAway/?q=sdk&type=all&language=&sort...
    Se a sua cidade não tem servidor, com as imagens Docker e configurações OpenTofu que criamos este ano é possível transformar feeds GTFS/GTFS-RT em uma REST API
    O BART também oferece GTFS e GTFS-RT: https://mobilitydatabase.org/feeds/mdb-53
    Outros órgãos de transporte público nos EUA também, por causa de novas regras federais, estão em uma situação em que precisam divulgar pelo menos os dados estáticos de horários em GTFS
    Sempre precisamos de desenvolvedores para contribuir com software relacionado, e precisamos especialmente de desenvolvedores iOS para ajudar a melhorar a experiência de 250 mil usuários por dia. O contato é aaron@onebusaway.org
    Docker: https://github.com/OneBusAway/onebusaway-docker / OpenTofu: https://github.com/OneBusAway/onebusaway-deployment

    • Atualmente há apenas 9 cidades atendidas no mundo todo, e 2 delas estão em beta; acho que contribuições open source também ficariam mais fáceis se isso se espalhasse mais
      Eu também gostaria de adicionar a minha cidade se o processo fosse simples. Parece que recentemente foi gasto bastante tempo de engenharia no suporte a bibliotecas e SDKs, mas me parece melhor investir em um caminho fácil de experiência para desenvolvedores, para que novos contribuidores consigam dedicar tempo
      Por exemplo, esta documentação é bem complexa: https://github.com/OneBusAway/onebusaway/wiki/Multi-Region
    • Fico curioso para saber onde encontrar a lista de cidades em que o OneBusAway foi implantado. Ao buscar por “onebusaway cities”, aparece https://onebusaway.org/onebusaway-deployments/, mas cidades mencionadas ali, como Adelaide, não apareciam
    • Se é open source, fico curioso por que está publicado apenas na Play Store e não é distribuído no F-Droid
    • Fico curioso por que apps de rastreamento de ônibus, não só o OneBusAway mas também o Google Maps etc., são tão imprecisos
      Aparece “chega em 4 minutos” e o ônibus sai na minha frente, ou diz “5 minutos de atraso” e ele chega 2 minutos adiantado. Às vezes o ônibus simplesmente nem aparece
      Imagino que seja um problema nos dados de origem, mas gostaria de entender os bastidores do que torna isso tão pouco confiável. Para referência, estou falando de Seattle
  • BART e MUNI dão suporte ao General Transit Feed Specification, e há uma forma padronizada de obter esses dados
    https://gtfs.org/documentation/overview/#gtfs-realtime

    • Fico curioso sobre quão confiáveis são as informações em tempo real. O Google Maps diz “ônibus chega em 4 minutos” enquanto eu estou vendo o ônibus partir, ou diz “5 minutos de atraso” e na prática ele chega 2 minutos adiantado; isso acontece com frequência
  • Gostei muito. Antigamente, quando eu morava em Boston sem carro, cheguei a montar por conta própria algo muito mais tosco para lidar com casos em que havia várias maneiras de ir de A a B e o “melhor” caminho mudava conforme o horário ou os lugares onde eu passaria no meio
    Por exemplo, antes de ir ao trabalho eu levava meu filho à escola e, às vezes, tomava café da manhã depois de deixá-lo. Ter uma opção de ver próximos horários de partida teria me permitido tomar decisões com muito mais flexibilidade em situações não determinísticas, como caminhar com uma criança de 4 anos por lugares interessantes, ou decidir se era preciso correr para pegar um trem que, se perdido, deixaria um buraco no intervalo entre partidas

  • É surpreendente que https://oeffi.schildbach.de/index.html ainda não tenha sido mencionado. Ele faz exatamente o que se quer aqui

  • No Reino Unido, dá para comprar um dispositivo de painel de partidas que imita os painéis de saída das estações
    https://ukdepartureboards.co.uk/store/product/desktop-depart...

  • Eu também moro em SF e, para resolver o mesmo problema, criei um app iOS chamado A Better Ride
    Ele mostra apenas os horários de partida do transporte público nas paradas próximas. O objetivo é reduzir o estresse tornando o transporte público previsível e fácil de explorar. É um projeto pessoal que faço no meu tempo livre, sem monetização

    • Esse app é realmente excelente. Ele se diferencia de outros apps de transporte público por mostrar apenas as linhas ao redor e para onde elas vão
      Permite explorar lugares aonde dá para ir sem se preocupar com baldeações
    • O app é muito bacana. O mapa em tempo real é especialmente bom
      Eu também estou criando hardware nessa área e recentemente lidei bastante com GTFS, então dá para perceber de imediato quanto trabalho foi necessário para parsear e exibir dados de transporte
      Se você topar, eu gostaria de conversar sobre as decisões de UX para resumir e agrupar os deslocamentos possíveis a partir de cada parada, e também sobre o backend
    • Muito bom. Há muitos detalhes cuidadosos, como mostrar de que lado da rua fica e qual é a direção geral do trajeto
  • Não é preciso fazer jailbreak no Kindle nem renderizar imagens
    Basta abrir uma página web criada por você no navegador do Kindle e, com um comando de debug, desligar os anúncios de “protetor de tela” ou a tela de economia de energia do Kindle
    A barra do navegador ainda fica em uma das bordas do Kindle, mas você pode girar o aparelho para deixá-la embaixo ou à direita. Em troca, dá para criar uma tela de clima, transporte e notícias com HTML/CSS/JS simples e a linguagem de backend que quiser, e rodá-la em algo como uma instância barata de US$ 4 da DigitalOcean
    https://blog.notfaqs.com/2018/06/kindle-e-reader-disable-scr...
    https://www.mobileread.com/forums/showthread.php?t=198334

    • Seria ainda mais legal se a Amazon incentivasse e criasse um modo quiosque no navegador do Kindle para esse tipo de hack de display
    • Fiz isso com um inkBOOK que roda Android. Instalei o APK do Chrome e abri uma página web que eu mesmo fiz
      Com document.documentElement.requestFullscreen(), ele entra em tela cheia de verdade e nem a barra de status aparece. A API de transporte público local era acessível diretamente pela página web, então nem precisei de backend
    • Pesquisei com um Kindle de 4ª geração e parece que quase não dá para usar sites HTTPS, porque os certificados não são válidos
      Ainda assim, acho que funcionaria bem fazendo-o se comunicar com um servidor na rede local
    • É quase certo que, em Kindles posteriores, a função de desativar o protetor de tela tenha sido removida
  • Acho que não entendi bem esse caso de uso. A pessoa não quer “pegar o trem”, quer ir de A a B
    Se o trem quebrou, se há greve, se a linha está bloqueada das 13h às 16h ou se não é horário de operação, eu quero alternativas. Então, para mim, preciso do que o Google Maps oferece, ou tenta oferecer
    Se estou indo do Ferry Building para Oakland e o BART quebrou, talvez a balsa seja melhor; se for tarde, talvez só reste táxi ou Uber
    Em Tokyo, se vou de Shibuya a Azabujuban, há opções como Ginza Line→Namboku Line, Hanzomon Line→Oedo Line, Yamanote Line→Namboku Line, ônibus 6, táxi etc.
    Entre as estações e pontos de ônibus de cada linha também se caminha de 3 a 8 minutos, então o que está mais perto de onde estou agora influencia a escolha. Também importa se estou atrasado ou com bagagem grande ou pesada

    • O Google Maps parece partir de uma premissa muito forte de que caminhar é algo muito ruim, que ninguém quer fazer
      Por exemplo, para ir a certo lugar, eu poderia pegar o ônibus 39 e caminhar 15 minutos, mas, se pergunto ao Google, ele me dá uma rota com 2 baldeações. Com sorte, o tempo fica parecido com pegar um ônibus e caminhar
      Então, se quero saber quando vem o ônibus 39, preciso dizer de propósito um destino diferente
      Além disso, onde moro, os dados em tempo real do Google são suspeitos, e os dados da operadora ou de alguns terceiros são mais confiáveis
    • O caso de uso é deslocamento cotidiano. Na maior parte do tempo, muitas pessoas realmente querem “pegar o trem”
      Há situações em que é preciso fazer um grande planejamento em tempo real, e seria bom ter uma ferramenta para isso, mas deslocamentos cotidianos podem ser cobertos muito bem por uma ferramenta muito mais simples
      O exemplo de Tokyo que você deu também é interessante, mas, segundo o Google, a maioria das opções de transporte público leva de 30 a 40 minutos, então são quase equivalentes para um planejamento aproximado. Táxi é mais rápido, e bicicleta também é quase parecida
    • No deslocamento diário para o trabalho, eu realmente só quero “pegar o trem”
      Se o trem quebrou ou há greve, isso aparece na lista de trens em tempo real como uma lacuna inesperada
      Quando estou circulando pela cidade, abro o Google Maps e coloco o destino, mas, quando saio de casa para ir a um lugar onde já fui 500 vezes, não faço questão de abrir
    • A experiência de transporte público no centro de Tokyo não pode ser generalizada para o mundo inteiro
      É tão sem sentido quanto falar com base na experiência de comprar em lojas ou comer apenas dentro da Disneyland. Só encostar o Suica já é várias vezes mais rápido do que em outros lugares, e há lugares onde isso nem é suportado
    • Talvez você goste de https://citymapper.com
      Ele tem muitos recursos, mas não sei até que ponto é suportado em Tokyo. Em London, quando você pede para ir para casa, ele mostra várias maneiras otimizadas por custo, distância a pé, velocidade, baldeações, acessibilidade etc.
  • Estou usando algo parecido em um tablet/dispositivo de sala de reunião Lenovo ThinkSmart View reaproveitado, com o Home Assistant integrado ao órgão de transporte local.
    A vantagem é que ele fica muito mais pronto para usar. Claro que instalei uma ROM Android customizada no Lenovo, mas dá para exibir qualquer coisa que eu quiser e também controlar coisas como luzes ou o robô aspirador.

  • Também há um produto bonito que transforma o mapa do BART em um painel físico de LED: https://www.designrules.co/