Récupérer les stats internet de votre box fibre SFR 7 (GR140CG)

Bonjour,

Suite à changement de ma box SFR, ce que j’avais mis en place ne fonctionnait plus.
Et je suis tombé sur ça.
Puis, avec l’aide précieuse de mon “pote” Claude j’ai enfin pu mettre au point ce script python, statsbox.py :

#!/usr/bin/env python3

import hashlib
import hmac
import requests
import json
import sys
import argparse

# Configuration par défaut
BOX_URL = "http://xx.xx.xx.xx"
USERNAME = "xxxxxxx"
PASSWORD = "xxxxxxxx"

def login(base_url, username, password):
    """Login to the box and return authenticated session"""
    s = requests.Session()
    
    # Get nonce
    r = s.get(base_url + "/uxfwk.session.loader.js")
    
    sessionid = None
    nonce = None
    for line in r.text.split('\n'):
        if 'sessionid' in line.lower():
            sessionid = line.split(':')[1].strip().strip('"').strip("'").strip(',').strip(';').strip()
            #print(f"DEBUG: Found sessionid line: {line.strip()}", file=sys.stderr)
        if 'nonce' in line.lower():
            # Parse: nonce: 'ABC123',
            raw = line.split(':')[1].strip()  # " 'ABC123',"
            nonce = raw.strip(',').strip(';').strip().strip('"').strip("'").strip()  # "ABC123"
            #print(f"DEBUG: Found nonce line: {line.strip()}", file=sys.stderr)
            #print(f"DEBUG: Raw nonce after split: '{raw}'", file=sys.stderr)
    
    #print(f"DEBUG: Parsed sessionid = {sessionid}", file=sys.stderr)
    #print(f"DEBUG: Parsed nonce = '{nonce}'", file=sys.stderr)
    #print(f"DEBUG: Nonce length = {len(nonce) if nonce else 0}", file=sys.stderr)
    
    if not nonce:
        raise Exception("Failed to get nonce")
    
    # Compute hash
    sha256_usr = hashlib.sha256(username.encode('UTF-8')).hexdigest()
    hmac_usr = hmac.new(bytes(nonce, 'UTF-8'), digestmod='sha256')
    hmac_usr.update(sha256_usr.encode())
    
    sha256_pass = hashlib.sha256(password.encode('UTF-8')).hexdigest()
    hmac_pass = hmac.new(bytes(nonce, 'UTF-8'), digestmod='sha256')
    hmac_pass.update(sha256_pass.encode())
    
    credentials = hashlib.sha256(bytes(hmac_usr.hexdigest() + hmac_pass.hexdigest(), 'UTF-8')).hexdigest()
    #print(f"DEBUG: Credentials hash = {credentials}", file=sys.stderr)
    s.headers['Authorization'] = 'Digest ' + credentials
    
    # Get XSRF token
    r = s.get(base_url + "/index.html")
    
    # Debug: afficher ce qu'on reçoit
    #print(f"DEBUG: Response status = {r.status_code}", file=sys.stderr)
    #print(f"DEBUG: Response headers = {dict(r.headers)}", file=sys.stderr)
    #print(f"DEBUG: Cookies = {[(c.name, c.value) for c in s.cookies]}", file=sys.stderr)
    
    if 'X-XSRF-TOKEN' in r.headers:
        s.headers['X-XSRF-TOKEN'] = r.headers['X-XSRF-TOKEN']
        #print(f"DEBUG: XSRF token found in headers", file=sys.stderr)
    elif 'XSRF-TOKEN' in s.cookies:
        s.headers['X-XSRF-TOKEN'] = s.cookies['XSRF-TOKEN']
        #print(f"DEBUG: XSRF token found in cookies", file=sys.stderr)
    else:
        print(f"ERROR: XSRF token not found!", file=sys.stderr)
        raise Exception("Failed to get XSRF token")
    
    return s

def get_wan_stats(session, base_url):
    """Get WAN statistics (rx_b and tx_b)"""
    r = session.get(base_url + '/ss-json/fgw.wan/fgw.wanstatistics.json')
    data = json.loads(r.text)
    
    if 'statistics' in data and len(data['statistics']) > 0:
        stats = data['statistics'][0]
        return {
            'rx_bytes': stats.get('rx_b', 0),
            'tx_bytes': stats.get('tx_b', 0)
        }
    return None

def logout(session, base_url):
    """Logout from the box"""
    try:
        session.get(base_url + '/logout.cmd')
    except:
        pass  # Ignore logout errors

