Smart tableau – nouveau End Device pour mon system domotique

J’ai décidé d’ajouter un nouveau « end device » dans le système pour pouvoir mesure/contrôler certains éléments de la cuisine.

Cadre_avant

Les fonctions de ce nouveau « end device » sont :

  • Détection de personne (capteur PIR)
  • Détecteur de fumée (démontage d’un détecteur de fumée du commerce)
  • Mesure Température et humidité

L’ensemble des capteurs est monte à l’arrière d’un cadre qui est pose dans la cuisine au-dessus des rangements. Le capteur de fumée est donc très prêt du plafond pour être sur qu’il détectera bien un incendie.

L’ensemble du montage communique par ZigBee avec le coordinateur. Ensuite le coordinateur transmet les infos au « brain » au travers d’une liaison série USB (exactement comme les autres devices).

Le « brain » python doit donc maintenant être capable de comprendre ce nouveau device pour, par exemple, prévenir de la détection d’un incendie.

Ceci est très simple grâce au design du system domotique puisque le « détecteur incendie » correspond parfaitement à un device de type « interrupteur stable » et donc il suffit de rajouter dans le « brain » une nouvelle action pour cet interrupteur stable en particulier. On ajoute donc :

if ((aOneDevice.id == 2) and (aOneDevice.currentStatus=="unstable")):
 sendEmailFireDetected()

dans la boucle des détections évènements trigger par les interrupteurs stable :

#Step 1 : Verifier tous les detecteurs (interupteurs stables) pour voir si ils ont ete actives et prendre les actions correspondantes avant de les reset
        #Par exemple si le detecteur de fumee a ete active alors on va envoyer un mail 
        logging.info("Checking all possible event")
        for aOneDevice in iListOfDevice.registeredDevices:
            logging.debug("checking event : " + str(aOneDevice.id))
            if ((aOneDevice.id == 2) and (aOneDevice.currentStatus=="unstable")):
                sendEmailFireDetected()
            elif ((aOneDevice.id == 10) and (aOneDevice.currentStatus=="unstable")):
                self.PeopleDetectedEntree(iListOfDevice)
            elif ((aOneDevice.id == 9) and (aOneDevice.currentStatus=="unstable")):
                self.PeopleDetectedCharlesRoom(iListOfDevice)
            aOneDevice.reset()

Ensuite il ne reste plus qu’à créer la fonction « send fire detection » pour prévenir l’utilisateur qu’un incendie a été détecté. Pour le moment on se contente d’envoyer un email mais dans l’avenir on peut simplement faire évoluer cette action avec par exemple un appel automatique au pompier

L’envoie de l’email est assure par la fonction suivante :

def sendEmailFireDetected():
 '''Envoie email lors de la detection incendie'''
 logging.error("Envoi email detection incendie")
 # Define email addresses to use
 addr_to = Config["addr_to"]
 addr_from = Config["addr_from"]
 # Define SMTP email server details
 smtp_server = Config["smtp_server"]
 smtp_user = Config["smtp_user"]
 smtp_pass = Config["smtp_pass"]

 # Construct email
 expires = datetime.datetime.now()
 msg = MIMEText('Fire detected at ' + str(expires))
 msg['To'] = addr_to
 msg['From'] = addr_from
 msg['Subject'] = 'Fire alarm'

 # Send the message via an SMTP server
 s = smtplib.SMTP(smtp_server, 587)
 s.login(smtp_user,smtp_pass)
 s.sendmail(addr_from, addr_to, msg.as_string())
 s.quit()

Création d’un daemon python capable de communiquer avec le port USB et un socket

Actuellement quand je clique sur un lien qui nécessite l’envoie d’un message a un capteur dans l’appartement comme par exemple « actualiser la température » :

ScreenShot001

Le site web envoie une requête AJAX qui appel un script python pour communiquer par USB depuis le RaspberryPi vers l’Arduino Leonardo. Le script monitor ensuite le port USB pour la réponse de l’Arduino Leonardo pendant 10s pour récupérer la réponse du capteur et la transmettre au site web et à l’utilisateur.

TemperatureRequestV1

