Add additional sensors for Arlo Baby camera (#15074)
* Add additional sensors for Arlo Baby camera * Fix linter errors * Fix linter error * Add tests for Arlo sensors * Fix linter errors * Bump pyarlo dependency to 0.1.9 * Remove unnecessary AttributeError except * Fix module reference error in py35 * Fix test * Address PR review concerns * Convert to standalone pytest methods * Fix linter errors * Fix linter errors * Fix linter errors * Fix test * Remove redundant check, fix async test * Fix linter error * Added check for total_cameras sensor, added additional attribute tests * Add missing docstring
This commit is contained in:
parent
f65c3940ae
commit
0f1bcfd63b
4 changed files with 294 additions and 9 deletions
|
@ -16,7 +16,7 @@ from homeassistant.const import (
|
|||
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__)
|
||||
|
||||
|
|
|
@ -13,7 +13,10 @@ import homeassistant.helpers.config_validation as cv
|
|||
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
|
||||
|
@ -28,7 +31,10 @@ SENSOR_TYPES = {
|
|||
'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({
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -101,6 +122,15 @@ class ArloSensor(Entity):
|
|||
"""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)
|
||||
|
@ -133,6 +163,24 @@ class ArloSensor(Entity):
|
|||
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."""
|
||||
|
@ -141,10 +189,7 @@ class ArloSensor(Entity):
|
|||
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':
|
||||
if self._sensor_type != 'total_cameras':
|
||||
attrs['model'] = self._data.model_id
|
||||
|
||||
return attrs
|
||||
|
|
|
@ -730,7 +730,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
|
||||
|
|
240
tests/components/sensor/test_arlo.py
Normal file
240
tests/components/sensor/test_arlo.py
Normal file
|
@ -0,0 +1,240 @@
|
|||
"""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):
|
||||
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()
|
||||
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."""
|
||||
return _get_sensor('Humidity', 'humidity')
|
||||
|
||||
|
||||
@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():
|
||||
"""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()
|
||||
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
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def mock_dispatch():
|
||||
"""Mock the dispatcher connect method."""
|
||||
target = 'homeassistant.components.sensor.arlo.async_dispatcher_connect'
|
||||
with patch(target, MagicMock()) as _mock:
|
||||
yield _mock
|
||||
|
||||
|
||||
def test_setup_with_no_data(platform_setup, hass):
|
||||
"""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):
|
||||
"""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):
|
||||
"""Test the name property."""
|
||||
assert default_sensor.name == 'Last'
|
||||
|
||||
|
||||
async def test_async_added_to_hass(sensor_with_hass_data, mock_dispatch):
|
||||
"""Test dispatcher called when added."""
|
||||
await sensor_with_hass_data.async_added_to_hass()
|
||||
assert len(mock_dispatch.mock_calls) == 1
|
||||
kall = mock_dispatch.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):
|
||||
"""Test the state property."""
|
||||
assert default_sensor.state is None
|
||||
|
||||
|
||||
def test_sensor_icon_battery(battery_sensor):
|
||||
"""Test the battery icon."""
|
||||
assert battery_sensor.icon == 'mdi:battery-50'
|
||||
|
||||
|
||||
def test_sensor_icon(temperature_sensor):
|
||||
"""Test the icon property."""
|
||||
assert temperature_sensor.icon == 'mdi:thermometer'
|
||||
|
||||
|
||||
def test_unit_of_measure(default_sensor, battery_sensor):
|
||||
"""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):
|
||||
"""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):
|
||||
"""Test update method for total_cameras sensor type."""
|
||||
cameras_sensor.update()
|
||||
assert cameras_sensor.state == 2
|
||||
|
||||
|
||||
def test_update_captured_today(captured_sensor):
|
||||
"""Test update method for captured_today sensor type."""
|
||||
captured_sensor.update()
|
||||
assert captured_sensor.state == 5
|
||||
|
||||
|
||||
def _test_attributes(sensor_type):
|
||||
data = _get_named_tuple({
|
||||
'model_id': 'TEST123'
|
||||
})
|
||||
sensor = _get_sensor('test', sensor_type, data)
|
||||
attrs = 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') == 'TEST123'
|
||||
|
||||
|
||||
def test_state_attributes():
|
||||
"""Test attributes for camera sensor types."""
|
||||
_test_attributes('battery_level')
|
||||
_test_attributes('signal_strength')
|
||||
_test_attributes('temperature')
|
||||
_test_attributes('humidity')
|
||||
_test_attributes('air_quality')
|
||||
|
||||
|
||||
def test_attributes_total_cameras(cameras_sensor):
|
||||
"""Test attributes for total cameras sensor type."""
|
||||
attrs = cameras_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') is None
|
||||
|
||||
|
||||
def _test_update(sensor_type, key, value):
|
||||
data = _get_named_tuple({
|
||||
key: value
|
||||
})
|
||||
sensor = _get_sensor('test', sensor_type, data)
|
||||
sensor.update()
|
||||
assert sensor.state == value
|
||||
|
||||
|
||||
def test_update():
|
||||
"""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)
|
Loading…
Add table
Reference in a new issue