mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
UVC camera platform handling unavailable NVR or cameras better (#14864)
* fixed tests: using correct camera configuration now and error handling tests must be separated out to ensure that the setup_component call is actually executed * better error handling during setup; raising PlatformNotReady in likely recoverable cases; added tests
This commit is contained in:
parent
d3d9d9ebf2
commit
f242418986
@ -13,6 +13,7 @@ import voluptuous as vol
|
|||||||
from homeassistant.const import CONF_PORT
|
from homeassistant.const import CONF_PORT
|
||||||
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
|
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
|
|
||||||
REQUIREMENTS = ['uvcclient==0.10.1']
|
REQUIREMENTS = ['uvcclient==0.10.1']
|
||||||
|
|
||||||
@ -41,25 +42,26 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
port = config[CONF_PORT]
|
port = config[CONF_PORT]
|
||||||
|
|
||||||
from uvcclient import nvr
|
from uvcclient import nvr
|
||||||
nvrconn = nvr.UVCRemote(addr, port, key)
|
|
||||||
try:
|
try:
|
||||||
|
# Exceptions may be raised in all method calls to the nvr library.
|
||||||
|
nvrconn = nvr.UVCRemote(addr, port, key)
|
||||||
cameras = nvrconn.index()
|
cameras = nvrconn.index()
|
||||||
|
|
||||||
|
identifier = 'id' if nvrconn.server_version >= (3, 2, 0) else 'uuid'
|
||||||
|
# Filter out airCam models, which are not supported in the latest
|
||||||
|
# version of UnifiVideo and which are EOL by Ubiquiti
|
||||||
|
cameras = [
|
||||||
|
camera for camera in cameras
|
||||||
|
if 'airCam' not in nvrconn.get_camera(camera[identifier])['model']]
|
||||||
except nvr.NotAuthorized:
|
except nvr.NotAuthorized:
|
||||||
_LOGGER.error("Authorization failure while connecting to NVR")
|
_LOGGER.error("Authorization failure while connecting to NVR")
|
||||||
return False
|
return False
|
||||||
except nvr.NvrError:
|
except nvr.NvrError as ex:
|
||||||
_LOGGER.error("NVR refuses to talk to me")
|
_LOGGER.error("NVR refuses to talk to me: %s", str(ex))
|
||||||
return False
|
raise PlatformNotReady
|
||||||
except requests.exceptions.ConnectionError as ex:
|
except requests.exceptions.ConnectionError as ex:
|
||||||
_LOGGER.error("Unable to connect to NVR: %s", str(ex))
|
_LOGGER.error("Unable to connect to NVR: %s", str(ex))
|
||||||
return False
|
raise PlatformNotReady
|
||||||
|
|
||||||
identifier = 'id' if nvrconn.server_version >= (3, 2, 0) else 'uuid'
|
|
||||||
# Filter out airCam models, which are not supported in the latest
|
|
||||||
# version of UnifiVideo and which are EOL by Ubiquiti
|
|
||||||
cameras = [
|
|
||||||
camera for camera in cameras
|
|
||||||
if 'airCam' not in nvrconn.get_camera(camera[identifier])['model']]
|
|
||||||
|
|
||||||
add_devices([UnifiVideoCamera(nvrconn,
|
add_devices([UnifiVideoCamera(nvrconn,
|
||||||
camera[identifier],
|
camera[identifier],
|
||||||
|
@ -7,6 +7,7 @@ import requests
|
|||||||
from uvcclient import camera
|
from uvcclient import camera
|
||||||
from uvcclient import nvr
|
from uvcclient import nvr
|
||||||
|
|
||||||
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
from homeassistant.setup import setup_component
|
from homeassistant.setup import setup_component
|
||||||
from homeassistant.components.camera import uvc
|
from homeassistant.components.camera import uvc
|
||||||
from tests.common import get_test_home_assistant
|
from tests.common import get_test_home_assistant
|
||||||
@ -34,21 +35,21 @@ class TestUVCSetup(unittest.TestCase):
|
|||||||
'port': 123,
|
'port': 123,
|
||||||
'key': 'secret',
|
'key': 'secret',
|
||||||
}
|
}
|
||||||
fake_cameras = [
|
mock_cameras = [
|
||||||
{'uuid': 'one', 'name': 'Front', 'id': 'id1'},
|
{'uuid': 'one', 'name': 'Front', 'id': 'id1'},
|
||||||
{'uuid': 'two', 'name': 'Back', 'id': 'id2'},
|
{'uuid': 'two', 'name': 'Back', 'id': 'id2'},
|
||||||
{'uuid': 'three', 'name': 'Old AirCam', 'id': 'id3'},
|
{'uuid': 'three', 'name': 'Old AirCam', 'id': 'id3'},
|
||||||
]
|
]
|
||||||
|
|
||||||
def fake_get_camera(uuid):
|
def mock_get_camera(uuid):
|
||||||
"""Create a fake camera."""
|
"""Create a mock camera."""
|
||||||
if uuid == 'id3':
|
if uuid == 'id3':
|
||||||
return {'model': 'airCam'}
|
return {'model': 'airCam'}
|
||||||
else:
|
else:
|
||||||
return {'model': 'UVC'}
|
return {'model': 'UVC'}
|
||||||
|
|
||||||
mock_remote.return_value.index.return_value = fake_cameras
|
mock_remote.return_value.index.return_value = mock_cameras
|
||||||
mock_remote.return_value.get_camera.side_effect = fake_get_camera
|
mock_remote.return_value.get_camera.side_effect = mock_get_camera
|
||||||
mock_remote.return_value.server_version = (3, 2, 0)
|
mock_remote.return_value.server_version = (3, 2, 0)
|
||||||
|
|
||||||
assert setup_component(self.hass, 'camera', {'camera': config})
|
assert setup_component(self.hass, 'camera', {'camera': config})
|
||||||
@ -71,11 +72,11 @@ class TestUVCSetup(unittest.TestCase):
|
|||||||
'nvr': 'foo',
|
'nvr': 'foo',
|
||||||
'key': 'secret',
|
'key': 'secret',
|
||||||
}
|
}
|
||||||
fake_cameras = [
|
mock_cameras = [
|
||||||
{'uuid': 'one', 'name': 'Front', 'id': 'id1'},
|
{'uuid': 'one', 'name': 'Front', 'id': 'id1'},
|
||||||
{'uuid': 'two', 'name': 'Back', 'id': 'id2'},
|
{'uuid': 'two', 'name': 'Back', 'id': 'id2'},
|
||||||
]
|
]
|
||||||
mock_remote.return_value.index.return_value = fake_cameras
|
mock_remote.return_value.index.return_value = mock_cameras
|
||||||
mock_remote.return_value.get_camera.return_value = {'model': 'UVC'}
|
mock_remote.return_value.get_camera.return_value = {'model': 'UVC'}
|
||||||
mock_remote.return_value.server_version = (3, 2, 0)
|
mock_remote.return_value.server_version = (3, 2, 0)
|
||||||
|
|
||||||
@ -99,11 +100,11 @@ class TestUVCSetup(unittest.TestCase):
|
|||||||
'nvr': 'foo',
|
'nvr': 'foo',
|
||||||
'key': 'secret',
|
'key': 'secret',
|
||||||
}
|
}
|
||||||
fake_cameras = [
|
mock_cameras = [
|
||||||
{'uuid': 'one', 'name': 'Front', 'id': 'id1'},
|
{'uuid': 'one', 'name': 'Front', 'id': 'id1'},
|
||||||
{'uuid': 'two', 'name': 'Back', 'id': 'id2'},
|
{'uuid': 'two', 'name': 'Back', 'id': 'id2'},
|
||||||
]
|
]
|
||||||
mock_remote.return_value.index.return_value = fake_cameras
|
mock_remote.return_value.index.return_value = mock_cameras
|
||||||
mock_remote.return_value.get_camera.return_value = {'model': 'UVC'}
|
mock_remote.return_value.get_camera.return_value = {'model': 'UVC'}
|
||||||
mock_remote.return_value.server_version = (3, 1, 3)
|
mock_remote.return_value.server_version = (3, 1, 3)
|
||||||
|
|
||||||
@ -133,19 +134,62 @@ class TestUVCSetup(unittest.TestCase):
|
|||||||
|
|
||||||
@mock.patch.object(uvc, 'UnifiVideoCamera')
|
@mock.patch.object(uvc, 'UnifiVideoCamera')
|
||||||
@mock.patch('uvcclient.nvr.UVCRemote')
|
@mock.patch('uvcclient.nvr.UVCRemote')
|
||||||
def test_setup_nvr_errors(self, mock_remote, mock_uvc):
|
def setup_nvr_errors_during_indexing(self, error, mock_remote, mock_uvc):
|
||||||
"""Test for NVR errors."""
|
"""Setup test for NVR errors during indexing."""
|
||||||
errors = [nvr.NotAuthorized, nvr.NvrError,
|
|
||||||
requests.exceptions.ConnectionError]
|
|
||||||
config = {
|
config = {
|
||||||
'platform': 'uvc',
|
'platform': 'uvc',
|
||||||
'nvr': 'foo',
|
'nvr': 'foo',
|
||||||
'key': 'secret',
|
'key': 'secret',
|
||||||
}
|
}
|
||||||
for error in errors:
|
mock_remote.return_value.index.side_effect = error
|
||||||
mock_remote.return_value.index.side_effect = error
|
assert setup_component(self.hass, 'camera', {'camera': config})
|
||||||
assert setup_component(self.hass, 'camera', config)
|
assert not mock_uvc.called
|
||||||
assert not mock_uvc.called
|
|
||||||
|
def test_setup_nvr_error_during_indexing_notauthorized(self):
|
||||||
|
"""Test for error: nvr.NotAuthorized."""
|
||||||
|
self.setup_nvr_errors_during_indexing(nvr.NotAuthorized)
|
||||||
|
|
||||||
|
def test_setup_nvr_error_during_indexing_nvrerror(self):
|
||||||
|
"""Test for error: nvr.NvrError."""
|
||||||
|
self.setup_nvr_errors_during_indexing(nvr.NvrError)
|
||||||
|
self.assertRaises(PlatformNotReady)
|
||||||
|
|
||||||
|
def test_setup_nvr_error_during_indexing_connectionerror(self):
|
||||||
|
"""Test for error: requests.exceptions.ConnectionError."""
|
||||||
|
self.setup_nvr_errors_during_indexing(
|
||||||
|
requests.exceptions.ConnectionError)
|
||||||
|
self.assertRaises(PlatformNotReady)
|
||||||
|
|
||||||
|
@mock.patch.object(uvc, 'UnifiVideoCamera')
|
||||||
|
@mock.patch('uvcclient.nvr.UVCRemote.__init__')
|
||||||
|
def setup_nvr_errors_during_initialization(self, error, mock_remote,
|
||||||
|
mock_uvc):
|
||||||
|
"""Setup test for NVR errors during initialization."""
|
||||||
|
config = {
|
||||||
|
'platform': 'uvc',
|
||||||
|
'nvr': 'foo',
|
||||||
|
'key': 'secret',
|
||||||
|
}
|
||||||
|
mock_remote.return_value = None
|
||||||
|
mock_remote.side_effect = error
|
||||||
|
assert setup_component(self.hass, 'camera', {'camera': config})
|
||||||
|
assert not mock_remote.index.called
|
||||||
|
assert not mock_uvc.called
|
||||||
|
|
||||||
|
def test_setup_nvr_error_during_initialization_notauthorized(self):
|
||||||
|
"""Test for error: nvr.NotAuthorized."""
|
||||||
|
self.setup_nvr_errors_during_initialization(nvr.NotAuthorized)
|
||||||
|
|
||||||
|
def test_setup_nvr_error_during_initialization_nvrerror(self):
|
||||||
|
"""Test for error: nvr.NvrError."""
|
||||||
|
self.setup_nvr_errors_during_initialization(nvr.NvrError)
|
||||||
|
self.assertRaises(PlatformNotReady)
|
||||||
|
|
||||||
|
def test_setup_nvr_error_during_initialization_connectionerror(self):
|
||||||
|
"""Test for error: requests.exceptions.ConnectionError."""
|
||||||
|
self.setup_nvr_errors_during_initialization(
|
||||||
|
requests.exceptions.ConnectionError)
|
||||||
|
self.assertRaises(PlatformNotReady)
|
||||||
|
|
||||||
|
|
||||||
class TestUVC(unittest.TestCase):
|
class TestUVC(unittest.TestCase):
|
||||||
@ -208,8 +252,8 @@ class TestUVC(unittest.TestCase):
|
|||||||
"""Test the login tries."""
|
"""Test the login tries."""
|
||||||
responses = [0]
|
responses = [0]
|
||||||
|
|
||||||
def fake_login(*a):
|
def mock_login(*a):
|
||||||
"""Fake login."""
|
"""Mock login."""
|
||||||
try:
|
try:
|
||||||
responses.pop(0)
|
responses.pop(0)
|
||||||
raise socket.error
|
raise socket.error
|
||||||
@ -217,7 +261,7 @@ class TestUVC(unittest.TestCase):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
mock_store.return_value.get_camera_password.return_value = None
|
mock_store.return_value.get_camera_password.return_value = None
|
||||||
mock_camera.return_value.login.side_effect = fake_login
|
mock_camera.return_value.login.side_effect = mock_login
|
||||||
self.uvc._login()
|
self.uvc._login()
|
||||||
self.assertEqual(2, mock_camera.call_count)
|
self.assertEqual(2, mock_camera.call_count)
|
||||||
self.assertEqual('host-b', self.uvc._connect_addr)
|
self.assertEqual('host-b', self.uvc._connect_addr)
|
||||||
@ -263,8 +307,8 @@ class TestUVC(unittest.TestCase):
|
|||||||
"""Test the re-authentication."""
|
"""Test the re-authentication."""
|
||||||
responses = [0]
|
responses = [0]
|
||||||
|
|
||||||
def fake_snapshot():
|
def mock_snapshot():
|
||||||
"""Fake snapshot."""
|
"""Mock snapshot."""
|
||||||
try:
|
try:
|
||||||
responses.pop()
|
responses.pop()
|
||||||
raise camera.CameraAuthError()
|
raise camera.CameraAuthError()
|
||||||
@ -273,7 +317,7 @@ class TestUVC(unittest.TestCase):
|
|||||||
return 'image'
|
return 'image'
|
||||||
|
|
||||||
self.uvc._camera = mock.MagicMock()
|
self.uvc._camera = mock.MagicMock()
|
||||||
self.uvc._camera.get_snapshot.side_effect = fake_snapshot
|
self.uvc._camera.get_snapshot.side_effect = mock_snapshot
|
||||||
with mock.patch.object(self.uvc, '_login') as mock_login:
|
with mock.patch.object(self.uvc, '_login') as mock_login:
|
||||||
self.assertEqual('image', self.uvc.camera_image())
|
self.assertEqual('image', self.uvc.camera_image())
|
||||||
self.assertEqual(mock_login.call_count, 1)
|
self.assertEqual(mock_login.call_count, 1)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user