Python execution de commandes sur une IHM depuis un bouton en GPIO

a marqué ce sujet comme résolu.

Bonjour, je souhaite réaliser une machine a photo en python… j’aimerai pouvoir prendre une photo à l’aide d’un bouton poussoir relié par GPIO.

  • Mon programme sans le bouton fonctionne, l’application s’ouvre et je peux prendre des photos avec les boutons de l’IHM.
  • Mon programme avec le bouton fonctionne aussi, je peux prendre des photos en appuyant sur le BP.

MAIS, les deux ensembles ne fonctionnent pas, lorsque mon code pour le BP est actif, cela supprime mon IHM (ou en tout cas je ne la vois pas)…

Savez-vous comment faire pour régler ce problème afin que je puisse piloter mon IHM avec mes BP en GPIO ? ^^

mon code (le morceau de programme pour le BP se trouve l.140):

from tkinter import *
from tkinter import font
from picamera import PiCamera
import time
from time import sleep
from PIL import Image, ImageTk
import os
import RPi.GPIO as GPIO
#####from pynput import keyboard

""" +VARIABLES+ """
camera = PiCamera()
path = '/home/pi/Desktop/Python/Projet/Photos/Photo' #chemin d'accès photos (fait parti du nom) //à modifier si chemin différent
count = 2 #compteur qui permet de limiter le temps de prise de la photo en secondes
camera.resolution = (1920,1080) #personnalisation de la camera (FULL HD)
camera.framerate = 60 #personnalisation de la camera (60fps)
pinBtn = 2 # Definition des pins bouton
pinLedV = 16 # Definition des pins led
pinLedB = 21 # Definition des pins led
channel = 2
""" -VARIABLES- """

