Domotique par mail

Récupérer le sujet

a marqué ce sujet comme résolu.

Bonjour à toutes et à tous,

Je voudrais contrôler ma raspberry pi à distance (dans le but de faire de la domotique), et cela sans serveur web.

J'ai donc eu l'idée de l'envoi de mail. Je lui ai créé une adresse gmail, et selon l'objet du mail, elle va activer/désactiver des relais reliés aux pins GPIO.

J'ai trouvé comment connecter ma RPi à sa boîte mail via la librairie "imaplib". Je sais comment compter le nombre de mails, le nombre de mails non lus, mais pas comment récupérer l'objet du mail (si vous savez comment faire cela me faciliterais bien les choses ^^). Je me suis dis que passer par un script php qui ferrais un "echo" avec le sujet du mail (que mon script supprimerais aussitôt après avoir reçu l'objet pour qu'il n'y ai qu'un seul mail) serait plus simple.

Voici donc mon idée résumée :

  1. Le script détecte l'arrivée d'un mail

  2. Il va voir sur la page où sera écrit le sujet (par exemple http://www.monsite.com/subject.php)

  3. Il récupère le sujet, traite les relais appropriés, et supprime le mail

Si quelqu'un sait comment récupérer le sujet d'un mail en python, ou récupérer ce qu'il y a écrit sur une page web, qu'il se manifeste ;)

Merci d"'avance, Roumil

+0 -0

Bon, ça m'à l'air d'être un projet 'drole'.

J'ai jamais utiliser imaplib, mais une recherche rapide et : imap lib IMAP example with Gmail

La deuxième option me semble un peu laborieuse. Mais tout à fait envisageable. Du coup il y a pleins de moyens pour récuperer une page. Le plus simple c'est d'utiliser wget/curl puis ensuite de traiter le tout …

+0 -0

Oui, je suis déjà tombé sur ce lien. Mais il ne montre pas comment "extraire" le sujet d'un mail (je pense que ce n'est pas possible avec cette librairie.

D'après mon expérience, plus un projet et laborieux (mais pas trop quand même ^^ ), plus j'en apprends (d'ou ma citation). Donc je vais prendre cette option :)

J'ai fais ce code pour l'instant (j'ai juste enlevé l'adresse et le mdp):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# -*- coding: utf-8 -*-
import RPi.GPIO as GPIO, imaplib, time, sys, urllib2


def start():

    USERNAME="adresse@gmail.com"
    PASSWORD="motdepasse"

    etat1 = False
    etat2 = False
    etat3 = False                          ##Variables
    rad=17
    lampe = 22
    musique = 27

    GPIO.setwarnings(False)
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(rad, GPIO.OUT)
    GPIO.setup(lampe, GPIO.OUT)            ##GPIO initialisation
    GPIO.setup(musique, GPIO.OUT)

    m = imaplib.IMAP4_SSL('imap.gmail.com')
    m.login(USERNAME, PASSWORD)
    m.list()
    m.select('inbox')
    while 1:
        type, data = m.search(None, "UNSEEN")
        if type == "NO":
                print "Disable to connect"
                exit()
        else:
            count = len(data[0].split())
            if count > 0:
                subject = getSubject()
                if subject in ("radiateur", "Radiateur", "rad", "Rad"): ##Et on le traite
                    etat1 = not etat1
                    GPIO.output(rad, etat1)
                    m.store(1, '+FLAGS', r'\Deleted')

                elif subject in ("lampe", "Lampe"):
                    etat2 = not etat2
                    GPIO.output(lampe, etat2)
                    m.store(1, '+FLAGS', r'\Deleted')

                elif subject in ("Musique", "musqiue"):
                    etat3 = not etat3
                    GPIO.output(musique, etat3)
                    m.store(1, '+FLAGS', r'\Deleted')

                else:
                    print 'Invalid subject'
            else:
                print 'No mails'
    time.sleep(5)

def getSubject():
    return "Rad"

if __name__ == '__main__':
    start()

Mais je ne sais pas quoi mettre dans la fonction getSubject()… J'ai cherché un peu wget et curl, mais rien qui marche chez moi :'( Et quand bien même je saurais, je ne sais pas non plus comment récupérer le sujet d'un mail en PHP. Je sais envoyer des mails, mais c'est tout.

Tu ne saurais pas comment faire toi ? ;)

+0 -0

J'étais aussi tombé sur cette page, mais je n'avais pas vu qu'il expliquait comment il avait fait pour trouver l'expediteur et le sujet du mail ;)

Voilà donc mon code fini (et qui marche !)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# -*- coding: utf-8 -*-
import RPi.GPIO as GPIO, imaplib, time, sys, urllib2, random, email


def start():

    USERNAME="email@gmail.com"
    PASSWORD="password"

    etat1 = False
    etat2 = False
    etat3 = False                          ##Variables
    rad=17
    lampe = 22
    musique = 27

    GPIO.setwarnings(False)
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(rad, GPIO.OUT)
    GPIO.setup(lampe, GPIO.OUT)            ##GPIO initialisation
    GPIO.setup(musique, GPIO.OUT)


    while 1:
        time.sleep(1)
        m = imaplib.IMAP4_SSL('imap.gmail.com') ##MaJ des mails
        m.login(USERNAME, PASSWORD)
        m.list()
        m.select('inbox')
        type, data = m.search(None, "UNSEEN")
        if type == "NO":
                print "Disable to connect"
                exit()
        else:
            count = len(data[0].split())
            if count > 0:               ##S'il y a des mails non lus
                type, data = m.fetch(1, '(RFC822)')
                for reponse_part in data:
                    if isinstance(reponse_part, tuple):
                        msg = email.message_from_string(reponse_part[1])
                        subject = msg['subject']                        ##On prend l'expediteur et le sujet du mail non lu
                        sender = msg['from']
                i = 0
                for char in sender: ##On ne laisse que le mail
                    if char == '<':
                        sender = sender[i+1:len(sender)-1]
                    else:
                        i+=1

                print subject
                print sender
                if subject in ("radiateur", "Radiateur", "rad", "Rad") and sender == "axelrousselot@outlook.fr": ##Et on les traite
                    etat1 = not etat1
                    GPIO.output(rad, etat1)
                    m.store(1, '+FLAGS', r'\Deleted')
                    time.sleep(2)

                elif subject in ("lampe", "Lampe") and sender == "axelrousselot@outlook.fr":
                    etat2 = not etat2
                    GPIO.output(lampe, etat2)
                    m.store(1, '+FLAGS', r'\Deleted')
                    time.sleep(2)

                elif subject in ("Musique", "musqiue") and sender == "axelrousselot@outlook.fr":
                    etat3 = not etat3
                    GPIO.output(musique, etat3)
                    m.store(1, '+FLAGS', r'\Deleted')
                    time.sleep(2)

                else:
                    print 'Invalid subject or sender'
                    m.store(1, '+FLAGS', r'\Deleted')
            else:
                print 'No mails'

if __name__ == '__main__':
    start()

Oui, je sais, il était inutile de créer une fonction start(), mais au début je voulais séparer mon code en plusieurs fonctions (getHeaders, init, parseHeaders…) mais je manque d'entraînement dans ce domaine, je n'arrivais pas à passer les variable entre les fonctions.

Merci de votre aide ! :D

Croyez-vous qu'il serait pertinent que je rédige un mini-tuto sur ce projet ?

+0 -0

Je pense que c'est la ligne 52 à 68 qui reste à améliorer, on peut créer un dictionnaire dont les clés sont des tuples de mots clé, dont les valeurs du dictionnaire représentent les différents états.

Ensuite rad, lampe et musique sont à mon sens considérés comme des constantes, et donc je les mettrais entièrement en majuscule, RAD, LAMPE et MUSIQUE…

Ligne 64, ça ne risque pas de détecter musique, à toi de voir pourquoi…

En effet start est une fonction inutile, mais bon, c'est lié au fait de certaines incompréhension, je pense que tu l'amélioreras par la suite quand tu auras plus de confiance dans ce que tu fais.

Ok ok … En fait, si on cherche un_champ, il suffit de faire msg['un_champ']. C'était pas super claire, mais on le comprend si on a déjà regarder un email 'brut'.

Sinon, pour un tuto, je ne pense pas que tu ai vraiment le niveau. J'ai pas un super niveau en Python, mais je vois tout de suite que tu peux faire plus beau. Mais c'est sympa de proposer.

+0 -0

Encore que… Le fait d'avoir le support peut être intéressant pour créer un tutoriel en duo avec une personne compétente en python. Ça fait double bénéfices, progression de Roumil avec ce langage, création d'un tutoriel supplémentaire pour le forum.

Bref tout est possible, aide toi, le ciel t'aideras… Sachant que python, n'est qu'un langage pour parler au raspberry parmi tant d'autres, le principal objectif c'est pas le langage, mais bien discuter avec son rapsberry.

Merci bien Eskimon ! Je ferrai attendtion, promis ;)

Et c'est d'un dico comme ça dont tu parle ?

1
DICO = {("Radiateur", "Rad", "rad", "radiateur"):False, ("Lampe", "lampe"):False, ("Musique", "musique"):False}

Si oui, je l'exploite comment ? Car pour l'instant, j'ai ça (j'ai essayé de m'organiser un peu ^^ ):

domolib.py :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# -*- coding: utf-8 -*-
import RPi.GPIO as GPIO, imaplib, time, sys, random, email

RAD = 17
LAMPE = 27
MUSIQUE = 22

etat1 = False
etat2 = False
etat3 = False

def init():
    GPIO.setwarnings(False)
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(RAD, GPIO.OUT)
    GPIO.setup(LAMPE, GPIO.OUT)            ##GPIO initialisation
    GPIO.setup(MUSIQUE, GPIO.OUT)

def create_mail(username, password):
    m = imaplib.IMAP4_SSL('imap.gmail.com')
    m.login(username, password)
    return m

def is_new_mails(m):
    m.list()
    m.select('inbox')
    type, data = m.search(None, "UNSEEN")
    if type == "NO":
        print "Disable to connect"
        exit()
    else:
        count = len(data[0].split())
        if count > 0:
            return True
        else:
            return False

def get_data(m):
    type, data = m.fetch(1, '(RFC822)')
    for reponse_part in data:
        if isinstance(reponse_part, tuple):
            msg = email.message_from_string(reponse_part[1])
            subject = msg['subject']
            sender = msg['from']
    i = 0
    for char in sender:
        if char == '<':
            sender = sender[i+1: len(sender)-1]
        else:
            i+=1
    i = 0
    for char in subject:
         if char == ' ':
              subject = subject[0: i]
         else:
               i+=1
    subsend = [subject, sender]
    return subsend

def data_traitement(true_sender, data_mail, dico):
    if data_mail[1] == true_sender:
        if data_mail[0] == "Radiateur":
            global etat1
            etat1 = not etat1
            GPIO.output(RAD, etat1)
        elif data_mail[0] == "Lampe":
            global etat2
            etat2 = not etat2
            GPIO.output(LAMPE, etat2)
        elif data_mail[0] == "Musique":
            global etat3
            etat3 = not etat3
            GPIO.output(MUSIQUE, etat3)
    else:
        print "Bad sender"

def delete_mails(m):
    m.store(1, '+FLAGS', r'\Deleted')

mail_domo.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- coding: utf-8 -*-
from domolib import *
import RPi.GPIO as GPIO

USERNAME = "mail@gmail.com"
PASSWORD = "motdepasse"
TRUE_SENDER = "votremail@outlook.fr"
DELAY = 1
DICO = {("Radiateur", "Rad", "rad", "radiateur"):False, ("Lampe", "lampe"):False, ("Musique", "musique"):False}

if __name__ == '__main__':
    init()
    mail = create_mail(USERNAME, PASSWORD)
    while 1:
        if is_new_mails(mail):
            data_mail = get_data(mail)
            data_traitement(TRUE_SENDER, data_mail, DICO) 
            delete_mails(mail)
        else:
            print "Any new mail"
        time.sleep(DELAY)
+0 -0

Il suffirait de faire cela sur un simple exemple

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
DICO = {
        ("Radiateur", "Rad", "rad", "radiateur"):False, 
        ("Lampe", "lampe"):False, 
        ("Musique", "musique"):False
       }

for key in DICO:
    if 'Lampe' in key:
        DICO[key] = True
        break

print DICO

# {
#   ('Lampe', 'lampe'): True, ('Musique', 'musique'): False,
#   ('Radiateur', 'Rad', 'rad', 'radiateur'): False
# }
+0 -0

Bon, du coup, j'en suis là:

domolib.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# -*- coding: utf-8 -*-
import RPi.GPIO as GPIO, imaplib, time, sys, random, email, pickle

RAD = 17
LAMPE = 27
MUSIQUE = 22

def init(dico):
    GPIO.setwarnings(False)
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(RAD, GPIO.OUT)
    GPIO.setup(LAMPE, GPIO.OUT)            ##GPIO initialisation
    GPIO.setup(MUSIQUE, GPIO.OUT)
    with open("/root/domotique_mail/save/save.txt", "rb") as fichier:
        depickler = pickle.Unpickler(fichier)
        dico = depickler.load()
    fichier.close()
    

def create_mail(username, password):
    m = imaplib.IMAP4_SSL('imap.gmail.com')
    m.login(username, password)
    return m

def is_new_mails(m):
    m.list()
    m.select('inbox')
    type, data = m.search(None, "UNSEEN")
    if type == "NO":
        print "Disable to connect"
        exit()
    else:
        count = len(data[0].split())
        if count > 0:
            return True
        else:
            return False

def get_data(m):
    type, data = m.fetch(1, '(RFC822)')
    for reponse_part in data:
        if isinstance(reponse_part, tuple):
            msg = email.message_from_string(reponse_part[1])
            subject = msg['subject']
            sender = msg['from']
    i = 0
    for char in sender:
        if char == '<':
            sender = sender[i+1: len(sender)-1]
        else:
            i+=1
    i = 0
    for char in subject:
         if char == ' ':
              subject = subject[0: i]
         else:
               i+=1
    subsend = [subject, sender]
    return subsend

def data_traitement(true_sender, data_mail, dico):
    if data_mail[1] == true_sender:
        for key in dico:
            if data_mail[0] in key:
                dico[key] = not dico[key]
    else:
        print "Bad sender"

def print_data(dico):
    GPIO.output(RAD, dico[("Radiateur", "Rad", "rad", "radiateur")])
    GPIO.output(LAMPE, dico[("Lampe", "lampe")])
    GPIO.output(MUSIQUE, dico[("Musique", "musique")])
    with open("/root/domotique_mail/save/save.txt", "wb") as fichier: #On enregistre notre dico dans un fichier
        pickler = pickle.Pickler(fichier)
        pickler.dump(dico)
    fichier.close()

def delete_mails(m):
    m.store(1, '+FLAGS', r'\Deleted')

mail_domo.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- coding: utf-8 -*-
from domolib import *

USERNAME = "mail@gmail.com"
PASSWORD = "motdepasse"
TRUE_SENDER = "votremail@outlook.fr"
DELAY = 1
DICO = {("Radiateur", "Rad", "rad", "radiateur"):False, ("Lampe", "lampe"):False, ("Musique", "musique"):False}

if __name__ == '__main__':
    init(DICO)
    mail = create_mail(USERNAME, PASSWORD)
    while 1:
        if is_new_mails(mail):
            data_mail = get_data(mail)
            data_traitement(TRUE_SENDER, data_mail, DICO)
            print_data(DICO)
            delete_mails(mail)
        else:
            print "Any new mail"
        time.sleep(DELAY)

Tout d'abord, si les codes ne sont pas compréhensibles, dites le moi et je les commenterai ;)

