diff --git a/homeassistant/components/camera/local_file.py b/homeassistant/components/camera/local_file.py index 95d24c7d42e..95eade48568 100644 --- a/homeassistant/components/camera/local_file.py +++ b/homeassistant/components/camera/local_file.py @@ -11,31 +11,44 @@ import os import voluptuous as vol from homeassistant.const import CONF_NAME -from homeassistant.components.camera import Camera, PLATFORM_SCHEMA +from homeassistant.components.camera import ( + Camera, CAMERA_SERVICE_SCHEMA, DOMAIN, PLATFORM_SCHEMA) from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) CONF_FILE_PATH = 'file_path' - DEFAULT_NAME = 'Local File' +SERVICE_UPDATE_FILE_PATH = 'local_file_update_file_path' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_FILE_PATH): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string }) +CAMERA_SERVICE_UPDATE_FILE_PATH = CAMERA_SERVICE_SCHEMA.extend({ + vol.Required(CONF_FILE_PATH): cv.string +}) + def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Camera that works with local files.""" file_path = config[CONF_FILE_PATH] + camera = LocalFile(config[CONF_NAME], file_path) - # check filepath given is readable - if not os.access(file_path, os.R_OK): - _LOGGER.warning("Could not read camera %s image from file: %s", - config[CONF_NAME], file_path) + def update_file_path_service(call): + """Update the file path.""" + file_path = call.data.get(CONF_FILE_PATH) + camera.update_file_path(file_path) + return True - add_devices([LocalFile(config[CONF_NAME], file_path)]) + hass.services.register( + DOMAIN, + SERVICE_UPDATE_FILE_PATH, + update_file_path_service, + schema=CAMERA_SERVICE_UPDATE_FILE_PATH) + + add_devices([camera]) class LocalFile(Camera): @@ -46,6 +59,7 @@ class LocalFile(Camera): super().__init__() self._name = name + self.check_file_path_access(file_path) self._file_path = file_path # Set content type of local file content, _ = mimetypes.guess_type(file_path) @@ -61,7 +75,26 @@ class LocalFile(Camera): _LOGGER.warning("Could not read camera %s image from file: %s", self._name, self._file_path) + def check_file_path_access(self, file_path): + """Check that filepath given is readable.""" + if not os.access(file_path, os.R_OK): + _LOGGER.warning("Could not read camera %s image from file: %s", + self._name, file_path) + + def update_file_path(self, file_path): + """Update the file_path.""" + self.check_file_path_access(file_path) + self._file_path = file_path + self.schedule_update_ha_state() + @property def name(self): """Return the name of this camera.""" return self._name + + @property + def device_state_attributes(self): + """Return the camera state attributes.""" + return { + 'file_path': self._file_path, + } diff --git a/homeassistant/components/camera/services.yaml b/homeassistant/components/camera/services.yaml index b548f3d1ada..544fd0e6b8a 100644 --- a/homeassistant/components/camera/services.yaml +++ b/homeassistant/components/camera/services.yaml @@ -24,6 +24,16 @@ snapshot: description: Template of a Filename. Variable is entity_id. example: '/tmp/snapshot_{{ entity_id }}' +local_file_update_file_path: + description: Update the file_path for a local_file camera. + fields: + entity_id: + description: Name(s) of entities to update. + example: 'camera.local_file' + file_path: + description: Path to the new image file. + example: '/images/newimage.jpg' + onvif_ptz: description: Pan/Tilt/Zoom service for ONVIF camera. fields: @@ -39,4 +49,3 @@ onvif_ptz: zoom: description: "Zoom. Allowed values: ZOOM_IN, ZOOM_OUT" example: "ZOOM_IN" - diff --git a/tests/components/camera/test_local_file.py b/tests/components/camera/test_local_file.py index 1098c8c9233..40517ea1298 100644 --- a/tests/components/camera/test_local_file.py +++ b/tests/components/camera/test_local_file.py @@ -6,6 +6,9 @@ from unittest import mock # https://bugs.python.org/issue23004 from mock_open import MockOpen +from homeassistant.components.camera import DOMAIN +from homeassistant.components.camera.local_file import ( + SERVICE_UPDATE_FILE_PATH) from homeassistant.setup import async_setup_component from tests.common import mock_registry @@ -115,3 +118,37 @@ def test_camera_content_type(hass, aiohttp_client): assert resp_4.content_type == 'image/jpeg' body = yield from resp_4.text() assert body == image + + +async def test_update_file_path(hass): + """Test update_file_path service.""" + # Setup platform + + mock_registry(hass) + + with mock.patch('os.path.isfile', mock.Mock(return_value=True)), \ + mock.patch('os.access', mock.Mock(return_value=True)): + await async_setup_component(hass, 'camera', { + 'camera': { + 'platform': 'local_file', + 'file_path': 'mock/path.jpg' + } + }) + + # Fetch state and check motion detection attribute + state = hass.states.get('camera.local_file') + assert state.attributes.get('friendly_name') == 'Local File' + assert state.attributes.get('file_path') == 'mock/path.jpg' + + service_data = { + "entity_id": 'camera.local_file', + "file_path": 'new/path.jpg' + } + + await hass.services.async_call(DOMAIN, + SERVICE_UPDATE_FILE_PATH, + service_data) + await hass.async_block_till_done() + + state = hass.states.get('camera.local_file') + assert state.attributes.get('file_path') == 'new/path.jpg'