2013-01-21, [email protected]
2014-01-13, Revision to include new modules additionnal informations
2016-06-25, Update documentation for Netatmo Welcome
2017-01-09, Minor updates to packaging info
2020-12-07, Breaking changes due to removal of direct access to devices, "home" being now required (Netatmo redesign)
2023-07-12, Breaking changes due to deprecation of grant_type "password" for ALL apps
2023-07-24, Adding rawAPI call to direct access the netatmo API when no additional support is provided by the library
2023-12-04, New update to Netatmo authentication rules, no longer long lived refresh token -> credentials MUST be writable, Hard coding credentials in the library no longer possible (bad luck for small home automation device)
2024-01-03, New authentication method priorities, credential file as a parameter
No additional library other than standard Python library is required.
Both Python V2.7x and V3.x.x are supported without change.
More information about the Netatmo REST API can be obtained from http://dev.netatmo.com/doc/
This package support only preauthenticated scoped tokens created along apps.
Before being able to use the module you will need :
- A Netatmo user account having access to, at least, one station
- An application registered from the user account (see http://dev.netatmo.com/dev/createapp) to obtain application credentials.
- Create a couple access_token/refresh_token at the same time with your required scope (depending of your intents on library use)
In the netatmo philosophy, both the application itself and the user have to be registered thus have authentication credentials to be able to access any station. Registration is free for both.
Possible NETATMO_SCOPES ; read_station read_smarther write_smarther read_thermostat write_thermostat read_camera write_camera read_doorbell read_presence write_precense read_homecoach read_carbonmonoxidedetector read_smokedetector read_magellen write_magellan read_bubendorff write_bubendorff read_mx write_mx read_mhs1 write_mhs1
Copy the lnetatmo.py file in your work directory (or use pip install lnetatmo).
Authentication data can be supplied with 3 different methods (each method override any settings of previous methods) :
-
Some or all values can be defined by explicit call to initializer of ClientAuth class
̀```bash # Example: REFRESH_TOKEN supposed to be defined by an other method authData = lnetatmo.ClientAuth( clientId="netatmo-client-id", clientSecret="secret" ) ```
-
Some or all values can stored in ~/.netatmo.credentials (in your platform home directory) or a file path specified using the ̀
credentialFile
parameter. The file containing the keys in JSON format̀̀```bash $ cat .netatmo.credentials # Here all values are defined but it is not mandatory { "CLIENT_ID" : "`xxx", "CLIENT_SECRET" : "xxx", "REFRESH_TOKEN" : "xxx" } $ ̀̀```
-
Some or all values can be overriden by environment variables. This is the easiest method if your are packaging your application with Docker.
̀̀```bash $ export REFRESH_TOKEN="yyy" $ python3 MyCodeUsingLnetatmo.py ̀̀```
Due to Netatmo continuous changes, the credential file is the recommended method for production use as the refresh token will be frequently refreshed and this file MUST be writable by the library to keep a usable refresh token. You can also recover the
authorization.refreshToken
before your program termination and save it to be able to pass it back when your program restart.
If you provide all the values in a credential file, you can test that everything is working properly by simply running the package as a standalone program.
This will run a full access test to the account and stations and return 0 as return code if everything works well. If run interactively, it will also display an OK message.
$ python3 lnetatmo.py # or python2 as well
lnetatmo.py : OK
$ echo $?
0
Whatever is your choice for the library authentication setup, your application code if you were using previous library version, will not be affected.
Most of the time, the sequence of operations will be :
- Authenticate your program against Netatmo web server
- Get the device list accessible to the user
- Request data on one of these devices or directly access last data sent by the station
Example :
#!/usr/bin/python3
# encoding=utf-8
import lnetatmo
# 1 : Authenticate
authorization = lnetatmo.ClientAuth()
# 2 : Get devices list
weatherData = lnetatmo.WeatherStationData(authorization)
# 3 : Access most fresh data directly
print ("Current temperature (inside/outside): %s / %s °C" %
( weatherData.lastData()['indoor']['Temperature'],
weatherData.lastData()['outdoor']['Temperature'])
)
In this example, no init parameters are supplied to ClientAuth, the library is supposed to have been customized with the required values (see §2). The user must have nammed the sensors indoor and outdoor through the Web interface (or any other name as long as the program is requesting the same name).
The Netatmo design is based on stations (usually the in-house module) and modules (radio sensors reporting to a station, usually an outdoor sensor).
Sensor design is not exactly the same for station and external modules and they are not addressed the same way wether in the station or an external module. This is a design issue of the API that restrict the ability to write generic code that could work for station sensor the same way than other modules sensors. The station role (the reporting device) and module role (getting environmental data) should not have been mixed. The fact that a sensor is physically built in the station should not interfere with this two distincts objects.
The consequence is that, for the API, we will use terms of station data (for the sensors inside the station) and module data (for external(s) module). Lookup methods like moduleByName look for external modules and NOT station modules.
Having two roles, the station has a 'station_name' property as well as a 'module_name' for its internal sensor.
Exception : to reflect again the API structure, the last data uploaded by the station is indexed by module_name (wether it is a station module or an external module).
Sensors (stations and modules) are managed in the API using ID's (network hardware adresses). The Netatmo web account management gives you the capability to associate names to station sensor and module (and to the station itself). This is by far more comfortable and the interface provides service to locate a station or a module by name or by Id depending of your taste. Module lookup by name includes the optional station name in case multiple stations would have similar module names (if you monitor multiple stations/locations, it would not be a surprise that each of them would have an 'outdoor' module). This is a benefit in the sense it give you the ability to write generic code (for exemple, collect all 'outdoor' temperatures for all your stations).
The results are Python data structures, mostly dictionaries as they mirror easily the JSON returned data. All supplied classes provides simple properties to use as well as access to full data returned by the netatmo web services (rawData property for most classes).
_CLIENT_ID, _CLIENT_SECRET = Application ID and secret provided by Netatmo
application registration in your user account
_REFRESH_TOKEN : Refresh token created along the app client credentials
_BASE_URL and _*_REQ : Various URL to access Netatmo web services. They are
documented in http://dev.netatmo.com/doc/ They should not be changed unless
Netatmo API changes.
Constructor
authorization = lnetatmo.ClientAuth( clientId = _CLIENT_ID,
clientSecret = _CLIENT_SECRET,
refreshToken = _REFRESH_TOKEN,
credentialFile = "~/.netatmo.credentials"
)
Requires : Application and User credentials to access Netatmo API. if all this parameters are put in global variables they are not required (in library source code or in the main program through lnetatmo._CLIENT_ID = …)
Return : an authorization object that will supply the access token required by other web services. This class will handle the renewal of the access token if expiration is reached.
Properties, all properties are read-only unless specified :
- accessToken : Retrieve a valid access token (renewed if necessary)
- refreshToken : The token used to renew the access token (normally should not be used explicitely)
- expiration : The expiration time (epoch) of the current token
Constructor
user = lnetatmo.User( authorization )
Requires : an authorization object (ClientAuth instance)
Return : a User object. This object provides multiple informations on the user account such as the mail address of the user, the preferred language, …
Properties, all properties are read-only unless specified :
- rawData : Full dictionary of the returned JSON GETUSER Netatmo API service
- ownerMail : eMail address associated to the user account
- devList : List of Station's id accessible to the user account
In most cases, you will not need to use this class that is oriented toward an application that would use the other authentication method to an unknown user and then get information about him.
Constructor
weatherData = lnetatmo.WeatherStationData( authorization, home=None, station=None )
- Input : an authorization object (ClientAuth instance), an optional home name, an optional station name or id
Return : a WeatherStationData object. This object contains most administration properties of stations and modules accessible to the user and the last data pushed by the station to the Netatmo servers.
Raise a lnetatmo.NoDevice exception if no weather station is available for the given account.
If no home is specified, the first returned home will be set as default home. Same apply to station. If you have only one home and one station, you can safely ignore these new parameters. Note that return order is undefined. If you have multiple homes and a weather station in only one, the default home may be the one without station and the call will fail.
Breaking change:
If you used the station name in the past in any method call, you should be aware that Netatmo decided to rename your station with their own value thus your existing code will have to be updated.
Properties, all properties are read-only unless specified:
- rawData : Full dictionary of the returned JSON DEVICELIST Netatmo API service
- default_station : Name of the first station returned by the web service (warning, this is mainly for the ease of use of peoples having only 1 station).
- stations : Dictionary of stations (indexed by ID) accessible to this user account
- modules : Dictionary of modules (indexed by ID) accessible to the user account (whatever station there are plugged in)
Methods :
-
getStation (station=None) : Find a station by it's station name or station ID
- Input : Station name or ID to lookup (str)
- Output : station dictionary or None
-
stationByName (station=None) : Find a station by it's station name
- Input : Station name to lookup (str)
- Output : station dictionary or None
-
stationById (sid) : Find a station by it's Netatmo ID (mac address)
- Input : Station ID
- Output : station dictionary or None
-
moduleByName (module, station=None) : Find a module by it's module name
- Input : module name and optional station name
- Output : module dictionary or None
The station name parameter, if provided, is used to check wether the module belongs to the appropriate station (in case multiple stations would have same module name).
-
moduleById (mid, sid=None) : Find a module by it's ID and belonging station's ID
- Input : module ID and optional Station ID
- Output : module dictionary or None
-
modulesNamesList (station=None) : Get the list of modules names, including the station module name. Each of them should have a corrsponding entry in lastData. It is an equivalent (at lower cost) for lastData.keys()
-
lastData (station=None, exclude=0) : Get the last data uploaded by the station, exclude sensors with measurement older than given value (default return all)
- Input : station name OR id. If not provided default_station is used. Exclude is the delay in seconds from now to filter sensor readings.
- Output : Sensors data dictionary (Key is sensor name)
AT the time of this document, Available measures types are :
- a full or subset of Temperature, Pressure, Noise, Co2, Humidity, Rain (mm of precipitation during the last 5 minutes, or since the previous data upload), When (measurement timestamp) for modules including station module
- battery_vp : likely to be total battery voltage for external sensors (running on batteries) in mV (undocumented)
- rf_status : likely to be the % of radio signal between the station and a module (undocumented)
See Netatmo API documentation for units of regular measurements
If you named the internal sensor 'indoor' and the outdoor one 'outdoor' (simple is'n it ?) for your station in the user Web account station properties, you will access the data by :
# Last data access example
theData = weatherData.lastData()
print('Available modules : ', theData.keys() )
print('In-house CO2 level : ', theData['indoor']['Co2'] )
print('Outside temperature : ', theData['outdoor']['Temperature'] )
print('External module battery : ', "OK" if int(theData['outdoor']['battery_vp']) > 5000 \
else "NEEDS TO BE REPLACED" )
-
checkNotUpdated (station=None, delay=3600) :
- Input : optional station name (else default_station is used)
- Output : list of modules name for which last data update is older than specified delay (default 1 hour). If the station itself is lost, the module_name of the station will be returned (the key item of lastData information).
For example (following the previous one)
# Ensure data sanity
for m in weatherData.checkNotUpdated("<optional station name>"):
print("Warning, sensor %s information is obsolete" % m)
if moduleByName(m) == None : # Sensor is not an external module
print("The station is lost")
-
checkUpdated (station=None, delay=3600) :
- Input : optional station name (else default_station is used)
- Output : list of modules name for which last data update is newer than specified delay (default 1 hour).
Complement of the previous service
-
getMeasure (device_id, scale, mtype, module_id=None, date_begin=None, date_end=None, limit=None, optimize=False) :
- Input : All parameters specified in the Netatmo API service GETMEASURE (type being a python reserved word as been replaced by mtype).
- Output : A python dictionary reflecting the full service response. No transformation is applied.
-
MinMaxTH (station=None, module=None, frame="last24") : Return min and max temperature and humidity for the given station/module in the given timeframe
- Input :
- An optional station Name or ID, default_station is used if not supplied,
- An optional module name or ID, default : station sensor data is used
- A time frame that can be :
- "last24" : For a shifting window of the last 24 hours
- "day" : For all available data in the current day
- Output :
- A 4 values tuple (Temp mini, Temp maxi, Humid mini, Humid maxi)
Note : I have been oblliged to determine the min and max manually, the built-in service in the API doesn't always provide the actual min and max. The double parameter (scale) and aggregation request (min, max) is not satisfying at all if you slip over two days as required in a shifting 24 hours window.
- Input :
Constructor
homeData = lnetatmo.HomeData( authorization )
Requires : an authorization object (ClientAuth instance)
Return : a homeData object. This object contains most administration properties of home security products and notably Welcome & Presence cameras.
Note : the is_local property of camera is most of the time unusable if your IP changes, use cameraUrls to try to get a local IP as a replacement.
Properties, all properties are read-only unless specified:
- rawData : Full dictionary of the returned JSON DEVICELIST Netatmo API service
- default_home : Name of the first home returned by the web service (warning, this is mainly for the ease of use of peoples having cameras in only 1 house).
- default_camera : Data of the first camera in the default home returned by the web service (warning, this is mainly for the ease of use of peoples having only 1 camera).
- homes : Dictionary of homes (indexed by ID) accessible to this user account
- cameras : Dictionnary of cameras (indexed by home name and cameraID) accessible to this user
- persons : Dictionary of persons (indexed by ID) accessible to the user account
- events : Dictionary of events (indexed by cameraID and timestamp) seen by cameras
Methods :
-
homeById (hid) : Find a home by its Netatmo ID
- Input : Home ID
- Output : home dictionary or None
-
homeByName (home=None) : Find a home by it's home name
- Input : home name to lookup (str)
- Output : home dictionary or None
-
cameraById (hid) : Find a camera by its Netatmo ID
- Input : camera ID
- Output : camera dictionary or None
-
cameraByName (camera=None, home=None) : Find a camera by it's camera name
- Input : camera name and home name to lookup (str)
- Output : camera dictionary or None
-
cameraUrls (camera=None, home=None, cid=None) : return Urls to access camera live feed
- Input : camera name and optional home name or cameraID to lookup (str)
- Output : tuple with the vpn_url (for remote access) and local url to access the camera (commands)
-
url (camera=None, home=None, cid=None) : return the best url to access camera live feed
- Input : camera name and optional home name or cameraID to lookup (str)
- Output : the local url if available to reduce internet bandwith usage else the vpn url
-
personsAtHome (home=None) : return the list of known persons who are at home
- Input : home name to lookup (str)
- Output : list of persons seen
-
getCameraPicture (image_id, key): Download a specific image (of an event or user face) from the camera
- Input : image_id and key of an events or person face
- Output: Tuple with image data (to be stored in a file) and image type (jpg, png...)
-
getProfileImage (name) : Retreive the face of a given person
- Input : person name (str)
- Output: getCameraPicture data
-
updateEvent (event=None, home=None): Update the list of events
- Input: Id of the latest event and home name to update event list
-
personSeenByCamera (name, home=None, camera=None): Return true is a specific person has been seen by the camera in the last event
-
someoneKnownSeen (home=None, camera=None) : Return true is a known person has been in the last event
-
someoneUnknownSeen (home=None, camera=None) : Return true is an unknown person has been seen in the last event
-
motionDetected (home=None, camera=None) : Return true is a movement has been detected in the last event
-
presenceLight (camera=None, home=None, cid=None, setting=None) : return or set the Presence camera lighting mode
- Input : camera name and optional home name or cameraID to lookup (str), setting must be None|auto|on|off. currently not supported
- Output : setting requested if supplied else current camera setting
-
presenceStatus (mode, camera=None, home=None, cid=None) : set the camera on or off (current status in camera properties)
- Input : mode (on|off) (str), camera name and optional home name or cameraID to lookup (str)
- Output : requested mode if changed else None
-
getLiveSnapshot (camera=None, home=None, cid=None) : Get a jpeg of current live view of the camera
- Input : camera name and optional home name or cameraID to lookup (str)
- Output : jpeg binary content
homedata = lnetatmo.HomeData(authorization)
for k, v in homedata.homes.items():
homeid = k
S = v.get('smokedetectors')
C = v.get('cameras')
P = v.get('persons')
E = v.get('events')
if S or C or P or E:
print ('devices in HomeData')
for smokedetector in homedata.homes[homeid].get('smokedetectors'):
print (smokedetector['name'])
for camera in homedata.homes[homeid].get('cameras'):
print (camera['name'])
for persons in homedata.homes[homeid].get('persons'):
print (persons['name'])
for events in homedata.homes[homeid].get('events'):
print (events['name'])
return homeid
else:
homeid = k
return homeid
Constructor
homeStatus = lnetatmo.HomeStatus( authorization, home_id )
Requires :
- an authorization object (ClientAuth instance)
- home_id which can be found in https://dev.netatmo.com/apidocumentation/control by using "class homedata"
Return : a homeStatus object. This object contains most administration properties of Home+ Control devices such as Smarther thermostat, Socket, Cable Output, Centralized fan, Micromodules, ...
Methods :
-
getRoomsId : return all room ID
- Output : list of IDs of every single room (only the one with Smarther thermostat)
-
getListRoomParam : return every parameters of a room
- Input : room ID
- Output : list of parameters of a room
-
getRoomParam : return a specific parameter for a specific room
- Input : room ID and parameter
- Output : value
-
getModulesId : return all module IDs
- Output : list of IDs of every single module (socket, cable outlet, ...)
-
getListModuleParam : return every parameters of a module
- Input : module ID
- Output : list of parameters of a module
-
getModuleParam : return a specific parameter for a specific module
- Input : module ID and parameter
- Output : value
homestatus = lnetatmo.HomeStatus(authorization, homeid)
print ('Rooms in Homestatus')
for r in homestatus.rooms:
print (r)
print ('Persons in Homestatus')
if 'persons' in homestatus.rawData.keys():
for pp in homestatus.rawData['persons']:
print (pp)
print ('Modules in Homestatus')
for m in homestatus.modules:
for kt, vt in m.items():
if kt == 'type':
print (m['type'])
print (m['id'])
print (lnetatmo.TYPES[vt])
print (m.keys())
Constructor
device = lnetatmo.ThermostatData ( authorization, home_id )
Requires :
- an authorization object (ClientAuth instance)
- home_id which can be found in https://dev.netatmo.com/apidocumentation/control by using "class homedata"
Return : a device object. This object contains the Relay_Plug with Thermostat and temperature modules.
Methods :
-
rawData : Full dictionary of the returned JSON DEVICELIST Netatmo API service
- Output : list of IDs of every relay_plug
-
Relay_Plug :
- Output : Dictionairy of First Relay object
-
Thermostat_Data :
- Output : Dictionairy of Thermostat object in First Relay[Modules].
Example :
device = lnetatmo.ThermostatData(authorization, homeid)
for i in device.rawData:
print ('rawData')
print (i['_id'])
print (i['station_name'])
print (dir(i))
print ('modules in rawData')
print (i['modules'][0]['_id'])
print (i['modules'][0]['module_name'])
print (' ')
print ('Relay Data')
Relay = device.Relay_Plug()
print (Relay.keys())
print ('Thermostat Data')
TH = device.Thermostat_Data()
print (TH.keys())
Constructor
homesData = lnetatmo.HomesData ( authorization, home_id )
Requires :
- an authorization object (ClientAuth instance)
- home_id which can be found in https://dev.netatmo.com/apidocumentation/control by using "class homedata"
Return : a homesdata object. This object contains the Netatmo actual topology and static information of all devices present into a user account. It is also possible to specify a home_id to focus on one home.
Methods :
- rawData : Full dictionary of the returned JSON DEVICELIST Netatmo API service
- Output : list of IDs of every devices
Example :
homesData = lnetatmo.HomesData ( authorization, home_id )
print (homesdata.Homes_Data['name'])
print (homesdata.Homes_Data['altitude'])
print (homesdata.Homes_Data['coordinates'])
print (homesdata.Homes_Data['country'])
print (homesdata.Homes_Data['timezone'])
print ('rooms in HomesData')
for r in homesdata.Homes_Data.get('rooms'):
print (r)
print ('Devices in HomesData')
for m in homesdata.Homes_Data.get('modules'):
print (m)
print ('Persons in HomesData')
if 'persons' in homesdata.Homes_Data.keys():
for P in homesdata.Homes_Data.get('persons'):
print (P)
print ('Schedules in HomesData')
print (homesdata.Homes_Data['schedules'][0].keys())
Constructor
homecoach = lnetatmo.HomeCoach(authorization, home_id )
Requires :
- an authorization object (ClientAuth instance)
- home_id which can be found in https://dev.netatmo.com/apidocumentation/control by using "class homedata"
Return : a homecoach object. This object contains all Homecoach Data.
Methods :
-
rawData : Full dictionary of the returned JSON DEVICELIST Netatmo API service
- Output : list of all Homecoach in user account
-
checkNotUpdated :
- Output : list of modules name for which last data update is older than specified delay (default 1 hour).
-
checkUpdated :
- Output : list of modules name for which last data update is newer than specified delay (default 1 hour).
Example :
homecoach = lnetatmo.HomeCoach(authorization, homeid)
#
Not_updated = []
updated = []
d = {}
for device in homecoach.rawData:
_id = device['_id']
d.update({'When': device['dashboard_data']['time_utc']})
a = homecoach.checkNotUpdated(d, _id)
b = homecoach.checkUpdated(d, _id)
print (device['station_name'])
print (device['date_setup'])
print (device['last_setup'])
print (device['type'])
print (device['last_status_store'])
print (device['firmware'])
print (device['last_upgrade'])
print (device['wifi_status'])
print (device['reachable'])
print (device['co2_calibrating'])
print (device['data_type'])
print (device['place'])
print (device['dashboard_data'])
Not_updated.append(a)
updated.append(b)
D = homecoach.HomecoachDevice['dashboard_data']
print (D.keys())
# dict_keys(['time_utc', 'Temperature', 'CO2', 'Humidity', 'Noise', 'Pressure', 'AbsolutePressure', 'health_idx', 'min_temp', 'max_temp', 'date_max_temp', 'date_min_temp'])
print (Not_updated)
print (updated)
- rawAPI (authentication, APIkeyword, parameters) : Direct call an APIkeyword from Netatmo and return a dictionary with the raw response the APIkeywork is the path without the / before as specified in the documentation (eg. "gethomesdata" or "homestatus")
- toTimeString (timestamp) : Convert a Netatmo time stamp to a readable date/time format.
- toEpoch( dateString) : Convert a date string (form YYYY-MM-DD_HH:MM:SS) to timestamp
- todayStamps() : Return a couple of epoch time (start, end) for the current day
If you just need the current temperature and humidity reported by a sensor with associated min and max values on the last 24 hours, you can get it all with only one call that handle all required steps including authentication :
getStationMinMaxTH(station=None, module=None, home=None) :
- Input : optional station name and/or module name (if no station name is provided, default_station will be used, if no module name is provided, station sensor will be reported). if no home is specified, first returned home will be used
- Output : A tuple of 6 values (Temperature, Humidity, minT, MaxT, minH, maxH)
>>> import lnetatmo
>>> print(lnetatmo.getStationMinMaxTH())
[20, 33, 18.1, 20, 30, 34]
>>>
>>> print(lnetatmo.getStationMinMaxTH(module='outdoor'))
[2, 53, 1.2, 5.4, 51, 74]