Domotique: inversons la tendance

Domotique: inversons la tendance

Mon intérêt pour la domotique ne cesse de croitre avec le temps, et c’est en fouillant internet que je suis tombé sur un très beau projet: Gladys. L’idée de Gladys et assez nouvelle dans la domotique, et reprend un peu le même combat que Siri / Google Now. En gros, au lieu d’avoir un assistant qui exécute vos ordres, Gladys prend les devants et exécute des actions en évaluant vos habitudes, sans rien lui ordonner. Si vous voulez plus de détails, le site du projet est bien expliqué.

En réalité, la différence avec Yana (mon assistant domotique actuel, voir les précédents articles ou le site d’Idleman) est assez simple: ce dernier agit sur les périphériques de sortie, comme les prises radios ou les ampoules connectées tandis que Gladys se base sur la collecte d’informations de capteurs. Ni une ni deux, j’installe ce Gladys en parallèle de Yana pour tester le projet, et je dois avouer que j’ai vité été déçu. Le projet est très beau, sauf que sur le site, on vous vend un peu du rêve. En gros, la seul chose que vous pouvez connecter à Gladys aujourd’hui, ce sont des capteurs d’ouverture de porte et de présence. Oubliez les prises radios, les pins GPIO, les sondes 1-wire, les caméras etc… Il est certes possible de développer ses modules (et encore je n’ai pas vu de doc…) mais pour un coeur domotique, je trouve ça assez léger. Bref, Gladys doit encore gagner en maturité.

N’ayant pas abandonné l’idée d’ajouter plus de capteurs et d’anticipation à la domotique, je développe quelques petites choses sur Yana.

Je rédige donc mon cahier des charges:

– Ajout d’un mode “surveillance” à déclencher par la voix, qui permet de détecter un intrus, prendre des photos et les envoyer par mail, ainsi que déclencher une alarme pour informer l’éventuel cambrioleur qu’il est fiché et que le proprio est mis au courant de sa visite.

– Ajout d’un état “levé / couché”, permettant par exemple d’éteindre le réveil automatiquement lorsque l’état passe de “couché” à “levé” ou encore de ne pas faire sonner le réveil si je ne me suis pas couché la veille (sortie ou autre).

– Allumage automatique des lumières si basse luminosité avec message de bienvenue lorsque je rentre dans mon appart, par exemple au moment où je rentre de l’école, ou quand je rentre d’une soirée dans la nuit, mais pas dans la journée.

– Avertissement vocal + luminaire dès réception d’un mail

Et je commande:

– Un capteur de mouvement, dit PIR

– Un deuxième Raspberry (type A)

– Un capteur à effet hall

Le capteur de mouvement sera branché directement au Raspberry, (couplé avec la caméra), comme ceci:

pir
Motion Sensor

[EDIT]: Il se trouve qu’utiliser une carte Arduino est beaucoup plus propre. Pourquoi? Les processeurs équipés sur les carte Arduino (type ATmega) gèrent bien mieux l’écoute d’un pin (et gèrent les i/o analogiques) et donc les interruptions qui vont avec. De ce fait, j’utilise un Arduino auquel je branche tous mes capteurs, et j’envoie l’info via USB au Raspberry (alimenté par ce dernier). J’ai donc branché le PIR de la façon suivante, auquel j’ai ajouté un récepteur FM 434 Mhz:

PIR branché à un Arduino Uno
PIR branché à un Arduino Uno

Tandis que la paire Pi + capteur à effet hall (mesure du champ magnétique) me permettra de me fabriquer mon propre détecteur d’ouverture de porte. Voici le schéma de montage:

ouverture_thumb
Capteur à effet Hall

Pour plus de détails sur son fonctionnement, je vous invite à (re)lire mon projet “WC Project”. Au niveau de la méthode de récupération des données, j’utilise un soft client / serveur que j’ai codé en C. Une toute petite appli qui permet simplement de communiquer via le wifi en déposant sur une socket un “porte ouverte” ou “porte fermée”. Le capteur (le client donc) envoie l’info lors d’un changement d’état à Yana (le serveur), qui la stocke dans un fichier texte utilisé par le script explicité plus bas (Maison Autonome).

Voilà au passage le code du serveur:

 

#include 
#include 
#include 
#include 
#include 
#include 
 
char buffer[512];
int ma_socket;
 
void main ( void )
{
int client_socket;
struct sockaddr_in mon_address, client_address;
int mon_address_longueur, lg;
char *command_open="echo "0" > /home/pi/Scripts/Domotique/etat_porte.txt";
char *command_closed="echo "1" > /home/pi/Scripts/Domotique/etat_porte.txt";
 
bzero(&mon_address,sizeof(mon_address));
mon_address.sin_port = htons(1234);
mon_address.sin_family = AF_INET;
mon_address.sin_addr.s_addr = htonl(INADDR_ANY);
 
/* creation de socket */
if ((ma_socket = socket(AF_INET,SOCK_STREAM,0))== -1)
{
  printf("la creation rate\n");
  exit(0);
}
/* bind serveur - socket */
bind(ma_socket,(struct sockaddr *)&mon_address,sizeof(mon_address));
 
/* ecoute sur la socket */
listen(ma_socket,5);
 
/* accept la connexion */
mon_address_longueur = sizeof(client_address);
while(1)
{
  client_socket = accept(ma_socket,
                         (struct sockaddr *)&client_address,
                         &mon_address_longueur);
 
  if (fork() == 0)
  {
    close(ma_socket);
 
    lg = read(client_socket,buffer, 512);
    printf("le serveur a recu: %s\n",buffer);
    if (strcmp(buffer, "open")) {
        system(command_open);
    }
    else if (strcmp(buffer, "closed")) {
        system(command_closed);
    }
 
    write(client_socket, "OK\n", 512);
    shutdown(client_socket,2);
    close(client_socket);
    exit(0);
  }
  wait(NULL);
}

shutdown(ma_socket,2); close(ma_socket); }

 

Puis le code du client:

 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
#define SERVEURNAME "192.168.1.20" // adresse IP de mon serveur
 
int to_server_socket = -1;
 
void main (int n, char* param[])
{
 
    if (n != 2) {
        printf("Usage: ./client_gate open|closed\n");
        exit(1);
    }
 
    char *server_name = SERVEURNAME;
    struct sockaddr_in serverSockAddr;
    struct hostent *serverHostEnt;
    long hostAddr;
    long status;
    char buffer[512];
 
    bzero(&serverSockAddr,sizeof(serverSockAddr));
    hostAddr = inet_addr(SERVEURNAME);
    if ( (long)hostAddr != (long)-1)
        bcopy(&hostAddr,&serverSockAddr.sin_addr,sizeof(hostAddr));
    else
    {
        serverHostEnt = gethostbyname(SERVEURNAME);
        if (serverHostEnt == NULL)
        {
        printf("gethost rate\n");
        exit(2);
        }
        bcopy(serverHostEnt->h_addr,&serverSockAddr.sin_addr,serverHostEnt->h_length);
    }
    serverSockAddr.sin_port = htons(1234);
    serverSockAddr.sin_family = AF_INET;
 
    /* creation de la socket */
    if ( (to_server_socket = socket(AF_INET,SOCK_STREAM,0)) < 0)
    {
        printf("creation socket client ratee\n");
        exit(3);
    }
    /* requete de connexion */
    if(connect( to_server_socket,
                (struct sockaddr *)&serverSockAddr,
                sizeof(serverSockAddr)) < 0 )
    {
        printf("demande de connection ratee\n");
        exit(4);
    }
 
    /* envoie de donne et reception */
    write(to_server_socket, param[1], 20);
    read(to_server_socket,buffer,512);
    printf(buffer);


    /* fermeture de la connection */
    shutdown(to_server_socket,2);
    close(to_server_socket);
    exit(0);
}

 

Et voilà notre détecteur d'ouverture de porte wifi, fait maison et totalement fonctionnel pour un prix de 40€. Je vous met au défi de me trouver la même chose dans le commerce pour ce prix (en wifi toujours hein)! N'ayant besoin que d'un seul détecteur, le code est assez simple. Mais vous pouvez imaginer une gestion d'identifiants pour gérer plusieurs modules, et donc plusieurs portes.

Pour l'idée générale du fonctionnement côté software, j'ai pensé à faire un script que j'ai appelé "Maison Autonome" et qui a pour but de bien porter son nom =) En gros l'idée est surtout de ne pas s'égarer en ayant un peu de configuration dans Yana, quelques services, des dizaines de tâches Cron plus des centaintes de scripts ici et là. Mon objectif est le suivant:

- Configurer le maximum via Yana

- Dans Cron: une seule tâche liée à Yana, et pour automatiser d'éventuels scénarios, directement via l'interface web "événements".

- Un unique script python qui permettra de scripter tout ce que Yana n'est pas en mesure d'apporter aujourd'hui.

- Un mini serveur codé en C pour communiquer avec d'autres Raspberry (cf le détecteur d'ouverture de porte).

Pour le script, j'ai hésité entre Bash et Python, mais vu le nombre quasi-infinie de modules pour Python, mon choix a été vite fait. De plus, l'intéraction en Bash avec les GPIO est assez limitée. Ce dernier se verra certainement modifié avec le temps et les besoins qui apparaîtront. Bon et bien voici donc le monstre (les commentaires sont assez explicites): /! Je n'ai pas encore pu coder la pseudo-analyse du comportement sur l'etat "couche" / "leve" /!

# -*- coding: utf-8 -*-

import time, datetime
import picamera
import os, urllib, commands
import serial, sys, feedparser, re
import sunrise

# Les différents états de la porte:
#    0 = etat d'origine (porte fermee)
#    1 = porte ouverte -> Sortie
#    2 = porte fermée -> Personne a l'interieur
#    3 = porte ouverte -> Bienvenue
#    4 = porte fermée -> A l'interieur
etat_home_int = 0

# Variables pour Motion Sensor (heures de déclenchement)
t_light_start = 18
t_light_end = 8
t_wake_start = 6
t_wake_end = 10
h_last_motion = 24

# Variables pour vérifications Gmail
USERNAME="xxxxxxxx"
PASSWORD="xxxxxxxx"
PROTO="https://"
SERVER="mail.google.com"
PATH="/mail/feed/atom"
 
pirState = False                        # We start, assuming no motion detected
pirVal = False                          # We start, assuming no motion detected

# Phrases disponibles
sentence = "Intrusion detected!"
welcome = "Bienvenue petit valou"
goodbye = "Tu pars déjà?"
# Convertion pour utilisation dans une URL
speak_s1 = urllib.quote_plus(sentence)
speak_s2 = urllib.quote_plus(welcome)
speak_s3 = urllib.quote_plus(goodbye)

# Mémorisation des états précédents (pour gérer le changement d'état)
pre_state_mail = 0
pre_state_door = 0

# Init Serial USB connexion with Arduino
ser = serial.Serial('/dev/ttyACM0', 9600)

while True:

	print("Checking...")
	
	# Récupération des différents états dans les fichiers texte
	fic_phy = open("/home/pi/Scripts/Domotique/etat_physique.txt", 'r')
	fic_door = open("/home/pi/Scripts/Domotique/etat_porte.txt", 'r')
	fic_cam = open("/home/pi/Scripts/Domotique/surveillance.txt", 'r')
	fic_trigger = open("/home/pi/Scripts/Domotique/trigger_surveillance.txt", 'r')

	etat_physique = fic_phy.readline().rstrip()
	etat_porte = fic_door.readline().rstrip()
	surveillance = fic_cam.readline().rstrip()
	pre_state_alarm = fic_trigger.readline().rstrip()

	fic_phy.close()
	fic_door.close()
	fic_cam.close()
	fic_trigger.close()

	# Récupérer l'heure courante (sans les minutes)
	current_date = datetime.datetime.now()
	h_now = current_date.hour

	# Read USB connexion
	usb = ser.readline()
	mvt = usb.find("detected")

	print("Analyzing result...")
	if (mvt >= 0):
		#os.system("sudo mpg123 \'http://translate.google.com/translate_tts?tl=fr&client=tw-ob&q=mouvement\'")
		print("Mouvement detected!")
        	# Actions when motion is detected:
		os.system("sudo amixer -c 0 set PCM 100%  > /dev/null 2> /dev/null &")

		# Mark last detected motion
		current_date = datetime.datetime.now()
		h_last_motion = current_date.hour

		# Check if file camera_supervision.txt is on
		if (surveillance == "1" and pre_state_alarm == "0"):
			print("Intrusion detected!!!")
			# Write "1" in trigger_surveillance.txt
			fic_trigger = open('/home/pi/Scripts/Domotique/trigger_surveillance.txt', 'w')
			fic_trigger.write('1')
			fic_trigger.close()

			# Talk: physical intrusion detected, AND mail me.
			os.system("sudo hue set all --on && huecolor-basic --red")
			os.system("sudo mpg123 \'http://translate.google.com/translate_tts?tl=en&client=tw-ob&q=" + speak_s1 + "\'")
			# Take picture
			with picamera.PiCamera() as camera:
				camera.start_preview()
				time.sleep(5)
				camera.capture('/home/pi/Pictures/intrusion.jpg')
				camera.stop_preview()
			os.system("sudo echo 'Intrusion Detected!' | sudo mail -s 'Yana: Alerte' -A /home/pi/Pictures/intrusion.jpg valentin.dole@protonmail.com")
			time.sleep(10)
			os.system("sudo hue set all --off")

		# Check if etat_porte.txt equals to "1" (open) AND time is between t_light_start AND t_light_end
		if (pre_state_door != etat_porte):
			pre_state_door = etat_porte # Pas de capteur porte pour le moment
			if (etat_home_int == 0):
				# Qqn a l'interieur: ne rien faire
				etat_home_int += 1
			elif (etat_home_int == 1):
				# Sorite, dire aurevoir
				etat_home_int += 1
				os.system("sudo mpg123 \'http://translate.google.com/translate_tts?tl=fr&client=tw-ob&q=" + speak_s3 + "\' &")
			elif (etat_home_int == 2):
				# Porte fermee, personne a l'interieur
				etat_home_int += 1
			elif (etat_home_int == 3):
				# Ouverture, dire bonjour
				# Switch lights on (salon + cuisine) ANS say hello
				os.system("sudo mpg123 \'http://translate.google.com/translate_tts?tl=fr&client=tw-ob&q=" + speak_s2 + "\' &")
				if ((h_now > t_light_start and h_now <= 23) or (h_now >= 0 and h_now < t_light_end)):
					os.system("sudo /var/www/yana-server/plugins/radioRelay/radioEmission 0 8217034 1 on")
					os.system("sudo hue set 1 --on && sudo huecolor-basic --white")
				etat_home_int += 1
			elif (etat_home_int == 4):
				# Fermeture porte, retour a l'etat initial
				etat_home_int = 0

		# Allume la lumière si mouvement
		if ((h_now >= t_light_start and h_now <= 23) or (h_now >= 0 and h_now <= t_light_end)):
			print("Yana allume la lumiere du salon")
			#os.system("sudo /var/www/yana-server/plugins/radioRelay/radioEmission 0 8217034 1 on")
			os.system("sudo hue set 1 --on && sudo huecolor-basic --yellow")


	# Se connecte a votre compte gmail et retourne 0 si il n y a pas de nouveaux mails, 1 s il y en a.
	newmails = int(feedparser.parse(PROTO + USERNAME + ":" + PASSWORD + "@" + SERVER + PATH)["feed"]["fullcount"])
	print("Mails non lues: " + str(newmails))

	# Output data
	if (newmails > 0) and (pre_state_mail < newmails):
		# Blink Hue with blue light and talk: new mail
		ret_1 = commands.getoutput("hue get 1 | head -n 1 | grep true")
		ret_2 = commands.getoutput("hue get 2 | head -n 1 | grep true")
		os.system("sudo mpg123 \"http://translate.google.com/translate_tts?tl=fr&client=tw-ob&q=Valou,%20tu%20as%20ressu%20un%20nouveau%20mail\" &")
		if (ret_1 == "") :
			if (ret_2 == ""):
				os.system("sudo hue set all --on && sudo huescene-sequence ; sleep 15 ; sudo hue set all --off")
			else:
				os.system("sudo hue set all --on && sudo huescene-sequence ; sleep 15 ; sudo hue set 1 --off ; sudo hue set 2 --on")
		else :
			if (ret_2 == ""):
				os.system("sudo hue set all --on && sudo huescene-sequence ; sleep 15 ; sudo hue set 1 --on ; sudo hue set 2 --off")
			else:
				os.system("sudo hue set all --on && sudo huescene-sequence ; sleep 15 ; sudo hue set all --on")
		os.system("sudo huecolor-basic --yellow")

		# Mise à jour du compteur
		pre_state_mail = newmails
	elif (pre_state_mail > newmails):
		# Mise à jour du compteur
		pre_state_mail = newmails
		print("No mail's trigger")
	else:
		print("No mail's trigger")

	# Guess if someone is wake up or not
	if (h_now - h_last_motion >= 1) :
		os.system("sudo hue set all --off")
		fic_phy = open('/home/pi/Scripts/Domotique/etat_physique.txt', 'w')
		fic_phy.write('couche')
		fic_phy.close()

	time.sleep(1)

On y retrouve toutes les solutions à mes besoins, faisant basculer mon appart dans une première étape vers l'autonomie. Sinon, pour le fun j'ai customisé pas mal de plugins, comme l'ajout d'un suivi de la température sur les 12 derniers jours:

Température
Température

L'ajout d'un mémo sur la page "événements" pour pouvoir programmer différents scénarios:

Mémo
Mémo

Ou encore la fusion de 2 plugins pour utiliser la caméra comme appareil photo et comme caméra avec historique des enregistrements vidéos:

Caméra
Caméra

Et je suis tombé amoureux des ampoules Philips Hue, alors j'ai développé un plugin (disponible sur le market Yana) pour la gestion des ampoules avec reconnaissance vocale et widgets:

Dashboard
Dashboard

Et bien voilà, il ne me reste plus qu'à tester tout ça dans mon appart. Ce n'est donc pas fini mais je suis fière de mes petites retouches =) après, sans le travail d'Idleman, ou même les idées prises au projet Gladys, rien de tout ça n'aurait vu le jour. Il faut d'ailleurs aussi penser aux différentes communautés qui font vivre ces projets, et qui permettent d'innover dans notre petit monde.

2 Comments on “Domotique: inversons la tendance

  1. Bonjour,j’ai un petit probléme sur le plugin temperature présent sur le market de yana.
    J’ai activer le plugin et suivi vos instructions qui sont en bas de la page temperature (installation). Quand j ‘essai d’executer la ligne:
    sudo chown www-data:www-data /var/www/yana-server/plugins/temperature/get_temp.sh && sudo chmod 740 /var/www/yana-server/plugins/temperature/get_temp.sh
    J’ai un message d’erreur car il ne trouve pas le script get_temp.sh
    En ce qui concerne le widget temperature present sur la page d’accueil il fonctionne bien et me retourne la temperature capter par ma sonde .

    Sur la page temperature j’aimerai afficher de beau gaphique.A la place j ai 2 message d’erreur:
    HISTORIQUE DES 12 DERNIERS JOURS
    Warning: fopen(plugins/temperature/history_d.txt): failed to open stream: No such file or directory in /var/www/yana-server/plugins/2e262487d3580b4f8d2c160cb0091dd8_/temperature.plugin.enabled.php on line 60

    Warning: fclose() expects parameter 1 to be resource, boolean given in /var/www/yana-server/plugins/2e262487d3580b4f8d2c160cb0091dd8_/temperature.plugin.enabled.php on line 71
    — 07/07
    et ensuite
    HISTORIQUE DES 12 DERNIERES HEURES
    Warning: fopen(plugins/temperature/history_h.txt): failed to open stream: No such file or directory in /var/www/yana-server/plugins/2e262487d3580b4f8d2c160cb0091dd8_/temperature.plugin.enabled.php on line 79

    Warning: fclose() expects parameter 1 to be resource, boolean given in /var/www/yana-server/plugins/2e262487d3580b4f8d2c160cb0091dd8_/temperature.plugin.enabled.php on line 90
    — 09h00

    En attendant une reponse de votre part je vous souhaite une bonne soirée.Bravo pour votre travail que je suis avec attention

    • Bonjour et merci pour votre commentaire =)
      Pour votre problème, est-ce que votre dossier contenant Yana Server se trouve bien à /var/www/yana-server/ ? (Pour tester, tapez “ls /var/www/yana-server”: si la commande retourne un “ls: cannot access /var/www/yana-server/: No such file or directory” (ou équivalent en français) c’est que le problème vient de là. Sinon j’ai copié collé la commande sudo chown www-data etc… et cela fonctionne chez moi. Il se peut aussi que vous n’ayez pas la dernière version du plugin, au cas où re-téléchargez le du market idleman. Attention, il me semble que l’intégration du nouveau market dans l’interface Yana Server n’est pas encore effectuée, vous devrez l’installer manuellement. J’imagine que vous savez comment faire?