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-utilsII-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 1Dans 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 -sDe la même façon, nous pouvons remettre la télévision en veille :
echo "standby 0" | cec-client -sSachant 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 1III. 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-clientPour 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
 
doneVoici comment lancer cec-client pour rediriger la sortie dans un fichier :
cec-client -f /tmp/cecEnsuite, vous pouvez lancer le script, qui ira lire dans le fichier écrit par cec-client :
./cec-test.sh /tmp/cecEt 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 - BYEIII-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-devDans 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 -lcec3Et 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 ldconfigIII-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 -lcec4Vous 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':
        breakVous 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
 
doneV. 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.

 
		




