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