Interagir avec L’agenda Google via l’API (OAuth 2.0)

Interagir avec L’agenda Google via l’API (OAuth 2.0)

Ce tutoriel explique comment ajouter des événements dans un agenda Google à partir d’un script PHP sur Raspberry Pi. Le script demande l’autorisation de se connecter à l’agenda puis, une fois l’autorisation accordée, crée des événements sur l’agenda lorsqu’on le souhaite.

Nous avons créé un agenda « Pi Nautilus » dans l’article Raspberry Pi : envoyer des SMS sans 3G et gratuitement. Cet article explique également comment installer PHP sur un Pi si c’est nécessaire.

Nous allons maintenant écrire un programme, en PHP, qui créera automatiquement un événement dans l’agenda Google.

Télécharger le client PHP de l’API Google

Dans ce dépôt GitHub, suivre les instructions « Download the release » :

  • cliquer sur le lien ‘the releases
  • dans le chapitre « Downloads », sélectionner un fichier qui a un nom du genre google-api-php-client-[RELEASE_NAME].zip (pour moi google-api-php-client-2.0.3_PHP54.zip)
  • extraire le fichier zip
  • Copier les fichiers extraits dans un répertoire php-google-api-client  (placé dans notre répertoire de travail, pour moi /home/jf/exec).

Paramétrer un projet dans la console Google Api

La principale source d’informations que j’ai utilisé est PHP Quickstart, par Google.

Se connecter à la console Google API.

Créer un projet

Créer un projet, par exemple « Api Google Calendar » (noter le nom choisi)

Créer un projet dans la console Google Api

Créer un projet dans la console Google Api

 

Activer l’API Google Calendar

Activer l'API Google Calendar

Activer l’API Google Calendar

Obtenir des identifiants

Au sein du projet, aller dans le menu « identifiants ». Ne pas cliquer sur la fenêtre « API identifiants » mais aller dans l’onglet « Ecran d’autorisation OAuth ».

Google calendar API : écran d'autorisation OAuth

Google calendar API : écran d’autorisation OAuth

Maintenant, on peut cliquer sur le bouton « créer des identifiants », on choisit « ID client OAuth » puis « autre ». On lui donne un nom « client Pi calendar » et on crée l’ID :

Google calendar API : ajouter un client

Google calendar API : ajouter un client

Une fois le client créé, une fenêtre s’affiche avec notre ID client et son code secret. Les copier et les coller dans un document. On voit maintenant une liste des clients créés. Télécharger le fichier JSON en cliquant sur le bouton à droite de la ligne :

Google calendar API : télécharger la clé JSON

Google calendar API : télécharger la clé JSON

La clé doit être placée dans un répertoire /home/jf/exec/certificates  et renommée client_secret.json .

On en profite pour créer un répertoire /home/jf/exec/credentials , qui doit être vide.

Vérifier l’organisation du répertoire de travail

Mon répertoire de travail est /home/jf/exec.

A l’intérieur, j’y trouve trois répertoires :

  • certificates, qui contient client_secret.json ;
  • credentials, qui est vide pour l’instant ;
  • php-google-api-client, qui contient la bibliothèque PHP de client de l’API Google, comme dans cette copie d’écran :
Organisation des fichiers pour utiliser l'API Google

Organisation des fichiers pour utiliser l’API Google

Maintenant, on peut créer notre premier script pour intéragir avec notre agenda Google.

Créer un script PHP pour modifier l’agenda

Obtenir l’identifiant de l’agenda à modifier

L’id du calendrier est montrée lorsque je clique sur paramètres (onglet détails) :

Obtenir l'identifiant d'un agenda Google

Obtenir l’identifiant d’un agenda Google

A ce stade on a noté les informations suivantes (AAAA est composé de 44 caractères [a-z][0-9], BBBB de 24 caractères [A-Z][a-z][0-9], CCCC 26 caractères [a-z][0-9]) :

Nom du projet Api Google Calendar
Nom du produit Pi calendars by ALD
ID client AAAA.apps.googleusercontent.com
code secret client BBBB
ID agenda  parcours-performance.com_CCCC@group.calendar.google.com

Ecrire un premier script

