Créer une extension WordPress – Partie 1

Créer une extension WordPress – Partie 1

A force de travailler sur WordPress, j’ai appris pas mal de choses. Pour continuer à m’améliorer, il est essentiel (pour moi au moins) de formaliser l’état de mes connaissances. J’entame donc une nouvelle série d’articles sur la création d’extensions (plugin) pour WordPress.

Dans ce premier article de la série , j’explique comment créer l’extension de base, la version « 0.1 » que j’améliorerai ensuite. Cette extension de base doit contenir les fichiers de base, les répertoires nécessaires, les éléments d’internationalisation, …

Mon « environnement de développement »

Je travaille sur un PC Windows 10 équipé avec notepad++ pour l’édition des fichiers et filezilla pour l’échange FTP des fichiers. Filezilla est connecté à un hébergement web mutualisé sur OVH.

J’utilise aussi GitHub pour Windows, qui me permet de gérer mes versions dans un dépôt GitHub.

Lorsque je développe, j’interviens sur un site de test, qui n’est pas indexé par les robots des moteurs de recherche. Ce site de test contient au minimum les extensions activées suivantes, pour le débogage :

  • Debug Bar
  • Query Monitor

Selon les utilisations j’y ajouter d’autres extensions liées à debug bar, telles que : Debug Bar Actions and Filters Addon, Debug Bar Console, Debug Bar Custom Info, Debug Bar Post Types et Debug Bar Post Types. Il arrive aussi que j’ajoute « User Switching » lorsque j’ai besoin de tester avec différents rôles d’utilisateur.

L’extension de base (V 0.1)

Créer la structure

Avec Filezilla, je crée le répertoire clea-add-button  dans /wp-content/plugins/.

Puis je crée les sous-répertoires suivants :

  • admin pour tous les scripts relatifs au « back office » de WordPress
  • css pour les feuilles de style (côté front ou back office)
  • images
  • includes pour les scripts qui assurent la fonction principale de l’extension
  • js pour les scripts en JavaScript
  • languages pour les traductions de l’extension.

Créer les fichiers de base

Une extension doit au minimum contenir un fichier PHP dans le répertoire principal. Dans mon cas c’est clea-add-button.php . J’y ajoute un README.md  car c’est une bonne habitude de noter les changements au fur et à mesure de leur réalisation. Pour son contenu, je m’inspire de la mise en forme de cet exemple sur wordpress.org.

clea-add-button.php contient le code suivant :

<?php
/**
* Plugin Name: Clea add button
* Plugin URI:  https://knowledge.parcours-performance.com
* Description: Add a custom button at the end of each post
* Author:      Anne-Laure Delpech
* Author URI:  https://knowledge.parcours-performance.com
* License:     GPL2
* Domain Path: /languages
* Text Domain: clea-add-button
* 
* @package		clea-presentation
* @version		0.1.0
* @author 		Anne-Laure Delpech
* @copyright 	Copyright (c) 2016 Anne-Laure Delpech
* @link			https://github.com/aldelpech/CLEA-presentation
* @license 		http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* @since 		0.1.0
*/
 
// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
	die;
}

/*----------------------------------------------------------------------------*
 * Path to files
 * @since 0.7.0
 *----------------------------------------------------------------------------*/

define( 'CLEA_ADD_BUTTON_FILE', __FILE__ );
define( 'CLEA_ADD_BUTTON_BASENAME', plugin_basename( CLEA_ADD_BUTTON_FILE ));
define( 'CLEA_ADD_BUTTON_DIR_PATH', plugin_dir_path( CLEA_ADD_BUTTON_FILE ));
define( 'CLEA_ADD_BUTTON_DIR_URL', plugin_dir_url( CLEA_ADD_BUTTON_FILE ));
	

/********************************************************************************
* appeler d'autres fichiers php et les exécuter
* @since 0.1
********************************************************************************/	
	
// charger des styles, fonts ou scripts correctement
require_once CLEA_ADD_BUTTON_DIR_PATH . 'includes/clea-add-button-enqueues.php'; 

// internationalisation de l'extension
require_once CLEA_ADD_BUTTON_DIR_PATH . 'includes/clea-add-button-i18n.php'; 


/******************************************************************************
* Actions à réaliser à l'initialisation et l'activation du plugin
* @since 0.1 
******************************************************************************/
	
function clea_add_button_activation() {

}
register_activation_hook(__FILE__, 'clea_add_button_activation');
	
/*----------------------------------------------------------------------------*
 * deactivation and uninstall
 *----------------------------------------------------------------------------*/
/* upon deactivation, wordpress also needs to rewrite the rules */
register_deactivation_hook(__FILE__, 'clea_add_button_deactivation');

function clea_add_button_deactivation() {
	
}

// register uninstaller
register_uninstall_hook(__FILE__, 'clea_add_button_uninstall');

function clea_add_button_uninstall() {    
	// actions to perform once on plugin uninstall go here
	// remove all options and custom tables
	
	$option_name = 'clea_add_button';
 
	delete_option( $option_name );
	 
	// For site options in Multisite
	delete_site_option( $option_name );  
	
}

On notera que ce fichier va ouvrir deux autres scripts php, qui sont dans le sous-répertoire  ‘/includes.

On notera également qu’il n’y a rien dans les fonctions à utiliser à l’activation de l’extension ou à sa désactivation. Par contre, dans la fonction clea_add_button_uninstall , qui sera lancée si je désinstalle l’extension, il y a ce qu’il faut pour supprimer les réglages que je prévois de stocker dans la base de données.

Les fichiers pour charger des scripts ou styles

Comme je sais qu’il faudra certainement que je charge des scripts (fichiers javascript) ou des styles (fichiers css), j’ai déjà prévu un fichier clea-add-button-enqueues.php  dans le répertoire /includes . Ce fichier ne contient rien à ce stade, juste son en-tête :

<?php
/**
 *
 * Charger styles et scripts du plugin
 *
 *
 * @link       	https://github.com/aldelpech/clea-add-button
 * @since      	0.1.0
 *
 * @package    clea-add-button
 * @subpackage clea-presentation/includes
 * Text Domain: clea-add-button
 */

Le fichier pour l’internationalisation

Tous les fichiers PHP de l’extension démarrent par un en-tête qui contient au minimum les deux lignes suivantes :

* Domain Path: /languages
* Text Domain: clea-add-button

Ces deux lignes indiquent respectivement le répertoire dans lequel seront rangés les fichiers de traduction et le nom du « text domain ». Pour plus de précision, voir un ancien article : Créer un plugin de fonctionnalités (6) ; internationalisation.

Le script clea-add-button-i18n.php contient la fonction qui chargera la traduction adaptée à la « localisation » du WordPress utilisant l’extension.

<?php
/**
 *
 * charger le bon text domain (internationalisation)
 *
 *
 * @link       	https://github.com/aldelpech/clea-add-button
 * @since      	0.1.0
 *
 * @package    clea-add-button
 * @subpackage clea-presentation/includes
 * Text Domain: clea-add-button
 */

add_action( 'plugins_loaded', 'clea_add_button_load_plugin_textdomain' );
 
function clea_add_button_load_plugin_textdomain() {
	
    load_plugin_textdomain( 'clea-add-button', FALSE, CLEA_ADD_BUTTON_BASENAME . '/languages/' );
	
}

Activer l’extension sur un site WordPress

J’active l’extension comme n’importe quelle autre extension, dans « extensions » / « extensions installées » du tableau de bord WordPress.

Tout va bien : pas d’erreur (Query Monitor n’affiche qu’une « notice », qui n’a rien à voir avec notre extension).

Stocker cette version sur GitHub

Sur mon compte GitHub, je crée un nouveau dépôt.

Je le clone dans l’application GitHub pour Windows. Ensuite, avec Filezilla, je copie le répertoire de l’extension dans le répertoire affecté à ce dépôt.

Environnement de développement d'extension sous Filezilla

Environnement de développement d’extension sous Filezilla

 

Je fais un « commit » puis je synchronise.

On peut voir l’ensemble des fichiers de l’extension de ce « commit » sur GitHub, ici.

Et maintenant

Dans le prochain article de cette série , je vais explorer la création d’une page de réglage pour l’extension.

 

Insérer des données dans une box domotique Eedomus

Insérer des données dans une box domotique Eedomus

J’ai un Raspberry Pi qui collecte des données issues de capteurs reliés à un arduino. Actuellement les données sont archivées dans des fichiers textes sur le Pi. Je veux envoyer les données à ma box Eedomus pour qu’elle en assure l’archivage et l’affichage.

Il faut donc que je réalise les actions suivantes :

  • sur l’Eedomus : créer des « éléments de programmation » de type « état » pour chaque donnée à collecter.
  • Sur le Raspberry Pi : lire les fichiers texte des données collectées et les envoyer à l’Eedomus tous les quarts d’heure.

Eedomus : créer des éléments de programmation de type « état »

Eedomus : créer un element de programmation "état"Sur un navigateur internet, accéder à la console eedomus (https://secure.eedomus.com/).

  1. Cliquer sur l’onglet « configuration » puis sur le bouton « ajouter ou supprimer un périphérique ».
  2. Dans la nouvelle fenêtre, cliquer sur le bouton « ajouter un autre type de périphérique ».

Régler le nouveau capteur d’état et noter son code API (450118) pour le capteur ci-dessous :

Réglage d'un capteur d'état sur Eedomus

Une fois qu’on a cliqué sur le bouton « Sauver », le capteur est créé. Il est classé dans les périphériques virtuels.

Le capteur ne contient aucune valeur. Il faut lui en donner par des commandes HTTP via l’API d’Eedomus.

Raspberry Pi : envoyer une valeur au capteur créé

Voir la documentation Eedomus sur la manière de définir la valeur d’un capteur ici.

En manuel, pour envoyer une donnée à ce capteur, il me suffit d’envoyer une commande HTTP via un navigateur :

https://api.eedomus.com/set?action=periph.value&api_user=xxx&api_secret=xxx&periph_id=450118&value=24.2&value_date=2016-08-17 08:47:00

Et le navigateur affiche un message de succès :

{ "success": 1, "body": { "result": "[OK]" } }

Et dans la box Eedomus, la dernière valeur du capteur est maintenant 24.2°C à 8h47 le 17 août.

Raspberry Pi : lire un fichier texte et envoyer à l’Eedomus

J’explore les scripts PHP à faire dans l’article « Manipuler des fichiers en PHP« . Ici, je vais lire un fichier texte qui contient les températures mesurées dans mon bureau.

Localisation du fichier : /home/jf/temp/temperatures.log

Les données sont archivées sous la forme (11 dernières lignes) :

28-00043d87b4ff	24.25	Wed, 17 Aug 2016 06:58:25 GMT

28-00043d87b4ff	24.25	Wed, 17 Aug 2016 06:58:35 GMT

28-00043d87b4ff	24.312	Wed, 17 Aug 2016 07:05:52 GMT

28-00043d87b4ff	24.312	Wed, 17 Aug 2016 07:06:05 GMT

28-00043d87b4ff	24.25	Wed, 17 Aug 2016 07:06:16 GMT

Il va falloir que mon script php aille chercher la dernière ligne non vide puis en sorte la température (24.25) et la date au format compréhensible par l’Eedomus (AAAA-MM-JJ HH:MM:SS).

Pour lire le fichier à partir de la fin : voir cette question sur stackoverflow.

J’ai complété le fichier eedomus.php , créé dans l’article précédent de cette série « « , avec la fonction suivante :

/******************************************************************
* appel de l'API eedomus en PHP pour donner une valeur une donnée
*
* $id = 450118
* $val = 24.2
* $date = '2016-08-17 08:47:00'
* mettra la valeur 24.2, à la date 2016-08-17 08:47:00 dans le capteur "état" d'ID 450118
*
*****************************************************************/ 

//&value=24.2&value_date=2016-08-17 08:47:00

function al_set_eedomus_sensor( $id, $val, $date ) {
	
	global $api_user ;
	global $api_secret ;
	
	// eedomus date must be url encoded before it is sent
	$date = urlencode( $date );
	
	// construction de l'URL de l'API
	$url = "https://api.eedomus.com/set?action=periph.value";
	$url .= "&api_user=$api_user";
	$url .= "&api_secret=$api_secret";
	$url .= "&periph_id=$id";
	$url .= "&value=$val";
	$url .= "&value_date=$date";
	
	echo "http command --------------- \n\r" ;
	echo $url ;
	echo "\n --------------------------- \n\r" ;
	
	// 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."]\n";
	}
	else {
		// Tout s'est bien passé
		echo "OK, la valeur a été transmise [".$result."] \n";
	}	
	
}

et j’ai créé un fichier send-temp-to-eedomus.php (droits 740), placé comme eedomus.php  dans le répertoire ‘/home/jf/eedomus ‘ du Pi « alradio ». Ce script contient :

#!/usr/bin/php
 
<?php

/****************************************************
* read last non blank line from file
****************************************************/
$directory = '/home/jf/temp/';
$log_file = 'temperatures.log' ;
$pos = -10;							// number of lines to read at end of file

$t = "";
$handle = fopen( $directory . DIRECTORY_SEPARATOR . $log_file, 'r' );

// source : http://stackoverflow.com/questions/6451232/reading-large-files-from-end

while ( $t != "\n" ) {
    fseek( $handle, $pos, SEEK_END );
    $t = fgetc( $handle );
    $pos = $pos - 1;
}

$lastline = fgets( $handle );
echo " last line : " . $lastline  ;
fclose( $handle );

/****************************************************
* now get each individual values
****************************************************/
date_default_timezone_set ( 'Europe/Paris' ) ;

$val = explode( "\t", $lastline );
// echo "---------------------------- \n\r" ;
// var_dump( $val ) ;

$date_var = explode( " ", $val[2] ) ;
$day = $date_var[1] ;
// for month conversion from text to number, see http://stackoverflow.com/questions/3283550/convert-month-from-name-to-number
$date = date_parse( $date_var[2] );
// echo "month --------------------- \n\r" ;
// var_dump( $date["month"] );
$month = $date["month"] ;
$year = $date_var[3] ;
$time = explode( ":", $date_var[4] ) ;
$heure = $time[0] ;
$minute = $time[1] ;
$seconde = $time[2] ;
$fuseau = $date_var[5] ;

// convertir en timestamp GMT (utiliser mktime si l'heure n'est pas GMT)
$gmt_timestamp = gmmktime( $heure, $minute, $seconde, $month, $day, $year   ) ;
// convertir timestamp GMT en heure locale lisible par Eedomus 2016-08-17 08:47:00
$date_humain = date ( "d/m/Y H:i:s" , $gmt_timestamp ) ;
$date_eedomus = date ( "Y-m-d H:i:s" , $gmt_timestamp ) ;

// echo "time --------------------- \n\r" ;
// var_dump( $time ) ;
// echo "date_var --------------------- \n\r" ;
// var_dump( $date_var ) ;
echo "---------------------------- \n\r" ;
echo "°C : " . $val[1] . " date : " . $date_humain . "\n\r" ;
echo "---------------------------- \n\r" ;

/****************************************************
* now send the value to the Eedomus
* with al_set_eedomus_sensor( $id, $val, $date )
****************************************************/
include 'eedomus.php';
$id = 450118 ; // capteur eedomus 'température bureau'
al_set_eedomus_sensor( $id, $val[1], $date_eedomus ) ;

?>

Dans le Pi alradio, si je tape les commandes :

cd /home/jf/eedomus
./send-temp-to-eedomus.php

La console affiche les informations suivantes :

PHP : envoi de données à l'Eedomus

Et dans le tableau de bord Eedomus, la valeur a effectivement été modifiée.

Exécuter automatiquement le script d’envoi de données

Je vais utiliser le cron, comme dans l’article Supprimer les fichiers vidéo et image de plus de deux jours , à ceci près que c’est un fichier PHP que je vais exécuter.

J’utilise le générateur http://crontab-generator.org/ pour simplifier les choses.

J’ajoute donc la ligne suivante (SHIFT + INSER pour coller) au cron du Pi via crontab -e  :

*/15 * * * * /usr/bin/php /home/jf/eedomus/send-temp-to-eedomus.php

Puis « reboot » à 14h17. Logiquement je devrais avoir une nouvelle valeur vers 14h30.

Ca fonctionne. Le capteur est effectivement mis à jour toutes les 15 minutes sur l’Eedomus.

Manipuler des fichiers en PHP

Manipuler des fichiers en PHP

J’ai besoin de lire le contenu de fichiers créés par une application tierce. Il faut :

  • lister les fichiers avec extension ‘log’ dans un répertoire ;
  • pour chaque fichier, lire chaque ligne ;
  • pour chaque ligne, transformer son contenu texte en un array de données ;
  • selon la valeur de certains éléments de la ligne, réaliser une action spécifique ;
  • une fois le fichier traité, en modifier l’extension (devient ‘fait’).

Pour cela, j’utilise un script PHP installé dans le répertoire /var/www/html/tests  d’un Raspberry Pi sur lequel j’ai installé apache et PHP (cf article Un serveur Web sur mon Raspberry Pi).

De quoi je pars ?

/var/www/html/tests  contient le fichier test-explode.php  et un sous répertoire data .

/var/www/html/tests/data  contient 3 fichiers dont le nom est sous la forme domoAL_20150910.log . Chacun de ces fichiers contient 1000 à 2000 lignes de type :

1441868688	!	Thu, 10 Sep 2015 07:04:48 GMT ! dev_0x05 !	2756	2	1462	1831	1931	0
1441868744	!	Thu, 10 Sep 2015 07:05:44 GMT ! dev_0x05 !	2761	2	1462	1825	1918	0
1441868799	!	Thu, 10 Sep 2015 07:06:39 GMT ! dev_0x05 !	2756	2	1462	1825	1950	0
1441868854	!	Thu, 10 Sep 2015 07:07:34 GMT ! dev_0x05 !	2760	2	1468	1831	1993	868
1441868910	!	Thu, 10 Sep 2015 07:08:30 GMT ! dev_0x05 !	2754	2	1468	1843	2037	0
1441868965	!	Thu, 10 Sep 2015 07:09:25 GMT ! dev_0x05 !	2762	2	1468	1856	2075	0

Lister les fichiers d’un répertoire

Objectif : lister les fichiers avec extension ‘log’ dans un répertoire.

Ma solution est fortement inspirée de cette page de php.net.

<section class="data">
<H3>Test list each file in a directory</h3>	

<?php
// http://php.net/manual/fr/function.scandir.php
echo "<br /><hr />" ;
$directory = '../tests/data';
$file_read = array( 'log' );

$scanned_directory = array_diff( scandir( $directory ), array( '..', '.' ) );

foreach ( $scanned_directory as $key => $value ) {

	$type = explode( '.', $value ); 
	$type = array_reverse( $type );
	if( !in_array( $type[0], $file_read ) ) {		// continue if not ".log"
		continue;
	}

	$lines = 0;
	$handle = fopen( $directory . DIRECTORY_SEPARATOR . $value, 'r' );

	while ( !feof( $handle ) ) {

		if ( is_bool( $handle ) ) {
			break;
		}

		$line = fgets( $handle );
		$lines++;
	}

	fclose( $handle );

	$result['lines_html'][] = array ( $directory . '/' . $value ,  $lines ) ; 
	$result['lines_count'] = $result['lines_count'] + $lines;
	$result['files_count'] = $result['files_count'] + 1;
	
}
var_dump( $result ) ;
echo "<br /><hr />" ;

