Merge branch 'dev' into refactor-media-player

This commit is contained in:
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/mysensors.py
homeassistant/components/sensor/openweathermap.py homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/sabnzbd.py homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/swiss_public_transport.py
homeassistant/components/sensor/systemmonitor.py homeassistant/components/sensor/systemmonitor.py
homeassistant/components/sensor/time_date.py homeassistant/components/sensor/time_date.py
homeassistant/components/sensor/transmission.py homeassistant/components/sensor/transmission.py

View File

@ -1,6 +1,6 @@
# Adding support for a new device # 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: 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: 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 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 * 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 [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) and [Music Player Daemon](http://www.musicpd.org/)
* Track and control [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast) * 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 services by monitoring `ps` output * Track running system services and monitoring your system stats (Memory, disk usage, and more)
* Track and control [Tellstick devices and sensors](http://www.telldus.se/products/tellstick) * 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 the lights when people get home after sun set
* Turn on lights slowly during sun set to compensate for light loss * Turn on lights slowly during sun set to compensate for light loss
* Turn off all lights and devices when everybody leaves the house * Turn off all lights and devices when everybody leaves the house
* Offers web interface to monitor and control Home Assistant * 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 * 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) * [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: 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)) set_time_zone(config.get(CONF_TIME_ZONE))
for entity_id, attrs in config.get(CONF_CUSTOMIZE, {}).items(): customize = config.get(CONF_CUSTOMIZE)
Entity.overwrite_attribute(entity_id, attrs.keys(), attrs.values())
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: if CONF_TEMPERATURE_UNIT in config:
unit = config[CONF_TEMPERATURE_UNIT] unit = config[CONF_TEMPERATURE_UNIT]

View File