J’ai utilisé principalement le script PHP proposé par PHP Quickstart, de Google, ainsi qu’un bout de code trouvé sur la page consacrée à Events: quickAdd.

Attention : si vous utilisez le script PHP proposé dans PHP Quickstart, il faut noter que le code est prévu pour lire le contenu de l’agenda, pas y écrire.

Si on veut pouvoir accéder en lecture ET écriture, il faut modifier la ligne contenant « define( ‘SCOPES’,… », et remplacer CALENDAR_READONLY par CALENDAR. 

Et si on avait déjà obtenu une autorisation quand SCOPES était réglé sur CALENDAR_READONLY, il faut supprimer le fichier JSON dans le répertoire credentials avant de l’éxécuter de nouveau. Sinon, on a une autorisation en lecture seule et on demande l’écriture, ce qui provoque une erreur.  

Créer un fichier al-pi-create-google-calendar-event.php  dans notre répertoire de travail /home/jf/exec.

Dans ce fichier – en mode encodage UTF8, retours de ligne LINUX – placer le code suivant (remplacer l’id du calendrier par votre id) :

#!/usr/bin/php

<?php
require_once __DIR__ . '/php-google-api-client/vendor/autoload.php';


define('APPLICATION_NAME', 'Pi calendars by ALD');
define('CREDENTIALS_PATH', __DIR__ . '/credentials/calendar-php-quickstart.json');
define('CLIENT_SECRET_PATH', __DIR__ . '/credentials/client_secret.json');
// If modifying these scopes, delete your previously saved credentials
// at __DIR__ . '/credentials/calendar-php-quickstart.json
define('SCOPES', implode(' ', array(
  Google_Service_Calendar::CALENDAR) // CALENDAR_READONLY
));



if (php_sapi_name() != 'cli') {
  throw new Exception('This application must be run on the command line.');
}

// data for the function
$title = "19.8°C chez Pi Nautilus";
$cal_id = "parcours-performance.com_CCCC@group.calendar.google.com" ;

$create_event = al_pi_create_quick_event( $title, $cal_id ) ;
echo "event ID : " . $create_event . "\r\n" ;


function al_pi_create_quick_event( $title, $cal_id ) {
	
	// Get the API client and construct the service object.
	$client = getClient();
	$service = new Google_Service_Calendar($client);

	// https://developers.google.com/google-apps/calendar/v3/reference/events/quickAdd

	$optParams = Array(
			'sendNotifications' => true,
	);

	$createdEvent = $service->events->quickAdd(
		$cal_id,
		$title,
		$optParams
	);

	return $createdEvent->getId();
	
}

/**
 * Returns an authorized API client.
 * @return Google_Client the authorized client object
 */
function getClient() {
  $client = new Google_Client();
  $client->setApplicationName(APPLICATION_NAME);
  $client->setScopes(SCOPES);
  $client->setAuthConfig(CLIENT_SECRET_PATH);
  $client->setAccessType('offline');

  // Load previously authorized credentials from a file.
  $credentialsPath = expandHomeDirectory(CREDENTIALS_PATH);
  if (file_exists($credentialsPath)) {
    $accessToken = json_decode(file_get_contents($credentialsPath), true);
  } else {
    // Request authorization from the user.
    $authUrl = $client->createAuthUrl();
    printf("Open the following link in your browser:\n%s\n", $authUrl);
    print 'Enter verification code: ';
    $authCode = trim(fgets(STDIN));

    // Exchange authorization code for an access token.
    $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);

    // Store the credentials to disk.
    if(!file_exists(dirname($credentialsPath))) {
      mkdir(dirname($credentialsPath), 0700, true);
    }
    file_put_contents($credentialsPath, json_encode($accessToken));
    printf("Credentials saved to %s\n", $credentialsPath);
  }
  $client->setAccessToken($accessToken);

  // Refresh the token if it's expired.
  if ($client->isAccessTokenExpired()) {
    $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
    file_put_contents($credentialsPath, json_encode($client->getAccessToken()));
  }
  return $client;
}

/**
 * Expands the home directory alias '~' to the full path.
 * @param string $path the path to expand.
 * @return string the expanded path.
 */
function expandHomeDirectory($path) {
  $homeDirectory = getenv('HOME');
  if (empty($homeDirectory)) {
    $homeDirectory = getenv('HOMEDRIVE') . getenv('HOMEPATH');
  }
  return str_replace('~', realpath($homeDirectory), $path);
}


?>

En SSH, se connecter au Pi (Nautilus pour moi)

ssh root@nautilus

Se placer dans le répertoire de travail puis exécuter le fichier :

cd /home/jf/exec
./al-pi-create-google-calendar-event.php

Un message commençant par « Open the following link in your browser: » s’affiche sur l’écran de notre Pi. On copie le lien et on le colle dans un navigateur. Ca nous renvoie à une page dans laquelle je dois autoriser le partage. On copie le code et on le colle dans l’invite de commande du Pi. Le script sauvegarde notre autorisation dans le répertoire défini (/home/jf/exec/credentials) puis nous communique l’event ID :

Google API : autorisation OAuth en mode ligne de commande

Google API : autorisation OAuth en mode ligne de commande

Nous avons été autorisé à interagir avec le calendrier Google et notre premier événement à été créé. A partir de maintenant, nous n’aurons plus besoin d’obtenir de nouveau l’autorisation de l’agenda pour y intervenir.

Et maintenant ?

Maintenant que nous savons comment interagir avec une API Google, on peut en principe faire la même chose avec d’autres API, telles que celle de Google Sheet pour stocker des informations dans un tableur Google.

Données d’une box domotique Eedomus dans une page PHP

Données d’une box domotique Eedomus dans une page PHP

Dans ce second article de la série , je veux afficher une température stockée dans ma box domotique Eedomus + à mon tableau de bord (en PHP, sur Raspberry Pi).

Cet article a été complété le 8 mai 2017.

De quoi je pars ?

d’une page index.php servie par un Raspberry Pi, comme indiqué dans le premier article  de cette série.

l’API de la box Eedomus

Elle est bien documentée ici : API eedomus.

Obtenir une clé d’API Eedomus

Dans https://secure.eedomus.com/, cliquer sur configurer puis « mon compte ».

  1. Cocher la case « autoriser l’API via http » et « sauver ».
  2. Cliquer sur « consulter vos identifiants »

API Eedomus - 1

On obtient ainsi les valeurs de api_user et api_secret qui nous permettront de nous connecter. On accède aussi à un formulaire qui va nous permettre de construire la requête.

Faire une requête pour un capteur spécifique

J’utilise le formulaire précédent puis je copie la requête HTTP obtenue :

API Eedomus - 2

La requête est de type (remplacer api_user et api_secret) :

http://api.eedomus.com/get?api_user=xxx&api_secret=yyy&action=periph.caract&periph_id=166280

Lorsque je la tape dans un navigateur internet, j’obtiens la chaîne JSON suivante :

{ "success": 1, "body":{"periph_id": "166280", "name": "Nest - Température Couloir RdC", "last_value": "24.5", "last_value_text": "", "last_value_change": "2016-08-15 15:50:55"}}

Pour lire cette donnée dans mon fichier PHP, je me suis inspirée de l’exemple donné à la fin du document  API eedomus.

J’ai intégré le code suivant à ma page index.php :

<h3>Autres informations</h3>

		<?php include 'eedomus.php';?>

		<!-- nest temperature ID 166280 -->
		<p>couloir : <?php echo al_get_eedomus_value( 166280, 'last_value' ) ; ?> °C</p>

Et dans le fichier eedomus.php , j’ai intégré une fonction al_get_eedomus_value  :

<?php

// définition des variables
$api_user = 'xxx'; // a récupérer sur votre compte eedomus
$api_secret = 'yyy'; // a récupérer sur votre compte eedomus

/******************************************************************
* appel de l'API eedomus en PHP pour récupérer une donnée
*
* $id = 166280
* $val = 'last_value'
* retournera la température actuellement mesurée par le thermostat Nest
*
*****************************************************************/ 

function al_get_eedomus_value( $id, $val ) {
	
	global $api_user ;
	global $api_secret ;
	
	// construction de l'URL de l'API
	$url = "http://api.eedomus.com/get?action=periph.caract";
	$url .= "&api_user=" . $api_user;
	$url .= "&api_secret=" . $api_secret;
	$url .= "&periph_id=" . $id;

	// appel de l'API
	$result = file_get_contents($url);

	// on controle le résultat
	if (strpos($result, '"success": 1') == false)
	{
	  echo "Une erreur est survenue : [".$result."]";
	}
	else
	{
	  $result = json_decode( $result, true ) ; // true transforme $result en un array
	  $retour = $result['body'][$val];
	  return $retour ;
	}	
	
}

Le résultat est maintenant affiché sur mon tableau de bord :

Affichage PHP de capteur via l'API Eedomus

Mais les données ne sont mises à jour que si je rafraichis le tableau de bord manuellement.

Mettre à jour automatiquement les données de la page PHP

Cette question sur StackOverflow explique comment recharger une page toutes les 30 secondes. Il suffit d’ajouter <meta http-equiv= »refresh » content= »30″ />  en haut de la page PHP.

Comme 15 minutes = 900 secondes, j’ai intégré la ligne  <meta http-equiv= »refresh » content= »900″ /> en haut de ma page PHP. Le haut contient donc maintenant :

<!DOCTYPE html>
<html lang="fr">

<head>
	<meta charset="utf-8">
	<title>Nautilus</title>
	<meta name="viewport" content="width=device-width,initial-scale=1" />
	<link href="A-style.css" rel="stylesheet" media="all" type="text/css"> 
	<meta http-equiv="refresh" content="900" />
</head>

Complément (8 mai 2017)

J’ai affiné le contenu de ma page php pour avoir automatiquement la liste des périphériques de l’eedomus. C’est un préalable à un tableau de bord qui lirai automatiquement toutes les données de l’eedomus.

Obtenir la liste des périphériques

Dans le code ci-dessous, la fonction eedomus_url génère les url correctes pour :

  • vérifier qu’on se connecte correctement à la box Eedomus ;
  • Obtenir la liste des périphériques de la box Eedomus ;
  • Lire la valeur d’un périphérique donné de la box Eedomus

la fonction ald_decode_french  permet de lire correctement des textes encodés en français, avec des caractères spéciaux.

	<?php
	
	function eedomus_url( $action, $user_id, $pw ) {
		/* 
		* eedomus_url( "&action=auth.test", $ee_apiuser, $ee_passwd ) construit
		* https://api.eedomus.com/get?api_user=USER&api_secret=SECRET&action=auth.test
		* eedomus_url( "&action=periph.list", $ee_apiuser, $ee_passwd ) construit
		* https://api.eedomus.com/get?api_user=USER&api_secret=SECRET&action=periph.list		
		* eedomus_url( "&action=periph.caract&periph_id=156595", $ee_apiuser, $ee_passwd ) construit
		* https://api.eedomus.com/get?api_user=USER&api_secret=SECRET&action=periph.caract&periph_id=156595
		*
		* ces 3 url renvoient bien une info en Json, en provoenance de l'eedomus dans un navigateur
		*/
		
		$eedomus_get = "https://api.eedomus.com/get?api_user=" ;
		$eedomus_get .= $user_id ;
		$eedomus_get .= "&api_secret=" ;
		$eedomus_get .= $pw ;
		$eedomus_get .= $action ;

		return $eedomus_get ;
	}

	$ee_test = eedomus_url( "&action=auth.test", $ee_apiuser, $ee_passwd ) ; 
	$ee_list = eedomus_url( "&action=periph.list", $ee_apiuser, $ee_passwd ) ; 
	$ee_156595 = eedomus_url( "&action=periph.caract&periph_id=156595", $ee_apiuser, $ee_passwd ) ; 

	function ald_decode_french( $string ) {
		
		$contents = utf8_encode( $string ); 
		$results = json_decode( $contents ); 
		return $results ;
		
	}

Si j’utilise la fonction ald_decode_french  sans la partie ‘utf8_encode’, le résultat est vide car les accents provoquent des erreurs. Ci-dessous, dans l’image, à gauche on a le résultat de <pre><?php print_r ( file_get_contents( $ee_list ) ) ; ?> </pre>  et à droite le résultat de <pre><?php print_r ( ald_decode_french( file_get_contents( $ee_list ) ) ); ?> </pre> . Les accents sont correctement restitués dans la version de droite.

