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
This commit is contained in:
		
							parent
							
								
									4c6063cd8c
								
							
						
					
					
						commit
						c5d531eb10
					
				|  | @ -34,6 +34,7 @@ REMOTE_CONTROLER = 'REMOTE' | |||
| KEYPAD_CONTROLER = 'KEYPAD' | ||||
| PRESENCE_CAMERA_DETECTOR = 'PIR_CAMERA' | ||||
| 
 | ||||
| # This part of code must be the one to adapt to make work the underneath part | ||||
| from cookielib import CookieJar | ||||
| import urllib2 as request | ||||
| from urllib2 import HTTPError | ||||
|  | @ -41,28 +42,119 @@ from urllib import urlencode | |||
| from xml.etree import ElementTree as ET | ||||
| from datetime import datetime | ||||
| 
 | ||||
| def bytes2file (b): | ||||
| # Python2 and 3 adaptations | ||||
| # BEGIN | ||||
| def str2bytes (s): | ||||
| 	''' | ||||
| 	Gives a file-like class from a Bytes | ||||
| 	Bind to the bytes build-in function | ||||
| 	''' | ||||
| 	from io import BytesIO | ||||
| 	r = BytesIO () | ||||
| 	r.write (b) | ||||
| 	r.seek (0) | ||||
| 	return (r) | ||||
| 	return (bytes (s)) | ||||
| # END | ||||
| 
 | ||||
| def bytes2image (b): | ||||
| 	''' | ||||
| 	Gives a Image object from bytes | ||||
| 	Uses the bytes2file function | ||||
| 	''' | ||||
| 	from PIL import Image | ||||
| 	f = bytes2file (b) | ||||
| 	r = Image () | ||||
| 	r.open (f) | ||||
| 	return (r) | ||||
| # The next must stay a copy from the python3 library, starting from class Common | ||||
| 
 | ||||
| class HomeSFR (): | ||||
| class Common (): | ||||
| 	''' | ||||
| 	Common ressources to the library's classes | ||||
| 	''' | ||||
| 	 | ||||
| 	def __init__ (self): | ||||
| 		 | ||||
| 		# Specific configuration | ||||
| 		 | ||||
| 		self.base_url = 'https://home.sfr.fr' | ||||
| 		 | ||||
| 		# path to login test | ||||
| 		self.auth_path = '/mysensors' | ||||
| 		self.auth_ok = '/accueil'	# if logged | ||||
| 		self.auth_post_url = 'https://boutique.home.sfr.fr/authentification' | ||||
| 		self.auth_referer = 'https://boutique.home.sfr.fr/authentification?back=service' | ||||
| 		self.auth_user_field = 'email' | ||||
| 		self.auth_pass_field = 'passwd' | ||||
| 		self.auth_extra_fields = {'back': 'service', 'token_sso': '', 'error_sso': '', 'SubmitLogin': 'OK'} | ||||
| 		self.auth_logout_path = '/deconnexion' | ||||
| 		 | ||||
| 		# Path to sensors and mode | ||||
| 		self.sensors_list = '/mysensors' | ||||
| 		 | ||||
| 		# Path to list of alerts | ||||
| 		self.alerts_path = '/listalert' | ||||
| 		 | ||||
| 		# Path to get and set modes | ||||
| 		self.mode_get_path = '/mysensors' | ||||
| 		self.mode_get_label = 'alarm_mode' | ||||
| 		self.mode_set_path = '/alarmmode' | ||||
| 		self.mode_set_field = 'action'	# Name for GET field | ||||
| 		self.mode_off = 'OFF'		# Value for off | ||||
| 		self.mode_custom = 'CUSTOM'	# Value for custom | ||||
| 		self.mode_on = 'ON'		# Value for on | ||||
| 		 | ||||
| 		# Cameras | ||||
| 		self.cameras_list = '/homescope/mycams' | ||||
| 		self.camera_snapshot = '/homescope/snapshot' | ||||
| 		self.camera_snapshot_mac = 'mac' | ||||
| 		self.camera_video = '/homescope/flv' | ||||
| 		self.camera_vide_mac = 'mac' | ||||
| 		self.camera_recordings_list = '/listenr' | ||||
| 		self.camera_recordings_delete = '/delenr' | ||||
| 		self.camera_recordings_start = '/homescope/record' | ||||
| 		self.camera_recordings_stop = '/homescope/stoprecord' | ||||
| 		self.camera_recordings_mac = 'mac' | ||||
| 		self.camera_get_config_path = '/homescope/camsettings' | ||||
| 		self.camera_get_config_mac = 'mac' | ||||
| 		self.camera_get_config_flip = 'FLIP' | ||||
| 		self.camera_get_config_leds = 'LEDMODE' # set to 0 to turn the leds on | ||||
| 		self.camera_get_config_detectionsensibility = 'DP' # from 1 to 4 | ||||
| 		self.camera_get_config_recording = 'REC24' | ||||
| 		self.camera_get_config_name = 'NAME' | ||||
| 		self.camera_set_config_path = '/homescope/camsettings' | ||||
| 		self.camera_set_config_mac = 'mac' | ||||
| 		self.camera_set_config_flip = 'flip' | ||||
| 		self.camera_set_config_leds = 'led_mode' # set to 0 to turn the leds on | ||||
| 		self.camera_set_config_detectionsensibility = 'dp' # from 1 to 4 | ||||
| 		self.camera_set_config_recording = 'rec24' | ||||
| 		self.camera_set_config_name = 'name' | ||||
| 		 | ||||
| 		# Sensors | ||||
| 		self.sensors_list = '/mysensors' | ||||
| 		self.sensors_label = 'Sensor' | ||||
| 		self.sensors_label_id = 'id' | ||||
| 		self.sensors_mac_field = 'deviceMac' | ||||
| 		self.sensors_type_field = 'deviceType' | ||||
| 		self.sensors_model_field = 'deviceModel' | ||||
| 		self.sensors_version_field = 'deviceVersion' | ||||
| 		self.sensors_name_field = 'name' | ||||
| 		self.sensors_longname_field = 'long_name' | ||||
| 		self.sensors_namegender_field = 'name_gender' # Only usefull for French for the moment | ||||
| 		self.sensors_batterylevel_field = 'batteryLevel' | ||||
| 		self.sensors_signal_field = 'signalLevel' | ||||
| 		self.sensors_lasttrigger_field = 'lastTriggerTime' | ||||
| 		self.sensors_lasttrigger_dateformat = '%Y-%m-%d %H:%M:%S' | ||||
| 		self.sensors_status_field = 'status' | ||||
| 		self.sensors_status_value_ok = 'OK' | ||||
| 		# I don't have any other value for the moment | ||||
| 
 | ||||