?>

</section>

ce script affiche un array sur ma page (que j’ai mis en forme à la main) :

array(3) { 
	["lines_html"]=> array(3) {
		[0]=> array(2) {
			[0]=> string(33) "../tests/data/domoAL_20150910.log" 
			[1]=> int(1561) 
		} 
		[1]=> array(2) { 
			[0]=> string(33) "../tests/data/domoAL_20150913.log" 
			[1]=> int(1560) 
		} 
		[2]=> array(2) { 
			[0]=> string(33) "../tests/data/domoAL_20150914.log" 
			[1]=> int(1560) 
		} 
	} 
	["lines_count"]=> int(4681) 
	["files_count"]=> int(3) 
}

Le script trouve donc bien les 3 fichiers et le nombre de ligne de chaque fichier.

Lire les lignes d’un fichier

Objectif : pour chaque fichier, lire chaque ligne.

Créer un array avec la liste des fichiers

Je crée une fonction qui génère un array avec le nom de chaque fichier du répertoire.

<?php
// http://php.net/manual/fr/function.scandir.php

// list all log files in the ../tests/data directory in an array
$list_files = al_list_files_in_dir( '../tests/data', array( 'log' ) ) ;
echo "<br />liste des fichiers<hr />" ;
var_dump( $list_files ) ;
echo "<br /><hr />" ;

function al_list_files_in_dir( $directory, $file_read ) {

	$scanned_directory = array_diff( scandir( $directory ), array( '..', '.' ) );
	
	foreach ( $scanned_directory as $key => $value ) {
	
		$type = explode( '.', $value ); 
		$type = array_reverse( $type );
		if( !in_array( $type[0], $file_read ) ) {		// continue if not ".log"
			continue;
		}

		$result[] = $directory . '/' . $value ; 
	}
	
	return $result ;
	
}

?>

J’obtiens un array comme celui ci (mis en forme à la main) :

array(3) {
	[0]=> string(33) "../tests/data/domoAL_20150910.log" 
	[1]=> string(33) "../tests/data/domoAL_20150913.log" 
	[2]=> string(33) "../tests/data/domoAL_20150914.log" 
}

Dans chaque fichier, lire les lignes une à une

<H4>read each line of each log file</h4>	

<?php
foreach ( $list_files as $key => $value ) {
	
	$file_content = al_read_lines_in_file( $value ) ;
	echo "<br />contenu des fichiers<hr />" ;
	var_dump( $file_content ) ;
	echo "<br /><hr />" ;		
}

function al_read_lines_in_file( $file ) {
	
	$handle = fopen( $file, "r" );

	$count = 0 ;
	if ($handle) {
		
		while (($line = fgets($handle)) !== false) {
			$content[] = $line ;
		}
	
		fclose($handle);
		return $content ;
		
	} else {
		echo "can't open file \n" ;
	} 
	
}


?>

Affiche 3 gigantesques arrays de 1500 lignes chacun environ.

Mettre le contenu d’une ligne dans un array

Objectif : pour chaque ligne, transformer son contenu texte en un array de données.

Chaque ligne contient des données séparées soit par « ! », soit par une tabulation. Je veux la transformer en une série de données.

<H3>Store the content of each line in an array</h3>	
<?php

foreach ( $list_files as $key => $value ) {

	$file_content = al_read_lines_in_file( $value ) ;
	$line_count = 0 ;

	foreach ( $file_content as $contents => $content ) {
		
		$file_data[ $line_count ] = al_store_lines_in_array( $content ) ;
		$line_count++ ;
	}

	echo "<br />données" . $value . "<hr />" ;
	var_dump( $file_data ) ;
	echo "<br /><hr />" ;	
}



function al_store_lines_in_array( $text ) {

	$val = explode("!", $text);
	
	// split on tabulation
	$temp = preg_split( "/[\t]/", trim( $val[3] ) );

	// strip whitespace from the beginning and end of string.	
	$val[0] = trim( $val[0] );
	$val[1] = trim( $val[1] );
	$val[2] = trim( $val[2] );	
	$val[3] = trim( $val[3] );
	$val[4] = trim( $temp[0] );
	$val[5] = trim( $temp[1] );
	$val[6] = trim( $temp[2] );
	$val[7] = trim( $temp[3] );
	$val[8] = trim( $temp[4] );
	$val[9] = trim( $temp[5] );	

	return $val ;
}

?>

Pour le fichier ../tests/data/domoAL_20150910.log , ça me donne un array de 1560 arrays sous forme :

array(1560) { 
	[0]=> array(10) { 
		[0]=> string(10) "1441843202" 
		[1]=> string(29) "Thu, 10 Sep 2015 00:00:02 GMT" 
		[2]=> string(8) "dev_0x05" 
		[3]=> string(23) "2755	2	1531	1906	2168	0" 
		[4]=> string(4) "2755" 
		[5]=> string(1) "2" 
		[6]=> string(4) "1531" 
		[7]=> string(4) "1906" 
		[8]=> string(4) "2168" 
		[9]=> string(1) "0" 
	} 
	...
}

tout est sous la forme de texte (string). Il va falloir maintenant que je transforme la date « 1441843202 » et la dernière valeur (0 pour la ligne affiché ci-dessus) en données sur lesquelles je pourrai valider ou non une condition.

Mettre des conditions sur le contenu de l’array

Objectif : selon la valeur de certains éléments de la ligne, réaliser une action spécifique. Si le dernier élément de l’array n’est pas nul, l’afficher avec sa date. Si le temps écoulé depuis le dernier élément affiché est supérieur à 15 minutes, afficher la ligne même si sa dernière valeur est nulle.

conditions sur deux données

Je crée un array contenant des données comme celles qui doivent être produites par la lecture d’un des fichiers log. Je transforme les deux données sur lesquelles je veux pouvoir faire une condition en entiers avec la fonction intval() .

Pour le timestamp, je veux qu’il puisse être affiché de manière lisible : $date_humain = date ( « d/m/Y H:i:s » , $date_stamp ) ;  va transformer 1441843313 en 10/09/2015 02:01:53 .

Le script suivant lit le contenu de l’array de données puis :

  1. si la dernière valeur ($value[9] ) est nulle, met $save à faux, sauf si ça fait plus de 5 minutes que $save est faux.
  2. si $save est true, imprime les données

Vu l’array que j’ai construit pour cet essai, je devrais avoir 4 lignes imprimées et une non (celle qui correspond à la deuxième valeur de l’array, qui a un timestamp très proche du premier).

<H3>Test conditions sur éléments de l'array de données</h3>	

<?php

$data = array (
	0 => array(
		0 => "1441843202" ,
		1 => "Thu, 10 Sep 2015 00:00:02 GMT" ,
		2 => "dev_0x05" ,
		3 => "2755	2	1531	1906	2168	0" ,
		4 => "2755" ,
		5 => "2" ,
		6 => "1531" ,
		7 => "1906" ,
		8 => "2168" ,
		9 => "0" 	
	),
	1 => array(
		0 => "1441843258" ,
		1 => "Thu, 10 Sep 2015 00:00:58 GMT" ,
		2 => "dev_0x05" ,
		3 => "2761	2	1537	1906	2137	0" ,
		4 => "2761" ,
		5 => "2" ,
		6 => "1537" ,
		7 => "1906" ,
		8 => "2137" ,
		9 => "0"
	),
	2 => array(
		0 => "1441843313" ,
		1 => "Thu, 10 Sep 2015 00:01:53 GMT" ,
		2 => "dev_0x05" ,
		3 => "2761	2	1537	1906	2137	0" ,
		4 =>  "2761" ,
		5 => "2" ,
		6 => "1537" ,
		7 => "1906" ,
		8 => "2137" ,
		9 => "216"		
	),
	3 => array(
		0 => "1441849352" ,
		1 =>  "Thu, 10 Sep 2015 01:42:32 GMT" ,
		2 =>  "dev_0x05" ,
		3 =>  "2761	2	1537	1906	2137	0" ,
		4 =>  "2761" ,
		5 =>  "2" ,
		6 =>  "1537" ,
		7 => "1906" ,
		8 => "2137" ,
		9 => "0"		
	),
	4 => array(
		0 => "1441851457" ,
		1 => "Thu, 10 Sep 2015 02:17:37 GMT" ,
		2 => "dev_0x05" ,
		3 => "2761	2	1537	1906	2137	0" ,
		4 => "2761" ,
		5 => "2" ,
		6 => "1537" ,
		7 => "1906" ,
		8 => "2137" ,
		9 => "51"		
	)
) ;

// var_dump( $data ) ;
// echo "<hr /><br />" ;

echo "<hr />" ;	

$ref_time = 0000000 ;		

foreach ( $data as $key => $value ) {

	$date_stamp = intval( $value[0] ) ;
	$date_humain = date ( "d/m/Y H:i:s" , $date_stamp ) ;
	
	$time_lapsed = $date_stamp - $ref_time ; // temps écoulé depuis dernière ligne à imprimer
	
	$chaudiere = intval( $value[9] ) ;
	
	if ( $chaudiere == 0 ) {
		
		$save = false ; 
		
		if ( $time_lapsed > 300 ) {	// plus de 5 minutes
			
			$save = true ;
			$ref_time = $date_stamp ;
		}
		
		echo "<hr />" ;
		
	} else {
		
		$save = true ;
		
	}
	
	echo $key . " | \t" . $time_lapsed . " | \t" ; 

	if ( $save == true ) {
		
		echo $chaudiere . " | \t" . $save  . " | \t" . $date_stamp  . " | \t" . $date_humain ;
		
	}	else {
		
		echo "do not print" ;
		
	}
	
	echo "<hr />" ;
	
}


