Test libcloud 0.14 beta 3 sur GCE, AWS et Rackspace

Depuis la version 0.14 (encore en phase de bêta) il est possible d’utiliser Google Compute Engine. Il existe encore qq bugs que j’essaye d’aider a éliminer :

https://github.com/apache/libcloud/commit/7a04971288791a02c1c335f9d36da2f90d892b5b

Grace a libcloud il est tres facile de creer des machinnes virtuel dans les cloud public. Voila un petit exemple pour GCE, AWS et RackSpace :

def createNodeGce(iGceConex,iAmi, iInstance, iLocation):
 #image = [i for i in iGceConex.list_images() if i.id == iAmi ][0]
 size = [s for s in iGceConex.list_sizes() if s.name == iInstance][0]
 alocation = [s for s in iGceConex.list_locations() if s.name == iLocation][0]
 print("Create server with image : " + str(iAmi) + " and server type : " + str(size) + " and location : " + str(alocation))
 node = iGceConex.create_node(name=GCE_INSTANCE_NAME, image=iAmi, size=size,location=alocation)
 print(str(node))
 return node
def createNodeAws(iAwsConex,iAmi, iInstance):
 image = [i for i in iAwsConex.list_images() if i.id == iAmi ][0]
 size = [s for s in iAwsConex.list_sizes() if s.id == iInstance][0]
 print("Create server with image : " + str(image) + " and server type : " + str(size))
 node = iAwsConex.create_node(name=EC2_INSTANCE_NAME, image=image, size=size,ex_securitygroup=EC2_SECURITY_GROUP,ex_keyname=EC2_KEY_NAME)
 print(str(node))
def createNodeRackSpace(iRackConex,iAmi, iInstance):
 image = [i for i in iRackConex.list_images() if i.name == iAmi ][0]
 size = [s for s in iRackConex.list_sizes() if s.id == iInstance ][0]
 aSshKeyFilePath = "SSH_KEY_PUBLIC"
 print("Create server with image : " + str(image) + " and server type : " + str(size))
 node = iRackConex.create_node(name=RACKSPACE_INSTANCE_NAME, image=image, size=size)

L’exemple complet est disponnible sur github ICI :

https://github.com/charly37/LibCloud014b3Test/blob/master/TestLibCloud.py

Tester sur une machinne Ubuntu 13 sur AWS avec l’instal suivante :

#Install setuptools. Necessaire pour pip
sudo apt-get -y install python-setuptools

#Install GIT. Necessaire pour libcloud DEV
sudo apt-get -y install git

#install PIP
curl -O https://pypi.python.org/packages/source/p/pip/pip-1.4.1.tar.gz
tar xvzf pip-1.4.1.tar.gz
cd pip-1.4.1
sudo python setup.py install

#install libcloud 
sudo pip install git+https://git-wip-us.apache.org/repos/asf/libcloud.git@trunk#egg=apache-libcloud

Voila le resultat avec listing des VMs sur les 3 providers et puis creation d’une VM sur chaque provider avant de lister une nouvelle fois les VMs existente.
ubuntu@domU-12-31-39-09-54-77:~$ python test.py

Create libcloud conex obj
Then list the different provider object (to validate logins)
Listing GCE :
Listing EC2 :
Listing RackSpace :

Then list the different VMs (on each provider)
Listing GCE :
Vms :
[]

Listing EC2 :
Vms :
[<Node: uuid=f8cc89f6b6e061784e37c49fbc28a3917a86acf2, name=TEST, state=0, public_ips=[‘54.196.177.12’], provider=Amazon EC2 …>, <Node: uuid=e81bdb7b32d3fbff0ec745e9dc63cf1d7e5c32bb, name=____AWS_TUNNEL, state=0, public_ips=[‘54.227.255.145’, ‘54.227.255.145’], provider=Amazon EC2 …>, <Node: uuid=d231cd56e5e7f3ffad93eb7d1f0b9445e91199ed, name=____Couchbase Master, state=5, public_ips=[], provider=Amazon EC2 …>]

Listing RackSpace :
Vms :
[<Node: uuid=d6ef246e8a738ac80cdb99e2f162b1ec46f21992, name=Rgrid, state=0, public_ips=[u’2001:4801:7820:0075:55e7:a60b:ff10:acdc’, u’162.209.53.19′], provider=Rackspace Cloud (Next Gen) …>]

Then create nodes

