diff --git a/.coveragerc b/.coveragerc index f9f03ac2215adce0caf552409055f31f49fdb115..c93fecc9c2ed0d683188f93f93463760864c26fa 100644 --- a/.coveragerc +++ b/.coveragerc @@ -211,6 +211,7 @@ omit = homeassistant/components/camera/foscam.py homeassistant/components/camera/mjpeg.py homeassistant/components/camera/rpi_camera.py + homeassistant/components/camera/onvif.py homeassistant/components/camera/synology.py homeassistant/components/climate/eq3btsmart.py homeassistant/components/climate/flexit.py diff --git a/homeassistant/components/camera/onvif.py b/homeassistant/components/camera/onvif.py new file mode 100644 index 0000000000000000000000000000000000000000..f1c94f79c0b2deda1c514122c58390390812637a --- /dev/null +++ b/homeassistant/components/camera/onvif.py @@ -0,0 +1,102 @@ +""" +Support for ONVIF Cameras with FFmpeg as decoder. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/camera.onvif/ +""" +import asyncio +import logging + +import voluptuous as vol + +from homeassistant.const import ( + CONF_NAME, CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT) +from homeassistant.components.camera import Camera, PLATFORM_SCHEMA +from homeassistant.components.ffmpeg import ( + DATA_FFMPEG) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.aiohttp_client import ( + async_aiohttp_proxy_stream) + +_LOGGER = logging.getLogger(__name__) + +REQUIREMENTS = ['onvif-py3==0.1.3', + 'suds-py3==1.3.3.0', + 'http://github.com/tgaugry/suds-passworddigest-py3' + '/archive/86fc50e39b4d2b8997481967d6a7fe1c57118999.zip' + '#suds-passworddigest-py3==0.1.2a'] +DEPENDENCIES = ['ffmpeg'] +DEFAULT_NAME = 'ONVIF Camera' +DEFAULT_PORT = 5000 +DEFAULT_USERNAME = 'admin' +DEFAULT_PASSWORD = '888888' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, + vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, +}) + + +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Set up a ONVIF camera.""" + if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_HOST)): + return + async_add_devices([ONVIFCamera(hass, config)]) + + +class ONVIFCamera(Camera): + """An implementation of an ONVIF camera.""" + + def __init__(self, hass, config): + """Initialize a ONVIF camera.""" + from onvif import ONVIFService + super().__init__() + + self._name = config.get(CONF_NAME) + self._ffmpeg_arguments = '-q:v 2' + media = ONVIFService( + 'http://{}:{}/onvif/device_service'.format( + config.get(CONF_HOST), config.get(CONF_PORT)), + config.get(CONF_USERNAME), + config.get(CONF_PASSWORD), + '{}/deps/onvif/wsdl/media.wsdl'.format(hass.config.config_dir) + ) + self._input = media.GetStreamUri().Uri + _LOGGER.debug("ONVIF Camera Using the following URL for %s: %s", + self._name, self._input) + + @asyncio.coroutine + def async_camera_image(self): + """Return a still image response from the camera.""" + from haffmpeg import ImageFrame, IMAGE_JPEG + ffmpeg = ImageFrame( + self.hass.data[DATA_FFMPEG].binary, loop=self.hass.loop) + + image = yield from ffmpeg.get_image( + self._input, output_format=IMAGE_JPEG, + extra_cmd=self._ffmpeg_arguments) + return image + + @asyncio.coroutine + def handle_async_mjpeg_stream(self, request): + """Generate an HTTP MJPEG stream from the camera.""" + from haffmpeg import CameraMjpeg + + stream = CameraMjpeg(self.hass.data[DATA_FFMPEG].binary, + loop=self.hass.loop) + yield from stream.open_camera( + self._input, extra_cmd=self._ffmpeg_arguments) + + yield from async_aiohttp_proxy_stream( + self.hass, request, stream, + 'multipart/x-mixed-replace;boundary=ffserver') + yield from stream.close() + + @property + def name(self): + """Return the name of this camera.""" + return self._name diff --git a/requirements_all.txt b/requirements_all.txt index 625285ebf3e4c2075a9fc48c3210b524f70aaa22..c4032e65c77e9a3d0871302175ab56f3ef05cffc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -267,6 +267,9 @@ hikvision==0.4 # homeassistant.components.binary_sensor.workday holidays==0.8.1 +# homeassistant.components.camera.onvif +http://github.com/tgaugry/suds-passworddigest-py3/archive/86fc50e39b4d2b8997481967d6a7fe1c57118999.zip#suds-passworddigest-py3==0.1.2a + # homeassistant.components.switch.rachio https://github.com/Klikini/rachiopy/archive/2c8996fcfa97a9f361a789e0c998797ed2805281.zip#rachiopy==0.1.1 @@ -406,6 +409,9 @@ oemthermostat==1.1 # homeassistant.components.media_player.onkyo onkyo-eiscp==1.1 +# homeassistant.components.camera.onvif +onvif-py3==0.1.3 + # homeassistant.components.sensor.openevse openevsewifi==0.4 @@ -833,6 +839,9 @@ statsd==3.2.1 # homeassistant.components.sensor.steam_online steamodd==4.21 +# homeassistant.components.camera.onvif +suds-py3==1.3.3.0 + # homeassistant.components.binary_sensor.tapsaff tapsaff==0.1.3