La météo vocale avec du NodeJS avec un peu de MP3 et du ftp

janvier 31, 2018

Dans cet article je vais essayer de vous faire partager un petit outil qui est en place sur ce site : une annonce vocale de la météo.

Mais d’ou vient cette idée ?

Assez simple en parcourant mon compte twitter et le fil des actualités , j’avais trouvé un tweet de  @pirmax faisant référence a un article sur le site Yubigeek.

Un article proposait de créer un « bot » (un robot) qui dicterait la météo vocalement.

La mise en oeuvre :

Je ne vais pas reprendre tout l’article de Maxence mais en gros , il vous faut

coté matériel :

  • Un raspberry Pi
  • un petit haut parleur  (ou des écouteurs « pour tester »)

Coté système :

  • Une distribution Raspberry PI
  • NodeJS 
  • Quelques dépendances de NodeJS pour faire fonctionner ce bot

Et donc si tout se passe bien , vous aurez une belle voix qui vous annoncera la météo tous les X temps (en fonction de votre cron intégré au script, j’y reviens plus bas).

Jusque la , tout fonctionne … oui mais  bon entendre une voix robotisée « réciter » sa météo sur haut parleur ca devient lassant.

Alors que pouvons-faire de plus  ?

En cherchant un peu, et si vous connaissez un peu le service de traduction de Google , il est tout à fait possible de télécharger une phrase dictée au format MP3 

Je voulais donc trouver un système qui permettrait non plus de dicter la phrase mais la télécharger au format MP3.

J’ai commencé à chercher du coté de NodeJS et je suis tombé sur  ceci  : https://github.com/zlargon/google-tts

Et voici un exemple   ici : https://github.com/zlargon/google-tts/blob/master/example/download.js

Le principe : 

Ca initialise diverses librairies et on utilise « Google-TTS » (Google Text to speech) et on télécharge la phrase passée à la fonction :


// start
googleTTS('hello')
.then(function (url) {
console.log(url); // https://translate.google.com/translate_tts?...
var dest = path.resolve(__dirname, 'hello.mp3'); // file destination
console.log('Download to ' + dest + ' ...');
return downloadFile(url, dest);
})
.then(function () {
console.log('Download success');
})
.catch(function (err) {
console.error(err.stack);
});

Mais il fallait « merger » les 2 scripts : le premier qui permettait  de récupérer une phrase  et le deuxième qui lui s’occupe du téléchargement pour le stocker en local sur le raspberry et egalement l’option suplémentaire uploader le fichier mp3 sur ce site.

J’ai donc utilisé un ftp simple en nodeJS dans la boucle du scheduler

Voici le script complet :


var request = require('request');
var player = require('play-sound')(opts = {player: "omxplayer"});
var googleTTS = require('google-tts-api');
var schedule = require('node-schedule');
var fs = require('fs');
var path = require('path');
var http = require('http');
var https = require('https');
var urlParse  = require('url').parse;

//------------------------------------------------------------------------------------------------------
// client ftp pour uploads
var FTP  = require('ftp-simple'),
    config = {
        host: 'adresse_de_votre_ftp',
        port: 21,
        user: 'utilisateur',
        password: 'mot_de_passe'
    },
   ftp = FTP.create(config);
//--------------------------------------------------------------------------------------------------------

let downloadFile = (url, dest) => {
  return new Promise(function (resolve, reject) {
    var info = urlParse(url);
    var httpClient = info.protocol === 'https:' ? https : http;
    var options = {
      host: info.host,
      path: info.path,
      headers: {
        'user-agent': 'WHAT_EVER'
      }
    };

    httpClient.get(options, function(res) {
      // check status code
      if (res.statusCode !== 200) {
        reject(new Error('request to ' + url + ' failed, status code = ' + res.statusCode + ' (' + res.statusMessage + ')'));
        return;
      }

      var file = fs.createWriteStream(dest);
      file.on('finish', function() {
        // close() is async, call resolve after close completes.
        file.close(resolve);
      });
      file.on('error', function (err) {
        // Delete the file async. (But we don't check the result)
        fs.unlink(dest);
        reject(err);
      });

      res.pipe(file);
    })
    .on('error', function(err) {
      reject(err);
    })
    .end();
  });
}

let getMp3 = (ville, pathMp3) => {
    request({
        method: 'GET',
        url: 'http://www.prevision-meteo.ch/services/json/' + ville,
        headers: {
            'cache-control': 'no-cache'
        }
    }, function (error, response, body) {
        var b = JSON.parse(body);
        var name = b.city_info.name;
        var sunrise = b.city_info.sunrise;
        var sunset = b.city_info.sunset;
        var day_long = b.fcst_day_0.day_long;
        var tmin = b.fcst_day_0.tmin;
        var tmax = b.fcst_day_0.tmax;
        // var condition = b.fcst_day_0.condition;
 	var wnd_spd = b.current_condition.wnd_spd;
  	var condition = b.current_condition.condition;
        var hour = b.current_condition.hour;
        
		//ça parle
        /*speak("Bonjour, nous sommes " + day_long + ".", function () {
            speak("Aujourd'hui, à " + name + ", le temps sera " + condition + " avec une température minimum de " + tmin + "° et une température maximum de " + tmax + "°.", function () {
                speak("Le soleil se lèvera à " + sunrise + " et se couchera à " + sunset + ".", function () {
                    speak("Bonne journée à tous !", function () {
                        console.log('La météo a été récitée !');
                    });
                });
            });
        });*/
		
		//ça DL
		googleTTS(`Bonjour,aujourd'hui ${day_long}, à ${name}, a ${hour} le temps est  ${condition} avec une température mini de ${tmin}° et une température maxi de ${tmax }°, le vent souffle actuellement a ${wnd_spd} kilometre/heure.`, 'fr')
		.then(function (url) {
			console.log(url); // https://translate.google.com/translate_tts?...

			console.log('Download to ' + pathMp3 + ' ...');

			return downloadFile(url, pathMp3);
		})
		.then(function () {
			console.log('Download success');
		})
		.catch(function (err) {
			console.error(err.stack);
		});
    });
}

let ville = 'pont-Scorff';
let destMp3 = path.resolve(__dirname , 'meteo-du-jour.mp3');
console.log(destMp3);

//getMp3( ville,destMp3 )

 schedule.scheduleJob('00 01 * * * *', () => {
//schedule.scheduleJob('*/1 * * * *', () => {
    getMp3(ville,destMp3)
    
    ftp.upload("/home/pi/meteo-mp3/meteo-du-jour.mp3", "/le_chemin_sur_votre_ftp/meteo-du-jour.mp3", function(err){});
    });

Bon ça fonctionne mais après ?

Mon raspberry est posé dans un coin de mon bureau , connecté au réseau local chez moi et il vit sa vie de « petit » serveur linux  (en fait il redemarre en fonction des coupures de courant).
Pour rappel , j’avais opté pour un raspberry il y a déjà quelques années afin de m’affranchir de la lourdeur du logiciel Windows « Graph Weather » (excellent logiciel) , et surtout de laisser mon ordinateur en fonctionnement 24/24.

C’est au moment de l’achat de ma station WMR200 de chez OREGON que j’avais sauté le pas, (mes appareils de mesure sont connectés à ma base en radio et ma base en USB sur mon raspberry).

Désormais j’utilise le logiciel weewx disponible en Open Source ici aussi : http://www.weewx.com/

Concernant mon script de météo vocale : il suffit que je l’exécute en ligne de commande et le tour est joué sauf que ça ne fonctionne pas de façon autonome et automatique.


# nodejs meteo

il a fallu que je trouve un système qui me permette de m’affranchir également de ma session putty ssh sur mon raspberry.

J’ai donc installé le package pm2  : package NodeJS qui permet d’exécuter un programme NodeJs en mode « service » 

https://www.npmjs.com/package/pm2

il suffit de l’installer (la méthode est relativement simple et bien expliqué sur le site)

et donc ensuite exécuter le programme en mode service 


#pm2 start meteo
#pm2 stop meteo
#pm2 status meteo
#pm2 show meteo

Voici un exemple de quelques commandes  de pm2 : 

Les détails :

Dans le script j’utilise un scheduler (un cron) : ceci me permet d’executer le script à intervalle régulier.
Pour vos tests passer le scheduler à « toutes les minutes » et puis en production faites votre choix , pour ma part je l’ai passé à toutes les heures.


//Toutes les  minutes  :
schedule.scheduleJob('*/1 * * * *', () => {
....

//Toutes les heures :
schedule.scheduleJob('00 01 * * * *', () => {

Les explications pour le scheduler :
https://www.npmjs.com/package/node-schedule

Autre chose pour le MP3, j’ai rajouté également un paramètre dans la fonction de google-tts à la fin , on rajoute l’argument fr sinon votre MP3 sera légèrement anglophone…


googleTTS(`Bonjour,aujourd'hui ${day_long}, à ${name}, a ${hour} le temps est  ${condition} avec une température mini de ${tmin}° et une température maxi de ${tmax }°, le vent souffle actuellement a ${wnd_spd} kilometre/heure.`, 'fr')

Une autre chose importante : ne pas dépasser 200 caractères pour votre phrase , c’est la limite imposée par Google …

Le package pour le ftp :

https://www.npmjs.com/package/ftp-simple

Mise en place du lecteur Audio  :

J’ai utilisé un simple code HTML5  :

Voila c’est en place , encore un truc qui ne sert a rien mais indispensable !

N’hésitez pas à partager !
Un grand Merci a Dim , le roi du NodeJS !!! (si un jour il passe par là, car il est l’auteur de 90% du code)

 

Mise a jour le 7 decembre 2018 : suite a une maj de Clef sur Google Translate , le script ne fonctionnait plus

Infos trouvées ici : https://github.com/noelportugal/google-home-notifier/issues/46

Modifer le fichier package.json pour upgrader la version de google-tts


root@raspberrypi:/home/pi/meteo-mp3# more package.json
{
  "name": "meteo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node meteo.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "google-tts-api": "0.0.4",
    "node-schedule": "^1.3.0",
    "play-sound": "^1.1.2",
    "request": "^2.83.0"
  }
}

Faire un


#npm install google-tts-api --save 

A bientôt , et pensée pour Dim