Réorganisation du code
Beaucoup de code inutile et mal organisé enlevé et réadapté. J'ai aussi changé la façon de lire les états des accessoires en prévision de nouveau accessoires que je me suis procuré. En marge, j'ai également traduit les commentaires en français.
This commit is contained in:
parent
72e1c94a80
commit
b62c6c36ea
541
homesfr.py
541
homesfr.py
|
@ -1,198 +1,171 @@
|
||||||
'''
|
# 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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
authors = (
|
authors = (
|
||||||
'Gilles "Almtesh" Émilien MOREL',
|
'Gilles "Almtesh" Émilien MOREL',
|
||||||
)
|
)
|
||||||
name = 'homesfr for Python 3'
|
name = 'homesfr pour Python 3'
|
||||||
version = '1.2'
|
version = '1.3'
|
||||||
|
|
||||||
# Settable modes
|
# Modes utilisables
|
||||||
MODE_OFF = 0
|
MODE_OFF = 0
|
||||||
MODE_CUSTOM = 1
|
MODE_CUSTOM = 1
|
||||||
MODE_ON = 2
|
MODE_ON = 2
|
||||||
|
|
||||||
# Sensors names
|
# Types de capteurs
|
||||||
PRESENCE_DETECTOR = 'PIR_DETECTOR'
|
PRESENCE_DETECTOR = 'PIR_DETECTOR' # https://boutique.home.sfr.fr/detecteur-de-mouvement
|
||||||
MAGNETIC_OPENNING_DETECTOR = 'MAGNETIC'
|
MAGNETIC_OPENNING_DETECTOR = 'MAGNETIC' # https://boutique.home.sfr.fr/detecteur-d-ouverture-de-porte-ou-fenetre
|
||||||
SMOKE_DETECTOR = 'SMOKE'
|
SMOKE_DETECTOR = 'SMOKE' # https://boutique.home.sfr.fr/detecteur-de-fumee
|
||||||
SIREN = 'SIREN'
|
SIREN = 'SIREN' # https://boutique.home.sfr.fr/sirene-interieure (et peut-être https://boutique.home.sfr.fr/sirene-exterieure)
|
||||||
REMOTE_CONTROLER = 'REMOTE'
|
REMOTE_CONTROLER = 'REMOTE' # https://boutique.home.sfr.fr/telecommande
|
||||||
KEYPAD_CONTROLER = 'KEYPAD'
|
KEYPAD_CONTROLER = 'KEYPAD' # https://boutique.home.sfr.fr/clavier-de-commande
|
||||||
PRESENCE_CAMERA_DETECTOR = 'PIR_CAMERA'
|
PRESENCE_CAMERA_DETECTOR = 'PIR_CAMERA' # https://boutique.home.sfr.fr/camera
|
||||||
|
|
||||||
|
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_path = '/mysensors'
|
||||||
|
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
|
||||||
|
|
||||||
|
# Cémera
|
||||||
|
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'
|
||||||
|
|
||||||
|
# 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'
|
||||||
|
|
||||||
|
|
||||||
class Common ():
|
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_url = 'https://home.sfr.fr/logged' # 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_petmode = 'pet_mode'
|
|
||||||
self.camera_get_config_recording = 'rec24'
|
|
||||||
self.camera_get_config_privacy = 'privacy'
|
|
||||||
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_petmode = 'pet_mode'
|
|
||||||
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 is 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 ():
|
||||||
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 is not None:
|
if username is not None:
|
||||||
print ('init with username ' + username)
|
print ('initalisé avec l\'identifiant ' + username)
|
||||||
if cookies is not None:
|
if cookies is not None:
|
||||||
print ('init with cookies')
|
print ('initialisé avec des 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 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 is not None and self.password is not None:
|
if self.username is not None and self.password is not None:
|
||||||
|
@ -204,292 +177,264 @@ class HomeSFR (Common):
|
||||||
elif type (cookies) == CookieJar:
|
elif type (cookies) == CookieJar:
|
||||||
self.cookies = cookies
|
self.cookies = cookies
|
||||||
else:
|
else:
|
||||||
raise TypeError ('Cookies must be CookieJar type.')
|
raise TypeError ('Les cookies doivent être de type CookieJar !')
|
||||||
self.opener = request.build_opener (request.HTTPCookieProcessor (self.cookies))
|
self.opener = request.build_opener (request.HTTPCookieProcessor (self.cookies))
|
||||||
|
|
||||||
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 is not 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 is not None and self.password is not 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.auth_ok_url)
|
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
|
||||||
'''
|
'''
|
||||||
while (self.autologin and not self.test_login ()):
|
try:
|
||||||
self.login ()
|
return (self.opener.open (url, data = data))
|
||||||
|
except HTTPError as e:
|
||||||
|
if '403' in str (e) and self.autologin:
|
||||||
|
self.login ()
|
||||||
|
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:
|
if self.DEBUG:
|
||||||
print ('Will get ' + r)
|
print ('Demande ' + r)
|
||||||
self.opener.open (r)
|
self.get_or_autologin (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 ()
|
r = base_url + mode_get_path
|
||||||
r = self.base_url + self.mode_get_path
|
|
||||||
if self.DEBUG:
|
if self.DEBUG:
|
||||||
print ('Getting ' + r)
|
print ('Demande ' + r)
|
||||||
a = self.bytes2file (self.opener.open (r).read ())
|
a = bytes2file (self.get_or_autologin (r).read ())
|
||||||
b = ET.parse (a).getroot ()
|
b = get_xml_tree (a) [1]
|
||||||
c = b.get (self.mode_get_label)
|
c = b [mode_get_label]
|
||||||
if self.DEBUG:
|
if self.DEBUG:
|
||||||
print ('Got mode ' + c)
|
print ('Mode de détection ' + c)
|
||||||
if (c == self.mode_off):
|
if (c == 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 ()
|
r = Sensor (id, self.get_or_autologin)
|
||||||
r = Sensor (id, self.opener)
|
|
||||||
r.refresh ()
|
r.refresh ()
|
||||||
return (r)
|
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 ()
|
|
||||||
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 ():
|
||||||
'''
|
def __init__ (self, id, get_or_autologin):
|
||||||
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.sensor_dict = None
|
self.sensor_dict = None
|
||||||
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
|
||||||
'''
|
'''
|
||||||
|
|
||||||
r = self.base_url + self.sensors_list
|
r = base_url + sensors_list
|
||||||
self.sensor_dict = None
|
self.sensor_dict = None
|
||||||
self.sensor_dict = self.get_xml_elements (r, self.sensors_label, self.sensors_label_id) [self.id]
|
for i in get_xml_tree (bytes2file (self.get_or_autologin (r).read ())) [2]:
|
||||||
|
if i [0] == sensors_label and i [1] [sensors_label_id] == self.id:
|
||||||
|
self.sensor_dict = i [2]
|
||||||
|
break
|
||||||
|
|
||||||
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)
|
return (self.sensor_dict)
|
||||||
|
|
||||||
|
def get_value (self, lst, key):
|
||||||
|
for i in lst:
|
||||||
|
if i [0] == key:
|
||||||
|
return (i [2])
|
||||||
|
raise KeyError ()
|
||||||
|
|
||||||
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.sensor_dict, 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.sensor_dict, 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.sensor_dict, 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.sensor_dict, 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.sensor_dict, 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.sensor_dict, 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.sensor_dict, 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.sensor_dict, 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.sensor_dict, 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.sensor_dict, sensors_status_field) == sensors_status_value_ok)
|
||||||
|
|
||||||
def get_camera_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):
|
def get_camera_petmode (self):
|
||||||
|
@ -497,16 +442,16 @@ class Sensor (Common):
|
||||||
Gets the pet mode value
|
Gets the pet mode value
|
||||||
Pet mode is a setting on movement sensibility, to avoid trigger on pet movements
|
Pet mode is a setting on movement sensibility, to avoid trigger on pet movements
|
||||||
'''
|
'''
|
||||||
return (self.sensor_dict [self.camera_get_config_petmode] == '1')
|
return (self.sensor_dict [camera_get_config_petmode] == '1')
|
||||||
|
|
||||||
def get_camera_recording (self):
|
def get_camera_recording (self):
|
||||||
'''
|
'''
|
||||||
Gets if the camera records or not
|
Gets if the camera records or not
|
||||||
'''
|
'''
|
||||||
return (self.sensor_dict [self.camera_get_config_recording] == '1')
|
return (self.sensor_dict [camera_get_config_recording] == '1')
|
||||||
|
|
||||||
def get_camera_privacy (self):
|
def get_camera_privacy (self):
|
||||||
'''
|
'''
|
||||||
Gets if the camera records or not
|
Gets if the camera records or not
|
||||||
'''
|
'''
|
||||||
return (self.sensor_dict [self.camera_get_config_privacy] == '1')
|
return (self.sensor_dict [camera_get_config_privacy] == '1')
|
Loading…
Reference in New Issue