| 	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) | ||||
| 
 | ||||
| class HomeSFR (Common): | ||||
| 	def __init__ (self, username = None, password = None, cookies = None, debug = False, autologin = True): | ||||
| 		''' | ||||
| 		Sets the class with username and password couple, or cookies | ||||
|  | @ -71,6 +163,7 @@ class HomeSFR (): | |||
| 		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) | ||||
|  | @ -97,49 +190,7 @@ class HomeSFR (): | |||
| 		else: | ||||
| 			self.cookies = cookies | ||||
| 		self.opener = request.build_opener (request.HTTPCookieProcessor (self.cookies)) | ||||
| 		 | ||||
| 		# Specific configuration | ||||
| 		self.base_url = 'https://home.sfr.fr' | ||||
| 		 | ||||
| 		# path to login test | ||||
| 		self.auth_path = '/mysensors' | ||||
| 		self.auth_ok = '/accueil'	# if logged | ||||
| 		self.auth_post_url = 'https://boutique.home.sfr.fr/authentification' | ||||
| 		self.auth_referer = 'https://boutique.home.sfr.fr/authentification?back=service' | ||||
| 		self.auth_user_field = 'email' | ||||
| 		self.auth_pass_field = 'passwd' | ||||
| 		self.auth_extra_fields = {'back': 'service', 'token_sso': '', 'error_sso': '', 'SubmitLogin': 'OK'} | ||||
| 		self.auth_logout_path = '/deconnexion' | ||||
| 		 | ||||
| 		# Path to sensors and mode | ||||
| 		self.sensors_list = '/mysensors' | ||||
| 		self.sensors_label = 'Sensor' | ||||
| 		self.sensors_label_id = 'id' | ||||
| 		 | ||||
| 		# 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 | ||||
| 		# mac=00:0e:8f:c9:59:44&flip=0&led_mode=0&alert_pan=1&rec24=0&da=1&dp=4&name=Salon | ||||
| 		self.cameras_list = '/homescope/mycams' | ||||
| 		self.camera_snapshot = '/homescope/snapshot' | ||||
| 		self.camera_video = '/homescope/flv' | ||||
| 		self.camera_get_config_path = '/homescope/camsettings' | ||||
| 		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, | ||||
| 		 | ||||
| 	 | ||||
| 	def __str__ (self): | ||||
| 		''' | ||||
| 		Shows name, version, defined user and debug state | ||||
|  | @ -178,7 +229,7 @@ class HomeSFR (): | |||
| 			data = self.auth_extra_fields | ||||
| 			data [self.auth_user_field] = self.username | ||||
| 			data [self.auth_pass_field] = self.password | ||||
| 			data = bytes (urlencode (data)) | ||||
| 			data = str2bytes (urlencode (data)) | ||||
| 			if self.DEBUG: | ||||
| 				print ('Cookies ' + str( len(self.cookies))) | ||||
| 				print ('Sending data ' + str (data)) | ||||
|  | @ -243,7 +294,7 @@ class HomeSFR (): | |||
| 		r = self.base_url + self.mode_get_path | ||||
| 		if self.DEBUG: | ||||
| 			print ('Getting ' + r) | ||||
| 		a = bytes2file (self.opener.open (r).read ()) | ||||
| 		a = self.bytes2file (self.opener.open (r).read ()) | ||||
| 		b = ET.parse (a).getroot () | ||||
| 		c = b.get (self.mode_get_label) | ||||
| 		if self.DEBUG: | ||||
|  | @ -263,7 +314,7 @@ class HomeSFR (): | |||
| 		if (self.autologin and self.test_login () == False): | ||||
| 			self.login () | ||||
| 		r = self.base_url + self.sensors_list | ||||
| 		a = bytes2file (self.opener.open (r).read ()) | ||||
| 		a = self.bytes2file (self.opener.open (r).read ()) | ||||
| 		b = ET.parse (a) | ||||
| 		r = [] | ||||
| 		for i in b.findall (self.sensors_label): | ||||
|  | @ -275,29 +326,11 @@ class HomeSFR (): | |||
| 		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 | ||||
| 		''' | ||||
| 		def build_tree (element): | ||||
| 			r = {} | ||||
| 			if self.DEBUG: | ||||
| 				print ('Diving in the element ' + element.tag) | ||||
| 			for i in element.getchildren (): | ||||
| 				if i.getchildren () == []: | ||||
| 					r.update ({i.tag: i.text}) | ||||
| 				else: | ||||
| 					r.update ({i.tag: build_tree (i)}) | ||||
| 			return (r) | ||||
| 		if (self.autologin and self.test_login () == False): | ||||
| 			self.login () | ||||
| 		r = self.base_url + self.sensors_list | ||||
| 		a = bytes2file (self.opener.open (r).read ()) | ||||
| 		b = ET.parse (a) | ||||
| 		r = None | ||||
| 		for i in b.findall (self.sensors_label): | ||||
| 			if self.DEBUG: | ||||
| 				print ('Testing sensors ' + i.get (self.sensors_label_id)) | ||||
| 			if (i.get (self.sensors_label_id) == id): | ||||
| 				r = build_tree (i) | ||||
| 				break | ||||
| 		return (Sensor (r)) | ||||
| 		r = Sensor (id, self.opener) | ||||
| 		r.refresh () | ||||
| 		return (r) | ||||
| 	 | ||||
| 	def get_all_sensors (self): | ||||
| 		''' | ||||
|  | @ -307,32 +340,52 @@ class HomeSFR (): | |||
| 		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 | ||||
| 		''' | ||||
| 		if (self.autologin and self.test_login () == False): | ||||
| 			self.login () | ||||
| 		r = Camera (id, self.opener) | ||||
| 		r.refresh () | ||||
| 		return (r) | ||||
| 
 | ||||
| class Sensor: | ||||
| class Sensor (Common): | ||||
| 	''' | ||||
| 	Class used to read easily the sensors | ||||
| 	''' | ||||
| 	def __init__ (self, sensor_dict): | ||||
| 	def __init__ (self, id, opener): | ||||
| 		''' | ||||
| 		Initialize the class with the dict producted by HomeSFR.get_sensors () | ||||
| 		''' | ||||
| 		Common.__init__ (self) | ||||
| 		 | ||||
| 		self.sensor_dict = sensor_dict | ||||
| 		 | ||||
| 		# Field names | ||||
| 		self.type_field = 'deviceType' | ||||
| 		self.model_field = 'deviceModel' | ||||
| 		self.version_field = 'deviceVersion' | ||||
| 		self.name_field = 'name' | ||||
| 		self.longname_field = 'long_name' | ||||
| 		self.namegender_field = 'name_gender' # Only usefull for French for the moment | ||||
| 		self.batterylevel_field = 'batteryLevel' | ||||
| 		self.signal_field = 'signalLevel' | ||||
| 		self.lasttrigger_field = 'lastTriggerTime' | ||||
| 		self.lasttrigger_dateformat = '%Y-%m-%d %H:%M:%S' | ||||
| 		self.status_field = 'status' | ||||
| 		self.status_value_ok = 'OK' | ||||
| 		# I don't have any other value for the moment | ||||
| 		self.id = id | ||||
| 		self.sensor_dict = None | ||||
| 		self.opener = opener | ||||
| 	 | ||||
| 	def refresh (self): | ||||
| 		''' | ||||
| 		Gets or refresh the data for the sensor | ||||
| 		''' | ||||
| 		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) | ||||
| 		r = self.base_url + self.sensors_list | ||||
| 		a = self.bytes2file (self.opener.open (r).read ()) | ||||
| 		a.seek (0) | ||||
| 		b = ET.parse (a) | ||||
| 		self.sensor_dict = None | ||||
| 		for i in b.findall (self.sensors_label): | ||||
| 			if (i.get (self.sensors_label_id) == self.id): | ||||
| 				self.sensor_dict = build_tree (i) | ||||
| 				break | ||||
| 	 | ||||
| 	def get_raw (self): | ||||
| 		''' | ||||
|  | @ -340,72 +393,97 @@ class Sensor: | |||
| 		''' | ||||
| 		return (self.sensor_dict) | ||||
| 	 | ||||
| 	def get_mac (self): | ||||
| 		''' | ||||
| 		Returns the sensor's model, if any, None either | ||||
| 		''' | ||||
| 		return (self.sensor_dict [self.sensors_mac_field]) | ||||
| 	 | ||||
| 	def get_type (self): | ||||
| 		''' | ||||
| 		Returns the sensor's type | ||||
| 		''' | ||||
| 		return (self.sensor_dict [self.type_field]) | ||||
| 		return (self.sensor_dict [self.sensors_type_field]) | ||||
| 	 | ||||
| 	def get_model (self): | ||||
| 		''' | ||||
| 		Returns the sensor's model, if any, None either | ||||
| 		''' | ||||
| 		return (self.sensor_dict [self.model_field]) | ||||
| 		return (self.sensor_dict [self.sensors_model_field]) | ||||
| 	 | ||||
| 	def get_version (self): | ||||
| 		''' | ||||
| 		Returns the sensor's version | ||||
| 		''' | ||||
| 		return (self.sensor_dict [self.version_field]) | ||||
| 		return (self.sensor_dict [self.sensors_version_field]) | ||||
| 	 | ||||
| 	def get_name (self): | ||||
| 		''' | ||||
| 		Returns the sensor's name | ||||
| 		''' | ||||
| 		return (self.sensor_dict [self.name_field]) | ||||
| 		return (self.sensor_dict [self.sensors_name_field]) | ||||
| 	 | ||||
| 	def get_longname (self): | ||||
| 		''' | ||||
| 		Returns the sensor's type name in system's language and the sensor's name | ||||
| 		''' | ||||
| 		return (self.sensor_dict [self.longname_field]) | ||||
| 		return (self.sensor_dict [self.sensors_longname_field]) | ||||
| 	 | ||||
| 	def get_namegender (self): | ||||
| 		''' | ||||
| 		Return M for male and F for female. | ||||
| 		Only usefull for languages with gender on nouns | ||||
| 		''' | ||||
| 		return (self.sensor_dict [self.namegender_field]) | ||||
| 		return (self.sensor_dict [self.sensors_namegender_field]) | ||||
| 	 | ||||
| 	def get_batterylevel (self): | ||||
| 		''' | ||||
| 		Returns the sensor's battery level, out of 10 | ||||
| 		It seems that batteryless sensors return 255 | ||||
| 		''' | ||||
| 		return (int (self.sensor_dict [self.batterylevel_field])) | ||||
| 		return (int (self.sensor_dict [self.sensors_batterylevel_field])) | ||||
| 	 | ||||
| 	def get_signal (self): | ||||
| 		''' | ||||
| 		Returns the sensor's signal quality, out of 10 | ||||
| 		''' | ||||
| 		return (int (self.sensor_dict [self.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.lasttrigger_field] | ||||
| 		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.lasttrigger_dateformat) | ||||
| 			b = datetime.strptime (a, self.sensors_lasttrigger_dateformat) | ||||
| 		except ValueError: | ||||
| 			return (0) | ||||
| 		r = int (b.strftime ('%s')) | ||||
| 		r = int (b.timestamp ()) | ||||
| 		return (r) | ||||
| 	 | ||||
| 	def get_status (self): | ||||
| 		''' | ||||
| 		Returns True is the sensor is OK, False either | ||||
| 		''' | ||||
| 		return (self.sensor_dict [self.status_field] == self.status_value_ok) | ||||
| 		return (self.sensor_dict [self.sensors_status_field] == self.sensors_status_value_ok) | ||||
| 
 | ||||
| class Camera (Sensor): | ||||
| 	''' | ||||
| 	Class used to manipulate easily cameras | ||||
| 	''' | ||||
| 	def __init__ (self, sensor_dict, opener): | ||||
| 		''' | ||||
| 		Initialize the class with the dict producted by HomeSFR.get_camera () | ||||
| 		''' | ||||
| 		Sensor.__init__ (self, sensor_dict, opener) | ||||
| 	 | ||||
| 	def get_snapshot (self): | ||||
| 		''' | ||||
| 		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) | ||||
|  | @ -7,6 +7,7 @@ This is a wrap aroud website, this could stop working without prior notice | |||
| ''' | ||||
| 
 | ||||
| # TODO: | ||||
| ## Put common ressources in the Common class | ||||
| ## Manage cameras | ||||
| ### Get image | ||||
| ### Get video | ||||
|  | @ -39,28 +40,117 @@ from xml.etree import ElementTree as ET | |||
| from urllib.error import HTTPError | ||||
| from datetime import datetime | ||||
| 
 | ||||
| def bytes2file (b): | ||||
| # Python2 and 3 adaptations | ||||
| # BEGIN | ||||
| def str2bytes (s): | ||||
| 	''' | ||||
| 	Gives a file-like class from a Bytes | ||||
| 	Bind to the bytes build-in function | ||||
| 	''' | ||||
| 	from io import BytesIO | ||||
| 	r = BytesIO () | ||||
| 	r.write (b) | ||||
| 	r.seek (0) | ||||
| 	return (r) | ||||
| 	return (bytes (s, 'UTF8')) | ||||
| # END | ||||
| 
 | ||||
| def bytes2image (b): | ||||
| class Common (): | ||||
| 	''' | ||||
| 	Gives a Image object from bytes | ||||
| 	Uses the bytes2file function | ||||
| 	Common ressources to the library's classes | ||||
| 	''' | ||||
| 	from PIL import Image | ||||
| 	f = bytes2file (b) | ||||
| 	r = Image () | ||||
| 	r.open (f) | ||||
| 	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 = '/accueil'	# if logged | ||||
| 		self.auth_post_url = 'https://boutique.home.sfr.fr/authentification' | ||||
| 		self.auth_referer = 'https://boutique.home.sfr.fr/authentification?back=service' | ||||
| 		self.auth_user_field = 'email' | ||||
| 		self.auth_pass_field = 'passwd' | ||||
| 		self.auth_extra_fields = {'back': 'service', 'token_sso': '', 'error_sso': '', 'SubmitLogin': 'OK'} | ||||
| 		self.auth_logout_path = '/deconnexion' | ||||
| 		 | ||||
| 		# Path to sensors and mode | ||||
| 		self.sensors_list = '/mysensors' | ||||
| 		 | ||||
| 		# Path to list of alerts | ||||
| 		self.alerts_path = '/listalert' | ||||
| 		 | ||||
| 		# Path to get and set modes | ||||
| 		self.mode_get_path = '/mysensors' | ||||
| 		self.mode_get_label = 'alarm_mode' | ||||
| 		self.mode_set_path = '/alarmmode' | ||||
| 		self.mode_set_field = 'action'	# Name for GET field | ||||
| 		self.mode_off = 'OFF'		# Value for off | ||||
| 		self.mode_custom = 'CUSTOM'	# Value for custom | ||||
| 		self.mode_on = 'ON'		# Value for on | ||||
| 		 | ||||
| 		# Cameras | ||||
| 		self.cameras_list = '/homescope/mycams' | ||||
| 		self.camera_snapshot = '/homescope/snapshot' | ||||
| 		self.camera_snapshot_mac = 'mac' | ||||
| 		self.camera_video = '/homescope/flv' | ||||
| 		self.camera_vide_mac = 'mac' | ||||
| 		self.camera_recordings_list = '/listenr' | ||||
| 		self.camera_recordings_delete = '/delenr' | ||||
| 		self.camera_recordings_start = '/homescope/record' | ||||
| 		self.camera_recordings_stop = '/homescope/stoprecord' | ||||
| 		self.camera_recordings_mac = 'mac' | ||||
| 		self.camera_get_config_path = '/homescope/camsettings' | ||||
| 		self.camera_get_config_mac = 'mac' | ||||
| 		self.camera_get_config_flip = 'FLIP' | ||||
| 		self.camera_get_config_leds = 'LEDMODE' # set to 0 to turn the leds on | ||||
| 		self.camera_get_config_detectionsensibility = 'DP' # from 1 to 4 | ||||
| 		self.camera_get_config_recording = 'REC24' | ||||
| 		self.camera_get_config_name = 'NAME' | ||||
| 		self.camera_set_config_path = '/homescope/camsettings' | ||||
| 		self.camera_set_config_mac = 'mac' | ||||
| 		self.camera_set_config_flip = 'flip' | ||||
| 		self.camera_set_config_leds = 'led_mode' # set to 0 to turn the leds on | ||||
| 		self.camera_set_config_detectionsensibility = 'dp' # from 1 to 4 | ||||
| 		self.camera_set_config_recording = 'rec24' | ||||
| 		self.camera_set_config_name = 'name' | ||||
| 		 | ||||
| 		# Sensors | ||||
| 		self.sensors_list = '/mysensors' | ||||
| 		self.sensors_label = 'Sensor' | ||||
| 		self.sensors_label_id = 'id' | ||||
| 		self.sensors_mac_field = 'deviceMac' | ||||
| 		self.sensors_type_field = 'deviceType' | ||||
| 		self.sensors_model_field = 'deviceModel' | ||||
| 		self.sensors_version_field = 'deviceVersion' | ||||
| 		self.sensors_name_field = 'name' | ||||
| 		self.sensors_longname_field = 'long_name' | ||||
| 		self.sensors_namegender_field = 'name_gender' # Only usefull for French for the moment | ||||
| 		self.sensors_batterylevel_field = 'batteryLevel' | ||||
| 		self.sensors_signal_field = 'signalLevel' | ||||
| 		self.sensors_lasttrigger_field = 'lastTriggerTime' | ||||
| 		self.sensors_lasttrigger_dateformat = '%Y-%m-%d %H:%M:%S' | ||||
| 		self.sensors_status_field = 'status' | ||||
| 		self.sensors_status_value_ok = 'OK' | ||||
| 		# I don't have any other value for the moment | ||||
| 
 | ||||
| class HomeSFR (): | ||||
| 	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) | ||||
| 
 | ||||
| class HomeSFR (Common): | ||||
| 	def __init__ (self, username = None, password = None, cookies = None, debug = False, autologin = True): | ||||
| 		''' | ||||
| 		Sets the class with username and password couple, or cookies | ||||
|  | @ -69,6 +159,7 @@ class HomeSFR (): | |||
| 		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) | ||||
|  | @ -95,48 +186,6 @@ class HomeSFR (): | |||
| 		else: | ||||
| 			self.cookies = cookies | ||||
| 		self.opener = request.build_opener (request.HTTPCookieProcessor (self.cookies)) | ||||
| 		 | ||||
| 		# Specific configuration | ||||
| 		self.base_url = 'https://home.sfr.fr' | ||||
| 		 | ||||
| 		# path to login test | ||||
| 		self.auth_path = '/mysensors' | ||||
| 		self.auth_ok = '/accueil'	# if logged | ||||
| 		self.auth_post_url = 'https://boutique.home.sfr.fr/authentification' | ||||
| 		self.auth_referer = 'https://boutique.home.sfr.fr/authentification?back=service' | ||||
| 		self.auth_user_field = 'email' | ||||
| 		self.auth_pass_field = 'passwd' | ||||
| 		self.auth_extra_fields = {'back': 'service', 'token_sso': '', 'error_sso': '', 'SubmitLogin': 'OK'} | ||||
| 		self.auth_logout_path = '/deconnexion' | ||||
| 		 | ||||
| 		# Path to sensors and mode | ||||
| 		self.sensors_list = '/mysensors' | ||||
| 		self.sensors_label = 'Sensor' | ||||
| 		self.sensors_label_id = 'id' | ||||
| 		 | ||||
| 		# 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 | ||||
| 		# mac=00:0e:8f:c9:59:44&flip=0&led_mode=0&alert_pan=1&rec24=0&da=1&dp=4&name=Salon | ||||
| 		self.cameras_list = '/homescope/mycams' | ||||
| 		self.camera_snapshot = '/homescope/snapshot' | ||||
| 		self.camera_video = '/homescope/flv' | ||||
| 		self.camera_get_config_path = '/homescope/camsettings' | ||||
| 		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, | ||||
| 	 | ||||
| 	def __str__ (self): | ||||
| 		''' | ||||
|  | @ -176,7 +225,7 @@ class HomeSFR (): | |||
| 			data = self.auth_extra_fields | ||||
| 			data [self.auth_user_field] = self.username | ||||
| 			data [self.auth_pass_field] = self.password | ||||
| 			data = bytes (urlencode (data), 'UTF8') | ||||
| 			data = str2bytes (urlencode (data)) | ||||
| 			if self.DEBUG: | ||||
| 				print ('Cookies ' + str( len(self.cookies))) | ||||
| 				print ('Sending data ' + str (data)) | ||||
|  | @ -241,7 +290,7 @@ class HomeSFR (): | |||
| 		r = self.base_url + self.mode_get_path | ||||
| 		if self.DEBUG: | ||||
| 			print ('Getting ' + r) | ||||
| 		a = bytes2file (self.opener.open (r).readall ()) | ||||
| 		a = self.bytes2file (self.opener.open (r).read ()) | ||||
| 		b = ET.parse (a).getroot () | ||||
| 		c = b.get (self.mode_get_label) | ||||
| 		if self.DEBUG: | ||||
|  | @ -261,7 +310,7 @@ class HomeSFR (): | |||
| 		if (self.autologin and self.test_login () == False): | ||||
| 			self.login () | ||||
| 		r = self.base_url + self.sensors_list | ||||
| 		a = bytes2file (self.opener.open (r).readall ()) | ||||
| 		a = self.bytes2file (self.opener.open (r).read ()) | ||||
| 		b = ET.parse (a) | ||||
| 		r = [] | ||||
| 		for i in b.findall (self.sensors_label): | ||||
|  | @ -273,29 +322,11 @@ class HomeSFR (): | |||
| 		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 | ||||
| 		''' | ||||
| 		def build_tree (element): | ||||
| 			r = {} | ||||
| 			if self.DEBUG: | ||||
| 				print ('Diving in the element ' + element.tag) | ||||
| 			for i in element.getchildren (): | ||||
| 				if i.getchildren () == []: | ||||
| 					r.update ({i.tag: i.text}) | ||||
| 				else: | ||||
| 					r.update ({i.tag: build_tree (i)}) | ||||
| 			return (r) | ||||
| 		if (self.autologin and self.test_login () == False): | ||||
| 			self.login () | ||||
| 		r = self.base_url + self.sensors_list | ||||
| 		a = bytes2file (self.opener.open (r).readall ()) | ||||
| 		b = ET.parse (a) | ||||
| 		r = None | ||||
| 		for i in b.findall (self.sensors_label): | ||||
| 			if self.DEBUG: | ||||
| 				print ('Testing sensors ' + i.get (self.sensors_label_id)) | ||||
| 			if (i.get (self.sensors_label_id) == id): | ||||
| 				r = build_tree (i) | ||||
| 				break | ||||
| 		return (Sensor (r)) | ||||
| 		r = Sensor (id, self.opener) | ||||
| 		r.refresh () | ||||
| 		return (r) | ||||
| 	 | ||||
| 	def get_all_sensors (self): | ||||
| 		''' | ||||
|  | @ -305,32 +336,52 @@ class HomeSFR (): | |||
| 		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 | ||||
| 		''' | ||||
| 		if (self.autologin and self.test_login () == False): | ||||
| 			self.login () | ||||
| 		r = Camera (id, self.opener) | ||||
| 		r.refresh () | ||||
| 		return (r) | ||||
| 
 | ||||
| class Sensor: | ||||
| class Sensor (Common): | ||||
| 	''' | ||||
| 	Class used to read easily the sensors | ||||
| 	''' | ||||
| 	def __init__ (self, sensor_dict): | ||||
| 	def __init__ (self, id, opener): | ||||
| 		''' | ||||
| 		Initialize the class with the dict producted by HomeSFR.get_sensors () | ||||
| 		''' | ||||
| 		Common.__init__ (self) | ||||
| 		 | ||||
| 		self.sensor_dict = sensor_dict | ||||
| 		 | ||||
| 		# Field names | ||||
| 		self.type_field = 'deviceType' | ||||
| 		self.model_field = 'deviceModel' | ||||
| 		self.version_field = 'deviceVersion' | ||||
| 		self.name_field = 'name' | ||||
| 		self.longname_field = 'long_name' | ||||
| 		self.namegender_field = 'name_gender' # Only usefull for French for the moment | ||||
| 		self.batterylevel_field = 'batteryLevel' | ||||
| 		self.signal_field = 'signalLevel' | ||||
| 		self.lasttrigger_field = 'lastTriggerTime' | ||||
| 		self.lasttrigger_dateformat = '%Y-%m-%d %H:%M:%S' | ||||
| 		self.status_field = 'status' | ||||
| 		self.status_value_ok = 'OK' | ||||
| 		# I don't have any other value for the moment | ||||
| 		self.id = id | ||||
| 		self.sensor_dict = None | ||||
| 		self.opener = opener | ||||
| 	 | ||||
| 	def refresh (self): | ||||
| 		''' | ||||
| 		Gets or refresh the data for the sensor | ||||
| 		''' | ||||
| 		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) | ||||
| 		r = self.base_url + self.sensors_list | ||||
| 		a = self.bytes2file (self.opener.open (r).read ()) | ||||
| 		a.seek (0) | ||||
| 		b = ET.parse (a) | ||||
| 		self.sensor_dict = None | ||||
| 		for i in b.findall (self.sensors_label): | ||||
| 			if (i.get (self.sensors_label_id) == self.id): | ||||
| 				self.sensor_dict = build_tree (i) | ||||
| 				break | ||||
| 	 | ||||
| 	def get_raw (self): | ||||
| 		''' | ||||
|  | @ -338,65 +389,71 @@ class Sensor: | |||
| 		''' | ||||
| 		return (self.sensor_dict) | ||||
| 	 | ||||
| 	def get_mac (self): | ||||
| 		''' | ||||
| 		Returns the sensor's model, if any, None either | ||||
| 		''' | ||||
| 		return (self.sensor_dict [self.sensors_mac_field]) | ||||
| 	 | ||||
| 	def get_type (self): | ||||
| 		''' | ||||
| 		Returns the sensor's type | ||||
| 		''' | ||||
| 		return (self.sensor_dict [self.type_field]) | ||||
| 		return (self.sensor_dict [self.sensors_type_field]) | ||||
| 	 | ||||
| 	def get_model (self): | ||||
| 		''' | ||||
| 		Returns the sensor's model, if any, None either | ||||
| 		''' | ||||
| 		return (self.sensor_dict [self.model_field]) | ||||
| 		return (self.sensor_dict [self.sensors_model_field]) | ||||
| 	 | ||||
| 	def get_version (self): | ||||
| 		''' | ||||
| 		Returns the sensor's version | ||||
| 		''' | ||||
| 		return (self.sensor_dict [self.version_field]) | ||||
| 		return (self.sensor_dict [self.sensors_version_field]) | ||||
| 	 | ||||
| 	def get_name (self): | ||||
| 		''' | ||||
| 		Returns the sensor's name | ||||
| 		''' | ||||
| 		return (self.sensor_dict [self.name_field]) | ||||
| 		return (self.sensor_dict [self.sensors_name_field]) | ||||
| 	 | ||||
| 	def get_longname (self): | ||||
| 		''' | ||||
| 		Returns the sensor's type name in system's language and the sensor's name | ||||
| 		''' | ||||
| 		return (self.sensor_dict [self.longname_field]) | ||||
| 		return (self.sensor_dict [self.sensors_longname_field]) | ||||
| 	 | ||||
| 	def get_namegender (self): | ||||
| 		''' | ||||
| 		Return M for male and F for female. | ||||
| 		Only usefull for languages with gender on nouns | ||||
| 		''' | ||||
| 		return (self.sensor_dict [self.namegender_field]) | ||||
| 		return (self.sensor_dict [self.sensors_namegender_field]) | ||||
| 	 | ||||
| 	def get_batterylevel (self): | ||||
| 		''' | ||||
| 		Returns the sensor's battery level, out of 10 | ||||
| 		It seems that batteryless sensors return 255 | ||||
| 		''' | ||||
| 		return (int (self.sensor_dict [self.batterylevel_field])) | ||||
| 		return (int (self.sensor_dict [self.sensors_batterylevel_field])) | ||||
| 	 | ||||
| 	def get_signal (self): | ||||
| 		''' | ||||
| 		Returns the sensor's signal quality, out of 10 | ||||
| 		''' | ||||
| 		return (int (self.sensor_dict [self.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.lasttrigger_field] | ||||
| 		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.lasttrigger_dateformat) | ||||
| 			b = datetime.strptime (a, self.sensors_lasttrigger_dateformat) | ||||
| 		except ValueError: | ||||
| 			return (0) | ||||
| 		r = int (b.timestamp ()) | ||||
|  | @ -406,4 +463,23 @@ class Sensor: | |||
| 		''' | ||||
| 		Returns True is the sensor is OK, False either | ||||
| 		''' | ||||
| 		return (self.sensor_dict [self.status_field] == self.status_value_ok) | ||||
| 		return (self.sensor_dict [self.sensors_status_field] == self.sensors_status_value_ok) | ||||
| 
 | ||||
| class Camera (Sensor): | ||||
| 	''' | ||||
| 	Class used to manipulate easily cameras | ||||
| 	''' | ||||
| 	def __init__ (self, sensor_dict, opener): | ||||
| 		''' | ||||
| 		Initialize the class with the dict producted by HomeSFR.get_camera () | ||||
| 		''' | ||||
| 		Sensor.__init__ (self, sensor_dict, opener) | ||||
| 	 | ||||
| 	def get_snapshot (self): | ||||
| 		''' | ||||
| 		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) | ||||
		Loading…
	
		Reference in New Issue