@ -17,7 +17,7 @@ import homeassistant.util.dt as dt_util
from homeassistant.const import ( from homeassistant.const import (
STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME, STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME,
CONF_PLATFORM) CONF_PLATFORM, DEVICE_DEFAULT_NAME)
from homeassistant.components import group from homeassistant.components import group
DOMAIN = "device_tracker" DOMAIN = "device_tracker"
@ -169,66 +169,28 @@ class DeviceTracker(object):
if not self.lock.acquire(False): if not self.lock.acquire(False):
return return
found_devices = set(dev.upper() for dev in try:
self.device_scanner.scan_devices()) found_devices = set(dev.upper() for dev in
self.device_scanner.scan_devices())
for device in self.tracked: for device in self.tracked:
is_home = device in found_devices is_home = device in found_devices
self._update_state(now, device, is_home) self._update_state(now, device, is_home)
if is_home: if is_home:
found_devices.remove(device) found_devices.remove(device)
# Did we find any devices that we didn't know about yet? # Did we find any devices that we didn't know about yet?
new_devices = found_devices - self.untracked_devices new_devices = found_devices - self.untracked_devices
if new_devices: if new_devices:
if not self.track_new_devices: if not self.track_new_devices:
self.untracked_devices.update(new_devices) self.untracked_devices.update(new_devices)
# Write new devices to known devices file self._update_known_devices_file(new_devices)
if not self.invalid_known_devices_file: finally:
self.lock.release()
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()
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
def _read_known_devices_file(self): def _read_known_devices_file(self):
@ -309,6 +271,44 @@ class DeviceTracker(object):
finally: finally:
self.lock.release() 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): def _track_device(self, device, name):
""" """
Add a device to the list of tracked devices. 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='shortcut icon' href='/static/favicon.ico' />
<link rel='icon' type='image/png' <link rel='icon' type='image/png'
href='/static/favicon-192x192.png' sizes='192x192'> href='/static/favicon-192x192.png' sizes='192x192'>
<link rel='apple-touch-icon' sizes='192x192' <link rel='apple-touch-icon' sizes='180x180'
href='/static/favicon-192x192.png'> href='/static/favicon-apple-180x180.png'>
<meta name='theme-color' content='#03a9f4'> <meta name='theme-color' content='#03a9f4'>
</head> </head>
<body fullbleed> <body fullbleed>

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """ """ 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-a400: #ff3d00;
--paper-deep-orange-a700: #dd2c00; --paper-deep-orange-a700: #dd2c00;
--paper-brown-50: #795548; --paper-brown-50: #efebe9;
--paper-brown-100: #efebe9; --paper-brown-100: #d7ccc8;
--paper-brown-200: #d7ccc8; --paper-brown-200: #bcaaa4;
--paper-brown-300: #a1887f; --paper-brown-300: #a1887f;
--paper-brown-400: #8d6e63; --paper-brown-400: #8d6e63;
--paper-brown-500: #795548; --paper-brown-500: #795548;
@ -12569,13 +12569,18 @@ is separate from validation, and `allowed-pattern` does not affect how the input
_previousValidInput: { _previousValidInput: {
type: String, type: String,
value: '' value: ''
},
_patternAlreadyChecked: {
type: Boolean,
value: false
} }
}, },
listeners: { listeners: {
'input': '_onInput', 'input': '_onInput',
'keydown': '_onKeydown' 'keypress': '_onKeypress'
}, },
get _patternRegExp() { get _patternRegExp() {
@ -12600,33 +12605,54 @@ is separate from validation, and `allowed-pattern` does not affect how the input
_bindValueChanged: function() { _bindValueChanged: function() {
if (this.value !== this.bindValue) { 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 // manually notify because we don't want to notify until after setting value
this.fire('bind-value-changed', {value: this.bindValue}); this.fire('bind-value-changed', {value: this.bindValue});
}, },
_onInput: function() { _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.bindValue = this.value;
this._previousValidInput = this.value;
this._patternAlreadyChecked = false;
}, },
_isPrintable: function(keyCode) { _isPrintable: function(event) {
var printable = // What a control/printable character is varies wildly based on the browser.
(keyCode > 47 && keyCode < 58) || // number keys // - most control characters (arrows, backspace) do not send a `keypress` event
keyCode == 32 || keyCode == 13 || // spacebar & return key // in Chrome, but the *do* on Firefox
(keyCode > 64 && keyCode < 91) || // letter keys // - in Firefox, when they do send a `keypress` event, control chars have
(keyCode > 95 && keyCode < 112) || // numpad keys // a charCode = 0, keyCode = xx (for ex. 40 for down arrow)
(keyCode > 185 && keyCode < 193) || // ;=,-./` (in order) // - printable characters always send a keypress event.
(keyCode > 218 && keyCode < 223); // [\]' (in order) // - in Firefox, printable chars always have a keyCode = 0. In Chrome, the keyCode
return printable; // 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 _onKeypress: function(event) {
_correctNumpadKeys: function(keyCode) {
return (keyCode > 95 && keyCode < 112) ? keyCode - 48 : keyCode;
},
_onKeydown: function(event) {
if (!this.preventInvalidInput && this.type !== 'number') { if (!this.preventInvalidInput && this.type !== 'number') {
return; return;
} }
@ -12634,12 +12660,33 @@ is separate from validation, and `allowed-pattern` does not affect how the input
if (!regexp) { if (!regexp) {
return; 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(); 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, * Returns true if `value` is valid. The validator provided in `validator` will be used first,
* then any constraints. * 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. // Empty, non-required input is valid.
if (!this.required && this.value == '') if (!this.required && this.value == '')
return true; return true;
var valid; var valid;
if (this.hasValidator()) { if (this.hasValidator()) {
valid = Polymer.IronValidatableBehavior.validate.call(this, this.value); 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> </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> <script>
(function() { (function() {
"use strict"; "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"> <div class="horizontal justified layout">
<state-info state-obj="[[stateObj]]"></state-info> <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> </paper-toggle-button>
</div> </div>
@ -22026,6 +22114,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
this.forceStateChange(); this.forceStateChange();
}, },
toggleTapped: function(ev) {
ev.stopPropagation();
},
toggleChanged: function(ev) { toggleChanged: function(ev) {
var newVal = ev.target.checked; var newVal = ev.target.checked;
@ -22187,6 +22279,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
.state { .state {
margin-left: 16px; margin-left: 16px;
text-align: right; text-align: right;
overflow-x: hidden;
} }
.main-text { .main-text {
@ -22236,55 +22329,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}); });
})(); })();
</script> </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/"> <dom-module id="state-card" assetpath="cards/">
<style> <style>
:host { :host {
@ -22320,10 +22364,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}, },
listeners: { listeners: {
'click': 'cardClicked', 'tap': 'cardTapped',
}, },
cardClicked: function() { cardTapped: function() {
uiActions.showMoreInfoDialog(this.stateObj.entityId); 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) { if (newVal) {
this.volumeSliderValue = newVal.attributes.media_volume * 100; this.volumeSliderValue = newVal.attributes.media_volume * 100;
this.isMuted = newVal.attributes.media_is_volume_muted; this.isMuted = newVal.attributes.media_is_volume_muted;
console.log(this.isMuted);
} }
this.debounce('more-info-volume-animation-finish', function() { 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-scene.html">
<link rel="import" href="state-card-media_player.html"> <link rel="import" href="state-card-media_player.html">
<dom-module id="state-card-content">
<style>
:host {
display: block;
}
</style>
</dom-module>
<script> <script>
(function() { (function() {
var uiUtil = window.hass.uiUtil; var uiUtil = window.hass.uiUtil;

View File

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

View File

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

View File

@ -37,10 +37,10 @@
}, },
listeners: { listeners: {
'click': 'cardClicked', 'tap': 'cardTapped',
}, },
cardClicked: function() { cardTapped: function() {
uiActions.showMoreInfoDialog(this.stateObj.entityId); 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 low, high = target
if self.current_temperature < low: if self.current_temperature < low:
target = low temp = low
elif self.current_temperature > high: elif self.current_temperature > high:
target = high temp = high
else: else:
target = low + high temp = (low + high)/2
else: else:
temp = target temp = target

View File

@ -38,7 +38,7 @@ python-nest>=2.3.1
# Z-Wave (*.zwave) # Z-Wave (*.zwave)
pydispatcher>=2.0.5 pydispatcher>=2.0.5
# ISY994 bindings (*.isy994 # ISY994 bindings (*.isy994)
PyISY>=1.0.2 PyISY>=1.0.2
# PSutil (sensor.systemmonitor) # PSutil (sensor.systemmonitor)
@ -61,3 +61,6 @@ blockchain>=1.1.2
# MPD Bindings (media_player.mpd) # MPD Bindings (media_player.mpd)
python-mpd2>=0.5.4 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.loader as loader
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.const import ( 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 import homeassistant.components.device_tracker as device_tracker
from helpers import get_test_home_assistant 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 # To ensure all the three expected lines are there, we sort the file
with open(self.known_dev_path) as fil: with open(self.known_dev_path) as fil:
self.assertEqual( 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'], 'device,name,track,picture\n'],
sorted(fil)) sorted(fil))