From b915cf776bfc90ac87c71e374c2bce4625ba1c52 Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello <tchello.mello@gmail.com> Date: Tue, 17 Jan 2017 01:57:25 -0500 Subject: [PATCH] Introduced Amcrest camera sensors and bump Amcrest module version (#5310) * Introduced Amcrest camera sensors * Makes script/gen_requirements_all.py happy * Bump Amcrest version across all components * - Adjusted scan_interval to 10 seconds - Filtering HTTPError and ConnectTimeout exceptions - Removed @Throttle decorator --- .coveragerc | 1 + homeassistant/components/camera/amcrest.py | 2 +- homeassistant/components/sensor/amcrest.py | 142 +++++++++++++++++++++ requirements_all.txt | 3 +- 4 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/sensor/amcrest.py diff --git a/.coveragerc b/.coveragerc index 506e51a63d8..91d7e64e79b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -262,6 +262,7 @@ omit = homeassistant/components/openalpr.py homeassistant/components/remote/harmony.py homeassistant/components/scene/hunterdouglas_powerview.py + homeassistant/components/sensor/amcrest.py homeassistant/components/sensor/arest.py homeassistant/components/sensor/arwn.py homeassistant/components/sensor/bbox.py diff --git a/homeassistant/components/camera/amcrest.py b/homeassistant/components/camera/amcrest.py index a3d8dcf35df..bec760dbe10 100644 --- a/homeassistant/components/camera/amcrest.py +++ b/homeassistant/components/camera/amcrest.py @@ -20,7 +20,7 @@ from homeassistant.const import ( from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_create_clientsession -REQUIREMENTS = ['amcrest==1.0.0'] +REQUIREMENTS = ['amcrest==1.1.0'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/amcrest.py b/homeassistant/components/sensor/amcrest.py new file mode 100644 index 00000000000..7a41bcc6fe4 --- /dev/null +++ b/homeassistant/components/sensor/amcrest.py @@ -0,0 +1,142 @@ +""" +This component provides HA sensor support for Amcrest IP cameras. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.amcrest/ +""" +from datetime import timedelta +import logging + +import voluptuous as vol +import homeassistant.helpers.config_validation as cv + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_HOST, CONF_NAME, CONF_MONITORED_CONDITIONS, + CONF_SCAN_INTERVAL, CONF_USERNAME, CONF_PASSWORD, + CONF_PORT, STATE_UNKNOWN) +from homeassistant.helpers.entity import Entity +import homeassistant.loader as loader + +from requests.exceptions import HTTPError, ConnectTimeout + +REQUIREMENTS = ['amcrest==1.1.0'] + +_LOGGER = logging.getLogger(__name__) + +NOTIFICATION_ID = 'amcrest_notification' +NOTIFICATION_TITLE = 'Amcrest Sensor Setup' + +DEFAULT_NAME = 'Amcrest' +DEFAULT_PORT = 80 +DEFAULT_SCAN_INTERVAL = timedelta(seconds=10) + +# Sensor types are defined like: Name, units, icon +SENSOR_TYPES = { + 'motion_detector': ['Motion Detected', None, 'run'], + 'sdcard': ['SD Used', '%', 'sd'], + 'ptz_preset': ['PTZ Preset', None, 'camera-iris'], +} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + 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_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): + vol.All(vol.Coerce(int), vol.Range(min=1)), + vol.Required(CONF_MONITORED_CONDITIONS, default=[]): + vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up a sensor for an Amcrest IP Camera.""" + from amcrest import AmcrestCamera + + data = AmcrestCamera( + config.get(CONF_HOST), config.get(CONF_PORT), + config.get(CONF_USERNAME), config.get(CONF_PASSWORD)) + + persistent_notification = loader.get_component('persistent_notification') + try: + data.camera.current_time + except (ConnectTimeout, HTTPError) as ex: + _LOGGER.error("Unable to connect to Amcrest camera: %s", str(ex)) + persistent_notification.create( + hass, 'Error: {}<br />' + 'You will need to restart hass after fixing.' + ''.format(ex), + title=NOTIFICATION_TITLE, + notification_id=NOTIFICATION_ID) + return False + + sensors = [] + for sensor_type in config.get(CONF_MONITORED_CONDITIONS): + sensors.append(AmcrestSensor(config, data, sensor_type)) + + add_devices(sensors, True) + + return True + + +class AmcrestSensor(Entity): + """A sensor implementation for Amcrest IP camera.""" + + def __init__(self, device_info, data, sensor_type): + """Initialize a sensor for Amcrest camera.""" + super(AmcrestSensor, self).__init__() + self._attrs = {} + self._data = data + self._sensor_type = sensor_type + self._name = '{0}_{1}'.format(device_info.get(CONF_NAME), + SENSOR_TYPES.get(self._sensor_type)[0]) + self._icon = 'mdi:{}'.format(SENSOR_TYPES.get(self._sensor_type)[2]) + self._state = STATE_UNKNOWN + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return self._attrs + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return self._icon + + @property + def unit_of_measurement(self): + """Return the units of measurement.""" + return SENSOR_TYPES.get(self._sensor_type)[1] + + def update(self): + """Get the latest data and updates the state.""" + version, build_date = self._data.camera.software_information + self._attrs['Build Date'] = build_date.split('=')[-1] + self._attrs['Serial Number'] = self._data.camera.serial_number + self._attrs['Version'] = version.split('=')[-1] + + if self._sensor_type == 'motion_detector': + self._state = self._data.camera.is_motion_detected + self._attrs['Record Mode'] = self._data.camera.record_mode + + elif self._sensor_type == 'ptz_preset': + self._state = self._data.camera.ptz_presets_count + + elif self._sensor_type == 'sdcard': + sd_used = self._data.camera.storage_used + sd_total = self._data.camera.storage_total + self._attrs['Total'] = '{0} {1}'.format(*sd_total) + self._attrs['Used'] = '{0} {1}'.format(*sd_used) + self._state = self._data.camera.percent(sd_used[0], sd_total[0]) diff --git a/requirements_all.txt b/requirements_all.txt index 9bf5827d8cd..41acf7fcabd 100755 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -37,7 +37,8 @@ TwitterAPI==2.4.3 aiohttp_cors==0.5.0 # homeassistant.components.camera.amcrest -amcrest==1.0.0 +# homeassistant.components.sensor.amcrest +amcrest==1.1.0 # homeassistant.components.apcupsd apcaccess==0.0.4 -- GitLab