Compare commits

Author SHA1 Message Date
Gilles MOREL efd3144d9f Passage à la licence AGPL-3.0 2018-02-21 09:57:13 +01:00
Gilles MOREL 3df38d59bb Changing to WTFPL 2018-02-21 09:45:22 +01:00
Gilles MOREL b969b8fb81 Passage en version 1.0 2018-01-27 16:39:36 +01:00
Gilles MOREL fd58a374cd Deleted python2 support
I don't want to support Python2 anymore.
Sorry for Python2 users…
2018-01-27 16:22:13 +01:00
Gilles MOREL ee892b9d2c Check the cookies type
Python2:
- Added type check for cookies param in HomeSFR class

Python3:
- Added type check for cookies param in HomeSFR class
2016-12-01 11:27:01 +01:00
Gilles MOREL 46933a8156 Updated TODOS
Python2:
- Updated TODO
- Updated version

Python3:
- Updated TODO
2016-05-26 17:00:33 +02:00
Gilles MOREL 9f2f4a06d1 Added log retrievment
Python2:
- Added the Common.get_xml_elements () for multiple usage of this routine
- Added HomeSFR.do_autologin () to trigger the autologin
- Removed autologin code in methods which have it
- Added HomeSFR.get_logs () method

Python3:
- Added the Common.get_xml_elements () for multiple usage of this routine
- Added HomeSFR.do_autologin () to trigger the autologin
- Removed autologin code in methods which have it
- Added HomeSFR.get_logs () method
2016-05-26 16:57:48 +02:00
Gilles MOREL 6facbe84f8 Solved encoding error in Python2 library
Python2:
-Solved encoding error, there were not actually, that were the terminal which caused strange encodings
2016-05-25 17:43:00 +02:00
Gilles MOREL d68e3ceb3c Updated version numbers 2016-05-25 17:22:44 +02:00
Gilles MOREL f60d528ed0 Updated TODOS 2016-05-25 17:22:03 +02:00
Gilles MOREL 9dd904b4ea Addind Common class for common ressources
Python2:
- Adding Common class for common ressources
- Inheritance from Common to HomeSFR and Sensors (and it's inherited class Camera)
- Adding the Sensors.get_mac () method
- Adding the Camera.get_snapshot () method
- Addind str2bytes () function for Python3 code compatibility (this library and the Python3 ones are quite identical)

Python3:
- Adding Common class for common ressources
- Inheritance from Common to HomeSFR and Sensors (and it's inherited class Camera)
- Adding the Sensors.get_mac () method
- Adding the Camera.get_snapshot () method
- Addind str2bytes () function for Python2 code compatibility (this library and the Python2 ones are quite identical)
- Changing all "readall" to "read", the method "readall" does not seem to exist in Python2, from what I tested, there's no problem with it
2016-05-25 17:19:16 +02:00
Gilles MOREL beae1f2b97 Logout method
Python2:
- Added HomeSFR.logout () method to logout from service
- moved HomeSFR.get_cookies () method in the HomeSFR class

Python3:
- Added HomeSFR.logout () method to logout from service
- moved HomeSFR.get_cookies () method in the HomeSFR class
2016-05-23 15:11:43 +02:00
Gilles MOREL 15bc8e86bd Copy Python3 code to Python2 lib
Python2:
- Added author
- Added Sensor class
- Added HomeSFR class
- Corrected date error for cameras (datetime.datetime.strptime does not understand '0000-00-00 00:00:00')
- Added encoding error TODO

Python3:
- Changed __name__
- Remove import of datetime.timezone
- Corrected date error for cameras (datetime.datetime.strptime does not understand '0000-00-00 00:00:00')
2016-05-23 12:03:27 +02:00
Gilles MOREL 9af3dc69ab Cookies Import and Export
Python3:
- Added cookies import and export
- Changed HomeSFR.login () behavior if there's no username and password
- Changed HomeSFR.__str__ () if there's no username
- Added HomeSFR.get_cookies ()
- Set autologin always to False is there no credential to use
2016-05-23 09:23:26 +02:00
Gilles MOREL 60974181fa Documentation changes
Python3:
- Changed description of HomeSFR.get_sensors ()
- Changed description of HomeSFR.get_all_sensors ()
- Changed type of return of HomeSFR.get_all_sensors (), it was a list and the doc said it was a tuple
2016-05-23 08:41:07 +02:00
Gilles MOREL 11050152c3 Gerenal:
- Changed structure to allow other languages
- Added Python2's file to ignore in .gitignore
- Changed README with a language-free text

Python2:
- Added initial file with top

Python3:
- Added Sensor class for better reading of values
- Changed return of HomeSFR.get_sensor () method
2016-05-22 22:57:39 +02:00
Gilles MOREL fe41141672 Adding TODOS 2016-05-21 16:59:51 +02:00
Gilles MOREL 651fa470f7 No need of so much complicated gitignore file, for the moment. 2016-05-19 15:43:38 +02:00
Gilles MOREL a2afd83117 More details… 2016-05-19 15:35:32 +02:00
Gilles MOREL 61ce155e2b Adding the current version of the library 2016-05-19 15:15:55 +02:00
Gilles Émilien MOREL 516734b611 Update README.md
More informations…
2016-05-19 15:08:01 +02:00
Gilles Émilien MOREL 8224cd1594 Initial commit 2016-05-19 14:54:56 +02:00
2 changed files with 331 additions and 438 deletions

No files matched your search

+7 -7
View File
@@ -1,13 +1,13 @@
# Qu'est-ce que c'est ? # What's this?
Il s'agit d'une bibliothèque qui permet d'accéder au système Home by SFR. This is a set of libraries to control your Home by SFR account.
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. They all work around the website.
# Qu'est-ce que Home by SFR ? # What's Home by SFR?
Home by SFR est un service de sécurité et de domotique fourni par SFR. Home by SFR is a security and automation service provided by the French Internet provider SFR.
Cette bibliothèque ne peut utiliser que le service fourni par SFR en France. The library was only tested for the French version because I don't know if it exists elsewhere
# Licence # Licence
Voir la [licence](LICENSE) pour plus de détails. These libraries are under GNU GPL v3.
+324 -431
View File
@@ -1,608 +1,501 @@
# WARNING: ce code s'appuie sur une API découverte par rétro-ingénérie. '''
# Il peut cesser de fonctionner sans préavis. Home by SFR wrapping class
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
from urllib import request
from http.cookiejar import CookieJar
from urllib.parse import urlencode
from urllib.error import HTTPError
from time import time
authors = ( authors = (
'Gilles "Almtesh" Émilien MOREL', 'Gilles "Almtesh" Émilien MOREL',
) )
name = 'homesfr pour Python 3' name = 'homesfr for Python 3'
version = '1.4' version = '1.0'
# Modes utilisables # Settable modes
MODE_OFF = 0 MODE_OFF = 0
MODE_CUSTOM = 1 MODE_CUSTOM = 1
MODE_ON = 2 MODE_ON = 2
# Types de capteurs # Sensors names
PRESENCE_DETECTOR = 'PIR_DETECTOR' # https://boutique.home.sfr.fr/detecteur-de-mouvement PRESENCE_DETECTOR = 'PIR_DETECTOR'
MAGNETIC_OPENNING_DETECTOR = 'MAGNETIC' # https://boutique.home.sfr.fr/detecteur-d-ouverture-de-porte-ou-fenetre MAGNETIC_OPENNING_DETECTOR = 'MAGNETIC'
SMOKE_DETECTOR = 'SMOKE' # https://boutique.home.sfr.fr/detecteur-de-fumee SMOKE_DETECTOR = 'SMOKE'
SIREN = 'SIREN' # https://boutique.home.sfr.fr/sirene-interieure (et peut-être https://boutique.home.sfr.fr/sirene-exterieure) SIREN = 'SIREN'
REMOTE_CONTROLER = 'REMOTE' # https://boutique.home.sfr.fr/telecommande REMOTE_CONTROLER = 'REMOTE'
KEYPAD_CONTROLER = 'KEYPAD' # https://boutique.home.sfr.fr/clavier-de-commande KEYPAD_CONTROLER = 'KEYPAD'
PRESENCE_CAMERA_DETECTOR = 'PIR_CAMERA' # https://boutique.home.sfr.fr/camera PRESENCE_CAMERA_DETECTOR = 'PIR_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' from urllib import request
from http.cookiejar import CookieJar
from urllib.parse import urlencode
from xml.etree import ElementTree as ET
from urllib.error import HTTPError
from datetime import datetime
# Authentification class Common ():
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):
''' '''
Retourne une classe semblable à un fichier contant b Common ressources to the library's classes
''' '''
from io import BytesIO
r = BytesIO ()
r.write (b)
r.seek (0)
return (r)
def __init__ (self):
def bytes2image (b): # Specific configuration
'''
Retourne une Image PIL contenant l'image donnée en b
'''
from PIL import Image
f = bytes2file (b)
r = Image.open (f)
return (r)
self.base_url = 'https://home.sfr.fr'
def get_xml_tree (fp): # path to login test
''' self.auth_path = '/mysensors'
Retourne une variable itérable contenant les données d'un arbre XML self.auth_ok = '/accueil' # if logged
''' self.auth_post_url = 'https://boutique.home.sfr.fr/authentification'
def build_tree (element): self.auth_referer = 'https://boutique.home.sfr.fr/authentification?back=service'
if tuple (element) == (): self.auth_user_field = 'email'
return (element.tag, dict (element.items ()), element.text) 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):
'''
Gives a Image object from bytes
Uses the bytes2file function
'''
from PIL import Image
f = self.bytes2file (b)
r = Image.open (f)
return (r)
def get_xml_elements (self,url, label, id_label=None):
def build_tree (element):
r = {}
for i in element.getchildren ():
if i.getchildren () == []:
r.update ({i.tag: i.text})
else:
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:
sub = [] r = {}
for i in element: for i in b.findall (label):
sub.append (build_tree (i)) r.update ({i.get (id_label): build_tree (i)})
return (element.tag, dict (element.items ()), sub) return (r)
from xml.etree import ElementTree as ET
fp.seek (0)
root = ET.parse (fp).getroot ()
return (build_tree (root))
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)
class HomeSFR (Common):
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):
''' '''
Instancie la classe avec un identifiant et un mot de passe, ou des cookies Sets the class with username and password couple, or cookies
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é Both user/password and cookies can be set, the cookies will be used first
debug fourni des informations supplémentaires sur la sortie standard, s'il est à False, cette classe n'envoie rien sur la sortie standard The debug parameter defines if the class will write debug messages to stdout, if False, the stdout will never be writen by the class
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 ('Auteurs:') print ('Authors:')
for i in authors: for i in authors:
print (' - ' + i) print (' - ' + i)
if username is not None: if username != None:
print ('initalisé avec l\'identifiant ' + username) print ('init with username ' + username)
if cookies is not None: if cookies != None:
print ('initialisé avec des cookies') print ('init with cookies')
print ('debug = ' + str (debug)) print ('debug = ' + str (debug))
print ('autologin = ' + str (autologin)) print ('autologin = ' + str (autologin))
if (username is None or password is None) and cookies is None: if (username == None or password == None) and cookies == None:
raise TypeError ('Vous devez définir des identifiant ou des cookies !') raise TypeError ('You must define either username AND password or cookies')
self.username = username self.username = username
self.password = password self.password = password
if self.username is not None and self.password is not None: if self.username != None and self.password != None:
self.autologin = autologin self.autologin = autologin
else: else:
self.autologin = False self.autologin = False
if cookies is None: if cookies == None:
self.cookies = CookieJar () self.cookies = CookieJar ()
elif type (cookies) == CookieJar: elif type (cookies) == CookieJar:
self.cookies = cookies self.cookies = cookies
else: else:
raise TypeError ('Les cookies doivent être de type CookieJar !') raise TypeError ('Cookies must be CookieJar type.')
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):
''' '''
Returne des informations sur l'état de l'instance Shows name, version, defined user and debug state
''' '''
if self.username is not None: if self.username != None:
return (name + ' ' + version + '\nUtilisateur : ' + self.username + '\nDebug : ' + str (self.DEBUG)) return (name + ' ' + version + '\nUser: ' + self.username + '\nDebug: ' + str (self.DEBUG))
else: else:
return (name + ' ' + version + '\nUtilisateur : Inconnu, authentifié avec des cookies.\nDebug : ' + str (self.DEBUG)) return (name + ' ' + version + '\nUser: Unknown (auth from cookie class)\nDebug: ' + str (self.DEBUG))
def test_login (self): def test_login (self):
''' '''
Retourne l'état de l'authentification Tests if the client is logged
Return True if it's logged, returns False either
''' '''
try: try:
if self.DEBUG: if self.DEBUG:
print ('Test de l\'authentification') print ('Testing login')
self.opener.open (base_url + auth_path) self.opener.open (self.base_url + self.auth_path)
except HTTPError: except HTTPError:
if self.DEBUG: if self.DEBUG:
print ('Non connecté') print ('Not connected')
return (False) return (False)
if self.DEBUG: if self.DEBUG:
print ('Connecté') print ('Connected')
return (True) return (True)
def login (self): def login (self):
''' '''
S'authentifier auprès du service Logs in the HomeBySFR service
Retourne True si c'est réussi, False sinon Call this function first or exception will be raised
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 () Returns True if the login was a success, False either
This method will always return False if no username and password are defined
''' '''
if self.username is not None and self.password is not None: if self.username != None and self.password != None:
self.opener.open (auth_referer) self.opener.open (self.auth_referer)
data = auth_extra_fields data = self.auth_extra_fields
data [auth_user_field] = self.username data [self.auth_user_field] = self.username
data [auth_pass_field] = self.password data [self.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 ('Envoi de ' + str (data)) print ('Sending data ' + str (data))
a = self.opener.open (auth_post_url, data = data) a = self.opener.open (self.auth_post_url, data = data)
if self.DEBUG: if self.DEBUG:
print ('Authentification redirigée vers ' + a.geturl ()) print ('Auth redirected to ' + a.geturl ())
return (a.geturl () == auth_ok_url) return (a.geturl () == self.base_url + self.auth_ok)
else: else:
return (False) return (False)
def get_or_autologin (self, url, data = None): def do_autologin (self):
''' '''
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 Trigger the autologin
''' '''
try: if (self.autologin and self.test_login () == False):
if self.DEBUG: self.login ()
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):
''' '''
Se déconnecter Logs out from HomeBySFR service
L'instance devrait être supprimée après l'appel de cette fonction The object should be destroyed just after calling this method
''' '''
if self.DEBUG: if self.DEBUG:
print ('Demande de déconnexion') print ('Sending disconnect')
self.opener.open (base_url + auth_logout_path) self.opener.open (self.base_url + self.auth_logout_path)
if self.DEBUG: if self.DEBUG:
print ('Destruction des cookies') print ('Destroying 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):
''' '''
Récupérer les cookies Returns the CookieJar as it is now, for further use
Il est recommandé de supprimer l'instance après cette fonction It's strongly recommended to use this method only before a object delete
''' '''
return (self.cookies) return (self.cookies)
def set_mode (self, mode): def set_mode (self, mode):
''' '''
Modifie le mode de détection Sets the detection mode
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 = mode_off m = self.mode_off
elif mode == MODE_CUSTOM: elif mode == MODE_CUSTOM:
m = mode_custom m = self.mode_custom
elif mode == MODE_ON: elif mode == MODE_ON:
m = mode_on m = self.mode_on
else: else:
if self.DEBUG: if self.DEBUG:
print ('Vous devriez utiliser les constantes MODE_OFF, MODE_ON et MODE_CUSTOM.') print ('You should use the MODE_OFF, MODE_ON and MODE_CUSTOM constants to set this.')
raise ValueError raise ValueError
r = base_url + mode_set_path + '?' + mode_set_field + '=' + m r = self.base_url + self.mode_set_path + '?' + self.mode_set_field + '=' + m
self.get_or_autologin (r) if self.DEBUG:
print ('Will get ' + r)
self.opener.open (r)
return (True) return (True)
def get_mode (self): def get_mode (self):
''' '''
Retourne le mode de détection Gets the detection mode
Returns one of MODE_OFF, MODE_ON and MODE_CUSTOM constants, or None if something went wrong
''' '''
b = self.mysensors [1] self.do_autologin ()
c = b [mode_get_label] r = self.base_url + self.mode_get_path
if self.DEBUG: if self.DEBUG:
print ('Mode de détection ' + c) print ('Getting ' + r)
if (c == mode_off): a = self.bytes2file (self.opener.open (r).read ())
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 == mode_custom): if (c == self.mode_custom):
return (MODE_CUSTOM) return (MODE_CUSTOM)
if (c == mode_on): if (c == self.mode_on):
return (MODE_ON) return (MODE_ON)
return (None) return (None)
def list_sensors (self): def list_sensors (self):
''' '''
Retourne une liste des IDs des capteurs Returns a list of sensors' ids.
''' '''
r = base_url + sensors_list self.do_autologin ()
a = bytes2file (self.get_or_autologin (r).read ()) r = self.base_url + self.sensors_list
b = get_xml_tree (a) a = self.bytes2file (self.opener.open (r).read ())
b = ET.parse (a)
r = [] r = []
for i in b [2]: for i in b.findall (self.sensors_label):
try: r.append (i.get (self.sensors_label_id))
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):
''' '''
Retourne un objet Sensor à partir de l'ID Returns a Sensor object for the sensor id or None if sensor is not found
The available ids can be got from the list_sensors method
''' '''
return (Sensor (id, self.mysensors, self.get_or_autologin)) self.do_autologin ()
r = Sensor (id, self.opener)
r.refresh ()
return (r)
def get_all_sensors (self): def get_all_sensors (self):
''' '''
Retourne un tuple d'objet Sensor contenant tous les capteurs Returns a tuple of sensors as described in the get_sensor method
''' '''
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)
class Sensor (): def get_logs (self):
def __init__ (self, id, mysensors, get_or_autologin): '''
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 used to read easily the sensors
'''
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.mysensors = mysensors self.sensor_dict = None
self.get_or_autologin = get_or_autologin self.opener = opener
def refresh (self): def refresh (self):
''' '''
Mets à jour les données du capteur Gets or refresh the data for the sensor
''' '''
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):
''' '''
Retourne les données brutes du capteur Returns the raw dict, as presented in the original XML file
''' '''
for i in self.mysensors [2]: return (self.sensor_dict)
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):
''' '''
Retourne l'adresse matérielle du capteur, s'il en a une Returns the sensor's model, if any, None either
''' '''
return (self.get_value (self.get_raw (), sensors_mac_field)) return (self.sensor_dict [self.sensors_mac_field])
def get_type (self): def get_type (self):
''' '''
Retourne le type du capteur Returns the sensor's type
Les types sont ceux définis dans les constantes
''' '''
return (self.get_value (self.get_raw (), sensors_type_field)) return (self.sensor_dict [self.sensors_type_field])
def get_model (self): def get_model (self):
''' '''
Retourne le modèle du capteur Returns the sensor's model, if any, None either
''' '''
return (self.get_value (self.get_raw (), sensors_model_field)) return (self.sensor_dict [self.sensors_model_field])
def get_version (self): def get_version (self):
''' '''
Retourne la version du capteur Returns the sensor's version
''' '''
return (self.get_value (self.get_raw (), sensors_version_field)) return (self.sensor_dict [self.sensors_version_field])
def get_name (self): def get_name (self):
''' '''
Retourne le nom du capteur Returns the sensor's name
''' '''
return (self.get_value (self.get_raw (), sensors_name_field)) return (self.sensor_dict [self.sensors_name_field])
def get_longname (self): def get_longname (self):
''' '''
Retourne un nom long du capteur composé de son type en français et de son nom Returns the sensor's type name in system's language and the sensor's name
''' '''
return (self.get_value (self.get_raw (), sensors_longname_field)) return (self.sensor_dict [self.sensors_longname_field])
def get_namegender (self): def get_namegender (self):
''' '''
Retourne le genre du nom du type de capteur en français Return M for male and F for female.
M pour masculin et F pour féminin Only usefull for languages with gender on nouns
''' '''
return (self.get_value (self.get_raw (), sensors_namegender_field)) return (self.sensor_dict [self.sensors_namegender_field])
def get_batterylevel (self): def get_batterylevel (self):
''' '''
Retourne le niveau de batterie sur 10 Returns the sensor's battery level, out of 10
Toute autre valeur doit être considérée comme venant d'un capteur n'ayant pas de batterie It seems that batteryless sensors return 255
''' '''
return (int (self.get_value (self.get_raw (), sensors_batterylevel_field))) return (int (self.sensor_dict [self.sensors_batterylevel_field]))
def get_signal (self): def get_signal (self):
''' '''
Retourne le niveau de signal sur 10 Returns the sensor's signal quality, out of 10
Tout autre valeur est pour un capteur connecté par câble
''' '''
return (int (self.get_value (self.get_raw (), sensors_signal_field))) return (int (self.sensor_dict [self.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):
''' '''
Retourne True si le capteur est considéré comme opérationnel par le système Returns True is the sensor is OK, False either
''' '''
return (self.get_value (self.get_raw (), sensors_status_field) == sensors_status_value_ok) return (self.sensor_dict [self.sensors_status_field] == self.sensors_status_value_ok)
def get_camera_snapshot (self): class Camera (Sensor):
'''
Class used to manipulate easily cameras
'''
def __init__ (self, sensor_dict, opener):
''' '''
Retourne une capture de la caméra dans un objet PIL.Image Initialize the class with the dict producted by HomeSFR.get_camera ()
''' '''
r = base_url + camera_snapshot + '&' + camera_snapshot_mac + '=' + self.get_mac () Sensor.__init__ (self, sensor_dict, opener)
a = bytes2image (self.get_or_autologin (r).read ())
def get_snapshot (self):
'''
Get a snapshot from the camera
Return a PIL.Image object
'''
r = self.base_url + self.camera_snapshot + '?' + self.camera_snapshot_mac + '=' + self.get_mac ()
a = self.bytes2image (self.opener.open (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]))