Ce design fonctionne plutôt bien mais bug de temps en temps quand plusieurs personne tentent de rafraichir la température et l’humidité en même temps car une réponse « humidité » peut arriver avant une réponse « température » et les réponses seront inverse. De plus cela ne me permet pas de mettre des capteurs  qui ne nécessite pas d’être appelé pour envoyer une information tel qu’un détecteur de fumée 😉
J’ai donc décidé de revoir le script python pour lui permettre de recevoir des infos de capteur a tout moment sans interaction avec l’utilisateur. Ce daemon doit cependant toujours être capable de recevoir les demandes des utilisateurs du site web…
Le daemon est donc en constante attente soit de message provenant de l’Arduino Leonardo au travers du port USB, soit de message de l’utilisateur (site web) provenant d’un client léger python au travers d’un socket réseau.

TemperatureRequestV2Server

Le monitoring simultané du port USB et d’un socket se fait grâce au module python « select » qui est un appel direct à la fonction « select » disponible sous linux. C’est le moyen le plus simple que j’ai trouvé pour faire du multiplexing sur mes 2 entrées (port USB et socket). Le code est donc plutôt simple puisque l’on attend juste que « select » nous donne la main pour tester quelle « entrée » a reçue quelque chose. En fonction de l’entrée on procéder au traitement. Voilà le code avec quelque commentaire pour mieux comprendre 😉

Un gros changement a également eut lieu dans la chaine de réponse puisque le process est maintenant asychrone. Il faut donc inclure plus d’information dans la réponse tel que l’ID du capteur pour savoir a quoi correspond la valeur reçue sur le port USB. Voilà un nouvel exemple de réponse de l’Arduino  Leonardo
La réponse comporte plusieurs champ/valeur tel que l’ID du capteur. Cet ID sera également inséré dans la DB.

Bonus :

Comme il m’arrive de temps en temps de redémarrer mon Raspberry Pi et que le daemon n’est pas lance au démarrage le system pourrai ne plus fonctionner….
J’ai donc légèrement modifie mon client qui est appelé par le serveur web pour détecter si le daemon est bien démarrer sur le RaspberryPi. Si ce n’est pas le cas le Server se charge de démarrer le daemon. On ne pourra donc pas répondre a la requête utilisateur mais au moins les suivantes fonctionneront bien.

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
    s.connect((HOST, PORT))
except:
    print("no server available")
    s.close()
    print("starting server")
    subprocess.call("python PythonWrapperWebArduinoUsbD.py", shell=True)
    #on va rester la a l infini

Exemple :

On lance le script python serveur avec les arguments tel qui l serait lancer par le site web a la réception d’une requête client (les arguments utilisés simulant un Ping avec réponse hardcode ‘456’ d’un capteur.

pi@raspberrypi ~/Usb_Arduino_Leonardo $ python PythonWrapperWebArduinoUsbS.py -s J -i 20 -o TOTO
no server available
starting server

Le client détecte bien qu’il n y a pas de serveur et en lance donc un. Dans un autre terminal on relance le client

Client :

pi@raspberrypi ~/Usb_Arduino_Leonardo $ python PythonWrapperWebArduinoUsbS.py -s J -i 20 -o TOTO
Received 'MSG:J_ID:20_ORIGIN:TOTO'
pi@raspberrypi ~/Usb_Arduino_Leonardo $

Server :

pi@raspberrypi ~/Usb_Arduino_Leonardo $ python PythonWrapperWebArduinoUsbS.py -s J -i 20 -o TOTO
no server available
starting server
handle the server socket
handle all other sockets
input is : MSG:J_ID:20_ORIGIN:TOTO
Writting input to USB port and sending back to sender
Log line : DATE: 2013-03-11 21:02:08.518208 ORIGIN: TOTO CMD: J ID: 20
handle all other sockets
input is :
Closing socket

Le serveur à bien reçu la demande du client et à forwarder la demande a l’Arduino Leonardo par le port USB. Ensuite il a repris sa boucle et quelque secondes plus tard il a reçu la réponse ‘456’ sur le port USB. La réponse a été décodé et stocke dans la DataBase pour ensuite être utilise sur le site Web.