Compare commits
No commits in common. "b4540504f88cc6b6f34725b3a76300279eae7aa0" and "72e1c94a802432a425067d85f84cf731c103d1ee" have entirely different histories.
b4540504f8
...
72e1c94a80
574
homesfr.py
574
homesfr.py
|
@ -1,178 +1,198 @@
|
|||
# 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:
|
||||
##
|
||||
## Manage cameras
|
||||
### Get video
|
||||
|
||||
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
|
||||
|
||||
authors = (
|
||||
'Gilles "Almtesh" Émilien MOREL',
|
||||
)
|
||||
name = 'homesfr pour Python 3'
|
||||
version = '1.3'
|
||||
name = 'homesfr for Python 3'
|
||||
version = '1.2'
|
||||
|
||||
# Modes utilisables
|
||||
# Settable modes
|
||||
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
|
||||
|
||||
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'
|
||||
|
||||
# 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'
|
||||
|
||||
# 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'
|
||||
# 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'
|
||||
|
||||
|
||||
def bytes2file (b):
|
||||
class Common ():
|
||||
'''
|
||||
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):
|
||||
|
||||
# Specific configuration
|
||||
|
||||
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 (b):
|
||||
'''
|
||||
Retourne une Image PIL contenant l'image donnée en b
|
||||
'''
|
||||
from PIL import Image
|
||||
f = bytes2file (b)
|
||||
r = Image.open (f)
|
||||
return (r)
|
||||
|
||||
|
||||
def get_xml_tree (fp):
|
||||
'''
|
||||
Retourne une variable itérable contenant les données d'un arbre XML
|
||||
'''
|
||||
def build_tree (element):
|
||||
if tuple (element) == ():
|
||||
return (element.tag, dict (element.items ()), element.text)
|
||||
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 is None:
|
||||
r = []
|
||||
for i in b.findall (label):
|
||||
r.append (build_tree (i))
|
||||
return (tuple (r))
|
||||
else:
|
||||
sub = []
|
||||
for i in element:
|
||||
sub.append (build_tree (i))
|
||||
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))
|
||||
r = {}
|
||||
for i in b.findall (label):
|
||||
r.update ({i.get (id_label): build_tree (i)})
|
||||
return (r)
|
||||
|
||||
|
||||
class HomeSFR ():
|
||||
class HomeSFR (Common):
|
||||
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
|
||||
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é
|
||||
debug fourni des informations supplémentaires sur la sortie standard, s'il est à False, cette classe n'envoie rien sur la sortie standard
|
||||
Sets the class with username and password couple, or cookies
|
||||
Both user/password and cookies can be set, the cookies will be used first
|
||||
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
|
||||
if self.DEBUG:
|
||||
print (name + ' ' + version)
|
||||
print ('Auteurs:')
|
||||
print ('Authors:')
|
||||
for i in authors:
|
||||
print (' - ' + i)
|
||||
if username is not None:
|
||||
print ('initalisé avec l\'identifiant ' + username)
|
||||
print ('init with username ' + username)
|
||||
if cookies is not None:
|
||||
print ('initialisé avec des cookies')
|
||||
print ('init with cookies')
|
||||
print ('debug = ' + str (debug))
|
||||
print ('autologin = ' + str (autologin))
|
||||
|
||||
if (username is None or password is None) and cookies is 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.password = password
|
||||
if self.username is not None and self.password is not None:
|
||||
|
@ -184,299 +204,309 @@ class HomeSFR ():
|
|||
elif type (cookies) == CookieJar:
|
||||
self.cookies = cookies
|
||||
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))
|
||||
|
||||
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:
|
||||
return (name + ' ' + version + '\nUtilisateur : ' + self.username + '\nDebug : ' + str (self.DEBUG))
|
||||
return (name + ' ' + version + '\nUser: ' + self.username + '\nDebug: ' + str (self.DEBUG))
|
||||
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):
|
||||
'''
|
||||
Retourne l'état de l'authentification
|
||||
Tests if the client is logged
|
||||
Return True if it's logged, returns False either
|
||||
'''
|
||||
try:
|
||||
if self.DEBUG:
|
||||
print ('Test de l\'authentification')
|
||||
self.opener.open (base_url + auth_path)
|
||||
print ('Testing login')
|
||||
self.opener.open (self.base_url + self.auth_path)
|
||||
except HTTPError:
|
||||
if self.DEBUG:
|
||||
print ('Non connecté')
|
||||
print ('Not connected')
|
||||
return (False)
|
||||
if self.DEBUG:
|
||||
print ('Connecté')
|
||||
print ('Connected')
|
||||
return (True)
|
||||
|
||||
def login (self):
|
||||
'''
|
||||
S'authentifier auprès du service
|
||||
Retourne True si c'est réussi, False sinon
|
||||
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 ()
|
||||
Logs in the HomeBySFR service
|
||||
Call this function first or exception will be raised
|
||||
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:
|
||||
self.opener.open (auth_referer)
|
||||
data = auth_extra_fields
|
||||
data [auth_user_field] = self.username
|
||||
data [auth_pass_field] = self.password
|
||||
self.opener.open (self.auth_referer)
|
||||
data = self.auth_extra_fields
|
||||
data [self.auth_user_field] = self.username
|
||||
data [self.auth_pass_field] = self.password
|
||||
data = bytes (urlencode (data), 'UTF8')
|
||||
if self.DEBUG:
|
||||
print ('Cookies ' + str (len (self.cookies)))
|
||||
print ('Envoi de ' + str (data))
|
||||
a = self.opener.open (auth_post_url, data = data)
|
||||
print ('Sending data ' + str (data))
|
||||
a = self.opener.open (self.auth_post_url, data = data)
|
||||
if self.DEBUG:
|
||||
print ('Authentification redirigée vers ' + a.geturl ())
|
||||
return (a.geturl () == auth_ok_url)
|
||||
print ('Auth redirected to ' + a.geturl ())
|
||||
return (a.geturl () == self.auth_ok_url)
|
||||
else:
|
||||
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:
|
||||
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
|
||||
while (self.autologin and not self.test_login ()):
|
||||
self.login ()
|
||||
|
||||
def logout (self):
|
||||
'''
|
||||
Se déconnecter
|
||||
L'instance devrait être supprimée après l'appel de cette fonction
|
||||
Logs out from HomeBySFR service
|
||||
The object should be destroyed just after calling this method
|
||||
'''
|
||||
if self.DEBUG:
|
||||
print ('Demande de déconnexion')
|
||||
self.opener.open (base_url + auth_logout_path)
|
||||
print ('Sending disconnect')
|
||||
self.opener.open (self.base_url + self.auth_logout_path)
|
||||
if self.DEBUG:
|
||||
print ('Destruction des cookies')
|
||||
print ('Destroying cookies')
|
||||
del self.cookies
|
||||
self.cookies = None
|
||||
return (None)
|
||||
|
||||
def get_cookies (self):
|
||||
'''
|
||||
Récupérer les cookies
|
||||
Il est recommandé de supprimer l'instance après cette fonction
|
||||
Returns the CookieJar as it is now, for further use
|
||||
It's strongly recommended to use this method only before a object delete
|
||||
'''
|
||||
return (self.cookies)
|
||||
|
||||
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:
|
||||
m = mode_off
|
||||
m = self.mode_off
|
||||
elif mode == MODE_CUSTOM:
|
||||
m = mode_custom
|
||||
m = self.mode_custom
|
||||
elif mode == MODE_ON:
|
||||
m = mode_on
|
||||
m = self.mode_on
|
||||
else:
|
||||
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
|
||||
r = base_url + mode_set_path + '?' + mode_set_field + '=' + m
|
||||
r = self.base_url + self.mode_set_path + '?' + self.mode_set_field + '=' + m
|
||||
if self.DEBUG:
|
||||
print ('Demande ' + r)
|
||||
self.get_or_autologin (r)
|
||||
print ('Will get ' + r)
|
||||
self.opener.open (r)
|
||||
return (True)
|
||||
|
||||
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
|
||||
'''
|
||||
r = base_url + mode_get_path
|
||||
self.do_autologin ()
|
||||
r = self.base_url + self.mode_get_path
|
||||
if self.DEBUG:
|
||||
print ('Demande ' + r)
|
||||
a = bytes2file (self.get_or_autologin (r).read ())
|
||||
b = get_xml_tree (a) [1]
|
||||
c = b [mode_get_label]
|
||||
print ('Getting ' + r)
|
||||
a = self.bytes2file (self.opener.open (r).read ())
|
||||
b = ET.parse (a).getroot ()
|
||||
c = b.get (self.mode_get_label)
|
||||
if self.DEBUG:
|
||||
print ('Mode de détection ' + c)
|
||||
if (c == mode_off):
|
||||
print ('Got mode ' + c)
|
||||
if (c == self.mode_off):
|
||||
return (MODE_OFF)
|
||||
if (c == mode_custom):
|
||||
if (c == self.mode_custom):
|
||||
return (MODE_CUSTOM)
|
||||
if (c == mode_on):
|
||||
if (c == self.mode_on):
|
||||
return (MODE_ON)
|
||||
return (None)
|
||||
|
||||
def list_sensors (self):
|
||||
'''
|
||||
Retourne une liste des IDs des capteurs
|
||||
Returns a list of sensors' ids.
|
||||
'''
|
||||
r = base_url + sensors_list
|
||||
a = bytes2file (self.get_or_autologin (r).read ())
|
||||
b = get_xml_tree (a)
|
||||
self.do_autologin ()
|
||||
r = self.base_url + self.sensors_list
|
||||
a = self.bytes2file (self.opener.open (r).read ())
|
||||
b = ET.parse (a)
|
||||
r = []
|
||||
for i in b [2]:
|
||||
try:
|
||||
r.append (i [1] [sensors_label_id])
|
||||
except KeyError:
|
||||
pass
|
||||
for i in b.findall (self.sensors_label):
|
||||
r.append (i.get (self.sensors_label_id))
|
||||
return (list (r))
|
||||
|
||||
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
|
||||
'''
|
||||
r = Sensor (id, self.get_or_autologin)
|
||||
self.do_autologin ()
|
||||
r = Sensor (id, self.opener)
|
||||
r.refresh ()
|
||||
return (r)
|
||||
|
||||
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 = []
|
||||
for i in self.list_sensors ():
|
||||
r.append (self.get_sensor (i))
|
||||
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 ():
|
||||
def __init__ (self, id, get_or_autologin):
|
||||
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.sensor_dict = None
|
||||
self.get_or_autologin = get_or_autologin
|
||||
self.opener = opener
|
||||
|
||||
def refresh (self):
|
||||
'''
|
||||
Mets à jour les données du capteur
|
||||
Gets or refresh the data for the sensor
|
||||
'''
|
||||
|
||||
r = base_url + sensors_list
|
||||
r = self.base_url + self.sensors_list
|
||||
self.sensor_dict = None
|
||||
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
|
||||
self.sensor_dict = self.get_xml_elements (r, self.sensors_label, self.sensors_label_id) [self.id]
|
||||
|
||||
def get_raw (self):
|
||||
'''
|
||||
Retourne les données brutes du capteur
|
||||
Returns the raw dict, as presented in the original XML file
|
||||
'''
|
||||
return (self.sensor_dict)
|
||||
|
||||
def get_value (self, lst, key):
|
||||
for i in lst:
|
||||
if i [0] == key:
|
||||
return (i [2])
|
||||
raise KeyError ('no value ' + key)
|
||||
|
||||
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.sensor_dict, sensors_mac_field))
|
||||
return (self.sensor_dict [self.sensors_mac_field])
|
||||
|
||||
def get_type (self):
|
||||
'''
|
||||
Retourne le type du capteur
|
||||
Les types sont ceux définis dans les constantes
|
||||
Returns the sensor's type
|
||||
'''
|
||||
return (self.get_value (self.sensor_dict, sensors_type_field))
|
||||
return (self.sensor_dict [self.sensors_type_field])
|
||||
|
||||
def get_model (self):
|
||||
'''
|
||||
Retourne le modèle du capteur
|
||||
Returns the sensor's model, if any, None either
|
||||
'''
|
||||
return (self.get_value (self.sensor_dict, sensors_model_field))
|
||||
return (self.sensor_dict [self.sensors_model_field])
|
||||
|
||||
def get_version (self):
|
||||
'''
|
||||
Retourne la version du capteur
|
||||
Returns the sensor's version
|
||||
'''
|
||||
return (self.get_value (self.sensor_dict, sensors_version_field))
|
||||
return (self.sensor_dict [self.sensors_version_field])
|
||||
|
||||
def get_name (self):
|
||||
'''
|
||||
Retourne le nom du capteur
|
||||
Returns the sensor's name
|
||||
'''
|
||||
return (self.get_value (self.sensor_dict, sensors_name_field))
|
||||
return (self.sensor_dict [self.sensors_name_field])
|
||||
|
||||
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.sensor_dict, sensors_longname_field))
|
||||
return (self.sensor_dict [self.sensors_longname_field])
|
||||
|
||||
def get_namegender (self):
|
||||
'''
|
||||
Retourne le genre du nom du type de capteur en français
|
||||
M pour masculin et F pour féminin
|
||||
Return M for male and F for female.
|
||||
Only usefull for languages with gender on nouns
|
||||
'''
|
||||
return (self.get_value (self.sensor_dict, sensors_namegender_field))
|
||||
return (self.sensor_dict [self.sensors_namegender_field])
|
||||
|
||||
def get_batterylevel (self):
|
||||
'''
|
||||
Retourne le niveau de batterie sur 10
|
||||
Toute autre valeur doit être considérée comme venant d'un capteur n'ayant pas de batterie
|
||||
Returns the sensor's battery level, out of 10
|
||||
It seems that batteryless sensors return 255
|
||||
'''
|
||||
return (int (self.get_value (self.sensor_dict, sensors_batterylevel_field)))
|
||||
return (int (self.sensor_dict [self.sensors_batterylevel_field]))
|
||||
|
||||
def get_signal (self):
|
||||
'''
|
||||
Retourne le niveau de signal sur 10
|
||||
Tout autre valeur est pour un capteur connecté par câble
|
||||
Returns the sensor's signal quality, out of 10
|
||||
'''
|
||||
return (int (self.get_value (self.sensor_dict, 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):
|
||||
'''
|
||||
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.sensor_dict, 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):
|
||||
'''
|
||||
Retourne une capture de la caméra dans un objet PIL.Image
|
||||
Get a snapshot from the camera
|
||||
Return a PIL.Image object
|
||||
'''
|
||||
r = base_url + camera_snapshot + '&' + camera_snapshot_mac + '=' + self.get_mac ()
|
||||
a = bytes2image (self.get_or_autologin (r).read ())
|
||||
r = self.base_url + self.camera_snapshot + '?' + self.camera_snapshot_mac + '=' + self.get_mac ()
|
||||
a = self.bytes2image (self.opener.open (r).read ())
|
||||
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
|
||||
Gets the pet mode value
|
||||
Pet mode is a setting on movement sensibility, to avoid trigger on pet movements
|
||||
'''
|
||||
return (self.sensor_dict [camera_get_config_petmode] == '1')
|
||||
return (self.sensor_dict [self.camera_get_config_petmode] == '1')
|
||||
|
||||
def get_camera_recording (self):
|
||||
'''
|
||||
Retourne l'état de l'enregistrement vidéo 24/24
|
||||
Gets if the camera records or not
|
||||
'''
|
||||
return (self.sensor_dict [camera_get_config_recording] == '1')
|
||||
return (self.sensor_dict [self.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
|
||||
Gets if the camera records or not
|
||||
'''
|
||||
return (self.sensor_dict [camera_get_config_privacy] == '1')
|
||||
|
||||
def get_temperature (self):
|
||||
'''
|
||||
Retourne la température donnée par le capteur
|
||||
'''
|
||||
a = self.get_value (self.sensor_dict, 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.sensor_dict, sensors_temphum_root_field)
|
||||
for i in a:
|
||||
if i [1] [sensors_hum_name] == sensors_hum_text:
|
||||
return (int (i [2].replace ('%', '')))
|
||||
return (self.sensor_dict [self.camera_get_config_privacy] == '1')
|
Loading…
Reference in New Issue