
Prends le contrôle de ta TV avec HDMI-CEC
T'es-tu déjà demandé comment ta Switch pouvait allumer ta télévision directement sur la bonne source ? L'inverse est aussi vrai : si tu changes de source sur ta Switch, celle-ci peut s'allumer !
C'est grâce à un protocole de communication appelé Consumer Electronics Control, ou CEC, dont les messages transitent par les câbles HDMI. C'est d'ailleurs grâce à lui que tu peux parfois contrôler ta TV avec ta manette Xbox, ou contrôler ta Xbox avec ta télécommande.
Bon, ça ne marche pas toujours complètement car l'implémentation du CEC est propre à chacun des constructeurs, et ils n'en font parfois qu'à leur tête.
Dans mon cas, je voulais pouvoir allumer et éteindre Kodi sur mon Raspberry en fonction de ce que ma TV affiche comme source, pourquoi pas également réveiller / mettre en veille mon disque dur. Mais on peut imaginer un tas de possibilité, comme lancer la machine à Popcorn, allumer une lumière d'ambiance. Les possibilités sont infinies.
Il existe 2 packages sur Linux pour faire des trucs avec le CEC, cec-ctl
et cec-client
. Je trouvais que cec-client
était plus simple pour envoyer des commandes du Raspberry à ma TV et cec-ctl
pour écouter ce qui se passe sur le CEC.
On va donc utiliser un peu des deux.
Contrôler ta TV grâce à un Raspberry
Je ne me suis pas vraiment servi de ces possibilités, mais je les trouvais tout de même intéressantes.
Éteindre la TV: echo "standby 0" | cec-client RPI -s -d 1
Allumer la TV echo "on 0" | cec-client RPI -s -d 1
Changer la source de la TV pour le Raspberry: echo "as" | cec-client RPI -s -d 1
Changer la source pour ne plus être celle du Raspberry: echo "is" | cec-client RPI -s -d 1
Contrôler un Raspberry grâce à ta TV
La première étape, c'est de s'annoncer. Un peu comme quand t'arrives en société et que tu ne connais personne. Si tu ne le fais pas, et bien personne ne viendra vraiment te parler, c'est pareil ici, l'information sera plus limitée sans s'être présenté.
sudo cec-ctl --record --osd-name Fromage
--record
pour "Recording Device". Il y a d'autres mode comme--tv
ou--switch
(pour les switch HDMI), mais dans mon cas, c'est bien un périphérique de diffusion.--osd-name Fromage
pour nommer ton périphérique. Sur certaines TV, les sources s'affichent avec leur nom.
Le retour de cette commande t'indique ton Physical Address
, qui n'est autre qu'une adresse IP.
Driver Info:
Driver Name : vc4_hdmi
Adapter Name : vc4-hdmi-0
Capabilities : 0x0000011e
Logical Addresses
Transmit
Passthrough
Remote Control Support
Connector Info
Driver version : 6.6.51
Available Logical Addresses: 1
DRM Connector Info : card 1, connector 32
Physical Address : 1.0.0.0
Logical Address Mask : 0x0002
CEC Version : 2.0
Vendor ID : 0x000c03 (HDMI)
OSD Name : 'Record'
Logical Addresses : 1 (Allow RC Passthrough)
Logical Address : 1 (Recording Device 1)
Primary Device Type : Record
Logical Address Type : Record
All Device Types : Record
RC TV Profile : None
Device Features :
None
Ici, ma physical address est donc : 1.0.0.0
. Je pense que c'est simplement parce que je me suis branché sur le port HDMI numéro 1 de ma TV.
Une fois les présentations faites, tu peux aller écouter les évènements qui se passent grâce à la commande sudo cec-ctl -m. Cette commande te permet de recevoir tous les messages issus de la télévision par le biais du protocole CEC, que cela s'adresse à ton périphérique ou pas. Il y a plusieurs messages qui vont transiter, et la plupart ne nous intéresseront pas. Dans notre cas, j'ai isolé les messages suivants :
Je dois éteindre Kodi dans 2 cas:
- Quand ma TV s'éteint:
Received from TV to all (0 to 15): STANDBY (0x36)
- Quand ma TV change de source pour une différente de mon Raspberry:
Received from TV to all (0 to 15): ACTIVE_SOURCE (0x82):
phys-addr: 0.0.0.0
Et je dois l'allumer dans 2 cas:
- Quand ma TV change de source pour celle de mon Raspberry:
Received from TV to all (0 to 15): ACTIVE_SOURCE (0x82):
phys-addr: 1.0.0.0
- Quand ma TV s'allume directement avec mon Raspberry en source:
Received from TV to all (0 to 15): ROUTING_CHANGE (0x80):
orig-phys-addr: 0.0.0.0
new-phys-addr: 1.0.0.0
Et le script qui automatise tout ça
Pour récupérer mon adresse physique et s'annoncer en tant que périphérique de diffusion, il suffit de faire la première commande et d'en récupérer la sortie pour trouver la ligne Physical address: 1.0.0.0
:
def get_physical_address():
try:
# Exécuter la commande cec-ctl
result = subprocess.run(['sudo', 'cec-ctl', '--record', '--osd-name', 'Kodi'], capture_output=True, text=True, check=True)
output = result.stdout
# Utiliser une expression régulière pour extraire la valeur de Physical Address
match = re.search(r'Physical Address\s+:\s+([\d\.]+)', output)
if match:
physical_address = match.group(1)
return physical_address
else:
print("Physical Address not found in the output.")
return None
except subprocess.CalledProcessError as e:
print(f"Error executing cec-ctl: {e}")
return None
Enfin, pour lancer le monitoring et effectuer des actions en fonction de la sortie :
def monitor_cec(physical_address):
print(f"Monitoring CEC with physical address {physical_address}")
try:
# Lancer la commande de monitoring
process = subprocess.Popen(['sudo', 'cec-ctl', '-m'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
numberOfLinesToTrack = -1
stop_kodi_timer = None
# Lire la sortie en temps réel
for line in process.stdout:
if numberOfLinesToTrack > 0:
numberOfLinesToTrack -= 1
if "from TV to all" in line and "STANDBY" in line:
print("Received STANDBY command. Stopping Kodi")
stop_kodi_thread_starting(stop_kodi_timer)
elif "from TV to all" in line and "ROUTING_CHANGE" in line:
# Next line will be where TV is actually, and after it will be the destination.
numberOfLinesToTrack = 2
elif "from TV to all" in line and "ACTIVE_SOURCE" in line:
# Next line will be where TV is going.
numberOfLinesToTrack = 1
elif numberOfLinesToTrack == 0:
if physical_address in line:
print("Starting Kodi due to active source change")
start_kodi(stop_kodi_timer)
else:
print("Stopping Kodi due to active source change")
stop_kodi_thread_starting(stop_kodi_timer)
numberOfLinesToTrack = -1
# Attendre que le processus se termine
process.wait()
except Exception as e:
print(f"Error monitoring cec-ctl: {e}")
La petite subtilité ici est que je vais lire les lignes une à une. Parfois, j'ai besoin de combiner 2 lignes pour m'assurer que la TV change bien sa source pour moi, ou pas.
C'est exactement ce que font les conditions :
if "from TV to all" in line and "STANDBY" in line:
print("Received STANDBY command. Stopping Kodi")
stop_kodi_thread_starting(stop_kodi_timer)
elif "from TV to all" in line and "ROUTING_CHANGE" in line:
# Next line will be where TV is actually, and after it will be the destination.
numberOfLinesToTrack = 2
elif "from TV to all" in line and "ACTIVE_SOURCE" in line:
# Next line will be where TV is going.
numberOfLinesToTrack = 1
numberOfLinesToTrack
contient un entier qui est mis à une certaine valeur lorsque l'on rencontre l'évènement ROUTING_CHANGE ou ACTIVE_SOURCE, puis décrémenté à chaque nouvelle chaîne reçue. Une fois atteint 0, il va regarder si l'adresse IP mentionnée est présente. Cela correspond aux évènements de changement de source que j'ai listés un peu plus haut.
elif numberOfLinesToTrack == 0:
print(f"Line to track: {line}")
if physical_address in line:
print("Starting Kodi due to active source change")
start_kodi(stop_kodi_timer)
else:
print("Stopping Kodi due to active source change")
stop_kodi_thread_starting(stop_kodi_timer)
Si l'adresse physique est présente sur cette ligne, on va démarrer Kodi. Si non, on va l'éteindre.
Tu peux retrouver le script complet sur mon Github