Utilisation de utf8_encode pour des accents dans du Json

Et maintenant ?

Je vais maintenant voir comment un Raspberry Pi peut transmettre une valeur mesurée à l’Eedomus. Ce sera l’objet du prochain article de cette série . Ensuite, je pourrai utiliser le présent tutoriel pour l’afficher sur mon tableau de bord.

Données de Wunderground dans une page PHP

Données de Wunderground dans une page PHP

Aujourd’hui, je veux lire des données issues d’une station météo proche de chez moi via l’API wunderground. Je souhaite qu’elles s’affichent automatiquement dans mon tableau de bord domotique (une page PHP servie par un Raspberry Pi).

De quoi je pars ?

J’ai réglé un Raspberry Pi pour servir une page PHP qui affiche le flux de deux caméras de surveillance (voir l’article Récapitulatif : Raspberry Pi, motion et deux caméras).

Maintenant, je veux ajouter des données issues de capteurs externes ou non. Je commence par insérer des données issues d’une station météo externe, via l’API de wunderground.

Une API ?

J’ai suivi le très bon cours en ligne sur les API de Emily REESE, sur OpenClassRoom ici.

Une API, ou  « Application Programming Interface », est une interface entre un utilisateur et une application. Un menu constitue l’API d’un restaurant par exemple. En informatique, lorsqu’on parle d’API, on parle en général d’API REST (cf sur wikipedia). Elles utilisent des instructions HTTP.

En général les requêtes HTTP à des API REST obtiennent une réponse en JSON (cf  sur wikipedia).

L’API de wunderground

Wunderground est un site qui met à disposition des informations météos. Les personnes qui possèdent une station météo peuvent lui communiquer des données. On a ainsi accès à des données très locales.

J’ai identifié une station locale à Plonéour-Lanvern. Je veux en extraire des données.

Je me suis créé un compte sur Wunderground et j’ai généré une clé API : voir cet article en français pour savoir comment faire.

Avec la console apigee intégrée à wunderground ici, j’ai pu déterminer que la requête est sous la forme :

http://api.wunderground.com/api/APIkey/conditions/q/pws:Station-ID.json

avec APIkey qui est ma clé API (elle ressemble à « b8e924a8f008b81e« ) et Station-ID est égale à « IPLONOUR3 » pour la station qui m’intéresse. On obtient l’ID de la station en cliquant  sur le nom de la station (flèche rouge dans la copie d’écran ci-dessous). j’accède alors à une page dont l’adresse est https://www.wunderground.com/personal-weather-station/dashboard?ID=IPLONOUR3#history. J’ai donc l’ID.

ID d'une station sur Wunderground

Faire une requête en PHP

Je me suis inspirée de cette question sur StackOverflow pour créer ma requête.

Si je met http://api.wunderground.com/api/APIkey/conditions/q/pws:IPLONOUR3.json (remplacer APIkey par votre clé API) dans un navigateur web, j’obtiens en retour une chaîne JSON :

{
  "response": {
  "version":"0.1",
  "termsofService":"http://www.wunderground.com/weather/api/d/terms.html",
  "features": {
  "conditions": 1
  }
	}
  ,	"current_observation": {
		"image": {
		"url":"http://icons.wxug.com/graphics/wu2/logo_130x80.png",
		"title":"Weather Underground",
		"link":"http://www.wunderground.com"
		},
		"display_location": {
		"full":"Ploneour-Lanvern, France",
		"city":"Ploneour-Lanvern",
		"state":"",
		"state_name":"France",
		"country":"FR",
		"country_iso3166":"FR",
		"zip":"00000",
		"magic":"34",
		"wmo":"07201",
		"latitude":"47.900333",
		"longitude":"-4.287129",
		"elevation":"60.00000000"
		},
		"observation_location": {
		"full":"Rue François de Châteaubriand, Plonéour-Lanvern, ",
		"city":"Rue François de Châteaubriand, Plonéour-Lanvern",
		"state":"",
		"country":"FR",
		"country_iso3166":"FR",
		"latitude":"47.900333",
		"longitude":"-4.287129",
		"elevation":"206 ft"
		},
		"estimated": {
		},
		"station_id":"IPLONOUR3",
		"observation_time":"Last Updated on August 15, 3:01 PM CEST",
		"observation_time_rfc822":"Mon, 15 Aug 2016 15:01:45 +0200",
		"observation_epoch":"1471266105",
		"local_time_rfc822":"Mon, 15 Aug 2016 15:04:53 +0200",
		"local_epoch":"1471266293",
		"local_tz_short":"CEST",
		"local_tz_long":"Europe/Paris",
		"local_tz_offset":"+0200",
		"weather":"Clear",
		"temperature_string":"79.9 F (26.6 C)",
		"temp_f":79.9,
		"temp_c":26.6,
		"relative_humidity":"48%",
		"wind_string":"Calm",
		"wind_dir":"NE",
		"wind_degrees":50,
		"wind_mph":0.6,
		"wind_gust_mph":"5.0",
		"wind_kph":1.0,
		"wind_gust_kph":"8.0",
		"pressure_mb":"1018",
		"pressure_in":"30.06",
		"pressure_trend":"0",
		"dewpoint_string":"59 F (15 C)",
		"dewpoint_f":59,
		"dewpoint_c":15,
		"heat_index_string":"81 F (27 C)",
		"heat_index_f":81,
		"heat_index_c":27,
		"windchill_string":"NA",
		"windchill_f":"NA",
		"windchill_c":"NA",
		"feelslike_string":"81 F (27 C)",
		"feelslike_f":"81",
		"feelslike_c":"27",
		"visibility_mi":"N/A",
		"visibility_km":"N/A",
		"solarradiation":"--",
		"UV":"-1","precip_1hr_string":"-999.00 in ( 0 mm)",
		"precip_1hr_in":"-999.00",
		"precip_1hr_metric":" 0",
		"precip_today_string":"0.00 in (0 mm)",
		"precip_today_in":"0.00",
		"precip_today_metric":"0",
		"icon":"clear",
		"icon_url":"http://icons.wxug.com/i/c/k/clear.gif",
		"forecast_url":"http://www.wunderground.com/global/stations/07201.html",
		"history_url":"http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=IPLONOUR3",
		"ob_url":"http://www.wunderground.com/cgi-bin/findweather/getForecast?query=47.900333,-4.287129",
		"nowcast":""
	}
}

Les données qui m’intéressent sont :

  • « current_observation » -> « display_location » -> « city »
  • « current_observation » -> « temp_c »
  • « current_observation » -> « relative_humidity »
  • « current_observation » -> « wind_gust_kph »
  • « current_observation » -> « forecast_url »

En PHP, j’ajoute le code suivant à mon fichier index.php :

	<section class="capteurs">

		<h3>wunderground</h3>
		<?php

		// http://stackoverflow.com/questions/20044579/how-to-get-a-value-from-wunderground-json
		$json_string = file_get_contents("http://api.wunderground.com/api/APIkey/conditions/q/pws:IPLONOUR3.json");
		$parsed_json = json_decode($json_string);
		$location = $parsed_json->{'current_observation'}->{'display_location'}->{'city'};
		$temp_c = $parsed_json->{'current_observation'}->{'temp_c'};
		$humidity = $parsed_json->{'current_observation'}->{'relative_humidity'};
		$wind_gust_km = $parsed_json->{'current_observation'}->{'wind_gust_kph'};
		$forecast_url = $parsed_json->{'current_observation'}->{'forecast_url'};
		
		echo "A ${location} : ${temp_c} °C, ${humidity} HR, rafales à ${wind_gust_km} km/h - <a target='_blank' href='${forecast_url}'>prévisions</a>. \n";

		?>
	</section>

Et maintenant mon tableau de bord affiche la température, l’humidité, la vitesse des rafales de vent et un lien vers les prévisions météo locales :

Affichage PHP de données via l'API Wunderground

Et maintenant ?

Je vais maintenant intégrer une donnée issue de ma box domotique Eedomus. Ce sera l’objet du second article de cette série .