Merge branch 'dev' into refactor-media-player

pull/158/head
Paulus Schoutsen 2015-06-02 22:44:03 -07:00
commit 4cc8b0bda5
19 changed files with 564 additions and 160 deletions

View File

@ -41,6 +41,7 @@ omit =
homeassistant/components/sensor/mysensors.py
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/swiss_public_transport.py
homeassistant/components/sensor/systemmonitor.py
homeassistant/components/sensor/time_date.py
homeassistant/components/sensor/transmission.py

View File

@ -1,6 +1,6 @@
# Adding support for a new device
For help on building your component, please see the See the [developer documentation on home-assistant.io](https://home-assistant.io/developers/).
For help on building your component, please see the [developer documentation](https://home-assistant.io/developers/) on [home-assistant.io](https://home-assistant.io/).
After you finish adding support for your device:

View File

@ -6,18 +6,20 @@ Home Assistant is a home automation platform running on Python 3. The goal of Ho
It offers the following functionality through built-in components:
* Track if devices are home by monitoring connected devices to a wireless router (supporting [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index))
* Track and control [Philips Hue](http://meethue.com) lights
* Track and control [WeMo switches](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/)
* Track and control [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast)
* Track running services by monitoring `ps` output
* Track and control [Tellstick devices and sensors](http://www.telldus.se/products/tellstick)
* Track if devices are home by monitoring connected devices to a wireless router (supporting [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), and [DD-WRT](http://www.dd-wrt.com/site/index))
* Track and control [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors
* Track and control [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast) and [Music Player Daemon](http://www.musicpd.org/)
* Support for [ISY994](https://www.universal-devices.com/residential/isy994i-series/) (Insteon and X10 devices), [Z-Wave](http://www.z-wave.com/), [Nest Thermostats](https://nest.com/), and [Modbus](http://www.modbus.org/)
* Track running system services and monitoring your system stats (Memory, disk usage, and more)
* Control low-cost 433 MHz remote control wall-socket devices (https://github.com/r10r/rcswitch-pi) and other switches that can be turned on/off with shell commands
* Turn on the lights when people get home after sun set
* Turn on lights slowly during sun set to compensate for light loss
* Turn off all lights and devices when everybody leaves the house
* Offers web interface to monitor and control Home Assistant
* Offers a [REST API](https://home-assistant.io/developers/api.html) for easy integration with other projects
* [Ability to have multiple instances of Home Assistant work together](https://home-assistant.io/developers/architecture.html)
* Allow sending notifications using [Instapush](https://instapush.im), [Notify My Android (NMA)](http://www.notifymyandroid.com/), [PushBullet](https://www.pushbullet.com/), [PushOver](https://pushover.net/), and [Jabber (XMPP)](http://xmpp.org)
* Allow to display details about a running [Transmission](http://www.transmissionbt.com/) client, the [Bitcoin](https://bitcoin.org) network, local meteorological data from [OpenWeatherMap](http://openweathermap.org/), the time, the date, and the downloads from [SABnzbd](http://sabnzbd.org)
Home Assistant also includes functionality for controlling HTPCs:

View File

@ -235,8 +235,13 @@ def process_ha_core_config(hass, config):
set_time_zone(config.get(CONF_TIME_ZONE))
for entity_id, attrs in config.get(CONF_CUSTOMIZE, {}).items():
Entity.overwrite_attribute(entity_id, attrs.keys(), attrs.values())
customize = config.get(CONF_CUSTOMIZE)
if isinstance(customize, dict):
for entity_id, attrs in config.get(CONF_CUSTOMIZE, {}).items():
if not isinstance(attrs, dict):
continue
Entity.overwrite_attribute(entity_id, attrs.keys(), attrs.values())
if CONF_TEMPERATURE_UNIT in config:
unit = config[CONF_TEMPERATURE_UNIT]

View File

@ -17,7 +17,7 @@ import homeassistant.util.dt as dt_util
from homeassistant.const import (
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME,
CONF_PLATFORM)
CONF_PLATFORM, DEVICE_DEFAULT_NAME)
from homeassistant.components import group
DOMAIN = "device_tracker"
@ -169,66 +169,28 @@ class DeviceTracker(object):
if not self.lock.acquire(False):
return
found_devices = set(dev.upper() for dev in
self.device_scanner.scan_devices())
try:
found_devices = set(dev.upper() for dev in
self.device_scanner.scan_devices())
for device in self.tracked:
is_home = device in found_devices
for device in self.tracked:
is_home = device in found_devices
self._update_state(now, device, is_home)
self._update_state(now, device, is_home)
if is_home:
found_devices.remove(device)
if is_home:
found_devices.remove(device)
# Did we find any devices that we didn't know about yet?
new_devices = found_devices - self.untracked_devices
# Did we find any devices that we didn't know about yet?
new_devices = found_devices - self.untracked_devices
if new_devices:
if not self.track_new_devices:
self.untracked_devices.update(new_devices)
if new_devices:
if not self.track_new_devices:
self.untracked_devices.update(new_devices)
# Write new devices to known devices file
if not self.invalid_known_devices_file:
known_dev_path = self.hass.config.path(KNOWN_DEVICES_FILE)
try:
# If file does not exist we will write the header too
is_new_file = not os.path.isfile(known_dev_path)
with open(known_dev_path, 'a') as outp:
_LOGGER.info(
"Found %d new devices, updating %s",
len(new_devices), known_dev_path)
writer = csv.writer(outp)
if is_new_file:
writer.writerow((
"device", "name", "track", "picture"))
for device in new_devices:
# See if the device scanner knows the name
# else defaults to unknown device
dname = self.device_scanner.get_device_name(device)
name = dname or "unknown device"
track = 0
if self.track_new_devices:
self._track_device(device, name)
track = 1
writer.writerow((device, name, track, ""))
if self.track_new_devices:
self._generate_entity_ids(new_devices)
except IOError:
_LOGGER.exception(
"Error updating %s with %d new devices",
known_dev_path, len(new_devices))
self.lock.release()
self._update_known_devices_file(new_devices)
finally:
self.lock.release()
# pylint: disable=too-many-branches
def _read_known_devices_file(self):
@ -309,6 +271,44 @@ class DeviceTracker(object):
finally:
self.lock.release()
def _update_known_devices_file(self, new_devices):
""" Add new devices to known devices file. """
if not self.invalid_known_devices_file:
known_dev_path = self.hass.config.path(KNOWN_DEVICES_FILE)
try:
# If file does not exist we will write the header too
is_new_file = not os.path.isfile(known_dev_path)
with open(known_dev_path, 'a') as outp:
_LOGGER.info("Found %d new devices, updating %s",
len(new_devices), known_dev_path)
writer = csv.writer(outp)
if is_new_file:
writer.writerow(("device", "name", "track", "picture"))
for device in new_devices:
# See if the device scanner knows the name
# else defaults to unknown device
name = self.device_scanner.get_device_name(device) or \
DEVICE_DEFAULT_NAME
track = 0
if self.track_new_devices:
self._track_device(device, name)
track = 1
writer.writerow((device, name, track, ""))
if self.track_new_devices:
self._generate_entity_ids(new_devices)
except IOError:
_LOGGER.exception("Error updating %s with %d new devices",
known_dev_path, len(new_devices))
def _track_device(self, device, name):
"""
Add a device to the list of tracked devices.

View File

@ -15,8 +15,8 @@
<link rel='shortcut icon' href='/static/favicon.ico' />
<link rel='icon' type='image/png'
href='/static/favicon-192x192.png' sizes='192x192'>
<link rel='apple-touch-icon' sizes='192x192'
href='/static/favicon-192x192.png'>
<link rel='apple-touch-icon' sizes='180x180'
href='/static/favicon-apple-180x180.png'>
<meta name='theme-color' content='#03a9f4'>
</head>
<body fullbleed>

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "2d15135e9bfd0ee5b023d9abb79be62d"
VERSION = "ed339673ca129a1a51dcc3975d0a492d"

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -12326,9 +12326,9 @@ window.hass.uiUtil.formatDate = function(dateObj) {
--paper-deep-orange-a400: #ff3d00;
--paper-deep-orange-a700: #dd2c00;
--paper-brown-50: #795548;
--paper-brown-100: #efebe9;
--paper-brown-200: #d7ccc8;
--paper-brown-50: #efebe9;
--paper-brown-100: #d7ccc8;
--paper-brown-200: #bcaaa4;
--paper-brown-300: #a1887f;
--paper-brown-400: #8d6e63;
--paper-brown-500: #795548;
@ -12569,13 +12569,18 @@ is separate from validation, and `allowed-pattern` does not affect how the input
_previousValidInput: {
type: String,
value: ''
},
_patternAlreadyChecked: {
type: Boolean,
value: false
}
},
listeners: {
'input': '_onInput',
'keydown': '_onKeydown'
'keypress': '_onKeypress'
},
get _patternRegExp() {
@ -12600,33 +12605,54 @@ is separate from validation, and `allowed-pattern` does not affect how the input
_bindValueChanged: function() {
if (this.value !== this.bindValue) {
this.value = this.bindValue;
this.value = !this.bindValue ? '' : this.bindValue;
}
// manually notify because we don't want to notify until after setting value
this.fire('bind-value-changed', {value: this.bindValue});
},
_onInput: function() {
// Need to validate each of the characters pasted if they haven't
// been validated inside `_onKeypress` already.
if (this.preventInvalidInput && !this._patternAlreadyChecked) {
var valid = this._checkPatternValidity();
if (!valid) {
this.value = this._previousValidInput;
}
}
this.bindValue = this.value;
this._previousValidInput = this.value;
this._patternAlreadyChecked = false;
},
_isPrintable: function(keyCode) {
var printable =
(keyCode > 47 && keyCode < 58) || // number keys
keyCode == 32 || keyCode == 13 || // spacebar & return key
(keyCode > 64 && keyCode < 91) || // letter keys
(keyCode > 95 && keyCode < 112) || // numpad keys
(keyCode > 185 && keyCode < 193) || // ;=,-./` (in order)
(keyCode > 218 && keyCode < 223); // [\]' (in order)
return printable;
_isPrintable: function(event) {
// What a control/printable character is varies wildly based on the browser.
// - most control characters (arrows, backspace) do not send a `keypress` event
// in Chrome, but the *do* on Firefox
// - in Firefox, when they do send a `keypress` event, control chars have
// a charCode = 0, keyCode = xx (for ex. 40 for down arrow)
// - printable characters always send a keypress event.
// - in Firefox, printable chars always have a keyCode = 0. In Chrome, the keyCode
// always matches the charCode.
// None of this makes any sense.
var nonPrintable =
(event.keyCode == 8) || // backspace
(event.keyCode == 19) || // pause
(event.keyCode == 20) || // caps lock
(event.keyCode == 27) || // escape
(event.keyCode == 45) || // insert
(event.keyCode == 46) || // delete
(event.keyCode == 144) || // num lock
(event.keyCode == 145) || // scroll lock
(event.keyCode > 32 && event.keyCode < 41) || // page up/down, end, home, arrows
(event.keyCode > 111 && event.keyCode < 124); // fn keys
return !(event.charCode == 0 && nonPrintable);
},
// convert printable numpad keys to number keys
_correctNumpadKeys: function(keyCode) {
return (keyCode > 95 && keyCode < 112) ? keyCode - 48 : keyCode;
},
_onKeydown: function(event) {
_onKeypress: function(event) {
if (!this.preventInvalidInput && this.type !== 'number') {
return;
}
@ -12634,12 +12660,33 @@ is separate from validation, and `allowed-pattern` does not affect how the input
if (!regexp) {
return;
}
var thisChar = String.fromCharCode(this._correctNumpadKeys(event.keyCode));
if (this._isPrintable(event.keyCode) && !regexp.test(thisChar)) {
// Handle special keys and backspace
if (event.metaKey || event.ctrlKey || event.altKey)
return;
// Check the pattern either here or in `_onInput`, but not in both.
this._patternAlreadyChecked = true;
var thisChar = String.fromCharCode(event.charCode);
if (this._isPrintable(event) && !regexp.test(thisChar)) {
event.preventDefault();
}
},
_checkPatternValidity: function() {
var regexp = this._patternRegExp;
if (!regexp) {
return true;
}
for (var i = 0; i < this.value.length; i++) {
if (!regexp.test(this.value[i])) {
return false;
}
}
return true;
},
/**
* Returns true if `value` is valid. The validator provided in `validator` will be used first,
* then any constraints.
@ -12649,7 +12696,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input
// Empty, non-required input is valid.
if (!this.required && this.value == '')
return true;
var valid;
if (this.hasValidator()) {
valid = Polymer.IronValidatableBehavior.validate.call(this, this.value);
@ -13660,6 +13707,47 @@ is separate from validation, and `allowed-pattern` does not affect how the input
}
</style>
<script>
(function() {
var uiUtil = window.hass.uiUtil;
Polymer({
is: 'state-card-content',
properties: {
stateObj: {
type: Object,
observer: 'stateObjChanged',
}
},
stateObjChanged: function(newVal, oldVal) {
var root = Polymer.dom(this);
if (!newVal) {
if (root.lastChild) {
root.removeChild(root.lastChild);
}
return;
}
var newCardType = uiUtil.stateCardType(newVal);
if (!oldVal || uiUtil.stateCardType(oldVal) != newCardType) {
if (root.lastChild) {
root.removeChild(root.lastChild);
}
var stateCard = document.createElement("state-card-" + newCardType);
stateCard.stateObj = newVal;
root.appendChild(stateCard);
} else {
root.lastChild.stateObj = newVal;
}
},
});
})();
</script>
<script>
(function() {
"use strict";
@ -21995,7 +22083,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<div class="horizontal justified layout">
<state-info state-obj="[[stateObj]]"></state-info>
<paper-toggle-button class="self-center" checked="[[toggleChecked]]" on-change="toggleChanged" on-click="toggleClicked">
<paper-toggle-button class="self-center" checked="[[toggleChecked]]" on-change="toggleChanged" on-tap="toggleTapped">
</paper-toggle-button>
</div>
@ -22026,6 +22114,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
this.forceStateChange();
},
toggleTapped: function(ev) {
ev.stopPropagation();
},
toggleChanged: function(ev) {
var newVal = ev.target.checked;
@ -22187,6 +22279,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
.state {
margin-left: 16px;
text-align: right;
overflow-x: hidden;
}
.main-text {
@ -22236,55 +22329,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
});
})();
</script>
<dom-module id="state-card-content" assetpath="cards/">
<style>
:host {
display: block;
}
</style>
</dom-module>
<script>
(function() {
var uiUtil = window.hass.uiUtil;
Polymer({
is: 'state-card-content',
properties: {
stateObj: {
type: Object,
observer: 'stateObjChanged',
}
},
stateObjChanged: function(newVal, oldVal) {
var root = Polymer.dom(this);
if (!newVal) {
if (root.lastChild) {
root.removeChild(root.lastChild);
}
return;
}
var newCardType = uiUtil.stateCardType(newVal);
if (!oldVal || uiUtil.stateCardType(oldVal) != newCardType) {
if (root.lastChild) {
root.removeChild(root.lastChild);
}
var stateCard = document.createElement("state-card-" + newCardType);
stateCard.stateObj = newVal;
root.appendChild(stateCard);
} else {
root.lastChild.stateObj = newVal;
}
},
});
})();
</script>
<dom-module id="state-card" assetpath="cards/">
<style>
:host {
@ -22320,10 +22364,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
listeners: {
'click': 'cardClicked',
'tap': 'cardTapped',
},
cardClicked: function() {
cardTapped: function() {
uiActions.showMoreInfoDialog(this.stateObj.entityId);
},
});
@ -25792,7 +25836,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
if (newVal) {
this.volumeSliderValue = newVal.attributes.media_volume * 100;
this.isMuted = newVal.attributes.media_is_volume_muted;
console.log(this.isMuted);
}
this.debounce('more-info-volume-animation-finish', function() {

View File

@ -7,14 +7,6 @@
<link rel="import" href="state-card-scene.html">
<link rel="import" href="state-card-media_player.html">
<dom-module id="state-card-content">
<style>
:host {
display: block;
}
</style>
</dom-module>
<script>
(function() {
var uiUtil = window.hass.uiUtil;

View File

@ -11,6 +11,7 @@
.state {
margin-left: 16px;
text-align: right;
overflow-x: hidden;
}
.main-text {

View File

@ -16,7 +16,7 @@
<paper-toggle-button class='self-center'
checked="[[toggleChecked]]"
on-change="toggleChanged"
on-click="toggleClicked">
on-tap="toggleTapped">
</paper-toggle-button>
</div>
@ -47,6 +47,10 @@
this.forceStateChange();
},
toggleTapped: function(ev) {
ev.stopPropagation();
},
toggleChanged: function(ev) {
var newVal = ev.target.checked;

View File

@ -37,10 +37,10 @@
},
listeners: {
'click': 'cardClicked',
'tap': 'cardTapped',
},
cardClicked: function() {
cardTapped: function() {
uiActions.showMoreInfoDialog(this.stateObj.entityId);
},
});

View File

@ -0,0 +1,132 @@
"""
homeassistant.components.sensor.swiss_public_transport
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The Swiss public transport sensor will give you the next two departure times
from a given location to another one. This sensor is limited to Switzerland.
Configuration:
To use the Swiss public transport sensor you will need to add something like
the following to your config/configuration.yaml
sensor:
platform: swiss_public_transport
from: STATION_ID
to: STATION_ID
Variables:
from
*Required
Start station/stop of your trip. To search for the ID of the station, use the
an URL like this: http://transport.opendata.ch/v1/locations?query=Wankdorf
to query for the station. If the score is 100 ("score":"100" in the response),
it is a perfect match.
to
*Required
Destination station/stop of the trip. Same procedure as for the start station.
Details for the API : http://transport.opendata.ch
"""
import logging
from datetime import timedelta
from requests import get
from homeassistant.util import Throttle
import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
_RESOURCE = 'http://transport.opendata.ch/v1/'
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Get the Swiss public transport sensor. """
# journal contains [0] Station ID start, [1] Station ID destination
# [2] Station name start, and [3] Station name destination
journey = [config.get('from'), config.get('to')]
try:
for location in [config.get('from', None), config.get('to', None)]:
# transport.opendata.ch doesn't play nice with requests.Session
result = get(_RESOURCE + 'locations?query=%s' % location)
journey.append(result.json()['stations'][0]['name'])
except KeyError:
_LOGGER.exception(
"Unable to determine stations. "
"Check your settings and/or the availability of opendata.ch")
return None
dev = []
data = PublicTransportData(journey)
dev.append(SwissPublicTransportSensor(data, journey))
add_devices(dev)
# pylint: disable=too-few-public-methods
class SwissPublicTransportSensor(Entity):
""" Implements an Swiss public transport sensor. """
def __init__(self, data, journey):
self.data = data
self._name = '{}-{}'.format(journey[2], journey[3])
self.update()
@property
def name(self):
""" Returns the name. """
return self._name
@property
def state(self):
""" Returns the state of the device. """
return self._state
# pylint: disable=too-many-branches
def update(self):
""" Gets the latest data from opendata.ch and updates the states. """
times = self.data.update()
try:
self._state = ', '.join(times)
except TypeError:
pass
# pylint: disable=too-few-public-methods
class PublicTransportData(object):
""" Class for handling the data retrieval. """
def __init__(self, journey):
self.start = journey[0]
self.destination = journey[1]
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
""" Gets the latest data from opendata.ch. """
response = get(
_RESOURCE +
'connections?' +
'from=' + self.start + '&' +
'to=' + self.destination + '&' +
'fields[]=connections/from/departureTimestamp/&' +
'fields[]=connections/')
connections = response.json()['connections'][:2]
try:
return [
dt_util.datetime_to_short_time_str(
dt_util.as_local(dt_util.utc_from_timestamp(
item['from']['departureTimestamp']))
)
for item in connections
]
except KeyError:
return ['n/a']

View File

@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
"""
homeassistant.components.switch.command_switch
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows to configure custom shell commands to turn a switch on/off.
"""
import logging
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.const import STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME
import subprocess
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return switches controlled by shell commands. """
switches = config.get('switches', {})
devices = []
for dev_name, properties in switches.items():
devices.append(
CommandSwitch(
dev_name,
properties.get('oncmd', 'true'),
properties.get('offcmd', 'true')))
add_devices_callback(devices)
class CommandSwitch(ToggleEntity):
""" Represents a switch that can be togggled using shell commands """
def __init__(self, name, command_on, command_off):
self._name = name or DEVICE_DEFAULT_NAME
self._state = STATE_OFF
self._command_on = command_on
self._command_off = command_off
@staticmethod
def _switch(command):
""" Execute the actual commands """
_LOGGER.info('Running command: %s', command)
success = (subprocess.call(command, shell=True) == 0)
if not success:
_LOGGER.error('Command failed: %s', command)
return success
@property
def should_poll(self):
""" No polling needed """
return False
@property
def name(self):
""" The name of the switch """
return self._name
@property
def state(self):
""" Returns the state of the switch. """
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. """
if CommandSwitch._switch(self._command_on):
self._state = STATE_ON
def turn_off(self, **kwargs):
""" Turn the device off. """
if CommandSwitch._switch(self._command_off):
self._state = STATE_OFF

View File

@ -0,0 +1,139 @@
"""
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: hikvisioncam
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, is_https=False)
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.is_motion_detection_enabled()
_LOGGING.info('enabled: %s', enabled)
self._state = STATE_ON if enabled else STATE_OFF

View File

@ -80,11 +80,11 @@ class NestThermostat(ThermostatDevice):
low, high = target
if self.current_temperature < low:
target = low
temp = low
elif self.current_temperature > high:
target = high
temp = high
else:
target = low + high
temp = (low + high)/2
else:
temp = target

View File

@ -38,7 +38,7 @@ python-nest>=2.3.1
# Z-Wave (*.zwave)
pydispatcher>=2.0.5
# ISY994 bindings (*.isy994
# ISY994 bindings (*.isy994)
PyISY>=1.0.2
# PSutil (sensor.systemmonitor)
@ -61,3 +61,6 @@ blockchain>=1.1.2
# MPD Bindings (media_player.mpd)
python-mpd2>=0.5.4
# Hikvision (switch.hikvisioncam)
hikvision>=0.4

View File

@ -14,7 +14,8 @@ import homeassistant as ha
import homeassistant.loader as loader
import homeassistant.util.dt as dt_util
from homeassistant.const import (
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, CONF_PLATFORM)
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, CONF_PLATFORM,
DEVICE_DEFAULT_NAME)
import homeassistant.components.device_tracker as device_tracker
from helpers import get_test_home_assistant
@ -96,7 +97,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
# To ensure all the three expected lines are there, we sort the file
with open(self.known_dev_path) as fil:
self.assertEqual(
['DEV1,unknown device,0,\n', 'DEV2,dev2,0,\n',
['DEV1,{},0,\n'.format(DEVICE_DEFAULT_NAME), 'DEV2,dev2,0,\n',
'device,name,track,picture\n'],
sorted(fil))