class ONVIFCLI(Cmd): prompt = 'ONVIF >>> ' client = None cmd_parser = None def setup(self, args): ''' `args`: Instance of `argparse.ArgumentParser` ''' # Create onvif camera client self.client = ONVIFCamera(args.host, args.port, args.user, args.password, args.wsdl, encrypt=args.encrypt) # Create cmd argument parser self.create_cmd_parser() def create_cmd_parser(self): # Create parser to parse CMD, `params` is optional. cmd_parser = ThrowingArgumentParser( prog='ONVIF CMD', usage='CMD service operation [params]') cmd_parser.add_argument('service') cmd_parser.add_argument('operation') cmd_parser.add_argument('params', default='{}', nargs=REMAINDER) self.cmd_parser = cmd_parser def do_cmd(self, line): '''Usage: CMD service operation [parameters]''' try: args = self.cmd_parser.parse_args(line.split()) except ValueError as err: return error(err) # Check if args.service is valid if args.service not in SUPPORTED_SERVICES: return error('No Service: ' + args.service) args.params = ''.join(args.params) # params is optional if not args.params.strip(): args.params = '{}' # params must be a dictionary format string match = re.match(r"^.*?(\{.*\}).*$", args.params) if not match: return error('Invalid params') try: args.params = dict(literal_eval(match.group(1))) except ValueError as err: return error('Invalid params') try: # Get ONVIF service service = self.client.get_service(args.service) # Actually execute the command and get the response response = getattr(service, args.operation)(args.params) except MethodNotFound as err: return error('No Operation: %s' % args.operation) except Exception as err: return error(err) if isinstance(response, (Text, bool)): return success(response) # Try to convert instance to dictionary try: success(ONVIFService.to_dict(response)) except ONVIFError: error({}) def complete_cmd(self, text, line, begidx, endidx): # TODO: complete service operations # service.ws_client.service._ServiceSelector__services[0].ports[0].methods.keys() if not text: completions = SUPPORTED_SERVICES[:] else: completions = [ key for key in SUPPORTED_SERVICES if key.startswith(text) ] return completions def emptyline(self): return '' def do_EOF(self, line): return True
class ONVIFHassCamera(Camera): """An implementation of an ONVIF camera.""" def __init__(self, hass, config): """Initialize an ONVIF camera.""" super().__init__() _LOGGER.debug("Importing dependencies") _LOGGER.debug("Setting up the ONVIF camera component") self._username = config.get(CONF_USERNAME) self._password = config.get(CONF_PASSWORD) self._host = config.get(CONF_HOST) self._port = config.get(CONF_PORT) self._name = config.get(CONF_NAME) self._ffmpeg_arguments = config.get(CONF_EXTRA_ARGUMENTS) self._profile_index = config.get(CONF_PROFILE_IDX) self._rtsp_transport = config.get(CONF_RTSP_TRANSPORT) self._media_service = None self._ptz_service = None self._image_service = None self._input_uri = None self._input_uri_for_log = None self._profile_token = None self._profiles = None self._ptz_opt = None self._ptz_presets = None _LOGGER.debug("Setting up the ONVIF camera device @ '%s:%s'", self._host, self._port) self._camera = ONVIFCamera( self._host, self._port, self._username, self._password, "{}/wsdl/".format(os.path.dirname(onvif.__file__)), ) async def async_initialize(self): """ Initialize the camera. Initializes the camera by obtaining the input uri and connecting to the camera. Also retrieves the ONVIF profiles. """ try: _LOGGER.debug("Updating service addresses") await self._camera.update_xaddrs() await self.async_check_date_and_time() self._media_service = await self.async_obtain_media_service() self._profiles = await self.async_obtain_profiles() self._profile_token = self.index_to_profile_token() await self.async_obtain_input_uri() self._ptz_service = await self.async_obtain_ptz_service() except ClientConnectionError as err: _LOGGER.warning( "Couldn't connect to camera '%s', but will retry later. Error: %s", self._name, err, ) raise PlatformNotReady except Fault as err: _LOGGER.error( "Couldn't connect to camera '%s', please verify " "that the credentials are correct. Error: %s", self._name, err, ) async def async_check_date_and_time(self): """Warns if camera and system date not synced.""" _LOGGER.debug("Setting up the ONVIF device management service") devicemgmt = self._camera.create_devicemgmt_service() _LOGGER.debug("Retrieving current camera date/time") try: system_date = dt_util.utcnow() device_time = await devicemgmt.GetSystemDateAndTime() if not device_time: _LOGGER.debug( """Couldn't get camera '%s' date/time. GetSystemDateAndTime() return null/empty""", self._name, ) return if device_time.UTCDateTime: tzone = dt_util.UTC cdate = device_time.UTCDateTime else: tzone = (dt_util.get_time_zone(device_time.TimeZone) or dt_util.DEFAULT_TIME_ZONE) cdate = device_time.LocalDateTime if cdate is None: _LOGGER.warning("Could not retrieve date/time on this camera") else: cam_date = dt.datetime( cdate.Date.Year, cdate.Date.Month, cdate.Date.Day, cdate.Time.Hour, cdate.Time.Minute, cdate.Time.Second, 0, tzone, ) cam_date_utc = cam_date.astimezone(dt_util.UTC) _LOGGER.debug("TimeZone for date/time: %s", tzone) _LOGGER.debug("Camera date/time: %s", cam_date) _LOGGER.debug("Camera date/time in UTC: %s", cam_date_utc) _LOGGER.debug("System date/time: %s", system_date) dt_diff = cam_date - system_date dt_diff_seconds = dt_diff.total_seconds() if dt_diff_seconds > 5: _LOGGER.warning( "The date/time on the camera (UTC) is '%s', " "which is different from the system '%s', " "this could lead to authentication issues", cam_date_utc, system_date, ) except ServerDisconnectedError as err: _LOGGER.warning("Couldn't get camera '%s' date/time. Error: %s", self._name, err) async def async_obtain_profiles(self): """Obtain onvif profiles object.""" try: __profiles = await self._media_service.GetProfiles() _LOGGER.debug("Retrieved '%d' profiles", len(__profiles)) return __profiles except exceptions.ONVIFError as err: _LOGGER.error( "Couldn't retrieve profiles of camera '%s'. Error: %s", self._name, err, ) return None async def async_obtain_media_service(self): """Obtain onvif profiles object.""" try: _LOGGER.debug("Connecting with ONVIF Camera: %s on port %s", self._host, self._port) __media_service = self._camera.create_media_service() return __media_service except exceptions.ONVIFError as err: _LOGGER.error( "Couldn't retrieve media_service of camera '%s'. Error: %s", self._name, err, ) return None def index_to_profile_token(self): """Return token name from a index over profiles object.""" if self._profile_index >= len(self._profiles): _LOGGER.warning( "ONVIF Camera '%s' doesn't provide profile %d." " Using the last profile.", self._name, self._profile_index, ) self._profile_index = -1 _LOGGER.debug("Using profile index '%d'", self._profile_index) return self._profiles[self._profile_index].token async def async_obtain_input_uri(self): """Set the input uri for the camera.""" _LOGGER.debug("Retrieving stream uri") # Fix Onvif setup error on Goke GK7102 based IP camera #26781 # Assume not buggy camera and see if ClientConnectionError # reload fresh self._media_service before one retry for i in range(0, 2): try: req = self._media_service.create_type("GetStreamUri") req.ProfileToken = self._profile_token req.StreamSetup = { "Stream": "RTP-Unicast", "Transport": { "Protocol": self._rtsp_transport }, } stream_uri = await self._media_service.GetStreamUri(req) uri_no_auth = stream_uri.Uri self._input_uri_for_log = uri_no_auth.replace( "rtsp://", "rtsp://<user>:<password>@", 1) self._input_uri = uri_no_auth.replace( "rtsp://", "rtsp://%s:%s@" % (self._username, self._password), 1) _LOGGER.debug( "ONVIF Camera Using the following URL for %s: %s", self._name, self._input_uri_for_log, ) break except ClientConnectionError as err: if i == 0: _LOGGER.info( "GetStreamUri on '%s'. Error: %s. Trying a workaround against known issue(#26781)", self._name, err, ) self._media_service = self._camera.create_media_service() pass else: _LOGGER.error("Couldn't setup camera '%s'. Error: %s", self._name, err) return (None, None) async def async_obtain_ptz_service(self): """Set up PTZ service if available.""" _LOGGER.debug("Setting up the ONVIF PTZ service") if self._camera.get_service("ptz") is None: _LOGGER.debug("PTZ is not available") return None else: _LOGGER.debug("Completed set up of the ONVIF camera component") return self._camera.create_ptz_service() async def async_perform_ptz_move(self, pan, tilt, zoom, distance, speed, move_mode, continuous_timeout, timeout_compliance): """Perform legacy PTZ actions on the camera + new move_modes""" pan_val = (distance if pan == DIR_RIGHT else -distance if pan == DIR_LEFT else 0) tilt_val = (distance if tilt == DIR_UP else -distance if tilt == DIR_DOWN else 0) zoom_val = (distance if zoom == ZOOM_IN else -distance if zoom == ZOOM_OUT else 0) speed_val = speed await self.async_perform_ptz_advanced_move( (pan_val, tilt_val, zoom_val), (speed_val, speed_val, speed_val), move_mode, continuous_timeout, timeout_compliance) async def async_perform_ptz_advanced_move(self, ptz_vector, speed_vector, move_mode, continuous_timeout, timeout_compliance): """Perform a PTZ action on the camera.""" if self._ptz_service is None: _LOGGER.warning( "PTZ Move actions are not supported on camera '%s'", self._name) return if self._ptz_service: pan_val = ptz_vector[0] tilt_val = ptz_vector[1] zoom_val = ptz_vector[2] _LOGGER.debug( "Calling %s PTZ Move on camera '%s'| Pan = %4.2f | Tilt = %4.2f | Zoom = %4.2f | Speed = %s | Timeout = %1.1f", move_mode, self._name, ptz_vector[0], ptz_vector[1], ptz_vector[2], speed_vector, continuous_timeout) try: req = self._ptz_service.create_type(move_mode) req.ProfileToken = self._profile_token if move_mode == CONTINUOUS_MOVE: req.Velocity = { "PanTilt": { "x": pan_val, "y": tilt_val }, "Zoom": { "x": zoom_val }, } if continuous_timeout != 0: req.Timeout = dt.timedelta( 0, 0, continuous_timeout * 1000000) await self._ptz_service.ContinuousMove(req) if continuous_timeout != 0 and not timeout_compliance: await asyncio.sleep(continuous_timeout) req = self._ptz_service.create_type("Stop") req.ProfileToken = self._profile_token await self._ptz_service.Stop(req) elif move_mode == RELATIVE_MOVE: req.Translation = { "PanTilt": { "x": pan_val, "y": tilt_val }, "Zoom": { "x": zoom_val }, } req.Speed = { "PanTilt": { "x": speed_vector[0], "y": speed_vector[1] }, "Zoom": { "x": speed_vector[2] }, } await self._ptz_service.RelativeMove(req) elif move_mode == ABSOLUTE_MOVE: req.Position = { "PanTilt": { "x": pan_val, "y": tilt_val }, "Zoom": { "x": zoom_val }, } req.Speed = { "PanTilt": { "x": speed_vector[0], "y": speed_vector[1] }, "Zoom": { "x": speed_vector[2] }, } await self._ptz_service.AbsoluteMove(req) except exceptions.ONVIFError as err: if "Bad Request" in err.reason: self._ptz_service = None _LOGGER.debug("Camera '%s' doesn't support PTZ.", self._name) else: _LOGGER.debug("Camera '%s' doesn't support PTZ.", self._name) async def async_perform_ptz_preset(self, preset_operation, preset_name, preset_token): """Perform a PTZ Preset action on the camera.""" if self._ptz_service: if preset_operation in ( GOTO_HOME, SET_HOME, GOTO_PRESET, SET_PRESET, GET_PRESETS, ): try: _LOGGER.debug( "Calling PTZ preset| Operation = %s | PresetName = %s | PresetToken = %s", preset_operation, preset_name, preset_token, ) req = self._ptz_service.create_type(preset_operation) req.ProfileToken = self._profile_token if preset_operation == GOTO_PRESET: preset_token = next( (preset["token"] for preset in self._ptz_presets if preset["Name"] == preset_name), None, ) print(self._ptz_presets) _LOGGER.debug( "PresetToken from PresetName | PresetName = %s | PresetToken = %s", preset_name, preset_token, ) print( "PresetToken from PresetName | PresetName = %s | PresetToken = %s" % (preset_name, preset_token)) req.PresetToken = "%s" % preset_token req.Speed = { "PanTilt": { "x": 1.0, "y": 1.0 }, "Zoom": { "x": 1.0 }, } await self._ptz_service.GotoPreset(req) if preset_operation == SET_PRESET: req.PresetToken = preset_token req.PresetName = preset_name await self._ptz_service.SetPreset(req) if preset_operation == GET_PRESETS: __presets = await self._ptz_service.GetPresets(req) presets = [] if __presets is not None: for preset in __presets: presets.append(preset["Name"]) pn.create(self.hass, "\n".join(presets), title="Onvif PTZ Presets") if preset_operation == GOTO_HOME: await self._ptz_service.GotoHomePosition(req) if preset_operation == SET_HOME: await self._ptz_service.SetHomePosition(req) except exceptions.ONVIFError as err: if "Bad Request" in err.reason: _LOGGER.error( "Camera '%s' doesn't support PTZ %s operation.", self._name, preset_operation, ) except Exception as err: _LOGGER.info( "Camera '%s' PTZ %s operation failed with that reason: %s", self._name, preset_operation, err, ) else: _LOGGER.debug("PTZ %s operation is not implemented", preset_operation) else: self._ptz_service = None _LOGGER.debug("Camera '%s' doesn't support PTZ.", self._name) async def async_perform_reboot(self): """Perform a SystemReboot action on the camera.""" try: _LOGGER.debug("Calling SystemReboot") ret = await self._camera.devicemgmt.SystemReboot() _LOGGER.debug("Camera '%s' Reboot command returned '%s'", self._name, ret) except exceptions.ONVIFError as err: _LOGGER.error( "Couldn't reboot the camera '%s', please verify " "that the camera supports the command. Error: %s", self._name, err, ) async def async_added_to_hass(self): """Handle entity addition to hass.""" _LOGGER.debug("Camera '%s' added to hass", self._name) if ONVIF_DATA not in self.hass.data: self.hass.data[ONVIF_DATA] = {} self.hass.data[ONVIF_DATA][ENTITIES] = [] self.hass.data[ONVIF_DATA][ENTITIES].append(self) async def async_camera_image(self): """Return a still image response from the camera.""" _LOGGER.debug("Retrieving image from camera '%s'", self._name) ffmpeg = ImageFrame(self.hass.data[DATA_FFMPEG].binary, loop=self.hass.loop) image = await asyncio.shield( ffmpeg.get_image( self._input_uri, output_format=IMAGE_JPEG, extra_cmd=self._ffmpeg_arguments, )) return image async def handle_async_mjpeg_stream(self, request): """Generate an HTTP MJPEG stream from the camera.""" _LOGGER.debug("Handling mjpeg stream from camera '%s'", self._name) ffmpeg_manager = self.hass.data[DATA_FFMPEG] stream = CameraMjpeg(ffmpeg_manager.binary, loop=self.hass.loop) await stream.open_camera(self._input_uri, extra_cmd=self._ffmpeg_arguments) try: stream_reader = await stream.get_reader() return await async_aiohttp_proxy_stream( self.hass, request, stream_reader, ffmpeg_manager.ffmpeg_stream_content_type, ) finally: await stream.close() @property def supported_features(self): """Return supported features.""" if self._input_uri: return SUPPORT_STREAM return 0 async def stream_source(self): """Return the stream source.""" return self._input_uri @property def name(self): """Return the name of this camera.""" return self._name
class ONVIFHassCamera(Camera): """An implementation of an ONVIF camera.""" def __init__(self, hass, config): """Initialize an ONVIF camera.""" super().__init__() _LOGGER.debug("Importing dependencies") _LOGGER.debug("Setting up the ONVIF camera component") self._username = config.get(CONF_USERNAME) self._password = config.get(CONF_PASSWORD) self._host = config.get(CONF_HOST) self._port = config.get(CONF_PORT) self._name = config.get(CONF_NAME) self._ffmpeg_arguments = config.get(CONF_EXTRA_ARGUMENTS) self._profile_index = config.get(CONF_PROFILE) self._ptz_service = None self._input = None _LOGGER.debug( "Setting up the ONVIF camera device @ '%s:%s'", self._host, self._port ) self._camera = ONVIFCamera( self._host, self._port, self._username, self._password, "{}/wsdl/".format(os.path.dirname(onvif.__file__)), ) async def async_initialize(self): """ Initialize the camera. Initializes the camera by obtaining the input uri and connecting to the camera. Also retrieves the ONVIF profiles. """ try: _LOGGER.debug("Updating service addresses") await self._camera.update_xaddrs() await self.async_check_date_and_time() await self.async_obtain_input_uri() await self.setup_ptz() except ClientConnectionError as err: _LOGGER.warning( "Couldn't connect to camera '%s', but will retry later. Error: %s", self._name, err, ) raise PlatformNotReady except Fault as err: _LOGGER.error( "Couldn't connect to camera '%s', please verify " "that the credentials are correct. Error: %s", self._name, err, ) async def async_check_date_and_time(self): """Warns if camera and system date not synced.""" _LOGGER.debug("Setting up the ONVIF device management service") devicemgmt = self._camera.create_devicemgmt_service() _LOGGER.debug("Retrieving current camera date/time") try: system_date = dt_util.utcnow() device_time = await devicemgmt.GetSystemDateAndTime() if not device_time: _LOGGER.debug( """Couldn't get camera '%s' date/time. GetSystemDateAndTime() return null/empty""", self._name, ) return if device_time.UTCDateTime: tzone = dt_util.UTC cdate = device_time.UTCDateTime else: tzone = ( dt_util.get_time_zone(device_time.TimeZone) or dt_util.DEFAULT_TIME_ZONE ) cdate = device_time.LocalDateTime if cdate is None: _LOGGER.warning("Could not retrieve date/time on this camera") else: cam_date = dt.datetime( cdate.Date.Year, cdate.Date.Month, cdate.Date.Day, cdate.Time.Hour, cdate.Time.Minute, cdate.Time.Second, 0, tzone, ) cam_date_utc = cam_date.astimezone(dt_util.UTC) _LOGGER.debug("TimeZone for date/time: %s", tzone) _LOGGER.debug("Camera date/time: %s", cam_date) _LOGGER.debug("Camera date/time in UTC: %s", cam_date_utc) _LOGGER.debug("System date/time: %s", system_date) dt_diff = cam_date - system_date dt_diff_seconds = dt_diff.total_seconds() if dt_diff_seconds > 5: _LOGGER.warning( "The date/time on the camera (UTC) is '%s', " "which is different from the system '%s', " "this could lead to authentication issues", cam_date_utc, system_date, ) except ServerDisconnectedError as err: _LOGGER.warning( "Couldn't get camera '%s' date/time. Error: %s", self._name, err ) async def async_obtain_input_uri(self): """Set the input uri for the camera.""" _LOGGER.debug( "Connecting with ONVIF Camera: %s on port %s", self._host, self._port ) try: _LOGGER.debug("Retrieving profiles") media_service = self._camera.create_media_service() profiles = await media_service.GetProfiles() _LOGGER.debug("Retrieved '%d' profiles", len(profiles)) if self._profile_index >= len(profiles): _LOGGER.warning( "ONVIF Camera '%s' doesn't provide profile %d." " Using the last profile.", self._name, self._profile_index, ) self._profile_index = -1 _LOGGER.debug("Using profile index '%d'", self._profile_index) _LOGGER.debug("Retrieving stream uri") # Fix Onvif setup error on Goke GK7102 based IP camera # where we need to recreate media_service #26781 media_service = self._camera.create_media_service() req = media_service.create_type("GetStreamUri") req.ProfileToken = profiles[self._profile_index].token req.StreamSetup = { "Stream": "RTP-Unicast", "Transport": {"Protocol": "RTSP"}, } stream_uri = await media_service.GetStreamUri(req) uri_no_auth = stream_uri.Uri uri_for_log = uri_no_auth.replace("rtsp://", "rtsp://<user>:<password>@", 1) self._input = uri_no_auth.replace( "rtsp://", f"rtsp://{self._username}:{self._password}@", 1 ) _LOGGER.debug( "ONVIF Camera Using the following URL for %s: %s", self._name, uri_for_log, ) except exceptions.ONVIFError as err: _LOGGER.error("Couldn't setup camera '%s'. Error: %s", self._name, err) async def setup_ptz(self): """Set up PTZ if available.""" _LOGGER.debug("Setting up the ONVIF PTZ service") if self._camera.get_service("ptz") is None: _LOGGER.debug("PTZ is not available") else: self._ptz_service = self._camera.create_ptz_service() _LOGGER.debug("Completed set up of the ONVIF camera component") async def async_perform_ptz(self, pan, tilt, zoom): """Perform a PTZ action on the camera.""" from onvif import exceptions if self._ptz_service is None: _LOGGER.warning("PTZ actions are not supported on camera '%s'", self._name) return media_service = self._camera.create_media_service() profiles = await media_service.GetProfiles() req = self._ptz_service.create_type("ContinuousMove") req.ProfileToken = profiles[self._profile_index].token if self._ptz_service: pan_val = 1 if pan == DIR_RIGHT else -1 if pan == DIR_LEFT else 0 tilt_val = 1 if tilt == DIR_UP else -1 if tilt == DIR_DOWN else 0 zoom_val = 1 if zoom == ZOOM_IN else -1 if zoom == ZOOM_OUT else 0 req.Velocity = { "PanTilt": {"x": pan_val, "y": tilt_val}, "Zoom": {"x": zoom_val}, } try: _LOGGER.debug( "Calling PTZ | Pan = %d | Tilt = %d | Zoom = %d", pan_val, tilt_val, zoom_val, ) await self._ptz_service.ContinuousMove(req) except exceptions.ONVIFError as err: if "Bad Request" in err.reason: self._ptz_service = None _LOGGER.debug("Camera '%s' doesn't support PTZ.", self._name) else: _LOGGER.debug("Camera '%s' doesn't support PTZ.", self._name) async def async_added_to_hass(self): """Handle entity addition to hass.""" _LOGGER.debug("Camera '%s' added to hass", self._name) if ONVIF_DATA not in self.hass.data: self.hass.data[ONVIF_DATA] = {} self.hass.data[ONVIF_DATA][ENTITIES] = [] self.hass.data[ONVIF_DATA][ENTITIES].append(self) async def async_camera_image(self): """Return a still image response from the camera.""" _LOGGER.debug("Retrieving image from camera '%s'", self._name) ffmpeg = ImageFrame(self.hass.data[DATA_FFMPEG].binary, loop=self.hass.loop) image = await asyncio.shield( ffmpeg.get_image( self._input, output_format=IMAGE_JPEG, extra_cmd=self._ffmpeg_arguments ) ) return image async def handle_async_mjpeg_stream(self, request): """Generate an HTTP MJPEG stream from the camera.""" _LOGGER.debug("Handling mjpeg stream from camera '%s'", self._name) ffmpeg_manager = self.hass.data[DATA_FFMPEG] stream = CameraMjpeg(ffmpeg_manager.binary, loop=self.hass.loop) await stream.open_camera(self._input, extra_cmd=self._ffmpeg_arguments) try: stream_reader = await stream.get_reader() return await async_aiohttp_proxy_stream( self.hass, request, stream_reader, ffmpeg_manager.ffmpeg_stream_content_type, ) finally: await stream.close() @property def supported_features(self): """Return supported features.""" if self._input: return SUPPORT_STREAM return 0 async def stream_source(self): """Return the stream source.""" return self._input @property def name(self): """Return the name of this camera.""" return self._name
class ONVIFHassCamera(Camera): """An implementation of an ONVIF camera.""" def __init__(self, hass, config): """Initialize an ONVIF camera.""" super().__init__() _LOGGER.debug("Importing dependencies") import onvif from onvif import ONVIFCamera _LOGGER.debug("Setting up the ONVIF camera component") self._username = config.get(CONF_USERNAME) self._password = config.get(CONF_PASSWORD) self._host = config.get(CONF_HOST) self._port = config.get(CONF_PORT) self._name = config.get(CONF_NAME) self._ffmpeg_arguments = config.get(CONF_EXTRA_ARGUMENTS) self._profile_index = config.get(CONF_PROFILE) self._ptz_service = None self._input = None _LOGGER.debug("Setting up the ONVIF camera device @ '%s:%s'", self._host, self._port) self._camera = ONVIFCamera( self._host, self._port, self._username, self._password, '{}/wsdl/'.format(os.path.dirname(onvif.__file__))) async def async_initialize(self): """ Initialize the camera. Initializes the camera by obtaining the input uri and connecting to the camera. Also retrieves the ONVIF profiles. """ from aiohttp.client_exceptions import ClientConnectorError from homeassistant.exceptions import PlatformNotReady from zeep.exceptions import Fault import homeassistant.util.dt as dt_util try: _LOGGER.debug("Updating service addresses") await self._camera.update_xaddrs() _LOGGER.debug("Setting up the ONVIF device management service") devicemgmt = self._camera.create_devicemgmt_service() _LOGGER.debug("Retrieving current camera date/time") system_date = dt_util.utcnow() device_time = await devicemgmt.GetSystemDateAndTime() cdate = device_time.UTCDateTime cam_date = dt.datetime(cdate.Date.Year, cdate.Date.Month, cdate.Date.Day, cdate.Time.Hour, cdate.Time.Minute, cdate.Time.Second, 0, dt_util.UTC) _LOGGER.debug("Camera date/time: %s", cam_date) _LOGGER.debug("System date/time: %s", system_date) dt_diff = cam_date - system_date dt_diff_seconds = dt_diff.total_seconds() if dt_diff_seconds > 5: _LOGGER.warning( "The date/time on the camera is '%s', " "which is different from the system '%s', " "this could lead to authentication issues", cam_date, system_date) _LOGGER.debug("Obtaining input uri") await self.async_obtain_input_uri() _LOGGER.debug("Setting up the ONVIF PTZ service") if self._camera.get_service('ptz', create=False) is None: _LOGGER.warning("PTZ is not available on this camera") else: self._ptz_service = self._camera.create_ptz_service() _LOGGER.debug("Completed set up of the ONVIF camera component") except ClientConnectorError as err: _LOGGER.warning( "Couldn't connect to camera '%s', but will " "retry later. Error: %s", self._name, err) raise PlatformNotReady except Fault as err: _LOGGER.error( "Couldn't connect to camera '%s', please verify " "that the credentials are correct. Error: %s", self._name, err) return async def async_obtain_input_uri(self): """Set the input uri for the camera.""" from onvif import exceptions _LOGGER.debug("Connecting with ONVIF Camera: %s on port %s", self._host, self._port) try: _LOGGER.debug("Retrieving profiles") media_service = self._camera.create_media_service() profiles = await media_service.GetProfiles() _LOGGER.debug("Retrieved '%d' profiles", len(profiles)) if self._profile_index >= len(profiles): _LOGGER.warning( "ONVIF Camera '%s' doesn't provide profile %d." " Using the last profile.", self._name, self._profile_index) self._profile_index = -1 _LOGGER.debug("Using profile index '%d'", self._profile_index) _LOGGER.debug("Retrieving stream uri") req = media_service.create_type('GetStreamUri') req.ProfileToken = profiles[self._profile_index].token req.StreamSetup = { 'Stream': 'RTP-Unicast', 'Transport': { 'Protocol': 'RTSP' } } stream_uri = await media_service.GetStreamUri(req) uri_no_auth = stream_uri.Uri uri_for_log = uri_no_auth.replace('rtsp://', 'rtsp://<user>:<password>@', 1) self._input = uri_no_auth.replace( 'rtsp://', 'rtsp://{}:{}@'.format(self._username, self._password), 1) _LOGGER.debug("ONVIF Camera Using the following URL for %s: %s", self._name, uri_for_log) except exceptions.ONVIFError as err: _LOGGER.error("Couldn't setup camera '%s'. Error: %s", self._name, err) return async def async_perform_ptz(self, pan, tilt, zoom): """Perform a PTZ action on the camera.""" from onvif import exceptions if self._ptz_service is None: _LOGGER.warning("PTZ actions are not supported on camera '%s'", self._name) return if self._ptz_service: pan_val = 1 if pan == DIR_RIGHT else -1 if pan == DIR_LEFT else 0 tilt_val = 1 if tilt == DIR_UP else -1 if tilt == DIR_DOWN else 0 zoom_val = 1 if zoom == ZOOM_IN else -1 if zoom == ZOOM_OUT else 0 req = { "Velocity": { "PanTilt": { "_x": pan_val, "_y": tilt_val }, "Zoom": { "_x": zoom_val } } } try: _LOGGER.debug("Calling PTZ | Pan = %d | Tilt = %d | Zoom = %d", pan_val, tilt_val, zoom_val) await self._ptz_service.ContinuousMove(req) except exceptions.ONVIFError as err: if "Bad Request" in err.reason: self._ptz_service = None _LOGGER.debug("Camera '%s' doesn't support PTZ.", self._name) else: _LOGGER.debug("Camera '%s' doesn't support PTZ.", self._name) async def async_added_to_hass(self): """Handle entity addition to hass.""" _LOGGER.debug("Camera '%s' added to hass", self._name) if ONVIF_DATA not in self.hass.data: self.hass.data[ONVIF_DATA] = {} self.hass.data[ONVIF_DATA][ENTITIES] = [] self.hass.data[ONVIF_DATA][ENTITIES].append(self) async def async_camera_image(self): """Return a still image response from the camera.""" from haffmpeg.tools import ImageFrame, IMAGE_JPEG _LOGGER.debug("Retrieving image from camera '%s'", self._name) ffmpeg = ImageFrame(self.hass.data[DATA_FFMPEG].binary, loop=self.hass.loop) image = await asyncio.shield(ffmpeg.get_image( self._input, output_format=IMAGE_JPEG, extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop) return image async def handle_async_mjpeg_stream(self, request): """Generate an HTTP MJPEG stream from the camera.""" from haffmpeg.camera import CameraMjpeg _LOGGER.debug("Handling mjpeg stream from camera '%s'", self._name) ffmpeg_manager = self.hass.data[DATA_FFMPEG] stream = CameraMjpeg(ffmpeg_manager.binary, loop=self.hass.loop) await stream.open_camera(self._input, extra_cmd=self._ffmpeg_arguments) try: stream_reader = await stream.get_reader() return await async_aiohttp_proxy_stream( self.hass, request, stream_reader, ffmpeg_manager.ffmpeg_stream_content_type) finally: await stream.close() @property def supported_features(self): """Return supported features.""" if self._input: return SUPPORT_STREAM return 0 @property def stream_source(self): """Return the stream source.""" return self._input @property def name(self): """Return the name of this camera.""" return self._name
class ONVIFHassCamera(Camera): """An implementation of an ONVIF camera.""" def __init__(self, hass, config): """Initialize an ONVIF camera.""" super().__init__() _LOGGER.debug("Importing dependencies") _LOGGER.debug("Setting up the ONVIF camera component") self._username = config.get(CONF_USERNAME) self._password = config.get(CONF_PASSWORD) self._host = config.get(CONF_HOST) self._port = config.get(CONF_PORT) self._name = config.get(CONF_NAME) self._ffmpeg_arguments = config.get(CONF_EXTRA_ARGUMENTS) self._profile_index = config.get(CONF_PROFILE) self._ptz_service = None self._input = None self._snapshot = None self.stream_options[CONF_RTSP_TRANSPORT] = config.get( CONF_RTSP_TRANSPORT) self._mac = None _LOGGER.debug("Setting up the ONVIF camera device @ '%s:%s'", self._host, self._port) session = async_get_clientsession(hass) transport = AsyncTransport(None, session=session) self._camera = ONVIFCamera( self._host, self._port, self._username, self._password, "{}/wsdl/".format(os.path.dirname(onvif.__file__)), transport=transport, ) async def async_initialize(self): """ Initialize the camera. Initializes the camera by obtaining the input uri and connecting to the camera. Also retrieves the ONVIF profiles. """ try: _LOGGER.debug("Updating service addresses") await self._camera.update_xaddrs() await self.async_obtain_mac_address() await self.async_check_date_and_time() await self.async_obtain_input_uri() await self.async_obtain_snapshot_uri() self.setup_ptz() except ClientConnectionError as err: _LOGGER.warning( "Couldn't connect to camera '%s', but will retry later. Error: %s", self._name, err, ) raise PlatformNotReady except Fault as err: _LOGGER.error( "Couldn't connect to camera '%s', please verify " "that the credentials are correct. Error: %s", self._name, err, ) async def async_obtain_mac_address(self): """Obtain the MAC address of the camera to use as the unique ID.""" devicemgmt = self._camera.create_devicemgmt_service() network_interfaces = await devicemgmt.GetNetworkInterfaces() for interface in network_interfaces: if interface.Enabled: self._mac = interface.Info.HwAddress async def async_check_date_and_time(self): """Warns if camera and system date not synced.""" _LOGGER.debug("Setting up the ONVIF device management service") devicemgmt = self._camera.create_devicemgmt_service() _LOGGER.debug("Retrieving current camera date/time") try: system_date = dt_util.utcnow() device_time = await devicemgmt.GetSystemDateAndTime() if not device_time: _LOGGER.debug( """Couldn't get camera '%s' date/time. GetSystemDateAndTime() return null/empty""", self._name, ) return if device_time.UTCDateTime: tzone = dt_util.UTC cdate = device_time.UTCDateTime else: tzone = (dt_util.get_time_zone(device_time.TimeZone) or dt_util.DEFAULT_TIME_ZONE) cdate = device_time.LocalDateTime if cdate is None: _LOGGER.warning("Could not retrieve date/time on this camera") else: cam_date = dt.datetime( cdate.Date.Year, cdate.Date.Month, cdate.Date.Day, cdate.Time.Hour, cdate.Time.Minute, cdate.Time.Second, 0, tzone, ) cam_date_utc = cam_date.astimezone(dt_util.UTC) _LOGGER.debug("TimeZone for date/time: %s", tzone) _LOGGER.debug("Camera date/time: %s", cam_date) _LOGGER.debug("Camera date/time in UTC: %s", cam_date_utc) _LOGGER.debug("System date/time: %s", system_date) dt_diff = cam_date - system_date dt_diff_seconds = dt_diff.total_seconds() if dt_diff_seconds > 5: _LOGGER.warning( "The date/time on the camera (UTC) is '%s', " "which is different from the system '%s', " "this could lead to authentication issues", cam_date_utc, system_date, ) except ServerDisconnectedError as err: _LOGGER.warning("Couldn't get camera '%s' date/time. Error: %s", self._name, err) async def async_obtain_profile_token(self): """Obtain profile token to use with requests.""" try: media_service = self._camera.get_service("media") profiles = await media_service.GetProfiles() _LOGGER.debug("Retrieved '%d' profiles", len(profiles)) if self._profile_index >= len(profiles): _LOGGER.warning( "ONVIF Camera '%s' doesn't provide profile %d." " Using the last profile.", self._name, self._profile_index, ) self._profile_index = -1 _LOGGER.debug("Using profile index '%d'", self._profile_index) return profiles[self._profile_index].token except exceptions.ONVIFError as err: _LOGGER.error( "Couldn't retrieve profile token of camera '%s'. Error: %s", self._name, err, ) return None async def async_obtain_input_uri(self): """Set the input uri for the camera.""" _LOGGER.debug("Connecting with ONVIF Camera: %s on port %s", self._host, self._port) try: _LOGGER.debug("Retrieving profiles") media_service = self._camera.create_media_service() profiles = await media_service.GetProfiles() _LOGGER.debug("Retrieved '%d' profiles", len(profiles)) if self._profile_index >= len(profiles): _LOGGER.warning( "ONVIF Camera '%s' doesn't provide profile %d." " Using the last profile.", self._name, self._profile_index, ) self._profile_index = -1 _LOGGER.debug("Using profile index '%d'", self._profile_index) _LOGGER.debug("Retrieving stream uri") # Fix Onvif setup error on Goke GK7102 based IP camera # where we need to recreate media_service #26781 media_service = self._camera.create_media_service() req = media_service.create_type("GetStreamUri") req.ProfileToken = profiles[self._profile_index].token req.StreamSetup = { "Stream": "RTP-Unicast", "Transport": { "Protocol": "RTSP" }, } stream_uri = await media_service.GetStreamUri(req) uri_no_auth = stream_uri.Uri uri_for_log = uri_no_auth.replace("rtsp://", "rtsp://<user>:<password>@", 1) self._input = uri_no_auth.replace( "rtsp://", f"rtsp://{self._username}:{self._password}@", 1) _LOGGER.debug( "ONVIF Camera Using the following URL for %s: %s", self._name, uri_for_log, ) except exceptions.ONVIFError as err: _LOGGER.error("Couldn't setup camera '%s'. Error: %s", self._name, err) async def async_obtain_snapshot_uri(self): """Set the snapshot uri for the camera.""" _LOGGER.debug("Connecting with ONVIF Camera: %s on port %s", self._host, self._port) try: _LOGGER.debug("Retrieving profiles") media_service = self._camera.create_media_service() profiles = await media_service.GetProfiles() _LOGGER.debug("Retrieved '%d' profiles", len(profiles)) if self._profile_index >= len(profiles): _LOGGER.warning( "ONVIF Camera '%s' doesn't provide profile %d." " Using the last profile.", self._name, self._profile_index, ) self._profile_index = -1 _LOGGER.debug("Using profile index '%d'", self._profile_index) _LOGGER.debug("Retrieving snapshot uri") # Fix Onvif setup error on Goke GK7102 based IP camera # where we need to recreate media_service #26781 media_service = self._camera.create_media_service() req = media_service.create_type("GetSnapshotUri") req.ProfileToken = profiles[self._profile_index].token try: snapshot_uri = await media_service.GetSnapshotUri(req) self._snapshot = snapshot_uri.Uri except ServerDisconnectedError as err: _LOGGER.debug("Camera does not support GetSnapshotUri: %s", err) _LOGGER.debug( "ONVIF Camera Using the following URL for %s snapshot: %s", self._name, self._snapshot, ) except exceptions.ONVIFError as err: _LOGGER.error("Couldn't setup camera '%s'. Error: %s", self._name, err) def setup_ptz(self): """Set up PTZ if available.""" _LOGGER.debug("Setting up the ONVIF PTZ service") if self._camera.get_service("ptz", create=False) is None: _LOGGER.debug("PTZ is not available") else: self._ptz_service = self._camera.create_ptz_service() _LOGGER.debug("Completed set up of the ONVIF camera component") async def async_perform_ptz(self, pan, tilt, zoom, distance, speed, move_mode, continuous_duration): """Perform a PTZ action on the camera.""" if self._ptz_service is None: _LOGGER.warning("PTZ actions are not supported on camera '%s'", self._name) return if self._ptz_service: pan_val = distance * PAN_FACTOR.get(pan, 0) tilt_val = distance * TILT_FACTOR.get(tilt, 0) zoom_val = distance * ZOOM_FACTOR.get(zoom, 0) speed_val = speed _LOGGER.debug( "Calling %s PTZ | Pan = %4.2f | Tilt = %4.2f | Zoom = %4.2f | Speed = %4.2f", move_mode, pan_val, tilt_val, zoom_val, speed_val, ) try: req = self._ptz_service.create_type(move_mode) req.ProfileToken = await self.async_obtain_profile_token() if move_mode == CONTINUOUS_MOVE: req.Velocity = { "PanTilt": { "x": pan_val, "y": tilt_val }, "Zoom": { "x": zoom_val }, } await self._ptz_service.ContinuousMove(req) await asyncio.sleep(continuous_duration) req = self._ptz_service.create_type("Stop") req.ProfileToken = await self.async_obtain_profile_token() await self._ptz_service.Stop( {"ProfileToken": req.ProfileToken}) elif move_mode == RELATIVE_MOVE: req.Translation = { "PanTilt": { "x": pan_val, "y": tilt_val }, "Zoom": { "x": zoom_val }, } req.Speed = { "PanTilt": { "x": speed_val, "y": speed_val }, "Zoom": { "x": speed_val }, } await self._ptz_service.RelativeMove(req) elif move_mode == ABSOLUTE_MOVE: req.Position = { "PanTilt": { "x": pan_val, "y": tilt_val }, "Zoom": { "x": zoom_val }, } req.Speed = { "PanTilt": { "x": speed_val, "y": speed_val }, "Zoom": { "x": speed_val }, } await self._ptz_service.AbsoluteMove(req) except exceptions.ONVIFError as err: if "Bad Request" in err.reason: self._ptz_service = None _LOGGER.debug("Camera '%s' doesn't support PTZ.", self._name) else: _LOGGER.debug("Camera '%s' doesn't support PTZ.", self._name) async def async_added_to_hass(self): """Handle entity addition to hass.""" _LOGGER.debug("Camera '%s' added to hass", self._name) if ONVIF_DATA not in self.hass.data: self.hass.data[ONVIF_DATA] = {} self.hass.data[ONVIF_DATA][ENTITIES] = [] self.hass.data[ONVIF_DATA][ENTITIES].append(self) async def async_camera_image(self): """Return a still image response from the camera.""" _LOGGER.debug("Retrieving image from camera '%s'", self._name) image = None if self._snapshot is not None: auth = None if self._username and self._password: auth = HTTPDigestAuth(self._username, self._password) def fetch(): """Read image from a URL.""" try: response = requests.get(self._snapshot, timeout=5, auth=auth) if response.status_code < 300: return response.content except requests.exceptions.RequestException as error: _LOGGER.error( "Fetch snapshot image failed from %s, falling back to FFmpeg; %s", self._name, error, ) return None image = await self.hass.async_add_job(fetch) if image is None: # Don't keep trying the snapshot URL self._snapshot = None ffmpeg = ImageFrame(self.hass.data[DATA_FFMPEG].binary, loop=self.hass.loop) image = await asyncio.shield( ffmpeg.get_image( self._input, output_format=IMAGE_JPEG, extra_cmd=self._ffmpeg_arguments, )) return image async def handle_async_mjpeg_stream(self, request): """Generate an HTTP MJPEG stream from the camera.""" _LOGGER.debug("Handling mjpeg stream from camera '%s'", self._name) ffmpeg_manager = self.hass.data[DATA_FFMPEG] stream = CameraMjpeg(ffmpeg_manager.binary, loop=self.hass.loop) await stream.open_camera(self._input, extra_cmd=self._ffmpeg_arguments) try: stream_reader = await stream.get_reader() return await async_aiohttp_proxy_stream( self.hass, request, stream_reader, ffmpeg_manager.ffmpeg_stream_content_type, ) finally: await stream.close() @property def supported_features(self): """Return supported features.""" if self._input: return SUPPORT_STREAM return 0 async def stream_source(self): """Return the stream source.""" return self._input @property def name(self): """Return the name of this camera.""" return self._name @property def unique_id(self) -> Optional[str]: """Return a unique ID.""" if self._profile_index: return f"{self._mac}_{self._profile_index}" return self._mac
class ONVIFHassCamera(Camera): """An implementation of an ONVIF camera.""" def __init__(self, hass, config): """Initialize an ONVIF camera.""" super().__init__() _LOGGER.debug("Importing dependencies") import onvif from onvif import ONVIFCamera _LOGGER.debug("Setting up the ONVIF camera component") self._username = config.get(CONF_USERNAME) self._password = config.get(CONF_PASSWORD) self._host = config.get(CONF_HOST) self._port = config.get(CONF_PORT) self._name = config.get(CONF_NAME) self._ffmpeg_arguments = config.get(CONF_EXTRA_ARGUMENTS) self._profile_index = config.get(CONF_PROFILE) self._ptz_service = None self._input = None _LOGGER.debug("Setting up the ONVIF camera device @ '%s:%s'", self._host, self._port) self._camera = ONVIFCamera(self._host, self._port, self._username, self._password, '{}/wsdl/' .format(os.path.dirname(onvif.__file__))) async def async_initialize(self): """ Initialize the camera. Initializes the camera by obtaining the input uri and connecting to the camera. Also retrieves the ONVIF profiles. """ from aiohttp.client_exceptions import ClientConnectorError from homeassistant.exceptions import PlatformNotReady from zeep.exceptions import Fault import homeassistant.util.dt as dt_util try: _LOGGER.debug("Updating service addresses") await self._camera.update_xaddrs() _LOGGER.debug("Setting up the ONVIF device management service") devicemgmt = self._camera.create_devicemgmt_service() _LOGGER.debug("Retrieving current camera date/time") system_date = dt_util.utcnow() device_time = await devicemgmt.GetSystemDateAndTime() if device_time: cdate = device_time.UTCDateTime cam_date = dt.datetime(cdate.Date.Year, cdate.Date.Month, cdate.Date.Day, cdate.Time.Hour, cdate.Time.Minute, cdate.Time.Second, 0, dt_util.UTC) _LOGGER.debug("Camera date/time: %s", cam_date) _LOGGER.debug("System date/time: %s", system_date) dt_diff = cam_date - system_date dt_diff_seconds = dt_diff.total_seconds() if dt_diff_seconds > 5: _LOGGER.warning("The date/time on the camera is '%s', " "which is different from the system '%s', " "this could lead to authentication issues", cam_date, system_date) _LOGGER.debug("Obtaining input uri") await self.async_obtain_input_uri() _LOGGER.debug("Setting up the ONVIF PTZ service") if self._camera.get_service('ptz', create=False) is None: _LOGGER.warning("PTZ is not available on this camera") else: self._ptz_service = self._camera.create_ptz_service() _LOGGER.debug("Completed set up of the ONVIF camera component") except ClientConnectorError as err: _LOGGER.warning("Couldn't connect to camera '%s', but will " "retry later. Error: %s", self._name, err) raise PlatformNotReady except Fault as err: _LOGGER.error("Couldn't connect to camera '%s', please verify " "that the credentials are correct. Error: %s", self._name, err) return async def async_obtain_input_uri(self): """Set the input uri for the camera.""" from onvif import exceptions _LOGGER.debug("Connecting with ONVIF Camera: %s on port %s", self._host, self._port) try: _LOGGER.debug("Retrieving profiles") media_service = self._camera.create_media_service() profiles = await media_service.GetProfiles() _LOGGER.debug("Retrieved '%d' profiles", len(profiles)) if self._profile_index >= len(profiles): _LOGGER.warning("ONVIF Camera '%s' doesn't provide profile %d." " Using the last profile.", self._name, self._profile_index) self._profile_index = -1 _LOGGER.debug("Using profile index '%d'", self._profile_index) _LOGGER.debug("Retrieving stream uri") req = media_service.create_type('GetStreamUri') req.ProfileToken = profiles[self._profile_index].token req.StreamSetup = {'Stream': 'RTP-Unicast', 'Transport': {'Protocol': 'RTSP'}} stream_uri = await media_service.GetStreamUri(req) uri_no_auth = stream_uri.Uri uri_for_log = uri_no_auth.replace( 'rtsp://', 'rtsp://<user>:<password>@', 1) self._input = uri_no_auth.replace( 'rtsp://', 'rtsp://{}:{}@'.format(self._username, self._password), 1) _LOGGER.debug( "ONVIF Camera Using the following URL for %s: %s", self._name, uri_for_log) except exceptions.ONVIFError as err: _LOGGER.error("Couldn't setup camera '%s'. Error: %s", self._name, err) return async def async_perform_ptz(self, pan, tilt, zoom): """Perform a PTZ action on the camera.""" from onvif import exceptions if self._ptz_service is None: _LOGGER.warning("PTZ actions are not supported on camera '%s'", self._name) return if self._ptz_service: pan_val = 1 if pan == DIR_RIGHT else -1 if pan == DIR_LEFT else 0 tilt_val = 1 if tilt == DIR_UP else -1 if tilt == DIR_DOWN else 0 zoom_val = 1 if zoom == ZOOM_IN else -1 if zoom == ZOOM_OUT else 0 req = {"Velocity": { "PanTilt": {"_x": pan_val, "_y": tilt_val}, "Zoom": {"_x": zoom_val}}} try: _LOGGER.debug( "Calling PTZ | Pan = %d | Tilt = %d | Zoom = %d", pan_val, tilt_val, zoom_val) await self._ptz_service.ContinuousMove(req) except exceptions.ONVIFError as err: if "Bad Request" in err.reason: self._ptz_service = None _LOGGER.debug("Camera '%s' doesn't support PTZ.", self._name) else: _LOGGER.debug("Camera '%s' doesn't support PTZ.", self._name) async def async_added_to_hass(self): """Handle entity addition to hass.""" _LOGGER.debug("Camera '%s' added to hass", self._name) if ONVIF_DATA not in self.hass.data: self.hass.data[ONVIF_DATA] = {} self.hass.data[ONVIF_DATA][ENTITIES] = [] self.hass.data[ONVIF_DATA][ENTITIES].append(self) async def async_camera_image(self): """Return a still image response from the camera.""" from haffmpeg.tools import ImageFrame, IMAGE_JPEG _LOGGER.debug("Retrieving image from camera '%s'", self._name) ffmpeg = ImageFrame( self.hass.data[DATA_FFMPEG].binary, loop=self.hass.loop) image = await asyncio.shield(ffmpeg.get_image( self._input, output_format=IMAGE_JPEG, extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop) return image async def handle_async_mjpeg_stream(self, request): """Generate an HTTP MJPEG stream from the camera.""" from haffmpeg.camera import CameraMjpeg _LOGGER.debug("Handling mjpeg stream from camera '%s'", self._name) ffmpeg_manager = self.hass.data[DATA_FFMPEG] stream = CameraMjpeg(ffmpeg_manager.binary, loop=self.hass.loop) await stream.open_camera( self._input, extra_cmd=self._ffmpeg_arguments) try: stream_reader = await stream.get_reader() return await async_aiohttp_proxy_stream( self.hass, request, stream_reader, ffmpeg_manager.ffmpeg_stream_content_type) finally: await stream.close() @property def supported_features(self): """Return supported features.""" if self._input: return SUPPORT_STREAM return 0 @property def stream_source(self): """Return the stream source.""" return self._input @property def name(self): """Return the name of this camera.""" return self._name
class ONVIFCLI(Cmd): prompt = 'ONVIF >>> ' client = None cmd_parser = None def setup(self, args): ''' `args`: Instance of `argparse.ArgumentParser` ''' # Create onvif camera client self.client = ONVIFCamera(args.host, args.port, args.user, args.password, args.wsdl, encrypt=args.encrypt) # Create cmd argument parser self.create_cmd_parser() def create_cmd_parser(self): # Create parser to parse CMD, `params` is optional. cmd_parser = ThrowingArgumentParser(prog='ONVIF CMD', usage='CMD service operation [params]') cmd_parser.add_argument('service') cmd_parser.add_argument('operation') cmd_parser.add_argument('params', default='{}', nargs=REMAINDER) self.cmd_parser = cmd_parser def do_cmd(self, line): '''Usage: CMD service operation [parameters]''' try: args = self.cmd_parser.parse_args(line.split()) except ValueError as err: return error(err) # Check if args.service is valid if args.service not in SUPPORTED_SERVICES: return error('No Service: ' + args.service) args.params = ''.join(args.params) # params is optional if not args.params.strip(): args.params = '{}' # params must be a dictionary format string match = re.match(r"^.*?(\{.*\}).*$", args.params) if not match: return error('Invalid params') try: args.params = dict(literal_eval(match.group(1))) except ValueError as err: return error('Invalid params') try: # Get ONVIF service service = self.client.get_service(args.service) # Actually execute the command and get the response response = getattr(service, args.operation)(args.params) except MethodNotFound as err: return error('No Operation: %s' % args.operation) except Exception as err: return error(err) if isinstance(response, (Text, bool)): return success(response) # Try to convert instance to dictionary try: success(ONVIFService.to_dict(response)) except ONVIFError: error({}) def complete_cmd(self, text, line, begidx, endidx): # TODO: complete service operations # service.ws_client.service._ServiceSelector__services[0].ports[0].methods.keys() if not text: completions = SUPPORTED_SERVICES[:] else: completions = [ key for key in SUPPORTED_SERVICES if key.startswith(text) ] return completions def emptyline(self): return '' def do_EOF(self, line): return True