If you want a direct access to RATP’s open data API, you’ll need to fill a pdf form and send it to an email address. I tried and never heard back of them. So I used https://api-ratp.pierre-grimaud.fr in order to access real time schedules. Thank-you to Pierre Grimaud for providing this really cool alternative. The documentation of the API is pretty straightforward and it’s only a matter of finding the right station along its slug name. For example:curl -X GET "https://api-ratp.pierre-grimaud.fr/v4/stations/buses/58"
Maybe the most annoying part of using json with golang is that you have to describe the json as a struct (there are other way but they are less golangish). Because I’m lazy, I used https://mholt.github.io/json-to-go/ in order to convert a json output to the struct.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"os/exec"
"time"
)
func main() {
// Structure to store the configuration
// we use a structure so it will be easier to later add support for a configuration file for example
// Check https://api-ratp.pierre-grimaud.fr/v4/ for the parameters
type gobus struct {
mode string // buses, metro,…
line string // Line number or name
slug string // Stop slug: curl -X GET "https://api-ratp.pierre-grimaud.fr/v4/stations/buses/58"
fancy string // Fancy display name if you want
direction string // A or R (Aller or Retour)
wait int // How long do we wait between requests
}
type results struct { // Structure where we store the display and eventual success or fail
success bool
line1 string
line2 string
prev1 string
prev2 string
}
// json structure return by the API
// struct was automatically created from an output using https://mholt.github.io/json-to-go/
type ratp struct {
Result struct {
Schedules []struct {
Message string `json:"message"`
Destination string `json:"destination"`
} `json:"schedules"`
} `json:"result"`
Metadata struct {
Call string `json:"call"`
Date time.Time `json:"date"`
Version int `json:"version"`
} `json:"_metadata"`
}
var conf gobus // URL construction
conf.mode = "buses"
conf.line = "58"
conf.slug = "Didot"
conf.direction = "R"
//url to fetch that contains the line, stop and direction
url := fmt.Sprintf("https://api-ratp.pierre-grimaud.fr/v4/schedules/%s/%s/%s/%s",
conf.mode,
conf.line,
conf.slug,
conf.direction)
for { //let's do an infinite loop
var record ratp //Let's store the decoded JSON in the record variable
var display results //We will construct the result display messages here
display.success = false //Until proven, we failed at getting results
display.line1 = "Error while decoding the json"
display.line2 = "Error while decoding the json"
display.prev1 = "None"
display.prev2 = "None"
req, err := http.NewRequest("GET", url, nil)
if err != nil {
display.success = false
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
display.success = false
} else {
display.success = true
}
if display.success { //We only continue if we succeeded the http request
defer resp.Body.Close()
if resp.StatusCode != 200 { //if we don't get a 200 status code, then something went wrong server side
display.success = false
}
if err := json.NewDecoder(resp.Body).Decode(&record); err != nil { //We might get corrupted
display.success = false //or partial json
}
//If schedules are unavailable (various reasons why the server might return this)
if len(record.Result.Schedules) == 1 && record.Result.Schedules[0].Message == "Schedules unavailable" {
display.success = false
}
//If we have 2 results then we know the answer is ok
//Note: for 2 values, loops are a bit overkill so…
if len(record.Result.Schedules) == 2 {
display.success = true
display.line1 = record.Result.Schedules[0].Message
display.line2 = record.Result.Schedules[1].Message
display.prev1 = display.line1
display.prev2 = display.line2
}
} //display.success == true
c := exec.Command("clear") // we clear the screen
c.Stdout = os.Stdout
c.Run()
/* ===== Message display ===== */
fmt.Print("========== Next Bus [")
fmt.Print(time.Now().Format("15:04:05"))
fmt.Println("] ==========")
if display.success == true {
fmt.Println(display.line1)
fmt.Println(display.line2)
} else {
fmt.Println("Error, will try again in 60s")
fmt.Println("Previous was: ", display.prev1)
fmt.Println("Previous was: ", display.prev2)
} //else
fmt.Println("=========================================")
time.Sleep(time.Second * 60)
} //for
} //main
While the programm is not perfect and can easily be perfected, it was a cool “evening” project done while watching tv. The most important thing is that it fails gracefully if the API is not available.