12
12
"""
13
13
14
14
import warnings
15
- if __name__ == "__main__" : warnings .filterwarnings ("ignore" ) # For installation test only
15
+ if __name__ == "__main__" : warnings .filterwarnings ("ignore" ) # For installation test only
16
16
17
17
from sys import version_info
18
18
from os import getenv
@@ -87,20 +87,20 @@ def getParameter(key, default):
87
87
_GETHOMEDATA_REQ = _BASE_URL + "api/gethomedata"
88
88
_GETCAMERAPICTURE_REQ = _BASE_URL + "api/getcamerapicture"
89
89
_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" #
93
93
94
94
#TODO# Undocumented (but would be very usefull) API : Access currently forbidden (403)
95
95
96
96
_POST_UPDATE_HOME_REQ = _BASE_URL + "/api/updatehome"
97
97
98
98
# 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)
100
100
_PRES_DETECTION_KIND = ("humans" , "animals" , "vehicles" , "movements" )
101
101
_PRES_DETECTION_SETUP = ("ignore" , "record" , "record & notify" )
102
102
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")
104
104
105
105
# Regular (documented) commands (both cameras)
106
106
@@ -113,14 +113,14 @@ def getParameter(key, default):
113
113
114
114
_PRES_CDE_GET_LIGHT = "/command/floodlight_get_config"
115
115
# 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"
117
117
118
118
119
119
# For all cameras
120
120
121
- _CAM_CHANGE_STATUS = "/command/changestatus?status=%s" # "on"|"off"
121
+ _CAM_CHANGE_STATUS = "/command/changestatus?status=%s" # "on"|"off"
122
122
# 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"
124
124
125
125
#Known TYPE used by Netatmo services + API, there can be more types possible
126
126
TYPES = {
@@ -135,30 +135,30 @@ def getParameter(key, default):
135
135
'BNDL' : ["Bticino Doorlock" , 'Home + Security' ],
136
136
'BNEU' : ["Bticino external unit" , 'Home + Security' ],
137
137
'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
139
139
'BNSE' : ["Bticino Alarm Sensor" , 'Home + Security' ],
140
140
'BNSL' : ["Bticino Staircase Light" , 'Home + Security' ],
141
141
'BNTH' : ["Bticino Thermostat" , 'Home+Control' ],
142
142
'BNTR' : ["Bticino module towel rail" , 'Home+Control' ],
143
143
'BNXM' : ["Bticino X meter" , 'Home+Control' ],
144
144
145
- 'NACamera' : ["indoor camera" , 'Home + Security' ],
145
+ 'NACamera' : ["indoor camera" , 'Home + Security' ],
146
146
'NACamDoorTag' : ["door tag" , 'Home + Security' ],
147
147
'NAMain' : ["weather station" , 'Weather' ],
148
148
'NAModule1' : ["outdoor unit" , 'Weather' ],
149
149
'NAModule2' : ["wind unit" , 'Weather' ],
150
150
'NAModule3' : ["rain unit" , 'Weather' ],
151
151
'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
154
154
'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
156
156
'NDB' : ["doorbell" , 'Home + Security' ],
157
157
'NOC' : ["outdoor camera" , 'Home + Security' ],
158
- 'NRV' : ["thermostat valves" , 'Energy' ], # also API Home+Control
158
+ 'NRV' : ["thermostat valves" , 'Energy' ], # also API Home+Control
159
159
'NSD' : ["smoke sensor" , 'Home + Security' ],
160
160
'NHC' : ["home coach" , 'Aircare' ],
161
- 'NIS' : ["indoor sirene" , 'Home + Security' ],
161
+ 'NIS' : ["indoor sirene" , 'Home + Security' ],
162
162
163
163
'NLC' : ["Cable Outlet" , 'Home+Control' ],
164
164
'NLE' : ["Ecometer" , 'Home+Control' ],
@@ -193,14 +193,14 @@ def getParameter(key, default):
193
193
1 : "inHg" ,
194
194
2 : "mmHg"
195
195
},
196
- "Health index" : { # Homecoach
196
+ "Health index" : { # Homecoach
197
197
0 : "Healthy" ,
198
198
1 : "Fine" ,
199
199
2 : "Fair" ,
200
200
3 : "Poor" ,
201
201
4 : "Unhealthy"
202
202
},
203
- "Wifi status" : { # Wifi Signal quality
203
+ "Wifi status" : { # Wifi Signal quality
204
204
86 : "Bad" ,
205
205
71 : "Average" ,
206
206
56 : "Good"
@@ -226,6 +226,7 @@ class AuthFailure( Exception ):
226
226
class outOfScope ( Exception ):
227
227
pass
228
228
229
+
229
230
class ClientAuth :
230
231
"""
231
232
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):
369
370
resp = postRequest ("Thermostat" , _GETTHERMOSTATDATA_REQ , postParams )
370
371
self .rawData = resp ['body' ]['devices' ]
371
372
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']
383
401
else : return
384
402
return self .thermostat [self .defaultThermostatId ]
385
403
386
- def moduleNamesList (self , name = None , tid = None ): # ERROR getThermostat() got an unexpected keyword argument 'tid'
404
+ def moduleNamesList (self , name = None , tid = None ):
387
405
thermostat = self .getThermostat (name = name , tid = tid )
388
406
return [m ['name' ] for m in thermostat ['modules' ]] if thermostat else None
389
407
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
391
409
thermostat = self .getThermostat (tid = thermostatId )
392
410
for m in thermostat ['modules' ]:
393
411
if m ['name' ] == name : return m
@@ -562,6 +580,7 @@ def MinMaxTH(self, module=None, frame="last24"):
562
580
else :
563
581
return None
564
582
583
+
565
584
class DeviceList (WeatherStationData ):
566
585
"""
567
586
This class is now deprecated. Use WeatherStationData directly instead
@@ -570,6 +589,7 @@ class DeviceList(WeatherStationData):
570
589
DeprecationWarning )
571
590
pass
572
591
592
+
573
593
class HomeData :
574
594
"""
575
595
List the Netatmo home informations (Homes, cameras, events, persons)
@@ -586,35 +606,56 @@ def __init__(self, authData, home=None):
586
606
self .rawData = resp ['body' ]
587
607
# Collect homes
588
608
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 :
605
643
for e in curHome ['events' ]:
606
644
if e ['camera_id' ] not in self .events :
607
645
self .events [ e ['camera_id' ] ] = dict ()
608
646
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
+
618
659
def homeById (self , hid ):
619
660
return None if hid not in self .homes else self .homes [hid ]
620
661
@@ -859,6 +900,7 @@ class WelcomeData(HomeData):
859
900
DeprecationWarning )
860
901
pass
861
902
903
+
862
904
class HomesData :
863
905
"""
864
906
List the Netatmo actual topology and static information of all devices present
@@ -890,6 +932,7 @@ def __init__(self, authData, home=None):
890
932
# print (self.Homes_Data)
891
933
if not self .Homes_Data : raise NoDevice ("No Devices available" )
892
934
935
+
893
936
class HomeCoach :
894
937
"""
895
938
List the HomeCoach modules
@@ -902,7 +945,7 @@ def __init__(self, authData, home=None):
902
945
# I don't own a HomeCoach thus I am not able to test the HomeCoach support
903
946
904
947
# 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" \
906
949
# "Please report found issues (https://github.com/philippelt/netatmo-api-python/issues)"
907
950
908
951
self .getAuthToken = authData .accessToken
@@ -929,16 +972,19 @@ def lastData(self, _id=None, exclude=0):
929
972
930
973
def checkNotUpdated (self , delay = 3600 ):
931
974
res = self .lastData ()
975
+ _id = res ['_id' ]
932
976
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' } )
934
978
return ret if ret else None
935
979
936
980
def checkUpdated (self , delay = 3600 ):
937
981
res = self .lastData ()
982
+ _id = res ['_id' ]
938
983
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' } )
940
985
return ret if ret else None
941
986
987
+
942
988
# Utilities routines
943
989
944
990
def rawAPI (authData , url , parameters = {}):
@@ -1050,35 +1096,25 @@ def getStationMinMaxTH(station=None, module=None, home=None):
1050
1096
stderr .write ("Library source missing identification arguments to check lnetatmo.py (user/password/etc...)" )
1051
1097
exit (1 )
1052
1098
1053
- authorization = ClientAuth () # Test authentication method
1099
+ authorization = ClientAuth () # Test authentication method
1054
1100
1055
1101
try :
1056
- weatherStation = WeatherStationData (authorization ) # Test DEVICELIST
1102
+ weatherStation = WeatherStationData (authorization ) # Test DEVICELIST
1057
1103
except NoDevice :
1058
1104
logger .warning ("No weather station available for testing" )
1059
1105
else :
1060
- weatherStation .MinMaxTH () # Test GETMEASUR
1106
+ weatherStation .MinMaxTH () # Test GETMEASUR
1061
1107
1062
1108
try :
1063
1109
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
1077
1111
except NoDevice :
1078
1112
logger .warning ("No home available for testing" )
1079
1113
1080
1114
try :
1081
1115
thermostat = ThermostatData (authorization )
1116
+ Default_relay = thermostat .Relay_Plug ()
1117
+ Default_thermostat = thermostat .Thermostat_Data ()
1082
1118
except NoDevice :
1083
1119
logger .warning ("No thermostat avaible for testing" )
1084
1120
0 commit comments