From 0118e6131716e07fb634a6473e2e2bbbbe22915a Mon Sep 17 00:00:00 2001 From: JurgenLB <77586573+JurgenLB@users.noreply.github.com> Date: Tue, 7 Nov 2023 20:13:17 +0100 Subject: [PATCH 1/8] Update lnetatmo.py Fix some Typos --- lnetatmo.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lnetatmo.py b/lnetatmo.py index ad10f467..b782627b 100644 --- a/lnetatmo.py +++ b/lnetatmo.py @@ -149,8 +149,8 @@ def getParameter(key, default): 'NAModule2' : ["wind unit", 'Weather'], 'NAModule3' : ["rain unit", 'Weather'], 'NAModule4' : ["indoor unit", 'Weather'], - 'NAPlug' : ["thermostat relais station", 'Energy'], # A smart thermostat exist of a thermostat$ - # The relais module is also the bridge bet$ + 'NAPlug' : ["thermostat relais station", 'Energy'], # A smart thermostat exist of a thermostat and a Relais module + # The relais module is also the bridge for thermostat and Valves 'NATherm1' : ["thermostat", 'Energy'], 'NCO' : ["co2 sensor", 'Home + Security'], # The same API as smoke sensor 'NDB' : ["doorbell", 'Home + Security'], @@ -902,7 +902,7 @@ def __init__(self, authData, home=None): # I don't own a HomeCoach thus I am not able to test the HomeCoach support # warnings.warn("The HomeCoach code is not tested due to the lack of test environment.\n", RuntimeWarning ) -# "As Netatmo is continuously breaking API compatibility, risk that current bindings are wrong is h$ +# "As Netatmo is continuously breaking API compatibility, risk that current bindings are wrong is high.\n" \ # "Please report found issues (https://github.com/philippelt/netatmo-api-python/issues)" self.getAuthToken = authData.accessToken @@ -930,13 +930,13 @@ def lastData(self, _id=None, exclude=0): def checkNotUpdated(self, delay=3600): res = self.lastData() ret = [] - if time.time()-res['When'] > delay : ret.update({_id['_id']: 'Device Not Updated') + if time.time()-res['When'] > delay : ret.update({_id['_id']: 'Device Not Updated'}) return ret if ret else None def checkUpdated(self, delay=3600): res = self.lastData() ret = [] - if time.time()-res['When'] < delay : rret.update({_id['_id']: 'Device up-to-date') + if time.time()-res['When'] < delay : rret.update({_id['_id']: 'Device up-to-date'}) return ret if ret else None # Utilities routines From 9fc5f484cc53718086f6538cb408bada28068143 Mon Sep 17 00:00:00 2001 From: JurgenLB <77586573+JurgenLB@users.noreply.github.com> Date: Tue, 7 Nov 2023 20:22:45 +0100 Subject: [PATCH 2/8] Update lnetatmo.py Fix undefined variable --- lnetatmo.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lnetatmo.py b/lnetatmo.py index b782627b..5e2b82cb 100644 --- a/lnetatmo.py +++ b/lnetatmo.py @@ -929,14 +929,16 @@ def lastData(self, _id=None, exclude=0): def checkNotUpdated(self, delay=3600): res = self.lastData() + _id = res['_id'] ret = [] if time.time()-res['When'] > delay : ret.update({_id['_id']: 'Device Not Updated'}) return ret if ret else None def checkUpdated(self, delay=3600): res = self.lastData() + _id = res['_id'] ret = [] - if time.time()-res['When'] < delay : rret.update({_id['_id']: 'Device up-to-date'}) + if time.time()-res['When'] < delay : ret.update({_id['_id']: 'Device up-to-date'}) return ret if ret else None # Utilities routines From e551af89edb94a1be3d27c467ed80a950918a4f3 Mon Sep 17 00:00:00 2001 From: JurgenLB <77586573+JurgenLB@users.noreply.github.com> Date: Thu, 9 Nov 2023 19:55:05 +0100 Subject: [PATCH 3/8] Update lnetatmo.py correction spaces --- lnetatmo.py | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/lnetatmo.py b/lnetatmo.py index 5e2b82cb..174aca81 100644 --- a/lnetatmo.py +++ b/lnetatmo.py @@ -12,7 +12,7 @@ """ import warnings -if __name__ == "__main__": warnings.filterwarnings("ignore") # For installation test only +if __name__ == "__main__": warnings.filterwarnings("ignore") # For installation test only from sys import version_info from os import getenv @@ -87,20 +87,20 @@ def getParameter(key, default): _GETHOMEDATA_REQ = _BASE_URL + "api/gethomedata" _GETCAMERAPICTURE_REQ = _BASE_URL + "api/getcamerapicture" _GETEVENTSUNTIL_REQ = _BASE_URL + "api/geteventsuntil" -_HOME_STATUS = _BASE_URL + "api/homestatus" # Used for Home+ Control Devices -_GETHOMES_DATA = _BASE_URL + "api/homesdata" # New API -_GETHOMECOACH = _BASE_URL + "api/gethomecoachsdata" # +_HOME_STATUS = _BASE_URL + "api/homestatus" # Used for Home+ Control Devices +_GETHOMES_DATA = _BASE_URL + "api/homesdata" # New API +_GETHOMECOACH = _BASE_URL + "api/gethomecoachsdata" # #TODO# Undocumented (but would be very usefull) API : Access currently forbidden (403) _POST_UPDATE_HOME_REQ = _BASE_URL + "/api/updatehome" # For presence setting (POST BODY): -# _PRES_BODY_REC_SET = "home_id=%s&presence_settings[presence_record_%s]=%s" # (HomeId, DetectionKind, DetectionSetup.index) +# _PRES_BODY_REC_SET = "home_id=%s&presence_settings[presence_record_%s]=%s" # (HomeId, DetectionKind, DetectionSetup.index) _PRES_DETECTION_KIND = ("humans", "animals", "vehicles", "movements") _PRES_DETECTION_SETUP = ("ignore", "record", "record & notify") -# _PRES_BODY_ALERT_TIME = "home_id=%s&presence_settings[presence_notify_%s]=%s" # (HomeID, "from"|"to", "hh:mm") +# _PRES_BODY_ALERT_TIME = "home_id=%s&presence_settings[presence_notify_%s]=%s" # (HomeID, "from"|"to", "hh:mm") # Regular (documented) commands (both cameras) @@ -113,14 +113,14 @@ def getParameter(key, default): _PRES_CDE_GET_LIGHT = "/command/floodlight_get_config" # Not working yet, probably due to scope restriction -#_PRES_CDE_SET_LIGHT = "/command/floodlight_set_config?config=mode:%s" # "auto"|"on"|"off" +#_PRES_CDE_SET_LIGHT = "/command/floodlight_set_config?config=mode:%s" # "auto"|"on"|"off" # For all cameras -_CAM_CHANGE_STATUS = "/command/changestatus?status=%s" # "on"|"off" +_CAM_CHANGE_STATUS = "/command/changestatus?status=%s" # "on"|"off" # Not working yet -#_CAM_FTP_ACTIVE = "/command/ftp_set_config?config=on_off:%s" # "on"|"off" +#_CAM_FTP_ACTIVE = "/command/ftp_set_config?config=on_off:%s" # "on"|"off" #Known TYPE used by Netatmo services + API, there can be more types possible TYPES = { @@ -135,30 +135,30 @@ def getParameter(key, default): 'BNDL' : ["Bticino Doorlock", 'Home + Security'], 'BNEU' : ["Bticino external unit", 'Home + Security'], 'BNFC' : ["Bticino Thermostat", 'Home+Control'], - 'BNMH' : ["Bticino My Home Server 1", 'Home + Security'], # also API Home+Control GATEWAY + 'BNMH' : ["Bticino My Home Server 1", 'Home + Security'], # also API Home+Control GATEWAY 'BNSE' : ["Bticino Alarm Sensor", 'Home + Security'], 'BNSL' : ["Bticino Staircase Light", 'Home + Security'], 'BNTH' : ["Bticino Thermostat", 'Home+Control'], 'BNTR' : ["Bticino module towel rail", 'Home+Control'], 'BNXM' : ["Bticino X meter", 'Home+Control'], - 'NACamera' : ["indoor camera", 'Home + Security'], + 'NACamera' : ["indoor camera", 'Home + Security'], 'NACamDoorTag' : ["door tag", 'Home + Security'], 'NAMain' : ["weather station", 'Weather'], 'NAModule1' : ["outdoor unit", 'Weather'], 'NAModule2' : ["wind unit", 'Weather'], 'NAModule3' : ["rain unit", 'Weather'], 'NAModule4' : ["indoor unit", 'Weather'], - 'NAPlug' : ["thermostat relais station", 'Energy'], # A smart thermostat exist of a thermostat and a Relais module - # The relais module is also the bridge for thermostat and Valves + 'NAPlug' : ["thermostat relais station", 'Energy'], # A smart thermostat exist of a thermostat and a Relais module + # The relais module is also the bridge for thermostat and Valves 'NATherm1' : ["thermostat", 'Energy'], - 'NCO' : ["co2 sensor", 'Home + Security'], # The same API as smoke sensor + 'NCO' : ["co2 sensor", 'Home + Security'], # The same API as smoke sensor 'NDB' : ["doorbell", 'Home + Security'], 'NOC' : ["outdoor camera", 'Home + Security'], - 'NRV' : ["thermostat valves", 'Energy'], # also API Home+Control + 'NRV' : ["thermostat valves", 'Energy'], # also API Home+Control 'NSD' : ["smoke sensor", 'Home + Security'], 'NHC' : ["home coach", 'Aircare'], - 'NIS' : ["indoor sirene", 'Home + Security'], + 'NIS' : ["indoor sirene", 'Home + Security'], 'NLC' : ["Cable Outlet", 'Home+Control'], 'NLE' : ["Ecometer", 'Home+Control'], @@ -193,14 +193,14 @@ def getParameter(key, default): 1: "inHg", 2: "mmHg" }, - "Health index" : { # Homecoach + "Health index" : { # Homecoach 0: "Healthy", 1: "Fine", 2: "Fair", 3: "Poor", 4: "Unhealthy" }, - "Wifi status" : { # Wifi Signal quality + "Wifi status" : { # Wifi Signal quality 86: "Bad", 71: "Average", 56: "Good" @@ -371,10 +371,10 @@ def __init__(self, authData, home=None): if not self.rawData : raise NoDevice("No thermostat available") self.thermostatData = filter_home_data(self.rawData, home) if not self.thermostatData : raise NoHome("No home %s found" % home) - self.thermostatData['name'] = self.thermostatData['home_name'] + self.thermostatData['name'] = self.thermostatData['home_name'] # for m in self.thermostatData['modules']: m['name'] = m['module_name'] - self.defaultThermostat = self.thermostatData['home_name'] + self.defaultThermostat = self.thermostatData['home_name'] # self.defaultThermostatId = self.thermostatData['_id'] self.defaultModule = self.thermostatData['modules'][0] @@ -383,11 +383,11 @@ def getThermostat(self, name=None): else: return return self.thermostat[self.defaultThermostatId] - def moduleNamesList(self, name=None, tid=None): # ERROR getThermostat() got an unexpected keyword argument 'tid' + def moduleNamesList(self, name=None, tid=None): # ERROR getThermostat() got an unexpected keyword argument 'tid' thermostat = self.getThermostat(name=name, tid=tid) return [m['name'] for m in thermostat['modules']] if thermostat else None - def getModuleByName(self, name, thermostatId=None): # ERROR 'NoneType' object is not subscriptable + def getModuleByName(self, name, thermostatId=None): # ERROR 'NoneType' object is not subscriptable thermostat = self.getThermostat(tid=thermostatId) for m in thermostat['modules']: if m['name'] == name: return m @@ -1051,14 +1051,14 @@ def getStationMinMaxTH(station=None, module=None, home=None): stderr.write("Library source missing identification arguments to check lnetatmo.py (user/password/etc...)") exit(1) - authorization = ClientAuth() # Test authentication method + authorization = ClientAuth() # Test authentication method try: - weatherStation = WeatherStationData(authorization) # Test DEVICELIST + weatherStation = WeatherStationData(authorization) # Test DEVICELIST except NoDevice: logger.warning("No weather station available for testing") else: - weatherStation.MinMaxTH() # Test GETMEASUR + weatherStation.MinMaxTH() # Test GETMEASUR try: homes = HomeData(authorization) From 488cb07009bf0c2d4f5ca7ceb499df9a214c1b1a Mon Sep 17 00:00:00 2001 From: JurgenLB <77586573+JurgenLB@users.noreply.github.com> Date: Thu, 9 Nov 2023 23:04:34 +0100 Subject: [PATCH 4/8] Update lnetatmo.py --- lnetatmo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnetatmo.py b/lnetatmo.py index 174aca81..322ab207 100644 --- a/lnetatmo.py +++ b/lnetatmo.py @@ -931,14 +931,14 @@ def checkNotUpdated(self, delay=3600): res = self.lastData() _id = res['_id'] ret = [] - if time.time()-res['When'] > delay : ret.update({_id['_id']: 'Device Not Updated'}) + if time.time()-res['When'] > delay : ret.append({_id['_id']: 'Device Not Updated'}) return ret if ret else None def checkUpdated(self, delay=3600): res = self.lastData() _id = res['_id'] ret = [] - if time.time()-res['When'] < delay : ret.update({_id['_id']: 'Device up-to-date'}) + if time.time()-res['When'] < delay : ret.append({_id['_id']: 'Device up-to-date'}) return ret if ret else None # Utilities routines From ecddc7af595cb4ba3c19f7179fa4a3920ffad723 Mon Sep 17 00:00:00 2001 From: JurgenLB <77586573+JurgenLB@users.noreply.github.com> Date: Sat, 11 Nov 2023 19:43:39 +0100 Subject: [PATCH 5/8] Update lnetatmo.py Correcting Homedata execution when there are no smokesensors, camera's, persons or events in home data. --- lnetatmo.py | 91 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 38 deletions(-) diff --git a/lnetatmo.py b/lnetatmo.py index 322ab207..d0c41858 100644 --- a/lnetatmo.py +++ b/lnetatmo.py @@ -226,6 +226,7 @@ class AuthFailure( Exception ): class outOfScope( Exception ): pass + class ClientAuth: """ Request authentication and keep access token available through token method. Renew it automatically if necessary @@ -562,6 +563,7 @@ def MinMaxTH(self, module=None, frame="last24"): else: return None + class DeviceList(WeatherStationData): """ This class is now deprecated. Use WeatherStationData directly instead @@ -570,6 +572,7 @@ class DeviceList(WeatherStationData): DeprecationWarning ) pass + class HomeData: """ List the Netatmo home informations (Homes, cameras, events, persons) @@ -586,35 +589,56 @@ def __init__(self, authData, home=None): self.rawData = resp['body'] # Collect homes self.homes = { d['id'] : d for d in self.rawData['homes'] } - if not self.homes : raise NoDevice("No home available") - self.default_home = home or list(self.homes.values())[0]['name'] - # Split homes data by category - self.persons = dict() - self.events = dict() - self.cameras = dict() - self.lastEvent = dict() - for i in range(len(self.rawData['homes'])): - curHome = self.rawData['homes'][i] - nameHome = curHome['name'] - if nameHome not in self.cameras: - self.cameras[nameHome] = dict() - if 'persons' in curHome: - for p in curHome['persons']: - self.persons[ p['id'] ] = p - if 'events' in curHome: + for k, v in self.homes.items(): + self.homeid = k + C = v.get('cameras') + P = v.get('persons') + S = v.get('smokedetectors') + E = v.get('events') + if not S: + logger.warning('No Smokedetectors found') +# raise NoDevice("No Devices available") + if not C: + logger.warning('No Cameras found') +# raise NoDevice("No Cameras available") + if not P: + logger.warning('No Persons found') +# raise NoDevice("No Persons available") + if not E: + logger.warning('No events found') +# raise NoDevice("No Events available") + if S or C or P or E: + self.default_home = home or list(self.homes.values())[0]['name'] + # Split homes data by category + self.persons = dict() + self.events = dict() + self.cameras = dict() + self.lastEvent = dict() + for i in range(len(self.rawData['homes'])): + curHome = self.rawData['homes'][i] + nameHome = curHome['name'] + if nameHome not in self.cameras: + self.cameras[nameHome] = dict() + if 'persons' in curHome: + for p in curHome['persons']: + self.persons[ p['id'] ] = p + if 'events' in curHome: for e in curHome['events']: if e['camera_id'] not in self.events: self.events[ e['camera_id'] ] = dict() self.events[ e['camera_id'] ][ e['time'] ] = e - if 'cameras' in curHome: - for c in curHome['cameras']: - self.cameras[nameHome][ c['id'] ] = c - c["home_id"] = curHome['id'] - for camera in self.events: - self.lastEvent[camera] = self.events[camera][sorted(self.events[camera])[-1]] - if not self.cameras[self.default_home] : raise NoDevice("No camera available in default home") - self.default_camera = list(self.cameras[self.default_home].values())[0] - + if 'cameras' in curHome: + for c in curHome['cameras']: + self.cameras[nameHome][ c['id'] ] = c + c["home_id"] = curHome['id'] + for camera in self.events: + self.lastEvent[camera] = self.events[camera][sorted(self.events[camera])[-1]] + if not self.cameras[self.default_home] : raise NoDevice("No camera available in default home") + self.default_camera = list(self.cameras[self.default_home].values())[0] + else: + pass +# raise NoDevice("No Devices available") + def homeById(self, hid): return None if hid not in self.homes else self.homes[hid] @@ -859,6 +883,7 @@ class WelcomeData(HomeData): DeprecationWarning ) pass + class HomesData: """ List the Netatmo actual topology and static information of all devices present @@ -890,6 +915,7 @@ def __init__(self, authData, home=None): # print (self.Homes_Data) if not self.Homes_Data : raise NoDevice("No Devices available") + class HomeCoach: """ List the HomeCoach modules @@ -941,6 +967,7 @@ def checkUpdated(self, delay=3600): if time.time()-res['When'] < delay : ret.append({_id['_id']: 'Device up-to-date'}) return ret if ret else None + # Utilities routines def rawAPI(authData, url, parameters={}): @@ -1062,19 +1089,7 @@ def getStationMinMaxTH(station=None, module=None, home=None): try: homes = HomeData(authorization) - for k, v in homes.homes.items(): - #print (v) - C = v.pop('cameras') - P = v.pop('persons') - S = v.pop('smokedetectors') - # - if C == [] and P == [] and S == []: - #print (v) - logger.info("No Cameras, Persons, Smokedetectors found") - # - else: - homeid = k - # + homeid = homes.homeid except NoDevice : logger.warning("No home available for testing") From 1a607d133bcaee8be22c051942e734097e085cb7 Mon Sep 17 00:00:00 2001 From: JurgenLB <77586573+JurgenLB@users.noreply.github.com> Date: Sat, 11 Nov 2023 20:08:21 +0100 Subject: [PATCH 6/8] Update lnetatmo.py keeping [Fix] Don't crash MinMaxTH() on empty body #67 --- lnetatmo.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lnetatmo.py b/lnetatmo.py index d0c41858..6192a0a5 100644 --- a/lnetatmo.py +++ b/lnetatmo.py @@ -556,7 +556,7 @@ def MinMaxTH(self, module=None, frame="last24"): mtype = "Temperature,Humidity", date_begin = start, date_end = end) - if resp: + if resp and resp['body']: T = [v[0] for v in resp['body'].values()] H = [v[1] for v in resp['body'].values()] return min(T), max(T), min(H), max(H) @@ -1057,7 +1057,8 @@ def getStationMinMaxTH(station=None, module=None, home=None): for m in lastD.keys(): if time.time()-lastD[m]['When'] > 3600 : continue r = devList.MinMaxTH(module=m) - result[m] = (r[0], lastD[m]['Temperature'], r[1]) + if r: + result[m] = (r[0], lastD[m]['Temperature'], r[1]) else: if time.time()-lastD[module]['When'] > 3600 : result = ["-", "-"] else : From 50e0ac0d6b1bf932ac888727207e7d820a64ba04 Mon Sep 17 00:00:00 2001 From: JurgenLB <77586573+JurgenLB@users.noreply.github.com> Date: Sat, 11 Nov 2023 20:22:14 +0100 Subject: [PATCH 7/8] Update lnetatmo.py adjusting class "thermostat" --- lnetatmo.py | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/lnetatmo.py b/lnetatmo.py index 6192a0a5..983d0520 100644 --- a/lnetatmo.py +++ b/lnetatmo.py @@ -370,21 +370,38 @@ def __init__(self, authData, home=None): resp = postRequest("Thermostat", _GETTHERMOSTATDATA_REQ, postParams) self.rawData = resp['body']['devices'] if not self.rawData : raise NoDevice("No thermostat available") - self.thermostatData = filter_home_data(self.rawData, home) - if not self.thermostatData : raise NoHome("No home %s found" % home) - self.thermostatData['name'] = self.thermostatData['home_name'] # - for m in self.thermostatData['modules']: - m['name'] = m['module_name'] - self.defaultThermostat = self.thermostatData['home_name'] # - self.defaultThermostatId = self.thermostatData['_id'] - self.defaultModule = self.thermostatData['modules'][0] - - def getThermostat(self, name=None): + # + # keeping OLD code for Reference +# self.thermostatData = filter_home_data(self.rawData, home) +# if not self.thermostatData : raise NoHome("No home %s found" % home) +# self.thermostatData['name'] = self.thermostatData['home_name'] # New key = 'station_name' +# for m in self.thermostatData['modules']: +# m['name'] = m['module_name'] +# self.defaultThermostat = self.thermostatData['home_name'] # New key = 'station_name' +# self.defaultThermostatId = self.thermostatData['_id'] +# self.defaultModule = self.thermostatData['modules'][0] + # Standard the first Relaystation and Thermostat is returned + # self.rawData is list all stations + + def Relay_Plug(self, _id=None): + for Relay in self.rawData: + if _id in Relay: + return Relay + else: + #print (Relay['_id']) + return Relay + + def Thermostat_Data(self): + for thermostat in self.Relay_Plug()['modules']: + # + return thermostat + + def getThermostat(self, name=None, tid=None): if ['name'] != name: return None else: return return self.thermostat[self.defaultThermostatId] - def moduleNamesList(self, name=None, tid=None): # ERROR getThermostat() got an unexpected keyword argument 'tid' + def moduleNamesList(self, name=None, tid=None): thermostat = self.getThermostat(name=name, tid=tid) return [m['name'] for m in thermostat['modules']] if thermostat else None @@ -1096,6 +1113,8 @@ def getStationMinMaxTH(station=None, module=None, home=None): try: thermostat = ThermostatData(authorization) + Default_relay = thermostat.Relay_Plug() + Default_thermostat = thermostat.Thermostat_Data() except NoDevice: logger.warning("No thermostat avaible for testing") From 71bfa5527b377fbd56ae33c9b6012bf2dd65c542 Mon Sep 17 00:00:00 2001 From: JurgenLB <77586573+JurgenLB@users.noreply.github.com> Date: Sat, 11 Nov 2023 22:10:49 +0100 Subject: [PATCH 8/8] Update lnetatmo.py correcting function 'getThermostat' --- lnetatmo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnetatmo.py b/lnetatmo.py index 983d0520..ce5c7bea 100644 --- a/lnetatmo.py +++ b/lnetatmo.py @@ -397,7 +397,7 @@ def Thermostat_Data(self): return thermostat def getThermostat(self, name=None, tid=None): - if ['name'] != name: return None + if self.rawData[0]['station_name'] != name: return None # OLD ['name'] else: return return self.thermostat[self.defaultThermostatId]