Guide
(Foto: Øyvind N. Dahl)

Slik lager du din egen Ruter-skjerm med knøttemaskinen Arduino

Se hvordan vi bygde vår egen lille skjerm som viser når neste buss kommer.

Hei, dette er en Ekstra-sak som noen har delt med deg.

Lyst til å lese mer? Få fri tilgang, ny og bedre forside og annonsefritt nettsted for kun 49,- i måneden.
Prøv én måned gratis Les mer om Tek Ekstra

Skulle du ønske du visste nøyaktig når neste buss kommer til holdeplassen din, uten å måtte ta opp mobilen eller åpne en nettleser? Hadde det ikke vært kjekkere om du hadde det synlig hjemme, på samme måte som at du enkelt kan sjekke klokka på stekeovnen?  Vi i Tek.no bestemte oss for å lage vår egen lille sanntidsskjerm til å ha stående på skrivebordet, for å vise hvor lenge det er igjen til neste buss kommer.

I 2006 gjorde nemlig Ruter det mulig for hvem som helst å hente ut informasjon om for eksempel når neste buss kommer til en bestemt holdeplass – ved å legge ut sanntidsdataene sine åpent. Dette betydde at det nå var fritt frem for alle å hente ut disse dataene og lage nye applikasjoner basert på de.

Blant annet har tjenesten Mapnificient benyttet seg av dette til å lage en tjeneste som viser deg et kart med oversikt over hvor langt du kan komme deg med kollektivtransport på f.eks. en halvtime. Det er veldig nyttig dersom du tenker på å flytte eller bytte jobb.

Vi har tidligere skrevet blant annet en guide til hvordan du kommer igang med knøttemaskinen Arduino og hvordan du kan lage et utelys som skrus på automatisk når det blir mørkt. Ved å kombinere våre Arduino-kunnskaper med de åpne sanntidsdataene gikk vi igang med prosjektet. For anledningen gikk vi til innkjøp av et LCD Arduino-shield fra Linksprite med både en liten LCD-skjerm og noen knapper.

Vi delte oppgaven inn i tre deler:

  1. Få displayet til å fungere
  2. Få til å hente sanntidsdata fra Ruter
  3. Koble samme det hele
Arduino og LCD-shield.
Arduino og LCD-shield.Foto: Øyvind N. Dahl

 

Få displayet til å fungere

Skjermen vi brukte var en alfanumerisk LCD-skjerm. Den har mulighet til å vise to linjer tekst med opptil 16 tegn på hver linje. I tillegg har den 5 knapper. Skjermen kom i form av et såkalt Arduino-shield fra Linksprite, som betyr at vi kan plugge den inn på toppen av et standard Arduino-kort.

For å kunne styre skjermen fra Arduinoen, brukte vi et Arduino-bibliotek som heter «LiquidCrystal.h». Her finnes det masse funksjoner for å gjøre det enkelt å komme igang. Dette biblioteket ligger med som standard når du installerer Arduino på PC-en din. Alt du trenger å gjøre for å bruke det er å legge til følgende linje i toppen koden din:

#include <LiquidCrystal.h>

Hello World!
Hello World! Foto: Øyvind N. Dahl

En ting som er veldig vanlig når man skal lære seg noe nytt innenfor programmeringsverdenen, er å lage et såkalt «Hello World»-program. Dette er et program som ikke gjør noe mer enn å vise teksten «Hello Word» på skjermen, slik at du vet den fungerer. Derfor bestemte vi oss for å gjøre dette som første utfordring med skjermen vår.

Etter å ha lest litt på dokumentasjonen til LCD-skjermen, fant vi raskt ut hvordan dette kunne gjøres. Og bare en liten «klipp og lim» senere, er «Hello World» oppe og kjører.

Det gikk lekende lett. Alt vi trengte å gjøre var å initialisere skjermen i ved å bruke kommandoen:

lcd.begin(16, 2);

Etter å ha gjort dette, kunne vi enkelt skrive på skjermen ved å bruke følgende kommando:

lcd.print("hello world!"); 

Om du lurer på hvordan du får denne koden til å kjøre på Arduinoen, kan du sjekke denne guiden vi har skrevet. Der har vi forklart i detalj hvordan du programmerer Arduinoen.

Hente data fra sanntidssystemet

Vi finner holdeplass ID-en vha nettleseren.
Vi finner holdeplass ID-en vha nettleseren.Foto: Øyvind N. Dahl

For å få tilgang til sanntidssystemet måtte vi godta Norsk lisens for offentlige data og registrere oss på labs.ruter.no. Etter et par dager, fikk vi en epost med diverse linker til hvordan komme igang. Men for de som ikke bruker begreper som «JSON» og «REST» til daglig, så var det ikke helt rett frem å forstå hvordan man skulle bruke dette. Og vi fant heller ingen eksempler.

Men etter en del Googling og testing, så fant vi sakte men sikkert ut av hvordan vi kunne hente ut sanntidsdata. Og vi fant ut at vi faktisk kunne bruke en nettleser for å kjapt komme frem til det vi var ute etter. Det vi måtte gjøre var å sette sammen en URL bestående av «http://reisapi.ruter.no/» pluss de kommandoene vi ønsket å utføre. Deretter limte vi inn denne URL'en i adressefeltet til nettleseren for å se resulatet.

Hver holdeplass har en unik ID. For å kunne finne ut når neste buss kommer fra en bestemt holdeplass, så trenger vi å vite ID-en til holdeplassen. Ved å lete rundt i dokumentasjonen, fant vi ut at vi kunne søke på holdeplasser ved å bruke kommandoen «Place/GetPlaces». Så for å søke etter holdeplassen Rosenhoff, tastet vi følgende URL inn i nettleseren:

http://reisapi.ruter.no/Place/GetPlaces/rosenhoff

Sanntidsdata i en nettleser.
Sanntidsdata for en holdeplass vist i nettleseren.Foto: Øyvind N. Dahl

I resultatet, dukket det opp mange holdeplasser. Og dataene var heller ikke optimalisert for å bli lest av mennesker. Men etter litt intens kikking i dataene, finner vi ut at ID-en til holdeplassen Rosenhoff er 3011405.

Nå som vi har ID'en til holdeplassen, kan vi finne ut når de neste avgangene er, ved hjelp av følgende setning:

http://reisapi.ruter.no/StopVisit/GetDepartures/3011405

Når vi taster inn denne URL-en i nettleseren får vi masse data. Så nå gjelder det å finne ut hvilke data som er relevant for oss. Ved å bla nedover og kikke i dataene, dukket det etterhvert opp noe data kalt «MonitoredVehicleJourney», som så mistenkelig ut som sanntidsdata. Og etter nærmere inspeksjon, viser det seg å være nettopp dette. 

Men for å kunne hente ut nøyaktig den informasjonen vi er ute etter, så trenger vi en måte å hente ut dette automatisk på.

Programmere med Python

Python er et programmeringsspråk som er forholdsvis lett å komme igang med. Derfor bestemte vi oss for å lage et Python-script for å håndtere dataene.

Resultatet fra Python-programmet vårt.
Resultatet fra Python-programmet vårt.Foto: Øyvind N. Dahl

Vi går litt frem og tilbake mellom dataene i nettleseren vår, og Python-koden, for å finne ut nøyaktig hvilke data vi trenger og hvordan vi henter de. Og til slutt har vi eksperimentert oss frem til en løsning hvor vi klarer å plukke ut linjenummer, destinasjon og forventet ankomst for neste buss eller trikk til holdeplassen Rosenhoff. 

Forventet ankomst var oppgitt i et klokkeslett. Så for å finne ut hvor mange minutter det er igjen til bussen kommer, endte vi opp med en litt kronglete kode. Men den fungerte. 

Dette var koden vi brukte for å hente ut sanntidsdata:

import time, datetime
import requests, json

#Holdeplass nr 3011405 er Rosenhoff
ruterapi_url = "http://reisapi.ruter.no/StopVisit/GetDepartures/3011405"

#Her henter vi sanntidsdata for holdeplassen
r = requests.get(ruterapi_url)

#Her henter vi ut linjenummer, destinasjon og tid for ankomst
linjenummer = r.json()[0]["MonitoredVehicleJourney"]["LineRef"]
destinasjon = r.json()[0]["MonitoredVehicleJourney"]["DestinationName"]
ankomst = r.json()[0]["MonitoredVehicleJourney"]["MonitoredCall"]["AimedArrivalTime"]

#Her gjoer vi om ankomsttiden fra tekst til et tidsobjekt
t_ankomst = datetime.datetime.strptime(ankomst[:19], "%Y-%m-%dT%H:%M:%S")

#Her henter vi tiden akkurat naa
t_now = datetime.datetime.now()

#Saa gjoer vi om disse tidsobjektene til et standardformat
d1_ts = time.mktime(t_ankomst.timetuple())
d2_ts = time.mktime(t_now.timetuple())

#Naa kan finne ut hvor mange sekunder det er igjen
seconds_left = int(d2_ts-d1_ts)

#Her konverterer vi til minutter
time_left_str = " " + str(seconds_left/60) + " min"

#Vi har totalt 16 tegn paa skjermen. Her regner vi ut hvor mye plass vi har til destinasjonsnavnet
dest_len = 16 - len(time_left_str) - len(linjenummer) - 1

#Fjern tegn det ikke er plass til
destinasjon = destinasjon[:dest_len]

#Vi lager de to linjene med tekst
linje1 = "Rosenhoff: "
linje2 = linjenummer + " " + destinasjon + time_left_str

#Her skriver vi ut resultatet paa PC-skjermen
print ""
print linje1
print linje2
print ""

Nå gjenstår det bare å få sendt over de to linjene med tekst til Arduinoen, slik at vi kan presentere de på LCD-skjermen vår. 

Koble sammen Python og Arduino

Nå har vi både fått til det å skrive ut tekst på skjermen, og vi har klart å hente data fra sanntidssystemet til Ruter. Da gjenstår bare å koble det hele sammen. Vi trenger altså å overføre de to linjene med tekst som vi laget i Python-programmet, over til Arduino-skjermen.

Hvordan gjøre dette på en enkel måte? I artikkelen om hvordan du kan lage din egen fjernkontroll, lærte vi hvordan man kan bruke serieporten på en Arduino til å motta data fra PC-en. I dette prosjektet skal vi bruke samme metode. Så vi skal sette opp Arduinoen til å motta tekst over serieporten, og vi skal endre Python-programmet vårt til å sende tekst over serieporten.

Siden LCD-skjermen vår er begrenset til 16 tegn på to linjer, bestemmer vi oss for å sende nøyaktig 32 tegn fra Python-programmet hver gang vi vil oppdatere skjermen. Dette gjør at koden på Arduino-siden blir veldig enkel. Alt vi trenger å gjøre er å sjekke hvor mange tegn som er mottatt på serieporten – og når 32 tegn er mottatt – kan vi dele dette inn i to, og vise de første 16 tegnene på første linje og de neste 16 tegnene på neste linje.

For å sjekke om vi har mottatt 32 tegn eller mer på serieporten, bruker vi følgende linje:

if (Serial.available() > 31)

Verktøyet «Serial Monitor».
Verktøyet «Serial Monitor».Foto: Øyvind N. Dahl

Når vi har mottatt 32 tegn, skriver vi disse ut på skjermen. Vi kan velge om vi vil skrive til første eller andre linje ved å velge mellom disse to kommandoene:

