Téléphone à cadran S63 comme lecteur de mp3

Bonjour à tous,
Le but du projet est d’utiliser un téléphone à cadran S63 comme lecteur de mp3.

Matériel : téléphone S63, Raspberry Pi Zero W, DAC externe, Raspberry Pi OS Lite.

Fonctionnement :
1- au décroché dial-tone.mp3 est joué (fréquence 440Hz)
2- lorsque l’utilisateur compose un numéro, le son s’arrête et les impulsions créées par le cadran rotatif sont comptées pour connaitre le numéro saisi par l’utilisateur.
3- une fois les différents chiffres stockés, le numéro final sert de recherche de fichier mp3. Exemple si 42 est saisi le fichier 42_texteexemple.mp3 est joué.
4- juste avant la lecture de 42_texteexemple.mp3 le fichier recherche.mp3 est joué.
5- si le aucun fichier ne commence par 42 le fichier non2.mp3 est joué. L’utilisateur doit alors raccrocher pour recommencer.

Dans mon code l’étape 1 fonctionne, mais l’étape 2 ne fonctionne pas. Le fichier lis le fichier mp3 mais ne semble pas voir les numéros du cadran.

Voici le code avec toutes les fonctions

import RPi.GPIO as GPIO
import pygame
import os
import time
from typing import Optional
 
# Constants for GPIO pins
GPIO_PIN_OFF_HOOK = 25
GPIO_PIN_DIAL = 17
 
# Configuration des GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(GPIO_PIN_OFF_HOOK, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(GPIO_PIN_DIAL, GPIO.IN, pull_up_down=GPIO.PUD_UP)
 
# Initialisation de pygame pour la lecture audio
pygame.mixer.init()
 
# Chemin du répertoire des sons
SOUND_DIR = "/home/user/sounds"
 
def play_sound(file: str) -> None:
    """Plays an audio file if it exists."""
    if os.path.exists(file):
        print(f"Lecture du fichier : {file}")
        pygame.mixer.music.load(file)
        pygame.mixer.music.play()
        while pygame.mixer.music.get_busy() and GPIO.input(GPIO_PIN_OFF_HOOK) == GPIO.LOW:
            time.sleep(0.1)
    else:
        print(f"Fichier {file} non trouvé.")
 
def stop_sound() -> None:
    """Stops the currently playing audio."""
    if pygame.mixer.music.get_busy():
        print("Arrêt de la lecture audio.")
        pygame.mixer.music.stop()
 
def wait_for_off_hook() -> None:
    """Waits for the off-hook signal and plays the dial tone."""
    print("En attente de décroché...")
    while GPIO.input(GPIO_PIN_OFF_HOOK):
        time.sleep(0.1)
    print("Décroché détecté.")
    play_sound(os.path.join(SOUND_DIR, "dial-tone.mp3"))
 
def read_dial() -> Optional[int]:
    """Reads the number of pulses from the dial and returns the corresponding digit."""
    dial_count = 0
    print("Lecture du cadran...")
    while GPIO.input(GPIO_PIN_OFF_HOOK) == GPIO.LOW:
        while GPIO.input(GPIO_PIN_DIAL) == GPIO.LOW:
            time.sleep(0.01)
        while GPIO.input(GPIO_PIN_DIAL) == GPIO.HIGH:
            time.sleep(0.01)
        dial_count += 1
        time.sleep(0.1)  # Debouncing
 
    if dial_count == 10:
        return 0
    return dial_count
 
def find_sound_file(dialed_number: str) -> Optional[str]:
    """Searches for a sound file corresponding to the dialed number."""
    print(f"Recherche de fichier pour le numéro composé : {dialed_number}")
    for file in os.listdir(SOUND_DIR):
        if file.startswith(dialed_number + "_"):
            print(f"Fichier trouvé : {file}")
            return file
    print("Aucun fichier trouvé pour le numéro composé.")
    return None
 
def main() -> None:
    """Main function to handle the phone interaction."""
    try:
        while True:
            wait_for_off_hook()
 
            dialed_number = ""
            composition_started = False
 
            while GPIO.input(GPIO_PIN_OFF_HOOK) == GPIO.LOW:
                digit = read_dial()
                if digit is not None:
                    if not composition_started:
                        stop_sound()  # Stop dial tone sound once dialing starts
                        composition_started = True
                    dialed_number += str(digit)
                    print(f"Numéro composé jusqu'à présent : {dialed_number}")
 
            if dialed_number:
                search_file = find_sound_file(dialed_number)
                if search_file:
                    play_sound(os.path.join(SOUND_DIR, "recherche.mp3"))
                    play_sound(os.path.join(SOUND_DIR, search_file))
                else:
                    play_sound(os.path.join(SOUND_DIR, "non2.mp3"))
 
            stop_sound()  # Stop sound if the phone is hung up
 
    except KeyboardInterrupt:
        print("Programme interrompu par l'utilisateur.")
    finally:
        stop_sound()
        GPIO.cleanup()
        print("Nettoyage des GPIO et arrêt du programme.")
 
if __name__ == "__main__":
    main()

Je commence tout juste en Python et avec le Raspberry il doit y avoir une logique qui me manque.
J’ai essayer avec un LLM de trouver le soucis mais sans succès.

Merci pour votre aide.

Bonjour,

C’est bien cette étape qui ne fonctionne pas ?

Donc qui correspond à ce code ?

def read_dial() -> Optional[int]:
    """Reads the number of pulses from the dial and returns the corresponding digit."""
    dial_count = 0
    print("Lecture du cadran...")
    while GPIO.input(GPIO_PIN_OFF_HOOK) == GPIO.LOW:
        while GPIO.input(GPIO_PIN_DIAL) == GPIO.LOW:
            time.sleep(0.01)
        while GPIO.input(GPIO_PIN_DIAL) == GPIO.HIGH:
            time.sleep(0.01)
        dial_count += 1
        time.sleep(0.1)  # Debouncing
 
    if dial_count == 10:
        return 0
    return dial_count

Donc la question est est-ce que le programme voit les impulsions ?
ça peut être un problème logiciel, mais aussi matériel.
Vous pouvez ajouter des print pour voir si le programme passe par certains endroits par exemple:

dial_count += 1
print ("Nombre dial_count = {dialcount} )
        time.sleep(0.1)  # Debouncing

A+

Merci @jelopo.
Le problème venait bien de la logique de boucle et de la lecture du cadran.
J’ai pu identifier cela avec les print.

Lorsque l’utilisateur compose le numéro 8 la roue du cadran en retournant à sa position initiale ouvre et ferme 8 fois le circuit du GPIO 17.

Voici le code fonctionnel si cela peux intéresser quelqu’un :


import RPi.GPIO as GPIO
import pygame
import time
import os

# Configuration des GPIO et des chemins de fichiers
GPIO_PINS = {"PIN_DECROCHE": 25, "PIN_CADRAN": 17}
SOUND_DIR = "/home/user/sounds"
SOUNDS = {
    "DIAL_TONE": os.path.join(SOUND_DIR, "dial_tone_20.mp3"),
    "RECHERCHE": os.path.join(SOUND_DIR, "recherche.mp3"),
    "NON_TROUVE": os.path.join(SOUND_DIR, "non3.mp3"),
    "NOTIF": os.path.join(SOUND_DIR, "notif2.mp3")
}

# Initialisation de pygame pour la lecture audio
pygame.mixer.init()

# Déclaration globale de la variable sound_stopped
sound_stopped = False

def setup_gpio():
    """Configuration des GPIO."""
    print("Configuration des GPIO...")
    GPIO.setmode(GPIO.BCM)
    for pin in GPIO_PINS.values():
        GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    print("GPIO configurés.")

def play_sound(file_path):
    """Joue un fichier audio."""
    global sound_stopped  # Déclaration de l'utilisation de la variable globale
    print(f"Lecture du son : {file_path}")
    sound_stopped = False  # Réinitialiser sound_stopped à chaque nouvelle lecture
    pygame.mixer.music.load(file_path)
    pygame.mixer.music.play()

def wait_for_sound_to_finish_or_raccroche():
    """Attend la fin du son ou l'action de raccrocher."""
    while pygame.mixer.music.get_busy():
        if GPIO.input(GPIO_PINS["PIN_DECROCHE"]) == GPIO.HIGH:
            print("Le combiné a été raccroché pendant la lecture.")
            pygame.mixer.music.stop()
            return False
        time.sleep(0.1)
    return True

def stop_sound():
    """Arrête la lecture audio."""
    print("Arrêt de la lecture audio.")
    pygame.mixer.music.stop()

def read_dial():
    """Lit les impulsions du cadran et retourne le chiffre correspondant."""
    global sound_stopped  # Déclaration de l'utilisation de la variable globale
    print("Lecture du cadran...")
    pulses = 0
    last_state = GPIO.input(GPIO_PINS["PIN_CADRAN"])
    last_pulse_time = None
    start_time = time.time()
    
    # Temps pour détection d'impulsion stable (debouncing)
    debounce_time = 0.005  # 5ms
    pulse_timeout = 0.2  # 200ms pour considérer qu'il s'agit du même chiffre

    while True:
        if GPIO.input(GPIO_PINS["PIN_DECROCHE"]) == GPIO.HIGH:
            print("Le combiné a été raccroché.")
            stop_sound()
            return None

        current_state = GPIO.input(GPIO_PINS["PIN_CADRAN"])
        if current_state != last_state:
            time.sleep(debounce_time)  # Attendre pour stabiliser la lecture
            current_state = GPIO.input(GPIO_PINS["PIN_CADRAN"])  # Relecture pour confirmation
            if current_state != last_state:
                if not sound_stopped:
                    stop_sound()
                    sound_stopped = True
                if current_state == GPIO.HIGH:
                    current_time = time.time()
                    if last_pulse_time is None or (current_time - last_pulse_time) >= pulse_timeout:
                        if pulses > 0:
                            break  # Le chiffre est terminé
                        pulses = 1  # Nouvelle série d'impulsions
                    else:
                        pulses += 1
                    last_pulse_time = current_time
                last_state = current_state
        time.sleep(0.001)

    if pulses == 0:
        print("Aucune impulsion détectée.")
        return None

    pulses_mod = pulses % 10 if pulses > 0 else 10
    print(f"Nombre d'impulsions lues : {pulses}, chiffre obtenu : {pulses_mod}")
    return pulses_mod

def get_dialed_number():
    """Retourne le numéro composé par l'utilisateur."""
    print("Obtention du numéro composé...")
    number = ""
    digit_timeout_long = 3.0  # Temps d'attente (en secondes) après chaque chiffre pour vérifier si un autre chiffre est composé
    last_digit_time = None

    while True:
        if GPIO.input(GPIO_PINS["PIN_DECROCHE"]) == GPIO.HIGH:
            print("Le combiné a été raccroché.")
            stop_sound()
            return None

        digit = read_dial()

        if digit is not None:
            digit = digit if digit != 10 else 0
            number += str(digit)
            last_digit_time = time.time()
            print(f"Numéro actuel : {number}")

        # Si plus de 3 secondes se sont écoulées sans nouvelle impulsion, le numéro est terminé
        if last_digit_time and (time.time() - last_digit_time >= digit_timeout_long):
            print(f"Numéro final composé : {number}")
            return number

        time.sleep(0.01)

def find_audio_file(number):
    """Recherche un fichier audio correspondant au numéro composé."""
    print(f"Recherche du fichier audio pour le numéro : {number}")
    for file in os.listdir(SOUND_DIR):
        if file.startswith(f"{number}_") and file.endswith(".mp3"):
            file_path = os.path.join(SOUND_DIR, file)
            print(f"Fichier audio trouvé : {file_path}")
            return file_path
    print("Aucun fichier audio trouvé.")
    return None

def main():
    """Boucle principale du programme."""
    setup_gpio()

    try:
        while True:
            if GPIO.input(GPIO_PINS["PIN_DECROCHE"]) == GPIO.LOW:
                print("Le combiné a été décroché.")
                play_sound(SOUNDS["DIAL_TONE"])
                
                dialed_number = get_dialed_number()
                
                if dialed_number:
                    print(f"Numéro composé : {dialed_number}")
                    audio_file = find_audio_file(dialed_number)
                    if audio_file and wait_for_sound_to_finish_or_raccroche():
                        play_sound(audio_file)
                        wait_for_sound_to_finish_or_raccroche()
                    else:
                        play_sound(SOUNDS["NON_TROUVE"])
                        wait_for_sound_to_finish_or_raccroche()

                    while GPIO.input(GPIO_PINS["PIN_DECROCHE"]) == GPIO.LOW:
                        time.sleep(0.1)
                
                stop_sound()

            time.sleep(0.1)
    
    except KeyboardInterrupt:
        print("Programme arrêté par l'utilisateur.")
    finally:
        print("Nettoyage des GPIO et arrêt de pygame.")
        GPIO.cleanup()
        pygame.mixer.quit()

if __name__ == "__main__":
    main()

Mon problème principal est résolu.
Axe d’amélioration de ce code :
Une fois le premier chiffre reçu, le son dial_tone est arrêté. Cela entraine un petit délai qui fait que si l’utilisateur compose rapidement le numéro suivant, il est mal interprété.
Pistes :
1- Tester avec mpg321 comme lecteur audio au lieu de pygame.
2- Baisser le son à 0 au lieu de l’arrêter
Le raspberry pi zero W est très peu puissant, si vous avez des astuces sur son optimisation je suis preneur.
Merci