From 520a8d0d0ddef419ca47a50dffb8c7363286ea88 Mon Sep 17 00:00:00 2001 From: hydreliox Date: Wed, 6 Jan 2016 03:39:16 +0100 Subject: [PATCH 01/18] Add NetAtmo Platform Alpha version of the platform. API library is not yet on PyPI --- homeassistant/components/sensor/lnetatmo.py | 305 ++++++++++++++++++++ homeassistant/components/sensor/netatmo.py | 147 ++++++++++ 2 files changed, 452 insertions(+) create mode 100644 homeassistant/components/sensor/lnetatmo.py create mode 100644 homeassistant/components/sensor/netatmo.py diff --git a/homeassistant/components/sensor/lnetatmo.py b/homeassistant/components/sensor/lnetatmo.py new file mode 100644 index 00000000000..2be60c7fd29 --- /dev/null +++ b/homeassistant/components/sensor/lnetatmo.py @@ -0,0 +1,305 @@ +# Published Jan 2013 +# Revised Jan 2014 (to add new modules data) +# Author : Philippe Larduinat, philippelt@users.sourceforge.net +# Public domain source code + +# This API provides access to the Netatmo (Internet weather station) devices +# 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 + +from sys import version_info +import json, time + +# HTTP libraries depends upon Python 2 or 3 +if version_info.major == 3 : + import urllib.parse, urllib.request +else: + from urllib import urlencode + import urllib2 + +######################## USER SPECIFIC INFORMATION ###################### + +# To be able to have a program accessing your netatmo data, you have to register your program as +# a Netatmo app in your Netatmo account. All you have to do is to give it a name (whatever) and you will be +# returned a client_id and secret that your app has to supply to access netatmo servers. + +_CLIENT_ID = "55716d9c1b77591e138b4747" # Your client ID from Netatmo app registration at http://dev.netatmo.com/dev/listapps +_CLIENT_SECRET = "OA4Eb0oZW3B6YyR9wNh2HMkri2wV8g" # Your client app secret ' ' +_USERNAME = "hydreliox@gmail.com" # Your netatmo account username +_PASSWORD = "netatmo@gTV7y5Te" # Your netatmo account password + +######################################################################### + + +# Common definitions + +_BASE_URL = "https://api.netatmo.net/" +_AUTH_REQ = _BASE_URL + "oauth2/token" +_GETUSER_REQ = _BASE_URL + "api/getuser" +_DEVICELIST_REQ = _BASE_URL + "api/devicelist" +_GETMEASURE_REQ = _BASE_URL + "api/getmeasure" + + +class ClientAuth: + "Request authentication and keep access token available through token method. Renew it automatically if necessary" + + def __init__(self, clientId=_CLIENT_ID, + clientSecret=_CLIENT_SECRET, + username=_USERNAME, + password=_PASSWORD): + + postParams = { + "grant_type" : "password", + "client_id" : clientId, + "client_secret" : clientSecret, + "username" : username, + "password" : password, + "scope" : "read_station" + } + resp = postRequest(_AUTH_REQ, postParams) + + self._clientId = clientId + self._clientSecret = clientSecret + self._accessToken = resp['access_token'] + self.refreshToken = resp['refresh_token'] + self._scope = resp['scope'] + self.expiration = int(resp['expire_in'] + time.time()) + + @property + 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 + } + resp = postRequest(_AUTH_REQ, postParams) + + self._accessToken = resp['access_token'] + self.refreshToken = resp['refresh_token'] + self.expiration = int(resp['expire_in'] + time.time()) + + return self._accessToken + +class User: + + def __init__(self, authData): + + postParams = { + "access_token" : authData.accessToken + } + resp = postRequest(_GETUSER_REQ, postParams) + self.rawData = resp['body'] + self.id = self.rawData['_id'] + self.devList = self.rawData['devices'] + self.ownerMail = self.rawData['mail'] + +class DeviceList: + + def __init__(self, authData): + + self.getAuthToken = authData.accessToken + postParams = { + "access_token" : self.getAuthToken, + "app_type" : "app_station" + } + resp = postRequest(_DEVICELIST_REQ, postParams) + self.rawData = resp['body'] + self.stations = { d['_id'] : d for d in self.rawData['devices'] } + self.modules = { m['_id'] : m for m in self.rawData['modules'] } + self.default_station = list(self.stations.values())[0]['station_name'] + + def modulesNamesList(self, station=None): + res = [m['module_name'] for m in self.modules.values()] + res.append(self.stationByName(station)['module_name']) + return res + + def stationByName(self, station=None): + if not station : station = self.default_station + for i,s in self.stations.items(): + if s['station_name'] == station : return self.stations[i] + return None + + def stationById(self, sid): + return None if sid not in self.stations else self.stations[sid] + + def moduleByName(self, module, station=None): + s = None + if station : + s = self.stationByName(station) + if not s : return None + for m in self.modules: + mod = self.modules[m] + if mod['module_name'] == module : + if not s or mod['main_device'] == s['_id'] : return mod + return None + + def moduleById(self, mid, sid=None): + s = self.stationById(sid) if sid else None + if mid in self.modules : + return self.modules[mid] if not s or self.modules[mid]['main_device'] == s['_id'] else None + + def lastData(self, station=None, exclude=0): + s = self.stationByName(station) + if not s : return None + lastD = dict() + # Define oldest acceptable sensor measure event + limit = (time.time() - exclude) if exclude else 0 + ds = s['dashboard_data'] + if ds['time_utc'] > limit : + lastD[s['module_name']] = ds.copy() + lastD[s['module_name']]['When'] = lastD[s['module_name']].pop("time_utc") + lastD[s['module_name']]['wifi_status'] = s['wifi_status'] + for mId in s["modules"]: + ds = self.modules[mId]['dashboard_data'] + if ds['time_utc'] > limit : + mod = self.modules[mId] + lastD[mod['module_name']] = ds.copy() + lastD[mod['module_name']]['When'] = lastD[mod['module_name']].pop("time_utc") + # For potential use, add battery and radio coverage information to module data if present + for i in ('battery_vp', 'rf_status') : + if i in mod : lastD[mod['module_name']][i] = mod[i] + return lastD + + def checkNotUpdated(self, station=None, delay=3600): + res = self.lastData(station) + ret = [] + for mn,v in res.items(): + if time.time()-v['When'] > delay : ret.append(mn) + return ret if ret else None + + def checkUpdated(self, station=None, delay=3600): + res = self.lastData(station) + ret = [] + for mn,v in res.items(): + if time.time()-v['When'] < delay : ret.append(mn) + return ret if ret else None + + def getMeasure(self, device_id, scale, mtype, module_id=None, date_begin=None, date_end=None, limit=None, optimize=False, real_time=False): + postParams = { "access_token" : self.getAuthToken } + postParams['device_id'] = device_id + if module_id : postParams['module_id'] = module_id + postParams['scale'] = scale + postParams['type'] = mtype + if date_begin : postParams['date_begin'] = date_begin + if date_end : postParams['date_end'] = date_end + if limit : postParams['limit'] = limit + postParams['optimize'] = "true" if optimize else "false" + postParams['real_time'] = "true" if real_time else "false" + return postRequest(_GETMEASURE_REQ, postParams) + + def MinMaxTH(self, station=None, module=None, frame="last24"): + if not station : station = self.default_station + s = self.stationByName(station) + if not s : + s = self.stationById(station) + if not s : return None + if frame == "last24": + end = time.time() + start = end - 24*3600 # 24 hours ago + elif frame == "day": + start, end = todayStamps() + if module and module != s['module_name']: + m = self.moduleByName(module, s['station_name']) + if not m : + m = self.moduleById(s['_id'], module) + if not m : return None + # retrieve module's data + resp = self.getMeasure( + device_id = s['_id'], + module_id = m['_id'], + scale = "max", + mtype = "Temperature,Humidity", + date_begin = start, + date_end = end) + else : # retrieve station's data + resp = self.getMeasure( + device_id = s['_id'], + scale = "max", + mtype = "Temperature,Humidity", + date_begin = start, + date_end = end) + if resp: + 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) + else: + return None + +# Utilities routines + +def postRequest(url, params): + if version_info.major == 3: + req = urllib.request.Request(url) + req.add_header("Content-Type","application/x-www-form-urlencoded;charset=utf-8") + params = urllib.parse.urlencode(params).encode('utf-8') + resp = urllib.request.urlopen(req, params).read().decode("utf-8") + else: + params = urlencode(params) + headers = {"Content-Type" : "application/x-www-form-urlencoded;charset=utf-8"} + req = urllib2.Request(url=url, data=params, headers=headers) + resp = urllib2.urlopen(req).read() + return json.loads(resp) + +def toTimeString(value): + return time.strftime("%Y-%m-%d_%H:%M:%S", time.localtime(int(value))) + +def toEpoch(value): + return int(time.mktime(time.strptime(value,"%Y-%m-%d_%H:%M:%S"))) + +def todayStamps(): + today = time.strftime("%Y-%m-%d") + today = int(time.mktime(time.strptime(today,"%Y-%m-%d"))) + return today, today+3600*24 + +# Global shortcut + +def getStationMinMaxTH(station=None, module=None): + authorization = ClientAuth() + devList = DeviceList(authorization) + if not station : station = devList.default_station + if module : + mname = module + else : + mname = devList.stationByName(station)['module_name'] + lastD = devList.lastData(station) + if mname == "*": + result = dict() + 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]) + else: + if time.time()-lastD[mname]['When'] > 3600 : result = ["-", "-"] + else : result = [lastD[mname]['Temperature'], lastD[mname]['Humidity']] + result.extend(devList.MinMaxTH(station, mname)) + return result + +# 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) + + authorization = ClientAuth() # Test authentication method + user = User(authorization) # Test GETUSER + devList = DeviceList(authorization) # Test DEVICELIST + devList.MinMaxTH() # Test GETMEASURE + + # If we reach this line, all is OK + + # If launched interactively, display OK message + if stdout.isatty(): + print("lnetatmo.py : OK") + + exit(0) diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py new file mode 100644 index 00000000000..1d6834ee655 --- /dev/null +++ b/homeassistant/components/sensor/netatmo.py @@ -0,0 +1,147 @@ +""" +homeassistant.components.sensor.netatmo +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +NetAtmo Weather Service service. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/... +""" +import logging +from homeassistant.components.sensor import lnetatmo +from datetime import timedelta + +from homeassistant.const import (CONF_API_KEY, CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS, TEMP_FAHRENHEIT) +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle + +REQUIREMENTS = [] +_LOGGER = logging.getLogger(__name__) +SENSOR_TYPES = { + 'temperature': ['Temperature', ''], + 'humidity': ['Humidity', '%'] +} + +# Return cached results if last scan was less then this time ago +# NetAtmo Data is uploaded to server every 10mn so this time should not be under +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=600) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Get the NetAtmo sensor. """ + + try: + from homeassistant.components.sensor import lnetatmo + + except ImportError: + _LOGGER.exception( + "Unable to import lnetatmo. " + "Did you maybe not install the package ?") + + return False + + SENSOR_TYPES['temperature'][1] = hass.config.temperature_unit + unit = hass.config.temperature_unit + authorization = lnetatmo.ClientAuth(config.get(CONF_API_KEY, None), config.get('secret_key', None), config.get(CONF_USERNAME, None), config.get(CONF_PASSWORD, None)) + + if not authorization: + _LOGGER.error( + "Connection error " + "Please check your settings for NatAtmo API.") + return False + + data = NetAtmoData(authorization) + + module_name = 'Salon' + + dev = [] + try: + for variable in config['monitored_conditions']: + if variable not in SENSOR_TYPES: + _LOGGER.error('Sensor type: "%s" does not exist', variable) + else: + dev.append(NetAtmoSensor( data, module_name, variable, unit)) + except KeyError: + pass + + add_devices(dev) + + +# pylint: disable=too-few-public-methods +class NetAtmoSensor(Entity): + """ Implements a NetAtmo sensor. """ + + def __init__(self, netatmo_data, module_name, sensor_type, temp_unit): + self.client_name = 'NetAtmo' + self._name = SENSOR_TYPES[sensor_type][0] + self.netatmo_data = netatmo_data + self.module_name = module_name + self.temp_unit = temp_unit + self.type = sensor_type + self._state = None + self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] + self.update() + + @property + def name(self): + return '{} {}'.format(self.client_name, self._name) + + @property + def state(self): + """ Returns the state of the device. """ + return self._state + + @property + def unit_of_measurement(self): + """ Unit of measurement of this entity, if any. """ + return self._unit_of_measurement + + # pylint: disable=too-many-branches + def update(self): + """ Gets the latest data from NetAtmo API and updates the states. """ + + self.netatmo_data.update() + data = self.netatmo_data.data[self.module_name] + + if self.type == 'temperature': + if self.temp_unit == TEMP_CELCIUS: + self._state = round(data['Temperature'], + 1) + elif self.temp_unit == TEMP_FAHRENHEIT: + self._state = round(data['Temperature'], + 1) + else: + self._state = round(data['Temperature'], 1) + elif self.type == 'humidity': + self._state = data['Humidity'] + elif self.type == 'pressure': + self._state = round(data.get_pressure()['press'], 0) + elif self.type == 'clouds': + self._state = data.get_clouds() + elif self.type == 'rain': + if data.get_rain(): + self._state = round(data.get_rain()['3h'], 0) + self._unit_of_measurement = 'mm' + else: + self._state = 'not raining' + self._unit_of_measurement = '' + elif self.type == 'snow': + if data.get_snow(): + self._state = round(data.get_snow(), 0) + self._unit_of_measurement = 'mm' + else: + self._state = 'not snowing' + self._unit_of_measurement = '' + + +class NetAtmoData(object): + """ Gets the latest data from NetAtmo. """ + + def __init__(self, auth): + self.auth = auth + self.data = None + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """ Gets the latest data from NetAtmo. """ + devList = lnetatmo.DeviceList(self.auth) + self.data = devList.lastData(exclude=3600) \ No newline at end of file From a8b36d9baa839c410cd715a6508808b479611123 Mon Sep 17 00:00:00 2001 From: hydreliox Date: Wed, 6 Jan 2016 04:36:04 +0100 Subject: [PATCH 02/18] Multiple Module Configuration Handle multiple module (see configuration.yaml) --- homeassistant/components/sensor/netatmo.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index 1d6834ee655..da21323f8ed 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -51,15 +51,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): data = NetAtmoData(authorization) - module_name = 'Salon' - dev = [] try: - for variable in config['monitored_conditions']: - if variable not in SENSOR_TYPES: - _LOGGER.error('Sensor type: "%s" does not exist', variable) - else: - dev.append(NetAtmoSensor( data, module_name, variable, unit)) + for module_name, monitored_conditions in config['modules'].items(): + for variable in monitored_conditions: + if variable not in SENSOR_TYPES: + _LOGGER.error('Sensor type: "%s" does not exist', variable) + else: + dev.append(NetAtmoSensor(data, module_name, variable, unit)) except KeyError: pass @@ -72,7 +71,7 @@ class NetAtmoSensor(Entity): def __init__(self, netatmo_data, module_name, sensor_type, temp_unit): self.client_name = 'NetAtmo' - self._name = SENSOR_TYPES[sensor_type][0] + self._name = module_name + '_' + SENSOR_TYPES[sensor_type][0] self.netatmo_data = netatmo_data self.module_name = module_name self.temp_unit = temp_unit From 1ed574b2a0435dedccccf02bed6f1df1337e99d3 Mon Sep 17 00:00:00 2001 From: hydreliox Date: Fri, 8 Jan 2016 05:34:51 +0100 Subject: [PATCH 03/18] Point lnetatmo library directly from github --- homeassistant/components/sensor/netatmo.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index da21323f8ed..90dc10f182a 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -7,14 +7,16 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/... """ import logging -from homeassistant.components.sensor import lnetatmo from datetime import timedelta from homeassistant.const import (CONF_API_KEY, CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS, TEMP_FAHRENHEIT) from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -REQUIREMENTS = [] +REQUIREMENTS = [ + 'https://github.com/HydrelioxGitHub/netatmo-api-python/archive/' + 'f468d0926b1bc018df66896f5d67585343b56dda.zip'] + _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = { 'temperature': ['Temperature', ''], @@ -30,7 +32,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """ Get the NetAtmo sensor. """ try: - from homeassistant.components.sensor import lnetatmo + from lnetatmo import lnetatmo except ImportError: _LOGGER.exception( @@ -136,11 +138,13 @@ class NetAtmoData(object): """ Gets the latest data from NetAtmo. """ def __init__(self, auth): + from lnetatmo import DeviceList + self.auth = auth self.data = None @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """ Gets the latest data from NetAtmo. """ - devList = lnetatmo.DeviceList(self.auth) + devList = DeviceList(self.auth) self.data = devList.lastData(exclude=3600) \ No newline at end of file From 4c47ed31ff65b6ca62cdced873999abbe21603d0 Mon Sep 17 00:00:00 2001 From: hydreliox Date: Fri, 8 Jan 2016 06:19:03 +0100 Subject: [PATCH 04/18] Correction to import lnetatmo right --- homeassistant/components/sensor/netatmo.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index 90dc10f182a..179921169dd 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -15,7 +15,8 @@ from homeassistant.util import Throttle REQUIREMENTS = [ 'https://github.com/HydrelioxGitHub/netatmo-api-python/archive/' - 'f468d0926b1bc018df66896f5d67585343b56dda.zip'] + '59d157d03db0aa167730044667591adea4457ca8.zip' + '#lnetatmo==0.3.0.dev1'] _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = { @@ -32,7 +33,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """ Get the NetAtmo sensor. """ try: - from lnetatmo import lnetatmo + import lnetatmo except ImportError: _LOGGER.exception( @@ -138,13 +139,13 @@ class NetAtmoData(object): """ Gets the latest data from NetAtmo. """ def __init__(self, auth): - from lnetatmo import DeviceList self.auth = auth self.data = None @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): + import lnetatmo """ Gets the latest data from NetAtmo. """ - devList = DeviceList(self.auth) + devList = lnetatmo.DeviceList(self.auth) self.data = devList.lastData(exclude=3600) \ No newline at end of file From c97a25cc8693ddecf337a86edd1ffb576872e456 Mon Sep 17 00:00:00 2001 From: hydreliox Date: Mon, 11 Jan 2016 05:21:38 +0100 Subject: [PATCH 05/18] Add configuration verification and sensors type Generate an error in the configuration name of a module or a queried sensor type is not correct. Add support for co2 pressure and noise sensors --- homeassistant/components/sensor/netatmo.py | 44 +++++++++++----------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index 179921169dd..f512510b93d 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -8,7 +8,6 @@ https://home-assistant.io/components/... """ import logging from datetime import timedelta - from homeassistant.const import (CONF_API_KEY, CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS, TEMP_FAHRENHEIT) from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle @@ -21,6 +20,9 @@ REQUIREMENTS = [ _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = { 'temperature': ['Temperature', ''], + 'co2': ['CO2', 'ppm'], + 'pressure': ['Pressure', 'mb'], + 'noise': ['Noise', 'dB'], 'humidity': ['Humidity', '%'] } @@ -44,7 +46,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): SENSOR_TYPES['temperature'][1] = hass.config.temperature_unit unit = hass.config.temperature_unit - authorization = lnetatmo.ClientAuth(config.get(CONF_API_KEY, None), config.get('secret_key', None), config.get(CONF_USERNAME, None), config.get(CONF_PASSWORD, None)) + authorization = lnetatmo.ClientAuth(config.get(CONF_API_KEY, None), config.get('secret_key', None), + config.get(CONF_USERNAME, None), config.get(CONF_PASSWORD, None)) if not authorization: _LOGGER.error( @@ -56,7 +59,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None): dev = [] try: + """ Iterate each module """ for module_name, monitored_conditions in config['modules'].items(): + """ Test if module exist """ + if module_name not in data.get_module_names(): + _LOGGER.error('Module name: "%s" not found', module_name) + continue + """ Only create sensor for monitored """ for variable in monitored_conditions: if variable not in SENSOR_TYPES: _LOGGER.error('Sensor type: "%s" does not exist', variable) @@ -115,37 +124,30 @@ class NetAtmoSensor(Entity): self._state = round(data['Temperature'], 1) elif self.type == 'humidity': self._state = data['Humidity'] + elif self.type == 'noise': + self._state = data['Noise'] + elif self.type == 'co2': + self._state = data['CO2'] elif self.type == 'pressure': - self._state = round(data.get_pressure()['press'], 0) - elif self.type == 'clouds': - self._state = data.get_clouds() - elif self.type == 'rain': - if data.get_rain(): - self._state = round(data.get_rain()['3h'], 0) - self._unit_of_measurement = 'mm' - else: - self._state = 'not raining' - self._unit_of_measurement = '' - elif self.type == 'snow': - if data.get_snow(): - self._state = round(data.get_snow(), 0) - self._unit_of_measurement = 'mm' - else: - self._state = 'not snowing' - self._unit_of_measurement = '' + self._state = round(data['Pressure'], + 1) class NetAtmoData(object): """ Gets the latest data from NetAtmo. """ def __init__(self, auth): - self.auth = auth self.data = None + def get_module_names (self) : + self.update() + return self.data.keys() + + @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): import lnetatmo """ Gets the latest data from NetAtmo. """ devList = lnetatmo.DeviceList(self.auth) - self.data = devList.lastData(exclude=3600) \ No newline at end of file + self.data = devList.lastData(exclude=3600) From 7b999e6cd138b11a3e3e8f51611babaeb1df43cd Mon Sep 17 00:00:00 2001 From: hydreliox Date: Mon, 11 Jan 2016 05:29:32 +0100 Subject: [PATCH 06/18] Convert Temperature output Convert temperature from celcius to fahrenheit if the HA config need it --- homeassistant/components/sensor/netatmo.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index f512510b93d..a23ef5fa09a 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -11,6 +11,7 @@ from datetime import timedelta from homeassistant.const import (CONF_API_KEY, CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS, TEMP_FAHRENHEIT) from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle +from homeassistant.util.temperature import celcius_to_fahrenheit REQUIREMENTS = [ 'https://github.com/HydrelioxGitHub/netatmo-api-python/archive/' @@ -118,8 +119,8 @@ class NetAtmoSensor(Entity): self._state = round(data['Temperature'], 1) elif self.temp_unit == TEMP_FAHRENHEIT: - self._state = round(data['Temperature'], - 1) + converted_temperature = celcius_to_fahrenheit(data['Temperature']) + self._state = round(converted_temperature, 1) else: self._state = round(data['Temperature'], 1) elif self.type == 'humidity': From 1f1a46a8bdf97c6deff59a28d06fadf5d06d863a Mon Sep 17 00:00:00 2001 From: hydreliox Date: Mon, 11 Jan 2016 05:35:26 +0100 Subject: [PATCH 07/18] Remove unused library Remove the library used for dev --- homeassistant/components/sensor/lnetatmo.py | 305 -------------------- 1 file changed, 305 deletions(-) delete mode 100644 homeassistant/components/sensor/lnetatmo.py diff --git a/homeassistant/components/sensor/lnetatmo.py b/homeassistant/components/sensor/lnetatmo.py deleted file mode 100644 index 2be60c7fd29..00000000000 --- a/homeassistant/components/sensor/lnetatmo.py +++ /dev/null @@ -1,305 +0,0 @@ -# Published Jan 2013 -# Revised Jan 2014 (to add new modules data) -# Author : Philippe Larduinat, philippelt@users.sourceforge.net -# Public domain source code - -# This API provides access to the Netatmo (Internet weather station) devices -# 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 - -from sys import version_info -import json, time - -# HTTP libraries depends upon Python 2 or 3 -if version_info.major == 3 : - import urllib.parse, urllib.request -else: - from urllib import urlencode - import urllib2 - -######################## USER SPECIFIC INFORMATION ###################### - -# To be able to have a program accessing your netatmo data, you have to register your program as -# a Netatmo app in your Netatmo account. All you have to do is to give it a name (whatever) and you will be -# returned a client_id and secret that your app has to supply to access netatmo servers. - -_CLIENT_ID = "55716d9c1b77591e138b4747" # Your client ID from Netatmo app registration at http://dev.netatmo.com/dev/listapps -_CLIENT_SECRET = "OA4Eb0oZW3B6YyR9wNh2HMkri2wV8g" # Your client app secret ' ' -_USERNAME = "hydreliox@gmail.com" # Your netatmo account username -_PASSWORD = "netatmo@gTV7y5Te" # Your netatmo account password - -######################################################################### - - -# Common definitions - -_BASE_URL = "https://api.netatmo.net/" -_AUTH_REQ = _BASE_URL + "oauth2/token" -_GETUSER_REQ = _BASE_URL + "api/getuser" -_DEVICELIST_REQ = _BASE_URL + "api/devicelist" -_GETMEASURE_REQ = _BASE_URL + "api/getmeasure" - - -class ClientAuth: - "Request authentication and keep access token available through token method. Renew it automatically if necessary" - - def __init__(self, clientId=_CLIENT_ID, - clientSecret=_CLIENT_SECRET, - username=_USERNAME, - password=_PASSWORD): - - postParams = { - "grant_type" : "password", - "client_id" : clientId, - "client_secret" : clientSecret, - "username" : username, - "password" : password, - "scope" : "read_station" - } - resp = postRequest(_AUTH_REQ, postParams) - - self._clientId = clientId - self._clientSecret = clientSecret - self._accessToken = resp['access_token'] - self.refreshToken = resp['refresh_token'] - self._scope = resp['scope'] - self.expiration = int(resp['expire_in'] + time.time()) - - @property - 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 - } - resp = postRequest(_AUTH_REQ, postParams) - - self._accessToken = resp['access_token'] - self.refreshToken = resp['refresh_token'] - self.expiration = int(resp['expire_in'] + time.time()) - - return self._accessToken - -class User: - - def __init__(self, authData): - - postParams = { - "access_token" : authData.accessToken - } - resp = postRequest(_GETUSER_REQ, postParams) - self.rawData = resp['body'] - self.id = self.rawData['_id'] - self.devList = self.rawData['devices'] - self.ownerMail = self.rawData['mail'] - -class DeviceList: - - def __init__(self, authData): - - self.getAuthToken = authData.accessToken - postParams = { - "access_token" : self.getAuthToken, - "app_type" : "app_station" - } - resp = postRequest(_DEVICELIST_REQ, postParams) - self.rawData = resp['body'] - self.stations = { d['_id'] : d for d in self.rawData['devices'] } - self.modules = { m['_id'] : m for m in self.rawData['modules'] } - self.default_station = list(self.stations.values())[0]['station_name'] - - def modulesNamesList(self, station=None): - res = [m['module_name'] for m in self.modules.values()] - res.append(self.stationByName(station)['module_name']) - return res - - def stationByName(self, station=None): - if not station : station = self.default_station - for i,s in self.stations.items(): - if s['station_name'] == station : return self.stations[i] - return None - - def stationById(self, sid): - return None if sid not in self.stations else self.stations[sid] - - def moduleByName(self, module, station=None): - s = None - if station : - s = self.stationByName(station) - if not s : return None - for m in self.modules: - mod = self.modules[m] - if mod['module_name'] == module : - if not s or mod['main_device'] == s['_id'] : return mod - return None - - def moduleById(self, mid, sid=None): - s = self.stationById(sid) if sid else None - if mid in self.modules : - return self.modules[mid] if not s or self.modules[mid]['main_device'] == s['_id'] else None - - def lastData(self, station=None, exclude=0): - s = self.stationByName(station) - if not s : return None - lastD = dict() - # Define oldest acceptable sensor measure event - limit = (time.time() - exclude) if exclude else 0 - ds = s['dashboard_data'] - if ds['time_utc'] > limit : - lastD[s['module_name']] = ds.copy() - lastD[s['module_name']]['When'] = lastD[s['module_name']].pop("time_utc") - lastD[s['module_name']]['wifi_status'] = s['wifi_status'] - for mId in s["modules"]: - ds = self.modules[mId]['dashboard_data'] - if ds['time_utc'] > limit : - mod = self.modules[mId] - lastD[mod['module_name']] = ds.copy() - lastD[mod['module_name']]['When'] = lastD[mod['module_name']].pop("time_utc") - # For potential use, add battery and radio coverage information to module data if present - for i in ('battery_vp', 'rf_status') : - if i in mod : lastD[mod['module_name']][i] = mod[i] - return lastD - - def checkNotUpdated(self, station=None, delay=3600): - res = self.lastData(station) - ret = [] - for mn,v in res.items(): - if time.time()-v['When'] > delay : ret.append(mn) - return ret if ret else None - - def checkUpdated(self, station=None, delay=3600): - res = self.lastData(station) - ret = [] - for mn,v in res.items(): - if time.time()-v['When'] < delay : ret.append(mn) - return ret if ret else None - - def getMeasure(self, device_id, scale, mtype, module_id=None, date_begin=None, date_end=None, limit=None, optimize=False, real_time=False): - postParams = { "access_token" : self.getAuthToken } - postParams['device_id'] = device_id - if module_id : postParams['module_id'] = module_id - postParams['scale'] = scale - postParams['type'] = mtype - if date_begin : postParams['date_begin'] = date_begin - if date_end : postParams['date_end'] = date_end - if limit : postParams['limit'] = limit - postParams['optimize'] = "true" if optimize else "false" - postParams['real_time'] = "true" if real_time else "false" - return postRequest(_GETMEASURE_REQ, postParams) - - def MinMaxTH(self, station=None, module=None, frame="last24"): - if not station : station = self.default_station - s = self.stationByName(station) - if not s : - s = self.stationById(station) - if not s : return None - if frame == "last24": - end = time.time() - start = end - 24*3600 # 24 hours ago - elif frame == "day": - start, end = todayStamps() - if module and module != s['module_name']: - m = self.moduleByName(module, s['station_name']) - if not m : - m = self.moduleById(s['_id'], module) - if not m : return None - # retrieve module's data - resp = self.getMeasure( - device_id = s['_id'], - module_id = m['_id'], - scale = "max", - mtype = "Temperature,Humidity", - date_begin = start, - date_end = end) - else : # retrieve station's data - resp = self.getMeasure( - device_id = s['_id'], - scale = "max", - mtype = "Temperature,Humidity", - date_begin = start, - date_end = end) - if resp: - 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) - else: - return None - -# Utilities routines - -def postRequest(url, params): - if version_info.major == 3: - req = urllib.request.Request(url) - req.add_header("Content-Type","application/x-www-form-urlencoded;charset=utf-8") - params = urllib.parse.urlencode(params).encode('utf-8') - resp = urllib.request.urlopen(req, params).read().decode("utf-8") - else: - params = urlencode(params) - headers = {"Content-Type" : "application/x-www-form-urlencoded;charset=utf-8"} - req = urllib2.Request(url=url, data=params, headers=headers) - resp = urllib2.urlopen(req).read() - return json.loads(resp) - -def toTimeString(value): - return time.strftime("%Y-%m-%d_%H:%M:%S", time.localtime(int(value))) - -def toEpoch(value): - return int(time.mktime(time.strptime(value,"%Y-%m-%d_%H:%M:%S"))) - -def todayStamps(): - today = time.strftime("%Y-%m-%d") - today = int(time.mktime(time.strptime(today,"%Y-%m-%d"))) - return today, today+3600*24 - -# Global shortcut - -def getStationMinMaxTH(station=None, module=None): - authorization = ClientAuth() - devList = DeviceList(authorization) - if not station : station = devList.default_station - if module : - mname = module - else : - mname = devList.stationByName(station)['module_name'] - lastD = devList.lastData(station) - if mname == "*": - result = dict() - 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]) - else: - if time.time()-lastD[mname]['When'] > 3600 : result = ["-", "-"] - else : result = [lastD[mname]['Temperature'], lastD[mname]['Humidity']] - result.extend(devList.MinMaxTH(station, mname)) - return result - -# 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) - - authorization = ClientAuth() # Test authentication method - user = User(authorization) # Test GETUSER - devList = DeviceList(authorization) # Test DEVICELIST - devList.MinMaxTH() # Test GETMEASURE - - # If we reach this line, all is OK - - # If launched interactively, display OK message - if stdout.isatty(): - print("lnetatmo.py : OK") - - exit(0) From bcb42674fcdd1af216acdab5afc8f2c30aa88b92 Mon Sep 17 00:00:00 2001 From: hydreliox Date: Mon, 11 Jan 2016 06:57:31 +0100 Subject: [PATCH 08/18] Corrections due to pylint tests --- homeassistant/components/sensor/netatmo.py | 38 +++++++++++++--------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index a23ef5fa09a..58e8bd12d38 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -8,7 +8,8 @@ https://home-assistant.io/components/... """ import logging from datetime import timedelta -from homeassistant.const import (CONF_API_KEY, CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS, TEMP_FAHRENHEIT) +from homeassistant.const import (CONF_API_KEY, CONF_USERNAME, CONF_PASSWORD, + TEMP_CELCIUS, TEMP_FAHRENHEIT) from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.util.temperature import celcius_to_fahrenheit @@ -28,7 +29,8 @@ SENSOR_TYPES = { } # Return cached results if last scan was less then this time ago -# NetAtmo Data is uploaded to server every 10mn so this time should not be under +# NetAtmo Data is uploaded to server every 10mn +# so this time should not be under MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=600) @@ -47,8 +49,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): SENSOR_TYPES['temperature'][1] = hass.config.temperature_unit unit = hass.config.temperature_unit - authorization = lnetatmo.ClientAuth(config.get(CONF_API_KEY, None), config.get('secret_key', None), - config.get(CONF_USERNAME, None), config.get(CONF_PASSWORD, None)) + authorization = lnetatmo.ClientAuth(config.get(CONF_API_KEY, None), + config.get('secret_key', None), + config.get(CONF_USERNAME, None), + config.get(CONF_PASSWORD, None)) if not authorization: _LOGGER.error( @@ -60,18 +64,19 @@ def setup_platform(hass, config, add_devices, discovery_info=None): dev = [] try: - """ Iterate each module """ + # Iterate each module for module_name, monitored_conditions in config['modules'].items(): - """ Test if module exist """ - if module_name not in data.get_module_names(): + # Test if module exist """ + if module_name not in data.get_module_names(): _LOGGER.error('Module name: "%s" not found', module_name) continue - """ Only create sensor for monitored """ + # Only create sensor for monitored """ for variable in monitored_conditions: if variable not in SENSOR_TYPES: _LOGGER.error('Sensor type: "%s" does not exist', variable) else: - dev.append(NetAtmoSensor(data, module_name, variable, unit)) + dev.append( + NetAtmoSensor(data, module_name, variable, unit)) except KeyError: pass @@ -79,6 +84,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-few-public-methods +# pylint: disable=too-many-instance-attributes class NetAtmoSensor(Entity): """ Implements a NetAtmo sensor. """ @@ -119,7 +125,8 @@ class NetAtmoSensor(Entity): self._state = round(data['Temperature'], 1) elif self.temp_unit == TEMP_FAHRENHEIT: - converted_temperature = celcius_to_fahrenheit(data['Temperature']) + converted_temperature = celcius_to_fahrenheit( + data['Temperature']) self._state = round(converted_temperature, 1) else: self._state = round(data['Temperature'], 1) @@ -141,14 +148,15 @@ class NetAtmoData(object): self.auth = auth self.data = None - def get_module_names (self) : + def get_module_names(self): + """ Return all module available on the API as a list. """ self.update() return self.data.keys() - @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): + """ Call the NetAtmo API to update the data. """ import lnetatmo - """ Gets the latest data from NetAtmo. """ - devList = lnetatmo.DeviceList(self.auth) - self.data = devList.lastData(exclude=3600) + # Gets the latest data from NetAtmo. """ + dev_list = lnetatmo.DeviceList(self.auth) + self.data = dev_list.lastData(exclude=3600) From f182f7dccd2a97b2c692a9f5e85e510bdb3f85fa Mon Sep 17 00:00:00 2001 From: hydreliox Date: Wed, 13 Jan 2016 03:29:17 +0100 Subject: [PATCH 09/18] Remove uncessary requirement check HA already check the requirement before setup the platform --- homeassistant/components/sensor/netatmo.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index 58e8bd12d38..682ed07d838 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -37,16 +37,6 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=600) def setup_platform(hass, config, add_devices, discovery_info=None): """ Get the NetAtmo sensor. """ - try: - import lnetatmo - - except ImportError: - _LOGGER.exception( - "Unable to import lnetatmo. " - "Did you maybe not install the package ?") - - return False - SENSOR_TYPES['temperature'][1] = hass.config.temperature_unit unit = hass.config.temperature_unit authorization = lnetatmo.ClientAuth(config.get(CONF_API_KEY, None), From 6f1a25d8d6daad160eaff87c332c03bb3e6b493c Mon Sep 17 00:00:00 2001 From: hydreliox Date: Wed, 13 Jan 2016 03:43:10 +0100 Subject: [PATCH 10/18] Add a configuration validation All parameters are required --- homeassistant/components/sensor/netatmo.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index 682ed07d838..9cab253da11 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -11,6 +11,7 @@ from datetime import timedelta from homeassistant.const import (CONF_API_KEY, CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS, TEMP_FAHRENHEIT) from homeassistant.helpers.entity import Entity +from homeassistant.helpers import validate_config from homeassistant.util import Throttle from homeassistant.util.temperature import celcius_to_fahrenheit @@ -37,6 +38,16 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=600) def setup_platform(hass, config, add_devices, discovery_info=None): """ Get the NetAtmo sensor. """ + if not validate_config({DOMAIN: config}, + {DOMAIN: [CONF_API_KEY, + CONF_USERNAME, + CONF_PASSWORD, + 'secret_key']}, + _LOGGER): + return None + + import lnetatmo + SENSOR_TYPES['temperature'][1] = hass.config.temperature_unit unit = hass.config.temperature_unit authorization = lnetatmo.ClientAuth(config.get(CONF_API_KEY, None), From 03febb81d34313e9eeaf891f672f53ef8ef55998 Mon Sep 17 00:00:00 2001 From: hydreliox Date: Wed, 13 Jan 2016 03:45:43 +0100 Subject: [PATCH 11/18] Extract 'secret_key' as a constant --- homeassistant/components/sensor/netatmo.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index 9cab253da11..89cea9826b7 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -21,6 +21,7 @@ REQUIREMENTS = [ '#lnetatmo==0.3.0.dev1'] _LOGGER = logging.getLogger(__name__) + SENSOR_TYPES = { 'temperature': ['Temperature', ''], 'co2': ['CO2', 'ppm'], @@ -29,6 +30,8 @@ SENSOR_TYPES = { 'humidity': ['Humidity', '%'] } +CONF_SECRET_KEY = 'secret_key' + # Return cached results if last scan was less then this time ago # NetAtmo Data is uploaded to server every 10mn # so this time should not be under @@ -42,16 +45,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None): {DOMAIN: [CONF_API_KEY, CONF_USERNAME, CONF_PASSWORD, - 'secret_key']}, + CONF_SECRET_KEY]}, _LOGGER): return None - + import lnetatmo SENSOR_TYPES['temperature'][1] = hass.config.temperature_unit unit = hass.config.temperature_unit authorization = lnetatmo.ClientAuth(config.get(CONF_API_KEY, None), - config.get('secret_key', None), + config.get(CONF_SECRET_KEY, None), config.get(CONF_USERNAME, None), config.get(CONF_PASSWORD, None)) From 0c2fe4c5f3b3a9343f2c12dd1278f90c2ffdf5ca Mon Sep 17 00:00:00 2001 From: hydreliox Date: Wed, 13 Jan 2016 04:16:25 +0100 Subject: [PATCH 12/18] Remove temp unit logic HA should convert unit automatically --- homeassistant/components/sensor/netatmo.py | 35 +++++++--------------- 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index 89cea9826b7..ef945847e83 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -8,12 +8,12 @@ https://home-assistant.io/components/... """ import logging from datetime import timedelta +from homeassistant.components.sensor import DOMAIN from homeassistant.const import (CONF_API_KEY, CONF_USERNAME, CONF_PASSWORD, - TEMP_CELCIUS, TEMP_FAHRENHEIT) + TEMP_CELCIUS) from homeassistant.helpers.entity import Entity from homeassistant.helpers import validate_config from homeassistant.util import Throttle -from homeassistant.util.temperature import celcius_to_fahrenheit REQUIREMENTS = [ 'https://github.com/HydrelioxGitHub/netatmo-api-python/archive/' @@ -23,11 +23,11 @@ REQUIREMENTS = [ _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = { - 'temperature': ['Temperature', ''], - 'co2': ['CO2', 'ppm'], - 'pressure': ['Pressure', 'mb'], - 'noise': ['Noise', 'dB'], - 'humidity': ['Humidity', '%'] + 'temperature': ['Temperature', TEMP_CELCIUS], + 'co2' : ['CO2', 'ppm'], + 'pressure' : ['Pressure', 'mb'], + 'noise' : ['Noise', 'dB'], + 'humidity' : ['Humidity', '%'] } CONF_SECRET_KEY = 'secret_key' @@ -51,8 +51,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): import lnetatmo - SENSOR_TYPES['temperature'][1] = hass.config.temperature_unit - unit = hass.config.temperature_unit authorization = lnetatmo.ClientAuth(config.get(CONF_API_KEY, None), config.get(CONF_SECRET_KEY, None), config.get(CONF_USERNAME, None), @@ -80,7 +78,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error('Sensor type: "%s" does not exist', variable) else: dev.append( - NetAtmoSensor(data, module_name, variable, unit)) + NetAtmoSensor(data, module_name, variable)) except KeyError: pass @@ -88,16 +86,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # pylint: disable=too-few-public-methods -# pylint: disable=too-many-instance-attributes class NetAtmoSensor(Entity): """ Implements a NetAtmo sensor. """ - def __init__(self, netatmo_data, module_name, sensor_type, temp_unit): + def __init__(self, netatmo_data, module_name, sensor_type): self.client_name = 'NetAtmo' self._name = module_name + '_' + SENSOR_TYPES[sensor_type][0] self.netatmo_data = netatmo_data self.module_name = module_name - self.temp_unit = temp_unit self.type = sensor_type self._state = None self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] @@ -125,15 +121,7 @@ class NetAtmoSensor(Entity): data = self.netatmo_data.data[self.module_name] if self.type == 'temperature': - if self.temp_unit == TEMP_CELCIUS: - self._state = round(data['Temperature'], - 1) - elif self.temp_unit == TEMP_FAHRENHEIT: - converted_temperature = celcius_to_fahrenheit( - data['Temperature']) - self._state = round(converted_temperature, 1) - else: - self._state = round(data['Temperature'], 1) + self._state = round(data['Temperature'], 1) elif self.type == 'humidity': self._state = data['Humidity'] elif self.type == 'noise': @@ -141,8 +129,7 @@ class NetAtmoSensor(Entity): elif self.type == 'co2': self._state = data['CO2'] elif self.type == 'pressure': - self._state = round(data['Pressure'], - 1) + self._state = round(data['Pressure'], 1) class NetAtmoData(object): From dc31ddbef2e22c9b84bf4914d1b86f79ba2892e3 Mon Sep 17 00:00:00 2001 From: hydreliox Date: Wed, 13 Jan 2016 04:19:27 +0100 Subject: [PATCH 13/18] Extract 'modules' as a constant --- homeassistant/components/sensor/netatmo.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index ef945847e83..73befe83e28 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -31,6 +31,7 @@ SENSOR_TYPES = { } CONF_SECRET_KEY = 'secret_key' +ATTR_MODULE = 'modules' # Return cached results if last scan was less then this time ago # NetAtmo Data is uploaded to server every 10mn @@ -67,7 +68,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): dev = [] try: # Iterate each module - for module_name, monitored_conditions in config['modules'].items(): + for module_name, monitored_conditions in config[ATTR_MODULE].items(): # Test if module exist """ if module_name not in data.get_module_names(): _LOGGER.error('Module name: "%s" not found', module_name) From a4481efe079e6d4c9649a90f15eab68bd8ec8fd0 Mon Sep 17 00:00:00 2001 From: hydreliox Date: Wed, 13 Jan 2016 04:26:40 +0100 Subject: [PATCH 14/18] Code cleanup --- homeassistant/components/sensor/netatmo.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index 73befe83e28..01d837c737d 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -24,10 +24,10 @@ _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = { 'temperature': ['Temperature', TEMP_CELCIUS], - 'co2' : ['CO2', 'ppm'], - 'pressure' : ['Pressure', 'mb'], - 'noise' : ['Noise', 'dB'], - 'humidity' : ['Humidity', '%'] + 'co2': ['CO2', 'ppm'], + 'pressure': ['Pressure', 'mb'], + 'noise': ['Noise', 'dB'], + 'humidity': ['Humidity', '%'] } CONF_SECRET_KEY = 'secret_key' From 058dba50cc51ce2b77621d0a22158e7257aaaca2 Mon Sep 17 00:00:00 2001 From: hydreliox Date: Wed, 13 Jan 2016 08:46:45 +0100 Subject: [PATCH 15/18] Correct name using format instead of concatenation --- homeassistant/components/sensor/netatmo.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index 01d837c737d..6c29c41f3f5 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -91,8 +91,8 @@ class NetAtmoSensor(Entity): """ Implements a NetAtmo sensor. """ def __init__(self, netatmo_data, module_name, sensor_type): - self.client_name = 'NetAtmo' - self._name = module_name + '_' + SENSOR_TYPES[sensor_type][0] + self._name = "NetAtmo {} {}".format(module_name, + SENSOR_TYPES[sensor_type][0]) self.netatmo_data = netatmo_data self.module_name = module_name self.type = sensor_type @@ -102,7 +102,7 @@ class NetAtmoSensor(Entity): @property def name(self): - return '{} {}'.format(self.client_name, self._name) + return self._name @property def state(self): From 58cee75c0ebd0b3adceb897daae4a887fe5712aa Mon Sep 17 00:00:00 2001 From: hydreliox Date: Wed, 13 Jan 2016 09:06:16 +0100 Subject: [PATCH 16/18] coverage and requirements updated --- .coveragerc | 1 + requirements_all.txt | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.coveragerc b/.coveragerc index 272ace975c4..674516aaeba 100644 --- a/.coveragerc +++ b/.coveragerc @@ -96,6 +96,7 @@ omit = homeassistant/components/sensor/eliqonline.py homeassistant/components/sensor/forecast.py homeassistant/components/sensor/glances.py + homeassistant/components/sensor/netatmo.py homeassistant/components/sensor/openweathermap.py homeassistant/components/sensor/rest.py homeassistant/components/sensor/rpi_gpio.py diff --git a/requirements_all.txt b/requirements_all.txt index 1bc00b4c46d..289c1095258 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -137,6 +137,9 @@ eliqonline==1.0.11 # homeassistant.components.sensor.forecast python-forecastio==1.3.3 +# homeassistant.components.sensor.netatmo +https://github.com/HydrelioxGitHub/netatmo-api-python/archive/59d157d03db0aa167730044667591adea4457ca8.zip#lnetatmo==0.3.0.dev1 + # homeassistant.components.sensor.openweathermap pyowm==2.3.0 From 314d34a644317dd55175e1ed697f30c9d5d84f98 Mon Sep 17 00:00:00 2001 From: hydreliox Date: Thu, 14 Jan 2016 03:00:51 +0100 Subject: [PATCH 17/18] Update library lnetatmo requirements Thanks to @rmkraus --- homeassistant/components/sensor/netatmo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index 6c29c41f3f5..640d3519bdf 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -17,8 +17,8 @@ from homeassistant.util import Throttle REQUIREMENTS = [ 'https://github.com/HydrelioxGitHub/netatmo-api-python/archive/' - '59d157d03db0aa167730044667591adea4457ca8.zip' - '#lnetatmo==0.3.0.dev1'] + '43ff238a0122b0939a0dc4e8836b6782913fb6e2.zip' + '#lnetatmo==0.4.0'] _LOGGER = logging.getLogger(__name__) From 4dd558a42084eb1316c90b308438743905a2db55 Mon Sep 17 00:00:00 2001 From: hydreliox Date: Thu, 14 Jan 2016 07:09:25 +0100 Subject: [PATCH 18/18] Update Requirements --- requirements_all.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_all.txt b/requirements_all.txt index 289c1095258..7b43b7a3f1c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -138,7 +138,7 @@ eliqonline==1.0.11 python-forecastio==1.3.3 # homeassistant.components.sensor.netatmo -https://github.com/HydrelioxGitHub/netatmo-api-python/archive/59d157d03db0aa167730044667591adea4457ca8.zip#lnetatmo==0.3.0.dev1 +https://github.com/HydrelioxGitHub/netatmo-api-python/archive/43ff238a0122b0939a0dc4e8836b6782913fb6e2.zip#lnetatmo==0.4.0 # homeassistant.components.sensor.openweathermap pyowm==2.3.0