Et ensuite, j'ai un petit problème avec l'enregistrement de mon dictionnaire : il me met cette erreur :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Traceback (most recent call last):
  File "/root/domotique_mail/mail_domo.py", line 11, in <module>
    init(DICO)
  File "/root/domotique_mail/domolib.py", line 16, in init
    dico = depickler.load()
  File "/usr/lib/python2.7/pickle.py", line 858, in load
    dispatch[key](self)
  File "/usr/lib/python2.7/pickle.py", line 1187, in load_appends
    mark = self.marker()
  File "/usr/lib/python2.7/pickle.py", line 874, in marker
    while stack[k] is not mark: k = k-1
IndexError: list index out of range

D'ou cela peut venir ? (désolé pour l'accent sur le "u", mais ma RPi ne veut pas que je le mette ^^ )

+0 -0

Croyez-vous qu'il serait pertinent que je rédige un mini-tuto sur ce projet ?

Roumil

Le fait d'avoir le support peut être intéressant pour créer un tutoriel en duo avec une personne compétente en python. Ça fait double bénéfices, progression de Roumil avec ce langage, création d'un tutoriel supplémentaire pour le forum.

fred1599

Comme fred le dit, et comme ça à déjà été discuté autre part sur le forum (je ne trouve plus le sujet :x ), c’est une bonne idée. Je te propose mon aide si ça te tente toujours (tu n’as qu’a me MP).

+0 -0

Je suis pas fan de cette utilisation de la variable DICO. L'intérêt des dictionnaires est d'associer une valeur à une ou plusieurs clés pour les requêter en temps constant. Pourquoi regrouper les clés dans des tuples ? On perd tous les avantages d'une table associative en se réduisant à une requête linéaire, comme sur une liste de paires.

+0 -0

@fred1599 : C'est bon, j'ai résolu le problème, merci :)

@simbilou : J'accepte volontiers ton aide :) Mais je te MP dès que j'ai le temps, pour l'instant, c'est un peu tendu :/

@nochar Alors comment me conseilles-tu de faire? Cette manière de faire ne ma paraissait pas "naturelle", mais la seule de faire… Ou alors ne pas utiliser de dico ?

En tout cas, merci ;)

+0 -0

Première amélioration, séparer les états des sous-systèmes (musique, lumière, radiateurs) des "boutons" activateurs :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> DEVICES = { "heat": False, "light": False, "music": False }
>>> DICO = {
...     ("Radiateur", "Rad", "rad", "radiateur"): "heat",
...     ("Lampe", "lampe"): "light",
...     ("Musique", "musique"): "music"
... }
>>> SWITCHES = {}
>>> for key, val in DICO.items():
...     for elt in key:
...         SWITCHES[elt] = val
... 
>>> SWITCHES
{'musique': 'music', 'radiateur': 'heat', 'Lampe': 'light', 'Musique': 'music', 'lampe': 'light', 'Rad': 'heat', 'rad': 'heat', 'Radiateur': 'heat'}
>>> SWITCHES["Musique"]
'music'
>>> DEVICES[SWITCHES["Musique"]]
False
>>> DEVICES[SWITCHES["Musique"]] = True
>>> DEVICES[SWITCHES["Musique"]]
True
>>> DEVICES[SWITCHES["musique"]]
True

Deuxième amélioration, diviser la taille du dictionnaire switches par deux en gérant proprement la casse, et le remplir de façon utile en se donnant plus de moyens mnémotechniques :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
>>> DICO = { ('radiateur', 'rad'): 'heat', ('lampe', 'lum'): 'light', ('musique', 'mus'): 'music'}
>>> SWITCHES = {}
>>> for key, val in DICO.items():
...     for elt in key:
...         SWITCHES[elt] = val
... 
>>> SWITCHES
{'musique': 'music', 'radiateur': 'heat', 'rad': 'heat', 'mus': 'music', 'lampe': 'light', 'lum': 'light'}
>>> DEVICES[SWITCHES["MuSiQuE".lower()]]
True
+0 -0

@nohar,

Ah je pensais que tu voulais oublier l'histoire des dictionnaires… Je pense que ça serait pas plus mal dans un sens, que de les éviter. Pourquoi pas mettre cela dans un fichier de configuration, plutôt que de se taper du texte en dur dans le code, non?

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