Pilotage de la Freebox Révolution en python

J’ai découvert récemment que la Freebox Révolution exposait une API donnant accès à beaucoup de fonctionnalités intéressantes. Je m’en suis servi pour ajouter à ma tablette murale la capacité d’activer et désactiver la planification du wifi.

 

Mon besoin

Mon besoin était le suivant : J’utilise la planification du wifi offerte par la Freebox pour couper le wifi pendant les repas, ce n’est pas tant l’usage du wifi que je voulais couper (car mes enfants n’ont pas encore l’age d’utiliser Internet) mais surtout l’exposition aux ondes (ma box est très près de la table dans la cuisine). Mais une fois le repas terminé, je réactivais souvent le wifi en désactivant la planification via l’appli mobile Freebox Compagnon, en oubliant parfois de réactiver la planification…

J’ai donc ajouté à l’interface de ma tablette dans la cuisine un bouton me permettant de désactiver la planification du wifi (et ainsi réactiver le wifi). Une tâche cron régulière (10 min) se chargeant ensuite de réactiver la planification dès la fin de la période de coupure du wifi.

 

Présentation de l’API

Les fonctionnalités offertes par l’API son très riches. Cela va de la gestion des téléchargements, des fichiers stockés sur le disque dur interne à la configuration des paramètres de la freebox (serveur FTP, firewall, port forwarding ou ce qui nous intéresse ici le Wifi).

L’API est un webservice de type RESTful dont l’url sur le réseau local est https://mafreebox.freebox.fr/api/v4/.

v4 étant la version de l’API à la date d’écriture de cet article.

 

Enregistrement de notre application auprès de notre Freebox

La première étape pour effectuer des requêtes RESTful à notre Freebox est de déclarer notre application auprès de celle-ci. Ceci consiste à renseigner un app_id, un app_name et un device_name pour obtenir en retour un app_token qui sera utilisé lors des requêtes en guise d’authentification.  Nous devrons activer cet app_token en effectuant une manipulation physique sur la Freebox).

Voici le code que j’ai écris en Python 3 pour créer un accès à mon application dans la Freebox :

import urllib3
import requests
import json
import hmac
import hashlib
import pickle
import time
import datetime

fbx_url = "https://mafreebox.freebox.fr/api/v4/"

token=''
id = ''

app_id='fr.freebox.fbxpy'
app_name = 'Fbxpy'
app_version = '1'
device_name  'pi'

def fancy_print(data):
    print(json.dumps(data,indent=2,separators=(',', ': ')))

def connexion_post(method,data=None,headers={}):
    url = fbx_url + method
    if data: data = json.dumps(data)
    return json.loads(requests.post(url, data=data, headers=headers).text)

def register():
    global token,id
    payload = {'app_id': app_id, 'app_name': app_name, 'app_version': app_version, 'device_name': device_name}
    content=connexion_post('login/authorize/',payload)
    fancy_print(content)
    token=str(content["result"]["app_token"])
    app_id=str(content["result"]["track_id"])

register()

 

L’exécution de la méthode register va déclarer notre application Fbxpi à la Freebox et afficher l’app_token ainsi reçu. Conservez le, nous en aurons besoin par la suite. Pour terminer l’autorisation d’accès à l’API par notre application il faut se rendre physiquement sur la Freebox Révolution. Celle-ci affiche un message demandant si elle doit autoriser l’accès à l’API à notre application.

Demande activation Freebox

 

Ceci fait, on peut vérifier la création de notre accès sur l’interface web de gestion de la Freebox (Paramètres de la Freebox > Gestion des accès).

 

Authentification

Notre accès à l’API est créé et autorisé. Mais avant de pouvoir effectuer des requêtes via l’API nous devons utiliser le token reçu pour demander un session_token. Celui ci sera utilisé dans chacune de nos requêtes. Ce session_token est limité dans le temps, il faudra en redemander un à son expiration.

La méthode mksession() suivante permet de créer ce session_token et le conserver pour nos requêtes futures :

token = '' # Collez ici le token reçu précédemment

def mksession():
    challenge=str(connexion_get("login/")["result"]["challenge"])
    token_bytes= bytes(token , 'latin-1')
    challenge_bytes = bytes(challenge, 'latin-1')
    password = hmac.new(token_bytes,challenge_bytes,hashlib.sha1).hexdigest()
    data={
          "app_id": app_id,
           "app_version": app_version,
        "password": password
    }
    content = connexion_post("login/session/",data)
    return content["result"]["session_token"]

 

Cette méthode sera à appeler au début de notre programme.

 

Pilotage du wifi de la Freebox

Passons aux choses sérieuses et utilisons tout ceci pour piloter le wifi de notre Freebox :

def getWifiPlanningState(session_token):
    try:
        method = "/wifi/planning/"
        resultat =  connexion_get(method, headers={"X-Fbx-App-Auth": session_token})
        #fancy_print(resultat)
        if resultat["result"]["use_planning"]:
            return 'true'
        else:
            return 'false'
    except:
        return 'unknown'

def getWifiState(session_token):
    try:
        method = "wifi/ap/"
        resultat =  connexion_get(method, headers={"X-Fbx-App-Auth": session_token})
        #fancy_print(resultat)
        if resultat["result"][0]["status"]["state"] == 'active':
            if getWifiPlanningState(session_token) == 'true':
                return 'active_planif'
            else:
                return 'active'
        return resultat["result"][0]["status"]["state"]
    except:
        return 'unknown'

session_token = mksession()
etat=getWifiState(session_token)
closesession(session_token)

 

Notre méthode getWifiState retournera :

  • active si le wifi est activé
  • active_planif si le wifi est activé et que la planification est également active (et donc que nous sommes dans une plage horaire où le wifi doit être activé)
  • disabled si le wifi est désactivé
  • scanning si le wifi vient d’être activé et scanne les différents canaux en vue de chercher les meilleurs canaux non encombrés
  • no_param si le wifi n’est pas configuré
  • bad_param si le wifi est mal configuré
  • disabled_planning si le wifi est désactivé du fait de l’application de la planification
  • no_active_bss si le BSS configuré n’est pas trouvé
  • starting si le wifi est en phase d’activation
  • acs si le wifi est en train de chercher les meilleurs canaux non encombrés
  • ht_scan si le wifi est en train de chercher à se connecter à un autre point d’accès
  • dfs si le wifi est en train de sélectionner le bon jeu de fréquences
  • failed si l’activation du wifi a échouée

Je vous recommande de décommenter la ligne fancy_print(resultat) qui permet de visualiser la réponse brute reçue de l’API.

En fonction de l’état du wifi nous pouvons demander à désactiver la planification du wifi pour forcer son activation si nous sommes dans une plage horaire sans wifi :

def setWifiPlanningState(session_token,boolean):
    try:
        method = "/wifi/planning/"
        data = {
            "use_planning" : boolean
        }
        resultat =  connexion_put(method, data = data, headers={"X-Fbx-App-Auth": session_token})
        #fancy_print(resultat)
        if resultat["success"]:
            if resultat["result"]["use_planning"]:
                return 'true'
            else:
                return 'false'
        else:
            return 'false'
    except:
        return 'unknown'

session_token = mksession()
setWifiPlanningState(session_token,False)
closesession(session_token)

 

C’est cette méthode qui est déclenchée via ma tablette quand je veux réactiver le wifi. Une tâche cron se chargeant de réactiver le wifi lors de la fin de la plage horaire sans wifi.

Voici l’ensemble final que j’ai placé dans un module nommé Fbxpy.py :

import urllib3
import requests
import json
import hmac
import hashlib
import pickle
import time
import datetime

fbx_url = "https://mafreebox.freebox.fr/api/v4/"

token = '' # Collez ici le token reçu précédemment

app_id='fr.freebox.fbxpy'
app_name = 'Fbxpy'
app_version = '1'
device_name  'pi'

def fancy_print(data):
    print(json.dumps(data,indent=2,separators=(',', ': ')))

def connexion_post(method,data=None,headers={}):
    url = fbx_url + method
    if data: data = json.dumps(data)
    return json.loads(requests.post(url, data=data, headers=headers).text)

def connexion_get(method, headers={}):
    url = fbx_url + method
    return json.loads(requests.get(url, headers=headers).text)

def connexion_put(method,data=None,headers={}):
    url = fbx_url + method
    if data: data = json.dumps(data)
    return json.loads(requests.put(url, data=data, headers=headers).text)

def mksession():
    challenge=str(connexion_get("login/")["result"]["challenge"])
    token_bytes= bytes(token , 'latin-1')
    challenge_bytes = bytes(challenge, 'latin-1')
    password = hmac.new(token_bytes,challenge_bytes,hashlib.sha1).hexdigest()
    data={
          "app_id": app_id,
           "app_version": app_version,
        "password": password
    }
    content = connexion_post("login/session/",data)
    return content["result"]["session_token"]

def closesession(session_token):
    connexion_post('login/logout/', headers={"X-Fbx-App-Auth": session_token})

file_wifiplanif='/.../wifiplanif' # à modifier selon votre besoin

def loadWifiplanif():
    try:
        infile = open(file_wifiplanif,'rb')
        wifiplanif = pickle.load(infile)
        infile.close()
        return wifiplanif
    except:
        time.sleep(5)
        return loadWifiplanif()

def saveWifiplanif(wifiplanif):
    try:
        outfile = open(file_wifiplanif,'wb')
        pickle.dump(wifiplanif,outfile)
        outfile.close()
    except:
        time.sleep(5)
        saveWifiplanif(wifiplanif)

def getWifiPlanningState(session_token):
    try:
        method = "/wifi/planning/"
        resultat =  connexion_get(method, headers={"X-Fbx-App-Auth": session_token})
        #fancy_print(resultat)
        if resultat["result"]["use_planning"]:
            return 'true'
        else:
            return 'false'
    except:
        return 'unknown'

def getWifiState(session_token):
    try:
        method = "wifi/ap/"
        resultat =  connexion_get(method, headers={"X-Fbx-App-Auth": session_token})
        #fancy_print(resultat)
        if resultat["result"][0]["status"]["state"] == 'active':
            if getWifiPlanningState(session_token) == 'true':
                return 'active_planif'
            else:
                return 'active'
        return resultat["result"][0]["status"]["state"]
    except:
        return 'unknown'

def isWifiMustEnableByPlanning(session_token):
    try:
        method = "/wifi/planning/"
        resultat =  connexion_get(method, headers={"X-Fbx-App-Auth": session_token})
        #fancy_print(resultat)
        date = datetime.datetime.now()
        interval = date.weekday() * 48 + date.hour * 2 + (int(date.minute)//30)
        return (resultat["result"]["mapping"][interval] == "on")
    except:
        return 'unknown'

 

et la tâche cron associée :

from Fbxpy import *

session_token = mksession()

if (loadWifiplanif() and isWifiMustEnableByPlanning(session_token)):
    if setWifiPlanningState(session_token,True):
        saveWifiplanif(False)
 
closesession(session_token)

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *