Add additional sensors for Arlo Baby camera#15074
Add additional sensors for Arlo Baby camera#15074MartinHjelmare merged 20 commits intohome-assistant:devfrom
Conversation
| for base_station in arlo.base_stations: | ||
| if ((sensor_type == 'temperature' or \ | ||
| sensor_type == 'humidity' or \ | ||
| sensor_type == 'air_quality') and \ |
There was a problem hiding this comment.
the backslash is redundant between brackets
|
|
||
| for base_station in arlo.base_stations: | ||
| if ((sensor_type == 'temperature' or \ | ||
| sensor_type == 'humidity' or \ |
There was a problem hiding this comment.
the backslash is redundant between brackets
| sensors.append(ArloSensor(name, camera, sensor_type)) | ||
|
|
||
| for base_station in arlo.base_stations: | ||
| if ((sensor_type == 'temperature' or \ |
There was a problem hiding this comment.
the backslash is redundant between brackets
| sensor_type == 'humidity' or \ | ||
| sensor_type == 'air_quality': | ||
| continue | ||
|
|
MartinHjelmare
left a comment
There was a problem hiding this comment.
@tchellomello could you have a look and review?
| try: | ||
| self._state = self._data.battery_level | ||
| except TypeError: | ||
| except (AttributeError, TypeError): |
There was a problem hiding this comment.
Why would there be an AttributeError?
There was a problem hiding this comment.
Yeah I don't think the attribute error should be necessary since all cameras have the battery_level
|
It looks good to me. I don't have a baby monitor but code looks good! |
|
This PR may have been slightly premature. I’ve since discovered an issue with stats not updating. I’m working on resolving this before removing the WIP designation. If it would be preferable to close this PR and reopen one once this issue is resolved, feel free to close this. |
There was a problem hiding this comment.
@lukiffer could you bump the Pyarlo module to pyarlo==0.1.9 .
https://pypi.org/project/pyarlo/0.1.9/
Thanks!
| }) | ||
| sensor = TestArloSensor._get_mock_sensor('test', 'humidity', data) | ||
| attrs = sensor.device_state_attributes | ||
| self.assertEqual(attrs.get(ATTR_ATTRIBUTION), 'Data provided by arlo.netgear.com') |
| data = TestArloSensor._get_named_tuple({ | ||
| 'captured_today': [0, 0, 0, 0, 0] | ||
| }) | ||
| sensor = TestArloSensor._get_mock_sensor('Captured Today', 'captured_today', data) |
| data = TestArloSensor._get_named_tuple({ | ||
| 'cameras': [0, 0] | ||
| }) | ||
| sensor = TestArloSensor._get_mock_sensor('Arlo Cameras', 'total_cameras', data) |
| """Test the unit_of_measurement property.""" | ||
| sensor = TestArloSensor._get_mock_sensor() | ||
| self.assertIsNone(sensor.unit_of_measurement) | ||
| sensor = TestArloSensor._get_mock_sensor('Battery Level', 'battery_level') |
| 'battery_level': 50 | ||
| }) | ||
|
|
||
| sensor = TestArloSensor._get_mock_sensor('Battery Level', 'battery_level', data) |
| self.assertEqual(sensor.name, 'Last') | ||
|
|
||
| @asyncio.coroutine | ||
| @patch('homeassistant.helpers.dispatcher.async_dispatcher_connect', MagicMock()) |
| TEST_USERNAME = 'test@domain.com' | ||
| TEST_PASSWORD = 'password' | ||
|
|
||
| class TestArloSensor(unittest.TestCase): |
|
@tchellomello @MartinHjelmare above comments addressed and ready for review. |
| @callback | ||
| def _update_callback(self): | ||
| """Call update method.""" | ||
| _LOGGER.debug('update') |
There was a problem hiding this comment.
Remove this. The state update will be logged by the core at info level.
| self._sensor_type == 'captured_today' or \ | ||
| self._sensor_type == 'battery_level' or \ | ||
| self._sensor_type == 'signal_strength': | ||
| known_sensor_types = [ |
There was a problem hiding this comment.
This could be moved to a constant at module level.
There was a problem hiding this comment.
This was a lazy workaround for address a maximum conditions in an if statement on my part. There's a dictionary with keys of known types already at the module level, I'll use that.
| elif self._sensor_type == 'temperature': | ||
| try: | ||
| self._state = self._data.ambient_temperature | ||
| except (AttributeError, TypeError): |
There was a problem hiding this comment.
Why would there be these errors? The library shouldn't let a type error bubble up from a simple attribute access. All attributes should be initialized after init so attribute error shouldn't be possible either. If it is, the library should be updated to handle that better.
There was a problem hiding this comment.
I had these in there because the library was in some cases bubbling up an AttributeError but in further testing, it appears to be the result of an issue that was fixed in pyarlo==0.1.9 where the method to get the ambient sensor data was not being called on update. I've removed them and will push.
| sensor.update() | ||
| self.assertEqual(sensor.state, 2) | ||
|
|
||
| def test_update_caputred_today(self): |
|
|
||
| def test_setup_with_no_data(self): | ||
| """Test setup_platform with no data.""" | ||
| result = arlo.setup_platform(self.hass, None, None) |
There was a problem hiding this comment.
setup_platform shouldn't return anything. Please fix that in the module and update this test.
|
|
||
| def test_sensor_name(self): | ||
| """Test the name property.""" | ||
| sensor = TestArloSensor._get_mock_sensor() |
There was a problem hiding this comment.
The method is weirdly named since it doesn't seem to return a mock sensor but an actual instance of an arlo sensor.
Access methods using self..
There was a problem hiding this comment.
Will correct the method name and change to self access.
Also, just for reference as I'm coming back to python several years away, is it not optimal to use the @staticmethod decorator and treat these as "static" as opposed to members? Or is this just convention? Or is this an artifact of having this in a class as opposed to just standalone functions? Thanks for the help.
There was a problem hiding this comment.
Static methods can still be accessed on the instance, ie using self. inside instance methods. It's only inside the static method that the instance isn't available.
Yeah, the static methods are part of what could be cleaned up if we would move to standalone pytest tests.
| sensor = TestArloSensor._get_mock_sensor() | ||
| self.assertEqual(sensor.name, 'Last') | ||
|
|
||
| @asyncio.coroutine |
There was a problem hiding this comment.
Again, having just come back to python (after several years and from python2), async methods in python3 have come quite a long way, and I'm still getting accustomed to the new stuff:
When I omit this, it complains that the function is never awaited. In tests for other HA components, I noticed that standalone functions omit this, but functions declared in classes have it. Is this correlation true or just coincidence? If it does in fact need to be removed, what's the appropriate way to address the no-await issue?
There was a problem hiding this comment.
We're already declaring the method a coroutine by saying async def. This is the new async syntax in Python 3.5. So we shouldn't add the coroutine decorator on top of that. Class based unittest tests don't support coroutines as test methods. Here we have to move to standalone pytest tests.
| """Test dispatcher called when added.""" | ||
| sensor = TestArloSensor._get_mock_sensor() | ||
| await sensor.async_added_to_hass() | ||
| dispatcher.async_dispatcher_connect.assert_called_once() |
There was a problem hiding this comment.
We prefer not to use assert_called_once but instead use assert len(mock.calls) == 1. A typo on the mock method would pass silently.
|
|
||
| def test_sensor_state_default(self): | ||
| """Test the state property.""" | ||
| sensor = TestArloSensor._get_mock_sensor() |
There was a problem hiding this comment.
Use self._get_mock_sensor().
| sensors = None | ||
|
|
||
| def _add_devices(self, sensors, boolean): | ||
| if boolean: |
There was a problem hiding this comment.
Why would we only add the sensors when passing True as the second argument? That's not how the actual code works.
There was a problem hiding this comment.
Not actually sure what the second param does -- just noticed that it's hardcoded True in the sensor code. But this does make the test rather brittle and potentially (probably) incorrect. I've removed the condition for now and will take another pass after some discovery.
There was a problem hiding this comment.
Passing True to add_devices calls entity.update during entity addition.
|
@MartinHjelmare thanks for the feedback - super helpful insights. I converted to standalone pytest functions and cleaned up some of the other stuff you mentioned. |
| self._sensor_type == 'signal_strength': | ||
| known = False | ||
| for sensor_type in SENSOR_TYPES: | ||
| if self._sensor_type == sensor_type: |
There was a problem hiding this comment.
We already validate this in the config schema. We can remove this check completely here.
| def test_async_added_to_hass(default_sensor): | ||
| """Test dispatcher called when added.""" | ||
| yield from default_sensor.async_added_to_hass() | ||
| assert len(dispatcher.async_dispatcher_connect.calls) == 1 |
There was a problem hiding this comment.
We should check that the calls on the mock object is of correct length.
|
|
||
| @patch('homeassistant.helpers.dispatcher.async_dispatcher_connect', | ||
| MagicMock()) | ||
| def test_async_added_to_hass(default_sensor): |
There was a problem hiding this comment.
Define this as a coroutine with async def.
| MagicMock()) | ||
| def test_async_added_to_hass(default_sensor): | ||
| """Test dispatcher called when added.""" | ||
| yield from default_sensor.async_added_to_hass() |
There was a problem hiding this comment.
Use await instead of yield from.
| assert default_sensor.name == 'Last' | ||
|
|
||
|
|
||
| @patch('homeassistant.helpers.dispatcher.async_dispatcher_connect', |
There was a problem hiding this comment.
Move this to a fixture in which we yield the mock.
| """Test dispatcher called when added.""" | ||
| with patch( | ||
| 'homeassistant.components.sensor.arlo.async_dispatcher_connect', | ||
| MagicMock()) as mock: |
There was a problem hiding this comment.
visually indented line with same indent as next logical line
|
@MartinHjelmare thanks again for the feedback. I fixed a problem where I was patching the wrong namespace -- this helped make sense of a lot of the other feedback you gave. I did however still have one thing I was stuck on -- not sure if it's a blocker though: I removed the However, when I pass the sensor and the mock as a fixture, e.g.: I get As an aside, this may be getting a bit out of my league, and given that the code under test (for this specific test) isn't part of the scope of the initial feature addition, perhaps it would be appropriate to redact this test from this PR and file a separate PR with only the appropriate/pythonic implementation of this test. What are your thoughts? |
|
It's ok to keep the test as is. This is how you could have applied the patch within a fixture. Note the @pytest.fixture
def mock_dispatch():
with patch('path.to.dispatch') as _mock:
yield _mock |
MartinHjelmare
left a comment
There was a problem hiding this comment.
It's easier to catch bugs if we set up the complete platform, while patching appropriately, and run all checks on the entity using the home assistant core, eg get the state from the state machine and check that it's ok. Ie write the tests more as integration tests than isolated unit tests.
| self._sensor_type == 'battery_level' or \ | ||
| self._sensor_type == 'signal_strength': | ||
| attrs['model'] = self._data.model_id | ||
| attrs['model'] = self._data.model_id |
There was a problem hiding this comment.
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.
|
@MartinHjelmare I've added the additional test cases you mentioned and updated the dispatcher test to use a fixture -- thanks for giving me the last piece of the puzzle on that one (again). I also added the check for total_cameras sensor type -- good catch. I'm pretty sure that what you mentioned about having fully-setup integration tests as opposed to unit tests is not satisfied by the latest commits. I can definitely see the benefit of this approach (especially given the total_cameras example you highlighted). I would like to potentially focus on that a bit more in a separate branch/PR given the scope of those changes. Is that acceptable or should I proceed in this branch? |
|
@MartinHjelmare thanks again for all your help with this. @tchellomello could you also give these changes another look? Thanks! |
tchellomello
left a comment
There was a problem hiding this comment.
Looks good @lukiffer!! Thanks for your contribution!!
Thanks @MartinHjelmare for the code review!!
* 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
* 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
Description:
Adds additional temperature, humidity, and air quality sensors for Arlo Baby cameras implemented in
pyarlo==0.1.8.Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/home-assistant.io#5583
Example entry for
configuration.yaml(if applicable):Checklist:
tox. Your PR cannot be merged unless tests passIf user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
REQUIREMENTSvariable (example).requirements_all.txtby runningscript/gen_requirements_all.py..coveragerc.If the code does not interact with devices: