Archives de catégorie : Projets

La programmation du Raspberry Pi en Python

Le langage Python

Le Raspberry Pi a aussi été conçu pour permettre d’apprendre la programmation.

Le langage principal pour programmer sur Raspberry Pi est Python.

J’ai dû voir comment mettre en place mon environnement de programmation en Python sur mon Raspberry Pi pour réaliser mon projet.

Python est un langage de programmation qui est utilisé dans différents domaines (application, web, …) Il est souvent comparé à Perl, Ruby ou encore Java. D’après le site Raspberry Pi, les principaux points forts de Python sont :

  • Langage adapté aux débutants (comme moi 😉 )
  • Les grandes possibilités du langage. Il va aussi bien être adapté aux petits projets qu’aux gros projets
  • Peut-être utilisée sur différents types d’ordinateurs
  • Stable, langage qui est assez vieux et qui a fait ses preuves
  • Simple à comprendre

L’environnement de travail Python sur le Raspberry Pi

Pour programmer en Python sur Raspberry Pi, on peut lancer le programme Thonny Python IDE.

Voici à quoi ressemble l’IDE avec mon premier programme d’exemple (décrit dans la partie suivante).

Mes premiers essais en python

Pour découvrir Python j’ai essayé de réécrire en python les programmes d’exemples que j’ai codé quand j’ai découvert java lors de mon travail personnel sur les langages de programmation : ces exemples étaient l’écriture en java des exemple de programmes Cobol, Fortran que j’avais lus quand j’avais regardé « histoire des langages de programmation ».

Multiplication de deux nombres

Le premier programme faisait la multiplication de deux nombres ;

Voici le résultat en python.

# lit les nombres
input1 = input("Enter first number:" );
num1 = int(input1)
input2 = input("Enter Second number:");
num2 = int(input2)

# ça fait le calcul
result = num1 * num2;

# affiche le resultat
print("result is = " , result);

Comme différence avec Java, on voit que :

  • le programme est plus petit à écrire.
  • On n’a pas besoin de dire le type des variables
  • Il est plus facile d’afficher les résultats ou de demander des saisies de l’utilisateur
  • Pour les commentaires on utilise « # » à la place de « // »
  • On a dû faire attention à convertir la saisie (input1) en nombre (num1)

PGCD de deux nombres

Le second programme définissait une fonction qui pouvait être réutilisée. La fonction calculait le PGCD de deux nombres. Voici ce que cela donne en python :

def calculePGCD(na,nb):
   ia = na
   ib = nb

   while ib !=0 :
      itemp = ia
      ia = ib
      ib = itemp % ib
   return ia

a = 55
b = 33
resultat =calculePGCD(a,b)
print("le pgcd de",a,"et",b,"est", resultat)

Comme différence avec Java :

  • Pour utiliser une fonction on utilise « def»
  • Il faut faire attention aux indentations ; Python se sert des indentations pour structurer le programme (intérieur de la fonction, intérieur de la boucle while). En Java on utilisait les « { } »
  • Ici aussi, le code est très compact à écrire.

Reduction de fractions

Le troisième programme utilisait la fonction PGCD pour réduire une fraction. Voici ce que cela donne en Python :

#lit les nombres.
inputNumerateur = input ("Entre le numérateur:")
numerateur = int(inputNumerateur)
inputDenominateur = input ("Entre le dénominateur:")
denominateur = int(inputDenominateur)
print ("la saisie est :",numerateur,"/",denominateur)

# calcul du pgcd en utilisant le programme d'exemple
pgcd = calculePGCD (numerateur , denominateur)

#calcul du nouveu numérateur et du nouveau dénominateur
numerateur = (int) (numerateur / pgcd)
denominateur = (int) (denominateur / pgcd)

# affiche le resultat
print ("le resultat est :",numerateur,"/",denominateur)

Comme point particulier, on a dû faire attention à ce que le résultat des nouveaux numérateurs et dénominateurs soit bien un entier (par défaut, on obtenait un nombre décimal).

Conclusion

Python a l’air plus facile à manipuler pour apprendre que ce que j’avais dû faire en java (par exemple pour le jeu Pong du Tutorial que j’avais repris).

Je vais maintenant devoir voir comment utiliser Python pour les différentes tâches que je dois faire pour le projet (lire les capteurs, envoyer les données à Salesforce, etc…).

La conception du Raspberry Car – L’ordinateur

1.         Raspberry Pi

Comme système au cœur de la collecte et de l’envoi des données, j’avais besoin d’un ordinateur simple auquel on puisse ajouter des composants additionnels et les programmer facilement.

J’ai trouvé sur Internet que beaucoup de projets semblables ont été fait sur Raspberry Pi.

Le Raspberry pi est un ordinateur de la taille d’une carte de crédit que l’on peut brancher à un écran et utiliser comme un ordinateur standard. Sa petite taille, et son prix intéressant (50 euros !) font du Raspberry pi un produit idéal pour tester différentes choses. Pour sa taille il ne faut pas s’attendre à des performances incroyables comme sur mon ordinateur de jeu, mais pour l’objectif de ce travail personnel c’est largement suffisant.

Le Raspberry pi ne dispose pas d’un disque dur interne (cela augmenterait grandement sa taille), mais stocke ses données sur une carte SD.

Par défaut, le Raspberry pi, est nu : il est vendu sans accessoires.

Pour pouvoir le programmer (avant de l’installer dans la voiture), j’ai donc dû le compléter :

  • Avec une carte micro SD
  • Un câble d’alimentation micro USB standard relié à un chargeur
  • Un câble HDMI afin de connecter le Raspberry pi un écran.
  • Un clavier et une souris standard (sur le port USB)

Ensuite il a fallu installer le système d’exploitation :

  • La première étape est de préparer la carte micro SD en y installant un système d’exploitation. Pour faire simple, j’ai choisi le Linux Raspbian (Debian) que l’on peut télécharger directement depuis le site Internet de Raspberry pi. Une fois l’image téléchargée, on peut l’installer sur votre carte micro SD en utilisant le logiciel Win32DiskImager.  La procédure est décrite ici : https://projects.raspberrypi.org/en/projects/raspberry-pi-setting-up/3 .
  • Une fois la carte micro SD préparée, il suffit de l’insérer dans le Raspberry pi pour faire fonctionner automatiquement le système.

C’est dans cette configuration que je travaille à la maison pour programmer le Raspberry Pi (la petite carte entre le clavier et l’écran).

2.         Le Raspberry Pi en mode mobile

Mais pour l’utiliser dans la voiture :

  • On ne pourra pas utiliser un grand écran et le brancher sur une prise de courant,
  • On ne va pas utiliser un clavier et une souris avec des câbles car ce n’est pas pratique dans la voiture
  • On n’aura pas de prise de courant 220V pour alimenter le transformateur USB de l’ordinateur Raspberry PI. On pourrait le brancher sur l’adaptateur USB sur la prise allume-cigare, mais dès qu’on couperait le contact de la voiture, le Raspberry Pi s’étendrait aussi

La solution est de compléter le Raspberry PI par d’autres éléments :

  • Un petit écran tactile
  • Un clavier avec trackpad buetooth pour les manipulations qu’on ne peut pas faire avec l’écran tactile
  • Pour l’alimentation, une power bank de forte capacité.

Pour le petit écran tactile j’ai pris un écran de 5 pouces qui se branche directement sur le Raspberry Pi de la société Waveshare (800*480 pixels) pour 37 €. L’écran se branche par-dessus le Raspberry Pi par le connecteur GPIO 40 broches pour l’alimentation et par un adaptateur HDMI (la petite pièce bleue de la première photo).

Pour le clavier j’ai trouvé le clavier Logitech Touchpad K400 Plus, qui est léger et ne prend pas trop de place (35€).

Pour la powerbank, j’ai pris une powerbank 5V, 2.4A et une capacité de20100mAh (elle peut recharger un ordinateur portable en une heure ! (Environ 70€, c’est le composant le plus cher de la configuration 😉 )

B.        Les interfaces additionnelles

Pour être capable de réaliser le programme qui va interroger la voiture et connaître la position GPS, il faut étendre le Raspberry Pi avec deux composants :

1.         Composant GPS

La technologie GPS utilise des satellites en orbite autour de la Terre qui permettent de déterminer la localisation géographique d’un capteur avec les coordonnées en longitude, latitude et altitude.

Je vais brancher un tel capteur sur mon Raspberry Pi.

J’ai choisi le composant “GSM/GPRS/GNSS/Bluetooth HAT GPS Module Expansion Board Based on SIM868 for Raspberry Pi” de la société Waveshare (pour 40€). Il inclut aussi une carte GPRS pour se connecter au réseau mobile (voir la partie suivante « accès Internet »).

2.         La connexion OBD à la voiture

Les voitures modernes disposent d’une prise OBD. Ces trois lettres  » OBD  » veulent dire :  » On Board Diagnostics  » (Diagnostic embarqué à bord). Cette fameuse prise OBD permet d’accéder à toutes sortes d’informations en interrogeant les calculateurs électroniques de la voiture.

J’ai besoin d’un connecteur pour relier le Raspberry PI à la prise OBD de la voiture.

Il y en a de différents types :

  • Des cables OBD-USB que l’on connectera sur les ports USB du Raspberry Pi
  • Des modules OBD Bluetooth que l’on peut coupler au Raspberry Pi

Pour faire simple et ne pas avoir de soucis avec le Bluetooth, j’ai pris la première option.

Le câble que j’ai acheté est le ScanTool OBDLink SX USB (30€)

C.        L’accès Internet

Le Raspberry Pi doit se connecter à internet pour envoyer les données à Salesforce.  Par défaut le Raspberry Pi peut se connecter à un réseau Wifi.

Dans un premier temps nous avons fait les teste en utilisant le mode Tethering de mon téléphone portable, ou pour ne pas être dépendant de celui-ci, une borne wifi/4G de TP-Link avec un abonnement data spécifique.

Dans un second temps, j’essayerai d’utiliser le mode GSM Data de la carte GPS que j’ai acheté pour la géolocalisation, en lui insérant la carte SIM).

D.        Le tout assemblé

Et voici ce que donne le tout assemblé, une fois connecté à la voiture :

  • Le Raspberry Pi est connecté sous l’écran, comme expliqué dans la présentation de l’écran
  • Le câble rouge est relié sous le volant au bord OBD de la voiture.
  • Devant le Raspberry, on voit la carte GPS (à gauche, avec la LED allumée) relié au Raspberry Pi et au capteur de signal GPS (à droite)

Raspberry Cars : RC 2.0 !

Pour mon travail personnel de l’année 2019-2020 au Lycée Ermesinde, je vais étendre mon travail personnel de l’année dernière, avec de nouvelles technologies pour le rendre plus performant et avancé.

Le travail personnel de l’année dernière consistait à connecter le site Salesforce avec une voiture Mercedes pour recevoir des donnés sur la voiture en fonctionnement. Par exemple la position de la voiture, sa vitesse, le niveau de carburant. Avec ces informations, on a eu l’idée de faire une course virtuelle où des personnes peuvent aller d’un point de départ à un point d’arrivée, à des dates différentes, et par la suite on peut comparer les trajets entre eux.

Le projet de l’année dernière avait comme limitations que les données passaient par les serveurs de Mercedes : Les données de la voiture étaient lues par une application Mercedes sur le téléphone mobile puis envoyée sur le serveur Mercedes ; Ensuite, un programme exécuté chez Salesforce allait se connecter automatiquement au serveur Mercedes pour demander les données de la voiture et les enregistrer dans la base de données de Salesforce.

En résumé, les limitations étaient les suivantes :

  • On avait besoin d’avoir le téléphone dans la voiture
  • Ceci fonctionnait juste sur les voitures Mercedes
  • Pour que Salesforce se connecte aux API du serveur cloud de Mercedes il fallait un contrat avec Mercedes et une fois la phase de développement terminée il faut payer un abonnement
  • On ne pouvait pas facilement se connecter très souvent du serveur Salesforce au serveur Mercedes (on a fait toutes les 10min.)
  • L’application Mercedes n’enregistrait pas les donnés à une haute fréquence, on voudrait enregistrer toute les 2-10 secondes par exemple pour avoir une course plus dynamique

Ce que je veux réaliser cette année c’est :

  • Un petit ordinateur autonome dans la voiture,
  • Fonctionnant avec toutes les marques de voiture,
  • Ne nécessitant pas d’abonnement particulier,
  • Enregistrant de la même manière la position de la voiture, la vitesse, le niveau de carburant, l’état des pneus et d’autres paramètres,
  • Les envoyant à Salesforce le plus souvent possible.

Ce travail personnel va donc nécessiter différents groupes de tâches :

Voici la suite d’articles décrivant les différentes étapes du projet  :

 

Cars and Clouds : pour conclure !

Pendant ce travail personnel d’interfaçage entre les serveurs Cloud de Salesforce et Mercedes pour simuler une course de voiture,  j’ai beaucoup appris de nouvelles choses :

  • J’ai appris comment aller chercher et manipuler des données dans des différents serveurs : Salesforce et Mercedes et les faire travailler ensemble.
  • J’ai travaillé avec plusieurs langages de programmation (Apex, Javascript, SOQL, HTML) et des techniques comme JSON pour les fichiers
  • J’ai découvert comment stocker des données dans une base de données (avec salesforce)
  • J’ai découvert un mécanisme pour rendre sûrs les échanges entre les serveurs (token, etc.)

Ce travail personnel était aussi très divers :

  • Il y avait souvent des phases compliquées où il y avait des problèmes dans les programmes ou dans le serveur avec les données. Même en s’inspirant des exemples trouvés, cela n’allait pas toujours.
  • Pour moi la partie la plus compliquée était des programmer toute la partie avec les voitures et les connections, et aussi celle pour sauvegarder les données automatiquement (toutes les cinq minutes).
  • La partie la plus amusante était celle de faire la carte. C’est aussi celle où j’ai eu le plaisir de voir que c’était enfin réussi !
  • J’ai dû découvrir chaque partie, technique ou outil, mais aussi comment les faire aller ensemble. C’est deux choses différentes à faire.
  • La partie plus difficile est de trouver l’idée de début, l’objectif, et de trouver un chemin pour y arriver. En plus avec les idées qu’on a et qui ne marche pas toujours du premier coup… cela n’aide pas.
  • Je dis ici merci à mon père qui m’a accompagné au long de ce travail personnel pour regarder ces sujets les uns après les autres.

Pendant ce travail personnel, j’ai aussi fait un stage chez un grand garage à Luxembourg (Konz, qui vend des Land Rover et des Jaguar). Dans ce garage j’ai appris un peu plus en détails les serveurs du garage. Il y avait deux serveurs que j’ai vu : sur le premier il y avait les programmes et les documents. Et un autre avec les informations des employés pour gérer les accès. C’est encore un autre aspect de l’informatique que j’avais un peu déjà vu au lycée. C’était un stage très intéressant car il allait très bien avec mon projet personnel, devenir informaticien, et avec l’autre sujet que j’aime bien, les voitures (pendant le stage j’ai aussi fait des travaux à propos des voitures). Je dis merci aux personnes du stage pour m’avoir accueilli et donné plus d’idées sur ce deuxième sujet.

Dans ce travail personnel, cela a aussi été intéressant de mélanger ces deux sujets : informatique et automobile.

Plus tard j’aimerais vraiment faire quelque chose qui mélange les deux thèmes. Surtout qu’il y a de plus en plus d’informatique dans les voitures, et pleins de nouveaux sujets : les voitures qui se conduisent seules, les voitures électriques qui ont besoin de plus d’ordinateurs, les systèmes entre le conducteur et la voiture, les écrans et les commandes, … Tout un univers à découvrir et à apprendre !

Le Jeu (2/2) : Afficher le déroulement de la compétition des voitures d’une course fictive

L’objectif du projet était de simuler une ‘course’ pour des voitures su un même trajet, mais roulant à des dates différentes.

Attaquons-nous à la dernière étape : la course !

1.         La définition de la course

Si les voitures ne partent pas à la même heure, on doit pouvoir trouver comment comparer leur avancée dans la course par rapport à leur date de départ.

Une course dans notre jeu sera d’aller d’une place à l’autre le plus vite possible.

On va créer un objet Car Race qui mémorise :

  • le nom de la course
  • le point de départ
  • le point d’arrivée

Et pour chaque participant, un objet Racer qui va mémoriser

  • la course
  • la voiture
  • la date/heure de début
  • la date/heure de fin
  • la position enregistrée de début (car status)
  • la position enregistrée de fin (car status)

Voici la configuration des deux objets dans l’Object Manager :

2.         Afficher la compétition

Maintenant nous pouvons comparer le parcours complet des participants même s’ils partent à des dates différentes. On va afficher avec un composant mis sur l’objet Course la position des participants à x minutes, x étant saisi par l’utilisateur (10’ après le départ fictif, 20’ après le départ, etc.).

a)              Le service coté serveur

On a besoin d’un code Apex qui va calculer la position des participants après x minutes de course.

public class CarRaceController {

@AuraEnabled(Cacheable=true)
public static Car_Status__c[] getRacePositions(ID raceID, Integer chronoInMinutes) {
// on retrouve la liste des participants
List<Racer__c> lRacers = [
SELECT
Id,
Car_Race__c,
Car__c,
Start_Date__c
FROM Racer__c
where Car_Race__c =:raceID
];

// le tableau où on stocke les position qu'on a trouvées.
List<Car_Status__c> knowPositions = new List<Car_Status__c>(); 

// pour chaque participant, on cherche sa dernière position à cette minute de la course
for (Racer__c racer : lRacers) {
// calcule l'heure pour se participant
DateTime positionTime =  racer.Start_Date__c;
positionTime = positionTime.addMinutes(chronoInMinutes);
// recherche la dernière position connue pour cette heure
List<Car_Status__c> carPosition = [
SELECT
Id,
Location__Latitude__s,
Location__Longitude__s,
Requested_On__c,
Car__c,
Car__r.Name
FROM Car_Status__c
WHERE Car__c = :racer.Car__c
AND Requested_On__c >= :racer.Start_Date__c
AND Requested_On__c <= :positionTime
ORDER BY Requested_On__c DESC
LIMIT 1
];

// si on a trouvé une position, on la met de coté
if (carPosition.size()>0) {
knowPositions.add(carPosition[0]);
}
}

// on renvoie les positions qu'on a trouvée
return knowPositions;
}

}

b)              Le composant

Comme d’habitude on dit où il doit s’afficher : sur les pages des objets Courses (Car Race).

<?xml version="1.0" encoding="UTF-8"?>

<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="RaceCarPositionsMap">

<apiVersion>45.0</apiVersion>
<isExposed>true</isExposed>

<targets>
<target>lightning__RecordPage</target>
</targets>

<targetConfigs>
<targetConfig targets="lightning__RecordPage">
<objects>
<object>Car_Race__c</object>
</objects>

</targetConfig>
</targetConfigs>

</LightningComponentBundle>

La partie Html est un peu plus compliquée : elle a un champ inputMinutes pour saisir le nombre de minutes que l’on veut. Quand ce champ est modifié avec la valeur x, le code javascript minutesChangeHandler est appelé pour demander à Salesforce les positions à x minutes. La carte fonctionne sur le même mode que celle qu’on avait faite dans la partie précédente : elle affiche les marqueurs mapMarkers.

<template>
<article class="slds-card">

<div class="slds-m-around_medium">
<p>Race Id at {recordId}</p>
<p>Race Info of {raceName}</p>
<p>Position at {currentRaceMinutes}</p>
</div>

<lightning-input
name="inputMinutes" label="Minutes"
type="number"
value={currentRaceMinutes}
step="5" max="1000" min="0"
onchange={minutesChangeHandler}>
</lightning-input>

<div class="slds-m-around_medium">
<lightning-map
map-markers={mapMarkers}
zoom-level="11"
markers-title="Positions"
>
</lightning-map>
</div>
</article>
</template>

Le code Java script est fait sur le même modèle que le précédent, et va charger les positions des voitures à x minutes en appelant le code Apex getRacePositions avec l’id de la course et le nombre de minutes après le départ.

On fait le mapping deux fois :

  • une fois quand la page se charge
@wire(getRacePositions, { raceID :  '$recordId',  chronoInMinutes : '$currentRaceMinutes'  })
  • et une fois quand les minutes sont changées.
getRacePositions( { raceID :  this.recordId,  chronoInMinutes : this.currentRaceMinutes})

Le code pour créer les marqueurs ressemble à celui du composant précédent

this.mapMarkers = result.data.map(carPosition => {
const CarName = carPosition.Car__r.Name;
const Latitude = carPosition.Location__Latitude__s;
const Longitude = carPosition.Location__Longitude__s;

return {
location: { Latitude, Longitude },
title: CarName,
description: 'Coords: ${Latitude}, ${Longitude}',
icon: 'utility:animal_and_nature'
};

});

Voici le code complet javascript :

import { LightningElement, api, wire, track } from 'lwc';
import { getRecord , getFieldValue  } from 'lightning/uiRecordApi';
import getRacePositions from '@salesforce/apex/CarRaceController.getRacePositions';
import RACE_NAME from '@salesforce/schema/Car_Race__c.Name';
const raceFields = [
RACE_NAME
];

export default class RaceCarPositionsMap extends LightningElement {
@api recordId;
@wire(getRecord, { recordId: '$recordId', fields: raceFields })
raceInformation;

// les minutes où on se trouve (30 pour le test)
@track currentRaceMinutes = 0;

// les marqueurs pour la carte
@track mapMarkers = [];

 // les positions
@track carsPositions;

get raceName() {
return getFieldValue(this.raceInformation.data, RACE_NAME);
}

@wire(getRacePositions, { raceID :  '$recordId',  chronoInMinutes : '$currentRaceMinutes'  })
loadCar(result) {
if (result.data) {
this.carsPositions = result.data;
this.mapMarkers = result.data.map(carPosition => {
const CarName = carPosition.Car__r.Name;
const Latitude = carPosition.Location__Latitude__s;
const Longitude = carPosition.Location__Longitude__s;
return {
location: { Latitude, Longitude },
title: CarName,
description: 'Coords: ${Latitude}, ${Longitude}',
icon: 'utility:animal_and_nature'
};
});
}
}

minutesChangeHandler(event) {
this.currentRaceMinutes = event.target.value;
this.refreshMapForNewMinutes();
}

refreshMapForNewMinutes() {
getRacePositions( { raceID :  this.recordId,  chronoInMinutes : this.currentRaceMinutes})
.then(result => {
if (result.data) {
this.carsPositions = result.data;
this.mapMarkers = result.data.map(carPosition => {
const CarName = carPosition.Car__r.Name;
const Latitude = carPosition.Location__Latitude__s;
const Longitude = carPosition.Location__Longitude__s;
return {
location: { Latitude, Longitude },
title: CarName,
description: 'Coords: ${Latitude}, ${Longitude}',
icon: 'utility:animal_and_nature'
};
});
}
})
.catch(error => {
alert('Error '+JSON.stringify(error));
this.checkIfTimeEntryCanBeCreated();
});
}
}

 

c)              Le résultat

Une fois le composant installé dans la page des courses, voici ce que cela donne pour différentes minutes de course (85’, 90’).

Maintenant si on fait bouger les minutes, on voit les marqueurs des voitures se déplacer ! C’était l’objectif de cette partie ‘course virtuelle’ !

B.        Création de l’application Car Races

1.         Configuration

Pour accéder facilement à tous les écrans et objets de notre jeu, on peut créer « une application ».

Cela permet aux utilisateurs d’arriver plus facilement aux écrans concernant les voitures, la connexion, les courses.

Dans le menu Setup on choisit : App / App Manager puis on clique « New Lightning App ». On lui donne un nom et on choisit ce qu’elle doit afficher.

2.         Utilisation

Pour aller dans la nouvelle application, on clique en haut à gauche sur l’icône avec les neuf petits carrés, on choisit « Car Races » …

… et quand on est dans l’application, on ne voit dans la barre d’onglets que les objets configurés. C’est plus facile pour s’y retrouver.

Par exemple pour l’accès direct à la liste des voitures

Ou voir la carte des voitures avec leurs dernières positions enregistrées sur la home page

Ou encore … … voir une course !

 

Le Jeu (1/2) : Afficher la position des voitures en temps réel

Pour cette première partie du jeu, on va créer un composant qui va afficher dans une page Salesforce toutes les voitures sur une carte. A chaque fois qu’on regarde la carte, on voit si la voiture a bougé.

Pour cela je me suis inspiré du code de l’application « Ours » du cours Salesforce qui affichait une carte des Ours d’un Parc Naturel.

2.         Le composant

Comme avant, on doit faire un fichier de description pour dire où le composant s’affiche

<?xml version="1.0" encoding="UTF-8"?>

<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="CarInfo">
<apiVersion>45.0</apiVersion>

<isExposed>true</isExposed>

<targets>
<target>lightning__RecordPage</target>
</targets>

<targetConfigs>
<targetConfig targets="lightning__RecordPage">
<objects>
<object>Car__c</object>
</objects>
</targetConfig>
</targetConfigs>

</LightningComponentBundle>

Le code HTML du composant est simple car il y a dans salesforce un composant tout prêt : lightning-map, à qui on donne une liste d’objet à placer sur la carte : mapMarkers.

<template>

<article class="slds-card">
<lightning-map
map-markers={mapMarkers}
zoom-level="11"
markers-title="Cars">
</lightning-map>
</article>

</template>

Le code javascript du composant va juste

  • charger les voitures depuis salesforce en appelant un code Apex : getAllCars
  • transformer les voitures en marqueurs pour la carte : data.map
import { LightningElement, track, wire } from 'lwc';
import getAllCars from '@salesforce/apex/CarController.getAllCars';

export default class CarMap extends LightningElement {

@track mapMarkers = [];
@track cars;

@wire(getAllCars, {})
loadCars(result) {
if (result.data) {
this.cars = result.data;
this.mapMarkers = result.data.map(car => {
const Latitude = car.Last_Position__Latitude__s;
const Longitude = car.Last_Position__Longitude__s;
return {
location: { Latitude, Longitude },
title: car.Name,
description: 'Coords: ${Latitude}, ${Longitude}',
icon: 'utility:animal_and_nature'
};
});
}

}}

Le code APEX qui renvoie les voitures est juste une requête SOQL vers la base Salesforce pour savoir la dernière position des voitures.

@AuraEnabled(Cacheable=true)

public static Car__c[] getAllCars() {
return [SELECT Id, Name,
Last_Position__longitude__s, Last_Position__latitude__s
FROM Car__c
WHERE Last_Position__longitude__s!= null
AND Last_Position__latitude__s != null
ORDER BY Name LIMIT 50
];
}
}

3.         Le résultat

Et voici ce que donne ce composant une fois installé dans une page Salesforce :

On sait où sont les voitures au fur et à mesure que salesforce récupère les informations depuis le serveur Cloud Mercedes !

 

Le module de gestion de la connexion de Salesforce vers Mercedes Cloud (3/3) : La récupération des informations des voitures

1.         Utilisation pour stocker / mettre à jour la liste des cars dans SF

a)              Un objet en mémoire pour récupérer le contenu de la réponse du serveur

Quand on demandait la voiture à la main avec Curl, on obtenait la réponse suivante

[
{
"id":                "00338353A46599799B",
"licenseplate":     "S-GG-9041",
"finorvin":         "1HM8CE2A6H6CEC802"
}
]

C’est un texte qui décrit la liste de voitures. Dans ce cas une seule voiture.

Pour pouvoir la manipuler, il faut définir un objet en mémoire dans Salesforce pour Apex.

class Vehicule {
public String salesforceId        { get; set; }
public String vehiculeId { get; set; }
public String licenseplate       { get; set; }
public String finorvin           { get; set; }
}

Et on va transformer chaque voiture de la liste dans un de ses objets.

b)              Le code qui analyse le contenu de la réponse du serveur et crée les voitures en mémoire

Du coup on va utiliser l’outil de Salesforce qui sert à lire un message JSON

JSON.deserializeUntyped( leMessage );

Et stocker en mémoire les voitures dans une liste

public List<Vehicule> vehicules { get; set; }

Comme pour l’appel à l’API précédente (session), on peut maintenant écrire le code qui appelle Mercedes, analyse la réponse et mémorise les voitures

public void loadVehiculesFromServer() {
  • Préparation de la requête
//Set HTTPRequest Method
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setHeader('authorization', 'Bearer '+access_token );
req.setHeader('accept', 'application/json');
req.setEndpoint(API_URL_Vehicules);
  • Appel de la requête
//Execute web service call here
Http http = new Http();
HTTPResponse res = http.send(req);
  • Récupération de la réponse
vehiculesJson = res.getBody();
System.debug('response:' + vehiculesJson);
  • Décodage de la réponse
List<Object> results =
(List<Object>)JSON.deserializeUntyped(vehiculesJson);

vehicules = new List<Vehicule>();

for(Object o:results)  {
Map<String,Object> m = (Map<String,Object>)o;
Vehicule v = new Vehicule();
v.vehiculeId = (String) m.get('id');
v.licenseplate = (String)  m.get('licenseplate');
v.finorvin =   (String) m.get('finorvin');
vehicules.add(v);
}
}

c)              Le code qui met à jour / crée les voitures dans la base SF à partir des voitures en mémoire

 

Ce qu’on veut maintenant c’est d’enregistrer les voitures qu’on vient d’obtenir dans Salesforce (mais sans créer des double à chaque appel). Pour ça on doit vérifier si la voiture est déjà là ou pas.

Cela se fait avec un programme qui  :

  1. lit les id Mercedes des voitures retournées par l’api Mercedes (ce qu’on vient de coder)
  2. charge les voitures de la base SF avec ces ID Mercedes pour cette connexion, et les place dans une carte
  3. pour chaque voiture retournée par l’API Mercedes
    1. regarde si elle est déjà dans la base salesforce
    2. sinon ; il prépare la création
    3. si oui ; il prépare la mise à jour des données dans la base salesforce
  4. il met à jour toutes les voitures qui existaient déjà
  5. il crée toutes celles qui n’existaient pas
public void storeVehiculesInSalesforce() {

//  Etape 1
Set<String> vehiculeMBIds = new Set<String>();
for (Vehicule v:vehicules) {
vehiculeMBIds.add(v.vehiculeId);
}

//  Etape 2
List<Car__c> listSFCars = [
SELECT Id, Mercedes_ID__c, Mercedes_API_Connection__c, Car_number__c
FROM Car__c
WHERE   Mercedes_API_Connection__c = :salesforceConnectionId
AND Mercedes_ID__c in :vehiculeMBIds
];

Map<String,Car__c> mapSFCars = new Map<String,Car__c>();
for (Car__c c:listSFCars) {
mapSFCars.put(c.Mercedes_ID__c, c);
}

//  Etape 3
List<Car__c> carsToCreate = new List<Car__c>();
List<Car__c> carsToUpdate = new List<Car__c>();

for (Vehicule v:vehicules) {
//  Etape 3a
Car__c theSFCar =  mapSFCars.get(v.vehiculeId);

if (theSFCar == null) {

//  Etape 3b
carsToCreate.add(
new Car__c(
Mercedes_API_Connection__c = salesforceConnectionId,
Mercedes_ID__c = v.vehiculeId,
Car_number__c = v.licenseplate
)
);

} else {

//  Etape 3c
theSFCar.Car_number__c = v.licenseplate;
carsToUpdate.add(theSFCar);
}

}

insert carsToCreate;
update carsToUpdate;

}

Maintenant à chaque fois qu’on appelle ce code, la liste des voitures dans Salesforce est mise à jour avec la réponse de Mercedes.

2.         Utilisation pour demander la position de toutes les voitures et les stocker dans la base SF

On va faire la même chose pour l’appel qui demande à Salesforce où se trouve chaque voiture.

a)              La réponse du serveur

En Curl, on obtenait la réponse suivante

{
"longitude": {"value":11.372609,"retrievalstatus":"VALID","timestamp":1548425864},
"latitude":  {"value":47.90321,"retrievalstatus":"VALID","timestamp":1548425864},
"heading":   {"value":52.520008,"retrievalstatus":"VALID","timestamp":1548425864}
}

b)              Un objet en mémoire pour récupérer le contenu de la réponse du serveur

On va aussi créer un objet Apex pour récupérer le résultat

class VehiculeLocation {
DateTime requestedOn { get; set; }
Decimal longitude    { get; set; }
Decimal latitude     { get; set; }
Decimal heading { get; set; }
}

c)              Un objet dans SF pour stocker la position

A chaque fois qu’on appelle la demande de position, on veut stocker cela comme enregistrement en plus. On a besoin de créer un nouvel objet dans Salesforce.

On l’appelle « Car Status », et il a les champs Position, Direction et date d’observation.

Voici ce que cela donne dans l’Object Manager.

d)              Des champs sur la voiture pour mémoriser la dernière position

On va en profiter aussi pour enregistrer la dernière position de la voiture dans l’objet voiture lui-même : un champ « Last Position ».

e)              Le code qui analyse le contenu de la réponse du serveur

Comme auparavant, on va appeler la requête et analyser le résultat. En rouge ce qui est particulier pour ce cas.

public VehiculeLocation getVehiculeLocation(String aVehiculeMbId) {

//Set HTTPRequest Method
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setHeader('authorization', 'Bearer '+access_token );
req.setHeader('accept', 'application/json');
req.setEndpoint(API_URL_Vehicules+'/'+aVehiculeMbId+'/location');

//Execute web service call here
Http http = new Http();
HTTPResponse res = http.send(req);

//Helpful debug messages
String jsonResponse = res.getBody();
System.debug('response:' + jsonResponse);

Map<String, Object> results =(Map<String, Object>)JSON.deserializeUntyped(jsonResponse);

VehiculeLocation vl = new VehiculeLocation();
vl.requestedOn = System.now();
vl.longitude= (Decimal) ((Map<String, Object>) results.get('longitude')).get('value');
vl.latitude= (Decimal) ((Map<String, Object>) results.get('latitude')).get('value');
vl.heading=(Decimal) ((Map<String, Object>) results.get('heading')).get('value');
System.debug('pos:'+vl);

return vl;
}

f)               Le code qui stocke dans la base SF les positions des voitures récupérées

Comme pour la liste des voitures, on va enregistrer les positions obtenues.

On va donc écrire un programme qui

  1. pour chaque véhicule
    1. appelle le serveur pour obtenir la position (le code du paragraphe précèdent)
    2. retrouve la voiture dans la base SF
    3. crée en mémoire l’objet Car_Status__c à stocker dans SF
    4. mémorise sur la voiture sa dernière position
  2. enregistre tous les objets Car_Status__c en une fois
  3. met à jour toutes les voitures (nouvelles positions) en une fois

public void getAndStoreVehiculeLocations() {

list<Car_Status__c> statusToInsert = new list<Car_Status__c>();

//  Etape 1
for (Vehicule v:vehicules) {

//  Etape 1a
VehiculeLocation vl = getVehiculeLocation(v.vehiculeId);

// Etape 1b
Car__c theCar = [
SELECT
Id,
Mercedes_ID__c,
Mercedes_API_Connection__c,
Car_number__c
FROM Car__c
WHERE Mercedes_API_Connection__c = :salesforceConnectionId
AND Mercedes_ID__c = :v.vehiculeId
];

// Etape 1c
statusToInsert.add(
new Car_Status__c(
Car__c = theCar.id,
Heading__c = vl.heading,
Location__longitude__s = vl.longitude,
Location__latitude__s = vl.latitude,
Requested_On__c = vl.requestedOn
)
);

// Etape 1d
theCar.Last_Position__longitude__s = vl.longitude;
theCar.Last_Position__latitude__s = vl.latitude;
carsToUpdate.add(theCar);
}

// Etape 2
insert statusToInsert;

// Etape 3
update(carsToUpdate);

}

3.         Enregistrer automatiquement toutes les 5 ‘ la position de la voiture

Si on veut suivre les voitures en permanence, il faut répéter l’enregistrement de la position régulièrement.

Ceci est le code pour une seule connexion qu’on veut exécuter toutes les 5 minutes (le code Apex qu’on a écrit avant, dans la classe Mercedes API, avec ‘a011t00000ByCf5AAF’ qui est l’identifiant de l’objet pour la connexion à Mercedes) :

MercedesAPI api = new MercedesAPI('a011t00000ByCf5AAF');
api.loadVehiculesFromServer();
api.getAndStoreVehiculeLocations();

Pour cela, on doit le placer dans une classe Apex programmable qui :

  1. Charge la liste des connexions, et pour chacune
  2. Fait les appels pour la liste des positions
global class MercedesStatusRequestScheduler implements Schedulable {

global void execute(SchedulableContext SC) {
//  Etape 1
for (Mercedes_API_Connection__c c : [SELECT Id FROM Mercedes_API_Connection__c]) {
//  Etape 2
doRequest(c.id);
}

}

    @future(callout=true)
    public static void doRequest(String theConnectionId) {
       MercedesAPI api = new MercedesAPI(theid);
       api.loadVehiculesFromServer();
       api.getAndStoreVehiculeLocations();
   }

}

Pour chaque exécution souhaitée (toutes les 5 ‘ d’une heure, donc 12 fois), on doit enregistrer l’exécution dans le tableau d’exécution SF.

Voici le code qui permet d’enregistrer une exécution

public static void schedule(String name, String cron) {
MercedesStatusRequestScheduler leJob =
new MercedesStatusRequestScheduler();
String jobID = system.schedule(name,cron,leJob);
}

Pour le programmer toutes les 5’ on doit enregistrer 12 programmations ainsi

schedule('Mercedes-Status xh00m', '0 0 * * * ? *');
schedule('Mercedes-Status xh05m', '0 5 * * * ? *');
schedule('Mercedes-Status xh10m', '0 10 * * * ? *');
schedule('Mercedes-Status xh15m', '0 15 * * * ? *');
schedule('Mercedes-Status xh20m', '0 20 * * * ? *');
schedule('Mercedes-Status xh25m', '0 25 * * * ? *');
schedule('Mercedes-Status xh30m', '0 30 * * * ? *');
schedule('Mercedes-Status xh35m', '0 35 * * * ? *');
schedule('Mercedes-Status xh40m', '0 40 * * * ? *');
schedule('Mercedes-Status xh45m', '0 45 * * * ? *');
schedule('Mercedes-Status xh50m', '0 50 * * * ? *');
schedule('Mercedes-Status xh55m', '0 55 * * * ? *');