""" +GPIO+ """
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
# Definition des pins en entree / sortie
GPIO.setup(pinBtn, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
GPIO.setup(pinLedV, GPIO.OUT)
GPIO.setup(pinLedB, GPIO.OUT)
GPIO.setup(channel, GPIO.IN)
""" -GPIO- """

""" +FONCTION+ """
###fonction horloge
def digital_clock(): 
    time_live = time.strftime("%H:%M:%S")
    horloge.config(text=time_live)
    horloge.after(500, digital_clock) #actualisation toutes les 500ms

###fonction camera
def take_a_photo():
    dateiso = time.strftime("%Y-%m-%d_%H-%M-%S") #date/heure (locale) de la photo
    GPIO.output(21,True)
    camera.start_preview(fullscreen = False, window = (0,196,1280,720)) #permet de placer manuellement la frame du flux vidéo
    countdown(count) #appel de la fonction countdown
    camera.capture(path+'_'+dateiso+'.jpeg')
    photo_name = (path+'_'+dateiso+'.jpeg')     
    photo_button.grid_forget()
    maphoto = Image.open(path+'_'+dateiso+'.jpeg')
    maphoto = maphoto.resize((1280,720))
    photo = ImageTk.PhotoImage(maphoto)
    photo_label.config(image = photo)
    photo_label.image = photo
    camera.stop_preview()
    GPIO.output(21,False)
    print (photo_name)
    comment_label.config(text='Souhaitez-vous imprimer cette photo ?')
    print_a_photo() #appel de la fonction print_a_photo
    
###fonction décompte
def countdown(count):
    while count:
        mins, secs = divmod(count, 60)
        timer = '{:d}'.format(secs)
        print(timer, end="\r")
        time.sleep(1)
        count -= 1
    
def print_a_photo():
    print_frame.grid(row=2, column=0)
    YES_button.grid(row=0, column=0, padx=20)
    NO_button.grid(row=0, column=1, padx=20)
    
def back_to_photo():
    print_frame.grid_forget()
    YES_button.grid_forget()
    NO_button.grid_forget()
    photo_button.grid(row=2, column=0)
    
def enable_flash_light():
    GPIO.output(16,True)
    
def disable_flash_light():
    GPIO.output(16,False)
    
def my_callback(channel):
    print("> FLUX VIDEO OUVERT")
    dateiso = time.strftime("%Y-%m-%d_%H-%M-%S") #date/heure (locale) de la photo
    GPIO.output(21,True)
    camera.start_preview(fullscreen = False, window = (0,196,1280,720)) #permet de placer manuellement la frame du flux vidéo
    countdown(count) #appel de la fonction countdown
    camera.capture(path+'_'+dateiso+'.jpeg')
    photo_name = (path+'_'+dateiso+'.jpeg')     
    camera.stop_preview()
    GPIO.output(21,False)
    print (photo_name)
    
""" -FONCTION- """

""" +CREATION+ """
mainWindow = Tk() #creation mainWindow
title_frame = Frame(mainWindow, width=350, height=200, bg='black') #creation frame pour contenir le titre
frame = Frame(mainWindow, width=550, height=400, bg='black') #creation frame pour contenir le reste
print_frame = Frame(frame, bg='black') #creation frame pour boutons d'impression
flash_frame = Frame(mainWindow, bg='black')#creation frame pour activer/desactiver flash light
""" -CREATION- """

       
###personnalisation de la fenetre
mainWindow.title("SelfIN")
mainWindow.config(background='black')
mainWindow.attributes('-fullscreen', True) #application en plein écranS
mainWindow.bind('<F>',lambda e: mainWindow.destroy()) #pour enlever le plein écran et quitter

###ajout texte
Self = Label(title_frame, text=("Self"), font=("koliko-Bold.ttf", 60), bg='black',fg='white')
IN = Label(title_frame, text=("IN"), font=("koliko-Bold.ttf", 60), bg='black',fg='gold')

###ajout commentaires
comment_label = Label(frame, font=("koliko-Bold.ttf", 20), bg='black', fg='white')

###ajout bouton photo
photo_button = Button(frame, text="Prendre une photo", font=("koliko-Bold.ttf", 20), bg='gold', fg='black', command=take_a_photo)
YES_button = Button(print_frame, text="Oui", font=("koliko-Bold.ttf", 20), bg='green', fg='white')
NO_button = Button(print_frame, text="Non", font=("koliko-Bold.ttf", 20), bg='red', fg='white', command=back_to_photo)
enable_flash_button = Button(flash_frame, text="EF", font=("koliko-Bold.ttf", 8), bg='grey', fg='black', command=enable_flash_light)
disable_flash_button = Button(flash_frame, text="DF", font=("koliko-Bold.ttf", 8), bg='grey', fg='black', command=disable_flash_light)

###ajout photo
maphoto = Image.open("Images/base_background.png") #Chargement d'une image à partir de PIL
maphoto = maphoto.resize((1280,720))
photo = ImageTk.PhotoImage(maphoto) #Création d'une image compatible Tkinter
photo_label = Label(frame, image=photo, width = 1280, height = 720, borderwidth=0)
photo_label.image = photo #Maintient en vie de photo dans un objet non détruit

###ajout horloge
horloge = Label(mainWindow, font="koliko-Bold.ttf", bg='black', fg='white')

#ici on ajoute une tempo de 500ms ms pour eviter l'effet rebond
GPIO.add_event_detect(channel, GPIO.FALLING, callback=my_callback, bouncetime=500) 
# Boucle infinie
while True:
    etat = GPIO.input(pinBtn)
    
    # etat==0 => bouton appuye => LED allumee
    if (etat == 0) :
        #print("Appui detecte")
        GPIO.output(16,True)
    
    else :
        GPIO.output(16, False)
    
    # Temps de repos pour eviter la surchauffe du processeur
    #time.sleep(0.3)

""" +PLACEMENT+ """
title_frame.pack(expand=True)
Self.grid(row=0, column=0)
IN.grid(row=0, column=1)
horloge.pack()
frame.pack(expand=True)
photo_label.grid(row=0, column=0)
comment_label.grid(row=1, column=0)
photo_button.grid(row=2, column=0)
""""""
flash_frame.pack(expand=True)
enable_flash_button.grid(row=0, column=0)
disable_flash_button.grid(row=0, column=1)
""""""
print_frame.grid_forget()
YES_button.grid_forget()
NO_button.grid_forget()
""" -PLACEMENT- """

""" -FONCTION PRINCIPALE- """
digital_clock() #horloge
mainWindow.mainloop() #fonction principale application
""" -FONCTION PRINCIPALE- """
+0 -0

Salut,

Pour gérer les interactions, les programmes utilisent des "boucles d’évènements" pour faire l’acquisition des inputs et appeler les callbacks associées. En mixant tes programmes, tu n’as pas fusionné ces boucles mais tu les as mis côte à côte. l.142 tu as ta boucle infinie qui bloque ton programme pour scruter le bouton, et donc tu n’atteint pas la boucle de l’IHM l.177 qui affiche les widgets et scrute les interactions souris/clavier.

EDIT: donc pour résoudre ça, au lieu de scruter ton bouton dans une boucle infinie, il faudrait que tu scrute ça sur un timer par exemple.

+3 -0

EDIT: donc pour résoudre ça, au lieu de scruter ton bouton dans une boucle infinie, il faudrait que tu scrute ça sur un timer par exemple.

romantik

Merci pour ta réponse !

Pourrais-tu me montrer un exemple de code pour ce timer ? J’ai l’impression de pas trouver ce qu’il me faut sur le net… Sur le principe j’ai compris que les boucles ne peuvent pas fonctionner ensemble mais je ne vois pas comment faire.

+0 -0

pas vraiment, je connais mal python, mais tu utilises déjà un timer dans ton digital_clock() avec horloge.after(). Tu peux gérer de manière assez similaire.
ça va donner quelque chose un peu comme ça si je touche au minimum à ton code

def physical_button_loop(attachedWindow):
  etat = GPIO.input(pinBtn)  
  # etat==0 => bouton appuye => LED allumee
  if (etat == 0) :
    GPIO.output(16,True)
  else :
    GPIO.output(16, False)
  # Scruter le bouton toutes les 20ms
  attachedWindow.after(20, lambda : physical_button_loop(attachedWindow))
  
# [...]

""" -FONCTION PRINCIPALE- """
digital_clock() #horloge
physical_button_loop(mainWindow)
mainWindow.mainloop() #fonction principale application
""" -FONCTION PRINCIPALE- """

mais je pense que tu devrais structurer tout ça et faire des classes, dont la classe MainWindow qui gèrera ce timer en interne (ça éviterait de donner le widget en argument)

Par ailleurs, je vois des sleep qui trainent dans ton code. ça va bloquer les interactions utilisateurs. à partir du moment où tu fais de l’IHM, que tu fonctionne par évènement, tu ne peux pas te permettre de mettre ton programme en pause car il doit être disponible pour l’utilisateur.
Aussi, si tu as de gros traitements, tu peux les déléguer à des coroutines pour libérer ton IHM.

+0 -0

Merci pour ta réponse, j’ai modifié un peu le programme que tu m’as donné et ça a l’air de fonctionner pour le moment. Pour le sleep qu’il y a, c’est pas super grave car cela correspond à un temps d’attente (prise de la photo) mais tu as raison si j’arrive à faire sans ce sera que meilleur. Pour les classes aussi tu as raison mais je me suis jeté dans le code directement en apprenant sur le tas car je ne savais pas coder en Python… Toujours pareil, si j’arrive à modifier mon code pour le rendre plus propre je le ferai ! ^^

+1 -0

Plutôt qu’une boule infinie pour lire l’état de ton bouton, je te conseille de passer par un système d’interruption : https://roboticsbackend.com/raspberry-pi-gpio-interrupts-tutorial/

Comme cela se passe dans un autre thread (i.e. en "parallèle" du programme principal), ta boucle d’interface ne sera pas bloquée.

En gros tu aurais :

# Définition de l'interface et des évènements
# ...

GPIO.setmode(GPIO.BCM)
GPIO.setup(BUTTON_GPIO, GPIO.IN, pull_up_down=GPIO.PUD_UP)

GPIO.add_event_detect(
    BUTTON_GPIO,
    GPIO.FALLING, 
    callback=take_photo,
    bouncetime=50
)

mainWindow.mainloop()

Ta boucle infinie ligne 142 fait doublon avec l’interruption qui, en gros, exécute la même chose dans un second thread donc sans bloquer le programme principal.

+2 -0

Bonjour Vayel,

Merci pour ta réponse ! J’ai essayé de faire cette technique mais lorsque je presse le bouton, mon action s’exécute 2 fois. J’ai pensé que c’était dû au bouncetime mais même en le modifiant ça ne change pas. Aussi j’ai la même chose à faire avec un deuxième bouton (une deuxième action donc) mais elle n’est pas prise en compte pourtant j’ai repris la même syntaxe…

+0 -0

As-tu toujours ta boucle infinie ligne 142 ? Si oui, c’est normal que le code s’exécute deux fois : une fois dans la boucle, une fois via l’interruption.

Pour ton deuxième bouton, as-tu changer le pin dans l’appel à add_event_detect() (ligne 140) ?

+0 -0

J’ai bien enlevé la boucle mais en fait j’ai l’impression que c’est que l’action s’exécute et ne s’arrête pas, j’ai une led qui fonctionne en même temps que ma commande et cette led reste allumée même lorsque je ferme le programme, je sais qu’il existe le cleanup (que j’ai pas mis) mais je pense que le problème ne vient pas de là à la base.

Pour l’autre bouton j’ai déclaré le bouton sur "pinBtn2" pour l’instant et il est donc sur une autre pin. Mais par contre j’ai refais la même syntaxe, ce qui veut dire que j’ai 2 lignes add_event_detect() avec chacune ses gpio et ses commandes.

+0 -0

Est-ce que tu as aussi supprimé la l.140 ? car tu avais déjà configuré une interruption en fait.

Je crois que le plus simple est que tu repostes le nouveau code.

EDIT : En fait je viens de comprendre que c’est le deuxième bouton avec le commentaire de vayel, mb

+0 -0

Est-ce que tu as aussi supprimé la l.140 ? car tu avais déjà configuré une interruption en fait.

Je crois que le plus simple est que tu repostes le nouveau code.

EDIT : En fait je viens de comprendre que c’est le deuxième bouton avec le commentaire de vayel, mb

romantik

Oui j’ai bien supprimé ma boucle mais c’est au niveau des events je pense… Je n’ai pas accès à mon code tout de suite, je le posterai dès que possible. :)

+0 -0

Voici le code que j’ai actuellement :

from tkinter import *
from tkinter import font
from picamera import PiCamera
import time
from time import sleep
from PIL import Image, ImageTk
import os
import RPi.GPIO as GPIO
#####from pynput import keyboard

""" +VARIABLES+ """
camera = PiCamera()
path = '/home/pi/Desktop/Python/Projet/Photos/Photo' #chemin d'accès photos (fait parti du nom) //à modifier si chemin différent
count = 2 #compteur qui permet de limiter le temps de prise de la photo en secondes
camera.resolution = (1920,1080) #personnalisation de la camera (FULL HD)
camera.framerate = 60 #personnalisation de la camera (60fps)
pinBtnPhoto = 2   # Definition des pins bouton
pinBtnFlash = 17 # Definition des pins bouton
pinBtnNoFlash = 27 # Definition des pins bouton
pinLedG = 16 # Definition des pins led
pinLedB = 21 # Definition des pins led
""" -VARIABLES- """

""" +GPIO+ """
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
# Definition des pins en entree / sortie
GPIO.setup(pinBtnPhoto, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
GPIO.setup(pinBtnFlash, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
GPIO.setup(pinBtnNoFlash, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
GPIO.setup(pinLedG, GPIO.OUT)
GPIO.setup(pinLedB, GPIO.OUT)
""" -GPIO- """

""" +FONCTION+ """
###fonction horloge
def digital_clock(): 
    time_live = time.strftime("%H:%M:%S")
    horloge.config(text=time_live)
    horloge.after(500, digital_clock) #actualisation toutes les 500ms

###fonction camera
def take_a_photo(pinBtnPhoto):
    dateiso = time.strftime("%Y-%m-%d_%H-%M-%S") #date/heure (locale) de la photo
    GPIO.output(21,True)
    camera.start_preview(fullscreen = False, window = (0,196,1280,720)) #permet de placer manuellement la frame du flux vidéo
    countdown(count) #appel de la fonction countdown
    camera.capture(path+'_'+dateiso+'.jpeg')
    photo_name = (path+'_'+dateiso+'.jpeg')     
    photo_button.grid_forget()
    maphoto = Image.open(path+'_'+dateiso+'.jpeg')
    maphoto = maphoto.resize((1280,720))
    photo = ImageTk.PhotoImage(maphoto)
    photo_label.config(image = photo)
    photo_label.image = photo
    camera.stop_preview()
    GPIO.output(16,False)
    GPIO.output(21,False)
    print (photo_name)
    comment_label.config(text='Souhaitez-vous imprimer cette photo ?')
    print_a_photo() #appel de la fonction print_a_photo
    
###fonction décompte
def countdown(count):
    while count:
        mins, secs = divmod(count, 60)
        timer = '{:d}'.format(secs)
        print(timer, end="\r")
        time.sleep(1)
        count -= 1
    
def print_a_photo():
    print_frame.grid(row=2, column=0)
    YES_button.grid(row=0, column=0, padx=20)
    NO_button.grid(row=0, column=1, padx=20)

def back_to_photo():
    print_frame.grid_forget()
    YES_button.grid_forget() 
    NO_button.grid_forget()
    photo_button.grid(row=2, column=0)
    
def enable_flash_light(pinBtnFlash):
    GPIO.output(16,True)
    
def disable_flash_light(pinBtnNoFlash):
    GPIO.output(16,False)
    
"""def physical_button_loop(attachedWindow):
    etat = GPIO.input(pinBtn)  
    # etat==0 => bouton appuye => LED allumee
    if (etat == 0) :
        print("Appui detecte sur le bouton 1")
        take_a_photo()
    # Scruter le bouton toutes les 20ms
    attachedWindow.after(20, lambda : physical_button_loop(attachedWindow))"""
  
"""def physical_button2_loop(attachedWindow):
    etatbp = GPIO.input(pinBtn2)  
    # etat==0 => bouton appuye => LED allumee
    if (etatbp == 0) :
        print("Appui detecte sur le bouton 2")
        enable_flash_light()
    else :
        print("Appui detecte sur le bouton 2")
        disable_flash_light()
    # Scruter le bouton toutes les 20ms
    attachedWindow.after(20, lambda : physical_button2_loop(attachedWindow))"""
    
""" -FONCTION- """

""" +CREATION+ """
mainWindow = Tk() #creation mainWindow
title_frame = Frame(mainWindow, width=350, height=200, bg='black') #creation frame pour contenir le titre
frame = Frame(mainWindow, width=550, height=400, bg='black') #creation frame pour contenir le reste
print_frame = Frame(frame, bg='black') #creation frame pour boutons d'impression
flash_frame = Frame(mainWindow, bg='black')#creation frame pour activer/desactiver flash light
""" -CREATION- """

       
###personnalisation de la fenetre
mainWindow.title("SelfIN")
mainWindow.config(background='black')
mainWindow.attributes('-fullscreen', True) #application en plein écranS
mainWindow.bind('<F>',lambda e: mainWindow.destroy()) #pour enlever le plein écran et quitter

###ajout texte
Self = Label(title_frame, text=("Self"), font=("koliko-Bold.ttf", 60), bg='black',fg='white')
IN = Label(title_frame, text=("IN"), font=("koliko-Bold.ttf", 60), bg='black',fg='gold')

###ajout commentaires
comment_label = Label(frame, font=("koliko-Bold.ttf", 20), bg='black', fg='white')

###ajout bouton photo
photo_button = Button(frame, text="Prendre une photo", font=("koliko-Bold.ttf", 20), bg='gold', fg='black', command=take_a_photo)
YES_button = Button(print_frame, text="Oui", font=("koliko-Bold.ttf", 20), bg='green', fg='white')
NO_button = Button(print_frame, text="Non", font=("koliko-Bold.ttf", 20), bg='red', fg='white', command=back_to_photo)
enable_flash_button = Button(flash_frame, text="EF", font=("koliko-Bold.ttf", 8), bg='grey', fg='black', command=enable_flash_light)
disable_flash_button = Button(flash_frame, text="DF", font=("koliko-Bold.ttf", 8), bg='grey', fg='black', command=disable_flash_light)

###ajout photo
maphoto = Image.open("Images/base_background.png") #Chargement d'une image à partir de PIL
maphoto = maphoto.resize((1280,720))
photo = ImageTk.PhotoImage(maphoto) #Création d'une image compatible Tkinter
photo_label = Label(frame, image=photo, width = 1280, height = 720, borderwidth=0)
photo_label.image = photo #Maintient en vie de photo dans un objet non détruit

###ajout horloge
horloge = Label(mainWindow, font="koliko-Bold.ttf", bg='black', fg='white')

""" +PLACEMENT+ """
title_frame.pack(expand=True)
Self.grid(row=0, column=0)
IN.grid(row=0, column=1)
horloge.pack()
frame.pack(expand=True)
photo_label.grid(row=0, column=0)
comment_label.grid(row=1, column=0)
photo_button.grid(row=2, column=0)
""""""
flash_frame.pack(expand=True)
enable_flash_button.grid(row=0, column=0)
disable_flash_button.grid(row=0, column=1)
""""""
print_frame.grid_forget()
YES_button.grid_forget()
NO_button.grid_forget()
""" -PLACEMENT- """

""" -FONCTION PRINCIPALE- """
digital_clock() #horloge
#physical_button_loop(mainWindow)
#physical_button2_loop(mainWindow)
GPIO.add_event_detect(pinBtnPhoto, GPIO.FALLING, callback=take_a_photo, bouncetime=1000)
GPIO.add_event_detect(pinBtnFlash, GPIO.FALLING, callback=enable_flash_light, bouncetime=500)
GPIO.add_event_detect(pinBtnNoFlash, GPIO.FALLING, callback=disable_flash_light, bouncetime=500)
mainWindow.mainloop() #fonction principale application
""" -FONCTION PRINCIPALE- """
+0 -0

Aucune de tes interruptions fonctionnent ? Si tu mets un print() dans ta fonction enable_flash_light(), rien ne s’affiche à l’appui sur le bouton du GPIO 17 ?

+0 -0

Aucune de tes interruptions fonctionnent ? Si tu mets un print() dans ta fonction enable_flash_light(), rien ne s’affiche à l’appui sur le bouton du GPIO 17 ?

Vayel

En fait j’ai un problème au niveau du cablage je pense, les boutons ne fonctionnent pas mais lorsque je manipule un peu les différents fils j’ai bien mon action qui s’effectue mais pas tout le temps. Je n’arrive pas à savoir quel est le problème…

Et j’ai toujours mon autre problème : la commande "take_a_photo" s’effectue 2 fois par moment et 1 seule fois à d’autres moments…

+0 -0

Tes GPIO sont configurées en pull down l.28–30 et tu t’attends à un front descendant l.174–176.
Tes boutons sont donc branché au +Vcc ? Est-ce qu’ils sont normalement fermé ou normalement ouvert ?

romantik

C’est bien là le problème, je m’en suis rendu compte rapidement quand j’ai vu la tête de mon cablâge… :-°

Avec les résistances et le +5V ça à l’air beaucoup mieux ! (malheureusement j’ai pas assez de résistances pour faire tous mes tests, je les reprendrai lundi)

+0 -0

Pourquoi tu as besoin d’une résistance ? La raspberry ne contient pas la résistance de pull down lorsque tu le configure ?

romantik

Je sais pas, ça fait redémarrer la raspberry quand j’appuie sur un bouton qui n’a pas de résistance (il est cablé sur GND/+5V/pinX).

+0 -0

J’ai peur de comprendre … ^^'
Je crois que tu as branché le bouton en court-circuit
Il faut que tu fasses soit +5V<->bouton<->GPIO configuré en pull-down, soit GND<->bouton<->GPIO configuré en pull-up.
Mais j’ai l’impression que tu manques de base en électronique et développement informatique. On dirait que tu assembles des bouts de code et de montage sans vraiment les comprendre (en partie mais pas complètement) car tu n’as pas les fondamentaux. Je te conseillerais tout de même de lire des cours avant de te lancer dedans.

+0 -0

Je ne m’attendais pas vraiment à cette réponse … Tu sembles avoir besoin de ces connaissances pour arriver à bout de ton projet, c’est étonnant que tu te lance dedans si tu n’aimes pas ça car si tu t’y prends seul, tu vas devoir apprendre.

Tu le relie déjà au ground au travers la RPi en configurant ta GPIO en pull-down, de manière similaire à ce qui est montré dans le tuto arduino

Schéma de câblage d’un bouton sur une entrée arduino configurée en pull-up
+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte