Prochain train ESP8266 SSD1306 API RATP
Posted 6 yrs ago
Je vous propose de réaliser un objet connecté (IoT Device) qui récupère sur Internet les prochains passages de train à une gare et les affiche sur un petit écran.
Il n’y a pas besoin de beaucoup de composants pour réaliser ce projet. Le principal est une carte « NodeMCU » compatible Arduino (il faut faire une petite manip dans l’IDE) construite autour du composant ESP8266, on peut le trouver autour de 2€ sur les sites chinois. Le deuxième vous l’aurez deviné est l’écran. C’est un écran OLED construit autour du contrôleur SSD1306, on le trouve aussi autour de 2€. On peut s’en sortir pour moins de 5€ de composants.
J’ai crée un petit boitier avec le logiciel Fusion360 que j’ai ensuite imprimé avec une imprimante 3D. Vous pouvez télécharger les fichiers sur Thingiverse.
Le code est lui disponible sur mon Git.
Pour récupérer les prochains passages à une gare j’ai utilisé une API crée par Pierre Grimaud, RATP API REST.
Commençons par le cablage du montage
Écran OLED | NodeMCU |
---|---|
VCC | 3.3V |
GND | GND |
SCL | D1 |
SDA | D2 |
Passons maintenant au code.
Le programme envoie une requête de type GET vers l’API et récupère un objet JSON.
Faisons un tour sur l’API.
Cliquons sur le mode « Sandbox » afin de tester nos requêtes. Dans « type » on choisit son type de transport (rers, metros, tramways, noctiliens, bus), dans « code » le numéro ou lettre de la ligne ,dans « station » le nom de la station (pour les noms composés il faut remplacer les espaces par des « + » ex: denfert+rochereau) et enfin dans « way » la direction (A, R ou A+R).
On récupère un objet JSON de ce type. Une fois que vous avez trouvé la requête qui vous convient, conservez la.
{
"result": {
"schedules": [
{
"code": "EBOI",
"message": "Train à quai",
"destination": "Aeroport Charles de Gaulle 2 TGV"
},
{
"code": "EVAL",
"message": "19:15",
"destination": "Aeroport Charles de Gaulle 2 TGV"
},
{
"code": "IDRE",
"message": "19:18",
"destination": "Mitry-Claye"
},
{
"code": "ILOU",
"message": "19:22",
"destination": "Mitry-Claye"
},
{
"code": "EBOI",
"message": "19:23",
"destination": "Aeroport Charles de Gaulle 2 TGV"
},
{
"code": "EVAL",
"message": "19:26",
"destination": "Aeroport Charles de Gaulle 2 TGV"
}
]
},
"_metadata": {
"call": "GET /schedules/rers/B/denfert+rochereau/A",
"date": "2018-07-03T19:13:34+02:00",
"version": 3
}
}
Il va falloir "parser" le JSON pour l'utiliser facilement en C++, pour cela j'ai utilisé la bibliothèque ArduinoJSON.
Voici le code pour l'Arduino n'oubliez pas d'adapter votre requête et vos identifiants WiFi.
//#define DEBUG
// Json Parser
#include <ArduinoJson.h>
//Wifi
#include <ESP8266WiFi.h>
//Include the SSL client
#include <WiFiClientSecure.h>
// OLED Screen
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <ESP_SSD1306.h>
#define OLED_RESET 16
int yPos = 1;
ESP_SSD1306 display(OLED_RESET); // FOR I2C
char ssid[] = "Your_SSID"; // your network SSID (name)
char password[] = "Your_WPA_PASS"; // your network key
//Add a SSL client
WiFiClientSecure client;
// Address of the API host server
char hostAPI[] = "api-ratp.pierre-grimaud.fr";
//Request send to the API Server
char getRequest[] = "/v3/schedules/rers/B/denfert+rochereau/A+R?_format=json";
//Another example request try directly on the website they're swagger so you can directly test your requests
//char getRequest[] = "/v3/schedules/metros/14/bercy/A+R";
long checkDueTime;
#define checkDelay 60000 // 60 x 1000 (1 minute)
#define responsesfromAPI 11 // Number of trains from API response to parse
void setup() {
display.begin(SSD1306_SWITCHCAPVCC);
#ifdef DEBUG
Serial.begin(115200);
#endif
printIntro();
// Set WiFi to station mode and disconnect from an AP if it was Previously
// connected
WiFi.mode(WIFI_STA);
WiFi.disconnect();
delay(100);
// Attempt to connect to Wifi network:
Serial.print("Connecting Wifi: ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
#ifdef DEBUG
Serial.print(".");
#endif
printErrorAPI("Connecting", "WiFi");
}
IPAddress ip = WiFi.localIP();
display.clearDisplay();
display.setTextSize(2);
display.setCursor(35, 3);
display.println("Wifi Ok");
display.setTextSize(1);
display.setCursor(2, 30);
display.print("IP: ");
display.println(ip);
display.display();
delay(500);
#ifdef DEBUG
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(ip);
#endif
}
void loop() {
long now = millis();
if (now >= checkDueTime) {
parseAPI(getRequest);
checkDueTime = now + checkDelay;
}
}
void printIntro() {
display.clearDisplay();
display.setTextSize(3);
display.setTextColor(WHITE);
display.setCursor(35, 1);
display.println("HyDz");
display.setTextSize(2);
display.setCursor(10, 40);
display.println("Train API");
display.display();
delay(500);
}
void parseAPI(String request) {
String headers = "";
String body = "";
bool finishedHeaders = false;
bool currentLineIsBlank = true;
bool gotResponse = false;
long now;
if (client.connect(hostAPI, 443)) {
#ifdef DEBUG
Serial.println("connected");
Serial.println(request);
#endif
printErrorAPI("PARSE API", " ");
client.println("GET " + request + " HTTP/1.1");
client.print("Host: "); client.println(hostAPI);
//client.println("User-Agent: nodeMCU_by_HyDz");
client.println("");
now = millis();
// checking the timeout
while (millis() - now < 1500) {
while (client.available()) {
char c = client.read();
if (finishedHeaders) {
body = body + c;
} else {
if (currentLineIsBlank && c == '\n') {
finishedHeaders = true;
}
else {
headers = headers + c;
}
}
if (c == '\n') {
currentLineIsBlank = true;
} else if (c != '\r') {
currentLineIsBlank = false;
}
gotResponse = true;
}
}
if (gotResponse) {
display.clearDisplay();
display.setTextSize(2);
body.remove(0, 3);
#ifdef DEBUG
Serial.println(body);
#endif
const size_t bufferSize = JSON_ARRAY_SIZE(10) + JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2) + 11 * JSON_OBJECT_SIZE(3) + 780;
DynamicJsonBuffer jsonBuffer(bufferSize);
JsonObject& root = jsonBuffer.parseObject(body);
if (root.success()) {
JsonObject& result_code = root["result"];
const char* resulted_code = result_code["code"];
String error_code = result_code["code"].as<String>();
#ifdef DEBUG
Serial.println(resulted_code);
#endif
if (error_code.length() > 2) { // Check if they're an error code Code 500 = No Trains
printErrorAPI("Erreur", error_code);
return;
}
JsonArray& result_schedules = root["result"]["schedules"];
for (int i = 0; i < responsesfromAPI; i++) {
JsonObject& resulted_schedules = result_schedules[i];
const char* result_schedules_code = resulted_schedules["code"]; // "ECCO"
const char* result_schedules_message = resulted_schedules["message"]; // "Train à quai"
const char* result_schedules_destination = resulted_schedules["destination"]; // "Aeroport Charles de Gaulle 2 TGV"
#ifdef DEBUG
Serial.println(result_schedules_code);
Serial.println(result_schedules_message);
Serial.println(result_schedules_destination);
#endif
String resulted_message = resulted_schedules["message"].as<String>();
if ( resulted_message.length() <= 6) { // Only Print train with a time and skip No Stop trains
if (yPos < 61) {
#ifdef DEBUG
Serial.print("yPos: ");
Serial.println(yPos);
Serial.println(result_schedules_code);
Serial.println(result_schedules_message);
Serial.println(result_schedules_destination);
#endif
printTrain(result_schedules_message, result_schedules_destination, yPos);
yPos += 16;
} else {
yPos = 1;
return;
}
}
}
} else {
printErrorAPI("API", body);
return;
}
}
} else {
printErrorAPI("API CONNECT", "ERROR");
return;
}
}
void printTrain(const char* horaire, const char* dest, int yCurs) {
String destination(dest);
destination.remove(5);
#ifdef DEBUG
Serial.println(destination);
#endif
display.setCursor(1, yCurs);
display.println(horaire);
display.setCursor(65, yCurs);
display.println(destination);
display.display();
delay(500);
}
void printErrorAPI(String error1, String error2) {
#ifdef DEBUG
Serial.println(error1);
Serial.println(error2);
#endif
display.clearDisplay();
display.setTextSize(2);
display.setCursor(1, 1);
display.println(error1);
display.setCursor(1, 18);
display.println(error2);
display.display();
delay(500);
}