Compare commits

...

No commits in common. "b969b8fb81f344bff3dcd2f5cfa44d3e246d4017" and "master" have entirely different histories.

2 changed files with 448 additions and 342 deletions

View File

@ -1,13 +1,13 @@
# What's this? # Qu'est-ce que c'est ?
This is a set of libraries to control your Home by SFR account. Il s'agit d'une bibliothèque qui permet d'accéder au système Home by SFR.
They all work around the website. Le code s'appuie sur les appels fait sur le [site](https://home.sfr.fr). De fait, sa conception a été faite par rétro-ingénérie et peut cesser de fonctionner sans préavis.
# What's Home by SFR? # Qu'est-ce que Home by SFR ?
Home by SFR is a security and automation service provided by the French Internet provider SFR. Home by SFR est un service de sécurité et de domotique fourni par SFR.
The library was only tested for the French version because I don't know if it exists elsewhere Cette bibliothèque ne peut utiliser que le service fourni par SFR en France.
# Licence # Licence
These libraries are under GNU GPL v3. Voir la [licence](LICENSE) pour plus de détails.

View File

@ -1,501 +1,607 @@
''' # WARNING: ce code s'appuie sur une API découverte par rétro-ingénérie.
Home by SFR wrapping class # Il peut cesser de fonctionner sans préavis.
Plain use of your Home by SFR device from a Python 3 library
Warning:
This is a wrap aroud website, this could stop working without prior notice
'''
# TODO: # TODO:
## Manage cameras ##
### Get video
authors = (
'Gilles "Almtesh" Émilien MOREL',
)
name = 'homesfr for Python 3'
version = '1.0'
# Settable modes
MODE_OFF = 0
MODE_CUSTOM = 1
MODE_ON = 2
# Sensors names
PRESENCE_DETECTOR = 'PIR_DETECTOR'
MAGNETIC_OPENNING_DETECTOR = 'MAGNETIC'
SMOKE_DETECTOR = 'SMOKE'
SIREN = 'SIREN'
REMOTE_CONTROLER = 'REMOTE'
KEYPAD_CONTROLER = 'KEYPAD'
PRESENCE_CAMERA_DETECTOR = 'PIR_CAMERA'
from urllib import request from urllib import request
from http.cookiejar import CookieJar from http.cookiejar import CookieJar
from urllib.parse import urlencode from urllib.parse import urlencode
from xml.etree import ElementTree as ET
from urllib.error import HTTPError from urllib.error import HTTPError
from datetime import datetime from time import time
class Common (): authors = (
'Sasha "Almtesh" Évan MOREL',
)
name = 'homesfr pour Python 3'
version = '1.5'
# Modes utilisables
MODE_OFF = 0
MODE_CUSTOM = 1
MODE_ON = 2
# Types de capteurs
PRESENCE_DETECTOR = 'PIR_DETECTOR' # https://boutique.home.sfr.fr/detecteur-de-mouvement
MAGNETIC_OPENNING_DETECTOR = 'MAGNETIC' # https://boutique.home.sfr.fr/detecteur-d-ouverture-de-porte-ou-fenetre
SMOKE_DETECTOR = 'SMOKE' # https://boutique.home.sfr.fr/detecteur-de-fumee
SIREN = 'SIREN' # https://boutique.home.sfr.fr/sirene-interieure (et peut-être https://boutique.home.sfr.fr/sirene-exterieure)
REMOTE_CONTROLER = 'REMOTE' # https://boutique.home.sfr.fr/telecommande
KEYPAD_CONTROLER = 'KEYPAD' # https://boutique.home.sfr.fr/clavier-de-commande
PRESENCE_CAMERA_DETECTOR = 'PIR_CAMERA' # https://boutique.home.sfr.fr/camera
TEMPHUM_SENSOR = 'TEMP_HUM' # https://boutique.home.sfr.fr/thermometre
ONOFF_PLUG = 'ON_OFF_PLUG' # https://boutique.home.sfr.fr/prise-commandee-connectee-legrand
base_url = 'https://home.sfr.fr'
# Authentification
auth_path = '/mysensors'
auth_ok_url = 'https://home.sfr.fr/logged'
auth_post_url = 'https://boutique.home.sfr.fr/authentification'
auth_referer = 'https://boutique.home.sfr.fr/authentification?back=service'
auth_user_field = 'email'
auth_pass_field = 'passwd'
auth_extra_fields = {'back': 'service', 'token_sso': '', 'error_sso': '', 'SubmitLogin': 'OK'}
auth_logout_path = '/deconnexion'
# Chemin pour la liste des capteurs
sensors_list = '/mysensors'
# Chemin pour les alertes
alerts_path = '/listalert'
# Détection
mode_get_label = 'alarm_mode'
mode_set_path = '/alarmmode'
mode_set_field = 'action' # Name for GET field
mode_off = 'OFF' # Value for off
mode_custom = 'CUSTOM' # Value for custom
mode_on = 'ON' # Value for on
# Caméra
cameras_list = '/homescope/mycams'
camera_snapshot = '/homescope/snapshot?size=4'
camera_snapshot_mac = 'mac'
camera_video = '/homescope/flv'
camera_video_mac = 'mac'
camera_recordings_list = '/listenr'
camera_recordings_delete = '/delenr'
camera_recordings_start = '/homescope/record'
camera_recordings_stop = '/homescope/stoprecord'
camera_recordings_mac = 'mac'
camera_get_config_path = '/homescope/camsettings'
camera_get_config_mac = 'mac'
camera_get_config_flip = 'FLIP'
camera_get_config_leds = 'LEDMODE'
camera_get_config_petmode = 'pet_mode'
camera_get_config_recording = 'rec24'
camera_get_config_privacy = 'privacy'
camera_get_config_name = 'NAME'
camera_set_config_path = '/homescope/camsettings'
camera_set_config_mac = 'mac'
camera_set_config_flip = 'flip'
camera_set_config_leds = 'led_mode'
camera_set_config_petmode = 'pet_mode'
camera_set_config_recording = 'rec24'
# Le paramètre privacy se gère avec le bouton derrière la caméra.
camera_set_config_name = 'name'
# Capteurs
sensors_list = '/mysensors'
sensors_label = 'Sensor'
sensors_label_id = 'id'
sensors_mac_field = 'deviceMac'
sensors_type_field = 'deviceType'
sensors_model_field = 'deviceModel'
sensors_version_field = 'deviceVersion'
sensors_name_field = 'name'
sensors_longname_field = 'long_name'
sensors_namegender_field = 'name_gender'
sensors_batterylevel_field = 'batteryLevel'
sensors_signal_field = 'signalLevel'
sensors_status_field = 'status'
sensors_status_value_ok = 'OK'
# Capteurs de température et humidité
sensors_temphum_root_field = 'sensorValues'
sensors_temp_name = 'name'
sensors_temp_text = 'Temperature'
sensors_hum_name = 'name'
sensors_hum_text = 'Humidity'
# Prise connectée (ON_OFF_PLUG)
sensors_oop_stateroot = 'automation'
sensors_oop_state = 'on_off'
sensors_oop_power = 'power_level'
sensors_oop_control = '/plugcontrol'
sensors_oop_control_sensors_id_field = 'uid'
sensors_oop_control_action_field = 'action'
sensors_oop_control_action_value_on = 'on'
sensors_oop_control_action_value_off = 'off'
# Fil d'événements
logs_path = '/getlog?page=1&nbparpage=10000' # je pense qu'on récupère tous les événements avec cette valeur
logs_labels = 'LOG'
def bytes2file (b):
''' '''
Common ressources to the library's classes Retourne une classe semblable à un fichier contant b
''' '''
from io import BytesIO
def __init__ (self): r = BytesIO ()
r.write (b)
# Specific configuration r.seek (0)
return (r)
self.base_url = 'https://home.sfr.fr'
# path to login test
self.auth_path = '/mysensors'
self.auth_ok = '/accueil' # if logged
self.auth_post_url = 'https://boutique.home.sfr.fr/authentification'
self.auth_referer = 'https://boutique.home.sfr.fr/authentification?back=service'
self.auth_user_field = 'email'
self.auth_pass_field = 'passwd'
self.auth_extra_fields = {'back': 'service', 'token_sso': '', 'error_sso': '', 'SubmitLogin': 'OK'}
self.auth_logout_path = '/deconnexion'
# Path to sensors and mode
self.sensors_list = '/mysensors'
# Path to list of alerts
self.alerts_path = '/listalert'
# Path to get and set modes
self.mode_get_path = '/mysensors'
self.mode_get_label = 'alarm_mode'
self.mode_set_path = '/alarmmode'
self.mode_set_field = 'action' # Name for GET field
self.mode_off = 'OFF' # Value for off
self.mode_custom = 'CUSTOM' # Value for custom
self.mode_on = 'ON' # Value for on
# Cameras
self.cameras_list = '/homescope/mycams'
self.camera_snapshot = '/homescope/snapshot'
self.camera_snapshot_mac = 'mac'
self.camera_video = '/homescope/flv'
self.camera_vide_mac = 'mac'
self.camera_recordings_list = '/listenr'
self.camera_recordings_delete = '/delenr'
self.camera_recordings_start = '/homescope/record'
self.camera_recordings_stop = '/homescope/stoprecord'
self.camera_recordings_mac = 'mac'
self.camera_get_config_path = '/homescope/camsettings'
self.camera_get_config_mac = 'mac'
self.camera_get_config_flip = 'FLIP'
self.camera_get_config_leds = 'LEDMODE' # set to 0 to turn the leds on
self.camera_get_config_detectionsensibility = 'DP' # from 1 to 4
self.camera_get_config_recording = 'REC24'
self.camera_get_config_name = 'NAME'
self.camera_set_config_path = '/homescope/camsettings'
self.camera_set_config_mac = 'mac'
self.camera_set_config_flip = 'flip'
self.camera_set_config_leds = 'led_mode' # set to 0 to turn the leds on
self.camera_set_config_detectionsensibility = 'dp' # from 1 to 4
self.camera_set_config_recording = 'rec24'
self.camera_set_config_name = 'name'
# Sensors
self.sensors_list = '/mysensors'
self.sensors_label = 'Sensor'
self.sensors_label_id = 'id'
self.sensors_mac_field = 'deviceMac'
self.sensors_type_field = 'deviceType'
self.sensors_model_field = 'deviceModel'
self.sensors_version_field = 'deviceVersion'
self.sensors_name_field = 'name'
self.sensors_longname_field = 'long_name'
self.sensors_namegender_field = 'name_gender' # Only usefull for French for the moment
self.sensors_batterylevel_field = 'batteryLevel'
self.sensors_signal_field = 'signalLevel'
self.sensors_lasttrigger_field = 'lastTriggerTime'
self.sensors_lasttrigger_dateformat = '%Y-%m-%d %H:%M:%S'
self.sensors_status_field = 'status'
self.sensors_status_value_ok = 'OK'
# I don't have any other value for the moment
# Logs
self.logs_path = '/getlog?page=1&nbparpage=10000' # should retrieve all available logs
self.logs_labels = 'LOG'
def bytes2file (self, b):
'''
Gives a file-like class from a Bytes
'''
from io import BytesIO
r = BytesIO ()
r.write (b)
r.seek (0)
return (r)
def bytes2image (self, b): def bytes2image (b):
''' '''
Gives a Image object from bytes Retourne une Image PIL contenant l'image donnée en b
Uses the bytes2file function '''
''' from PIL import Image
from PIL import Image f = bytes2file (b)
f = self.bytes2file (b) r = Image.open (f)
r = Image.open (f) return (r)
return (r)
def get_xml_elements (self,url, label, id_label=None): def get_xml_tree (fp):
def build_tree (element): '''
r = {} Retourne une variable itérable contenant les données d'un arbre XML
for i in element.getchildren (): '''
if i.getchildren () == []: def build_tree (element):
r.update ({i.tag: i.text}) if tuple (element) == ():
else: return (element.tag, dict (element.items ()), element.text)
r.update ({i.tag: build_tree (i)})
return (r)
a = self.bytes2file (self.opener.open (url).read ())
a.seek (0)
b = ET.parse (a)
if id_label == None:
r = []
for i in b.findall (label):
r.append (build_tree (i))
return (tuple (r))
else: else:
r = {} sub = []
for i in b.findall (label): for i in element:
r.update ({i.get (id_label): build_tree (i)}) sub.append (build_tree (i))
return (r) return (element.tag, dict (element.items ()), sub)
from xml.etree import ElementTree as ET
fp.seek (0)
root = ET.parse (fp).getroot ()
return (build_tree (root))
class HomeSFR (Common):
class HomeSFR ():
class MySensors ():
'''
Sous-classe gérant la récupération et le stockage temporaire des données issue du fichier https://home.sfr.fr/mysensors
'''
def __init__ (self, __parent__, max_age = 60):
self.__parent__ = __parent__
self.max_age = max_age
self.data = None
self.date = None
def __add__ (self, value):
self.autorefresh ()
return (self.data + value)
def __contains__ (self, key):
self.autorefresh ()
return (key in self.data)
def __eq__ (self, value):
self.autorefresh ()
return (self.data == value)
def __ge__ (self, value):
self.autorefresh ()
return (self.data >= value)
def __getitem__ (self, key):
self.autorefresh ()
return (self.data [key])
def __gt__ (self, value):
self.autorefresh ()
return (self.data > value)
def __hash__ (self):
self.autorefresh ()
return (hash (self.data))
def __iter__ (self):
self.autorefresh ()
return (iter (self.data))
def __le__ (self, value):
self.autorefresh ()
return (self.data <= value)
def __len__ (self):
self.autorefresh ()
return (len (self.data))
def __lt__ (self, value):
self.autorefresh ()
return (self.data < value)
def __mul__ (self, value):
self.autorefresh ()
return (self.data * value)
def __ne__ (self, value):
self.autorefresh ()
return (self.data != value)
def __repr__ (self):
self.autorefresh ()
return (repr (self.data))
def __rmul__ (self, value):
self.autorefresh ()
return (value * self.data)
def count (self, value):
self.autorefresh ()
return (self.data.count (value))
def get_age (self):
if self.date is None:
return (None)
return (time () - self.date)
def refresh (self):
r = base_url + sensors_list
self.data = get_xml_tree (bytes2file (self.__parent__.get_or_autologin (r).read ()))
self.date = time ()
def autorefresh (self):
if self.date is None or self.max_age != 0 and self.get_age () > self.max_age:
self.refresh ()
def get_data (self):
self.autorefresh ()
return (self.data)
def __init__ (self, username = None, password = None, cookies = None, debug = False, autologin = True): def __init__ (self, username = None, password = None, cookies = None, debug = False, autologin = True):
''' '''
Sets the class with username and password couple, or cookies Instancie la classe avec un identifiant et un mot de passe, ou des cookies
Both user/password and cookies can be set, the cookies will be used first On peut définir les identifiants et les cookies, la classe utilisera les cookies par défaut et les identifiants si on n'est pas connecté
The debug parameter defines if the class will write debug messages to stdout, if False, the stdout will never be writen by the class debug fourni des informations supplémentaires sur la sortie standard, s'il est à False, cette classe n'envoie rien sur la sortie standard
The autologin parameter defines if the class will manage the login by itself, if False, the user must call login () to login and test_login () to check the login
The autologin paramater will always be False if no username and password are defined, and the login () method will always return False
''' '''
Common.__init__ (self)
self.DEBUG = debug self.DEBUG = debug
if self.DEBUG: if self.DEBUG:
print (name + ' ' + version) print (name + ' ' + version)
print ('Authors:') print ('Auteurs:')
for i in authors: for i in authors:
print (' - ' + i) print (' - ' + i)
if username != None: if username is not None:
print ('init with username ' + username) print ('initalisé avec l\'identifiant ' + username)
if cookies != None: if cookies is not None:
print ('init with cookies') print ('initialisé avec des cookies')
print ('Cookies ' + str (len (cookies)))
print ('debug = ' + str (debug)) print ('debug = ' + str (debug))
print ('autologin = ' + str (autologin)) print ('autologin = ' + str (autologin))
if (username == None or password == None) and cookies == None: if (username is None or password is None) and cookies is None:
raise TypeError ('You must define either username AND password or cookies') raise TypeError ('Vous devez définir des identifiant ou des cookies !')
self.username = username self.username = username
self.password = password self.password = password
if self.username != None and self.password != None: if self.username is not None and self.password is not None:
self.autologin = autologin self.autologin = autologin
else: else:
self.autologin = False self.autologin = False
if cookies == None: if cookies is None:
self.cookies = CookieJar () self.cookies = CookieJar ()
elif type (cookies) == CookieJar:
self.cookies = cookies
else: else:
raise TypeError ('Cookies must be CookieJar type.') self.cookies = cookies
self.opener = request.build_opener (request.HTTPCookieProcessor (self.cookies)) self.opener = request.build_opener (request.HTTPCookieProcessor (self.cookies))
self.mysensors = self.MySensors (self)
def __str__ (self): def __str__ (self):
''' '''
Shows name, version, defined user and debug state Returne des informations sur l'état de l'instance
''' '''
if self.username != None: if self.username is not None:
return (name + ' ' + version + '\nUser: ' + self.username + '\nDebug: ' + str (self.DEBUG)) return (name + ' ' + version + '\nUtilisateur : ' + self.username + '\nDebug : ' + str (self.DEBUG))
else: else:
return (name + ' ' + version + '\nUser: Unknown (auth from cookie class)\nDebug: ' + str (self.DEBUG)) return (name + ' ' + version + '\nUtilisateur : Inconnu, authentifié avec des cookies.\nDebug : ' + str (self.DEBUG))
def test_login (self): def test_login (self):
''' '''
Tests if the client is logged Retourne l'état de l'authentification
Return True if it's logged, returns False either
''' '''
try: try:
if self.DEBUG: if self.DEBUG:
print ('Testing login') print ('Test de l\'authentification')
self.opener.open (self.base_url + self.auth_path) self.opener.open (base_url + auth_path)
except HTTPError: except HTTPError:
if self.DEBUG: if self.DEBUG:
print ('Not connected') print ('Non connecté')
return (False) return (False)
if self.DEBUG: if self.DEBUG:
print ('Connected') print ('Connecté')
return (True) return (True)
def login (self): def login (self):
''' '''
Logs in the HomeBySFR service S'authentifier auprès du service
Call this function first or exception will be raised Retourne True si c'est réussi, False sinon
Returns True if the login was a success, False either Si seuls les cookies sont définis, la méthode retournera False, même si nous sommes authentifiés, pour savoir si on est authentifiés, utiliser test_login ()
This method will always return False if no username and password are defined
''' '''
if self.username != None and self.password != None: if self.username is not None and self.password is not None:
self.opener.open (self.auth_referer) self.opener.open (auth_referer)
data = self.auth_extra_fields data = auth_extra_fields
data [self.auth_user_field] = self.username data [auth_user_field] = self.username
data [self.auth_pass_field] = self.password data [auth_pass_field] = self.password
data = bytes (urlencode (data), 'UTF8') data = bytes (urlencode (data), 'UTF8')
if self.DEBUG: if self.DEBUG:
print ('Cookies ' + str( len(self.cookies))) print ('Cookies ' + str (len (self.cookies)))
print ('Sending data ' + str (data)) print ('Envoi de ' + str (data))
a = self.opener.open (self.auth_post_url, data = data) a = self.opener.open (auth_post_url, data = data)
if self.DEBUG: if self.DEBUG:
print ('Auth redirected to ' + a.geturl ()) print ('Authentification redirigée vers ' + a.geturl ())
return (a.geturl () == self.base_url + self.auth_ok) return (a.geturl () == auth_ok_url)
else: else:
return (False) return (False)
def do_autologin (self): def get_or_autologin (self, url, data = None):
''' '''
Trigger the autologin Fais une requête, si une erreur 403 est retourné, tente une authentification automatiquement (si réglé dans le paramètre autologin), sinon lève une exception
''' '''
if (self.autologin and self.test_login () == False): try:
self.login () if self.DEBUG:
print ('Getting ' + url)
return (self.opener.open (url, data = data))
except HTTPError as e:
if '403' in str (e) and self.autologin:
if self.DEBUG:
print ('Not logged, logging…')
self.login ()
if self.DEBUG:
print ('Getting ' + url)
return (self.opener.open (url, data = data))
else:
raise e
def logout (self): def logout (self):
''' '''
Logs out from HomeBySFR service Se déconnecter
The object should be destroyed just after calling this method L'instance devrait être supprimée après l'appel de cette fonction
''' '''
if self.DEBUG: if self.DEBUG:
print ('Sending disconnect') print ('Demande de déconnexion')
self.opener.open (self.base_url + self.auth_logout_path) self.opener.open (base_url + auth_logout_path)
if self.DEBUG: if self.DEBUG:
print ('Destroying cookies') print ('Destruction des cookies')
del self.cookies del self.cookies
self.cookies = None self.cookies = None
return (None) return (None)
def get_cookies (self): def get_cookies (self):
''' '''
Returns the CookieJar as it is now, for further use Récupérer les cookies
It's strongly recommended to use this method only before a object delete Il est recommandé de supprimer l'instance après cette fonction
''' '''
return (self.cookies) return (self.cookies)
def set_mode (self, mode): def set_mode (self, mode):
''' '''
Sets the detection mode Modifie le mode de détection
For the current version, use the MODE_OFF, MODE_ON and MODE_CUSTOM constants and always returns True, but raises an exception if a problem happens
''' '''
self.do_autologin ()
if mode == MODE_OFF: if mode == MODE_OFF:
m = self.mode_off m = mode_off
elif mode == MODE_CUSTOM: elif mode == MODE_CUSTOM:
m = self.mode_custom m = mode_custom
elif mode == MODE_ON: elif mode == MODE_ON:
m = self.mode_on m = mode_on
else: else:
if self.DEBUG: if self.DEBUG:
print ('You should use the MODE_OFF, MODE_ON and MODE_CUSTOM constants to set this.') print ('Vous devriez utiliser les constantes MODE_OFF, MODE_ON et MODE_CUSTOM.')
raise ValueError raise ValueError
r = self.base_url + self.mode_set_path + '?' + self.mode_set_field + '=' + m r = base_url + mode_set_path + '?' + mode_set_field + '=' + m
if self.DEBUG: self.get_or_autologin (r)
print ('Will get ' + r)
self.opener.open (r)
return (True) return (True)
def get_mode (self): def get_mode (self):
''' '''
Gets the detection mode Retourne le mode de détection
Returns one of MODE_OFF, MODE_ON and MODE_CUSTOM constants, or None if something went wrong
''' '''
self.do_autologin () b = self.mysensors [1]
r = self.base_url + self.mode_get_path c = b [mode_get_label]
if self.DEBUG: if self.DEBUG:
print ('Getting ' + r) print ('Mode de détection ' + c)
a = self.bytes2file (self.opener.open (r).read ()) if (c == mode_off):
b = ET.parse (a).getroot ()
c = b.get (self.mode_get_label)
if self.DEBUG:
print ('Got mode ' + c)
if (c == self.mode_off):
return (MODE_OFF) return (MODE_OFF)
if (c == self.mode_custom): if (c == mode_custom):
return (MODE_CUSTOM) return (MODE_CUSTOM)
if (c == self.mode_on): if (c == mode_on):
return (MODE_ON) return (MODE_ON)
return (None) return (None)
def list_sensors (self): def list_sensors (self):
''' '''
Returns a list of sensors' ids. Retourne une liste des IDs des capteurs
''' '''
self.do_autologin () r = base_url + sensors_list
r = self.base_url + self.sensors_list a = bytes2file (self.get_or_autologin (r).read ())
a = self.bytes2file (self.opener.open (r).read ()) b = get_xml_tree (a)
b = ET.parse (a)
r = [] r = []
for i in b.findall (self.sensors_label): for i in b [2]:
r.append (i.get (self.sensors_label_id)) try:
r.append (i [1] [sensors_label_id])
except KeyError:
pass
return (list (r)) return (list (r))
def get_sensor (self, id): def get_sensor (self, id):
''' '''
Returns a Sensor object for the sensor id or None if sensor is not found Retourne un objet Sensor à partir de l'ID
The available ids can be got from the list_sensors method
''' '''
self.do_autologin () return (Sensor (id, self.mysensors, self.get_or_autologin))
r = Sensor (id, self.opener)
r.refresh ()
return (r)
def get_all_sensors (self): def get_all_sensors (self):
''' '''
Returns a tuple of sensors as described in the get_sensor method Retourne un tuple d'objet Sensor contenant tous les capteurs
''' '''
r = [] r = []
for i in self.list_sensors (): for i in self.list_sensors ():
r.append (self.get_sensor (i)) r.append (self.get_sensor (i))
return (tuple (r)) return (tuple (r))
def get_camera (self, id):
'''
Get a Camera object from the sensor's id
'''
self.do_autologin ()
if (self.autologin and self.test_login () == False):
self.login ()
r = Camera (id, self.opener)
r.refresh ()
return (r)
def get_logs (self):
'''
Return the whole logs in a form of tuple of dicts, as returned by the site
'''
self.do_autologin ()
a = self.base_url + self.logs_path
return (self.get_xml_elements (a, self.logs_labels))
class Sensor (Common):
''' class Sensor ():
Class used to read easily the sensors def __init__ (self, id, mysensors, get_or_autologin):
'''
def __init__ (self, id, opener):
'''
Initialize the class with the dict producted by HomeSFR.get_sensors ()
'''
Common.__init__ (self)
self.id = id self.id = id
self.sensor_dict = None self.mysensors = mysensors
self.opener = opener self.get_or_autologin = get_or_autologin
def refresh (self): def refresh (self):
''' '''
Gets or refresh the data for the sensor Mets à jour les données du capteur
''' '''
self.mysensors.refresh ()
r = self.base_url + self.sensors_list
self.sensor_dict = None
self.sensor_dict = self.get_xml_elements (r, self.sensors_label, self.sensors_label_id) [self.id]
def get_raw (self): def get_raw (self):
''' '''
Returns the raw dict, as presented in the original XML file Retourne les données brutes du capteur
''' '''
return (self.sensor_dict) for i in self.mysensors [2]:
if i [0] == sensors_label and i [1] [sensors_label_id] == self.id:
return (i [2])
def get_attributes (self, lst, key):
for i in lst:
if i [0] == key:
return (i [1])
raise KeyError ('no key ' + key)
def get_value (self, lst, key):
for i in lst:
if i [0] == key:
return (i [2])
raise KeyError ('no key ' + key)
def get_mac (self): def get_mac (self):
''' '''
Returns the sensor's model, if any, None either Retourne l'adresse matérielle du capteur, s'il en a une
''' '''
return (self.sensor_dict [self.sensors_mac_field]) return (self.get_value (self.get_raw (), sensors_mac_field))
def get_type (self): def get_type (self):
''' '''
Returns the sensor's type Retourne le type du capteur
Les types sont ceux définis dans les constantes
''' '''
return (self.sensor_dict [self.sensors_type_field]) return (self.get_value (self.get_raw (), sensors_type_field))
def get_model (self): def get_model (self):
''' '''
Returns the sensor's model, if any, None either Retourne le modèle du capteur
''' '''
return (self.sensor_dict [self.sensors_model_field]) return (self.get_value (self.get_raw (), sensors_model_field))
def get_version (self): def get_version (self):
''' '''
Returns the sensor's version Retourne la version du capteur
''' '''
return (self.sensor_dict [self.sensors_version_field]) return (self.get_value (self.get_raw (), sensors_version_field))
def get_name (self): def get_name (self):
''' '''
Returns the sensor's name Retourne le nom du capteur
''' '''
return (self.sensor_dict [self.sensors_name_field]) return (self.get_value (self.get_raw (), sensors_name_field))
def get_longname (self): def get_longname (self):
''' '''
Returns the sensor's type name in system's language and the sensor's name Retourne un nom long du capteur composé de son type en français et de son nom
''' '''
return (self.sensor_dict [self.sensors_longname_field]) return (self.get_value (self.get_raw (), sensors_longname_field))
def get_namegender (self): def get_namegender (self):
''' '''
Return M for male and F for female. Retourne le genre du nom du type de capteur en français
Only usefull for languages with gender on nouns M pour masculin et F pour féminin
''' '''
return (self.sensor_dict [self.sensors_namegender_field]) return (self.get_value (self.get_raw (), sensors_namegender_field))
def get_batterylevel (self): def get_batterylevel (self):
''' '''
Returns the sensor's battery level, out of 10 Retourne le niveau de batterie sur 10
It seems that batteryless sensors return 255 Toute autre valeur doit être considérée comme venant d'un capteur n'ayant pas de batterie
''' '''
return (int (self.sensor_dict [self.sensors_batterylevel_field])) return (int (self.get_value (self.get_raw (), sensors_batterylevel_field)))
def get_signal (self): def get_signal (self):
''' '''
Returns the sensor's signal quality, out of 10 Retourne le niveau de signal sur 10
Tout autre valeur est pour un capteur connecté par câble
''' '''
return (int (self.sensor_dict [self.sensors_signal_field])) return (int (self.get_value (self.get_raw (), sensors_signal_field)))
def get_lasttrigger (self):
'''
Return the timestamp of the sensor's last triger
The sensors always trigger, even when the alarm's mode is off
'''
a = self.sensor_dict [self.sensors_lasttrigger_field]
# Try because camera return the date '0000-00-00 00:00:00' that is ununderstandable
try:
b = datetime.strptime (a, self.sensors_lasttrigger_dateformat)
except ValueError:
return (0)
r = int (b.timestamp ())
return (r)
def get_status (self): def get_status (self):
''' '''
Returns True is the sensor is OK, False either Retourne True si le capteur est considéré comme opérationnel par le système
''' '''
return (self.sensor_dict [self.sensors_status_field] == self.sensors_status_value_ok) return (self.get_value (self.get_raw (), sensors_status_field) == sensors_status_value_ok)
class Camera (Sensor):
'''
Class used to manipulate easily cameras
'''
def __init__ (self, sensor_dict, opener):
'''
Initialize the class with the dict producted by HomeSFR.get_camera ()
'''
Sensor.__init__ (self, sensor_dict, opener)
def get_snapshot (self): def get_camera_snapshot (self):
''' '''
Get a snapshot from the camera Retourne une capture de la caméra dans un objet PIL.Image
Return a PIL.Image object
''' '''
r = self.base_url + self.camera_snapshot + '?' + self.camera_snapshot_mac + '=' + self.get_mac () r = base_url + camera_snapshot + '&' + camera_snapshot_mac + '=' + self.get_mac ()
a = self.bytes2image (self.opener.open (r).read ()) a = bytes2image (self.get_or_autologin (r).read ())
return (a) return (a)
def get_camera_petmode (self):
'''
Retourne l'état du mode animaux domestiques
Ce mode réduit la sensibilité du capteur pour éviter des déclanchements d'alarme dus aux animaux
'''
return (self.get_raw () [camera_get_config_petmode] == '1')
def get_camera_recording (self):
'''
Retourne l'état de l'enregistrement vidéo 24/24
'''
return (self.get_raw () [camera_get_config_recording] == '1')
def get_camera_privacy (self):
'''
Si cette méthode retourne True, la caméra est paramétrée pour ne pas capture d'image
'''
return (self.get_raw () [camera_get_config_privacy] == '1')
def get_temperature (self):
'''
Retourne la température donnée par le capteur
'''
a = self.get_value (self.get_raw (), sensors_temphum_root_field)
for i in a:
if i [1] [sensors_temp_name] == sensors_temp_text:
return (float (i [2].replace ('°C', '')))
def get_humidity (self):
'''
Retourne l'humidité donnée par le capteur
'''
a = self.get_value (self.get_raw (), sensors_temphum_root_field)
for i in a:
if i [1] [sensors_hum_name] == sensors_hum_text:
return (float (i [2].replace ('%', '')))
def get_on_off_state (self):
'''
Retourne l'état d'une prise connectée, True sur la prise est fermée
'''
a = self.get_attributes (self.get_raw (), sensors_oop_stateroot)
return (True if a [sensors_oop_state] == '1' else False)
def set_on_off_state (self, state):
'''
Défini l'état d'une prise connectée, True pour fermer la prise
'''
r = base_url + sensors_oop_control + '?' + sensors_oop_control_sensors_id_field + '=' + self.id + '&' + sensors_oop_control_action_field + '=' + (sensors_oop_control_action_value_on if state else sensors_oop_control_action_value_off)
self.get_or_autologin (r)
def get_on_off_power (self):
'''
Retourne la puissance active qui traverse la prise, en watts
'''
a = self.get_attributes (self.get_raw (), sensors_oop_stateroot)
return (int (a [sensors_oop_power]))