4.         Amélioration : indiquer quelle connexion ou quelle voiture on veut suivre

Pour le moment, on ne peut pas activer désactiver l’enregistrement des positions.

Pour décider à chaque exécution :

  • si on veut charger les voitures d’une connexion
  • quelle voiture on veut interroger

On ajoute une checkbox :

  • à l’objet custom connection : Scheduled_Job_Execution__c
  • à l’objet custom car : Record_Status__c

Et on modifie le code ainsi

  • Quand on charge l’objet de connexion (Mercedes API connection) en mémoire, on mémorise si on doit faire l’enregistrement des positions
public boolean scheduledJobsExecution {get; set;  }
public MercedesAPI(Id aSalesforceConnectionId) {
…

scheduledJobsExecution = theConnection.Scheduled_Job_Execution__c;
}
  • Dans le code programmé toutes les 5 minutes, on vérifie si la connexion doit être enregistrée
@future(callout=true)
public static void doRequest(String theid) {
MercedesAPI api = new MercedesAPI(theid);
if (api.scheduledJobsExecution) {
api.loadVehiculesFromServer();
api.getAndStoreVehiculeLocations();
}
}

Dans le code d’interrogation et de stockage des statuts, on vérifie si la voiture doit être suivie pour sa position

public void getAndStoreVehiculeLocations() {
list<Car_Status__c> statusToInsert = new list<Car_Status__c>();
for (Vehicule v:vehicules) {
Car__c theCar = [
SELECT
Id,
Mercedes_ID__c,
Mercedes_API_Connection__c,
Car_number__c,
Record_Status__c
FROM Car__c
WHERE Mercedes_API_Connection__c = :salesforceConnectionId
AND Mercedes_ID__c = :v.vehiculeId
];
 
             if (theCar.Record_Status__c==true) {
VehiculeLocation vl = getVehiculeLocation(v.vehiculeId);
statusToInsert.add(new Car_Status__c(
Car__c = theCar.id,
Heading__c = vl.heading,
Location__longitude__s = vl.longitude,
Location__latitude__s = vl.latitude,
Requested_On__c = vl.requestedOn
)); 
            }

}

insert statusToInsert;
}

 

Et voilà maintenant, quand le code s’exécute, on n’accède à la connexion et on n’appelle l’enregistrement des voitures que si les cases sont cochées pour cette connexion et cette voiture.

Le module de gestion de la connexion de Salesforce vers Mercedes Cloud (2/3) : La connexion

Dans les Posts précédents, nous avons examiné comment appeler le serveur Mercedes Cloud ‘à la main’ dans la console de programmation Salesforce. Le but de ce Post est d’expliquer comment implémenter le code de manière à ce qu’il soit utilisable par un utilisateur normal.

1.         Un nouvel objet pour stocker les infos de connexion

On vient de travailler ‘à la main’ dans la console. Mais si on veut que ce soit facile de travailler avec tous ces codes, ce serait bien de les enregistrer quelque part.

Pour cela on va créer un objet SF qui sert à mémoriser les informations de la connexion vers Mercedes, on a besoin de :

  • client id =txt
  • client secret=txt
  • authorisation code=txt
  • access token=txt
  • expiration date = date/time
  • scope = texte
  • refresh token = texte

On va créer les champs de cet objet connexion avec le menu setup, comme on avait fait pour l’objet voiture.

On peut du coup saisir les informations de connexion dans Salesforce

Et aussi faire les requêtes avec du code SOQL comme on l’a fait avant pour les voitures :

SELECT Id, Mercedes_API_Client_ID__c, Mercedes_API_Client_Secret__c FROM Mercedes_API_Connection__c

2.         Comment savoir pour quelle connexion on a demandé l’autorisation ?

Quand le serveur Mercedes renvoie vers la page SF, on ne peut pas passer de paramètre. Il n’est pas possible de savoir quelle Mercedes Connection a été autorisée s’il y en a plusieurs configurées dans SF.

On va donc devoir « marquer » dans la base SF quelle connexion est concernée avant l’appel de la page d’autorisation Mercedes, pour que quand l’utilisateur revient dans SF sur la page d’après-autorisation on retrouve la connexion concernée.

On a ajouté deux champs à l’objet Mercedes Connection :

  • Last_Autorisation_Request_Time__c : la dernière fois qu’un utilisateur a demandé l’autorisation
  • Last_Autorisation_Request_User__c : quel utilisateur a demandé l’autorisation

Avant d’aller vers la page d’autorisation Mercedes, on va marquer la connexion ainsi en mémorisant que la connexion vient d’être demandée par l’utilisateur et que cela vient de se faire.

3.         Page pour demander la connexion

On va créer un composant visuel qui sera placé sur les objets Mercedes Api Connection.

Ce composant va permettre de cliquer sur un bouton pour aller sur le serveur Mercedes pour activer la connexion.

a)              Le composant visuel

C’est un simple composant qui affiche l’Id de la connexion et un bouton pour appeler la page de connexion

<template>

<lightning-card title="Connection Information" icon-name="standard:people">
<div class="slds-m-around_medium">
<p>The connection is </p>
<ul>
<li>SF ID : {recordId}</li>
</ul>
 
</div>
<lightning-button label="Activate Connection" onclick={handleActivateConnection} variant="brand"></lightning-button>
</lightning-card>
 
</template>

b)              Le code javascript du composant

Dans le code javascript du composant, ce qu’il y a de particulier est que :

  • Le code handleActivateConnection appelle d’abord une méthode memorizeAutorisationRequestContext du serveur (Apex) pour stocker le fait qu’on vient de lancer le processus pour cette connexion (voir paragraphe suivant)
memorizeAutorisationRequestContext( {aConnectionId :  this.recordId })
  • Quand cela a marché, dit d’aller vers la page de connexion Mercedes (l’URL est calculée par le code coté serveur) :
this[NavigationMixin.Navigate]({
"type" : "standard__webPage",
attributes: {
"url" : this.autorisationPageURL.data
}
}

Cela donne le code suivant pour le javascript du composant :

import { LightningElement, api, wire } from 'lwc';
import { getRecord /*, getFieldValue */ } from 'lightning/uiRecordApi';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { NavigationMixin } from 'lightning/navigation';

import memorizeAutorisationRequestContext from '@salesforce/apex/MercedesAPI.memorizeAutorisationRequestContext';

import getAuthorizationPageURL from '@salesforce/apex/MercedesAPI.getAuthorizationPageURL';

import CONNECTION_SALEFORCE_ID from '@salesforce/schema/Mercedes_API_Connection__c.Id';

import CONNECTION_CLIENT_ID from '@salesforce/schema/Mercedes_API_Connection__c.Mercedes_API_Client_ID__c';

import CONNECTION_CLIENT_SECRET from '@salesforce/schema/Mercedes_API_Connection__c.Mercedes_API_Client_Secret__c';

import CONNECTION_ACCESS_TOKEN from '@salesforce/schema/Mercedes_API_Connection__c.Mercedes_API_Acces_Token__c';


const connectionFields = [
CONNECTION_SALEFORCE_ID,
CONNECTION_CLIENT_ID,
CONNECTION_CLIENT_SECRET,
CONNECTION_ACCESS_TOKEN
];

export default class MercedesConnectionManager extends NavigationMixin(LightningElement)
{

@api recordId; // Connection Id
@wire(getRecord, { recordId: '$recordId', fields: connectionFields })
mercedesConnection;

@wire(getAuthorizationPageURL, {  aConnectionId: '$recordId' })
autorisationPageURL;
get urlInfos() {
return JSON.stringify(this.autorisationPageURL);
}

handleActivateConnection() {
memorizeAutorisationRequestContext( {aConnectionId :  this.recordId })
.then(result => {
// small event message
const evt = new ShowToastEvent({
title: 'Connection Context Memorized',
message: 'The connection context has been successfully memorized for '+result,
variant: 'success',
});
this.dispatchEvent(evt);
// go to page
this[NavigationMixin.Navigate]({
"type" : "standard__webPage",
attributes: {
"url" : this.autorisationPageURL.data
}
},
{
replace: true
}
);
})
.catch(error => {
const evt = new ShowToastEvent({
title: 'Connection Context Not Memorized',
message: 'An error occured '+JSON.stringify(error),
variant: 'error',
});
this.dispatchEvent(evt);
});
}
}

c)              Le code Apex

C’est le code du côté Salesforce qui fait les calculs et les sauvegarde (dans la classe MercedesAPI).

Avant d’aller vers la page d’autorisation Mercedes, on marque la connexion ainsi :

@AuraEnabled(cacheable=false)
public static id memorizeAutorisationRequestContext(Id aConnectionId) {
// recupere la connection
Mercedes_API_Connection__c theConnection = [
SELECT
Id,
Name,
Last_Autorisation_Request_User__c,
Last_Autorisation_Request_Time__c
FROM Mercedes_API_Connection__c
where id = :aConnectionId
];

// memorise qui et quand
theConnection.Last_Autorisation_Request_User__c = UserInfo.getUserId();
theConnection.Last_Autorisation_Request_Time__c = System.now();

// sauve le resultat
update theConnection;

return aConnectionId;
}

Et le code qui calcule la page pour demander l’autorisation d’accès à Mercedes

@AuraEnabled(cacheable=true)
public static String getAuthorizationPageURL(Id aConnectionId) {
Mercedes_API_Connection__c myConnection = [
SELECT
Id,
Mercedes_API_Client_ID__c
FROM Mercedes_API_Connection__c
WHERE id = :aConnectionId
];

return API_URL_Authorization
+ '?response_type=code'
+ '&client_id=' + myConnection.Mercedes_API_Client_ID__c
+ '&redirect_uri='+redirectURI
+ '&scope='+requestedScope;

}

d)              Les informations du composant pour indiquer qu’il est affichable sur les pages ‘Mercedes Api Connection’

<?xml version="1.0" encoding="UTF-8"?>

<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="mercedesConnectionManager">

<apiVersion>45.0</apiVersion>

<isExposed>true</isExposed>

<targets>
<target>lightning__RecordPage</target>
</targets>

<targetConfigs>
<targetConfig targets="lightning__RecordPage">
<objects>
<object>Mercedes_API_Connection__c</object>
</objects>
</targetConfig>

</targetConfigs>

</LightningComponentBundle>

e)              Le composant ajouté à la page de l’objet API Connection

On ajoute le composant à la page comme on avait fait avant pour le composant qui affichait la couleur d’une voiture.

4.         Page qui récupère les informations de demande d’autorisation

Quand on clique sur le lien, on arrive chez Mercedes où on peut valider qu’on autorise l’accès, puis Mercedes redirige sur la page Mercedes API Authorization (ce qu’on avait fait dans la partie d’avant)

Il faut maintenant faire que cette page utilise le code d’autorisation qu’elle a récupéré

a)              La page

Pour cela on ajoute un bouton sur la page

<apex:page controller="Mercedes_API_Authorization_Ctrl" >
Authorization : {! authorizationCode}
<apex:form >
  <apex:commandButton value="validate connection request" action="{!saveAuthorizationCode}" />
</apex:form>
</apex:page>

Le bouton appelle une action saveAuthorizationCode dans le contrôleur de la page (Mercedes_API_Authorization_Ctrl )

b)              Le code Apex

Du coup, quand on revient dans l’environnement SF de l’utilisateur on peut retrouver la bonne connexion pour y sauver les informations de session (token, etc.) : on prend la dernière connexion modifiée par cet utilisateur

public PageReference saveAuthorizationCode() {

mercedesConnection = [
SELECT
Id,
Name,
Mercedes_API_Client_ID__c,
Mercedes_API_Client_Secret__c,
Mercedes_API_Autorization_Code__c
FROM Mercedes_API_Connection__c
where
Last_Autorisation_Request_User__c= : UserInfo.getUserId()
order by Last_Autorisation_Request_Time__c desc
limit 1
];

….

}
  1. c) On utilise le code de connexion qu’on avait auparavant recopié dans une classe MercedesAPI pour pouvoir le réutiliser.
MercedesAPI api = new MercedesAPI(
theConnection.Mercedes_API_Client_ID__c,
theConnection.Mercedes_API_Client_Secret__c
);
api.initSession(authorizationCode);
  1. d) On stocke l’autorisation et les tokens obtenus
theConnection.Mercedes_API_Autorization_Code__c = authorizationCode;
theConnection.Mercedes_API_Acces_Token__c = api.access_token;
// sauver les autres champs ici
update theConnection;

Et voilà la connexion est établie !

Voici le code avec tous les morceaux mis ensemble.

 
public PageReference saveAuthorizationCode() {

Mercedes_API_Connection__c theConnection = [
SELECT
Id,
Mercedes_API_Client_ID__c,
Mercedes_API_Client_Secret__c,
Mercedes_API_Autorization_Code__c
FROM Mercedes_API_Connection__c
// where id = :monID
limit 1
];

MercedesAPI api = new MercedesAPI(
theConnection.Mercedes_API_Client_ID__c,
theConnection.Mercedes_API_Client_Secret__c
);

api.initSession(authorizationCode);
api.getVehicules();

theConnection.Mercedes_API_Autorization_Code__c = authorizationCode;
theConnection.Mercedes_API_Acces_Token__c = api.access_token;

// sauver les autres champs ici
update theConnection;

vehiculeTexte = api.vehiculesJson;
return null;
}

Et on peut demander à rafraîchir cette partie de page après le clic sur le bouton

<apex:commandButton value="validate connection request" action="{!saveAuthorizationCode}" reRender="vehiculesPanel"/>

Voici ce qu’on voit quand on arrive sur la page ‘redirect url’ après être passé sur le serveur Mercedes :

Puis ce qu’on voit quand on clique sur ‘Validate’

La connexion est maintenant active et le token de session est enregistré.

5.         Rafraichissement automatique du token de connexion

Normalement, le jeton de session n’est valide qu’une heure. Il faudrait faire un code qui appel l’API pour rafraîchir automatiquement le token toutes les heures.

Je ne l’ai pas fait dans le temps de mon Trape.

C’est une amélioration à faire. Pour le moment, il faut recliquer sur ces écrans toutes les heures.

Le module de gestion de la connexion de Salesforce vers Mercedes Cloud (1/3) : Techniques utilisées

Dans les deux premières parties nous avons parlé de l’API Mercedes et on l’a utilisée ‘à la main’ avec CuRL en ligne de commande, puis on a regardé comment utiliser et configurer Salesforce,  puis  programmer des composants visuels avec Salesforce et enfin comment stocker et lire en mode programmation des données dans Salesforce.

Maintenant, on regarde comment programmer ces API Mercedes que l’on a manipulé en CuRL avec les outils de programmation de Salesforce.

1.         La page qui permet de recevoir l’autorisation

Quand on appelé le mécanisme de Mercedes pour obtenir le code d’autorisation, on allait sur une URL Mercedes avec nos données de l’application (dont le client id) , c’était l’étape A ; puis l’utilisateur tapait ses données de connexion pour autoriser l’application (étape B) ; et enfin Mercedes renvoyait sur une page de l’application en passant le code d’autorisation en paramètre (étape C).

L’URL de l’étape C ressemblait à ceci, avec le code obtenu en rouge :

https://localhost/?code=6e9d89c4-a374-4bb9-8c88-673034f87342

Parce qu’on avait dit que la redirect URL de l’application était https://localhost/.

En Salesforce on a besoin de faire une page web qui va savoir lire le paramètre code pour qu’on puisse l’utiliser par la suite.

Le code de la page est simple. Ce sera une page Visual Force, vide pour le moment

<apex:page controller="Mercedes_API_Authorization_Ctrl">
</apex:page>

Avec un controller Mercedes_API_Authorization_Ctrl qui :

  • Stocke le code d’autorisation (en bleu)
  • Lit le paramètre passé quand la page se charge. (en orange).
public class Mercedes_API_Authorization_Ctrl {
    public String authorizationCode { get; set; }
 
public Mercedes_API_Authorization_Ctrl(){
   authorizationCode =
apexpages.currentpage().getparameters().get('code');
}
}

Pour faire plus visible, on peut aussi modifier la page pour qu’elle affiche le paramètre obtenu

<apex:page controller= »Mercedes_API_Authorization_Ctrl »>
  Authorization : {! authorizationCode}
</apex:page>

 

On doit ajouter cette redirect URL à l’application déclarée chez Mercedes :

Essayons d’ouvrir la première URL (etape A), avec la bonne redirect URL (en vert), puis suivons les étapes, on arrive à :

https://api.secure.mercedes-benz.com/oidc10/auth/oauth/v2/authorize?response_type=code&client_id=36f504aa-f141-41ad-93ae-09603cddf507&redirect_uri=https%3A%2F%2Fc.eu16.visual.force.com%2Fapex%2FMercedes_API_Authorization&scope=mb:user:pool:reader%20mb:vehicle:status:general

 

On sait récupérer dans SF le code d’autorisation !

 

2.         Le code pour demander le jeton de session

 

Après avoir reçu le code d’autorisation, quand on travaillait à la main :

  • on devait récupérer les jetons de session
  • on pouvait récupérer les données avec l’API

Maintenant on doit faire la même chose qu’on faisait en Curl, mais en SF, avec le langage APEX.

Avec CuRL, à chaque fois :

  • on créait la requête (la commande tapée)
  • on l’exécutait (appel du serveur Mercedes par le serveur Salesforce), en lançant la commande
  • on traitait le résultat (en le lisant)

On va faire la même chose en programmant.

L’appel CuRL pour l’obtention de la session était

curl -X POST "https://api.secure.mercedes-benz.com/oidc10/auth/oauth/v2/token" \
-H "authorization: Basic ZGY4YTczYmUtYTY4Zi00.....hMWQwLWRjMmM0ZWI4MTU0ZA==" \
-H "content-type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code&code=297ded07-43ea-4fcc-ab73-6b6376d787ca&redirect_uri=https%3A%2F%2Flocalhost.com"

Le Code correspondant en Apex est

  • Préparation de la requête
// creation d’une requête HTTP
HttpRequest req = new HttpRequest();
req.setMethod('POST');

// vers le serveur Mercedes
req.setEndpoint('https://api.secure.mercedes-benz.com/oidc10/auth/oauth/v2/token');

// avec le bon code d’auorisation
req.setHeader('authorization', 'Basic ZGY4YTczYmUtYTY4Zi0.....2NiZTMxYmUxMDM5OjBlYjRmMGE0LTM1YzgtNDc0Ny1hMWQwLWRjMmM0ZWI4MTU0ZA==');
req.setHeader('content-type', 'application/x-www-form-urlencoded');

// avec le message de demande de session
String messageBody =
'grant_type=authorization_code'
+'&code=297ded07-43ea-4fcc-ab73-6b6376d787ca'    +'&redirect_uri=https%3A%2F%2Fc.eu16.visual.force.com%2Fapex%2FMercedes_API_Authorization';
req.setBody(messageBody);
  • Exécution de la requête
//Execute de la requête HTTP
Http http = new Http();
HTTPResponse res = http.send(req);
  • Lecture de la réponse et traitement (juste un affichage)
String jsonResponse = res.getBody();

Map<String, Object> results = (Map<String, Object>)

JSON.deserializeUntyped(jsonResponse);

String access_token = (String)  results.get('access_token');
String token_type   = (String)  results.get('token_type');
Integer expires_in = (Integer) results.get('expires_in');
String refresh_token=  (String)  results.get('refresh_token');
String scope         = (String)  results.get('scope');

System.debug('access_token:' + access_token);
System.debug('token_type:' + token_type);
System.debug('expires_in:' + expires_in);
System.debug('refresh_token:' + refresh_token);
System.debug('scope:' + scope); 

Voici ce qu’on obtient en debug quand on lance le code dans la Console

3.         Le code pour faire un appel API : liste des voitures

De même, pour obtenir la liste des véhicules, on appelait avec CuRL

curl -X GET "https://api.mercedes-benz.com/experimental/connectedvehicle/v1/vehicles"  -H "accept: application/json"  -H "authorization: Bearer f9da01aa-0c07-412b-bb0c-f31a7b413b7b"

On fait les mêmes étapes en Apex :

  • Préparation de la requête (on passe le token obtenu comme avec CuRL)
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setHeader('authorization', 'Bearer '+access_token );
req.setHeader('accept', 'application/json');
req.setEndpoint('https://api.mercedes-benz.com/experimental/connectedvehicle/v1/vehicles');
  • Exécution de la requête
Http http = new Http();
HTTPResponse res = http.send(req);
  • Lecture de la réponse et traitement (ici on imprime juste la liste)
String jsonResponse = res.getBody();
System.debug('response:' + jsonResponse);

Voici ce qu’on obtient dans le Debug :

Et voici ! Reste à intégrer ce code manipulé à la main dans une application… Ce sera l’objet des travaux du prochain post !

A la découverte de salesforce (3/3) – Stocker des données sur le serveur Salesforce

Les objets que j’ai créés sont stockés dans la base de données de Salesforce. Les écrans de Salesforce vont les chercher pour les afficher, ou quand on modifie dans un écran il va dire à Salesforce de stocker le changement dans sa base de données.

Il y a un langage qui sert à aller chercher les données dans Salesforce : SOQL (Salesforce Object Query Language).

2.         Exemple liste des Cars : taper une requête dans la console

Pour essayer d’utiliser le langage de requête SOQL on peut quand on est connecté dans Salesforce aller dans le menu en haut à droite et lancer la Console.

Dans la nouvelle fenêtre qui s’ouvre (La Console), on peut aller dans le Query Editor, taper la requête suivante et cliquer sur Execute.

SELECT Id, Name, Car_Color__c, Car_number__c, Car_Model__c, Car_Owner__c FROM Car__c where car_color__c = 'red'

On obtient alors la liste des deux voitures rouges qui s’affiche.

3.         Exemple liste des Cars : dans le code de programmation coté serveur (Apex)

Dans la dernière partie, pour faire le composant visuel, on a parlé de la programmation en HTML et en Java Script dans le navigateur pour accéder à Salesforce.

Mais dans Salesforce il y a aussi un langage de programmation qui s’exécute sur le serveur de Salesforce. Ce langage s’appelle Apex.

Dans la partie suivante, je vais en avoir besoin pour programmer les communications avec le serveur Mercedes depuis Salesforce.

Ce langage Apex permet aussi d’appeler les requêtes vers la base de données qu’on a vue juste avant.

Pour le moment on va faire un petit essai d’exécution d’un petit programme Apex dans la console. Pour cela on va dans le menu Debug, on choisit Open Execute Anonymous Window, et on peut taper un petit programme Apex qui va lancer une requête.

Le code du petit programme Apex à saisir :

List<Car__c> listeDesVoituresRouges = [
SELECT Id, Name, Car_Color__c, Car_number__c
FROM Car__c
where car_color__c = 'red'
];

for (Car__c uneVoiture : listeDesVoituresRouges) {
System.debug('La voiture '+uneVoiture.Name+ ' est '+ uneVoiture.car_color__c);
}

On clique sur Execute

On peut aller regarder le résultat dans le log d’exécution (le fichier que SF a généré pendant que le programme s’exécute sur le serveur Salesforce)

Cette possibilité de programmation du côté des serveurs Salesforce sera utilisée beaucoup dans la partie suivante.