1212"""
1313
1414import warnings
15- if __name__ == "__main__" : warnings .filterwarnings ("ignore" ) # For installation test only
15+ if __name__ == "__main__" : warnings .filterwarnings ("ignore" ) # For installation test only
1616
1717from sys import version_info
1818from os import getenv
@@ -87,20 +87,20 @@ def getParameter(key, default):
8787_GETHOMEDATA_REQ = _BASE_URL + "api/gethomedata"
8888_GETCAMERAPICTURE_REQ = _BASE_URL + "api/getcamerapicture"
8989_GETEVENTSUNTIL_REQ = _BASE_URL + "api/geteventsuntil"
90- _HOME_STATUS = _BASE_URL + "api/homestatus" # Used for Home+ Control Devices
91- _GETHOMES_DATA = _BASE_URL + "api/homesdata" # New API
92- _GETHOMECOACH = _BASE_URL + "api/gethomecoachsdata" #
90+ _HOME_STATUS = _BASE_URL + "api/homestatus" # Used for Home+ Control Devices
91+ _GETHOMES_DATA = _BASE_URL + "api/homesdata" # New API
92+ _GETHOMECOACH = _BASE_URL + "api/gethomecoachsdata" #
9393
9494#TODO# Undocumented (but would be very usefull) API : Access currently forbidden (403)
9595
9696_POST_UPDATE_HOME_REQ = _BASE_URL + "/api/updatehome"
9797
9898# For presence setting (POST BODY):
99- # _PRES_BODY_REC_SET = "home_id=%s&presence_settings[presence_record_%s]=%s" # (HomeId, DetectionKind, DetectionSetup.index)
99+ # _PRES_BODY_REC_SET = "home_id=%s&presence_settings[presence_record_%s]=%s" # (HomeId, DetectionKind, DetectionSetup.index)
100100_PRES_DETECTION_KIND = ("humans" , "animals" , "vehicles" , "movements" )
101101_PRES_DETECTION_SETUP = ("ignore" , "record" , "record & notify" )
102102
103- # _PRES_BODY_ALERT_TIME = "home_id=%s&presence_settings[presence_notify_%s]=%s" # (HomeID, "from"|"to", "hh:mm")
103+ # _PRES_BODY_ALERT_TIME = "home_id=%s&presence_settings[presence_notify_%s]=%s" # (HomeID, "from"|"to", "hh:mm")
104104
105105# Regular (documented) commands (both cameras)
106106
@@ -113,14 +113,14 @@ def getParameter(key, default):
113113
114114_PRES_CDE_GET_LIGHT = "/command/floodlight_get_config"
115115# Not working yet, probably due to scope restriction
116- #_PRES_CDE_SET_LIGHT = "/command/floodlight_set_config?config=mode:%s" # "auto"|"on"|"off"
116+ #_PRES_CDE_SET_LIGHT = "/command/floodlight_set_config?config=mode:%s" # "auto"|"on"|"off"
117117
118118
119119# For all cameras
120120
121- _CAM_CHANGE_STATUS = "/command/changestatus?status=%s" # "on"|"off"
121+ _CAM_CHANGE_STATUS = "/command/changestatus?status=%s" # "on"|"off"
122122# Not working yet
123- #_CAM_FTP_ACTIVE = "/command/ftp_set_config?config=on_off:%s" # "on"|"off"
123+ #_CAM_FTP_ACTIVE = "/command/ftp_set_config?config=on_off:%s" # "on"|"off"
124124
125125#Known TYPE used by Netatmo services + API, there can be more types possible
126126TYPES = {
@@ -135,30 +135,30 @@ def getParameter(key, default):
135135 'BNDL' : ["Bticino Doorlock" , 'Home + Security' ],
136136 'BNEU' : ["Bticino external unit" , 'Home + Security' ],
137137 'BNFC' : ["Bticino Thermostat" , 'Home+Control' ],
138- 'BNMH' : ["Bticino My Home Server 1" , 'Home + Security' ], # also API Home+Control GATEWAY
138+ 'BNMH' : ["Bticino My Home Server 1" , 'Home + Security' ], # also API Home+Control GATEWAY
139139 'BNSE' : ["Bticino Alarm Sensor" , 'Home + Security' ],
140140 'BNSL' : ["Bticino Staircase Light" , 'Home + Security' ],
141141 'BNTH' : ["Bticino Thermostat" , 'Home+Control' ],
142142 'BNTR' : ["Bticino module towel rail" , 'Home+Control' ],
143143 'BNXM' : ["Bticino X meter" , 'Home+Control' ],
144144
145- 'NACamera' : ["indoor camera" , 'Home + Security' ],
145+ 'NACamera' : ["indoor camera" , 'Home + Security' ],
146146 'NACamDoorTag' : ["door tag" , 'Home + Security' ],
147147 'NAMain' : ["weather station" , 'Weather' ],
148148 'NAModule1' : ["outdoor unit" , 'Weather' ],
149149 'NAModule2' : ["wind unit" , 'Weather' ],
150150 'NAModule3' : ["rain unit" , 'Weather' ],
151151 'NAModule4' : ["indoor unit" , 'Weather' ],
152- 'NAPlug' : ["thermostat relais station" , 'Energy' ], # A smart thermostat exist of a thermostat$
153- # The relais module is also the bridge bet$
152+ 'NAPlug' : ["thermostat relais station" , 'Energy' ], # A smart thermostat exist of a thermostat and a Relais module
153+ # The relais module is also the bridge for thermostat and Valves
154154 'NATherm1' : ["thermostat" , 'Energy' ],
155- 'NCO' : ["co2 sensor" , 'Home + Security' ], # The same API as smoke sensor
155+ 'NCO' : ["co2 sensor" , 'Home + Security' ], # The same API as smoke sensor
156156 'NDB' : ["doorbell" , 'Home + Security' ],
157157 'NOC' : ["outdoor camera" , 'Home + Security' ],
158- 'NRV' : ["thermostat valves" , 'Energy' ], # also API Home+Control
158+ 'NRV' : ["thermostat valves" , 'Energy' ], # also API Home+Control
159159 'NSD' : ["smoke sensor" , 'Home + Security' ],
160160 'NHC' : ["home coach" , 'Aircare' ],
161- 'NIS' : ["indoor sirene" , 'Home + Security' ],
161+ 'NIS' : ["indoor sirene" , 'Home + Security' ],
162162
163163 'NLC' : ["Cable Outlet" , 'Home+Control' ],
164164 'NLE' : ["Ecometer" , 'Home+Control' ],
@@ -193,14 +193,14 @@ def getParameter(key, default):
193193 1 : "inHg" ,
194194 2 : "mmHg"
195195 },
196- "Health index" : { # Homecoach
196+ "Health index" : { # Homecoach
197197 0 : "Healthy" ,
198198 1 : "Fine" ,
199199 2 : "Fair" ,
200200 3 : "Poor" ,
201201 4 : "Unhealthy"
202202 },
203- "Wifi status" : { # Wifi Signal quality
203+ "Wifi status" : { # Wifi Signal quality
204204 86 : "Bad" ,
205205 71 : "Average" ,
206206 56 : "Good"
@@ -226,6 +226,7 @@ class AuthFailure( Exception ):
226226class outOfScope ( Exception ):
227227 pass
228228
229+
229230class ClientAuth :
230231 """
231232 Request authentication and keep access token available through token method. Renew it automatically if necessary
@@ -369,25 +370,42 @@ def __init__(self, authData, home=None):
369370 resp = postRequest ("Thermostat" , _GETTHERMOSTATDATA_REQ , postParams )
370371 self .rawData = resp ['body' ]['devices' ]
371372 if not self .rawData : raise NoDevice ("No thermostat available" )
372- self .thermostatData = filter_home_data (self .rawData , home )
373- if not self .thermostatData : raise NoHome ("No home %s found" % home )
374- self .thermostatData ['name' ] = self .thermostatData ['home_name' ]
375- for m in self .thermostatData ['modules' ]:
376- m ['name' ] = m ['module_name' ]
377- self .defaultThermostat = self .thermostatData ['home_name' ]
378- self .defaultThermostatId = self .thermostatData ['_id' ]
379- self .defaultModule = self .thermostatData ['modules' ][0 ]
380-
381- def getThermostat (self , name = None ):
382- if ['name' ] != name : return None
373+ #
374+ # keeping OLD code for Reference
375+ # self.thermostatData = filter_home_data(self.rawData, home)
376+ # if not self.thermostatData : raise NoHome("No home %s found" % home)
377+ # self.thermostatData['name'] = self.thermostatData['home_name'] # New key = 'station_name'
378+ # for m in self.thermostatData['modules']:
379+ # m['name'] = m['module_name']
380+ # self.defaultThermostat = self.thermostatData['home_name'] # New key = 'station_name'
381+ # self.defaultThermostatId = self.thermostatData['_id']
382+ # self.defaultModule = self.thermostatData['modules'][0]
383+ # Standard the first Relaystation and Thermostat is returned
384+ # self.rawData is list all stations
385+
386+ def Relay_Plug (self , _id = None ):
387+ for Relay in self .rawData :
388+ if _id in Relay :
389+ return Relay
390+ else :
391+ #print (Relay['_id'])
392+ return Relay
393+
394+ def Thermostat_Data (self ):
395+ for thermostat in self .Relay_Plug ()['modules' ]:
396+ #
397+ return thermostat
398+
399+ def getThermostat (self , name = None , tid = None ):
400+ if self .rawData [0 ]['station_name' ] != name : return None # OLD ['name']
383401 else : return
384402 return self .thermostat [self .defaultThermostatId ]
385403
386- def moduleNamesList (self , name = None , tid = None ): # ERROR getThermostat() got an unexpected keyword argument 'tid'
404+ def moduleNamesList (self , name = None , tid = None ):
387405 thermostat = self .getThermostat (name = name , tid = tid )
388406 return [m ['name' ] for m in thermostat ['modules' ]] if thermostat else None
389407
390- def getModuleByName (self , name , thermostatId = None ): # ERROR 'NoneType' object is not subscriptable
408+ def getModuleByName (self , name , thermostatId = None ): # ERROR 'NoneType' object is not subscriptable
391409 thermostat = self .getThermostat (tid = thermostatId )
392410 for m in thermostat ['modules' ]:
393411 if m ['name' ] == name : return m
@@ -562,6 +580,7 @@ def MinMaxTH(self, module=None, frame="last24"):
562580 else :
563581 return None
564582
583+
565584class DeviceList (WeatherStationData ):
566585 """
567586 This class is now deprecated. Use WeatherStationData directly instead
@@ -570,6 +589,7 @@ class DeviceList(WeatherStationData):
570589 DeprecationWarning )
571590 pass
572591
592+
573593class HomeData :
574594 """
575595 List the Netatmo home informations (Homes, cameras, events, persons)
@@ -586,35 +606,56 @@ def __init__(self, authData, home=None):
586606 self .rawData = resp ['body' ]
587607 # Collect homes
588608 self .homes = { d ['id' ] : d for d in self .rawData ['homes' ] }
589- if not self .homes : raise NoDevice ("No home available" )
590- self .default_home = home or list (self .homes .values ())[0 ]['name' ]
591- # Split homes data by category
592- self .persons = dict ()
593- self .events = dict ()
594- self .cameras = dict ()
595- self .lastEvent = dict ()
596- for i in range (len (self .rawData ['homes' ])):
597- curHome = self .rawData ['homes' ][i ]
598- nameHome = curHome ['name' ]
599- if nameHome not in self .cameras :
600- self .cameras [nameHome ] = dict ()
601- if 'persons' in curHome :
602- for p in curHome ['persons' ]:
603- self .persons [ p ['id' ] ] = p
604- if 'events' in curHome :
609+ for k , v in self .homes .items ():
610+ self .homeid = k
611+ C = v .get ('cameras' )
612+ P = v .get ('persons' )
613+ S = v .get ('smokedetectors' )
614+ E = v .get ('events' )
615+ if not S :
616+ logger .warning ('No Smokedetectors found' )
617+ # raise NoDevice("No Devices available")
618+ if not C :
619+ logger .warning ('No Cameras found' )
620+ # raise NoDevice("No Cameras available")
621+ if not P :
622+ logger .warning ('No Persons found' )
623+ # raise NoDevice("No Persons available")
624+ if not E :
625+ logger .warning ('No events found' )
626+ # raise NoDevice("No Events available")
627+ if S or C or P or E :
628+ self .default_home = home or list (self .homes .values ())[0 ]['name' ]
629+ # Split homes data by category
630+ self .persons = dict ()
631+ self .events = dict ()
632+ self .cameras = dict ()
633+ self .lastEvent = dict ()
634+ for i in range (len (self .rawData ['homes' ])):
635+ curHome = self .rawData ['homes' ][i ]
636+ nameHome = curHome ['name' ]
637+ if nameHome not in self .cameras :
638+ self .cameras [nameHome ] = dict ()
639+ if 'persons' in curHome :
640+ for p in curHome ['persons' ]:
641+ self .persons [ p ['id' ] ] = p
642+ if 'events' in curHome :
605643 for e in curHome ['events' ]:
606644 if e ['camera_id' ] not in self .events :
607645 self .events [ e ['camera_id' ] ] = dict ()
608646 self .events [ e ['camera_id' ] ][ e ['time' ] ] = e
609- if 'cameras' in curHome :
610- for c in curHome ['cameras' ]:
611- self .cameras [nameHome ][ c ['id' ] ] = c
612- c ["home_id" ] = curHome ['id' ]
613- for camera in self .events :
614- self .lastEvent [camera ] = self .events [camera ][sorted (self .events [camera ])[- 1 ]]
615- if not self .cameras [self .default_home ] : raise NoDevice ("No camera available in default home" )
616- self .default_camera = list (self .cameras [self .default_home ].values ())[0 ]
617-
647+ if 'cameras' in curHome :
648+ for c in curHome ['cameras' ]:
649+ self .cameras [nameHome ][ c ['id' ] ] = c
650+ c ["home_id" ] = curHome ['id' ]
651+ for camera in self .events :
652+ self .lastEvent [camera ] = self .events [camera ][sorted (self .events [camera ])[- 1 ]]
653+ if not self .cameras [self .default_home ] : raise NoDevice ("No camera available in default home" )
654+ self .default_camera = list (self .cameras [self .default_home ].values ())[0 ]
655+ else :
656+ pass
657+ # raise NoDevice("No Devices available")
658+
618659 def homeById (self , hid ):
619660 return None if hid not in self .homes else self .homes [hid ]
620661
@@ -859,6 +900,7 @@ class WelcomeData(HomeData):
859900 DeprecationWarning )
860901 pass
861902
903+
862904class HomesData :
863905 """
864906 List the Netatmo actual topology and static information of all devices present
@@ -890,6 +932,7 @@ def __init__(self, authData, home=None):
890932# print (self.Homes_Data)
891933 if not self .Homes_Data : raise NoDevice ("No Devices available" )
892934
935+
893936class HomeCoach :
894937 """
895938 List the HomeCoach modules
@@ -902,7 +945,7 @@ def __init__(self, authData, home=None):
902945 # I don't own a HomeCoach thus I am not able to test the HomeCoach support
903946
904947# warnings.warn("The HomeCoach code is not tested due to the lack of test environment.\n", RuntimeWarning )
905- # "As Netatmo is continuously breaking API compatibility, risk that current bindings are wrong is h$
948+ # "As Netatmo is continuously breaking API compatibility, risk that current bindings are wrong is high.\n" \
906949# "Please report found issues (https://github.com/philippelt/netatmo-api-python/issues)"
907950
908951 self .getAuthToken = authData .accessToken
@@ -929,16 +972,19 @@ def lastData(self, _id=None, exclude=0):
929972
930973 def checkNotUpdated (self , delay = 3600 ):
931974 res = self .lastData ()
975+ _id = res ['_id' ]
932976 ret = []
933- if time .time ()- res ['When' ] > delay : ret .update ({_id ['_id' ]: 'Device Not Updated' )
977+ if time .time ()- res ['When' ] > delay : ret .append ({_id ['_id' ]: 'Device Not Updated' } )
934978 return ret if ret else None
935979
936980 def checkUpdated (self , delay = 3600 ):
937981 res = self .lastData ()
982+ _id = res ['_id' ]
938983 ret = []
939- if time .time ()- res ['When' ] < delay : rret . update ({_id ['_id' ]: 'Device up-to-date' )
984+ if time .time ()- res ['When' ] < delay : ret . append ({_id ['_id' ]: 'Device up-to-date' } )
940985 return ret if ret else None
941986
987+
942988# Utilities routines
943989
944990def rawAPI (authData , url , parameters = {}):
@@ -1050,35 +1096,25 @@ def getStationMinMaxTH(station=None, module=None, home=None):
10501096 stderr .write ("Library source missing identification arguments to check lnetatmo.py (user/password/etc...)" )
10511097 exit (1 )
10521098
1053- authorization = ClientAuth () # Test authentication method
1099+ authorization = ClientAuth () # Test authentication method
10541100
10551101 try :
1056- weatherStation = WeatherStationData (authorization ) # Test DEVICELIST
1102+ weatherStation = WeatherStationData (authorization ) # Test DEVICELIST
10571103 except NoDevice :
10581104 logger .warning ("No weather station available for testing" )
10591105 else :
1060- weatherStation .MinMaxTH () # Test GETMEASUR
1106+ weatherStation .MinMaxTH () # Test GETMEASUR
10611107
10621108 try :
10631109 homes = HomeData (authorization )
1064- for k , v in homes .homes .items ():
1065- #print (v)
1066- C = v .pop ('cameras' )
1067- P = v .pop ('persons' )
1068- S = v .pop ('smokedetectors' )
1069- #
1070- if C == [] and P == [] and S == []:
1071- #print (v)
1072- logger .info ("No Cameras, Persons, Smokedetectors found" )
1073- #
1074- else :
1075- homeid = k
1076- #
1110+ homeid = homes .homeid
10771111 except NoDevice :
10781112 logger .warning ("No home available for testing" )
10791113
10801114 try :
10811115 thermostat = ThermostatData (authorization )
1116+ Default_relay = thermostat .Relay_Plug ()
1117+ Default_thermostat = thermostat .Thermostat_Data ()
10821118 except NoDevice :
10831119 logger .warning ("No thermostat avaible for testing" )
10841120
0 commit comments