Sanntid hjemme: Følg Ruter hjemmefra med Arduino og Python
Sanntidsskjermene som Ruter har satt opp ved flere t-bane, trikk og bussholdeplasser har blitt et kjærkomment bidrag til informasjonen du som reisende får om avganger. Ikke bare kan du ha stålkontroll over hvilke avganger som går, og i hvilken rekkefølge, du blir også oppdatert om estimert ankomsttid slik at du vet at du mista bussen du løp for å rekke eller om bussen har blitt forsinka i rush-trafikken.
I tillegg til skjermene på holdeplassene som opplyser om sanntid, kan du få tak i app-er til mobiltelefonen og du kan også se dataene på forskjellige tredjepartsløsninger som f.eks på informasjonsskjermene ved Instiutt for informatikk ved Universitetet i Oslo. At dataene kan vises på mange forskjellige steder er mulig siden Ruter legger ut all ruteinformasjon til fri bruk (noen restriksjoner gjelder) på nettet. Det er dette vi kan benytte oss av når vi skal lage vårt eget sanntidssystem.
Informasjonsskjerm med sanntidsinformasjon
Vårt ferdige produkt vil være et sanntidssystem, ikke ulikt det man kan finne på holdeplassene. Selvsagt blir vårt produkt mindre robust og pålitelig, men i prototypings-fasen er dette helt greit. Skjermen skal vise hvilken linje neste avgang gjelder, hvilket navn det er på denne linjas endestasjon og hvor mange minutter det er til ankomst/avgang. For å få til dette benytter vi oss av en “dum” skjerm og av programvare som skriver teksten til denne. Skjermen vi bruker kobler vi til datamaskinen igjennom et Arduino grensesnitt. Vi bruker datamaskinen for å gjøre det meste av jobben, og vi legger mest mulig kompleksitet hit.
Informasjonsskjermen
Arduino er et veldig bra sted å begynne for å lære elektronikk, og hvordan vi kan kombinere dette med datamaskiner. Arduino har mange pedagogiske resursser tilgjengelig, og er laget for å være enkelt å komme igang med samt fleksibelt nok til å kunne skreddersys med egne komponenter.
Alle komponentene vi trenger følger med i Arduino innførings-pakke (Starter Kit). Vi tar utgangspunkt i oppgave 11, hvor vi skal lage en krystall-kule. Vi benytter oss av skjema-tegningene for å koble en LCD-skjerm til Arduino-brettet.
På Arduinobrettet laster vi opp følgende Sketch:
#include LiquidCrystal lcd(12,11,5,4,3,2); char a; void setup() { Serial.begin(9600); lcd.begin(16,2); lcd.print("Ready for"); lcd.setCursor(0,1); lcd.print("duty!"); } void loop() { while (Serial.available() > 0 ){ a = Serial.read(); if (a == '!') { lcd.clear(); lcd.setCursor(0,0); } else if (a == '?') { lcd.setCursor(0,1); } else { lcd.print(a); } } }
Vi benytter oss av LiquidCrystal biblioteket som følger med Arduino for å gjøre det enklere å skrive til brettet. Vi setter opp en Seriell-forbindelse og skriver en velkomstmelding til LCD-skjermen. Når LCD-skjermen er initialisert er det i hovedsak tre metoder vi benytter: print, clear og setCursor. Førstnevnte skriver en melding til skjermen, clear fjerner all tekst. setCursor benytter vi for å hoppe mellom skjermens to linjer. Metoden setCursor(0,0) setter skrivehodet øverst til venstre, og setCursor(0,1) setter skrivehodet til venstre i nedre rad.
Det er veldig lite kompleksitet på dette nivået, det eneste vi ønsker å gjøre er å kunne skrive, flytte mellom linjene og fjerne all tekst fra skjermen. Så lenge det er data i seriell-bufferet leser vi et tegn, og skriver dette til skjerm med mindre det er et spesialtegn. For dette programmet er “!” og “?” definert til å ha spesielle funksjoner. Utropstegnet fjerner all tekst og resetter skrivehodet til øvre venstre posisjon, spørsmålstegnet flytter skrivehodet til nedre venstre posisjon.
Skrive til skjerm
Når LCD-skjermen er satt opp og Arduino-koden er lastet opp kan man skrive til skjermen. Bruk CoolTerm (kan lastes ned her) og koble denne til Arduinobrettet (la brettet være koblet til via USB porten). Nå kan du skrive tekst som dukker opp på skjermen, samt kontrollere at spesialtegnene fungerer. Fungerer det? Kult, la oss koble oss opp mot Trafikanten/Ruter.
Trafikkdata fra Ruter
Ruter har offentliggjort mye av sin reiseinformasjon, og de har lagt ut sanntidsinformasjonen i tillegg til reiseplanleggeren, avvik og mye annet snasent. Les mer på Ruter sine API-sider
Vi skal benytte oss av to datasett fra Ruter, først må vi finne vår stasjons-ID ved hjelp av en kommaseparert fil med oversikt over alle stasjonene. Denne semi-statiske filen har meta-data som vi trenger for å kunne finne riktig stasjon fra REST JSON sanntids-APIet.
Finne stasjons-ID
For å finne riktig stasjon gjorde jeg en pragmatisk avgjøre om å ikke parse CSV-fila med alle stasjons-IDene. Jeg bare åpnet fila i TextMate (som jeg benytter for å redigere kode), og søkte etter Blindern T som ligger i nærheten av både jobb og hjem. Jeg fant ut Blindern T sin stasjons-ID er: 3010360
Hente og parse trafikkinformasjon
Ved å gå til datapunktet for sanntidsinformasjon ved denne IDen får jeg en JSON strøm/fil tilbake med alle avanger fra denne stasjonen: Blindern datapunkt.
I denne strømmen får vi mange objekter med avganger, og alle avganger har masse data vi kan bruke. JSON strømmen er ikke lett å lese og forstå, men så er den heller ikke laget for mennesker. Jeg benytter programmet VisualJSON for å lese JSON-filer.
I VisualJSON kan vi se hvilke verdier i hver oppføring vi ønsker å ta vare på. DestinationDisplay inneholder navnet på endestasjonen, og LineRef inneholder linjenummeret. Praktisk å ta vare på disse. I tillegg trenger vi å lese ExpectedArrivalTime som har et noe merkelig tidsformat: “/Date(1378070709000+0200)/”.
Se hele implentasjonen? Mot slutten av siden finner du scriptet som henter, bearbeider, mellomlagrer og sender dataene skjermen.
Bearbeiding av datoene
De fleste dataene for hvert avgangsobjekt er klare for visning, men når det gjelder datoene trenger vi å gjøre noen endringer. Formatet vi fikk disse i er vanskelig å forstå for mennesker. Datamaskiner kan enkelt regne ut hvor mange sekunder som har passert siden torsdag 1 January 1970 (tidsformatet Epoch time), men for mennesker er dette vanskeligere. Vi parser derfor epoch formatet til datetime, og i fra dette formatet finder vi time delta (tidsdifferansen) mellom når avgangen er forventet og nå. Når vi tar denne tidsdifferansen og gjør den om til minutter har vi antall minutter til avgangen som vises.
Mellomlagring av data
Jeg har valgt å lage en klasse som inneholder de nødvendige dataene for hver avgang. Objektene vi oppretter av denne klassen legger jeg inn i en liste, hvor jeg henter ut ett og ett element. Når lista er tom går jeg til nett-tjenesten for å hente nye data. Det er fint å slippe å gå til tjenesten hele tiden for å hente data, samtidig må vi også bli oppdatert på eventuelle forsinkelser eller dersom antatt ankomsttid skulle flyttes framover i tid. Jeg syntes at å lagre alle avgangene i en kø for så å hente nye data når denne er tømt er en god løsning på denne utfordringen.
Formatere data for LCD-skjerm
For at dataene skal vises riktig må vi formatere de. Måten jeg har valgt å gjøre dette på er ved å lage setninger for hver linje. Altså først sende en melding om at skjermen må tømmes og resettes, deretter øverste linje med linjenummer og endestasjonsnavn. Når dette er sendt, venter jeg 0.2 sekund før jeg sender linjeskift-kommandoen (“?”), og linje nummer to. Jeg venter 0.2 sekunder mellom hver melding for å ikke mate skjermen med for mye informasjon samtidig. Når all tekst vises lar jeg denne stå i 4 sekunder før jeg går videre til neste avreise.
Implementasjon av programvaren
Nedenfor finner du hele scriptet som henter data fra Ruter, og som sender dette til Arduinobrettet
import urllib2, json, datetime, re, serial, time from pprint import pprint class TrafficInfo: def __init__(self, line_ref, destination, arrival_minutes, monitored, platform): self.line_ref = line_ref self.destination = destination self.arrival_minutes = arrival_minutes self.monitored = monitored self.platform = platform def days_hours_minutes(td): return td.days, td.seconds//3600, (td.seconds//60)%60 def fetchTrafficFromStation(station_id): url_loc = "http://reis.trafikanten.no/reisrest/realtime/getrealtimedata/%s" % station_id response = urllib2.urlopen(url_loc) data = response.read() return data station_id = "3010360" ser = serial.Serial("/dev/tty.usbmodemfa131", 9600) arr = [] while(True): if len(arr) == 0: print "Fetching new data" data = json.loads(fetchTrafficFromStation(station_id)) for el in data: timecode = el["ExpectedArrivalTime"] m = re.split("(\d{13})\+(\d{4})",timecode) dt = datetime.datetime.fromtimestamp(float(m[1])/1000) tn = datetime.datetime.now() d = dt - tn td_human = days_hours_minutes(d) minutes = False if (d.seconds < (3600 - 1)): minutes = td_human[2] ti = TrafficInfo(el["LineRef"], el["DestinationDisplay"], minutes, el["Monitored"], el["DeparturePlatformName"]) arr.append(ti) time.sleep(1) ar = arr.pop(0) time.sleep(1) ser.write("!") time.sleep(0.3) over_str = "%s : %s" % (ar.line_ref,ar.destination) ser.write(over_str) time.sleep(0.3) ser.write("?") time.sleep(0.3) under_str = "%s %s" % (str(ar.arrival_minutes), "Minutes") ser.write(under_str) time.sleep(4)
Du trenger ikke nødvendigvis en LCD-skjerm for å hente trafikkdata fra Ruter, og du trenger ikke nødvendigvis å vise trafikkdata dersom du har en LCD-skjerm. Det er bare fantasien som setter grenser. Hva med for eksempel værdata fra yr.no, eller temperaturen i kjøleskapet? Arduino er et flott sett for å leke med noen av mulighetene elektronikk og informatikk gir deg.
Appropos Arduino: I forrige uke var jeg med på en workshop med Tom Igoe, som har skrevet boken Making things Talk. Jeg har selv ikke lest denne boka, men har hørt at den er en god kilde til kunnskap og inspirasjon. Check it out!
2 thoughts on “Sanntid hjemme: Følg Ruter hjemmefra med Arduino og Python”
Dette var veldig interessant!
En liten kommentar:
På linje 23 i Arduino-koden står det:
} else if (a != ‘?’ || a != ‘!’) {
Det er kanskje tilstrekkelig å skrive
} else {
siden betingelsen (a != ‘?’ || a != ‘!’) alltid er sann.
Så hyggelig at du likte artikkelen. Håper å komme med flere endags-prosjekter snart. Si gjerne ifra dersom du har noen lignende ting, eller ideer. Jeg er alltid åpen for inspirasjon.
Når det gjelder koden, er jeg helt enig. Burde ha tatt en refaktor.Takk for tipset!