Skip to content

Commit caf8058

Browse files
authored
Merge pull request #90 from JurgenLB/master
updated Thermostat and Homecoach
2 parents 47bc528 + 009231b commit caf8058

File tree

2 files changed

+150
-68
lines changed

2 files changed

+150
-68
lines changed

.netatmo.credentials

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
{
2+
"USER_MAIL" : "",
3+
"USER_PASSWORD" : "",
24
"CLIENT_ID" : "",
35
"CLIENT_SECRET" : "",
4-
"REFRESH_TOKEN" : ""
6+
"ACCESS_TOKEN" : "",
7+
"REFRESH_TOKEN" : "",
8+
"SCOPE" : ""
59
}
610

lnetatmo.py

Lines changed: 145 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Multiple contributors : see https://github.com/philippelt/netatmo-api-python
44
# License : GPL V3
55
"""
6-
This API provides access to the Netatmo weather station or/and the Welcome camera
6+
This API provides access to the Netatmo weather station or/and other installed devices
77
This package can be used with Python2 or Python3 applications and do not
88
require anything else than standard libraries
99
@@ -121,8 +121,8 @@
121121
'NAModule2' : ["wind unit", 'Weather'],
122122
'NAModule3' : ["rain unit", 'Weather'],
123123
'NAModule4' : ["indoor unit", 'Weather'],
124-
'NAPlug' : ["thermostat relais station", 'Energy'], # A smart thermostat exist of a thermostat and a Relais module
125-
# The relais module is also the bridge for thermostat and Valves
124+
'NAPlug' : ["thermostat relais station", 'Energy'], # A smart thermostat exist of a thermostat module and a Relay device
125+
# The relay device is also the bridge for thermostat and Valves
126126
'NATherm1' : ["thermostat", 'Energy'],
127127
'NCO' : ["co2 sensor", 'Home + Security'], # The same API as smoke sensor
128128
'NDB' : ["doorbell", 'Home + Security'],
@@ -131,6 +131,7 @@
131131
'NSD' : ["smoke sensor", 'Home + Security'],
132132
'NHC' : ["home coach", 'Aircare'],
133133
'NIS' : ["indoor sirene", 'Home + Security'],
134+
'NDL' : ["Doorlock", 'Home + Security'],
134135

135136
'NLC' : ["Cable Outlet", 'Home+Control'],
136137
'NLE' : ["Ecometer", 'Home+Control'],
@@ -231,9 +232,9 @@ def __init__(self, clientId=None,
231232

232233
self._clientId = clientId or cred["CLIENT_ID"]
233234
self._clientSecret = clientSecret or cred["CLIENT_SECRET"]
235+
self._accessToken = None #accessToken or cred["ACCESS_TOKEN"] # Will be refreshed before any use
234236
self.refreshToken = refreshToken or cred["REFRESH_TOKEN"]
235237
self.expiration = 0 # Force refresh token
236-
self._accessToken = None # Will be refreshed before any use
237238

238239
@property
239240
def accessToken(self):
@@ -341,8 +342,8 @@ def getModuleParam(self, module_id, param):
341342

342343
class ThermostatData:
343344
"""
344-
List the Thermostat and temperature modules
345-
345+
List the Relay station and Thermostat modules
346+
Valves are controlled by HomesData and HomeStatus in new API
346347
Args:
347348
authData (clientAuth): Authentication information with a working access Token
348349
home : Home name or id of the home who's thermostat belongs to
@@ -375,36 +376,72 @@ def __init__(self, authData, home=None):
375376
# Standard the first Relaystation and Thermostat is returned
376377
# self.rawData is list all stations
377378

