"OK Google, ouvre le volet de la chambre" 0

Do It Yourself
Partager :

I. Objectif


Si vous avez suivi le tutoriel pour contrôler vos volets depuis votre PC (article disponible ici : https://www.romainpiquard.fr/article-133-controler-ses-volets-somfy-avec-un-arduino.php), alors vous allez être ravi de pouvoir maintenant les contrôler via une page web ou même via votre voix si vous possédez un Google Home ou Google Assistant sur votre mobile.

Voici le schéma final que nous obtiendrons à la fin de cet article :



Cet article va donc être découpé en plusieurs parties :
  1. Créer le projet Firebase pour héberger la base de données, les fonctions et la page web
  2. Déployer les fonctions d'authentification/commandes de volets sur Firebase
  3. Déployer la page web sur Firebase pour contrôler les volets depuis un navigateur
  4. Créer le projet sur Google Actions permettant d'ajouter notre application "Controle des volets" à Google Home
  5. Déployer sur le Raspberry Pi une petite application qui va lire la base de données Firebase afin d'envoyer les commandes à l'Arduino
  6. Associer l'application "Controle des volets" à Google Home


II. Matériel requis

 
  • Arduino connecté au transmetteur 433.42 MHz préparé dans l'article précédent
  • Raspberry Pi Disponible sur Amazon
 

III. Procédure

III. 1. Branchement de l'Arduino sur le Raspberry Pi

 
Je ne détaillerai pas dans cet article comment configurer le Raspbery Pi pour sa première installation ni comment il est possible de s'y connecter en accès à distance mais cela sera nécessaire si vous ne possédez pas de clavier/souris et d'écran à brancher sur celui-ci.

Branchez tout simplement en USB l'Arduino au Raspberry Pi.
Tapez ensuite la commande suivante depuis un terminal sur le Raspberry Pi :

 
ls /dev/tty*

Identifiez le port utilisé par l'Arduino parmi l'ensemble des éléments dans la liste qui s'affiche. En général, il s'agit de /dev/ttyACM0. Notez ceci dans un coin car ça vous sera utile plus tard.
 
Astuce : Si vous n'arrivez pas à identifier l'Arduino dans la liste, lancez la commande une première fois avec l'Arduino débranché, puis une seconde fois avec l'Arduino branché. L'élément ajouté lors de la deuxième commande correspond au port de votre Arduino.
 

III. 2. Création d'un projet sur Firebase


La création d'un projet Firebase va permettre d'héberger notre base de données, quelques fonctions (permettant l'authentification et la gestion des commandes) ainsi qu'une page web pour contrôler les volets à la fois depuis Google Assistant, mais également depuis le navigateur.

Il faut tout d'abord créer un projet sur ce site : https://console.firebase.google.com

Cliquez sur Ajouter un projet et saisissez un nom pour votre projet, par exemple "Controle des volets".



Cliquez enfin sur Créer un projet en bas de la fenêtre.
 

III. 3. Déploiement du projet sur Firebase

III. 3. 1. Initialisation du projet


Le déploiement sur Firebase va se faire depuis un projet Node.js . Pour cela, téléchargez puis installez Node.js en version LTS sur ce site : https://nodejs.org


Une fois installé, depuis une console, tapez les commandes suivantes pour initialiser votre projet :
 
REM Remplacez le chemin ci-dessous par le dossier où vous souhaitez placer le projet
cd "C:\CHEMIN_VERS_LE_DOSSIER_DU_PROJET"

REM Installe le client pour créer un projet Firebase
npm install -g firebase-tools

REM Une fois la commande tapée, saisissez le login/mot de passe de votre compte Google
firebase login

REM Créé le projet firebase
firebase init

Complétez avec les différentes informations demandées.
Il faudra cocher Database (pour stocker l'état des volets), Functions (pour les fonctions de commande de volets et d'authentification) et Hosting (pour la page web qui permettra de contrôler les volets).
Sélectionnez ensuite le projet Firebase que vous avez créé précédemment. Laissez ensuite tous les paramètres par défaut (en désactivant ESLint et en installant toutes les dépendances npm).

Si tout se passe bien, vous devriez avoir dans le dossier de votre projet les fichiers suivants :

 

III. 3. 2. Fonctions


Modifiez le fichier functions/index.js en remplaçant son contenu par le suivant :
'use strict';

const functions = require('firebase-functions');
const {smarthome} = require('actions-on-google');
const util = require('util');
const admin = require('firebase-admin');
admin.initializeApp();
const firebaseRef = admin.database().ref('/');

exports.fakeauth = functions.https.onRequest((request, response) => {
	const responseurl = util.format('%s?code=%s&state=%s', decodeURIComponent(request.query.redirect_uri), 'xxxxxx', request.query.state);
	console.log(responseurl);
	return response.redirect(responseurl);
});

exports.faketoken = functions.https.onRequest((request, response) => {
	const grantType = request.query.grant_type ? request.query.grant_type : request.body.grant_type;
	const secondsInDay = 86400; // 60 * 60 * 24
	const HTTP_STATUS_OK = 200;
	console.log(`Grant type ${grantType}`);

	let obj;
	if (grantType === 'authorization_code') {
		obj = {
			token_type: 'bearer',
			access_token: '123access',
			refresh_token: '123refresh',
			expires_in: secondsInDay,
		};
	} else if (grantType === 'refresh_token') {
		obj = {
			token_type: 'bearer',
			access_token: '123access',
			expires_in: secondsInDay,
		};
	}
	response.status(HTTP_STATUS_OK).json(obj);
});

const app = smarthome({
	debug: true,
	key: '<api-key>',
});

app.onSync((body) => {
	console.log(body);

	return {
		requestId: body.requestId,
		payload: {
			agentUserId: '123',
			devices: [
			{
				id: 'volet-chambre',
				type: 'action.devices.types.BLINDS',
				traits: [
					'action.devices.traits.OpenClose',
				],
				name: {
					defaultNames: ['Volet Chambre'],
					name: 'Volet chambre',
					nicknames: ['Volet de la chambre'],
				},
				willReportState: true,
				attributes: {
					openDirection: ['UP', 'DOWN']
				},
				deviceInfo: {
					manufacturer: 'SOMFY',
					model: 'somfy-blinds',
					hwVersion: '1.0.0',
					swVersion: '1.0.0',
				}
			},
			{
				id: 'volet-salon-gauche',
				type: 'action.devices.types.BLINDS',
				traits: [
					'action.devices.traits.OpenClose',
				],
				name: {
					defaultNames: ['Volet Salon Gauche'],
					name: 'Volet salon gauche',
					nicknames: ['Volet gauche du salon'],
				},
				willReportState: true,
				attributes: {
					openDirection: ['UP', 'DOWN']
				},
				deviceInfo: {
					manufacturer: 'SOMFY',
					model: 'somfy-blinds',
					hwVersion: '1.0.0',
					swVersion: '1.0.0',
				}
			},
			{
				id: 'volet-salon-milieu',
				type: 'action.devices.types.BLINDS',
				traits: [
					'action.devices.traits.OpenClose',
				],
				name: {
					defaultNames: ['Volet Salon Milieu'],
					name: 'Volet salon milieu',
					nicknames: ['Volet milieu du salon'],
				},
				willReportState: true,
				attributes: {
					openDirection: ['UP', 'DOWN']
				},
				deviceInfo: {
					manufacturer: 'SOMFY',
					model: 'somfy-blinds',
					hwVersion: '1.0.0',
					swVersion: '1.0.0',
				}
			},
			{
				id: 'volet-salon-droit',
				type: 'action.devices.types.BLINDS',
				traits: [
					'action.devices.traits.OpenClose',
				],
				name: {
					defaultNames: ['Volet Salon Droit'],
					name: 'Volet salon droit',
					nicknames: ['Volet droit du salon'],
				},
				willReportState: true,
				attributes: {
					openDirection: ['UP', 'DOWN']
				},
				deviceInfo: {
					manufacturer: 'SOMFY',
					model: 'somfy-blinds',
					hwVersion: '1.0.0',
					swVersion: '1.0.0',
				}
			}]
		}
	};
});

const queryFirebase = (deviceId) => firebaseRef.child(deviceId).once('value')
.then((snapshot) => {
	const snapshotVal = snapshot.val();
	return {
		openDirection: snapshotVal.State.openDirection,
		openPercent: snapshotVal.State.percentageOpen
	};
});

const queryDevice = (deviceId) => queryFirebase(deviceId).then((data) => ({
	openDirection: data.openDirection,
	openPercent: data.openPercent
}));

app.onQuery((body) => {
	console.log(body);

	const {requestId} = body;
	const payload = {
		devices: {},
	};
	const queryPromises = [];
	for (const input of body.inputs) {
		for (const device of input.payload.devices) {
			const deviceId = device.id;
			queryPromises.push(
				queryDevice(deviceId)
				.then((data) => {
					payload.devices[deviceId] = {
						openState : [data]
					};
					return null;
				})
			);
		}
	}
	return Promise.all(queryPromises).then((values) => ({
			requestId: requestId,
			payload: payload,
		})
	);
});

app.onExecute((body) => {
	console.log(body);

	const {requestId} = body;
	const payload = {
		commands: [{
			ids: [],
			status: 'SUCCESS',
			states: {
				online: true,
			}
		}]
	};
	for (const input of body.inputs) {
		for (const command of input.payload.commands) {
			for (const device of command.devices) {
				const deviceId = device.id;
				payload.commands[0].ids.push(deviceId);
				for (const execution of command.execution) {
					const execCommand = execution.command;
					const params = execution.params;
					
					switch (execCommand) {
						case 'action.devices.commands.OpenClose':
							if (params.hasOwnProperty('openDirection')) {
								firebaseRef.child(deviceId).child('State').update({
									openDirection: params.openDirection
								});
								payload.commands[0].states.openDirection = params.openDirection;
							}
							if (params.hasOwnProperty('openPercent')) {
								firebaseRef.child(deviceId).child('State').update({
									percentageOpen: params.openPercent
								});
								payload.commands[0].states.openPercent = params.openPercent;
							}
						break;
					}
				}
			}
		}
	}
	return {
		requestId: requestId,
		payload: payload,
	};
});

exports.smarthome = functions.https.onRequest(app);

exports.requestsync = functions.https.onRequest((request, response) => {
	console.info('Request SYNC for user 123');
	app.requestSync('123')
	.then((res) => {
		console.log('Request sync completed');
		response.json(res.data);
		return null;
	}).catch((err) => {
		console.error(err);
	});
});

exports.reportstate = functions.database.ref('{deviceId}').onWrite((event) => {
	console.info('Firebase write event triggered this cloud function');
});

En adaptant les lignes 53 à 140, vous pouvez ajouter/supprimer autant de volets que vous le souhaitez selon votre configuration.

Ce code va simplement définir les différentes fonctions dont voici un récapitulatif :
  • fakeauth : Simule une authentification OAuth.
  • faketoken : Génère de faux token pour l'authentification OAuth.
  • smarthome : Gère les commandes pour nos volets. Cette fonction retourne l'état des volets, envoie la commande aux volets, ...
  • requestsync : Nous ne l'utiliserons pas dans le cas présent mais cette fonction permet de gérer l'ajout de nouveaux volets à notre Google Home sans devoir désassocier et réassocier l'application au Google Home.
 

III. 3. 3. Page web


Modifiez le fichier public/index.html en remplassant son contenu par le suivant :
 
<!doctype html>
<html>
	<head>
		<meta charset="utf-8">
		<title>Volets</title>
		<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
		<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/css/materialize.min.css">
		<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
		<style>
			h1 { font-size: 2.5rem; margin: 0 0 30px 0; }
			h2 { font-size: 1.5rem; }
			span.badge { border-radius: 2px; background-color: #26a69a; color: #fff; }
			
			input[type=range]::-webkit-slider-thumb { background-color: #ab47bc; }
			input[type=range]::-moz-range-thumb { background-color: #ab47bc; }
			input[type=range]::-ms-thumb { background-color: #ab47bc; }

			input[type=range] + .thumb { background-color: #dedede; }
			input[type=range] + .thumb.active .value { color: #ab47bc; }
		</style>
	</head>
	<body>
		<div class="container">
			<div class="row">
				<form action="#">
					<div class="col s12 m4">
						<div class="card-panel grey lighten-3">
							<h1>Commandes unitaires</h1>
							<div id="volet-salon-gauche">
								<h2>Volet gauche du salon <span class="badge currentValue"></span></h2>
								<div class="row center-align">
									<div class="col s6">
										<a class="btn-move-blind btn-floating btn-large waves-effect waves-light" data-blind="volet-salon-gauche" data-percentage="100"><i class="material-icons left">arrow_upward</i>Monter</a>
									</div>
									<div class="col s6">
										<a class="btn-move-blind btn-floating btn-large waves-effect waves-light blue lighten-1" data-blind="volet-salon-gauche" data-percentage="0"><i class="material-icons left">arrow_downward</i>Descendre</a>
									</div>
								</div>
								<div class="row center-align">
									<div class="col s8 range-field">
										<input class="purple lighten-1" type="range" min="0" max="100" step="10" value="0" />
									</div>
									<div class="col s4">
										<a class="btn-validate-percentage btn-floating waves-effect waves-light purple lighten-1" data-blind="volet-salon-gauche"><i class="material-icons left">done</i>Valider</a>
									</div>
								</div>
							</div>
							<div id="volet-salon-milieu">
								<h2>Volet milieu du salon <span class="badge currentValue"></span></h2>
								<div class="row center-align">
									<div class="col s6">
										<a class="btn-move-blind btn-floating btn-large waves-effect waves-light" data-blind="volet-salon-milieu" data-percentage="100"><i class="material-icons left">arrow_upward</i>Monter</a>
									</div>
									<div class="col s6">
										<a class="btn-move-blind btn-floating btn-large waves-effect waves-light blue lighten-1" data-blind="volet-salon-milieu" data-percentage="0"><i class="material-icons left">arrow_downward</i>Descendre</a>
									</div>
								</div>
								<div class="row center-align">
									<div class="col s8 range-field">
										<input class="purple lighten-1" type="range" min="0" max="100" step="10" value="0" />
									</div>
									<div class="col s4">
										<a class="btn-validate-percentage btn-floating waves-effect waves-light purple lighten-1" data-blind="volet-salon-milieu"><i class="material-icons left">done</i>Valider</a>
									</div>
								</div>
							</div>
							<div id="volet-salon-droit">
								<h2>Volet droit du salon <span class="badge currentValue"></span></h2>
								<div class="row center-align">
									<div class="col s6">
										<a class="btn-move-blind btn-floating btn-large waves-effect waves-light" data-blind="volet-salon-droit" data-percentage="100"><i class="material-icons left">arrow_upward</i>Monter</a>
									</div>
									<div class="col s6">
										<a class="btn-move-blind btn-floating btn-large waves-effect waves-light blue lighten-1" data-blind="volet-salon-droit" data-percentage="0"><i class="material-icons left">arrow_downward</i>Descendre</a>
									</div>
								</div>
								<div class="row center-align">
									<div class="col s8 range-field">
										<input class="purple lighten-1" type="range" min="0" max="100" step="10" value="0" />
									</div>
									<div class="col s4">
										<a class="btn-validate-percentage btn-floating waves-effect waves-light purple lighten-1" data-blind="volet-salon-droit"><i class="material-icons left">done</i>Valider</a>
									</div>
								</div>
							</div>
							<div id="volet-chambre">
								<h2>Volet de la chambre <span class="badge currentValue"></span></h2>
								<div class="row center-align">
									<div class="col s6">
										<a class="btn-move-blind btn-floating btn-large waves-effect waves-light" data-blind="volet-chambre" data-percentage="100"><i class="material-icons left">arrow_upward</i>Monter</a>
									</div>
									<div class="col s6">
										<a class="btn-move-blind btn-floating btn-large waves-effect waves-light blue lighten-1" data-blind="volet-chambre" data-percentage="0"><i class="material-icons left">arrow_downward</i>Descendre</a>
									</div>
								</div>
								<div class="row center-align">
									<div class="col s8 range-field">
										<input class="purple lighten-1" type="range" min="0" max="100" step="10" value="0" />
									</div>
									<div class="col s4">
										<a class="btn-validate-percentage btn-floating waves-effect waves-light purple lighten-1" data-blind="volet-chambre"><i class="material-icons left">done</i>Valider</a>
									</div>
								</div>
							</div>
						</div>
					</div>
					<div class="col s12 m4">
						<div class="card-panel grey lighten-2">
							<h1>Volets du salon</h1>
							<div id="volets-salon">
								<div class="row center-align">
									<div class="col s6">
										<a class="btn-move-blind btn-floating btn-large waves-effect waves-light" data-blind="volet-salon-gauche,volet-salon-milieu,volet-salon-droit" data-percentage="100"><i class="material-icons left">arrow_upward</i>Monter</a>
									</div>
									<div class="col s6">
										<a class="btn-move-blind btn-floating btn-large waves-effect waves-light blue lighten-1" data-blind="volet-salon-gauche,volet-salon-milieu,volet-salon-droit" data-percentage="0"><i class="material-icons left">arrow_downward</i>Descendre</a>
									</div>
								</div>
								<div class="row center-align">
									<div class="col s8 range-field">
										<input class="purple lighten-1" type="range" min="0" max="100" step="10" value="0" />
									</div>
									<div class="col s4">
										<a class="btn-validate-percentage btn-floating waves-effect waves-light purple lighten-1" data-blind="volet-salon-gauche,volet-salon-milieu,volet-salon-droit"><i class="material-icons left">done</i>Valider</a>
									</div>
								</div>
							</div>
						</div>
					</div>
					<div class="col s12 m4">
						<div class="card-panel grey lighten-1">
							<h1>Tous les volets</h1>
							<div id="tous-volets">
								<div class="row center-align">
									<div class="col s6">
										<a class="btn-move-blind btn-floating btn-large waves-effect waves-light" data-blind="volet-salon-gauche,volet-salon-milieu,volet-salon-droit,volet-chambre" data-percentage="100"><i class="material-icons left">arrow_upward</i>Monter</a>
									</div>
									<div class="col s6">
										<a class="btn-move-blind btn-floating btn-large waves-effect waves-light blue lighten-1" data-blind="volet-salon-gauche,volet-salon-milieu,volet-salon-droit,volet-chambre" data-percentage="0"><i class="material-icons left">arrow_downward</i>Descendre</a>
									</div>
								</div>
								<div class="row center-align">
									<div class="col s8 range-field">
										<input class="purple lighten-1" type="range" min="0" max="100" step="10" value="0" />
									</div>
									<div class="col s4">
										<a class="btn-validate-percentage btn-floating waves-effect waves-light purple lighten-1" data-blind="volet-salon-gauche,volet-salon-milieu,volet-salon-droit,volet-chambre"><i class="material-icons left">done</i>Valider</a>
									</div>
								</div>
							</div>
						</div>
					</div>
				</form>
			</div>
		</div>
	
		<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
	
		<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/js/materialize.min.js"></script>
		
		<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/loadingoverlay.min.js"></script>
		
		<script src="/__/firebase/4.8.1/firebase-app.js"></script>
		<script src="/__/firebase/4.8.1/firebase-auth.js"></script>
		<script src="/__/firebase/4.8.1/firebase-database.js"></script>
		<script src="/__/firebase/init.js"></script>
		
		<script>
			$(function() {
				//Affiche les valeurs actuelles des volets
				firebase.database().ref('/').on('value', function(snapshot) {
					jQuery.each(snapshot.val(), function(blind, blindSettings) {
						var divBlind = $('#' + blind);
						
						if (divBlind.length) {
							divBlind.find('.currentValue').text(blindSettings.State.percentageOpen);
							divBlind.find('.range-field').find('input').val(blindSettings.State.percentageOpen);
						}
					});
				});
				
				$('.btn-move-blind').on('click', function()
				{
					$.LoadingOverlay('show');
					
					var blind = $(this).data('blind');
					var blinds = blind.split(',');
					var percentage = $(this).data('percentage');
					
					let pkg = {
						percentageOpen: percentage
					};

					$.each(blinds, function(i, currentBlind) {
						firebase.database().ref('/').child(currentBlind).child('State').update(pkg);
					});
					
					$.LoadingOverlay('hide');
				});
				
				$('.btn-validate-percentage').on('click', function()
				{
					$.LoadingOverlay('show');
					
					var blind = $(this).data('blind');
					var blinds = blind.split(',');
					var percentage = parseInt($(this).closest('.row').find('input').val(), 10);
					
					let pkg = {
						percentageOpen: percentage
					};

					$.each(blinds, function(i, currentBlind) {
						firebase.database().ref('/').child(currentBlind).child('State').update(pkg);
					});
					
					$.LoadingOverlay('hide');
				});
			});
		</script>
	<body>
</html>

En adaptant les lignes 26 à 152, vous pouvez ajouter/supprimer des volets selon votre configuration et les regrouper comme vous le souhaitez (par pièce, par type de volet, ...)

Le code ci-dessus va nous générer une page web qui aura le rendu suivant :


 

III. 3. 4. Déploiement


Pour finir, envoyez tout votre projet sur firebase via la commande suivante :
 
firebase deploy
 

III. 4. Création d'un projet sur Google Actions


La création du projet sur Google Actions permet de créer l'application "Controle des volets" que nous pourrons par la suite ajouter à notre Google Home.
Pour cela, créez un nouveau projet en allant sur ce site : https://console.actions.google.com

En sélectionnant le champ Projet Name, une liste déroulante avec vos différents projets Google s'affiche. Il faut sélectionner le projet Firebase précédemment créé pour l'associer à notre projet Google Actions.


Sur la page suivante, choisir le type de projet "Home control" puis "Smart home" sur la page suivante. Cela va nous permettre de créer des actions dans notre Google Home spécifiques au contrôle d'objets connectés de notre maison.

Vous devriez alors arriver sur cette page :


Voici comment configurer chacun des onglets présents dans le menu de gauche :
  • Invocation
    • Indiquer dans le champ "Display name" le nom souhaité pour notre service. Personnellement, je l'ai appelé "Volets".
  • Actions
    • Indiquer dans le champ "Add fulfillment URL" l'URL de votre fonction sur Firebase qui va gérer vos smart actions. L'URL peut être obtenue en allant sur https://console.firebase.google.com, en cliquant sur votre projet puis sur Functions dans le menu de gauche. L'URL ressemble à https://**********.cloudfunctions.net/smarthome .
  • Account linking
    • Dans "Account creation", choisir "No, I only want to allow account creation on my website"
    • Dans "Linkin type", choisir "OAuth" puis "Authorization code"
    • Dans "Client information", saisir ABC123 pour le Client ID et le Client secret puis les URLs de vos fonctions Firebase pour les champs "Authorization URL" et "Token URL". Ces URLs peuvent être obtenues en allant sur https://console.firebase.google.com, en cliquant sur votre projet puis sur Functions dans le menu de gauche. Les URLs ressemblent à https://**********.cloudfunctions.net/fakeauth et https://**********.cloudfunctions.net/faketoken .
 

III. 5. Installation de l'application de commande de l'Arduino sur le Raspberry Pi


Si vous avez tout bien suivi jusque là, nous avons maintenant une page web ainsi qu'une action pour notre Google Home qui vont tous les deux modifier la base de données Firebase avec le nouvel état à donner à nos volets. Il ne reste donc plus qu'à faire tourner une application qui va lire en continu cette base de données et qui va transmettre à l'Arduino les commandes à exécuter.

Nous allons faire cela avec une application NodeJS qui va tourner sur le Raspberry Pi.

Depuis un terminal sur le Raspberry Pi, installez NodeJS avec les commandes suivantes :
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
sudo apt-get install -y nodejs

Créez ensuite un dossier dans lequel seront stockés tous les fichiers du projet.
Toujours dans le terminal, accédez au dossier précédemment créé puis tapez la commande suivante :
 
npm init
Différentes informations vous seront demandées :
  • Nom du projet (Par défaut il s'agit du nom du dossier donc faire juste "Entrée")
  • Version ("1.0.0" par défaut donc faire juste "Entrée")
  • Description (Faire juste "Entrée" ou saisir une description du projet)
  • Entry point ("index.js" par défaut donc faire juste "Entrée")
  • test command (Faire juste "Entrée")
  • git repository (Faire juste "Entrée")
  • keywords (Faire juste "Entrée")
  • author (Faire juste "Entrée" ou saisir votre nom)
  • license (Faire juste "Entrée")
Puis confirmez le récapitulatif.

Dans le dossier de votre application, créez le fichier index.js avec le contenu suivant :
 
var firebase = require('firebase');
const SerialPort = require('serialport');
const Readline = require('@serialport/parser-readline');
const BlindDuration = 28000; //Durée en millisecondes pour ouvrir ou fermer entièrement un volet
 
const port = new SerialPort('/dev/ttyACM0', {
	baudRate: 115200
});
const parser = port.pipe(new Readline({ delimiter: '\r\n' }));
 
parser.on('data', function (data) {
	console.log('Data :', data);
});
 
firebase.initializeApp({
	'appName': 'Controle des volets',
	'serviceAccount': './service-account.json',
	'authDomain': '**********.firebaseapp.com',
	'databaseURL': 'https://**********.firebaseio.com/'
});
 
var firebaseRef = firebase.app().database().ref('/');
 
firebaseRef.child('volet-salon-gauche').on('value', (snapshot) => {
	manageNewValue(snapshot, 'volet-salon-gauche', 0);
});
 
firebaseRef.child('volet-salon-milieu').on('value', (snapshot) => {
	manageNewValue(snapshot, 'volet-salon-milieu', 1);
});
 
firebaseRef.child('volet-salon-droit').on('value', (snapshot) => {
	manageNewValue(snapshot, 'volet-salon-droit', 2);
});
 
firebaseRef.child('volet-chambre').on('value', (snapshot) => {
	manageNewValue(snapshot, 'volet-chambre', 3);
});

function manageNewValue(snapshot, blindName, blindID) {
	if (snapshot.exists()) {
		const snapshotVal = snapshot.val();
		const currentPercentage = snapshotVal.State.currentPercentage;
		const percentageOpen = snapshotVal.State.percentageOpen;
		
		if (currentPercentage != percentageOpen) {
			var duration = Math.abs(percentageOpen-currentPercentage) / 100 * BlindDuration;
 
			if (currentPercentage > percentageOpen) {
				executeCommand('d', blindID);
			} else {
				executeCommand('m', blindID);
			}
			
			if (percentageOpen != 100 && percentageOpen != 0)  {
				setTimeout(updateCurrentPercentage, duration, blindName, percentageOpen, 's', blindID);
			} else {
				setTimeout(updateCurrentPercentage, duration, blindName, percentageOpen, '', '');
			}
		}
	}
}

function executeCommand(command, blindID) {
	port.write(command + blindID);
}
 
function updateCurrentPercentage(device, value, command, blindID) {
	firebaseRef.child(device).child('State').update({
		currentPercentage: value
	});
	
	if (command) {
		executeCommand(command, blindID);
	}
}

Dans le code précédent, il faut modifier la durée d'ouverture/fermeture d'un volet à la ligne 4 (en millisecondes). Si la durée d'ouverture/fermeture est différente entre chaque volet, il faudra adapter le code en conséquence.
Il faut également modifier le port série de la ligne 6 si vous avez trouvé une autre valeur lors de la commande saisie dans le chapitre III. 1. de cet article.
Remplacez les astérisques des lignes 18 et 19 avec l'ID de votre projet Firebase. L'ID est visible sur la page suivante https://console.firebase.google.com/ (sous le nom de votre projet).
Pour finir, adaptez les lignes 24 à 38 selon votre configuration.
Sauvegardez enfin les modifications effectuées dans le fichier index.js.

Saisissez les commande suivantes pour ajouter les dépendances au projet :
npm install firebase
npm install serialport

Il faut maintenant générer un fichier depuis Firebase avec les informations d'authentification qui permettent à notre application d'accès à la base de données.
Pour cela, rendez-vous sur https://console.firebase.google.com puis cliquez sur votre projet.
Allez dans Settings/Utilisateurs et autorisations puis cliquez sur Comptes de service.
Téléchargez le fichier JSON en cliquant sur Générer une nouvelle clé privée et enregistrez-le sous le nom "service-account.json" dans le dossier du projet.

Pour finir, tapez la commande suivante pour lancer l'application :
sudo node index.js
 
Note : Pour éviter de devoir retaper la commande ci-dessus à chaque redémarrage du Raspberry Pi, il est possible de la lancer automatiquement au démarrage.

Pour cela, tapez la commande suivante dans un terminal :
sudo nano /etc/rc.local
Juste avant la ligne "exit 0", tapez la commande. Par exemple :
sudo node /home/pi/volets/index.js &
Puis sauvegardez avec Ctrl+O puis "Entrée" et enfin Ctrl+X pour fermer l'éditeur.
 

III. 6. Association de l'application avec Google Home


Cette dernière étape est la plus simple. Maintenant que tout est correctement configuré, il suffit d'ajouter l'application "Volets" créé précédemment sur Google Actions.

Pour cela, lancez l'application Google Home sur votre smartphone (disponible sur le Play Store de Google ou l'App Store d'Apple) :


Sur cet écran, allez dans Ajouter. Vous obtiendrez ensuite l'écran suivant :



Cliquez sur le bouton Configurer un appareil pour afficher l'écran suivant :



Pour finir, choisissez Vous avez déjà configuré des appareils ? puis sélectionnez votre application [test] Volets pour l'ajouter à vos appareils connectés :



 

IV. Utilisation


Vous avez dorénavant deux possibilités pour gérer vos volets : via la page web ou à la voix avec un Google Home ou Google Assistant sur votre téléphone.
 

IV. 1. Page web


Rendez-vous sur la page que vous avez créé précédemment dans Firebase.
 
Note : L'URL peut être obtenue en allant sur https://console.firebase.google.com, en cliquant sur votre projet puis sur Hosting dans le menu de gauche. L'URL ressemble à https://**********.firebaseapp.com .

Via les boutons "Flèche vers le haut" ou "Flèche vers le bas" de chaque volet, vous pouvez demander l'ouverture ou la fermeture complète d'un volet. Ces mêmes boutons sont disponibles pour ouvrir/fermer l'ensemble des volets du salon, ou de la maison complète.
Avec les curseurs, vous pouvez demander à ce que les volets s'ouvrent à 10%, 20%, ... 0% correspond à l'état fermé tandis que 100% est l'état ouvert.

Attention : Aucune sécurité n'a été mise en place sur cette page web. N'importe qui ayant obtenu l'URL de la page peut contrôler vos volets. Je vous laisse maître de la sécurisation de votre page web.
 

IV. 2. Contrôle par la voix


Vous pouvez contrôler individuellement chaque volet de votre maison via les commandes vocales suivantes :
 
Commandes disponibles
Commande vocale Résultat
OK Google, ouvre Volet gauche du salon Ouvre entièrement le volet gauche du salon
OK Google, ferme Volet droit du salon Ferme entièrement le volet droit du salon
OK Google, descend Volet de la chambre Descend de 10% le volet de la chambre
OK Google, monte Volet milieu du salon Monte de 10% le volet milieu du salon
OK Google, ouvre à 20% Volet de la chambre Ouvre le volet de la chambre à 20%
OK Google, monte de 30% Volet gauche du salon Monte le volet gauche du salon de 30%
OK Google, ouvre à moitié Volet droit du salon Ouvre le volet droit du salon à 50%


En utilisant les routines, vous pouvez également ouvrir/fermer l'ensemble de vos volets. Par exemple, créez une routine se déclenchant sur la phrase "Ferme tous les volets" et qui va exécuter les actions "Ferme Volet gauche du salon", "Ferme Volet milieu du salon", "Ferme Volet droit du salon" et "Ferme Volet de la chambre".
 

V. Sources

 

Commentaires 0