""" This component provides basic support for Amcrest IP cameras. For more details about this component, please refer to the documentation at https://home-assistant.io/components/amcrest/ """ import logging from datetime import timedelta import aiohttp import voluptuous as vol from requests.exceptions import HTTPError, ConnectTimeout from requests.exceptions import ConnectionError as ConnectError from homeassistant.const import ( CONF_NAME, CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD, CONF_SENSORS, CONF_SWITCHES, CONF_SCAN_INTERVAL, HTTP_BASIC_AUTHENTICATION, ) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv REQUIREMENTS = ["amcrest==1.2.3"] DEPENDENCIES = ["ffmpeg"] _LOGGER = logging.getLogger(__name__) CONF_AUTHENTICATION = "authentication" CONF_RESOLUTION = "resolution" CONF_STREAM_SOURCE = "stream_source" CONF_FFMPEG_ARGUMENTS = "ffmpeg_arguments" DEFAULT_NAME = "Amcrest Camera" DEFAULT_PORT = 80 DEFAULT_RESOLUTION = "high" DEFAULT_STREAM_SOURCE = "snapshot" TIMEOUT = 10 DATA_AMCREST = "amcrest" DOMAIN = "amcrest" NOTIFICATION_ID = "amcrest_notification" NOTIFICATION_TITLE = "Amcrest Camera Setup" RESOLUTION_LIST = {"high": 0, "low": 1} SCAN_INTERVAL = timedelta(seconds=10) AUTHENTICATION_LIST = {"basic": "basic"} STREAM_SOURCE_LIST = {"mjpeg": 0, "snapshot": 1, "rtsp": 2} # Sensor types are defined like: Name, units, icon SENSORS = { "motion_detector": ["Motion Detected", None, "mdi:run"], "sdcard": ["SD Used", "%", "mdi:sd"], "ptz_preset": ["PTZ Preset", None, "mdi:camera-iris"], } # Switch types are defined like: Name, icon SWITCHES = { "motion_detection": ["Motion Detection", "mdi:run-fast"], "motion_recording": ["Motion Recording", "mdi:record-rec"], } CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.All( cv.ensure_list, [ vol.Schema( { vol.Required(CONF_HOST): cv.string, vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Optional( CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION ): vol.All(vol.In(AUTHENTICATION_LIST)), vol.Optional( CONF_RESOLUTION, default=DEFAULT_RESOLUTION ): vol.All(vol.In(RESOLUTION_LIST)), vol.Optional( CONF_STREAM_SOURCE, default=DEFAULT_STREAM_SOURCE ): vol.All(vol.In(STREAM_SOURCE_LIST)), vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string, vol.Optional( CONF_SCAN_INTERVAL, default=SCAN_INTERVAL ): cv.time_period, vol.Optional(CONF_SENSORS): vol.All( cv.ensure_list, [vol.In(SENSORS)] ), vol.Optional(CONF_SWITCHES): vol.All( cv.ensure_list, [vol.In(SWITCHES)] ), } ) ], ) }, extra=vol.ALLOW_EXTRA, ) def setup(hass, config): """Set up the Amcrest IP Camera component.""" from amcrest import AmcrestCamera hass.data[DATA_AMCREST] = {} amcrest_cams = config[DOMAIN] for device in amcrest_cams: try: camera = AmcrestCamera( device.get(CONF_HOST), device.get(CONF_PORT), device.get(CONF_USERNAME), device.get(CONF_PASSWORD), ).camera # pylint: disable=pointless-statement camera.current_time except (ConnectError, ConnectTimeout, HTTPError) as ex: _LOGGER.error("Unable to connect to Amcrest camera: %s", str(ex)) hass.components.persistent_notification.create( "Error: {}
" "You will need to restart hass after fixing." "".format(ex), title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID, ) continue ffmpeg_arguments = device.get(CONF_FFMPEG_ARGUMENTS) name = device.get(CONF_NAME) resolution = RESOLUTION_LIST[device.get(CONF_RESOLUTION)] sensors = device.get(CONF_SENSORS) switches = device.get(CONF_SWITCHES) stream_source = STREAM_SOURCE_LIST[device.get(CONF_STREAM_SOURCE)] username = device.get(CONF_USERNAME) password = device.get(CONF_PASSWORD) # currently aiohttp only works with basic authentication # only valid for mjpeg streaming if username is not None and password is not None: if device.get(CONF_AUTHENTICATION) == HTTP_BASIC_AUTHENTICATION: authentication = aiohttp.BasicAuth(username, password) else: authentication = None hass.data[DATA_AMCREST][name] = AmcrestDevice( camera, name, authentication, ffmpeg_arguments, stream_source, resolution ) discovery.load_platform(hass, "camera", DOMAIN, {CONF_NAME: name}, config) if sensors: discovery.load_platform( hass, "sensor", DOMAIN, {CONF_NAME: name, CONF_SENSORS: sensors}, config ) if switches: discovery.load_platform( hass, "switch", DOMAIN, {CONF_NAME: name, CONF_SWITCHES: switches}, config, ) return True class AmcrestDevice: """Representation of a base Amcrest discovery device.""" def __init__( self, camera, name, authentication, ffmpeg_arguments, stream_source, resolution ): """Initialize the entity.""" self.device = camera self.name = name self.authentication = authentication self.ffmpeg_arguments = ffmpeg_arguments self.stream_source = stream_source self.resolution = resolution