Create server with image : <NodeImage: id=ami-ad184ac4, name=099720109477/ubuntu/images/ebs/ubuntu-saucy-13.10-amd64-server-20131015, driver=Amazon EC2  …> and server type : <NodeSize: id=t1.micro, name=Micro Instance, ram=613 disk=15 bandwidth=None price=0.02 driver=Amazon EC2 …>
<Node: uuid=0c2b1e827325d3605c338aed44eb7d87e2b448b1, name=test, state=3, public_ips=[], provider=Amazon EC2 …>
Create server with image : <NodeImage: id=f70ed7c7-b42e-4d77-83d8-40fa29825b85, name=CentOS 6.4, driver=Rackspace Cloud (Next Gen)  …> and server type : <OpenStackNodeSize: id=3, name=1GB Standard Instance, ram=1024, disk=40, bandwidth=None, price=0.06, driver=Rackspace Cloud (Next Gen), vcpus=1,  …>
Create server with image : centos-6-v20131120 and server type : <NodeSize: id=12907738072351752276, name=n1-standard-1, ram=3840 disk=10 bandwidth=0 price=None driver=Google Compute Engine …> and location : <NodeLocation: id=654410885410918457, name=us-central1-a, country=us, driver=Google Compute Engine>
<Node: uuid=cd805862d414792acd7b7ed8771de3d56ace54ad, name=test, state=0, public_ips=[u’173.255.117.171′], provider=Google Compute Engine …>

Wait few seconds

Then list the different VMs again(on each provider)

Listing GCE :
Vms :
[<Node: uuid=cd805862d414792acd7b7ed8771de3d56ace54ad, name=test, state=0, public_ips=[u’173.255.117.171′], provider=Google Compute Engine …>]

Listing EC2 :
Vms :
[<Node: uuid=f8cc89f6b6e061784e37c49fbc28a3917a86acf2, name=TEST, state=0, public_ips=[‘54.196.177.12’], provider=Amazon EC2 …>, <Node: uuid=e81bdb7b32d3fbff0ec745e9dc63cf1d7e5c32bb, name=____AWS_TUNNEL, state=0, public_ips=[‘54.227.255.145’, ‘54.227.255.145’], provider=Amazon EC2 …>, <Node: uuid=d231cd56e5e7f3ffad93eb7d1f0b9445e91199ed, name=____Couchbase Master, state=5, public_ips=[], provider=Amazon EC2 …>, <Node: uuid=0c2b1e827325d3605c338aed44eb7d87e2b448b1, name=test, state=0, public_ips=[‘50.16.66.112′], provider=Amazon EC2 …>]

Listing RackSpace :
Vms :
[<Node: uuid=a41759d8732fdbddf37a22327d63734bd89647aa, name=test, state=0, public_ips=[u’2001:4801:7819:0074:55e7:a60b:ff10:dc68′, u’166.78.242.86′], provider=Rackspace Cloud (Next Gen) …>, <Node: uuid=d6ef246e8a738ac80cdb99e2f162b1ec46f21992, name=Rgrid, state=0, public_ips=[u’2001:4801:7820:0075:55e7:a60b:ff10:acdc’, u’162.209.53.19′], provider=Rackspace Cloud (Next Gen) …>]
ubuntu@domU-12-31-39-09-54-77:~$

Remplacement serveur web lighttpd par twisted

Dans mon système domotique fait maison j’utilise twisted pour la partie “logique” du système et lighttpd pour la UI. Pour des raisons de simplicité j’ai décidé d’utiliser twisted pour gérer la partie Web.

ChangeSet : https://code.google.com/p/domotique-1/source/detail?r=281ebe02e19eef0f07098430de31681c8a836713

Il y a juste un petit piège pour la gestion du php dans twisted. Il faut definir une classe en charge de la gestion des pages php :

class PhpScript(twcgi.FilteredScript):
 """
 Twisted wrapping class to execute PHP code.
 """
 filter = '/usr/bin/php-cgi' # Points to the perl parser
def runProcess(self, env, request, qargs=[]):
 env['REDIRECT_STATUS'] = ''
 return FilteredScript.runProcess(self, env, request, qargs)

En cas de probleme il faudra verifier que le binaire “/usr/bin/php-cgi” est present.
Ensuite il faut declarer la classe dans le reacteur avec :

root.processors = {".php": PhpScript}

Serveur CentOs 6.4 avec stockage éphémère sur Amazon Web Service

Pour des raisons de portabilité entre Cloud provider j’ai décidé de migrer de l’image “Linux AMI” disponible uniquement sur AWS vers CentOS 6.4 disponible chez tous les Cloud public.

Pendant la migration j’ai cependant constate quelques problèmes avec les disques éphémères de mes instances qui n’été plus accessibles.

Avec la distribution « Linux AMI » et d’autres (par exemple Ubuntu) les disques éphémères sont utilisable dès la création de l’instance :

ubuntu@ip-10-181-140-167:~$ df
 Filesystem     1K-blocks   Used Available Use% Mounted on
 /dev/xvda1       8125880 739816   6966636  10% /
 none                   4      0         4   0% /sys/fs/cgroup
 udev              836600     12    836588   1% /dev
 tmpfs             169208    184    169024   1% /run
 none                5120      0      5120   0% /run/lock
 none              846032      0    846032   0% /run/shm
 none              102400      0    102400   0% /run/user
 /dev/xvdb      153899044 192068 145889352   1% /mnt

Alors qu’ils ne sont pas disponibles sur CentOS :

[root@ip-10-180-141-3 ~]# df
 Filesystem           1K-blocks      Used Available Use% Mounted on
 /dev/xvde              8256952    649900   7187624   9% /
 tmpfs                   847608         0    847608   0% /dev/shm
 [root@ip-10-180-141-3 ~]# ll

Pour pouvoir utiliser les disques éphémères sur une centOS il faut faire 2 choses !

1/demander à avoir les disques éphémères lors de la création de la VM.

Cela peut se faire dans l’interface graphique lors de la création de la VM (fig) ou alors en ligne de commande lors de la création également.

Ephemere

Dans mon cas j’utilise « libcloud » et il faut donc ajouter l’option « block mapping ». Voilà comment procéder :

node = EC2_Conex.create_node(name=aServerName, image=image, 
size=size,ex_securitygroup=[EC2_SECURITY_GROUP],ex_keyname=EC2_KEY_NAME, 
ex_blockdevicemappings=[{'DeviceName': '/dev/sdb', 'VirtualName': 'ephemeral0'}])

Ensuite il faut attendre que la VM soit créé pour passer à l’étape 2.

J’ai créé 2 VMs pour montrer la différence « avec » et « sans » l’option.

AVEC :

charles@ip-10-164-17-109:~$ ssh -i /Rbox/conf/Rbox_Proto2.pem root@ec2-107-21-170-54.compute-1.amazonaws.com
 Last login: Fri Oct  4 08:58:53 2013 from 10.164.17.109
 [root@ip-10-182-164-253 ~]# fdisk -l

 Disk /dev/xvdf: 160.1 GB, 160104972288 bytes
 255 heads, 63 sectors/track, 19464 cylinders
 Units = cylinders of 16065 * 512 = 8225280 bytes
 Sector size (logical/physical): 512 bytes / 512 bytes
 I/O size (minimum/optimal): 512 bytes / 512 bytes
 Disk identifier: 0x00000000

 Disk /dev/xvde: 8589 MB, 8589934592 bytes
 255 heads, 63 sectors/track, 1044 cylinders
 Units = cylinders of 16065 * 512 = 8225280 bytes
 Sector size (logical/physical): 512 bytes / 512 bytes
 I/O size (minimum/optimal): 512 bytes / 512 bytes
 Disk identifier: 0x00000000

Sans

[root@ip-10-28-86-161 ~]# fdisk -l

 Disk /dev/xvde: 8589 MB, 8589934592 bytes
 255 heads, 63 sectors/track, 1044 cylinders
 Units = cylinders of 16065 * 512 = 8225280 bytes
 Sector size (logical/physical): 512 bytes / 512 bytes
 I/O size (minimum/optimal): 512 bytes / 512 bytes
 Disk identifier: 0x00000000

 [root@ip-10-28-86-161 ~]# exit

La VM créer sans cette options ne pourra jamais utiliser le stockage éphémère et vous pouvez vous arrêter ici…..

La seconde étape permet d’utiliser les disques. En effet il ne sont pas utilisable pour le moment meme sur la machine qui a le disque éphémère :

[root@ip-10-182-164-253 ~]# df
 Filesystem           1K-blocks      Used Available Use% Mounted on
 /dev/xvde              8256952    650188   7187336   9% /
 tmpfs                   847608         0    847608   0% /dev/shm
 [root@ip-10-182-164-253 ~]#

