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/9] 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/9] 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/9] 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/9] 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/9] 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/9] 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/9] 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 ) From d9178b68d8b260a93c4071b84696ef8f02bcdd73 Mon Sep 17 00:00:00 2001 From: JurgenLB <77586573+JurgenLB@users.noreply.github.com> Date: Sun, 25 Jan 2026 08:49:06 +0100 Subject: [PATCH 8/9] Deprecate classes and add example functions Mark several classes and their constructors as deprecated. Add example functions for HomeStatus, ThermostatData, HomesData, and HomeCoach with exception handling. --- usage.md | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/usage.md b/usage.md index cb8eb6cb..8eb556af 100644 --- a/usage.md +++ b/usage.md @@ -207,7 +207,7 @@ Properties, all properties are read-only unless specified : #### 4-3 User class #### - +_!!Deprecated_ Constructor @@ -355,7 +355,7 @@ at all if you slip over two days as required in a shifting 24 hours window. #### 4-5 HomeData class #### - +_!!Deprecated_ Constructor @@ -512,6 +512,7 @@ Methods : * Output : value ``` +def HOMESTATUS(): homestatus = lnetatmo.HomeStatus(authorization, homeid) print ('Rooms in Homestatus') for r in homestatus.rooms: @@ -529,7 +530,10 @@ Methods : print (lnetatmo.TYPES[vt]) print (m.keys()) - +try: + HOMESTATUS() +except Exception as e: + print (e) ``` @@ -563,6 +567,7 @@ Methods : Example : ``` +def THERMOSTAT(): device = lnetatmo.ThermostatData(authorization, homeid) for i in device.rawData: print ('rawData') @@ -580,6 +585,10 @@ Example : TH = device.Thermostat_Data() print (TH.keys()) +try: + THERMOSTAT() +except Exception as e: + print (e) ``` #### 4-8 HomesData class #### @@ -606,6 +615,7 @@ Methods : Example : ``` +def HOMESDATA(): homesData = lnetatmo.HomesData ( authorization, home_id ) print (homesdata.Homes_Data['name']) print (homesdata.Homes_Data['altitude']) @@ -625,6 +635,10 @@ Example : print ('Schedules in HomesData') print (homesdata.Homes_Data['schedules'][0].keys()) +try: + HOMESDATA() +except Exception as e: + print (e) ``` #### 4-9 Homecoach class #### @@ -658,6 +672,7 @@ Example : ``` +def HOMECOACH(): homecoach = lnetatmo.HomeCoach(authorization, homeid) # Not_updated = [] @@ -689,6 +704,10 @@ Example : print (Not_updated) print (updated) +try: + HOMECOACH() +except Exception as e: + print (e) ``` From 86d90a5f19ed540a65a0bc0ab204c85dc0a6f6cb Mon Sep 17 00:00:00 2001 From: JurgenLB <77586573+JurgenLB@users.noreply.github.com> Date: Sun, 25 Jan 2026 08:57:55 +0100 Subject: [PATCH 9/9] Refine device categories Updated device categories for Bticino and thermostat components to include both Home Control and Energy. The Energy category will be removed in the near future, In the Energy App there is already a message to be replaced by 'Home + Control' --- lnetatmo.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lnetatmo.py b/lnetatmo.py index 3145ba80..18619ffe 100644 --- a/lnetatmo.py +++ b/lnetatmo.py @@ -112,7 +112,7 @@ 'BNFC' : ["Bticino Thermostat", 'Home+Control'], 'BNIL' : ["Bticino intelligent light", 'Home+Control'], 'BNLD' : ["Bticino module lighting dimmer", 'Home+Control'], - 'BNMH' : ["Bticino My Home Server 1", 'Home + Security'], # also API Home+Control GATEWAY + 'BNMH' : ["Bticino My Home Server 1", 'Home + Security & Home+Control'], # GATEWAY 'BNMS' : ["Bticino module motorized shade", 'Home+Control'], 'BNSE' : ["Bticino Alarm Sensor", 'Home + Security'], 'BNSL' : ["Bticino Staircase Light", 'Home + Security'], @@ -127,13 +127,13 @@ 'NAModule2' : ["wind unit", 'Weather'], 'NAModule3' : ["rain unit", 'Weather'], 'NAModule4' : ["indoor unit", 'Weather'], - 'NAPlug' : ["thermostat relais station", 'Energy'], # A smart thermostat exist of a thermostat module and a Relay device + 'NAPlug' : ["thermostat relais station", 'Home+Control & Energy'], # A smart thermostat exist of a thermostat module and a Relay device # The relay device is also the bridge for thermostat and Valves - 'NATherm1' : ["thermostat", 'Energy'], + 'NATherm1' : ["thermostat", 'Home+Control & Energy'], '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", 'Home+Control & Energy'], 'NSD' : ["smoke sensor", 'Home + Security'], 'NHC' : ["home coach", 'Aircare'], 'NIS' : ["indoor sirene", 'Home + Security'],