378-
# FIXME : This code is wrong as it will always return the first Relay
379-
# I don't own a thermostat I can't fix this code, help welcome
380-
def Relay_Plug(self, _id=None):
379+
# if no ID is given the Relaystation at index 0 is returned
380+
def Relay_Plug(self, Rid=""):
381381
for Relay in self.rawData:
382-
if _id in Relay:
383-
return Relay
384-
else:
382+
if Rid in Relay['_id']:
383+
print ('Relay ', Rid, 'in rawData')
384+
#print (Relay.keys())
385385
#print (Relay['_id'])
386386
return Relay
387+
#dict_keys(['_id', 'applications', 'cipher_id', 'command', 'config_version', 'd_amount', 'date_creation', 'dev_has_init', 'device_group', 'firmware', 'firmware_private', 'homekit_nb_pairing', 'last_bilan', 'last_day_extremum', 'last_fw_update', 'last_measure_stored', 'last_setup', 'last_status_store', 'last_sync_asked', 'last_time_boiler_on', 'mg_station_name', 'migration_date', 'module_history', 'netcom_transport', 'new_historic_data', 'place', 'plug_connected_boiler', 'recompute_outdoor_time', 'record_storage', 'rf_amb_status', 'setpoint_order_history', 'skip_module_history_creation', 'subtype', 'type', 'u_amount', 'update_device', 'upgrade_record_ts', 'wifi_status', 'room', 'modules', 'station_name', 'udp_conn', 'last_plug_seen'])
387388

388-
# FIXME : Probably wrong again, always returning "first" thermostat ?
389-
def Thermostat_Data(self):
390-
for thermostat in self.Relay_Plug()['modules']:
391-
#
392-
return thermostat
389+
# if no ID is given the Thermostatmodule at index 0 is returned
390+
def Thermostat_Data(self, tid=""):
391+
for Relay in self.rawData:
392+
for thermostat in Relay['modules']:
393+
if tid in thermostat['_id']:
394+
print ('Thermostat ',tid, 'in Relay', Relay['_id'], Relay['station_name'])
395+
#print (thermostat['_id'])
396+
#print (thermostat.keys())
397+
return thermostat
398+
#dict_keys(['_id', 'module_name', 'type', 'firmware', 'last_message', 'rf_status', 'battery_vp', 'therm_orientation', 'therm_relay_cmd', 'anticipating', 'battery_percent', 'event_history', 'last_therm_seen', 'setpoint', 'therm_program_list', 'measured'])
393399

394-
def getThermostat(self, name=None, tid=None):
395-
if self.rawData[0]['station_name'] != name: return None # OLD ['name']
396-
# FIXME: No thermostat property !!
397-
return self.thermostat[self.defaultThermostatId]
400+
401+
def getThermostat(self, name=None, id=""):
402+
for Relay in self.rawData:
403+
for module in Relay['modules']:
404+
if id == Relay['_id']:
405+
print ('Relay ', id, 'found')
406+
return Relay
407+
elif name == Relay['station_name']:
408+
print ('Relay ', name, 'found')
409+
return Relay
410+
elif id == module['_id']:
411+
print ('Thermostat ', id, 'found in Relay', Relay['_id'], Relay['station_name'])
412+
return module
413+
elif name == module['module_name']:
414+
print ('Thermostat ', name, 'found in Relay', Relay['_id'], Relay['station_name'])
415+
return module
416+
else:
417+
#print ('Device NOT Found')
418+
pass
398419

399420
def moduleNamesList(self, name=None, tid=None):
400-
thermostat = self.getThermostat(name=name, tid=tid)
401-
return [m['name'] for m in thermostat['modules']] if thermostat else None
421+
l = []
422+
for Relay in self.rawData:
423+
if id == Relay['_id'] or name == Relay['station_name']:
424+
RL = []
425+
for module in Relay['modules']:
426+
RL.append(module['module_name'])
427+
return RL
428+
else:
429+
#print ("Cloud Data")
430+
for module in Relay['modules']:
431+
l.append(module['module_name'])
432+
#This return a list off all connected Thermostat in the cloud.
433+
return l
402434

403-
def getModuleByName(self, name, thermostatId=None): # ERROR 'NoneType' object is not subscriptable
404-
thermostat = self.getThermostat(tid=thermostatId)
405-
for m in thermostat['modules']:
406-
if m['name'] == name: return m
407-
return None
435+
def getModuleByName(self, name, tid=""):
436+
for Relay in self.rawData:
437+
for module in Relay['modules']:
438+
#print (module['module_name'], module['_id'])
439+
if module['module_name'] == name:
440+
return module
441+
elif module['_id'] == tid:
442+
return module
443+
else:
444+
pass
408445

409446

410447
class WeatherStationData:
@@ -595,31 +632,48 @@ class HomeData:
595632
596633
Args:
597634
authData (ClientAuth): Authentication information with a working access Token
635+
home : Home name of the home where's devices are installed
598636
"""
599637
def __init__(self, authData, home=None):
638+
warnings.warn("The 'HomeData' class is deprecated'",
639+
DeprecationWarning )
600640
self.getAuthToken = authData.accessToken
601641
postParams = {
602642
"access_token" : self.getAuthToken
603643
}
604644
resp = postRequest("Home data", _GETHOMEDATA_REQ, postParams)
605645
self.rawData = resp['body']
606646
# Collect homes
607-
self.homes = { d['id'] : d for d in self.rawData['homes'] }
608-
# FIXME : Doesn't use the home parameter to select the appropriate home !
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-
S or logger.warning('No smoke detector found')
616-
C or logger.warning('No Cameras found')
617-
P or logger.warning('No Persons found')
618-
E or logger.warning('No events found')
619-
if not (C or P or S or E):
620-
raise NoDevice("No device found in home %s" % k)
647+
self.homes = self.rawData['homes'][0]
648+
for d in self.rawData['homes'] :
649+
if home == d['name']:
650+
self.homes = d
651+
else:
652+
pass
653+
#
654+
#print (self.homes.keys())
655+
#dict_keys(['id', 'name', 'persons', 'place', 'cameras', 'smokedetectors', 'events'])
656+
self.homeid = self.homes['id']
657+
C = self.homes['cameras']
658+
P = self.homes['persons']
659+
S = self.homes['smokedetectors']
660+
E = None
661+
# events not always in self.homes
662+
if 'events' in self.homes.keys():
663+
E = self.homes['events']
664+
#
665+
if not S:
666+
logger.warning('No smoke detector found')
667+
if not C:
668+
logger.warning('No Cameras found')
669+
if not P:
670+
logger.warning('No Persons found')
671+
if not E:
672+
logger.warning('No events found')
673+
# if not (C or P or S or E):
674+
# raise NoDevice("No device found in home %s" % k)
621675
if S or C or P or E:
622-
self.default_home = home or list(self.homes.values())[0]['name']
676+
self.default_home = home or self.homes['name']
623677
# Split homes data by category
624678
self.persons = {}
625679
self.events = {}
@@ -644,6 +698,7 @@ def __init__(self, authData, home=None):
644698
c["home_id"] = curHome['id']
645699
for camera,e in self.events.items():
646700
self.lastEvent[camera] = e[sorted(e)[-1]]
701+
#self.default_home has no key homeId use homeName instead!
647702
if not self.cameras[self.default_home] : raise NoDevice("No camera available in default home")
648703
self.default_camera = list(self.cameras[self.default_home].values())[0]
649704
else:
@@ -921,6 +976,9 @@ def __init__(self, authData, home=None):
921976
#print (h.keys())
922977
if home in (h["name"], h["id"]):
923978
self.Homes_Data = h
979+
else:
980+
self.Homes_Data = self.rawData[0]
981+
self.homeid = self.Homes_Data['id']
924982
if not self.Homes_Data : raise NoDevice("No Devices available")
925983

926984

@@ -932,10 +990,10 @@ class HomeCoach:
932990
authData (clientAuth): Authentication information with a working access Token
933991
home : Home name or id of the home who's HomeCoach belongs to
934992
"""
935-
# FIXME: home parameter not used, unpredictible behavior
936-
def __init__(self, authData, home=None):
937-
# I don't own a HomeCoach thus I am not able to test the HomeCoach support
938993

994+
def __init__(self, authData):
995+
# I don't own a HomeCoach thus I am not able to test the HomeCoach support
996+
# Homecoach does not need or use HomeID parameter
939997
# warnings.warn("The HomeCoach code is not tested due to the lack of test environment.\n", RuntimeWarning )
940998
# "As Netatmo is continuously breaking API compatibility, risk that current bindings are wrong is high.\n" \
941999
# "Please report found issues (https://github.com/philippelt/netatmo-api-python/issues)"
@@ -949,34 +1007,39 @@ def __init__(self, authData, home=None):
9491007
# homecoach data
9501008
if not self.rawData : raise NoDevice("No HomeCoach available")
9511009