2/Rendre les disques utilisable

 [root@ip-10-182-164-253 ~]# fdisk -l

 Disk /dev/xvdf: 160.1 GB, 160104972288 bytes
 255 heads, 63 sectors/track, 19464 cylinders
 Units = cylinders of 16065 * 512 = 8225280 bytes
 Sector size (logical/physical): 512 bytes / 512 bytes
 I/O size (minimum/optimal): 512 bytes / 512 bytes
 Disk identifier: 0x00000000

 Disk /dev/xvde: 8589 MB, 8589934592 bytes
 255 heads, 63 sectors/track, 1044 cylinders
 Units = cylinders of 16065 * 512 = 8225280 bytes
 Sector size (logical/physical): 512 bytes / 512 bytes
 I/O size (minimum/optimal): 512 bytes / 512 bytes
 Disk identifier: 0x00000000

 [root@ip-10-182-164-253 ~]# df
 Filesystem           1K-blocks      Used Available Use% Mounted on
 /dev/xvde              8256952    650188   7187336   9% /
 tmpfs                   847608         0    847608   0% /dev/shm
 [root@ip-10-182-164-253 ~]# mkfs -t ext4 /dev/xvdf
 mke2fs 1.41.12 (17-May-2010)
 Filesystem label=
 OS type: Linux
 Block size=4096 (log=2)
 Fragment size=4096 (log=2)
 Stride=0 blocks, Stripe width=0 blocks
 9773056 inodes, 39088128 blocks
 1954406 blocks (5.00%) reserved for the super user
 First data block=0
 Maximum filesystem blocks=4294967296
 1193 block groups
 32768 blocks per group, 32768 fragments per group
 8192 inodes per group
 Superblock backups stored on blocks:
         32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
         4096000, 7962624, 11239424, 20480000, 23887872

 Writing inode tables: done
 Creating journal (32768 blocks): done
 Writing superblocks and filesystem accounting information:
 done

 This filesystem will be automatically checked every 34 mounts or
 180 days, whichever comes first.  Use tune2fs -c or -i to override.
 [root@ip-10-182-164-253 ~]#
 [root@ip-10-182-164-253 ~]# df
 Filesystem           1K-blocks      Used Available Use% Mounted on
 /dev/xvde              8256952    650188   7187336   9% /
 tmpfs                   847608         0    847608   0% /dev/shm
 [root@ip-10-182-164-253 ~]# mkdir /ephemeral0
 [root@ip-10-182-164-253 ~]# echo "/dev/xvdf /ephemeral0 ext4 defaults 1 2" >> /etc/fstab
 [root@ip-10-182-164-253 ~]# mount /ephemeral0
 [root@ip-10-182-164-253 ~]# df
 Filesystem           1K-blocks      Used Available Use% Mounted on
 /dev/xvde              8256952    650192   7187332   9% /
 tmpfs                   847608         0    847608   0% /dev/shm
 /dev/xvdf            153899044    191936 145889484   1% /ephemeral0

Et voilà 😉

Resume :

mkfs -t ext4 /dev/xvdf
mkdir /ephemeral0
echo "/dev/xvdf /ephemeral0 ext4 defaults 1 2" >> /etc/fstab
mount /ephemeral0

Test nouvelle API freebox OS pour arret automatique Wifi

Avec le déploiement du Freebox OS depuis Juin 2013 l’API pour dialoguer avec la Freebox évolue. Heureusement Free offre une très bonne documentation pour utiliser cette nouvelle API :

http://dev.freebox.fr/sdk/os/

La documentation est plutôt claire et comporte beaucoup d’exemple.

J’ai décidé de tester cette nouvelle API pour 2 raisons :

  1. Mon système de domotique actuel utilise la Freebox pour le control de la Télé. Pour le moment il utilise la vieille API :
    def sendMsgToFreebox(self,key):
    url = "http://hd1.freebox.fr/pub/remote_control?" + "key=" + key + "&code=59999459"
    logging.error("sending : " + url)
    reponse = urllib.urlopen(url)
    logging.error("reponse : " + str(reponse))

    Cette API sera peut-être remplace par la nouvelle version et je serais donc obliger de migrer

  2. Je veux ajouter le control du wifi sur le système domotique pour pouvoir allumer ou éteindre le wifi. Le but final étant un poil plus complexe.

Je veux créer une application android qui allume le wifi de l’appartement en même temps que le wifi de la tablette. Plus exactement l’application android devra envoyer un message au système domotique pour allumer le wifi. Le système allumera le wifi s’il n’est pas déjà en route.

Ensuite le système domotique monitor le nombre de client utilisant le wifi en temps réel. Il peut également prendre la décision de couper le wifi si aucun client est connecte depuis un certain temps. Le but de cette application est de couper le wifi quand il n’est pas utilisé pour réduire les ondes présentes dans l’appartement.

Dans un second temps je souhaite ajouter un bouton prêt de la Freebox pour pourvoir effectuer une demande d’allumage du wifi (comme le fait l’application android) pour les tablettes qui n’ont pas de connexion 3G et ne peuvent donc pas envoyer la demande d’allumage wifi par la 3G.

L’image ci-dessous résume le flow :

AutoWifi