echo "<br />" ;	

?>

et le résultat est conforme à mes attentes :

tests en php de manipulation de tableau

Modifier l’extension d’un fichier

Objectif : une fois le fichier traité, en modifier l’extension (devient ‘fait’).

ici j’ai fait beaucoup usage de /var/log/apache2/error.log  !!! J’ai ainsi compris que j’obtenais une erreur « Permission denied » lorsque je voulais renommer.

Je n’ai jamais réussi à réaliser l’opération, même en la remplaçant par une copie puis une suppression du fichier initial… Puis, j’ai eu l’idée que c’est normal ! Heureusement que l’on ne peut pas supprimer ou renommer des fichiers au travers d’un navigateur internet…

j’ai donc créé un fichier test-rename.php (droits 740) dans le répertoire /home/jf/exec du Raspberry Pi. Dans le répertoire /home/jf/snd/data, j’ai placé les 3 fichiers à renommer. Et ça fonctionne !

#!/usr/bin/php

<?php
$directory = '/home/jf/snd/data';
$file_read = array( 'log' );

$scanned_directory = array_diff( scandir( $directory ), array( '..', '.' ) );


foreach ( $scanned_directory as $key => $value ) {

	$type = explode( '.', $value ); 
	// var_dump( $type ) ;
	
	$type = array_reverse( $type );
	if( !in_array( $type[0], $file_read ) ) {		// continue if not ".log"
		continue;
	}

	$old_name = $directory . DIRECTORY_SEPARATOR . $value ;
	$new_name = $directory . DIRECTORY_SEPARATOR . $type[1] . ".fait" ;
	
	$return_val = rename( $old_name, $new_name );
			
	if ( $return_val == 1 ) { 
	   echo "success : " . $old_name . " renamed " . $new_name . "\r\n"; 
	} else { 
	   echo "failed to rename " . $old_name . " to : " . $new_name . "\r\n" ; 
	} 		

}

?>

Ensuite dans la console de commande du Raspberry Pi :

cd /home/jf/exec
./test-rename.php

Et ça y est, la console affiche

success : /home/jf/snd/data/domoAL_20150910.log renamed /home/jf/snd/data/domoAL_20150910.fait
success : /home/jf/snd/data/domoAL_20150913.log renamed /home/jf/snd/data/domoAL_20150913.fait
success : /home/jf/snd/data/domoAL_20150914.log renamed /home/jf/snd/data/domoAL_20150914.fait

Et si je vérifie, les 3 fichiers ont bien été renommés.

Et maintenant ?

J’ai fait fonctionner les prototypes de code dont je vais avoir besoin. Je vais les utiliser pour faire échanger des données entre un Raspberry Pi (qui contient les fichiers log) et une box domotique qui va archiver et afficher les données.

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 .

Récapitulatif : Raspberry Pi, motion et deux caméras

Récapitulatif : Raspberry Pi, motion et deux caméras

Les 5 précédents articles de cette série explorent différents aspects de la gestion de caméras de surveillance sur Rasberry Pi et la diffusion des flux vidéos sur une page web. Cet article en fait la synthèse.

L’objectif :

A partir d’une Raspberry Pi B+, installer motion réglé correctement pour :

  • gérer deux caméras de surveillance IP ;
  • collecter les vidéos et images générées sur détection de mouvement avec aussi peu de faux positifs que possible ;
  • afficher les flux vidéos en temps réel sur une page web accessible sur le réseau local.

Préparation du Raspberry Pi

  • Installer Raspbian Jessie Lite – voir l’article Mise en service d’un Raspberry Pi
  • Régler le Pi pour fonctionner correctement en wifi sur une adresse fixe 192.168.1.104 (nom nautilus) – voir l’article Raspberry Pi : connexion internet en wifi et/ou IP fixe

Pour moi toutes ces étapes réalisées correspondent à l’image de carte sd « jessie-lite-wifi fixe-103-2016-07-16.img « , dans le répertoire « Downloads\2016-07 Pi Images « . Il me suffit de régler l’adresse wifi et le nom du Pi comme suit :

  • Connecter le Pi en ethernet, sans dongle wifi ;
  • Editer /etc/dhcpcd.conf   et modifier l’adresse IP attribuée ;
  • Taper raspi-config  puis modifier Hostname (advanced options) et changer le nom vers « nautilus ». 
  • Mettre le dongle wifi sur le Pi puis taper « reboot  » pour redémarrer.

Ensuite se reconnecter en wifi et mettre le Pi à jour :

  • apt-get update
  • apt-get upgrade
  • rpi-update

Enlever la connexion ethernet et taper « reboot  » pour redémarrer.

uname -a  nous indique la version du système d’exploitation en fonctionnement.

Le résultat, le 14/08/2016 est 4.4.17+ #901 Fri Aug 12 17:48:40 BST 2016 armv6l GNU/Linux  .

df -h  montre qu’on utilise 17% de la carte SD de 8 Go.

Installer motion

apt-get install -y libjpeg62-turbo libjpeg62-turbo-dev  libavformat-dev  libavcodec-dev  libavutil-dev libc6-dev zlib1g zlib1g-dev
apt-get install motion

Régler motion pour mes deux caméras

Il me faut 3 fichiers de configuration (droits d’accès 664, propriétaires motion / video) puisque j’ai deux caméras (cf l’article une caméra IP avec une vieille tablette android ?) :

  • /etc/motion/motion.conf  pour tous les éléments communs aux deux caméras ;
  • /etc/motion/thread0.conf  pour la caméra ‘Foscam’ ;
  • /etc/motion/thread1.conf  pour la caméra ‘galaxy’ ;

J’exécute donc les commandes suivantes pour créer les fichiers, et donner les bons droits :

chown -R root:root /etc/motion/
touch /etc/motion/thread1.conf
touch /etc/motion/thread0.conf
chown root:motion /etc/motion/thread0.conf
chown root:motion /etc/motion/thread1.conf
chmod -R 664 /etc/motion/
chmod 755 /etc/motion/
Nota : on trouve de nombreux tutoriels dans lesquels les droits de /etc/motion sont en 664 mais ça ne fonctionne pas dans mon cas.
Faire une copie de motion.conf au cas où :
cp /etc/motion/motion.conf /etc/motion/motion.conf.OLD

créer certains fichiers et donner les droits d’accès

Exécuter chaque ligne de commande non commentée :

# /usr/bin/motion : 755
chmod 755 /usr/bin/motion

#/var/lib/motion/ ne sert pas

#var/run/motion (pour motion.pid)
mkdir /var/run/motion
touch /var/run/motion/motion.pid
chown -R motion:motion /var/run/motion/
chmod 755 /var/run/motion/
chmod 644 /var/run/motion/motion.pid

# thread2.conf (foscam) : /home/jf/motion/log/foscam
# thread1.conf (tablette) : /home/jf/motion/log/galaxy
mkdir /home/jf/motion
mkdir /home/jf/motion/galaxy
mkdir /home/jf/motion/foscam
touch /home/jf/motion/motion-log.log
chown -R root:motion /home/jf/motion/
chown motion:motion /home/jf/motion/motion-log.log
chmod -R 644 /home/jf/motion/
chmod 777 /home/jf/motion/
chmod 775 /home/jf/motion/foscam/
chmod 775 /home/jf/motion/galaxy/
Attention, les fichiers créés avec touch via Cygwin sur mon PC sont en mode sauts de ligne Windows. Il faut les passer en saut de ligne UNIX (/home/jf/motion/log/motion-log.log et /var/run/motion/motion.pid).

éditer les fichiers de configuration

Dans etc/motion/motion.conf  les lignes suivantes sont modifiées ou décommentées :

process_id_file /var/run/motion/motion.pid 
logfile /home/jf/motion/log/motion-log.log
v4l2_palette 8
width 640
height 480

netcam_keepalive on

# Number of motion thread to show in SDL Window (default: 0 = disabled)
sdl_threadnr 0 

text_double on
target_dir /home/jf/log/

# Restrict stream connections to localhost only (default: on)
stream_localhost off

webcontrol_port 8080  #must be in motion.conf, not in a thread.

# Restrict control connections to localhost only (default: on)
webcontrol_localhost off

# Output for http server, select off to choose raw text plain (default: on)
webcontrol_html_output on

thread /etc/motion/thread0.conf
thread /etc/motion/thread1.conf

Attention : il faut bien vérifier que thread0.conf et thread1.conf ont des sauts de ligne UNIX.

etc/thread0.conf  règle la configuration pour la caméra devant la maison (évidemment il faut modifier USER et PWD ainsi que l’adresse IP pour que ça corresponde à la caméra) :

# caméra FOSCAM devant la maison
# thread0.conf

videodevice /dev/video0
netcam_url http://USER:PWD@192.168.X.X:80/videostream.cgi?user=USER&pwd=PWD

############################################################
# Régler sensibilité aux mouvements pour cette caméra extérieure

threshold 3000
threshold_tune off
noise_level 32
noise_tune off
despeckle_filter EedDl
smart_mask_speed 10
lightswitch 25
minimum_motion_frames 3

############################################################

# Target base directory for pictures and films
target_dir /home/jf/motion/log/foscam

snapshot_filename f-%v-%Y%m%d%H%M%S-snapshot
picture_filename f-%v-%Y%m%d%H%M%S-%q
movie_filename f-%v-%Y%m%d%H%M%S
timelapse_filename f-%Y%m%d-timelapse

