Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 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/arlo.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.dispatcher import dispatcher_send

REQUIREMENTS = ['pyarlo==0.1.8']
REQUIREMENTS = ['pyarlo==0.1.9']

_LOGGER = logging.getLogger(__name__)

Expand Down
61 changes: 52 additions & 9 deletions homeassistant/components/sensor/arlo.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
from homeassistant.components.arlo import (
CONF_ATTRIBUTION, DEFAULT_BRAND, DATA_ARLO, SIGNAL_UPDATE_ARLO)
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS)
from homeassistant.const import (
ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS, TEMP_CELSIUS,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY)

from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.icon import icon_for_battery_level
Expand All @@ -28,7 +31,10 @@
'total_cameras': ['Arlo Cameras', None, 'video'],
'captured_today': ['Captured Today', None, 'file-video'],
'battery_level': ['Battery Level', '%', 'battery-50'],
'signal_strength': ['Signal Strength', None, 'signal']
'signal_strength': ['Signal Strength', None, 'signal'],
'temperature': ['Temperature', TEMP_CELSIUS, 'thermometer'],
'humidity': ['Humidity', '%', 'water-percent'],
'air_quality': ['Air Quality', 'ppm', 'biohazard']
}

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
Expand All @@ -41,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up an Arlo IP sensor."""
arlo = hass.data.get(DATA_ARLO)
if not arlo:
return False
return

sensors = []
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
Expand All @@ -50,10 +56,24 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
SENSOR_TYPES[sensor_type][0], arlo, sensor_type))
else:
for camera in arlo.cameras:
if sensor_type == 'temperature' or \
sensor_type == 'humidity' or \
sensor_type == 'air_quality':
continue

name = '{0} {1}'.format(
SENSOR_TYPES[sensor_type][0], camera.name)
sensors.append(ArloSensor(name, camera, sensor_type))

for base_station in arlo.base_stations:
if ((sensor_type == 'temperature' or
sensor_type == 'humidity' or
sensor_type == 'air_quality') and
base_station.model_id == 'ABC1000'):
name = '{0} {1}'.format(
SENSOR_TYPES[sensor_type][0], base_station.name)
sensors.append(ArloSensor(name, base_station, sensor_type))

add_devices(sensors, True)


Expand All @@ -62,6 +82,7 @@ class ArloSensor(Entity):

def __init__(self, name, device, sensor_type):
"""Initialize an Arlo sensor."""
_LOGGER.debug('ArloSensor created for %s', name)
self._name = name
self._data = device
self._sensor_type = sensor_type
Expand Down Expand Up @@ -101,6 +122,15 @@ def unit_of_measurement(self):
"""Return the units of measurement."""
return SENSOR_TYPES.get(self._sensor_type)[1]

@property
def device_class(self):
"""Return the device class of the sensor."""
if self._sensor_type == 'temperature':
return DEVICE_CLASS_TEMPERATURE
elif self._sensor_type == 'humidity':
return DEVICE_CLASS_HUMIDITY
return None

def update(self):
"""Get the latest data and updates the state."""
_LOGGER.debug("Updating Arlo sensor %s", self.name)
Expand Down Expand Up @@ -133,18 +163,31 @@ def update(self):
except TypeError:
self._state = None

elif self._sensor_type == 'temperature':
try:
self._state = self._data.ambient_temperature
except TypeError:
self._state = None

elif self._sensor_type == 'humidity':
try:
self._state = self._data.ambient_humidity
except TypeError:
self._state = None

elif self._sensor_type == 'air_quality':
try:
self._state = self._data.ambient_air_quality
except TypeError:
self._state = None

@property
def device_state_attributes(self):
"""Return the device state attributes."""
attrs = {}

attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION
attrs['brand'] = DEFAULT_BRAND

if self._sensor_type == 'last_capture' or \
self._sensor_type == 'captured_today' or \
self._sensor_type == 'battery_level' or \
self._sensor_type == 'signal_strength':
attrs['model'] = self._data.model_id
attrs['model'] = self._data.model_id

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.

Add a test or update the test where all sensor types are set up, and test that attributes return ok also for the total cameras type. I think we need to add a check here where we don't add the model attribute for that sensor type.


return attrs
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,7 @@ pyairvisual==2.0.1
pyalarmdotcom==0.3.2

# homeassistant.components.arlo
pyarlo==0.1.8
pyarlo==0.1.9

# homeassistant.components.notify.xmpp
pyasn1-modules==0.1.5
Expand Down
218 changes: 218 additions & 0 deletions tests/components/sensor/test_arlo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
"""The tests for the Netgear Arlo sensors."""
from collections import namedtuple
from unittest.mock import patch, MagicMock
import pytest
from homeassistant.const import (
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY, ATTR_ATTRIBUTION)
from homeassistant.components.sensor import arlo
from homeassistant.components.arlo import DATA_ARLO


def _get_named_tuple(input_dict):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

return namedtuple('Struct', input_dict.keys())(*input_dict.values())


def _get_sensor(name='Last', sensor_type='last_capture', data=None):
if data is None:
data = {}
return arlo.ArloSensor(name, data, sensor_type)


@pytest.fixture()
def default_sensor():
"""Create an ArloSensor with default values."""
return _get_sensor()


@pytest.fixture()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

def battery_sensor():
"""Create an ArloSensor with battery data."""
data = _get_named_tuple({
'battery_level': 50
})
return _get_sensor('Battery Level', 'battery_level', data)


@pytest.fixture()
def temperature_sensor():
"""Create a temperature ArloSensor."""
return _get_sensor('Temperature', 'temperature')


@pytest.fixture()
def humidity_sensor():
"""Create a humidity ArloSensor."""
data = _get_named_tuple({
'model_id': 'ABC1000'
})
return _get_sensor('Humidity', 'humidity', data)


@pytest.fixture()
def cameras_sensor():
"""Create a total cameras ArloSensor."""
data = _get_named_tuple({
'cameras': [0, 0]
})
return _get_sensor('Arlo Cameras', 'total_cameras', data)


@pytest.fixture()
def captured_sensor():
"""Create a captured today ArloSensor."""
data = _get_named_tuple({
'captured_today': [0, 0, 0, 0, 0]
})
return _get_sensor('Captured Today', 'captured_today', data)


class PlatformSetupFixture():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

"""Fixture for testing platform setup call to add_devices()."""

def __init__(self):
"""Instantiate the platform setup fixture."""
self.sensors = None
self.update = False

def add_devices(self, sensors, update):
"""Mock method for adding devices."""
self.sensors = sensors
self.update = update


@pytest.fixture()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

def platform_setup():
"""Create an instance of the PlatformSetupFixture class."""
return PlatformSetupFixture()


@pytest.fixture()
def sensor_with_hass_data(default_sensor, hass):
"""Create a sensor with async_dispatcher_connected mocked."""
hass.data = {}
default_sensor.hass = hass
return default_sensor


def test_setup_with_no_data(platform_setup, hass):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

"""Test setup_platform with no data."""
arlo.setup_platform(hass, None, platform_setup.add_devices)
assert platform_setup.sensors is None
assert not platform_setup.update


def test_setup_with_valid_data(platform_setup, hass):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

"""Test setup_platform with valid data."""
config = {
'monitored_conditions': [
'last_capture',
'total_cameras',
'captured_today',
'battery_level',
'signal_strength',
'temperature',
'humidity',
'air_quality'
]
}

hass.data[DATA_ARLO] = _get_named_tuple({
'cameras': [_get_named_tuple({
'name': 'Camera',
'model_id': 'ABC1000'
})],
'base_stations': [_get_named_tuple({
'name': 'Base Station',
'model_id': 'ABC1000'
})]
})

arlo.setup_platform(hass, config, platform_setup.add_devices)
assert len(platform_setup.sensors) == 8
assert platform_setup.update


def test_sensor_name(default_sensor):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

"""Test the name property."""
assert default_sensor.name == 'Last'


async def test_async_added_to_hass(sensor_with_hass_data):
"""Test dispatcher called when added."""
with patch(
'homeassistant.components.sensor.arlo.async_dispatcher_connect',
MagicMock()) as mock:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

visually indented line with same indent as next logical line

await sensor_with_hass_data.async_added_to_hass()
assert len(mock.mock_calls) == 1
kall = mock.call_args
args, kwargs = kall
assert len(args) == 3
assert args[0] == sensor_with_hass_data.hass
assert args[1] == 'arlo_update'
assert not kwargs


def test_sensor_state_default(default_sensor):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

"""Test the state property."""
assert default_sensor.state is None


def test_sensor_icon_battery(battery_sensor):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

"""Test the battery icon."""
assert battery_sensor.icon == 'mdi:battery-50'


def test_sensor_icon(temperature_sensor):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

"""Test the icon property."""
assert temperature_sensor.icon == 'mdi:thermometer'


def test_unit_of_measure(default_sensor, battery_sensor):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

"""Test the unit_of_measurement property."""
assert default_sensor.unit_of_measurement is None
assert battery_sensor.unit_of_measurement == '%'


def test_device_class(default_sensor, temperature_sensor, humidity_sensor):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

"""Test the device_class property."""
assert default_sensor.device_class is None
assert temperature_sensor.device_class == DEVICE_CLASS_TEMPERATURE
assert humidity_sensor.device_class == DEVICE_CLASS_HUMIDITY


def test_update_total_cameras(cameras_sensor):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

"""Test update method for total_cameras sensor type."""
cameras_sensor.update()
assert cameras_sensor.state == 2


def test_update_captured_today(captured_sensor):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

"""Test update method for captured_today sensor type."""
captured_sensor.update()
assert captured_sensor.state == 5


def test_attributes_known_sensor(humidity_sensor):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

"""Test attributes for known sensor type."""
attrs = humidity_sensor.device_state_attributes
assert attrs.get(ATTR_ATTRIBUTION) == 'Data provided by arlo.netgear.com'
assert attrs.get('brand') == 'Netgear Arlo'
assert attrs.get('model') == 'ABC1000'


def _test_update(sensor_type, key, value):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

data = _get_named_tuple({
key: value
})
sensor = _get_sensor('test', sensor_type, data)
sensor.update()
assert sensor.state == value


def test_update():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

"""Test update method for direct transcription sensor types."""
_test_update('battery_level', 'battery_level', 100)
_test_update('signal_strength', 'signal_strength', 100)
_test_update('temperature', 'ambient_temperature', 21.4)
_test_update('humidity', 'ambient_humidity', 45.1)
_test_update('air_quality', 'ambient_air_quality', 14.2)