#!/usr/bin/env python3 """ Script pour activer les boutons volume du Chromebook Taeko Les boutons sont connectés aux GPIO 3 (Volume Down) et 4 (Volume Up) sur le chip cros-ec-gpio (gpiochip1) """ import sys import gpiod from evdev import UInput, ecodes as e import time import signal # Configuration CHIP_PATH = "/dev/gpiochip1" # cros-ec-gpio VOLUME_DOWN_PIN = 3 # EC:EC_VOLDN_BTN_ODL VOLUME_UP_PIN = 4 # EC:EC_VOLUP_BTN_ODL # Les GPIO sont actifs à LOW (ODL = Open Drain Low) # Note: gpiod retourne des Value.ACTIVE/INACTIVE, pas des entiers POLL_INTERVAL = 0.01 # 10ms class VolumeButtonMapper: def __init__(self): self.ui = None self.request = None self.running = True # États des boutons (1 = relâché, 0 = appuyé) self.voldown_state = 1 self.volup_state = 1 # Timestamps pour la répétition automatique self.voldown_press_time = None self.voldown_last_repeat = None self.volup_press_time = None self.volup_last_repeat = None # Paramètres de répétition self.HOLD_DELAY = 1.0 # Délai avant répétition (1 seconde) self.REPEAT_INTERVAL = 0.2 # Intervalle de répétition (0.2 secondes) def setup(self): """Initialise le périphérique virtuel et les GPIO""" print(f"Initialisation du mapper de boutons volume...") # Créer un périphérique d'entrée virtuel cap = { e.EV_KEY: [e.KEY_VOLUMEDOWN, e.KEY_VOLUMEUP] } self.ui = UInput(cap, name='chromebook-volume-buttons', version=0x1) print(f"Périphérique virtuel créé: {self.ui.device.path}") # Configurer les GPIO en lecture simple (sans edge detection) self.request = gpiod.request_lines( CHIP_PATH, consumer="volume-buttons", config={ VOLUME_DOWN_PIN: gpiod.LineSettings( direction=gpiod.line.Direction.INPUT, bias=gpiod.line.Bias.PULL_UP ), VOLUME_UP_PIN: gpiod.LineSettings( direction=gpiod.line.Direction.INPUT, bias=gpiod.line.Bias.PULL_UP ) } ) print(f"GPIO configurés:") print(f" - Volume Down: GPIO {VOLUME_DOWN_PIN}") print(f" - Volume Up: GPIO {VOLUME_UP_PIN}") print(f"Appuyez sur les boutons volume pour tester...") def send_volume_event(self, key_code, button_name): """Envoie un événement de volume (appui complet)""" self.ui.write(e.EV_KEY, key_code, 1) # Appuyer self.ui.syn() self.ui.write(e.EV_KEY, key_code, 0) # Relâcher self.ui.syn() def check_buttons(self): """Vérifie l'état des boutons et gère la répétition automatique""" try: # Lire les deux GPIO values = self.request.get_values([VOLUME_DOWN_PIN, VOLUME_UP_PIN]) voldown_gpio = values[0] volup_gpio = values[1] current_time = time.time() # === GESTION VOLUME DOWN === # ODL (Open Drain Low): INACTIVE = bouton appuyé, ACTIVE = bouton relâché voldown_pressed = (voldown_gpio == gpiod.line.Value.INACTIVE) # Détection du changement d'état if voldown_pressed and self.voldown_state == 1: # Transition relâché -> appuyé print("Volume Down appuyé") self.voldown_state = 0 self.voldown_press_time = current_time self.voldown_last_repeat = None # Action immédiate au premier appui self.send_volume_event(e.KEY_VOLUMEDOWN, "Volume Down") print(" -> Son baissé") elif not voldown_pressed and self.voldown_state == 0: # Transition appuyé -> relâché print("Volume Down relâché") self.voldown_state = 1 self.voldown_press_time = None self.voldown_last_repeat = None elif voldown_pressed and self.voldown_state == 0: # Bouton toujours appuyé - vérifier si répétition nécessaire time_held = current_time - self.voldown_press_time if time_held >= self.HOLD_DELAY: # Bouton maintenu assez longtemps if self.voldown_last_repeat is None: # Première répétition self.voldown_last_repeat = current_time self.send_volume_event(e.KEY_VOLUMEDOWN, "Volume Down") print(" -> Son baissé (répétition)") elif (current_time - self.voldown_last_repeat) >= self.REPEAT_INTERVAL: # Répétitions suivantes self.voldown_last_repeat = current_time self.send_volume_event(e.KEY_VOLUMEDOWN, "Volume Down") print(" -> Son baissé (répétition)") # === GESTION VOLUME UP === # ODL (Open Drain Low): INACTIVE = bouton appuyé, ACTIVE = bouton relâché volup_pressed = (volup_gpio == gpiod.line.Value.INACTIVE) # Détection du changement d'état if volup_pressed and self.volup_state == 1: # Transition relâché -> appuyé print("Volume Up appuyé") self.volup_state = 0 self.volup_press_time = current_time self.volup_last_repeat = None # Action immédiate au premier appui self.send_volume_event(e.KEY_VOLUMEUP, "Volume Up") print(" -> Son augmenté") elif not volup_pressed and self.volup_state == 0: # Transition appuyé -> relâché print("Volume Up relâché") self.volup_state = 1 self.volup_press_time = None self.volup_last_repeat = None elif volup_pressed and self.volup_state == 0: # Bouton toujours appuyé - vérifier si répétition nécessaire time_held = current_time - self.volup_press_time if time_held >= self.HOLD_DELAY: # Bouton maintenu assez longtemps if self.volup_last_repeat is None: # Première répétition self.volup_last_repeat = current_time self.send_volume_event(e.KEY_VOLUMEUP, "Volume Up") print(" -> Son augmenté (répétition)") elif (current_time - self.volup_last_repeat) >= self.REPEAT_INTERVAL: # Répétitions suivantes self.volup_last_repeat = current_time self.send_volume_event(e.KEY_VOLUMEUP, "Volume Up") print(" -> Son augmenté (répétition)") except Exception as ex: print(f"Erreur lors de la lecture des GPIO: {ex}") def run(self): """Boucle principale de monitoring des GPIO""" try: self.setup() # Boucle de polling while self.running: # Vérifier les deux boutons self.check_buttons() # Petit délai pour éviter de surcharger le CPU time.sleep(POLL_INTERVAL) except PermissionError: print("\nErreur: Permission refusée.") print("Ce script doit être exécuté avec les privilèges root:") print(f" sudo python3 {sys.argv[0]}") sys.exit(1) except KeyboardInterrupt: print("\nArrêt demandé par l'utilisateur...") except Exception as ex: print(f"\nErreur: {ex}") import traceback traceback.print_exc() sys.exit(1) finally: self.cleanup() def cleanup(self): """Nettoie les ressources""" print("\nNettoyage...") if self.request: self.request.release() if self.ui: self.ui.close() print("Arrêté proprement.") def signal_handler(self, signum, frame): """Gère les signaux pour un arrêt propre""" self.running = False def main(): mapper = VolumeButtonMapper() # Configurer les gestionnaires de signaux signal.signal(signal.SIGINT, mapper.signal_handler) signal.signal(signal.SIGTERM, mapper.signal_handler) mapper.run() if __name__ == "__main__": main()