From ecd09f22efec66d61f6a33943678234036365c53 Mon Sep 17 00:00:00 2001 From: Finbarr Brady Date: Sat, 30 May 2015 13:44:29 +0000 Subject: [PATCH 1/8] Support for Hikvision camera motion detection. --- homeassistant/components/switch/hikvision.py | 221 +++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 homeassistant/components/switch/hikvision.py diff --git a/homeassistant/components/switch/hikvision.py b/homeassistant/components/switch/hikvision.py new file mode 100644 index 00000000000..31684171e1c --- /dev/null +++ b/homeassistant/components/switch/hikvision.py @@ -0,0 +1,221 @@ +""" +homeassistant.components.switch.hikvision +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Support turning on/off motion detection on Hikvision cameras. + +Note: Currently works using default https port only. + +CGI API Guide: +http://bit.ly/1RuyUuF + +Configuration: + +To use the Hikvision motion detection +switch you will need to add something like the +following to your config/configuration.yaml + +switch: + platform: hikvision + name: Hikvision Cam 1 Motion Detection + host: 192.168.1.26 + username: YOUR_USERNAME + password: YOUR_PASSWORD + +Variables: + +host +*Required +This is the IP address of your Hikvision camera. Example: 192.168.1.32 + +username +*Required +Your Hikvision camera username + +password +*Required +Your Hikvision camera username + +name +*Optional +The name to use when displaying this switch instance. + +""" +from homeassistant.helpers.entity import ToggleEntity +from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +import logging +import requests +from requests.auth import HTTPBasicAuth +from xml.etree import ElementTree + +_LOGGING = logging.getLogger(__name__) + +# pylint: disable=too-many-arguments +# pylint: disable=too-many-instance-attributes + + +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Setup Hikvision Camera config. """ + + host = config.get(CONF_HOST, None) + port = config.get('port', "80") + name = config.get('name', "Hikvision Camera Motion Detection") + username = config.get(CONF_USERNAME, "admin") + password = config.get(CONF_PASSWORD, "12345") + channel_id = config.get('channel_id', "1") + xml_namespace = config.get( + 'xml_namespace', "http://www.hikvision.com/ver10/XMLSchema") + + # Required to parse and change xml with the host camera + _LOGGING.info('ElementTree.register_namespace: %s', xml_namespace) + ElementTree.register_namespace("", xml_namespace) + + if not host: + _LOGGING.error('Missing config variable-host') + return False + + add_devices_callback([ + HikvisionMotionSwitch( + name, host, port, username, password, channel_id, xml_namespace) + ]) + + +class HikvisionMotionSwitch(ToggleEntity): + + """ Provides a switch to toggle on/off motion detection. """ + + def __init__(self, name, host, port, username, + password, channel_id, xml_namespace): + self._name = name + self._username = username + self._password = password + self._channel_id = channel_id + self._host = host + self._port = port + self._xml_namespace = xml_namespace + self._state = STATE_OFF + self.url = 'https://%s/MotionDetection/%s/' % ( + self._host, self._channel_id) + self.xml_motion_detection_off = None + self.xml_motion_detection_on = None + self.update() + + @property + def should_poll(self): + """ Poll for status regularly. """ + return True + + @property + def name(self): + """ Returns the name of the device if any. """ + return self._name + + @property + def state(self): + """ Returns the state of the device if any. """ + return self._state + + @property + def is_on(self): + """ True if device is on. """ + return self._state == STATE_ON + + def turn_on(self, **kwargs): + """ Turn the device on. """ + + _LOGGING.info("Turning on Motion Detection ") + self.toggle_motion_detection() + + def turn_off(self, **kwargs): + """ Turn the device off. """ + + _LOGGING.info("Turning off Motion Detection ") + self.toggle_motion_detection() + + def toggle_motion_detection(self): + """ + # See http://bit.ly/1KtcW7b + """ + + if self._state == STATE_ON: + xml = self.xml_motion_detection_off + self._state = STATE_OFF + else: + self._state = STATE_ON + xml = self.xml_motion_detection_on + + _LOGGING.info('xml:') + _LOGGING.info("%s", xml) + + response = requests.put(self.url, auth=HTTPBasicAuth( + self._username, self._password), verify=False, data=xml) + _LOGGING.info('Response: %s', response.text) + + if response.status_code != 200: + _LOGGING.error("There was an error connecting to %s", self.url) + _LOGGING.error("status_code %s", response.esponsestatus_code) + return + + try: + tree = ElementTree.fromstring(response.content) + find_result = tree.findall( + './/{%s}statusString' % self._xml_namespace) + if len(find_result) == 0: + _LOGGING.error("Problem getting motion detection status") + self.update() + return + + if find_result[0].text.strip() == 'OK': + _LOGGING.info('Updated successfully') + + except AttributeError as attib_err: + _LOGGING.error( + 'There was a problem parsing the response: %s', attib_err) + self.update() + return + + def update(self): + """ + # See http://bit.ly/1KtcW7b + """ + _LOGGING.info('url: %s', self.url) + + response = requests.get(self.url, auth=HTTPBasicAuth( + self._username, self._password), verify=False) + _LOGGING.info('Response: %s', response.text) + + if response.status_code != 200: + _LOGGING.error("There was an error connecting to %s", self.url) + _LOGGING.error("status_code %s", response.status_code) + return + + try: + tree = ElementTree.fromstring(response.content) + find_result = tree.findall('.//{%s}enabled' % self._xml_namespace) + if len(find_result) == 0: + _LOGGING.error("Problem getting motion detection status") + return + + result = find_result[0].text.strip() + _LOGGING.info( + 'Current motion detection state? enabled: %s', result) + + if result == 'true': + self._state = STATE_ON + # Save this for future switch off + find_result[0].text = 'false' + self.xml_motion_detection_off = ElementTree.tostring( + tree, encoding='unicode') + else: + self._state = STATE_OFF + # Save this for future switch on + find_result[0].text = 'true' + self.xml_motion_detection_on = ElementTree.tostring( + tree, encoding='unicode') + + except AttributeError as attib_err: + _LOGGING.error( + 'There was a problem parsing ' + 'camera motion detection state: %s', attib_err) + return From 84e81a9e4e0c7555d6319bd9e451df6bd1674f7d Mon Sep 17 00:00:00 2001 From: Finbarr Brady Date: Tue, 2 Jun 2015 16:00:29 +0100 Subject: [PATCH 2/8] Renamed hikvision component. Added module from pip --- .../components/switch/hikvisioncam.py | 138 ++++++++++++++++++ requirements.txt | 3 + 2 files changed, 141 insertions(+) create mode 100644 homeassistant/components/switch/hikvisioncam.py diff --git a/homeassistant/components/switch/hikvisioncam.py b/homeassistant/components/switch/hikvisioncam.py new file mode 100644 index 00000000000..3db11b96b18 --- /dev/null +++ b/homeassistant/components/switch/hikvisioncam.py @@ -0,0 +1,138 @@ +""" +homeassistant.components.switch.hikvision +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Support turning on/off motion detection on Hikvision cameras. + +Note: Currently works using default https port only. + +CGI API Guide: +http://bit.ly/1RuyUuF + +Configuration: + +To use the Hikvision motion detection +switch you will need to add something like the +following to your config/configuration.yaml + +switch: + platform: hikvision + name: Hikvision Cam 1 Motion Detection + host: 192.168.1.26 + username: YOUR_USERNAME + password: YOUR_PASSWORD + +Variables: + +host +*Required +This is the IP address of your Hikvision camera. Example: 192.168.1.32 + +username +*Required +Your Hikvision camera username + +password +*Required +Your Hikvision camera username + +name +*Optional +The name to use when displaying this switch instance. + +""" +from homeassistant.helpers.entity import ToggleEntity +from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +import logging + +try: + import hikvision.api + from hikvision.error import HikvisionError, MissingParamError +except ImportError: + hikvision.api = None + +_LOGGING = logging.getLogger(__name__) + +# pylint: disable=too-many-arguments +# pylint: disable=too-many-instance-attributes + + +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Setup Hikvision Camera config. """ + + host = config.get(CONF_HOST, None) + port = config.get('port', "80") + name = config.get('name', "Hikvision Camera Motion Detection") + username = config.get(CONF_USERNAME, "admin") + password = config.get(CONF_PASSWORD, "12345") + + if hikvision.api is None: + _LOGGING.error(( + "Failed to import hikvision. Did you maybe not install the " + "'hikvision' dependency?")) + + return False + + try: + hikvision_cam = hikvision.api.CreateDevice( + host, port=port, username=username, password=password) + except MissingParamError as param_err: + _LOGGING.error("Missing required param: %s", param_err) + return False + except HikvisionError as conn_err: + _LOGGING.error("Unable to connect: %s", conn_err) + return False + + add_devices_callback([ + HikvisionMotionSwitch(name, hikvision_cam) + ]) + + +class HikvisionMotionSwitch(ToggleEntity): + + """ Provides a switch to toggle on/off motion detection. """ + + def __init__(self, name, hikvision_cam): + self._name = name + self._hikvision_cam = hikvision_cam + self._state = STATE_OFF + + @property + def should_poll(self): + """ Poll for status regularly. """ + return True + + @property + def name(self): + """ Returns the name of the device if any. """ + return self._name + + @property + def state(self): + """ Returns the state of the device if any. """ + return self._state + + @property + def is_on(self): + """ True if device is on. """ + return self._state == STATE_ON + + def turn_on(self, **kwargs): + """ Turn the device on. """ + + _LOGGING.info("Turning on Motion Detection ") + self._hikvision_cam.enable_motion_detection() + + def turn_off(self, **kwargs): + """ Turn the device off. """ + + _LOGGING.info("Turning off Motion Detection ") + self._hikvision_cam.disable_motion_detection() + + def update(self): + """ Update Motion Detection state """ + enabled = self._hikvision_cam.hik_camera.is_motion_detection_enabled() + _LOGGING.info('enabled: %s', enabled) + + self._state = STATE_ON if enabled else STATE_OFF diff --git a/requirements.txt b/requirements.txt index 5dc0e7cc18b..22bf213c21c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -58,3 +58,6 @@ sleekxmpp>=1.3.1 # Blockchain (sensor.bitcoin) blockchain>=1.1.2 + +# Hikvision (switch.hikvision) +hikvision>=0.3 From bae530b30d9e63a65d77fc7f289a8dbb54bdc9d7 Mon Sep 17 00:00:00 2001 From: Finbarr Brady Date: Tue, 2 Jun 2015 16:03:29 +0100 Subject: [PATCH 3/8] Delete original file --- homeassistant/components/switch/hikvision.py | 221 ------------------- 1 file changed, 221 deletions(-) delete mode 100644 homeassistant/components/switch/hikvision.py diff --git a/homeassistant/components/switch/hikvision.py b/homeassistant/components/switch/hikvision.py deleted file mode 100644 index 31684171e1c..00000000000 --- a/homeassistant/components/switch/hikvision.py +++ /dev/null @@ -1,221 +0,0 @@ -""" -homeassistant.components.switch.hikvision -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Support turning on/off motion detection on Hikvision cameras. - -Note: Currently works using default https port only. - -CGI API Guide: -http://bit.ly/1RuyUuF - -Configuration: - -To use the Hikvision motion detection -switch you will need to add something like the -following to your config/configuration.yaml - -switch: - platform: hikvision - name: Hikvision Cam 1 Motion Detection - host: 192.168.1.26 - username: YOUR_USERNAME - password: YOUR_PASSWORD - -Variables: - -host -*Required -This is the IP address of your Hikvision camera. Example: 192.168.1.32 - -username -*Required -Your Hikvision camera username - -password -*Required -Your Hikvision camera username - -name -*Optional -The name to use when displaying this switch instance. - -""" -from homeassistant.helpers.entity import ToggleEntity -from homeassistant.const import STATE_ON, STATE_OFF -from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD -import logging -import requests -from requests.auth import HTTPBasicAuth -from xml.etree import ElementTree - -_LOGGING = logging.getLogger(__name__) - -# pylint: disable=too-many-arguments -# pylint: disable=too-many-instance-attributes - - -def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Setup Hikvision Camera config. """ - - host = config.get(CONF_HOST, None) - port = config.get('port', "80") - name = config.get('name', "Hikvision Camera Motion Detection") - username = config.get(CONF_USERNAME, "admin") - password = config.get(CONF_PASSWORD, "12345") - channel_id = config.get('channel_id', "1") - xml_namespace = config.get( - 'xml_namespace', "http://www.hikvision.com/ver10/XMLSchema") - - # Required to parse and change xml with the host camera - _LOGGING.info('ElementTree.register_namespace: %s', xml_namespace) - ElementTree.register_namespace("", xml_namespace) - - if not host: - _LOGGING.error('Missing config variable-host') - return False - - add_devices_callback([ - HikvisionMotionSwitch( - name, host, port, username, password, channel_id, xml_namespace) - ]) - - -class HikvisionMotionSwitch(ToggleEntity): - - """ Provides a switch to toggle on/off motion detection. """ - - def __init__(self, name, host, port, username, - password, channel_id, xml_namespace): - self._name = name - self._username = username - self._password = password - self._channel_id = channel_id - self._host = host - self._port = port - self._xml_namespace = xml_namespace - self._state = STATE_OFF - self.url = 'https://%s/MotionDetection/%s/' % ( - self._host, self._channel_id) - self.xml_motion_detection_off = None - self.xml_motion_detection_on = None - self.update() - - @property - def should_poll(self): - """ Poll for status regularly. """ - return True - - @property - def name(self): - """ Returns the name of the device if any. """ - return self._name - - @property - def state(self): - """ Returns the state of the device if any. """ - return self._state - - @property - def is_on(self): - """ True if device is on. """ - return self._state == STATE_ON - - def turn_on(self, **kwargs): - """ Turn the device on. """ - - _LOGGING.info("Turning on Motion Detection ") - self.toggle_motion_detection() - - def turn_off(self, **kwargs): - """ Turn the device off. """ - - _LOGGING.info("Turning off Motion Detection ") - self.toggle_motion_detection() - - def toggle_motion_detection(self): - """ - # See http://bit.ly/1KtcW7b - """ - - if self._state == STATE_ON: - xml = self.xml_motion_detection_off - self._state = STATE_OFF - else: - self._state = STATE_ON - xml = self.xml_motion_detection_on - - _LOGGING.info('xml:') - _LOGGING.info("%s", xml) - - response = requests.put(self.url, auth=HTTPBasicAuth( - self._username, self._password), verify=False, data=xml) - _LOGGING.info('Response: %s', response.text) - - if response.status_code != 200: - _LOGGING.error("There was an error connecting to %s", self.url) - _LOGGING.error("status_code %s", response.esponsestatus_code) - return - - try: - tree = ElementTree.fromstring(response.content) - find_result = tree.findall( - './/{%s}statusString' % self._xml_namespace) - if len(find_result) == 0: - _LOGGING.error("Problem getting motion detection status") - self.update() - return - - if find_result[0].text.strip() == 'OK': - _LOGGING.info('Updated successfully') - - except AttributeError as attib_err: - _LOGGING.error( - 'There was a problem parsing the response: %s', attib_err) - self.update() - return - - def update(self): - """ - # See http://bit.ly/1KtcW7b - """ - _LOGGING.info('url: %s', self.url) - - response = requests.get(self.url, auth=HTTPBasicAuth( - self._username, self._password), verify=False) - _LOGGING.info('Response: %s', response.text) - - if response.status_code != 200: - _LOGGING.error("There was an error connecting to %s", self.url) - _LOGGING.error("status_code %s", response.status_code) - return - - try: - tree = ElementTree.fromstring(response.content) - find_result = tree.findall('.//{%s}enabled' % self._xml_namespace) - if len(find_result) == 0: - _LOGGING.error("Problem getting motion detection status") - return - - result = find_result[0].text.strip() - _LOGGING.info( - 'Current motion detection state? enabled: %s', result) - - if result == 'true': - self._state = STATE_ON - # Save this for future switch off - find_result[0].text = 'false' - self.xml_motion_detection_off = ElementTree.tostring( - tree, encoding='unicode') - else: - self._state = STATE_OFF - # Save this for future switch on - find_result[0].text = 'true' - self.xml_motion_detection_on = ElementTree.tostring( - tree, encoding='unicode') - - except AttributeError as attib_err: - _LOGGING.error( - 'There was a problem parsing ' - 'camera motion detection state: %s', attib_err) - return From bbf8420d804513cd974c760cec27d79686edaab2 Mon Sep 17 00:00:00 2001 From: Finbarr Brady Date: Tue, 2 Jun 2015 16:04:45 +0100 Subject: [PATCH 4/8] Updated component id --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 22bf213c21c..9b6885ef19c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -59,5 +59,5 @@ sleekxmpp>=1.3.1 # Blockchain (sensor.bitcoin) blockchain>=1.1.2 -# Hikvision (switch.hikvision) +# Hikvision (switch.hikvisioncam) hikvision>=0.3 From ebc0ffd879cabda557420e15323cf2f84f51ece2 Mon Sep 17 00:00:00 2001 From: Finbarr Brady Date: Tue, 2 Jun 2015 17:14:12 +0100 Subject: [PATCH 5/8] https false --- homeassistant/components/switch/hikvisioncam.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/switch/hikvisioncam.py b/homeassistant/components/switch/hikvisioncam.py index 3db11b96b18..9cec027c58a 100644 --- a/homeassistant/components/switch/hikvisioncam.py +++ b/homeassistant/components/switch/hikvisioncam.py @@ -76,7 +76,8 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): try: hikvision_cam = hikvision.api.CreateDevice( - host, port=port, username=username, password=password) + host, port=port, username=username, + password=password, is_https=False) except MissingParamError as param_err: _LOGGING.error("Missing required param: %s", param_err) return False From 3701a82de1d6f6625ad1d96f77bc93f68ff9d2ed Mon Sep 17 00:00:00 2001 From: Finbarr Brady Date: Tue, 2 Jun 2015 17:17:36 +0100 Subject: [PATCH 6/8] Fix typo --- homeassistant/components/switch/hikvisioncam.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/switch/hikvisioncam.py b/homeassistant/components/switch/hikvisioncam.py index 9cec027c58a..3c85cfce640 100644 --- a/homeassistant/components/switch/hikvisioncam.py +++ b/homeassistant/components/switch/hikvisioncam.py @@ -133,7 +133,7 @@ class HikvisionMotionSwitch(ToggleEntity): def update(self): """ Update Motion Detection state """ - enabled = self._hikvision_cam.hik_camera.is_motion_detection_enabled() + enabled = self._hikvision_cam.is_motion_detection_enabled() _LOGGING.info('enabled: %s', enabled) self._state = STATE_ON if enabled else STATE_OFF From b11320a5fbecfa8f49b1568b2b4abce2274a0d6e Mon Sep 17 00:00:00 2001 From: Finbarr Brady Date: Tue, 2 Jun 2015 17:18:41 +0100 Subject: [PATCH 7/8] Bump version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9b6885ef19c..8356ca80824 100644 --- a/requirements.txt +++ b/requirements.txt @@ -60,4 +60,4 @@ sleekxmpp>=1.3.1 blockchain>=1.1.2 # Hikvision (switch.hikvisioncam) -hikvision>=0.3 +hikvision>=0.4 From b5ee05a13e5f26cba5ee6e05161ae18c03e3e570 Mon Sep 17 00:00:00 2001 From: Finbarr Brady Date: Tue, 2 Jun 2015 17:33:05 +0100 Subject: [PATCH 8/8] docs --- homeassistant/components/switch/hikvisioncam.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/switch/hikvisioncam.py b/homeassistant/components/switch/hikvisioncam.py index 3c85cfce640..af9c4c6ad40 100644 --- a/homeassistant/components/switch/hikvisioncam.py +++ b/homeassistant/components/switch/hikvisioncam.py @@ -16,7 +16,7 @@ switch you will need to add something like the following to your config/configuration.yaml switch: - platform: hikvision + platform: hikvisioncam name: Hikvision Cam 1 Motion Detection host: 192.168.1.26 username: YOUR_USERNAME