1 – L’utilisateur active le wifi sur sa tablette Wifi/3G. L’application envoie une demande d’activation Wifi au Raspberry Pi en utilisant le réseau 3G

OU

1bis – L’utilisateur appuie sur le bouton d’activation du wifi sur le Raspberry Pi

2 – Le système domotique héberge sur le Raspberry Pi envoie une demande à la Freebox d’activation du wifi si le wifi n’est pas déjà actif

3 – L’utilisateur se connecte automatiquement au wifi et peut maintenant utilise le réseau wifi pour aller sur internet

4 – Le système domotique monitor régulièrement le nombre d’utilisateur wifi. Si le nombre de device est de 0 depuis plus de X temps (1 heure par défaut) le système éteint le wifi de la Freebox en utilisant l’API.

Le système complet ne fonctionne pas encore. Pour le moment j’arrive uniquement à me logger sur la Freebox (système de challenge avec token). Je continuerai les étapes suivantes les WE prochains et j’updaterai l’article. Voila le code actuel :

#!/usr/bin/python
# -*- coding: utf-8 -*-

import requests
import logging
import time
import itertools
import json
import os
import sys

from hashlib import sha1
import hmac

class FreeboxApplication:
'''Represents an application which interact with freebox server
API doc : http://dev.freebox.fr/sdk/os/'''

def __init__(self):
#I kept the same parameter name than the one use in freebox API for more readability
self.app_id="DomosId"
self.app_name="DomosApp"
self.app_version="1"
self.device_name="DomosDeviceName"
#To know if the APP is register on freeboxOS side
self.registerIntoFreeboxServer=False
#Registration parameters
self.app_token=""
self.track_id=""
self.challenge=""
self.loadAppTokenFromFile()

def __repr__(self):
aRetString = ""
aRetString = aRetString + "self.app_id: " + str(self.app_id)
aRetString = aRetString + "self.app_name: " + str(self.app_name)
aRetString = aRetString + "self.app_version: " + str(self.app_version)
aRetString = aRetString + "self.device_name: " + str(self.device_name)
aRetString = aRetString + "self.registerIntoFreeboxServer: " + str(self.registerIntoFreeboxServer)
aRetString = aRetString + "self.app_token: " + str(self.app_token)
aRetString = aRetString + "self.track_id: " + str(self.track_id)
aRetString = aRetString + "self.challenge: " + str(self.challenge)
return aRetString

def getataForRequests(self):
return json.dumps({"app_id": self.app_id,"app_name": self.app_name,"app_version": self.app_version,"device_name": self.device_name})

def loadAppTokenFromFile(self):
#Degeu...
if (os.path.isfile("AppToken.txt")):
aAppTokenBackupFile = open("AppToken.txt", "r")
self.app_token = aAppTokenBackupFile.read()
logging.info("APP token read from file. New APP token is : " + str(self.app_token))
aAppTokenBackupFile.close()
else:
logging.info("No file for APP token - request a new one")
self.initialLogging()
#Fin Degeu

def initialLogging(self):
#only once. Register the APP on freebox side
logging.info("Starting initial registration")
aRequestUrl = "http://mafreebox.freebox.fr/api/v1/login/authorize/"
aHeaders = {'Content-type': 'application/json', 'Accept': 'application/json'}

logging.debug("URL used : " + aRequestUrl)
logging.debug("Datas used : " + str(self.getataForRequests()))

aRequestResult = requests.post(aRequestUrl, data=self.getataForRequests(), headers=aHeaders)
logging.debug("Request result : " + str(aRequestResult))
logging.debug("Request result : " + str(aRequestResult.json()))
logging.debug("Registration result : " + str(aRequestResult.json()['success']))

#if (aRequestResult.status_code != "200") or (aRequestResult.json()['success'] != True):
if (aRequestResult.status_code != requests.codes.ok) or (aRequestResult.json()['success'] != True):
logging.critical("Error during intial registration into Freebox Server")
else:
logging.debug("Please go to your Freebox. There should be a message saying that an application request access to freebox API. Please validate the request using the front display")
self.app_token = aRequestResult.json()['result']['app_token']
self.track_id = aRequestResult.json()['result']['track_id']
logging.debug("app_token : " + str(self.app_token))
logging.debug("track_id : " + str(self.track_id))
logging.info("Ending initial registration")

aLoopInd = 0
while ((self.registerIntoFreeboxServer != True) and (aLoopInd < 10)):
self.trackRegristration()
time.sleep(15) # Delay for 1 minute (60 seconds)
aLoopInd = aLoopInd + 1
if (self.registerIntoFreeboxServer != True):
logging.critical("Initial registration fails - Exiting with error")
sys.exit(1)
else:
#Degeu...
aAppTokenBackupFile = open("AppToken.txt", "w")
aAppTokenBackupFile.write(self.app_token)
aAppTokenBackupFile.close()
#Fin Degeu