952-
for h in self.rawData:
953-
# FIXME: This loop is nonsense (always end with the last value)
954-
self.HomecoachDevice = h
955-
# print ('Homecoach = ', self.HomecoachDevice)
956-
# print (' ')
957-
# print ('Homecoach_data = ', self.rawData[i]['dashboard_data'])
958-
# print (' ')
1010+
def HomecoachDevice(self, hid=""):
1011+
for device in self.rawData:
1012+
if hid == device['_id']:
1013+
return device
1014+
return None
9591015

960-
def Dashboard(self):
961-
D = self.HomecoachDevice['dashboard_data']
962-
return D
1016+
def Dashboard(self, hid=""):
1017+
#D = self.HomecoachDevice['dashboard_data']
1018+
for device in self.rawData:
1019+
if hid == device['_id']:
1020+
D = device['dashboard_data']
1021+
return D
9631022

964-
# FIXME: Exclusion of outdated info is not handled (exclude parameter unused)
9651023
def lastData(self, hid=None, exclude=0):
966-
if hid is not None:
967-
s = self.HomecoachDevice['dashboard_data']['time_utc']
968-
_id = self.HomecoachDevice[hid]
969-
return {'When':s}, {'_id':_id}
970-
return {'When': 0 }, {'_id': hid}
1024+
for device in self.rawData:
1025+
if hid == device['_id']:
1026+
# LastData in HomeCoach
1027+
#s = self.HomecoachDevice['dashboard_data']['time_utc']
1028+
# Define oldest acceptable sensor measure event
1029+
limit = (time.time() - exclude) if exclude else 0
1030+
ds = device['dashboard_data']['time_utc']
1031+
return { '_id': hid, 'When': ds if device.get('time_utc',limit+10) > limit else 0}
1032+
else:
1033+
pass
9711034

972-
def checkNotUpdated(self, res, _id, delay=3600):
1035+
def checkNotUpdated(self, res, hid, delay=3600):
9731036
ret = []
974-
if time.time()-res['When'] > delay : ret.append({_id: 'Device Not Updated'})
1037+
if time.time()-res['When'] > delay : ret.append({hid: 'Device Not Updated'})
9751038
return ret if ret else None
9761039

977-
def checkUpdated(self, res, _id, delay=3600):
1040+
def checkUpdated(self, res, hid, delay=3600):
9781041
ret = []
979-
if time.time()-res['When'] < delay : ret.append({_id: 'Device up-to-date'})
1042+
if time.time()-res['When'] < delay : ret.append({hid: 'Device up-to-date'})
9801043
return ret if ret else None
9811044

9821045

@@ -1099,23 +1162,38 @@ def getStationMinMaxTH(station=None, module=None, home=None):
10991162

11001163
try:
11011164
homes = HomeData(authorization)
1102-
homeid = homes.homeid
11031165
except NoDevice :
11041166
logger.warning("No home available for testing")
11051167

11061168
try:
11071169
thermostat = ThermostatData(authorization)
11081170
Default_relay = thermostat.Relay_Plug()
11091171
Default_thermostat = thermostat.Thermostat_Data()
1172+
thermostat.getThermostat()
1173+
print (thermostat.moduleNamesList())
1174+
#print (thermostat.getModuleByName(name))
11101175
except NoDevice:
11111176
logger.warning("No thermostat avaible for testing")
11121177

11131178
try:
1179+
print (' ')
1180+
logger.info("Homes Data")
1181+
#homesdata = HomesData(authorization, homeid)
11141182
homesdata = HomesData(authorization)
1183+
homeid = homesdata.homeid
11151184
except NoDevice:
11161185
logger.warning("No HomesData avaible for testing")
11171186

11181187
try:
1188+
print (' ')
1189+
logger.info("Home Status")
1190+
HomeStatus(authorization, homeid)
1191+
except NoDevice:
1192+
logger.warning("No Home available for testing")
1193+
1194+
try:
1195+
print (' ')
1196+
logger.info("HomeCoach")
11191197
Homecoach = HomeCoach(authorization)
11201198
except NoDevice:
11211199
logger.warning("No HomeCoach avaible for testing")

0 commit comments

Comments
 (0)