def main():
    # Parse command line arguments
    parser = argparse.ArgumentParser(description='Get SFR Box WAN statistics')
    parser.add_argument('-u', '--user', default=USERNAME, help='Username (default: admin)')
    parser.add_argument('-p', '--password', default=PASSWORD, help='Password')
    parser.add_argument('--url', default=BOX_URL, help='Box URL (default: http://192.168.1.1)')
    args = parser.parse_args()
    
    try:
        # Login
        session = login(args.url, args.user, args.password)
        
        # Get current stats
        stats = get_wan_stats(session, args.url)
        if not stats:
            logout(session, args.url)
            print("ERROR: Could not retrieve WAN statistics", file=sys.stderr)
            sys.exit(1)
        
        # Logout
        logout(session, args.url)
        
        # Output raw counters in JSON format
        print(json.dumps({
            'rx_bytes': stats['rx_bytes'],
            'tx_bytes': stats['tx_bytes']
        }))
        
    except Exception as e:
        print(f"ERROR: {e}", file=sys.stderr)
        sys.exit(1)

if __name__ == '__main__':
    main()

A placer au même endroit que vos autres scripts python que vous utilisez déjà [peut-être …] (pour mon cas, c’est dans /config/python_scripts).
Et n’oubliez pas de placer les bonnes valeurs dans les variables BOX_URL, USERNAME et PASSWORD.

Il faut ajouter ceci dans votre config.yaml :

command_line:
# récup des stats WAN rx et tx de la box SFR
  - sensor:
      name: "box_wan_stats"
      command: "python /config/python_scripts/statsbox.py"
      value_template: "OK"
      json_attributes:
        - rx_bytes
        - tx_bytes
      scan_interval: 60  # Toutes les minutes

template:
# récup du débit IN & OUT du lien WAN de la box SFR
  - sensor:
      - name: "BOX WAN RX Bytes"
        unique_id: box_wan_rx_bytes
        unit_of_measurement: "B"
        device_class: data_size
        state_class: total_increasing
        icon: mdi:download-network
        state: "{{ state_attr('sensor.box_wan_stats', 'rx_bytes') | int(0) }}"
      - name: "BOX WAN TX Bytes"
        unique_id: box_wan_tx_bytes
        unit_of_measurement: "B"
        device_class: data_size
        state_class: total_increasing
        icon: mdi:upload-network
        state: "{{ state_attr('sensor.box_wan_stats', 'tx_bytes') | int(0) }}"

En option, à des fin de test et debug :

shell_command:
  get_box_stats: "python3 /config/python_scripts/statsbox.py  >/config/tmp/box_stats.json 2>/config/tmp/box_stats_error.log"

N’oubliez pas de créer le répertoire tmp dans /config.

Puis un redémarrage de HA pour faire apparaitre 3 nouveaux sensors :

  • sensor.box_wan_stats
  • sensor.box_wan_rx_bytes
  • sensor.box_wan_tx_bytes

Pour info, les valeurs rx et tx représentent le cumul d’octets envoyés ou reçus par l’interface fibre. Et au delà de 4Go, ça repart depuis 0. Là, le relevé de ce cumul est donc réalisé toutes les 5 minutes.

Ensuite, à vous de décider du moyen pour tracer les courbes. Pour ma part, tout va dans InfluxDB v2 puis Grafana fait le boulot.

Le script python fonctionne bien avec un box SFR fibre v7, modèle GR140CG, avec le firmware 3ENT010301r008. Une éventuelle maj de ce dernier pourrait le rendre inopérant.

D’autres infos peuvent être récupérées, en fait toutes celles que vous avez via l’interface web de la box. Mais j’ai aussi l’intégration SFR qui complète.

A bientôt.

1 « J'aime »

J’ai corrigé le script python vers la fin, car rentrer une valeur 0 si on n’arrive pas à récupérer les stats de la box fausse tout.

Je lance maintenant toutes les minutes au lieu de 5.

Et au bout de plusieurs semaines, au vu du résultat très faible des chiffres, je me pose la question de la justesse des données retournées par la box sur les stats wan : le cumul journalier en réception tourne aux alentours de 500Mo par jour, ce qui est ridicule au vu de tout le bazar qui est sur mon réseau domestique

L’archi de mon réseau domestique fait que tout le trafic internet de la box SFR passe par son port LAN4 et va sur un routeur Netgear. J’ai donc complété le script python pour récupérer les stats du port LAN4. Et le verdict est tombé : lorsque j’ai un trafic de 80Mb/s sur le LAN4 (DL d’une ISO de 6Go), j’ai un trafic de 100Ko/s sur le WAN …

Bref, les stats WAN de la box fibre SFR7 sont, à ce jour, totalement pourries.