lcd.setCursor(0,0); //For å skrive på første linje
lcd.setCursor(0,1); //For å skrive på andre linje

Vi tester at koden fungerer, ved å bruke verktøyet Serial Monitor i Arduino-programmet. Her skriver vi inn 32 tegn og trykker på «Send» – og vi kan se at de 32 tegnene dukker opp på LCD-skjermen vår. Dette virker lovende. 

Her er Arduino-koden vi endte opp med:

#include <LiquidCrystal.h>
LiquidCrystal lcd( 8, 9, 4, 5, 6, 7 );
char buffer[50];
void setup()
{
// initialize serial:
Serial.begin(9600);
lcd.begin(16, 2);
lcd.setCursor(0,0);
lcd.print("Velkommen!");
}
void loop()
{
if (Serial.available() > 31) {
Serial.readBytes(buffer, 16);
lcd.setCursor(0,0);
lcd.print(buffer);
Serial.readBytes(buffer, 16);
lcd.setCursor(0,1);
lcd.print(buffer);
}
}

For å få koden over på Arduinoen, bruker vi Arduino-programmet på PC-en. Først velger vi «Verify» for å sjekke at koden er gyldig, deretter velger vi «Upload» for å laste opp koden til Arduinoen.

Et merkelig problem oppstår

Det neste vi skal gjøre er å få sendt sanntidsdataene fra Python-programmet og over til Arduinoen, for å få hele systemet til å fungere sammen. Vi starter med å sende en enkel tekst fra Python over til Arduinoen, for å se om denne dukker opp på skjermen. Men det gjør den ikke. Ingenting skjer på skjermen. Hva er galt?

Etter å ha vært igjennom en fase med å skylde på at Arduinoen er ødelagt og deretter skylde på at USB-porten til PC-en er ødelagt, så går vi over i en mer konstruktiv fase hvor vi faktisk prøver å finne ut hva som er galt.

Det fungerte å sende tekst fra Serial Monitor første gang vi prøvde. Så vi prøver å sende tekst en gang til fra dette verktøyet, bare for å sjekke at det fortsatt fungerer. Vi taster inn en rekke tilfeldige bokstaver og sender over til Arduinoen. Og joda, dette fungerer fortsatt. Skjermen viser de tilfeldige bokstavene vi sendte over. Så går vi tilbake til Python-programmet vårt for å prøve å sende over noe tekst igjen. 

Og da skjer det noe spennende. Teksten vi sender dukker ikke opp, men vi kan se at skjermen fjerner de tilfeldige bokstavene og går tilbake til «Velkommen»-teksten som vi har lagt inn at skal skrives ved oppstart. Dette må bety at Arduinoen gjør en omstart når vi kobler til serieporten. Men hvorfor?

La oss spørre Google.

Og jammen viser det seg at dette er et velkjent problem på mange Arduino-kort. Det henvises til flere løsninger, blant annet å koble på en 120 Ohms motstand mellom de to pinnene Reset og 5V.

Etter litt tankevirksomhet innser vi at vi ikke trenger å hindre Arduinoen i å gjøre en omstart. Vi trenger jo nemlig bare å koble til serieporten én gang, helt i starten. Etter dette er vi jo tilkoblet og kan sende tekst over problemfritt. Derfor legger vi rett og slett bare inn en liten pause i Python-koden etter kodelinjen som setter opp koblingen til Arduinoen, slik at Arduinoen rekker å starte opp før vi begynner å sende noe.

Vår egenlagde sanntidsskjerm

Vår fungerende sannstidsskjerm. .
Vår fungerende sannstidsskjerm. Foto: Øyvind N. Dahl

Da gjenstår det bare for oss å få det hele til å gå kontinuerlig. Vi velger å koble til sanntidssystemet hvert 20. sekund for å hente nye data. Det burde holde.

