Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion homeassistant/components/netatmo.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle

REQUIREMENTS = ['pyatmo==1.1.1']
REQUIREMENTS = ['pyatmo==1.2']

_LOGGER = logging.getLogger(__name__)

Expand Down
114 changes: 79 additions & 35 deletions homeassistant/components/sensor/netatmo_public.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (CONF_NAME, CONF_TYPE)
from homeassistant.const import (
CONF_NAME, CONF_MODE, CONF_MONITORED_CONDITIONS, TEMP_CELSIUS,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY, STATE_UNKNOWN)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
Expand All @@ -26,23 +28,32 @@
CONF_LON_SW = 'lon_sw'

DEFAULT_NAME = 'Netatmo Public Data'
DEFAULT_TYPE = 'max'
SENSOR_TYPES = {'max', 'avg'}
DEFAULT_MODE = 'avg'
MODE_TYPES = {'max', 'avg'}

SENSOR_TYPES = {
'temperature': ['Temperature', TEMP_CELSIUS, 'mdi:thermometer',
DEVICE_CLASS_TEMPERATURE],
'pressure': ['Pressure', 'mbar', 'mdi:gauge', None],
'humidity': ['Humidity', '%', 'mdi:water-percent', DEVICE_CLASS_HUMIDITY],
'rain': ['Rain', 'mm', 'mdi:weather-rainy', None],
'windstrength': ['Wind Strength', 'km/h', 'mdi:weather-windy', None],
'guststrength': ['Gust Strength', 'km/h', 'mdi:weather-windy', None],
}

# NetAtmo Data is uploaded to server every 10 minutes
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=600)


PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_AREAS): vol.All(cv.ensure_list, [
{
vol.Required(CONF_LAT_NE): cv.latitude,
vol.Required(CONF_LAT_SW): cv.latitude,
vol.Required(CONF_LON_NE): cv.longitude,
vol.Required(CONF_LON_SW): cv.longitude,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_TYPE, default=DEFAULT_TYPE):
vol.In(SENSOR_TYPES)
vol.Required(CONF_MONITORED_CONDITIONS): [vol.In(SENSOR_TYPES)],
vol.Optional(CONF_MODE, default=DEFAULT_MODE): vol.In(MODE_TYPES),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
}
]),
})
Expand All @@ -59,20 +70,29 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
lat_ne=area_conf.get(CONF_LAT_NE),
lon_ne=area_conf.get(CONF_LON_NE),
lat_sw=area_conf.get(CONF_LAT_SW),
lon_sw=area_conf.get(CONF_LON_SW),
calculation=area_conf.get(CONF_TYPE))
sensors.append(NetatmoPublicSensor(area_conf.get(CONF_NAME), data))
add_entities(sensors)
lon_sw=area_conf.get(CONF_LON_SW))
for sensor_type in area_conf.get(CONF_MONITORED_CONDITIONS):
sensors.append(NetatmoPublicSensor(area_conf.get(CONF_NAME),
data, sensor_type,
area_conf.get(CONF_MODE)))
add_entities(sensors, True)


class NetatmoPublicSensor(Entity):
"""Represent a single sensor in a Netatmo."""

def __init__(self, name, data):
def __init__(self, area_name, data, sensor_type, mode):
"""Initialize the sensor."""
self.netatmo_data = data
self._name = name
self.type = sensor_type
self._mode = mode
self._name = '{} {}'.format(area_name,
SENSOR_TYPES[self.type][0])
self._area_name = area_name
self._state = None
self._device_class = SENSOR_TYPES[self.type][3]
self._icon = SENSOR_TYPES[self.type][2]
self._unit_of_measurement = SENSOR_TYPES[self.type][1]

@property
def name(self):
Expand All @@ -82,60 +102,84 @@ def name(self):
@property
def icon(self):
"""Icon to use in the frontend."""
return 'mdi:weather-rainy'
return self._icon

@property
def device_class(self):
"""Return the device class of the sensor."""
return None
return self._device_class

@property
def state(self):
"""Return true if binary sensor is on."""
"""Return the state of the device."""
return self._state

@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity."""
return 'mm'
return self._unit_of_measurement

def update(self):
"""Get the latest data from NetAtmo API and updates the states."""
self.netatmo_data.update()
self._state = self.netatmo_data.data

if self.netatmo_data.data is None:
_LOGGER.warning("No data found for %s", self._name)
self._state = STATE_UNKNOWN
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use None to represent unknown state.

return

data = None

if self.type == 'temperature':
data = self.netatmo_data.data.getLatestTemperatures()
elif self.type == 'pressure':
data = self.netatmo_data.data.getLatestPressures()
elif self.type == 'humidity':
data = self.netatmo_data.data.getLatestHumidities()
elif self.type == 'rain':
data = self.netatmo_data.data.getLatestRain()
elif self.type == 'windstrength':
data = self.netatmo_data.data.getLatestWindStrengths()
elif self.type == 'guststrength':
data = self.netatmo_data.data.getLatestGustStrengths()

if not data:
_LOGGER.warning("No station provides %s data in the area %s",
self.type, self._area_name)
self._state = STATE_UNKNOWN
return

if self._mode == 'avg':
self._state = round(sum(data.values()) / len(data), 1)
elif self._mode == 'max':
self._state = max(data.values())


class NetatmoPublicData:
"""Get the latest data from NetAtmo."""

def __init__(self, auth, lat_ne, lon_ne, lat_sw, lon_sw, calculation):
def __init__(self, auth, lat_ne, lon_ne, lat_sw, lon_sw):
"""Initialize the data object."""
self.auth = auth
self.data = None
self.lat_ne = lat_ne
self.lon_ne = lon_ne
self.lat_sw = lat_sw
self.lon_sw = lon_sw
self.calculation = calculation

@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Request an update from the Netatmo API."""
import pyatmo
raindata = pyatmo.PublicData(self.auth,
LAT_NE=self.lat_ne,
LON_NE=self.lon_ne,
LAT_SW=self.lat_sw,
LON_SW=self.lon_sw,
required_data_type="rain")

if raindata.CountStationInArea() == 0:
_LOGGER.warning('No Rain Station available in this area.')
data = pyatmo.PublicData(self.auth,
LAT_NE=self.lat_ne,
LON_NE=self.lon_ne,
LAT_SW=self.lat_sw,
LON_SW=self.lon_sw,
filtering=True)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is filtering?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filtering makes the API response exclude abnormal sensor readings. For example if someone installs an outside sensor indoors.


if data.CountStationInArea() == 0:
_LOGGER.warning('No Stations available in this area.')
return

raindata_live = raindata.getLive()

if self.calculation == 'avg':
self.data = sum(raindata_live.values()) / len(raindata_live)
else:
self.data = max(raindata_live.values())
self.data = data
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,7 @@ pyasn1-modules==0.1.5
pyasn1==0.3.7

# homeassistant.components.netatmo
pyatmo==1.1.1
pyatmo==1.2

# homeassistant.components.apple_tv
pyatv==0.3.10
Expand Down