I. Introduction▲
Le High-Definition Multimedia Interface (HDMI) est une norme définissant une interface audio et vidéo entièrement numérique. Mais en plus de faire transiter des flux, une seconde norme, qui est optionnelle, permet la communication de commandes de contrôle entre les périphériques connectés. Celle-ci s'appelle Consumer Electronics Control (CEC) et cet article explique comment contrôler le Raspberry Pi grâce aux signaux envoyés à la télévision par la télécommande.
C'est grâce à la norme CEC, que nous pouvons contrôler Kodi avec la télécommande.
I-A. Support▲
Tous les appareils ne supportent par la norme CEC. Cette page énumère les constructeurs supportant la norme et quelles sont les commandes supportées.
Pour ne pas aider, chaque constructeur utilise un nom différent pour appeler leur propre implémentation de la norme(1) :
Marque |
Nom de l'implémentation |
AOC |
E-Link |
Funai, Sylvania, Emerson, Magnavox, Philips |
Fun-Link |
Insignia |
INlink |
ITT |
T-Link |
Hitachi |
HDMI-CEC |
LG |
SimpLink |
Mitsubishi |
NetCommand for HDMI |
Onkyo |
Remote Interactive over HDMI (RIHD) |
Panasonic |
EZ-Sync |
Philips |
EasyLink |
Pioneer |
Kuro Link |
Samsung |
Anynet+ |
Sharp |
Aquos Link |
Sony |
BRAVIA Link, BRAVIA Sync, Control for HDMI |
Toshiba |
CE-Link, Regza Link |
Runco International |
RuncoLink |
I-B. Configuration▲
La configuration utilisée à travers le tutoriel est la suivante :
- une télévision connectée au Raspberry Pi à l'aide d'un câble HDMI ;
- une télécommande permettant de contrôler la télévision.
Tous les câbles HDMI ne supportent pas la norme CEC.
II. Démarrage▲
Les distributions Linux ont accès aux fonctionnalités de la norme CEC à travers la bibliothèque libCEC. Celle-ci ne suffit pas pour facilement accéder aux fonctionnalités liées à la norme CEC. GNU/Linux intègre aussi un programme utilitaire qui écoutera et affichera les données CEC provenant du câble HDMI :
sudo aptitude install cec-utils
II-A. Faire un scan des périphériques CEC▲
Avant toute chose, détectons les périphériques CEC connectés :
echo "scan" | cec-client -s -d 1
Dans la documentation de cec-client, nous pouvons découvrir que l'option -s permet d'exécuter une commande unique (et après, le programme quitte) et -d permet de définir le niveau de journalisation.
La commande retourne :
opening a connection to the CEC adapter...
requesting CEC bus information ...
CEC bus information
===================
device #0: TV
address: 0.0.0.0
active source: no
vendor: Samsung
osd string: TV
CEC version: unknown
power status: standby
language: fra
device #1: Recorder 1
address: 2.0.0.0
active source: no
vendor: Pulse Eight
osd string: CECTester
CEC version: 1.4
power status: on
language: eng
currently active source: unknown (-1)
Dans la sortie de cec-client, nous observons deux périphériques alors que nous n'avons qu'une seule télévision. Cela est dû au fait que pour interagir avec les périphériques CEC, votre périphérique doit s'inscrire à ce réseau. C'est ce que fait ici le client, en initialisant un périphérique « CECTester ».
II-B. Allumer/Mettre en veille la télévision▲
Pour commencer, allumons la télévision grâce à cec-utils. Pour cela, il faut envoyer un signal « on 0 ».
echo "on 0" | cec-client -s
De la même façon, nous pouvons remettre la télévision en veille :
echo "standby 0" | cec-client -s
Sachant que seule la télévision est connectée au Raspberry Pi, la télévision possède donc l'identifiant 0 au sein de l'outil cec-client.
II-C. Obtenir la liste des commandes disponibles▲
Précédemment, nous avons utilisé les commandes scan, on et standby. Toutefois, il n'est pas possible de deviner ces commandes : cec-client possède un code spécifique pour obtenir les commandes disponibles :
echo h | cec-client -s -d 1
III. Contrôler le Raspberry Pi avec la télécommande▲
Avant de commencer, nous pouvons vérifier que les appuis de touches de la télécommande sont bien envoyés au Raspberry Pi avec la commande :
cec-client
Pour chaque appui, nous pouvons lire :
TRAFFIC: [ 7267] >> 01:44:04
DEBUG: [ 7267] key pressed: right (4)
DEBUG: [ 7267] >> TV (0) -> Recorder 1 (1): user control pressed (44)
TRAFFIC: [ 7375] >> 01:8b:04
DEBUG: [ 7375] key released: right (4)
DEBUG: [ 7375] >> TV (0) -> Recorder 1 (1): vendor remote button up (8B)
Si lorsque vous appuyez sur les flèches de votre télécommande, rien ne se passe dans la sortie de cec-client, allez dans les paramètres de votre télévision afin d'activer CEC, ou encore de lancer une recherche des périphériques connectées à travers CEC, et d'indiquer à la télévision d'utiliser le périphérique « CECTester ».
Si cec-client ne réagit toujours pas à la télécommande, tapez « as » directement dans la sortie de cec-client pour passer la télévision en source active.
III-A. À partir de BASH▲
Vous pouvez rediriger la sortie de cec-client dans un fichier. Ensuite, il suffit de lire ce fichier avec un script pour savoir sur quelle touche l'utilisateur a appuyé :
echo ""
>
${1}
while
true
do
if
grep -q "key pressed: left"
${1}
then
echo "Vous avez appuyé sur la flèche gauche"
fi
if
grep -q "key pressed: right"
${1}
then
echo "Vous avez appuyé sur la flèche droite - BYE"
exit 0
fi
echo ""
>
${1}
sleep 1s
done
Voici comment lancer cec-client pour rediriger la sortie dans un fichier :
cec-client -f /tmp/cec
Ensuite, vous pouvez lancer le script, qui ira lire dans le fichier écrit par cec-client :
./cec-test.sh /tmp/cec
Et retournera ce qui suit, après avoir appuyé trois fois à gauche et une fois à droite :
Vous avez appuyé sur la flèche gauche
Vous avez appuyé sur la flèche gauche
Vous avez appuyé sur la flèche gauche
Vous avez appuyé sur la flèche droite - BYE
III-B. À partir de libCEC▲
Afin d'écrire un programme communiquant dans la norme CEC, vous devez installer la bibliothèque libCEC :
apt-get install libcec-dev
Dans la suite des exemples, l'affichage du journal complet de la bibliothèque a été commenté. Décommenttez la ligne relative aux logs afin d'obtenir de meilleures informations sur la connexion entre le Raspberry Pi et la télévision.
III-B-1. libCEC3▲
Raspbian, à travers les paquets de la distribution, propose la version 3 de la bibliothèque libCEC.
III-B-1-a. En C▲
Le programme suivant vous permettra de détecter les appuis sur les touches de la télécommande :
#include <libcec/cecc.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
static
int
g_bExit=
0
;
// Fonction appelée lorsque le programme reçoit un signal SIGINT
// => provoque l'arrêt de la boucle principale du main()
static
void
sighandler
(
int
iSignal)
{
printf
(
"
Capture du signal : %d - sortie
\n
"
, iSignal);
g_bExit =
1
;
}
// Fonction pour afficher les messages de journalisation de libcec
static
int
cb_cec_log_message
(
void
*
lib, const
cec_log_message message)
{
(
void
)lib;
const
char
*
strLevel;
switch
(
message.level)
{
case
CEC_LOG_ERROR:
strLevel =
"
ERROR:
"
;
break
;
case
CEC_LOG_WARNING:
strLevel =
"
WARNING:
"
;
break
;
case
CEC_LOG_NOTICE:
strLevel =
"
NOTICE:
"
;
break
;
case
CEC_LOG_TRAFFIC:
strLevel =
"
TRAFFIC:
"
;
break
;
case
CEC_LOG_DEBUG:
strLevel =
"
DEBUG:
"
;
break
;
default
:
break
;
}
printf
(
"
%s[%lld]
\t
%s
\n
"
, strLevel, message.time, message.message);
return
0
;
}
// Fonction appelée à chaque appui sur une touche de la télécommande
static
int
keyPressHandler
(
void
*
lib, const
cec_keypress key)
{
(
void
)lib;
printf
(
"
Appui touche détecté : %d pendant %d
\n
"
,key.keycode,key.duration);
return
0
;
}
int
main
(
)
{
// Installation de notre gestionnaire de signaux
if
(
signal
(
SIGINT, sighandler) ==
SIG_ERR)
{
printf
(
"
Impossible de placer le gestionnaire de signaux
\n
"
);
return
-
1
;
}
libcec_configuration g_config;
libcec_connection_t g_iface;
char
strPort[10
];
// Configuration des callbacks
ICECCallbacks g_callbacks;
// g_callbacks.CBCecLogMessage = &cb_cec_log_message;
g_callbacks.CBCecKeyPress =
&
keyPressHandler;
// Configuration de notre instance de libcec
libcec_clear_configuration
(&
g_config);
g_config.clientVersion =
LIBCEC_VERSION_CURRENT;
g_config.bActivateSource =
0
;
g_config.callbacks =
&
g_callbacks;
snprintf
(
g_config.strDeviceName, sizeof
(
g_config.strDeviceName), "
libCEC test
"
);
g_config.deviceTypes.types[0
] =
CEC_DEVICE_TYPE_RECORDING_DEVICE;
// Initialisation
g_iface =
libcec_initialise
(&
g_config);
if
(
g_iface ==
NULL
)
{
printf
(
"
Impossible d'initialiser libCEC
\n
"
);
return
-
1
;
}
// Appel obligatoire après l'initialisation de la bibliothèque pour initialiser la machine hôte
libcec_init_video_standalone
(
g_iface);
// Recherche du périphérique de connexion
cec_adapter devices[10
];
int8_t iDevicesFound;
printf
(
"
Autodétection périphérique :
"
);
iDevicesFound =
libcec_find_adapters
(
g_iface, devices, sizeof
(
devices) /
sizeof
(
devices), NULL
);
if
(
iDevicesFound <=
0
)
{
printf
(
"
ECHEC
\n
"
);
libcec_destroy
(
g_iface);
return
1
;
}
else
{
printf
(
"
\n
Chemin : %s
\n
Port : %s
\n\n
"
, devices[0
].path, devices[0
].comm);
strcpy
(
strPort, devices[0
].comm);
}
// Ouverture de la connexion
printf
(
"
ouverture d'une connexion à l'adaptateur CEC...
\n
"
);
if
(!
libcec_open
(
g_iface, strPort, 5000
))
{
printf
(
"
impossible d'ouvrir le périphérique avec le port %s
\n
"
, strPort);
libcec_destroy
(
g_iface);
return
1
;
}
while
(!
g_bExit)
{
usleep
(
50000
);
}
// Nettoyage (fermeture de la bibliothèque)
libcec_destroy
(
g_iface);
return
0
;
}
Vous pouvez le compiler avec la commande :
gcc test-cecc.c -Wall -Wextra -o ./test-cecc -lcec3
Et vous obtiendrez une sortie de la sorte :
Autodétection périphérique :
Chemin : Raspberry Pi
Port : RPI
ouverture d'une connexion à l'adaptateur CEC...
Appui touche détecté : 3 pendant 0
Appui touche détecté : 3 pendant 107
Appui touche détecté : 3 pendant 0
Appui touche détecté : 3 pendant 120
Appui touche détecté : 4 pendant 0
Appui touche détecté : 4 pendant 111
^CCapture du signal : 2 - sortie
(Ici, j'ai appuyé trois fois sur la flèche gauche (3), et une fois sur la flèche droite (4) de la télécommande.)
III-B-2. LibCEC4▲
Depuis le 26 octobre 2016, la version 4 de la bibliothèque libCEC est disponible.
Cette nouvelle version apporte des améliorations sur l'implémentation de la bibliothèque en C tout en corrigeant des bogues dont deux reproductibles avec le Raspberry Pi 3.
Tant que la version 4 n'est pas disponible à travers les paquets de la distribution utilisée, il faudra la compiler à partir du code source pour l'installer. De plus, le fait de compiler la bibliothèque vous permet de l'utiliser avec Python.
III-B-2-a. Installation▲
La procédure est détaillée sur GitHub :
sudo apt-get update
sudo apt-get install cmake libudev-dev libxrandr-dev python-dev swig
cd
git clone https://github.com/Pulse-Eight/platform.git
mkdir platform/build
cd platform/build
cmake ..
make
sudo make install
cd
git clone https://github.com/Pulse-Eight/libcec.git
mkdir libcec/build
cd libcec/build
cmake -DRPI_INCLUDE_DIR=/opt/vc/include -DRPI_LIB_DIR=/opt/vc/lib ..
make -j4
sudo make install
sudo ldconfig
III-B-2-b. En C▲
Voici le même programme C que pour la version 3, converti pour la nouvelle version de libCEC :
#include <libcec/cecc.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
static
int
g_bExit=
0
;
// Fonction appelée lorsque le programme reçoit un signal SIGINT
// => provoque l'arrêt de la boucle principale du main()
static
void
sighandler
(
int
iSignal)
{
printf
(
"
Capture du signal : %d - sortie
\n
"
, iSignal);
g_bExit =
1
;
}
// Fonction pour afficher les messages de journalisation de libcec
static
void
cb_cec_log_message
(
void
*
lib, const
cec_log_message*
message)
{
(
void
)lib;
const
char
*
strLevel;
switch
(
message->
level)
{
case
CEC_LOG_ERROR:
strLevel =
"
ERROR:
"
;
break
;
case
CEC_LOG_WARNING:
strLevel =
"
WARNING:
"
;
break
;
case
CEC_LOG_NOTICE:
strLevel =
"
NOTICE:
"
;
break
;
case
CEC_LOG_TRAFFIC:
strLevel =
"
TRAFFIC:
"
;
break
;
case
CEC_LOG_DEBUG:
strLevel =
"
DEBUG:
"
;
break
;
default
:
break
;
}
printf
(
"
%s[%lld]
\t
%s
\n
"
, strLevel, message->
time, message->
message);
}
// Fonction appelée à chaque appui sur une touche de la télécommande
static
void
keyPressHandler
(
void
*
lib, const
cec_keypress*
key)
{
(
void
)lib;
printf
(
"
Appui touche détecté : %d pendant %d
\n
"
,key->
keycode,key->
duration);
}
// Callbacks (bien faire attention que les callbacks non utilisés soient à NULL)
static
ICECCallbacks g_callbacks =
{
// .logMessage = cb_cec_log_message,
.logMessage =
NULL
,
.keyPress =
keyPressHandler,
.commandReceived =
NULL
,
.configurationChanged =
NULL
,
.alert =
NULL
,
.menuStateChanged =
NULL
,
.sourceActivated =
NULL
}
;
int
main
(
)
{
// Installation de notre gestionnaire de signaux
if
(
signal
(
SIGINT, sighandler) ==
SIG_ERR)
{
printf
(
"
Impossible de placer le gestionnaire de signaux
\n
"
);
return
-
1
;
}
libcec_configuration g_config;
libcec_connection_t g_iface;
char
strPort[10
];
// Configuration de notre instance de libcec
libcec_clear_configuration
(&
g_config);
g_config.clientVersion =
LIBCEC_VERSION_CURRENT;
g_config.bActivateSource =
0
;
g_config.callbacks =
&
g_callbacks;
snprintf
(
g_config.strDeviceName, sizeof
(
g_config.strDeviceName), "
libCEC4
"
);
g_config.deviceTypes.types[0
] =
CEC_DEVICE_TYPE_RECORDING_DEVICE;
// Initialisation
g_iface =
libcec_initialise
(&
g_config);
if
(
g_iface ==
NULL
)
{
printf
(
"
Impossible d'initialiser libCEC
\n
"
);
return
-
1
;
}
// Appel obligatoire après l'initialisation de la bibliothèque pour initialiser la machine hôte
libcec_init_video_standalone
(
g_iface);
// Recherche du périphérique de connexion
cec_adapter devices[10
];
int8_t iDevicesFound;
printf
(
"
Autodétection périphérique :
"
);
iDevicesFound =
libcec_find_adapters
(
g_iface, devices, sizeof
(
devices) /
sizeof
(
devices), NULL
);
if
(
iDevicesFound <=
0
)
{
printf
(
"
ECHEC
\n
"
);
libcec_destroy
(
g_iface);
return
1
;
}
else
{
printf
(
"
\n
Chemin: %s
\n
Port: %s
\n\n
"
, devices[0
].path, devices[0
].comm);
strcpy
(
strPort, devices[0
].comm);
}
// Ouverture de la connexion
printf
(
"
ouverture d'une connexion à l'adaptateur CEC...
\n
"
);
if
(!
libcec_open
(
g_iface, strPort, 5000
))
{
printf
(
"
impossible d'ouvrir le périphérique avec le port %s
\n
"
, strPort);
libcec_destroy
(
g_iface);
return
1
;
}
while
(!
g_bExit)
{
usleep
(
50000
);
}
// Nettoyage (fermeture de la bibliothèque)
libcec_destroy
(
g_iface);
return
0
;
}
Le code ci-dessus compilera avec la commande suivante :
gcc test-cecc.c -Wall -Wextra -o ./test-cecc -lcec4
Vous pouvez obtenir la sortie suivante :
Autodétection périphérique :
Chemin: Raspberry Pi
Port: RPI
ouverture d'une connexion à l'adaptateur CEC...
Appui touche détecté : 3 pendant 0
Appui touche détecté : 3 pendant 220
Appui touche détecté : 0 pendant 0
Appui touche détecté : 0 pendant 108
Appui touche détecté : 33 pendant 0
Appui touche détecté : 33 pendant 110
(Ici, j'ai appuyé sur la flèche gauche (3), la touche OK (0) et la touche 1 (33) de la télécommande.)
III-B-2-c. En Python▲
Grâce à la compilation de la bibliothèque, vous avez aussi obtenu le wrapper Python.
Voici un petit programme pour tester et récupérer les appuis sur les touches de la télécommande :
#!/usr/bin/python3
import
cec
def
LogCallback
(
level, time, message):
if
level ==
cec.CEC_LOG_ERROR:
levelstr =
"ERROR: "
elif
level ==
cec.CEC_LOG_WARNING:
levelstr =
"WARNING: "
elif
level ==
cec.CEC_LOG_NOTICE:
levelstr =
"NOTICE: "
elif
level ==
cec.CEC_LOG_TRAFFIC:
levelstr =
"TRAFFIC: "
elif
level ==
cec.CEC_LOG_DEBUG:
levelstr =
"DEBUG: "
print
(
levelstr +
"["
+
str(
time) +
"] "
+
message)
return
0
def
keyPressCallback
(
key, duration):
print
(
"[key pressed] "
+
str(
key))
return
0
# Configuration de notre instance libcec
cecconfig =
cec.libcec_configuration
(
)
cecconfig.strDeviceName =
"libCEC"
cecconfig.bActivateSource =
0
cecconfig.deviceTypes.Add
(
cec.CEC_DEVICE_TYPE_RECORDING_DEVICE)
cecconfig.clientVersion =
cec.LIBCEC_VERSION_CURRENT
# cecconfig.SetLogCallback(LogCallback)
cecconfig.SetKeyPressCallback
(
keyPressCallback)
# Lancement et ouverture de notre périphérique
lib =
cec.ICECAdapter.Create
(
cecconfig)
adapter =
lib.DetectAdapters
(
)[0
].strComName
lib.Open
(
adapter)
while
True
:
command =
input(
"..."
).lower
(
)
if
command ==
'q'
:
break
Vous obtiendrez une sortie comme suit :
[key pressed] 3
[key pressed] 3
[key pressed] 2
[key pressed] 2
[key pressed] 68
[key pressed] 68
(Ici, j'ai appuyé sur la flèche gauche (3), la touche bas (2) et la touche « Lecture » (68) de la télécommande.)
On remarque deux lignes par touche appuyée : la première indique l'appui, la seconde le relâchement.
IV. Conclusion▲
Grâce à libCEC et cec-utils nous avons pu découvrir comment lire les événements envoyés à la télévision par la télécommande. Avec ces bases, on peut maintenant mettre en place une infinité de programmes se servant de la télécommande comme interface d'entrées utilisateur.
Par exemple, vous pouvez réutiliser le script BASH ci-dessus en l'associant avec xdotool comme suit, afin de déplacer le curseur de la souris à l'aide de la télécommande :
#!/bin/bash
echo ""
>
${1}
while
true
do
if
grep -q "key pressed: left"
${1}
then
xdotool mousemove_relative -- -10
0
fi
if
grep -q "key pressed: right"
${1}
then
xdotool mousemove_relative 10
0
fi
if
grep -q "key pressed: down"
${1}
then
xdotool mousemove_relative 0
10
fi
if
grep -q "key pressed: up"
${1}
then
xdotool mousemove_relative -- 0
-10
fi
if
grep -q "key pressed: select"
${1}
then
xdotool click 1
fi
echo ""
>
${1}
sleep 1s
done
V. Remerciements▲
Cet article n'aurait pas pu voir le jour sans le support de la Raspberry Pi Foundation. En effet, la fondation m'a envoyé un exemplaire du Raspberry Pi 3 : modèle B afin de permettre l'écriture de ce document. |
Merci aussi aux relecteurs : Franck Talbart et zoom61, ainsi qu'au relecteur orthographique : Maxy35.