############################################################

stream_port 8081

etc/thread1.conf  règle la configuration pour la caméra créée sur la vieille tablette samsung selon l’article une caméra IP avec une vieille tablette android ? :

#caméra Galaxy tab dans la maison

videodevice /dev/video1

netcam_url http://192.168.X.X:8090/video

############################################################

threshold 3000
threshold_tune off
noise_level 32
noise_tune off
despeckle_filter EedDl
smart_mask_speed 10
lightswitch 25
minimum_motion_frames 3

############################################################

# Target base directory for pictures and films
target_dir /home/jf/motion/log/galaxy

snapshot_filename g-%v-%Y%m%d%H%M%S-snapshot
picture_filename g-%v-%Y%m%d%H%M%S-%q
movie_filename g-%v-%Y%m%d%H%M%S
timelapse_filename g-%Y%m%d-timelapse

############################################################

stream_port 8082

Démarrer motion comme un service

Dans /etc/default/motion, modifier la ligne suivante (passer à yes) : 

start_motion_daemon=yes

Redémarrer le Pi avec reboot.

http://192.168.1.104:8082/ et http://192.168.1.104:8081/ affichent les deux flux vidéos sur un navigateur local.

Si ça ne fonctionne pas, modifier /etc/init.d/motion  comme indiqué dans cet article, en y ajoutant la ligne sleep 30 . Pour moi, ça fonctionne sans.

réglage motion comme un service

Pour déboguer, on peut souhaiter savoir quel est l’utilisateur du service motion :

ps -aef | grep motion

Dans mon cas, ça montre deux utilisateurs (1ère colonne) : motion et root.

motion-user

A ce stade, si on tape 192.168.1.104:8081 ou 192.168.1.104:8082 dans un navigateur du réseau local, on voit les flux vidéos en temps réel. 

Supprimer les anciens fichiers image et vidéo 

 Comme dans l’article Supprimer les fichiers vidéo et image de plus de deux jours sauf que le script bash est modifié pour tenir compte des deux répertoires de stockage d’image :
  • créer le fichier cameradeletecron.sh dans le répertoire /home/jf/motion et lui donner les droits d’acès 764
  • Dans ce fichier, mettre les lignes suivantes :
#!/bin/bash

# /home/jf/motion/camera–delete–cron.sh

# efface les fichiers avi et jpg de plus de deux jours 
find /home/jf/motion/foscam -maxdepth 1 -name '*.avi' -type f -mtime +2 -exec rm {} \;
find /home/jf/motion/foscam -maxdepth 1 -name '*.jpg' -type f -mtime +2 -exec rm {} \;
 
find /home/jf/motion/galaxy -maxdepth 1 -name '*.avi' -type f -mtime +2 -exec rm {} \;
find /home/jf/motion/galaxy -maxdepth 1 -name '*.jpg' -type f -mtime +2 -exec rm {} \;
  •  Dans le cron, via crontab -e, insérer les lignes suivantes :
#env
SHELL=/bin/bash
HOME=/home/jf
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
 
* 1 * * * bash /home/jf/motion/camera-delete-cron.sh
  • Redémarrer le Pi avec reboot.

Visualiser les flux vidéos sur une page PHP

  • Installer apache et PHP – voir l’article Un serveur Web sur mon Raspberry Pi.
  • dans le répertoire /var/www/html , créer une page index.php  qui affiche les deux flux vidéos et y ajouter un fichier A-style.css .
  • supprimer index.html

index.php contient :

<!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"> 
</head>

<body class="pi-dashboard">

	<H1>Tableau de bord maison 192.168.1.104 (nautilus)</h1>
	 
	<section class="video-surveillance">

		<img name="Galaxy-Tab" class="stream" src="https://192.168.1.104:8082/?action=stream" width="600" height="450" alt="Live Feed" title="Galaxy Tab GT-P6210" />

		<img name="Foscam-FI8905W" class="stream" src= "http://192.168.1.104:8081/?action=stream" width="600" height="450" title="Foscam FI8905W"/>
		
	</section>
	
	<section class="capteurs">

		<h3>Autres informations</h3>
		<p>A venir...</p>
		
	</section>
 
</body>
</html>

A-style.css contient :

body {
 padding: 0.3%;
 font-family: Georgia, "Times New Roman",
 Times, serif;
 color: purple;
 background-color: #d8da3d ;
 width: 95%;
 text-align: center;
}

section.video-surveillance {
 display: flex; /* or inline-flex */
 justify-content: space-around;
 display: inline-block;
}
 
h1 {
 font-family: Helvetica, Geneva, Arial,
 SunSans-Regular, sans-serif ;
 margin-top: 1em;
}

img.stream {
 padding: 1%;
}


@media only screen and (min-width: 800px){
 
 section.video-surveillance {
 flex-direction: row ;
 }
 
 img.stream {
 max-width: 45% ;
 height: auto;
 }

}

@media only screen and (max-width: 800px){
 
 section.video-surveillance {
 flex-direction: column ;
 }
 
 img.stream {
 max-width: 95%;
 height: auto;
 }
 
}

A ce stade, la page php s’affiche lorsqu’on tape « nautilus » dans un navigateur local (192.168.1.104 dans une tablette)et les deux flux vidéos s’affichent en streaming.

df -h  montre qu’on utilise maintenant 19% de la carte SD de 8 Go.

Mode opératoire pour créer des supports de cours en ligne

Mode opératoire pour créer des supports de cours en ligne

Je crée un cours (une formation) en ligne pour mon site professionnel. Je me rends compte que ce n’est pas si simple que ça de créer des vidéos à partir de powerpoint (2010) et d’un enregistrement de ma voix. J’ai déjà exposé les grands principes dans un article précédent (créer une vidéo à partir d’un Powerpoint (2010)). Ici je met en ligne mon mode opératoire précis. Ca servira au moins à m’en souvenir la prochaine fois !

Etape 1. Créer le Powerpoint.

Rien à dire de spécial là dessus.

Etape 2. Ecrire le texte qui sera ensuite dit

Etape 2. Ecrire le texte qui sera ensuite ditJe le met dans les commentaires, en caractères de taille 14.

Au fur et à mesure, je crée le résumé que je met sur la diapo et chaque fois que je résume une idée je passe à la ligne dans les commentaires.

(Cliquer sur l’image pour la voir en plus grand).

Etape 3. Préparer les changements de vue

Etape 3. Préparer les changements de vueLorsque je dirai mon texte il faudra que je cliques sur le diaporama pour changer de page ou faire afficher les animations correspondants à ce que je dis.

Je crée donc les animations et j’indique « CLIC » dans le texte pour me souvenir que c’est à faire à ce stade.

(Cliquer sur l’image pour la voir en plus grand).

Etape 4. Créer l’aide visuelle pour dire le texte

ppt-video-3J’imprime en pdf le diaporama en mode pages de commentaire.

C’est ce document qui me servira de script pour dire le texte.

(Cliquer sur l’image pour la voir en plus grand).

Etape 5. s’entraîner et vérifier

J’ai connecté un deuxième écran à mon PC. Je pourrais aussi utiliser un vidéoprojecteur en le réglant pour qu’il ne duplique pas l’écran de l’ordinateur.

J’ouvre le pdf du texte à dire dans le deuxième écran.

Je démarre le diaporama dans le premier écran.

Je dis le texte une ou deux fois pour vérifier que les clics vont bien, que mon texte va bien. Si nécessaire je modifie le diaporama et je crée un nouveau pdf du texte à dire.

Etape 6. Dire le texte dans le powerpoint

J’utilise un micro Yeti (ici sur amazon). Il est cher mais permet de régler la direction du son. C’est très utile dans certaines circonstances. Je le connecte en USB à mon ordinateur et je vérifie que le bouton mute est désactivé (la LED rouge est fixe lorsqu’il peut enregistrer, clignotante lorsqu’il est sourd / mute).

Nota : la façon de faire un enregistrement sonore et minuter les animations est décrite dans le premier article sur les vidéos à partir d’un Powerpoint.

Je fais un premier test vite fait pour m’assurer que le micro est bien réglé :

  • j’enregistre quelques phrases puis j’arrête l’enregistrement ;
  • Je lis la diapo en mode diaporama. Si le son est bon, j’efface l’enregistrement et je procède maintenant à la version définitive.
  • je commence à la première diapo, en enregistrant à la fois le son et le minutage.
  • Lorsque j’ai fini la première diapo, j’arrête l’enregistrement.
  • Je me place sur la deuxième diapo et je choisis « démarrer l’enregistrement à partir de la diapositive actuelle ».
  • Et ainsi de suite jusqu’à la fin du diaporama. Evidemment, il est prudent de faire des sauvegardes intermédiaires.

Etape 7 : vérifier puis transformer en vidéo

J’enregistre en qualité ‘Qualité HD & ordinateur’ puisque je vais ensuite la mettre en ligne via Viméo qui gérera la qualité en fonction de la bande passante de l’internaute.