I Python-programmet rydder vi opp litt i koden og lager en løkke som sørger for å oppdatere Arduino-skjermen med fersk sanntidsdata hvert 20. sekund.

Skjermen fungerer utmerket og vi har full kontroll på når den neste bussen kommer til holdeplassen. Følelsen av å ha laget vår egen sanntidsskjerm er fantastisk god.

Men det er fortsatt mange interessante oppgraderinger å prøve ut. For eksempel kunne det vært interessant å ha de 3 neste bussene rullende over den nederste linjen, slik som på de skjermene som er på holdeplassene. Og vi kunne ha koblet opp knappene under skjermen til å fungert som navigasjon, og dermed ha mulighet til å skifte mellom våre favorittholdeplasser.

Men vi sier oss fornøyd i denne omgang.

Her er Python-koden vi endte opp med:

import serial
import time, datetime
import requests, json

def hent_sanntid():
#Holdeplass nr 3011405 er Rosenhoff
ruterapi_url = "http://reisapi.ruter.no/StopVisit/GetDepartures/3011405"

#Her henter vi sanntidsdata for holdeplassen
r = requests.get(ruterapi_url)

#Her henter vi ut linjenummer, destinasjon og tid for ankomst
linjenummer = r.json()[0]["MonitoredVehicleJourney"]["LineRef"]
destinasjon = r.json()[0]["MonitoredVehicleJourney"]["DestinationName"]
ankomst = r.json()[0]["MonitoredVehicleJourney"]["MonitoredCall"]["AimedArrivalTime"]

#Her gjoer vi om ankomsttiden fra tekst til et tidsobjekt
t_ankomst = datetime.datetime.strptime(ankomst[:19], "%Y-%m-%dT%H:%M:%S")

#Her henter vi tiden akkurat naa
t_now = datetime.datetime.now()

#Saa gjoer vi om disse tidsobjektene til et standardformat
d1_ts = time.mktime(t_ankomst.timetuple())
d2_ts = time.mktime(t_now.timetuple())

#Saa finner vi ut hvor mange sekunder det er igjen
seconds_left = int(d2_ts-d1_ts)

#Og skriver dette som minutter
time_left_str = " " + str(seconds_left/60) + " min"

#Finn ut hvor mye plass vi har til destinasjonsnavnet
dest_len = 16 - len(time_left_str) - len(linjenummer) - 1

#Soerg for at det ikke er noen norske bokstaver
destinasjon = destinasjon.encode('ascii', 'ignore')

#Fjern tegn det ikke er plass til
destinasjon = destinasjon[:dest_len]

#Fyll evt inn mellomrom dersom navnet er kort
while len(destinasjon) < dest_len:
destinasjon = destinasjon + " "

#Sette sammen linjen
linje2 = linjenummer + " " + destinasjon + time_left_str
return linje2

#Innstillinger
port_name = '/dev/ttyACM0'
port_speed = 9600

# Koble til Arduino
arduino = serial.Serial(port_name, port_speed)
time.sleep(3)   #Fordi Arduino-kort resettes naar man kobler til serieporten

# Send info til arduino om hva som skjer
arduino.write("Kobler til Trafikanten... ")
arduino.flush()

while(1):
# Hent sanntidsdata
data_klartekst = hent_sanntid()

# Send over data til arduino
arduino.write("Rosenhoff: ")
arduino.write(data_klartekst)
arduino.flush()

#Vent 20 sekunder for alt gjentas
time.sleep(20)

#Lukk serieport
arduino.close() # close port

(Kilder: Labs.ruter.no, SourceforgeArduino)

Gå til side

Kommentarer (11)

Her er noen av sakene du kan lese på Ekstra i dag:

Domo arigato, Mr. Roboto

Klassisk sci-fi forteller oss at menneskelignende robototer skulle stått klare til å ta over verden. Hvor blir det egentlig av robotrevolusjonen?

Ekstra
Til toppen