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