J’obtiens un fichier WMV d’environ 8 Mo pour 58 » de vidéo (en qualité internet et DVD, la même vidéo ferait 3 Mo.

Etape 8 : mettre la vidéo en ligne (sur VIMEO)

J’ai acheté un compte « plus » sur Vimeo, qui permet de gérer la confidentialité des vidéos mises en ligne. C’est très simple.

On peut mettre en ligne soit en faisant glisser le fichier dans la zone correspondante, soit, encore mieux, via dropbox (c’est beaucoup plus rapide puisque je ne monte qu’une seule fois le fichier sur internet).

Etape 9 : régler les paramètres de la vidéo (sur VIMEO)

Paramètres basic :

  • choisir la vignette

Paramètres Confidentialité :

  • Qui peut regarder cette vidéo : masquer cette vidéo sur vimeo.com (Cette vidéo peut être intégrée à d’autres sites mais ne peut pas être visionnée sur Vimeo.com)
  • Où est-ce que cette vidéo peut être intégrée ? : seulement sur les sites de mon choix (et saisir les noms de domaine)
  • Qui peut commenter cette vidéo : tout le monde
  • Que peuvent faire les gens avec cette vidéo : ne rien cocher (pas de téléchargement, pas d’ajout à une collection)

Paramètres collections

  • Album : cocher le bon album

Paramètres Intégration

  • Pré-réglage « cours – par défaut »

Paramètres intégration Vimeo

Paramètres Avancé :

  • Licence Creative Commons : aucune

Intégrer la vidéo sur une page WordPress

Le domaine d’intégration doit avoir été défini dans les paramètres de confidentialité.

Dans Viméo, cliquer sur « share » en haut à droite de la vidéo. Copier le lien et le coller directement dans l’éditeur visuel du contenu WordPress de destination

Et voilà, c’est fait !

Raspberry Pi : créer un serveur de musique avec mpd

Raspberry Pi : créer un serveur de musique avec mpd

J’ai de la musique digitalisée sur un disque dur. Je voudrais la partager dans toute la maison sans avoir à laisser un PC gourmand en route 24/24. Je crée donc un serveur de musique sur un Raspberry Pi disposant d’un disque dur externe. J’utilise mpd comme serveur.

Note du 11/1/2018 : ce serveur ne me plaisait pas vraiment car je ne trouvais pas d’application Android vraiment agréable pour le gérer. Et je ne voyais pas comment l’utiliser pour gérer des listes de lecture et des destinations différentes. J’ai donc repris le collier sur le sujet. J’ai créé d’autres articles de la série pour créer un serveur bien mieux, avec mini DLNA et UPnP.

Dernière MAJ : 11 janvier 2018

Comment fonctionne un serveur de musique ?

J’ai eu beaucoup de mal à comprendre quoi choisir…

Je me suis refusée à prendre des systèmes qui arrivent avec la distribution, je voulais apprendre quelque chose en installant un serveur par dessus une distribution Raspbian classique (Jessie Lite dans mon cas).

Lorsqu’on a de la musique sur un disque dur, il nous faut trois éléments pour pouvoir l’entendre quelque part :

  1. un serveur de musique, qui gère les fichiers, les indexe et les met à disposition ;
  2. un client, qui agit sur le serveur pour lui dire quoi jouer, créer des « playlists », lui dire où émettre les sons générés par la playlist.
  3. un « renderer » (je ne vois pas comment traduire ça), qui restitue la musique dans des haut-parleurs.

mpd tient lieu de serveur. Sur le même pi, on place un client (mpc) avec lequel on peut intéragir en ligne de commande. Et si on connecte le Pi contenant mpd à des haut-parleurs (ou à un ampli), c’est mpd qui assure la fonction de « renderer ».

Si je veux jouer de la musique à partir du même disque dur dans d’autres endroits, j’ai deux possibilités :

  1. lire le flux audio du premier Pi et le jouer sur l’équipement qui le lit. Par exemple, je peux l’écouter sur une tablette équipée de MPdroid, qui va tenir lieu de client, mais qui va aussi servir de renderer pour un éventuel flux audio.
  2. installer d’autres Pi équipés de mpd. Dans ce cas, il semblerait (je n’ai pas encore essayé) qu’il faut installer icecast sur le premier Pi et c’est icecast qui se charge de transmettre les fichiers au deuxième Pi.

Préparer le Raspberry Pi

J’utilise un Raspberry Pi B+, avec un dongle wifi Edimax EW-7811Un. Pour le préparer, suivre ces trois autres articles de ce site :

J’ai donc un Pi correctement installé, avec un disque dur externe monté sur le répertoire /media/iomega et partagé par samba.

Note du 2/1/2018 : je l’ai aussi fait sur un Pi 3 sans souci

Installer mpd

J’ai principalement utilisé les sources suivantes :

Installer le serveur mpd, le client mpc et le gestionnaire de son alsa-utils.

apt-get install mpd mc alsa-utils

Installer les codecs qui pourraient être utiles :

apt-get install lame flac faad vorbis-tools

Vérifier que le son sort du Raspberry Pi

brancher des haut-parleurs dans la prise jack audio du Pi. On utilise la carte audio (plutôt mauvaise sur le Pi version 1).

Selon cet article, en anglais, on teste un son comme ça :

aplay /usr/share/sounds/alsa/Front_Right.wav

Si aucun son ne sort, on vérifie que le driver audio Broadcom est bien présent

lsmod | grep snd_bcm2835

S’il y a quelque chose qui s’affiche, c’est bon. Sinon, il faut charger le driver avec

modprobe snd_bcm2835

Si la commande s’éxécute sans message d’erreur c’est que c’est bon.

Mais aucune des deux commandes suivantes ne produit de son dans mes hauts parleurs

speaker-test -t sine -f 440 -c 2 -s 2
aplay /usr/share/sounds/alsa/Front_Center.wav

Je force donc l’utilisation de la sortie analogique dans laquelle les haut-parleurs sont connectés. Sinon c’est la sortie HDMI qui est utilisée par défaut.

amixer cset numid=3 1

Je n’entend toujours rien. Je monte le volume des haut-parleurs et là c’est bon.

configurer mpd

Pour bien comprendre le contenu du fichier de configuration, voir The Music Player Daemon – User’s Manual.

mpd a en principe créé un utilisateur ‘mpd’, qui appartient au groupe ‘audio’.

Modifier la configuration de samba

Les utilisateurs du groupe ‘audio’ doivent pouvoir lire les contenus de /media/iomega/MULTIMEDIA/musique et écrire dans /media/iomega/MULTIMEDIA/mpd.

Dans etc/smb.conf etc/samba/smb.conf (modifié le 2/1/2018)

valid users = @users, @audio  #utilisateurs ayant le droit de lire = de groupes @group
read only = yes
write list = @users, @audio   # utilisateurs ayant droit d’écrire
browseable = yes
public = yes

Je pense que ça ne sert à rien de régler valid users  et write list  puisque j’ai défini public=yes .

Modifier la configuration de mpd

éditer /etc/mpd.conf pour que :

music_directory		"/media/iomega/MULTIMEDIA/musique"
playlist_directory	"/media/iomega/MULTIMEDIA/musique/mpd"
db_file			"/media/iomega/MULTIMEDIA/musique/mpd/mpd.db"
user			« mpd »
#group               	"audio"
auto_update     	"yes"

volume_normalization    "yes"

bind_to_address          "192.168.1.102"
port			 "6600"

audio_output {
	type		"alsa"
	name		"Ampli salon"
#	device		"hw:0,0"	# optional
#	mixer_type      "hardware"      # optional
#	mixer_device	"default"	# optional
#	mixer_control	"PCM"		# optional
#	mixer_index	"0"		# optional
}

auto_update permet une mise à jour automatique de la base de données lorsque les fichiers sont modifiés.

Le réglage de la sortie audio de type « alsa » est identique à la configuration par défaut. La seule différence est le nom de cette sortie (« ampli salon »), qui permet d’identifier les sorties dans les clients.

Note : Si j’utilisais une carte son usb, il faudrait changer la ligne device             « hw:0,0 » , probablement en mettant « hw:1,0 ».

Pour savoir quels sont les numéros de cartes audio, taper :

cat /proc/asound/modules

Ça dit quelque chose comme :

0 snd_bcm2835
1 snd_sub_audio

La carte audio du Pi (driver snd_bcm2835) est numérotée 0. La carte USB (ici driver snd_sub_audio) est n°1.

On peut le vérifier en connectant la carte USB aux haut-parleurs puis en tapant :

aplay -D plughw:1,0 /usr/share/sounds/alsa/Front_Right.wav

Si le volume est suffisant (assez élevé), on entend une voix qui dit « front » puis « right ».

Ca fonctionne avec une carte audio à 2.82 € pièce (livraison gratuite si au moins 3 achetées) achetée sur ce site chinois.

J’ai par erreur laissé le # devant group. Cette instruction n’est donc pas utilisée. Ca n’a pas gêné, sans doute car j’avais réglé le disque comme public avec samba.

Créer les répertoires, fichiers et droits d’accès nécessaires

Créer tous les répertoires ou fichiers listés dans etc/mpd.conf et qui n’existent pas. Ensuite, j’ai placé mon utilisateur windows (alwindows) dans le groupe audio, et l’utilisateur mpd dans le groupe users mais je ne suis pas certaine que ça soit indispensable.

adduser mpd users
adduser mpd audio
adduser alwindows audio

Régler les droits d’accès et les propriétaires de tout ce qui se trouve dans les répertoires utilisés par mpd et définis dans mpd.conf :

chown -R mpd:audio /media/iomega/MULTIMEDIA
chmod -R g+rwx /media/iomega/MULTIMEDIA

chown -R mpd:audio /var/log/mpd
chmod -R g+rwx /var/log/mpd

chown -R mpd:audio /run/mpd
chmod -R g+rwx /run/mpd

chown -R mpd:audio /var/lib/mpd
chmod -R g+rwx /var/lib/mpd

Redémarrer samba :

service smbd restart

lancer mpd

mpd

Si bug « « socket: Failed to bind to ‘192.168.1.102:6600’: Address already in use » arrêter mpd avec :

service mpd stop

Puis le redémarrer avec

mpd --no-daemon --stdout --verbose

On voit la base de données qui se met à jour. Ca défile de 19h45 à 19h53 pour 65 Go de musique.

En cas de problème, il peut être utile de lire le log /var/log/mpd/mpd.log

Normalement, pour lancer mpd comme un service, on tape :

/etc/init.d/mpd start

tester avec MPdroid

MAJ : MPdroid fonctionne correctement mais son interface utilisateur est vraiment compliquée. J’ai installé M.A.L.P. – MPD Client, et c’est plus simple à utiliser.

Il suffit de renseigner l’adresse du serveur (192.168.1.102) et du port (6600) et le client se lance sur une tablette android.

Ca fonctionne : de ma tablette je déclenche de la musique.

Ecouter des radios web avec mpd

Je me suis inspirée de Un serveur musical avec MPD et Installation de MPD sur un raspberry pi !.

Dans le répertoire déclaré en tant que « playlist_directory » lors de la configuration de mpd (« /media/iomega/MULTIMEDIA/musique/mpd »), créer un fichier radios.m3u , dans lequel on place les url des radios sous la forme :

#EXTM3U
#EXTINF:-1,Country
http://streaming.radionomy.com/NashvilleEdge

Les sites European radios  et flux Radio liste les adresses de streaming des radios.

Ensuite, régler le propriétaire et les droits d’accès de ce fichier :

chown -R mpd:audio /media/iomega/MULTIMEDIA/musique/mpd/radios.m3u
chmod -R g+rwx /media/iomega/MULTIMEDIA/musique/mpd/radios.m3u

Réglages et création d’un flux audio

Imaginons que j’aime bien la musique qui se joue dans le salon et que j’ai envie de l’écouter dans mon bureau. Dans ce cas, il me suffit d’écouter le flux audio issu du salon. Mpd peut être configuré pour générer un flux audio qu’on peut écouter avec un navigateur internet ou autre lecteur de musique capable de lire un flux internet. On peut même créer une radio web diffusée à l’extérieur. Mais bon, ce n’est pas ce que je veux faire !

Pour générer un flux audio, éditer etc/mpd.conf et régler le « audio_output » de type « httpd » :

audio_output {
	type		"httpd"
	name		"flux httpd"
	encoder		"lame"			# optional, vorbis or lame
	port		"8000"
	bind_to_address "192.168.1.102"   		# optional, IPv4 or IPv6
	quality		"5.0"			# do not define if bitrate is defined
#	bitrate		"128"			# do not define if quality is defined
	format		"44100:16:1"
	max_clients "0"         # optional 0=no limit
}

redémarrer mpd avec service mpd restart .

Pour accéder au flux ainsi défini, on tape l’adresse suivante dans un navigateur (ou un lecteur capable de lire un flux réseau) :

  • http://192.168.1.102:8000/mpd.mp3 si on a réglé l’encodeur sur « lame »
  • http://192.168.1.102:8000/mpd.ogg si on a réglé l’encodeur sur « vorbis »

Mais cette adresse ne fonctionne que si l’on a lancé la génération d’un flux de streaming avec un client. J’ai utilisé Chimney, disponible gratuitement sur le « store » Windows 10 :

  • lancer la lecture de quelque chose ;
  • dans l’onglet général, cocher « stream to desktop » ;
  • dans l’onglet output, cochés les outputs « ampli salon » et « flux httpd » (ce sont les noms que j’ai défini dans mpd.conf) ;
  • dans l’onglet streaming, rien à faire si on n’a pas modifié le port par défaut (8000).

Ensuite, je peux ouvrir le flux audio, dans VLC ou un navigateur internet.

Utilisation de MPdroid

MPdroid est un client android pour mpd.

MAJ : MPdroid fonctionne correctement mais son interface utilisateur est vraiment compliquée. J’ai installé M.A.L.P. – MPD Client, et c’est plus simple à utiliser.

Une fois que le client est installé, il suffit de renseigner les éléments suivants :

  • Hôte : 192.168.1.102
  • Port : 6600
  • Hôte streaming : 192.168.1.102
  • Port streaming : 8000
  • Mot de passe : rien
  • Suffixe url streaming : mpd.mp3
  • Notification persistante : pas coché

Utilisation de VLC (sur PC) pour lire le flux audio

Pour créer un lien rapide vers le flux, on ouvre le flux une première fois à la main, en indiquant http://192.168.1.102:8000/mpd.mp3 dans Média / ouvrir un flux réseau. On lance la lecture.

Ensuite, dans Média / Enregistrer la liste de lecture, on enregistre, par exemple sous le nom salon-stream.xspf  .

Celà crée un fichier (que l’on peut éditer avec notepad++ par exemple) :

<?xml version="1.0" encoding="UTF-8"?>
<playlist xmlns="http://xspf.org/ns/0/" xmlns:vlc="http://www.videolan.org/vlc/playlist/ns/0/" version="1">
	<title>Liste de lecture</title>
	<trackList>
		<track>
			<location>http://192.168.1.102:8000/mpd.mp3</location>
			<title>flux httpd</title>
			<extension application="http://www.videolan.org/vlc/playlist/0">
				<vlc:id>2</vlc:id>
				<vlc:option>network-caching=1000</vlc:option>
			</extension>
		</track>
	</trackList>
	<extension application="http://www.videolan.org/vlc/playlist/0">
			<vlc:item tid="0"/>
	</extension>
</playlist>

Et voilà, un simple clic et j’accède au flux audio sur mon ordinateur Windows.

Et maintenant ?

Je peux explorer l’intérêt de disposer d’UPnP comme expliqué dans cet article de « lesbonscomptes.com » ou avec un media player spécifique tel que proposé ici sur GitHub.

Il faut aussi que je trouve un client sous Windows 10 plus agréable que Chimney.

Monter un disque dur externe partagé sur un Raspberry Pi

Monter un disque dur externe partagé sur un Raspberry Pi

Je veux disposer d’un disque dur accessible en permanence dans la maison. Mais je ne souhaite pas le connecter à un PC à cause de la consommation d’énergie d’un tel appareil. Je vais donc le connecter à un Raspberry Pi qui pourra rester allumer en permanence et mettre les fichiers à disposition.

31/08/2022 : Je viens de mettre en service un Raspberry Pi 3B et le contenu de cet article est toujours d’actualité. J’ai juste apporté quelques précisions. 

Préparer le Raspberry Pi

J’ai utilisé un Raspberry Pi 3B, avec un dongle wifi Edimax EW-7811Un.

J’ai installé l’OS Raspberry  Lite en 64 bits  (version de fin août 2022) selon la méthode décrite dans Mise en service d’un Raspberry Pi.

Mettre le Pi à jour

Exécuter

apt update

apt upgrade

Connecter un disque dur ou une clé USB

J’ai connecté une clé USB de 32 Go.

Un disque dur doit avoir une alimentation externe. Le Pi ne pourra pas lui fournir assez d’énergie. Le disque est donc connecté d’une part à une source d’énergie, d’autre part à un port USB du Raspberry Pi pour l’échange de données.

On peut connecter le disque au Pi sans manipulations préalables. Mais ensuite il faut faire des réglages pour accéder au disque et le partager avec d’autres équipements du réseau local.

Pour ce qui suit, je me suis inspirée principalement des articles suivants :

identifier le type de disque dur

Pour savoir quel type de disque dur on a, on tape :

blkid

Le Pi nous répond avec la liste des unités de stockage qu’il a identifié. Pour moi, il y a les 3 partitions de la carte SD du Pi et le disque dur IOMEGA_HDD que je viens de connecter :

blkid-pour identifier un disque dur externe

J’apprends ainsi que le disque est monté comme sda1 et qu’il est de type NTFS (le système de fichiers).

Comme le disque utilise le système NTFS, j’installe les drivers correspondants sur le Pi :

apt-get install ntfs-3g

Monter le disque de manière permanente

Si je décide de connecter un autre disque dur, ou une clé USB, le disque Iomega pourrait devenir sda2 et je ne saurai plus comment y accéder. Il faut donc lui donner une « adresse » permanente.

On crée un répertoire sur lequel on va monter le disque dur :

mkdir /media/iomega

Evidemment on peut appeler le répertoire comme on veut, la seule contrainte est que ce soit un sous-répertoire de /media .

Il faut pouvoir donner l’accès à ce répertoire. Dans mon cas, c’est à l’utilisateur al que je veut donner l’accès. Pour savoir quel est l’identifiant de l’utilisateur (-u ) al et du groupe (-g )correspondant, je tape :

id -u al
id -g al

La réponse est 1001 pour les deux. Je sais donc que ‘group id’ est 1001 et ‘user id’ est 1001. Pour donner la propriété d’un répertoire au fichier, on définit d’abord l’utilisateur, ensuite le groupe ([utilisateur]:[groupe] ) :

chown 1001:1001 /media/iomega

Pour monter le disque dur de manière permanente sur /media/iomega :

mount -t ntfs-3g -o uid=1001,gid=1001 /dev/sda1 /media/iomega

Si le disque était en FAT32, j’aurais utilisé -t vfat  à la place de -t ntfs-3g .

02/01/2018 : En entrant cette commande j’ai eu une erreur « Mount is denied because the NTFS volume is already exclusively opened.« . J’ai simplement déconnecté le disque avec la commande umount /dev/sda1  puis exécuté de nouveau la commande précédente.

A partir de maintenant, si je veux déconnecter le disque dur, j’utilise la commande :

umount /media/iomega

Dans /etc/fstab , j’ajoute la ligne suivante :

/dev/sda1     /media/iomega   ntfs   nofail,uid=1001,gid=1001   0       0

Noter la règle nofail , qui permet d’éviter que le Pi ne bloque au démarrage si le disque dur n’est pas connecté ou pas prêt. On vérifie que tout va bien avec mount –a . S’il n’y a pas d’erreur, pas de souci.

Enfin, j’ajuste les paramètres de démarrage du Pi pour laisser le temps au disque dur de démarrer et d’être monté.

On édite /boot/cmdline.txt  et on ajoute rootdelay=5  à la fin de l’unique ligne qu’il contient :

dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait rootdelay=5

Je redémarre le Raspberry Pi et je vérifie que tout va bien en allant regarder le contenu de /media/iomega.

A ce stade, je fais une copie de la carte SD.

Accès au disque par Filezilla

A ce stade je peux transférer des fichiers dans la clé USB en utilisant Filezilla pour un accès FTP.

Partager le disque dur avec Samba

Pour cette phase, j’ai suivi les instructions de ces deux sites :

Samba a été installé lors de la mise en service du Pi. Il ne reste plus qu’à le configurer pour partager le disque dur externe. Il vaut mieux créer une copie du fichier de configuration avant de le modifier :

cp /etc/samba/smb.conf /etc/samba/smb.old

On édite ensuite /etc/samba/smb.conf  et on y ajoute tout à la fin :

[MUSIC]
comment = Musique AL
path = /media/iomega
valid users = @users
force group = users
create mask = 0660
directory mask = 0771
read only = no
browseable = yes
public = yes

Je ne suis pas très sûre des raisons d’être de la plupart des lignes. Mais valid users  définit que tous les utilisateurs du groupe users auront droit d’accès.

On redémarre samba pour utiliser les nouveaux paramètres :

service smbd restart

Accéder au disque de l’extérieur

Avec certaines versions de Windows, je peux maintenant voir le disque Iomega dans l’explorateur de fichiers, sous « réseau ». Avec d’autres versions (Windows 10 en particulier), il faudra que je crée une « connexion réseau ». Mais dans tous les cas Windows me demandera un nom d’utilisateur et un mot de passe…

créer un nom d’utilisateur Samba

Sur le Pi, j’ajoute un utilisateur ‘alwindows’ qui fera partie du groupe users, que j’ai précédemment défini comme pouvant accéder au disque (dans smb.conf).

useradd alwindows -m -G users

Je crée un mot de passe samba pour cet utilisateur. Le Pi me demande de l’entrer deux fois :

smbpasswd -a alwindows

Je redémarre samba.

service smbd restart

Sous Windows 10, créer un lecteur réseau

Dans l’ordinateur Windows si je vois le disque dur sous « Réseau », je clique dessus et je met cet utilisateur et le mot de passe.

Le disque dur du Raspberry Pi vu dans l'explorateur Windows

Sous Windows 10 (et dans d’autres cas si mes souvenirs sont bons), il faut créer un lecteur réseau.

Dans l’explorateur de fichier, cliquer à droite sur « Ce PC » et choisir « connecter un lecteur réseau » :

Créer un lecteur réseau sous WIndows 10 - étape 1

Indiquer la localisation du lecteur : \\NomDuPi\NomDuDisqueDansSamba :

Créer un lecteur réseau sous WIndows 10 - étape 2

Entrer le nom d’utilisateur Samba et le mot de passe précédemment défini :

Créer un lecteur réseau sous WIndows 10 - étape 3

J’ai maintenant accès au disque dur géré par le Raspberry Pi sous « Ce PC »

Créer un lecteur réseau sous WIndows 10 - et voilà !

Et voilà. Les fichiers de ce disque sont accessibles 24h sur 24 sans laisser un ordinateur énergivore en fonctionnement !

Une application android pour afficher une page web d’un Raspberry Pi

Une application android pour afficher une page web d’un Raspberry Pi

J’ai un Raspberry Pi réglé en tant que serveur web et qui gère motion, un programme pour configurer et diffuser des flux vidéos. L’un de ces flux vidéos provient d’une vieille tablette android, dont la caméra avant a été transformée en caméra IP (article ici). Ce que je veux maintenant c’est que la tablette, qui sera fixée au mur dans un couloir, affiche en permanence le contenu d’une page web qui montre entre autres les flux vidéos capturés. J’ai donc créé une application android pour la tablette, avec App Inventor.

Je considère ici que le lecteur connaît le fonctionnement d’App Inventor. Si ce n’est pas le cas, cette page en anglais donne les bases.

Document et fichiers de l’application mis à jour le 19/7/2016 pour permettre à l’utilisateur de définir l’url à afficher. Voir paragraphe « la version 3 ».

L’application avec App Inventor

Pour afficher une page web avec une application App Inventor, on a deux solutions possibles :

  1. avec un « web viewer » ;
  2. avec « activity starter », qui démarre alors le navigateur web de l’appareil ;

Et il faut interdire l’écran d’économie d’énergie

Comme la tablette sera constamment branchée, je préfère qu’elle soit toujours visible plutôt que d’avoir à la « tapoter » à chaque fois.

J’ai donc adopté l’astuce issue de cette source, pour garder l’écran allumé en permanence. Une horloge déclenche toutes les x secondes (TimerInterval) une notification d’alerte. Comme l’alerte a été réglé pour que sa couleur de fond et son texte soient transparents, on ne voit pas qu’elle apparaît et elle empêche l’écran de s’éteindre. C’est la partie when Clock1.Timer  des blocs qui suivent.

Solution 1 avec webviewer

En mode « designer » :

  • screen1 est en sizing  « fixed », ScreenOrientation  en « sensor » ;
  • Dans les « non visible components », on trouve trois composants :
    • webviewer, qui visualise une page web ;
    • notifier1 réglé avec BackgroundColor  et TextColor  sur « none » ;
    • clock1 avec TimerAlwaysFires  et TimerEnabled  cochés, TimerInterval  sur 5000 (ce sont des millisecondes en principe, donc ici 5 secondes)

En mode « blocks » :

Application android App Inventor pour une page web d'un Raspberry Pi

Voici le fichier en .aia utilisable sur MIT App Inventor (à renommer en .aia au lieu de .zip) : raspberry_pi_dashboard_webviewer

Et voici l’affichage qui en résulte (malgré les règles de style responsive…), en mode portrait puis paysage :

Solution 2 avec « Activity Starter »

En mode « designer » :

  • screen1 est en sizing  « fixed », ScreenOrientation  en « sensor » et contient un « label » et un bouton ;
  • Dans les « non visible components », on trouve trois composants :
    • activityStarter, dont la propriété action  est « android.intent.action.VIEW » et la propriété DataUri  est « http://192.168.1.30/ » ;
    • notifier1 réglé avec BackgroundColor  et TextColor  sur « none » ;
    • clock1 avec TimerAlwaysFires  et TimerEnabled  cochés, TimerInterval  sur 5000 (ce sont des millisecondes en principe, donc ici 5 secondes)

En mode « blocks » :

Application android App Inventor avec ActivityStarter

Voici le fichier en .aia utilisable sur MIT App Inventor (à renommer en .aia au lieu de .zip) : raspberry_pi_dashboard_activity_starter

Et voici l’affichage qui en résulte. On voit que c’est nettement mieux adapté car les caractéristiques responsive de ma page sont correctement respectées :

La version 3

L'interface utilisateur Je me suis rendu compte que ce n’était pas très pratique : si l’adresse du tableau de bord change, je suis obligée d’aller changer un bloc dans appinventor puis de reconstruire l’application avant de l’installer sur la tablette destinataire. J’ai donc réalisé une version 3, dans laquelle l’utilisateur décide lui-même quelle sera l’adresse de la page à ouvrir.

J’ai mis l’adresse actuelle de mon tableau de bord par défaut (192.168.1.103) mais l’utilisateur peut maintenant en changer sans difficulté. On peut même taper ‘google.com’ et accéder ainsi à la page http://google.com.

L’interface utilisateur (photo à droite) est très simple : lorsqu’on ouvre l’application, on voit écrit une suggestion d’adresse, qui est aussi l’adresse par défaut. L’utilisateur tape ce qu’il veut et clique sur le bouton « voir le dashboard ». L’application vérifie que cette adresse est valide (dans la photo c’est en cours) puis ouvre l’adresse dans un navigateur. Tant que l’application est active, l’écran ne se met jamais en veille.

Par contre, si on se trompe d’adresse locale, il n’y a pas d’erreur de type 404 qui est renvoyée et l’application cherche éternellement. L’utilisateur peut cependant indiquer une nouvelle adresse dans le champs prévu et relancer avec le bouton « voir le dashboard ». C’est irritant mais pas bloquant…

Pour mettre au point le système de vérification, je me suis inspirée de Check Internet Connection In App Inventor.

L’interface de cette application avec appinventor2 (mode designer)

Vue "designer" App Inventor 2
Le composant « notifier » est réglé sur fond transparent et texte transparent pour être invisible.

Les blocs de programmation avec appinventor 2 (mode blocks)

Vue "blocks" App Inventor 2

Les fichiers résultat

la version .aia utilisable sur MIT App Inventorrenommer en .aia au lieu de .zip) : raspberry_pi_dashboard_V3 (zip / aia)

Fichier Version 3 : Application android (apk)

Et maintenant

J’ai appris que seule la solution 2, avec « activity starter » permet de visualiser correctement une page web dans une application android, en respectant les règles de style.

Voici un fichier zip qui contient la page index.php servie par le Raspberry Pi, la feuille de style associée : index-Pi (zip)

Et voici l’application (apk) téléchargeable sur une tablette (mais attention elle ne fonctionnera que pour lire une page index.html ou index.php située à l’adresse 192.168.1.30) : Application android (apk)

Ma vieille tablette samsung est maintenant transformée en caméra IP (cf cet autre article) et affiche en permanence une page web qui contient le flux vidéo de la tablette elle même (mais servie par un raspberry Pi) et d’une autre caméra. C’est chouette, non ?