From 5227e5a466c3100053b95cbffb876ecf8ec51d33 Mon Sep 17 00:00:00 2001 From: JurgenLB <77586573+JurgenLB@users.noreply.github.com> Date: Sat, 1 Nov 2025 16:33:55 +0100 Subject: [PATCH 1/7] Update indoor camera description --- lnetatmo.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lnetatmo.py b/lnetatmo.py index b2ea9d39..92bfe694 100644 --- a/lnetatmo.py +++ b/lnetatmo.py @@ -120,7 +120,7 @@ 'BNTR' : ["Bticino module towel rail", 'Home+Control'], 'BNXM' : ["Bticino X meter", 'Home+Control'], - 'NACamera' : ["indoor camera", 'Home + Security'], + 'NACamera' : ["indoor camera Welcome", 'Home + Security'], 'NACamDoorTag' : ["door tag", 'Home + Security'], 'NAMain' : ["weather station", 'Weather'], 'NAModule1' : ["outdoor unit", 'Weather'], @@ -138,6 +138,7 @@ 'NHC' : ["home coach", 'Aircare'], 'NIS' : ["indoor sirene", 'Home + Security'], 'NDL' : ["Doorlock", 'Home + Security'], + 'NPC' : ["indoor camera Advance", 'Home + Security'], 'NLAO' : ["Magellan Green power remote control on-off", 'Home+Control'], 'NLAS' : ["Magellan Green Power Remote control scenarios", 'Home+Control'], From ee78ceff9fa7dc28944fe222986967f5178404a3 Mon Sep 17 00:00:00 2001 From: JurgenLB <77586573+JurgenLB@users.noreply.github.com> Date: Sat, 1 Nov 2025 22:07:37 +0100 Subject: [PATCH 2/7] change to support Winddata from Anemometer --- samples/weather2file.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/samples/weather2file.py b/samples/weather2file.py index 19b43525..6c042c51 100755 --- a/samples/weather2file.py +++ b/samples/weather2file.py @@ -434,10 +434,9 @@ def _to_dataframe(self, module_data_body, module_data, station_name, station_mac def get_module_df(self, newest_utctime, station_name, station_mac, module_data_overview, end_date_timestamp, dtype={}, time_z=None): logging.info(f'Processing {module_data_overview["module_name"]}...') - - + module_name = module_data_overview["module_name"] - + # We start by collecting new data keep_collecting_module_data = True @@ -461,9 +460,11 @@ def get_module_df(self, newest_utctime, station_name, station_mac, module_data_o keep_collecting_module_data = False else: logging.info(f'Collecting data for {module_name}...') - + while(keep_collecting_module_data): - + if (module_data_overview['data_type'] == 'wind'): + module_data_overview['data_type'] = ['WindStrength', 'WindAngle', 'GustStrength', 'GustAngle'] + # Get new data from Netatmo d = self._get_field_dict(station_mac, module_data_overview['_id'], @@ -500,7 +501,7 @@ def get_module_df(self, newest_utctime, station_name, station_mac, module_data_o logging.error(e) keep_collecting_module_data = False logging.error(f'Something fishy is going on... Aborting collection for module {module_name}') - + if data: df_module = pd.concat(data,ignore_index=True) @@ -595,11 +596,11 @@ def main(): else: logging.basicConfig(format=" %(levelname)s: %(message)s", level=verbose_dict[args.verbose]) - + # Handle dataframes (loading, appending, saving). df_handler = df_handler_dict[args.format](file_name=args.file_name, output_path=args.output_path) - + # Rate handler to make sure that we don't exceed Netatmos user rate limits rate_limit_handler = RateLimitHandler( user_request_limit_per_ten_seconds=args.ten_second_rate_limit, @@ -608,12 +609,12 @@ def main(): for station_name, station_data_overview in rate_limit_handler.get_stations(): - + station_mac = station_data_overview['_id'] - + station_timezone = timezone(station_data_overview['place']['timezone']) logging.info(f'Timezone {station_timezone} extracted from data.') - + end_datetime_timestamp = np.floor(datetime.timestamp(station_timezone.localize(args.end_datetime))) newest_utc = df_handler.get_newest_timestamp(station_data_overview['_id']) df_handler.append( @@ -627,7 +628,7 @@ def main(): station_timezone)) for module_data_overview in station_data_overview['modules']: - + df_handler.append( rate_limit_handler.get_module_df( df_handler.get_newest_timestamp(module_data_overview['_id']), @@ -636,8 +637,8 @@ def main(): module_data_overview, end_datetime_timestamp, df_handler.dtype_dict, - station_timezone)) - + station_timezone)) + @@ -646,4 +647,4 @@ def main(): if __name__ == "__main__": main() - + From 3d24447d5be69dc772d0c900bd8e1f612a19cde5 Mon Sep 17 00:00:00 2001 From: JurgenLB <77586573+JurgenLB@users.noreply.github.com> Date: Sat, 1 Nov 2025 22:32:56 +0100 Subject: [PATCH 3/7] Raise exception when server response is None. Added error handling for server response in multiple locations. --- lnetatmo.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/lnetatmo.py b/lnetatmo.py index 92bfe694..1a822f2e 100644 --- a/lnetatmo.py +++ b/lnetatmo.py @@ -277,6 +277,10 @@ def renew_token(self): "client_secret" : self._clientSecret } resp = postRequest("authentication", _AUTH_REQ, postParams) + if not resp: + raise AuthFailure("No response received from server.") + if not resp: + raise AuthFailure("Authentication Error from server.") if self.refreshToken != resp['refresh_token']: self.refreshToken = resp['refresh_token'] cred = {"CLIENT_ID":self._clientId, @@ -302,7 +306,9 @@ def __init__(self, authData): postParams = { "access_token" : authData.accessToken } - resp = postRequest("Weather station", _GETSTATIONDATA_REQ, postParams) + resp = postRequest("Weather station User", _GETSTATIONDATA_REQ, postParams) + if not resp: + raise AuthFailure("No response received from server.") self.rawData = resp['body'] self.devList = self.rawData['devices'] self.ownerMail = self.rawData['user']['mail'] @@ -331,6 +337,8 @@ def __init__(self, authData, home_id): "home_id": home_id } resp = postRequest("home_status", _HOME_STATUS, postParams) + if not resp: + raise AuthFailure("No response received from server.") self.resp = resp self.rawData = resp['body']['home'] if not self.rawData : raise NoHome("No home %s found" % home_id) @@ -389,6 +397,8 @@ def __init__(self, authData, home=None): "access_token" : self.getAuthToken } resp = postRequest("Thermostat", _GETTHERMOSTATDATA_REQ, postParams) + if not resp: + raise AuthFailure("No response received from server.") self.rawData = resp['body']['devices'] if not self.rawData : raise NoDevice("No thermostat available") # @@ -485,6 +495,8 @@ def __init__(self, authData, home=None, station=None): "access_token" : self.getAuthToken } resp = postRequest("Weather station", _GETSTATIONDATA_REQ, postParams) + if not resp: + raise AuthFailure("No response received from server.") self.rawData = resp['body']['devices'] # Weather data if not self.rawData : raise NoDevice("No weather station in any homes") @@ -670,6 +682,8 @@ def __init__(self, authData, home=None): "access_token" : self.getAuthToken } resp = postRequest("Home data", _GETHOMEDATA_REQ, postParams) + if not resp: + raise AuthFailure("No response received from server.") self.rawData = resp['body'] # Collect homes self.homes = self.rawData['homes'][0] @@ -782,12 +796,14 @@ def cameraUrls(self, camera=None, home=None, cid=None): if camera_data: vpn_url = camera_data['vpn_url'] resp = postRequest("Camera", vpn_url + '/command/ping') + if not resp: + raise AuthFailure("No response received from server.") temp_local_url=resp['local_url'] try: resp = postRequest("Camera", temp_local_url + '/command/ping',timeout=1) if resp and temp_local_url == resp['local_url']: local_url = temp_local_url - except: # On this particular request, vithout errors from previous requests, error is timeout + except: # On this particular request, without errors from previous requests, error is timeout local_url = None return vpn_url, local_url @@ -820,6 +836,8 @@ def getCameraPicture(self, image_id, key): "key" : key } resp = postRequest("Camera", _GETCAMERAPICTURE_REQ, postParams) + if not resp: + raise AuthFailure("No response received from server.") return resp, "jpeg" def getProfileImage(self, name): @@ -853,6 +871,8 @@ def updateEvent(self, event=None, home=None): "event_id" : event['id'] } resp = postRequest("Camera", _GETEVENTSUNTIL_REQ, postParams) + if not resp: + raise AuthFailure("No response received from server.") eventList = resp['body']['events_list'] for e in eventList: self.events[ e['camera_id'] ][ e['time'] ] = e @@ -994,6 +1014,8 @@ def __init__(self, authData, home=None): } # resp = postRequest("Module", _GETHOMES_DATA, postParams) + if not resp: + raise AuthFailure("No response received from server.") # self.rawData = resp['body']['devices'] self.rawData = resp['body']['homes'] if not self.rawData : raise NoHome("No home %s found" % home) @@ -1031,6 +1053,8 @@ def __init__(self, authData): "access_token" : self.getAuthToken } resp = postRequest("HomeCoach", _GETHOMECOACH, postParams) + if not resp: + raise AuthFailure("No response received from server.") self.rawData = resp['body']['devices'] # homecoach data if not self.rawData : raise NoDevice("No HomeCoach available") @@ -1078,6 +1102,8 @@ def rawAPI(authData, url, parameters=None): if parameters is None: parameters = {} parameters["access_token"] = authData.accessToken resp = postRequest("rawAPI", fullUrl, parameters) + if not resp: + raise AuthFailure("No response received from server.") return resp["body"] if "body" in resp else resp def filter_home_data(rawData, home): From 8134efcf54152cacfb60e32e090d69fdb722dd68 Mon Sep 17 00:00:00 2001 From: JurgenLB <77586573+JurgenLB@users.noreply.github.com> Date: Sun, 2 Nov 2025 12:24:06 +0100 Subject: [PATCH 4/7] Remove duplicate response check in authentication Removed redundant check for server response before raising AuthFailure. --- lnetatmo.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lnetatmo.py b/lnetatmo.py index 1a822f2e..e6117b14 100644 --- a/lnetatmo.py +++ b/lnetatmo.py @@ -277,8 +277,6 @@ def renew_token(self): "client_secret" : self._clientSecret } resp = postRequest("authentication", _AUTH_REQ, postParams) - if not resp: - raise AuthFailure("No response received from server.") if not resp: raise AuthFailure("Authentication Error from server.") if self.refreshToken != resp['refresh_token']: From cdb818a601649081e8e1c191813b17c444501dc9 Mon Sep 17 00:00:00 2001 From: JurgenLB <77586573+JurgenLB@users.noreply.github.com> Date: Sun, 2 Nov 2025 14:19:32 +0100 Subject: [PATCH 5/7] Remove unwanted check Removed redundant authentication error checks from multiple requests. --- lnetatmo.py | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/lnetatmo.py b/lnetatmo.py index e6117b14..fae654dd 100644 --- a/lnetatmo.py +++ b/lnetatmo.py @@ -277,8 +277,7 @@ def renew_token(self): "client_secret" : self._clientSecret } resp = postRequest("authentication", _AUTH_REQ, postParams) - if not resp: - raise AuthFailure("Authentication Error from server.") + if self.refreshToken != resp['refresh_token']: self.refreshToken = resp['refresh_token'] cred = {"CLIENT_ID":self._clientId, @@ -304,9 +303,8 @@ def __init__(self, authData): postParams = { "access_token" : authData.accessToken } - resp = postRequest("Weather station User", _GETSTATIONDATA_REQ, postParams) - if not resp: - raise AuthFailure("No response received from server.") + resp = postRequest("Station User", _GETSTATIONDATA_REQ, postParams) + self.rawData = resp['body'] self.devList = self.rawData['devices'] self.ownerMail = self.rawData['user']['mail'] @@ -335,8 +333,7 @@ def __init__(self, authData, home_id): "home_id": home_id } resp = postRequest("home_status", _HOME_STATUS, postParams) - if not resp: - raise AuthFailure("No response received from server.") + self.resp = resp self.rawData = resp['body']['home'] if not self.rawData : raise NoHome("No home %s found" % home_id) @@ -395,8 +392,7 @@ def __init__(self, authData, home=None): "access_token" : self.getAuthToken } resp = postRequest("Thermostat", _GETTHERMOSTATDATA_REQ, postParams) - if not resp: - raise AuthFailure("No response received from server.") + self.rawData = resp['body']['devices'] if not self.rawData : raise NoDevice("No thermostat available") # @@ -493,8 +489,7 @@ def __init__(self, authData, home=None, station=None): "access_token" : self.getAuthToken } resp = postRequest("Weather station", _GETSTATIONDATA_REQ, postParams) - if not resp: - raise AuthFailure("No response received from server.") + self.rawData = resp['body']['devices'] # Weather data if not self.rawData : raise NoDevice("No weather station in any homes") @@ -680,8 +675,7 @@ def __init__(self, authData, home=None): "access_token" : self.getAuthToken } resp = postRequest("Home data", _GETHOMEDATA_REQ, postParams) - if not resp: - raise AuthFailure("No response received from server.") + self.rawData = resp['body'] # Collect homes self.homes = self.rawData['homes'][0] @@ -794,8 +788,7 @@ def cameraUrls(self, camera=None, home=None, cid=None): if camera_data: vpn_url = camera_data['vpn_url'] resp = postRequest("Camera", vpn_url + '/command/ping') - if not resp: - raise AuthFailure("No response received from server.") + temp_local_url=resp['local_url'] try: resp = postRequest("Camera", temp_local_url + '/command/ping',timeout=1) @@ -834,8 +827,7 @@ def getCameraPicture(self, image_id, key): "key" : key } resp = postRequest("Camera", _GETCAMERAPICTURE_REQ, postParams) - if not resp: - raise AuthFailure("No response received from server.") + return resp, "jpeg" def getProfileImage(self, name): @@ -869,8 +861,7 @@ def updateEvent(self, event=None, home=None): "event_id" : event['id'] } resp = postRequest("Camera", _GETEVENTSUNTIL_REQ, postParams) - if not resp: - raise AuthFailure("No response received from server.") + eventList = resp['body']['events_list'] for e in eventList: self.events[ e['camera_id'] ][ e['time'] ] = e @@ -1012,8 +1003,7 @@ def __init__(self, authData, home=None): } # resp = postRequest("Module", _GETHOMES_DATA, postParams) - if not resp: - raise AuthFailure("No response received from server.") + # self.rawData = resp['body']['devices'] self.rawData = resp['body']['homes'] if not self.rawData : raise NoHome("No home %s found" % home) @@ -1051,8 +1041,7 @@ def __init__(self, authData): "access_token" : self.getAuthToken } resp = postRequest("HomeCoach", _GETHOMECOACH, postParams) - if not resp: - raise AuthFailure("No response received from server.") + self.rawData = resp['body']['devices'] # homecoach data if not self.rawData : raise NoDevice("No HomeCoach available") @@ -1100,8 +1089,7 @@ def rawAPI(authData, url, parameters=None): if parameters is None: parameters = {} parameters["access_token"] = authData.accessToken resp = postRequest("rawAPI", fullUrl, parameters) - if not resp: - raise AuthFailure("No response received from server.") + return resp["body"] if "body" in resp else resp def filter_home_data(rawData, home): From 681a0df85c0d0cd969c66299a412f64f2f5445ee Mon Sep 17 00:00:00 2001 From: JurgenLB <77586573+JurgenLB@users.noreply.github.com> Date: Sun, 2 Nov 2025 16:59:25 +0100 Subject: [PATCH 6/7] Add runtime warnings for deprecated classes Added runtime warning for deprecated User and HomeData classes. --- lnetatmo.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lnetatmo.py b/lnetatmo.py index fae654dd..465c64bc 100644 --- a/lnetatmo.py +++ b/lnetatmo.py @@ -299,6 +299,8 @@ class User: """ warnings.warn("The 'User' class is no longer maintained by Netatmo", DeprecationWarning ) + warnings.warn("The 'User' class code is deprecated.\n" , + RuntimeWarning ) def __init__(self, authData): postParams = { "access_token" : authData.accessToken @@ -670,6 +672,9 @@ class HomeData: def __init__(self, authData, home=None): warnings.warn("The 'HomeData' class is deprecated'", DeprecationWarning ) + warnings.warn("The HomeData code is deprecated.\n" , + RuntimeWarning ) + self.getAuthToken = authData.accessToken postParams = { "access_token" : self.getAuthToken From 9b03bfba835691915fc30f1a0e35b5bcb59ca1d1 Mon Sep 17 00:00:00 2001 From: JurgenLB <77586573+JurgenLB@users.noreply.github.com> Date: Sun, 2 Nov 2025 17:58:05 +0100 Subject: [PATCH 7/7] Revise deprecation warnings Updated deprecation warnings for User and HomeData classes. --- lnetatmo.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lnetatmo.py b/lnetatmo.py index 465c64bc..3145ba80 100644 --- a/lnetatmo.py +++ b/lnetatmo.py @@ -299,9 +299,11 @@ class User: """ warnings.warn("The 'User' class is no longer maintained by Netatmo", DeprecationWarning ) - warnings.warn("The 'User' class code is deprecated.\n" , - RuntimeWarning ) + def __init__(self, authData): + # + warnings.warn("The 'User' class code is deprecated and no longer maintained by Netatmo.\n" , + RuntimeWarning ) postParams = { "access_token" : authData.accessToken } @@ -670,8 +672,10 @@ class HomeData: home : Home name of the home where's devices are installed """ def __init__(self, authData, home=None): + # warnings.warn("The 'HomeData' class is deprecated'", DeprecationWarning ) + # warnings.warn("The HomeData code is deprecated.\n" , RuntimeWarning )