Après pas mal de temps sans poster, 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 GitHub.
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.
Rien de bien compliqué.
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 le site de 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": "ECCI",
"message": "15:44",
"destination": "Aeroport Charles de Gaulle 2 TGV"
},
{
"code": "AIDA",
"message": "15:47",
"destination": "Gare du Nord"
},
{
"code": "ILUS",
"message": "15:53",
"destination": "Mitry-Claye"
},
{
"code": "ECCI",
"message": "15:58",
"destination": "Aeroport Charles de Gaulle 2 TGV"
},
{
"code": "AIDA",
"message": "16:02",
"destination": "Gare du Nord"
},
{
"code": "ILAN",
"message": "16:08",
"destination": "Mitry-Claye"
},
{
"code": "STOC",
"message": "Train à quai",
"destination": "Robinson"
},
{
"code": "PLOM",
"message": "15:44",
"destination": "Saint-Remy-les-Chevreuse"
},
{
"code": "KALI",
"message": "15:49",
"destination": "Massy-Palaiseau"
},
{
"code": "STOC",
"message": "15:52",
"destination": "Robinson"
},
{
"code": "PLOI",
"message": "16:02",
"destination": "Saint-Remy-les-Chevreuse"
},
{
"code": "KALI",
"message": "16:03",
"destination": "Massy-Palaiseau"
}
]
},
"_metadata": {
"call": "GET /schedules/rers/B/denfert+rochereau/A+R",
"date": "2018-03-18T15:41:20+01: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
//Wifi
#include
//Include the SSL client
#include
// OLED Screen
#include
#include
#include
#include
#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();
#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();
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);
}
Si vous trouvez l’article trop succin et avez des questions, n’hésitez pas à utiliser les commentaires.
This is top-notch. It has been simply incredibly generous with you to provide openly what exactly many individuals would’ve marketed for an ebook to end up making some cash for their end, primarily given that you could have tried it in the event you wanted.
Outstanding! I simply wanted to write down a quick word to say thanks to you for those wonderful tips and hints you are showing on this site.
This is terrific! I ‘d mention that most of us visitors are endowed to exist in a fabulous place with very many wonderful individuals with very helpful things.
How incredible! Your good knowledge and kindness in playing with all the pieces were very useful. I don’t know what I would have done if I had not encountered such a step like this.
This is top-notch. I am commenting to let you know what a terrific experience my daughter enjoyed reading through your web page. She noticed a wide variety of pieces, with the inclusion of what it is like to have an awesome helping style to have the rest without hassle grasp some grueling matters.
That’s great! I wish to show thanks to you just for bailing me out of this particular trouble.As a result of checking through the net and meeting techniques that were not productive, I thought my life was done.