Skip to content

Commit

Permalink
Imported the new version of netatmo api
Browse files Browse the repository at this point in the history
  • Loading branch information
Samuel Dumont committed Dec 15, 2018
2 parents 5c528a9 + 79704a0 commit 8860303
Show file tree
Hide file tree
Showing 11 changed files with 336 additions and 90 deletions.
21 changes: 21 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2018 Hugo DUPRAS

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include README.md
include LICENSE.txt
111 changes: 60 additions & 51 deletions lnetatmo.py → pyatmo.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,61 +9,66 @@
cameras or/and the Netatmo smart thermostat
This package can be used with Python2 or Python3 applications and do not
require anything else than standard libraries
PythonAPI Netatmo REST data access
coding=utf-8
"""
import time
from os import getenv
from os.path import expanduser, exists
import json, time
import json
import time
from smart_home.WeatherStation import WeatherStationData, DeviceList
from smart_home.Camera import CameraData
from smart_home.Thermostat import ThermostatData
from smart_home.VaillantThermostat import VaillantThermostatData
from smart_home.PublicData import PublicData
from smart_home.HomeCoach import HomeCoachData
from smart_home import _BASE_URL, postRequest, NoDevice

#########################################################################

cred = { # You can hard code authentication information in the following lines
"CLIENT_ID" : "", # Your client ID from Netatmo app registration at http://dev.netatmo.com/dev/listapps
"CLIENT_SECRET" : "", # Your client app secret ' '
"USERNAME" : "", # Your netatmo account username
"PASSWORD" : "", # Your netatmo account password
"APP_VERSION" : "", # The app version
"USER_PREFIX" : "", # The user prefix
"SCOPE" : ""
}
# Your client ID from Netatmo app registration at http://dev.netatmo.com/dev/listapps
"CLIENT_ID": "",
"CLIENT_SECRET": "", # Your client app secret ' '
"USERNAME": "", # Your netatmo account username
"PASSWORD": "", # Your netatmo account password
"APP_VERSION": "", # The app version
"USER_PREFIX": "", # The user prefix
"SCOPE": ""
}

# Other authentication setup management (optionals)

CREDENTIALS = expanduser("~/.netatmo.credentials")


def getParameter(key, default):
return getenv(key, default[key])


# 2 : Override hard coded values with credentials file if any
if exists(CREDENTIALS) :
if exists(CREDENTIALS):
with open(CREDENTIALS, "r") as f:
cred.update({k.upper():v for k,v in json.loads(f.read()).items()})
cred.update({k.upper(): v for k, v in json.loads(f.read()).items()})

# 3 : Override final value with content of env variables if defined
_CLIENT_ID = getParameter("CLIENT_ID", cred)
_CLIENT_ID = getParameter("CLIENT_ID", cred)
_CLIENT_SECRET = getParameter("CLIENT_SECRET", cred)
_USERNAME = getParameter("USERNAME", cred)
_PASSWORD = getParameter("PASSWORD", cred)
_APP_VERSION = getParameter("APP_VERSION", cred)
_USER_PREFIX = getParameter("USER_PREFIX", cred)
_SCOPE = getParameter("SCOPE", cred)
_USERNAME = getParameter("USERNAME", cred)
_PASSWORD = getParameter("PASSWORD", cred)
_APP_VERSION = getParameter("APP_VERSION", cred)
_USER_PREFIX = getParameter("USER_PREFIX", cred)
_SCOPE = getParameter("SCOPE", cred)


# Common definitions
_AUTH_REQ = _BASE_URL + "oauth2/token"
_AUTH_REQ = _BASE_URL + "oauth2/token"


class ClientAuth:
"""
Request authentication and keep access token available through token method. Renew it automatically if necessary
Args:
clientId (str): Application clientId delivered by Netatmo on dev.netatmo.com
clientSecret (str): Application Secret key delivered by Netatmo on dev.netatmo.com
Expand All @@ -81,25 +86,25 @@ class ClientAuth:
"""

def __init__(self, clientId=_CLIENT_ID,
clientSecret=_CLIENT_SECRET,
username=_USERNAME,
password=_PASSWORD,
scope=_SCOPE,
app_version=_APP_VERSION,
user_prefix=_USER_PREFIX):
clientSecret=_CLIENT_SECRET,
username=_USERNAME,
password=_PASSWORD,
scope=_SCOPE,
app_version=_APP_VERSION,
user_prefix=_USER_PREFIX):
postParams = {
"grant_type": "password",
"client_id": clientId,
"client_secret": clientSecret,
"username": username,
"password": password,
"scope": scope
}
"grant_type": "password",
"client_id": clientId,
"client_secret": clientSecret,
"username": username,
"password": password,
"scope": scope
}

if user_prefix:
postParams.update({"user_prefix":user_prefix})
postParams.update({"user_prefix": user_prefix})
if app_version:
postParams.update({"app_version":app_version})
postParams.update({"app_version": app_version})
resp = postRequest(_AUTH_REQ, postParams)
self._clientId = clientId
self._clientSecret = clientSecret
Expand All @@ -113,11 +118,11 @@ def accessToken(self):

if self.expiration < time.time(): # Token should be renewed
postParams = {
"grant_type": "refresh_token",
"refresh_token": self.refreshToken,
"client_id": self._clientId,
"client_secret": self._clientSecret
}
"grant_type": "refresh_token",
"refresh_token": self.refreshToken,
"client_id": self._clientId,
"client_secret": self._clientSecret
}
resp = postRequest(_AUTH_REQ, postParams)
self._accessToken = resp['access_token']
self.refreshToken = resp['refresh_token']
Expand All @@ -128,30 +133,34 @@ def accessToken(self):
class User:
"""
This class returns basic information about the user
Args:
authData (ClientAuth): Authentication information with a working access Token
"""

def __init__(self, authData):
postParams = {
"access_token" : authData.accessToken
}
"access_token": authData.accessToken
}
resp = postRequest(_GETSTATIONDATA_REQ, postParams)
self.rawData = resp['body']
self.devList = self.rawData['devices']
self.ownerMail = self.rawData['user']['mail']

# auto-test when executed directly


if __name__ == "__main__":

from sys import exit, stdout, stderr

if not _CLIENT_ID or not _CLIENT_SECRET or not _USERNAME or not _PASSWORD :
stderr.write("Library source missing identification arguments to check lnetatmo.py (user/password/etc...)")
exit(1)
if not _CLIENT_ID or not _CLIENT_SECRET or not _USERNAME or not _PASSWORD:
stderr.write(
"Library source missing identification arguments to check lnetatmo.py (user/password/etc...)")
exit(1)

authorization = ClientAuth(scope="read_station read_camera access_camera read_thermostat write_thermostat read_presence access_presence") # Test authentication method
# Test authentication method
authorization = ClientAuth(
scope="read_station read_camera access_camera read_thermostat write_thermostat read_presence access_presence")

try:
devList = DeviceList(authorization) # Test DEVICELIST
Expand All @@ -161,24 +170,24 @@ def __init__(self, authData):
else:
devList.MinMaxTH() # Test GETMEASUR


try:
Camera = CameraData(authorization)
except NoDevice :
except NoDevice:
if stdout.isatty():
print("lnetatmo.py : warning, no camera available for testing")

try:
Thermostat = ThermostatData(authorization)
except NoDevice :
except NoDevice:
if stdout.isatty():
print("lnetatmo.py : warning, no thermostat available for testing")

PublicData(authorization)

# If we reach this line, all is OK

# If launched interactively, display OK message
if stdout.isatty():
print("lnetatmo.py : OK")

exit(0)

2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[metadata]
description-file = README.md
18 changes: 11 additions & 7 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@
# python setup.py --dry-run --verbose install

from distutils.core import setup
import setuptools


setup(
name='lnetatmo',
version='0.9.2.1-vaillant', # Should be updated with new versions
author='Samuel Dumont, forked from Philippe Larduinat',
author_email='[email protected], [email protected]',
py_modules=['lnetatmo'],
name='pyatmo',
version='1.4.1-vaillant', # Should be updated with new versions
author='Samuel Dumont, based on Hugo Dupras',
author_email='[email protected]',
py_modules=['pyatmo'],

packages=['smart_home'],
package_dir={'smart_home': 'smart_home'},
scripts=[],
data_files=[],

url='https://github.com/samueldumont/netatmo-api-python',
license='Open Source',
description='Simple API to access Vaillant vSmart thermostat data (rebranded Netatmo thermostat)',
license='MIT',
description='Simple API to access Netatmo weather station data from any python script. '
'Design for Home-Assitant (but not only)',
long_description=open('README.md').read()
)
36 changes: 20 additions & 16 deletions smart_home/Camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ def __init__(self, authData, size=15):
"size": size
}
resp = postRequest(_GETHOMEDATA_REQ, postParams)
if 'body' not in resp:
raise URLError('No data returned by Netatmo server')
self.rawData = resp['body']
self.homes = {d['id']: d for d in self.rawData['homes']}
if not self.homes:
Expand All @@ -47,18 +49,20 @@ def __init__(self, authData, size=15):
self.types[nameHome] = dict()
for p in self.rawData['homes'][i]['persons']:
self.persons[p['id']] = p
for e in self.rawData['homes'][i]['events']:
if e['type'] == 'outdoor':
if e['camera_id'] not in self.outdoor_events:
self.outdoor_events[e['camera_id']] = dict()
self.outdoor_events[e['camera_id']][e['time']] = e
elif e['type'] != 'outdoor':
if e['camera_id'] not in self.events:
self.events[e['camera_id']] = dict()
self.events[e['camera_id']][e['time']] = e
if 'events' in self.rawData['homes'][i]:
self.default_home = self.rawData['homes'][i]['name']
for e in self.rawData['homes'][i]['events']:
if e['type'] == 'outdoor':
if e['camera_id'] not in self.outdoor_events:
self.outdoor_events[e['camera_id']] = dict()
self.outdoor_events[e['camera_id']][e['time']] = e
elif e['type'] != 'outdoor':
if e['camera_id'] not in self.events:
self.events[e['camera_id']] = dict()
self.events[e['camera_id']][e['time']] = e
for c in self.rawData['homes'][i]['cameras']:
self.cameras[nameHome][c['id']] = c
if c['type'] == 'NACamera' and 'modules' in c :
if c['type'] == 'NACamera' and 'modules' in c:
for m in c['modules']:
self.modules[m['id']] = m
self.modules[m['id']]['cam_id'] = c['id']
Expand All @@ -70,7 +74,6 @@ def __init__(self, authData, size=15):
for camera in self.outdoor_events:
self.outdoor_lastEvent[camera] = self.outdoor_events[camera][
sorted(self.outdoor_events[camera])[-1]]
self.default_home = list(self.homes.values())[0]['name']
if self.modules != {}:
self.default_module = list(self.modules.values())[0]['name']
else:
Expand Down Expand Up @@ -300,7 +303,7 @@ def _knownPersons(self):

def knownPersonsNames(self):
names = []
for p_id,p in self._knownPersons().items():
for p_id, p in self._knownPersons().items():
names.append(p['pseudo'])
return names

Expand Down Expand Up @@ -390,10 +393,11 @@ def outdoormotionDetected(self, home=None, camera=None, offset=0):
except TypeError:
print("outdoormotionDetected: Camera name or home is unknown")
return False
if self.lastEvent[cam_id]['type'] == 'movement':
if self.lastEvent[cam_id]['video_status'] == 'recording' and\
self.lastEvent[cam_id]['time'] + offset > int(time.time()):
return True
if cam_id in self.lastEvent:
if self.lastEvent[cam_id]['type'] == 'movement':
if self.lastEvent[cam_id]['video_status'] == 'recording' and\
self.lastEvent[cam_id]['time'] + offset > int(time.time()):
return True
return False

def humanDetected(self, home=None, camera=None, offset=0):
Expand Down
18 changes: 18 additions & 0 deletions smart_home/HomeCoach.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""
coding=utf-8
"""
from .WeatherStation import WeatherStationData

from . import _BASE_URL

_GETHOMECOACHDATA_REQ = _BASE_URL + "api/gethomecoachsdata"


class HomeCoachData(WeatherStationData):
"""
List the Home Couch devices (stations and modules)
Args:
authData (ClientAuth): Authentication information with a working access Token
"""
def __init__(self, authData):
super(HomeCoachData, self).__init__(authData, urlReq=_GETHOMECOACHDATA_REQ)
Loading

0 comments on commit 8860303

Please sign in to comment.