def trackRegristration(self):
logging.info("Starting trackRegristration")
aRequestUrl = "http://mafreebox.freebox.fr/api/v1/login/authorize/" + str(self.track_id)
aHeaders = {'Content-type': 'application/json', 'Accept': 'application/json'}

logging.debug("URL used : " + aRequestUrl)

aRequestResult = requests.get(aRequestUrl, headers=aHeaders)
logging.debug("Request result : " + str(aRequestResult))
logging.debug("Request result : " + str(aRequestResult.json()))
if (aRequestResult.status_code != requests.codes.ok):
logging.critical("Error during trackRegristration")
else:
if (aRequestResult.json()['result']['status'] == "granted"):
logging.debug("OK during trackRegristration")
self.registerIntoFreeboxServer=True
self.challenge=aRequestResult.json()['result']['challenge']
logging.info("APP is correclty registered")
logging.info("Ending trackRegristration")

def logWithPassword(self, iPassword):
#only once. Register the APP on freebox side
logging.info("Starting logWithPassword")
aRequestUrl = "http://mafreebox.freebox.fr/api/v1/login/session/"
aHeaders = {'Content-type': 'application/json', 'Accept': 'application/json'}

logging.debug("URL used : " + aRequestUrl)

aDataToLog = json.dumps({"app_id": self.app_id,"password": iPassword})

logging.debug("Datas used : " + str(aDataToLog))

aRequestResult = requests.post(aRequestUrl, data=aDataToLog, headers=aHeaders)
logging.debug("Request result : " + str(aRequestResult))
logging.debug("Request result : " + str(aRequestResult.json()))
logging.debug("Registration result : " + str(aRequestResult.json()['success']))

#if (aRequestResult.status_code != "200") or (aRequestResult.json()['success'] != True):
if (aRequestResult.status_code != requests.codes.ok) or (aRequestResult.json()['success'] != True):
logging.critical("Error during intial registration into Freebox Server")
else:
logging.debug("You re log")
logging.info("Ending logWithPassword")

def loginProcedure(self):
logging.info("Starting loginProcedure")
aRequestUrl = "http://mafreebox.freebox.fr/api/v1/login/"
aHeaders = {'Content-type': 'application/json', 'Accept': 'application/json'}

logging.debug("URL used : " + aRequestUrl)

aRequestResult = requests.get(aRequestUrl, headers=aHeaders)
logging.debug("Request result : " + str(aRequestResult))
logging.debug("Request result : " + str(aRequestResult.json()))
if (aRequestResult.status_code != requests.codes.ok):
logging.critical("Error during loginProcedure")
else:
if (aRequestResult.json()['success'] == True):
logging.debug("OK during loginProcedure")
achallenge=aRequestResult.json()['result']['challenge']
logging.info("We have the challenge : " + str(achallenge))
return achallenge
else:
logging.critical("Error during loginProcedure")
logging.info("Ending loginProcedure")

def computePassword(self, iChallenge):
hashed = hmac.new(self.app_token, iChallenge, sha1)
logging.info("Password computed : " + str(hashed.digest().encode('hex')))
return hashed.digest().encode('hex')

def loginfull(self):
aNewChallenge = self.loginProcedure()
#password = hmac-sha1(app_token, challenge)
#voir http://stackoverflow.com/questions/8338661/implementaion-hmac-sha1-in-python
#http://stackoverflow.com/questions/13019598/python-hmac-sha1-vs-java-hmac-sha1-different-results
aPassword = self.computePassword(aNewChallenge)
self.logWithPassword(aPassword)

print ("Starting")

aLogFileToUse='WifiAutoControl.log'

#Clean previous log file
with open(aLogFileToUse, 'w'):
pass

logging.basicConfig(filename=aLogFileToUse,level=logging.DEBUG,format='%(asctime)s - %(levelname)s - %(message)s')

aMyApp = FreeboxApplication()

aMyApp.loginfull()

print ("Ending")

L’ensemble du code est dispo sur mon dépôt de domotique.

Update 3 octobre :

Une fois logger on peut utiliser l’API mais il n’y a pas bcp de permission setter a ON par défaut. Il faut mieux aller dans l OS freebox et tout autoriser avant de poursuivre les tests :

FreeboxOs permissions

Ensuite on verifie que les permissions sont bien prise en compte. Pour cela on refait une demande de login et on regarde la reponse :

2013-10-03 19:40:08,070 - DEBUG - Request result : {u'result': 
{u'challenge': u'7IxxxxxxxxxxxwP+cb', u'password_salt': u'uALF+MxxxxxxxxqYi',
 u'permissions': {u'contacts': True, u'settings': True, u'explorer': True,
 u'calls': True, u'downloader': True}, u'session_token': u'K6jjKxxxxxxxxxxxxxxxxxxxxxWCq'}, u'success': True}

Maintenant les permissions sont toutes a True….. On peut continuer. J’ai changer le script pour récupérer la liste des appareils connecte au wifi. J’ai beaucoup de chance car il y a également des infos sur les dernières connexion et je pourrai donc faire un système un peu plus intelligent pour couper le wifi même si il y a des appareil connecte du moment qu ils n’utilisent pas réellement le wifi.

La dernière version est dispo sur mon dépôt de domotique et liste les appareils wifi connecte (et leur nombre). Elle ne coupe/active pas encore le wifi….a suivre

Detection visage en Python avec OpenCV et camera IP

J’ai ajoute une webcam dans le salon qui stream en direct sur internet mais le but est de détecter les personnes pressentes dans le salon et de pouvoir créer des actions plus intelligentes que celle que j’ai aujourd’hui.

Actuellement il y a une platine Arduino avec un détecteur de présence, température, humidité dans le salon. Le système est donc capable de détecter les personnes mais il ne peut qu’ouvrir les volets pou allumer une lumière.

Ce que je veux faire maintenant est de détecter les personnes présentes dans le salon et en fonction de leur attitude le système effectuera des actions plus intelligentes. Par exemple si quelqu’un se met sur le canapé face a la télévision…..alors on allume la télévision automatiquement 😉

La première étape a été l achat d’une camera IP low cost. J’ai choisit ce modèle car d’après les forums que j’ai pu voir leur framework est plutôt ouvert et on peut accéder au flux de la camera facilement depuis openCV :

cameraIP

Foscam FI8910W

Ensuite… on trouve pas mal de tuto sur google pour utiliser openCV et les cameraIP (il suffit de mixer les 2 codes 😉 ). OpenCV s’installe très facilement sur Ubuntu et s’interface plutôt facilement avec python grâce a la libraire CV2 (ne pas utiliser CV).

Voila le code fonctionnel :

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cv2, math
import numpy as np

cv2.namedWindow("preview")
#vc = cv2.VideoCapture("http://192.168.0.9/videostream.asf?user=guest&pwd=guest")
vc = cv2.VideoCapture(0)

if vc.isOpened(): # try to get the first frame
    rval, frame = vc.read()
else:
    rval = False

while rval:
    cv2.imshow("preview", frame)
    rval, frame = vc.read()

    hc = cv2.CascadeClassifier("/usr/share/opencv/haarcascades/haarcascade_frontalface_alt2.xml")
    faces = hc.detectMultiScale(frame)
    for face in faces:
        cv2.rectangle(frame, (face[0], face[1]), (face[0] + face[2], face[0] + face[3]), (255, 0, 0), 3)

    key = cv2.waitKey(20)
    if key == 27: # exit on ESC
        break

Il faut juste faire attention que le fichier xml de filtre existe bien (sinon le telecharger sur internet et changer la path). Le résultat avec mon colloc 😉

OpenCv_FaceDetection

Étape suivante…..porter tout ça sur Raspbian….et je sens que ça va être pénible. Ensuite il faudra être capable de dire ce que font les personnes présentes dans le salon….

 

 

Reconnaissance vocale

Création d’une application android pour le système de domotique qui sera capable de reconnaitre la parole pour actionner certaines actions. Le but est de remplacer “Tasker + auto voice” par une application fait maison et plus plus adapte au système.

on utilise l API google et c est google qui fait tout le boulot de décodage de la parole (et plutôt bien) :

public void speak(View view) {
 Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
// Specify the calling package to identify your application
 intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, getClass().getPackage().getName());
// Given an hint to the recognizer about what the user is going to say
 //There are two form of language model available
 //1.LANGUAGE_MODEL_WEB_SEARCH : For short phrases
 //2.LANGUAGE_MODEL_FREE_FORM  : If not sure about the words or phrases and its domain.
 intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
// Specify how many results you want to receive. The results will be sorted where the first result is the one with higher confidence.
 int noOfMatches = 4;
 intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, noOfMatches);
 //Start the Voice recognizer activity for the result.
 startActivityForResult(intent, VOICE_RECOGNITION_REQUEST_CODE);
 }

et on attend la réponse de l’activité :

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 if (requestCode == VOICE_RECOGNITION_REQUEST_CODE)
//If Voice recognition is successful then it returns RESULT_OK
 if(resultCode == RESULT_OK) {
ArrayList<java.lang.String> textMatchList = data
 .getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
if (!textMatchList.isEmpty()) {
 mlvTextMatches
 .setAdapter(new ArrayAdapter<java.lang.String>(this,
 android.R.layout.simple_list_item_1,
 textMatchList));
 sendMsgToServer(textMatchList);
}

ensuite avec les résultats (plusieurs phrases correspondant a ce que google a décoder) on appel une fonction “sendMsgToServer” qui va envoyer ses phrases aux serveur python qui gère tout la centrale domotique.

public void sendMsgToServer(ArrayList<java.lang.String> aStrings){
String url = "http://192.168.0.6/Sender/XbeeWrapper.php?iCmdType=CMD_SPEAK&iCmdStrings=";
 String aCmd = combine(aStrings,"_");
 url = url + aCmd;
 url = url.replace(' ','-');
 url = Normalizer.normalize(url, Normalizer.Form.NFD);
 url = url.replaceAll("[^\\p{ASCII}]", "");
 grabURL(url);
 }

Attention : pour faire cela on utilise une autre classe qui dérive de classe asynch car dans android toutes les opérations longues doivent être faites ailleurs que dans le thread principale.

private class GrabURL extends AsyncTask<String, Void, Void> {

je vais pas détailler ici pkoi et comment car il existe une multitude de référence sur le sujet sur internet. Bref le serveur python reçoit les phrases et parcours tous les objets du réseaux domotique pour savoir auquel est destine la commande :

def TranslateVocalAction(self,Sentences, Devices):
 logging.warning("Translate : " + str(Sentences))
 #Weight, Device ID, CommandID
 aBestDeviceMatch=(0,0,0)
 for aOneObj in Devices.registeredDevices:
 (aWeight,aCmdId)=aOneObj.getDeviceWeightSpeech(Sentences)
 logging.warning ("DeviceId : " + str(aOneObj.id) + " has weight : " + str(aWeight))
 if(aWeight > aBestDeviceMatch[0]):
 aBestDeviceMatch=(aWeight,aOneObj.id,aCmdId)
 logging.warning("Device is : " + str(aBestDeviceMatch))
 self.SendMessage( str(aBestDeviceMatch[2]),"VoiceAction",Devices)
 return (Devices.getDevice(aBestDeviceMatch[1]).description,aBestDeviceMatch[2])

Pour cela il parcours la description et les commandes possible de tous les objets qui se présentes sous la forme :

VoletSalon = InterupteurMultiStable()
 VoletSalon.id =5
 VoletSalon.description="volets salon"
 VoletSalon.InPossibleCmd ={ "10" : "off fermer ferme","9" : "on ouvre ouvrir","25" : "stop arreter arrete"}
 VoletSalon.InActionsCommands={ "9" : "self.turnOn(9)","10" : "self.turnOff(10)","25" : "self.turnOff(10)"}
 self.registeredDevices.append(VoletSalon)
LumiereSalonHalogene = InterupteurBiStable()
 LumiereSalonHalogene.id =6
 LumiereSalonHalogene.description="lumiere halogene salon"
 LumiereSalonHalogene.InPossibleCmd ={ "13" : "off eteint eteindre","14" : "on allume allumer"}
 LumiereSalonHalogene.InActionsCommands={ "13" : "self.turnOff(13)","14" : "self.turnOn(14)"}
 self.registeredDevices.append(LumiereSalonHalogene)
....

et donne un poids pour chaque objet en fonction de sa description et de ses possibles commandes. Le poids dépend du nombre de mots commun entre les strings et les mots présent dans la phrase (pondéré par leur longueur)

Ensuite il exécute la commande qui a le poids le plus grand. C’est assez facile en fin de compte. En parallèle le serveur python renvoi la commande exécuté et l’objet associe :

return (Devices.getDevice(aBestDeviceMatch[1]).description,aBestDeviceMatch[2])

Ces infos sont ensuite affiches sur la UI du telephonne portable pour que l utilisateur puisse vérifier :

protected void onPostExecute(Void unused) {
 Dialog.dismiss();
 if (Error != null) {
 Toast.makeText(VoiceRecognitionActivity.this, Error, Toast.LENGTH_LONG).show();
 } else {
 Toast.makeText(VoiceRecognitionActivity.this, "Source: " + Content, Toast.LENGTH_LONG).show();
 }
 }

Je ne détaille pas bcp plus car tous le code est tjs visible au même endroit sur le dépôt central de mon projet domotique et assez court pour être compris facilement.

Je ferais une vidéo du système en action ASAP.