diff --git a/homeassistant/components/apcupsd.py b/homeassistant/components/apcupsd.py index 867208305b0..72db3e06dee 100644 --- a/homeassistant/components/apcupsd.py +++ b/homeassistant/components/apcupsd.py @@ -32,7 +32,7 @@ VALUE_ONLINE = 'ONLINE' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ - vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, }), }, extra=vol.ALLOW_EXTRA) diff --git a/homeassistant/components/climate/ecobee.py b/homeassistant/components/climate/ecobee.py index da4b29dfe92..2417a8562ce 100644 --- a/homeassistant/components/climate/ecobee.py +++ b/homeassistant/components/climate/ecobee.py @@ -181,7 +181,7 @@ class Thermostat(ClimateDevice): else: operation = status return { - "humidity": self.thermostat['runtime']['actualHumidity'], + "actual_humidity": self.thermostat['runtime']['actualHumidity'], "fan": self.fan, "mode": self.mode, "operation": operation, diff --git a/homeassistant/components/climate/homematic.py b/homeassistant/components/climate/homematic.py index be81bb9326e..e51ad5e67a5 100644 --- a/homeassistant/components/climate/homematic.py +++ b/homeassistant/components/climate/homematic.py @@ -101,7 +101,7 @@ class HMThermostat(homematic.HMDevice, ClimateDevice): for mode, state in HM_STATE_MAP.items(): if state == operation_mode: code = getattr(self._hmdevice, mode, 0) - self._hmdevice.STATE = code + self._hmdevice.MODE = code @property def min_temp(self): diff --git a/homeassistant/components/climate/zwave.py b/homeassistant/components/climate/zwave.py index c8425ab4e8c..530e3ea028f 100755 --- a/homeassistant/components/climate/zwave.py +++ b/homeassistant/components/climate/zwave.py @@ -12,7 +12,7 @@ from homeassistant.components.climate import ClimateDevice from homeassistant.components.zwave import ( ATTR_NODE_ID, ATTR_VALUE_ID, ZWaveDeviceEntity) from homeassistant.components import zwave -from homeassistant.const import (TEMP_FAHRENHEIT, TEMP_CELSIUS) +from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT _LOGGER = logging.getLogger(__name__) @@ -59,11 +59,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.debug("No discovery_info=%s or no NETWORK=%s", discovery_info, zwave.NETWORK) return - + temp_unit = hass.config.units.temperature_unit node = zwave.NETWORK.nodes[discovery_info[ATTR_NODE_ID]] value = node.values[discovery_info[ATTR_VALUE_ID]] value.set_change_verified(False) - add_devices([ZWaveClimate(value)]) + add_devices([ZWaveClimate(value, temp_unit)]) _LOGGER.debug("discovery_info=%s and zwave.NETWORK=%s", discovery_info, zwave.NETWORK) @@ -73,7 +73,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): """Represents a ZWave Climate device.""" # pylint: disable=too-many-public-methods, too-many-instance-attributes - def __init__(self, value): + def __init__(self, value, temp_unit): """Initialize the zwave climate device.""" from openzwave.network import ZWaveNetwork from pydispatch import dispatcher @@ -87,7 +87,8 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): self._fan_list = None self._current_swing_mode = None self._swing_list = None - self._unit = None + self._unit = temp_unit + _LOGGER.debug("temp_unit is %s", self._unit) self._zxt_120 = None self.update_properties() # register listener @@ -115,18 +116,6 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): def update_properties(self): """Callback on data change for the registered node/value pair.""" - # Set point - for value in self._node.get_values( - class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values(): - self._unit = value.units - if self.current_operation is not None: - if SET_TEMP_TO_INDEX.get(self._current_operation) \ - != value.index: - continue - if self._zxt_120: - continue - self._target_temperature = int(value.data) - # Operation Mode for value in self._node.get_values( class_id=COMMAND_CLASS_THERMOSTAT_MODE).values(): @@ -140,6 +129,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): class_id=COMMAND_CLASS_SENSOR_MULTILEVEL).values(): if value.label == 'Temperature': self._current_temperature = int(value.data) + self._unit = value.units # Fan Mode for value in self._node.get_values( class_id=COMMAND_CLASS_THERMOSTAT_FAN_MODE).values(): @@ -158,6 +148,17 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): _LOGGER.debug("self._swing_list=%s", self._swing_list) _LOGGER.debug("self._current_swing_mode=%s", self._current_swing_mode) + # Set point + for value in self._node.get_values( + class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values(): + if self.current_operation is not None and \ + self.current_operation != 'Off': + if SET_TEMP_TO_INDEX.get(self._current_operation) \ + != value.index: + continue + if self._zxt_120: + continue + self._target_temperature = int(value.data) @property def should_poll(self): @@ -187,14 +188,12 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): @property def unit_of_measurement(self): """Return the unit of measurement.""" - unit = self._unit - if unit == 'C': + if self._unit == 'C': return TEMP_CELSIUS - elif unit == 'F': + elif self._unit == 'F': return TEMP_FAHRENHEIT else: - _LOGGER.exception("unit_of_measurement=%s is not valid", - unit) + return self._unit @property def current_temperature(self): diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py index 3f08c7ff229..876a8b46cfa 100644 --- a/homeassistant/components/cover/__init__.py +++ b/homeassistant/components/cover/__init__.py @@ -61,6 +61,7 @@ SERVICE_TO_METHOD = { SERVICE_STOP_COVER: {'method': 'stop_cover'}, SERVICE_OPEN_COVER_TILT: {'method': 'open_cover_tilt'}, SERVICE_CLOSE_COVER_TILT: {'method': 'close_cover_tilt'}, + SERVICE_STOP_COVER_TILT: {'method': 'stop_cover_tilt'}, SERVICE_SET_COVER_TILT_POSITION: { 'method': 'set_cover_tilt_position', 'schema': COVER_SET_COVER_TILT_POSITION_SCHEMA}, diff --git a/homeassistant/components/cover/homematic.py b/homeassistant/components/cover/homematic.py index cab6b51e645..fd68ac3d265 100644 --- a/homeassistant/components/cover/homematic.py +++ b/homeassistant/components/cover/homematic.py @@ -11,7 +11,7 @@ properly configured. import logging from homeassistant.const import STATE_UNKNOWN from homeassistant.components.cover import CoverDevice,\ - ATTR_CURRENT_POSITION + ATTR_POSITION import homeassistant.components.homematic as homematic _LOGGER = logging.getLogger(__name__) @@ -41,16 +41,16 @@ class HMCover(homematic.HMDevice, CoverDevice): None is unknown, 0 is closed, 100 is fully open. """ if self.available: - return int((1 - self._hm_get_state()) * 100) + return int(self._hm_get_state() * 100) return None def set_cover_position(self, **kwargs): """Move the cover to a specific position.""" if self.available: - if ATTR_CURRENT_POSITION in kwargs: - position = float(kwargs[ATTR_CURRENT_POSITION]) + if ATTR_POSITION in kwargs: + position = float(kwargs[ATTR_POSITION]) position = min(100, max(0, position)) - level = (100 - position) / 100.0 + level = position / 100.0 self._hmdevice.set_level(level, self._channel) @property diff --git a/homeassistant/components/cover/mqtt.py b/homeassistant/components/cover/mqtt.py index dd6b10e244d..b47bcf124e1 100644 --- a/homeassistant/components/cover/mqtt.py +++ b/homeassistant/components/cover/mqtt.py @@ -97,12 +97,16 @@ class MqttCover(CoverDevice): hass, value_template, payload) if payload == self._state_open: self._state = False + _LOGGER.warning("state=%s", int(self._state)) self.update_ha_state() elif payload == self._state_closed: self._state = True self.update_ha_state() elif payload.isnumeric() and 0 <= int(payload) <= 100: - self._state = int(payload) + if int(payload) > 0: + self._state = False + else: + self._state = True self._position = int(payload) self.update_ha_state() else: @@ -129,11 +133,7 @@ class MqttCover(CoverDevice): @property def is_closed(self): """Return if the cover is closed.""" - if self.current_cover_position is not None: - if self.current_cover_position > 0: - return False - else: - return True + return self._state @property def current_cover_position(self): diff --git a/homeassistant/components/cover/zwave.py b/homeassistant/components/cover/zwave.py index 83d55001fe2..d7ebfb834e8 100644 --- a/homeassistant/components/cover/zwave.py +++ b/homeassistant/components/cover/zwave.py @@ -96,6 +96,8 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice): @property def is_closed(self): """Return if the cover is closed.""" + if self.current_cover_position is None: + return None if self.current_cover_position > 0: return False else: diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index b260eccd7d1..a4f65ab4ea4 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -388,7 +388,8 @@ def load_config(path: str, hass: HomeAssistantType, consider_home: timedelta): try: return [ Device(hass, consider_home, device.get('track', False), - str(dev_id).lower(), str(device.get('mac')).upper(), + str(dev_id).lower(), None if device.get('mac') is None + else str(device.get('mac')).upper(), device.get('name'), device.get('picture'), device.get('gravatar'), device.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE)) diff --git a/homeassistant/components/device_tracker/bluetooth_le_tracker.py b/homeassistant/components/device_tracker/bluetooth_le_tracker.py index 6576f46bad7..ce8a535ff57 100644 --- a/homeassistant/components/device_tracker/bluetooth_le_tracker.py +++ b/homeassistant/components/device_tracker/bluetooth_le_tracker.py @@ -2,16 +2,19 @@ import logging from datetime import timedelta +import voluptuous as vol from homeassistant.helpers.event import track_point_in_utc_time from homeassistant.components.device_tracker import ( YAML_DEVICES, CONF_TRACK_NEW, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL, + PLATFORM_SCHEMA, load_config, ) import homeassistant.util as util import homeassistant.util.dt as dt_util +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -19,6 +22,11 @@ REQUIREMENTS = ['gattlib==0.20150805'] BLE_PREFIX = 'BLE_' MIN_SEEN_NEW = 5 +CONF_SCAN_DURATION = "scan_duration" + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_SCAN_DURATION, default=10): cv.positive_int +}) def setup_scanner(hass, config, see): @@ -51,12 +59,13 @@ def setup_scanner(hass, config, see): """Discover Bluetooth LE devices.""" _LOGGER.debug("Discovering Bluetooth LE devices") service = DiscoveryService() - devices = service.discover(10) + devices = service.discover(duration) _LOGGER.debug("Bluetooth LE devices discovered = %s", devices) return devices yaml_path = hass.config.path(YAML_DEVICES) + duration = config.get(CONF_SCAN_DURATION) devs_to_track = [] devs_donot_track = [] @@ -65,11 +74,13 @@ def setup_scanner(hass, config, see): # to 0 for device in load_config(yaml_path, hass, 0): # check if device is a valid bluetooth device - if device.mac and device.mac[:3].upper() == BLE_PREFIX: + if device.mac and device.mac[:4].upper() == BLE_PREFIX: if device.track: - devs_to_track.append(device.mac[3:]) + _LOGGER.debug("Adding %s to BLE tracker", device.mac) + devs_to_track.append(device.mac[4:]) else: - devs_donot_track.append(device.mac[3:]) + _LOGGER.debug("Adding %s to BLE do not track", device.mac) + devs_donot_track.append(device.mac[4:]) # if track new devices is true discover new devices # on every scan. @@ -96,7 +107,7 @@ def setup_scanner(hass, config, see): if track_new: for address in devs: if address not in devs_to_track and \ - address not in devs_donot_track: + address not in devs_donot_track: _LOGGER.info("Discovered Bluetooth LE device %s", address) see_device(address, devs[address], new_device=True) diff --git a/homeassistant/components/fan/insteon_hub.py b/homeassistant/components/fan/insteon_hub.py deleted file mode 100644 index 4d65ee1f02b..00000000000 --- a/homeassistant/components/fan/insteon_hub.py +++ /dev/null @@ -1,66 +0,0 @@ -""" -Support for Insteon FanLinc. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/fan.insteon/ -""" - -import logging - -from homeassistant.components.fan import (FanEntity, SUPPORT_SET_SPEED, - SPEED_OFF, SPEED_LOW, SPEED_MED, - SPEED_HIGH) -from homeassistant.components.insteon_hub import (InsteonDevice, INSTEON, - filter_devices) -from homeassistant.const import STATE_UNKNOWN - -_LOGGER = logging.getLogger(__name__) - -DEVICE_CATEGORIES = [ - { - 'DevCat': 1, - 'SubCat': [46] - } -] - -DEPENDENCIES = ['insteon_hub'] - - -def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Insteon Hub fan platform.""" - devs = [] - for device in filter_devices(INSTEON.devices, DEVICE_CATEGORIES): - devs.append(InsteonFanDevice(device)) - add_devices(devs) - - -class InsteonFanDevice(InsteonDevice, FanEntity): - """Represet an insteon fan device.""" - - def __init__(self, node: object) -> None: - """Initialize the device.""" - super(InsteonFanDevice, self).__init__(node) - self.speed = STATE_UNKNOWN # Insteon hub can't get state via REST - - def turn_on(self, speed: str=None): - """Turn the fan on.""" - self.set_speed(speed if speed else SPEED_MED) - - def turn_off(self): - """Turn the fan off.""" - self.set_speed(SPEED_OFF) - - def set_speed(self, speed: str) -> None: - """Set the fan speed.""" - if self._send_command('fan', payload={'speed', speed}): - self.speed = speed - - @property - def supported_features(self) -> int: - """Get the supported features for device.""" - return SUPPORT_SET_SPEED - - @property - def speed_list(self) -> list: - """Get the available speeds for the fan.""" - return [SPEED_OFF, SPEED_LOW, SPEED_MED, SPEED_HIGH] diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 5fce36f45b1..b1bd204e1ce 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -2,7 +2,7 @@ FINGERPRINTS = { "core.js": "1fd10c1fcdf56a61f60cf861d5a0368c", - "frontend.html": "88c97d278de3320278da6c32fe9e7d61", + "frontend.html": "610cc799225ede933a9894b64bb35717", "mdi.html": "710b84acc99b32514f52291aba9cd8e8", "panels/ha-panel-dev-event.html": "3cc881ae8026c0fba5aa67d334a3ab2b", "panels/ha-panel-dev-info.html": "34e2df1af32e60fffcafe7e008a92169", diff --git a/homeassistant/components/frontend/www_static/core.js.gz b/homeassistant/components/frontend/www_static/core.js.gz index 847c937ec81..5accff51795 100644 Binary files a/homeassistant/components/frontend/www_static/core.js.gz and b/homeassistant/components/frontend/www_static/core.js.gz differ diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 128248ce62e..367b8d15a5a 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -1,5 +1,5 @@ \ No newline at end of file +var r=t.propertyDataFromStyles(n._styles,this),i=!this.__notStyleScopeCacheable;i&&(r.key.customStyle=this.customStyle,e=n._styleCache.retrieve(this.is,r.key,this._styles));var a=Boolean(e);a?this._styleProperties=e._styleProperties:this._computeStyleProperties(r.properties),this._computeOwnStyleProperties(),a||(e=o.retrieve(this.is,this._ownStyleProperties,this._styles));var l=Boolean(e)&&!a,h=this._applyStyleProperties(e);a||(h=h&&s?h.cloneNode(!0):h,e={style:h,_scopeSelector:this._scopeSelector,_styleProperties:this._styleProperties},i&&(r.key.customStyle={},this.mixin(r.key.customStyle,this.customStyle),n._styleCache.store(this.is,e,r.key,this._styles)),l||o.store(this.is,Object.create(e),this._ownStyleProperties,this._styles))},_computeStyleProperties:function(e){var n=this._findStyleHost();n._styleProperties||n._computeStyleProperties();var r=Object.create(n._styleProperties),s=t.hostAndRootPropertiesForScope(this);this.mixin(r,s.hostProps),e=e||t.propertyDataFromStyles(n._styles,this).properties,this.mixin(r,e),this.mixin(r,s.rootProps),t.mixinCustomStyle(r,this.customStyle),t.reify(r),this._styleProperties=r},_computeOwnStyleProperties:function(){for(var e,t={},n=0;n0&&l.push(t);return[{removed:a,added:l}]}},Polymer.Collection.get=function(e){return Polymer._collections.get(e)||new Polymer.Collection(e)},Polymer.Collection.applySplices=function(e,t){var n=Polymer._collections.get(e);return n?n._applySplices(t):null},Polymer({is:"dom-repeat",extends:"template",_template:null,properties:{items:{type:Array},as:{type:String,value:"item"},indexAs:{type:String,value:"index"},sort:{type:Function,observer:"_sortChanged"},filter:{type:Function,observer:"_filterChanged"},observe:{type:String,observer:"_observeChanged"},delay:Number,renderedItemCount:{type:Number,notify:!0,readOnly:!0},initialCount:{type:Number,observer:"_initializeChunking"},targetFramerate:{type:Number,value:20},_targetFrameTime:{type:Number,computed:"_computeFrameTime(targetFramerate)"}},behaviors:[Polymer.Templatizer],observers:["_itemsChanged(items.*)"],created:function(){this._instances=[],this._pool=[],this._limit=1/0;var e=this;this._boundRenderChunk=function(){e._renderChunk()}},detached:function(){this.__isDetached=!0;for(var e=0;e=0;t--){var n=this._instances[t];n.isPlaceholder&&t=this._limit&&(n=this._downgradeInstance(t,n.__key__)),e[n.__key__]=t,n.isPlaceholder||n.__setProperty(this.indexAs,t,!0)}this._pool.length=0,this._setRenderedItemCount(this._instances.length),this.fire("dom-change"),this._tryRenderChunk()},_applyFullRefresh:function(){var e,t=this.collection;if(this._sortFn)e=t?t.getKeys():[];else{e=[];var n=this.items;if(n)for(var r=0;r=r;a--)this._detachAndRemoveInstance(a)},_numericSort:function(e,t){return e-t},_applySplicesUserSort:function(e){for(var t,n,r=this.collection,s={},i=0;i=0;i--){var h=a[i];void 0!==h&&this._detachAndRemoveInstance(h)}var c=this;if(l.length){this._filterFn&&(l=l.filter(function(e){return c._filterFn(r.getItem(e))})),l.sort(function(e,t){return c._sortFn(r.getItem(e),r.getItem(t))});var u=0;for(i=0;i>1,a=this._instances[o].__key__,l=this._sortFn(n.getItem(a),r);if(l<0)e=o+1;else{if(!(l>0)){i=o;break}s=o-1}}return i<0&&(i=s+1),this._insertPlaceholder(i,t),i},_applySplicesArrayOrder:function(e){for(var t,n=0;n=0?(e=this.as+"."+e.substring(n+1),i._notifyPath(e,t,!0)):i.__setProperty(this.as,t,!0))}},itemForElement:function(e){var t=this.modelForElement(e);return t&&t[this.as]},keyForElement:function(e){var t=this.modelForElement(e);return t&&t.__key__},indexForElement:function(e){var t=this.modelForElement(e);return t&&t[this.indexAs]}}),Polymer({is:"array-selector",_template:null,properties:{items:{type:Array,observer:"clearSelection"},multi:{type:Boolean,value:!1,observer:"clearSelection"},selected:{type:Object,notify:!0},selectedItem:{type:Object,notify:!0},toggle:{type:Boolean,value:!1}},clearSelection:function(){if(Array.isArray(this.selected))for(var e=0;e \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/frontend.html.gz b/homeassistant/components/frontend/www_static/frontend.html.gz index bde1fe17a8d..5f719af4014 100644 Binary files a/homeassistant/components/frontend/www_static/frontend.html.gz and b/homeassistant/components/frontend/www_static/frontend.html.gz differ diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index 670ba0292bf..659ec6552f7 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 670ba0292bfca2b65aeca70804c0856b6cabf10e +Subproject commit 659ec6552f761ff4779dd52ee35d26f7be5e111f diff --git a/homeassistant/components/frontend/www_static/mdi.html.gz b/homeassistant/components/frontend/www_static/mdi.html.gz index 3ab4238a397..09cadf26e6b 100644 Binary files a/homeassistant/components/frontend/www_static/mdi.html.gz and b/homeassistant/components/frontend/www_static/mdi.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.html.gz index 686df6e41ee..d32bfd90fb1 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-info.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-info.html.gz index f2414ac3907..d42283913c0 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-info.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-info.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html.gz index 95cdd62d016..8ff19b321b3 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html.gz index 679cc353108..b7118f9fc63 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-template.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-template.html.gz index 997d951f324..3671424d539 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-template.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-template.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-history.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-history.html.gz index c0368bed006..aa972aff21b 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-history.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-history.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-iframe.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-iframe.html.gz index 7425bda4684..05702a62ffa 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-iframe.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-iframe.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-logbook.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-logbook.html.gz index 124f8f5ace2..c68590101cf 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-logbook.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-logbook.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-map.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-map.html.gz index 97afe1f0e84..30adcaafce3 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-map.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-map.html.gz differ diff --git a/homeassistant/components/frontend/www_static/service_worker.js b/homeassistant/components/frontend/www_static/service_worker.js index d7c78d557d4..120314a120f 100644 --- a/homeassistant/components/frontend/www_static/service_worker.js +++ b/homeassistant/components/frontend/www_static/service_worker.js @@ -1 +1 @@ -"use strict";function setOfCachedUrls(e){return e.keys().then(function(e){return e.map(function(e){return e.url})}).then(function(e){return new Set(e)})}function notificationEventCallback(e,t){firePushCallback({action:t.action,data:t.notification.data,tag:t.notification.tag,type:e},t.notification.data.jwt)}function firePushCallback(e,t){delete e.data.jwt,0===Object.keys(e.data).length&&e.data.constructor===Object&&delete e.data,fetch("/api/notify.html5/callback",{method:"POST",headers:new Headers({"Content-Type":"application/json",Authorization:"Bearer "+t}),body:JSON.stringify(e)})}var precacheConfig=[["/","cf23e37da78b0dfa560d4a1895b39f76"],["/frontend/panels/dev-event-3cc881ae8026c0fba5aa67d334a3ab2b.html","e22ed0d2d10777c87eb9620d81f525b4"],["/frontend/panels/dev-info-34e2df1af32e60fffcafe7e008a92169.html","7e939dc762dc0c0ec769db4ea76a4b09"],["/frontend/panels/dev-service-bb5c587ada694e0fd42ceaaedd6fe6aa.html","782c4860c5e8ab274231ba9dfd528f29"],["/frontend/panels/dev-state-4608326978256644c42b13940c028e0a.html","26758b741ac1b7c8e9cfcb24762d8774"],["/frontend/panels/dev-template-0a099d4589636ed3038a3e9f020468a7.html","99114026cf9193263c74cc25f9f6a469"],["/frontend/panels/map-af7d04aff7dd5479c5a0016bc8d4dd7d.html","6031df1b4d23d5b321208449b2d293f8"],["/static/core-1fd10c1fcdf56a61f60cf861d5a0368c.js","800ebb1bbb48274790f2ee1a2e53a24c"],["/static/frontend-88c97d278de3320278da6c32fe9e7d61.html","be147f0848a4730291bac9cdb76e2d65"],["/static/mdi-710b84acc99b32514f52291aba9cd8e8.html","149c8eaf6bb78a9b642c7bcedab86900"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/icons/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/icons/favicon.ico","04235bda7843ec2fceb1cbe2bc696cf4"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"],["static/webcomponents-lite.min.js","b0f32ad3c7749c40d486603f31c9d8b1"]],cacheName="sw-precache-v2--"+(self.registration?self.registration.scope:""),ignoreUrlParametersMatching=[/^utm_/],addDirectoryIndex=function(e,t){var a=new URL(e);return"/"===a.pathname.slice(-1)&&(a.pathname+=t),a.toString()},createCacheKey=function(e,t,a,n){var c=new URL(e);return n&&c.toString().match(n)||(c.search+=(c.search?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(a)),c.toString()},isPathWhitelisted=function(e,t){if(0===e.length)return!0;var a=new URL(t).pathname;return e.some(function(e){return a.match(e)})},stripIgnoredUrlParameters=function(e,t){var a=new URL(e);return a.search=a.search.slice(1).split("&").map(function(e){return e.split("=")}).filter(function(e){return t.every(function(t){return!t.test(e[0])})}).map(function(e){return e.join("=")}).join("&"),a.toString()},hashParamName="_sw-precache",urlsToCacheKeys=new Map(precacheConfig.map(function(e){var t=e[0],a=e[1],n=new URL(t,self.location),c=createCacheKey(n,hashParamName,a,!1);return[n.toString(),c]}));self.addEventListener("install",function(e){e.waitUntil(caches.open(cacheName).then(function(e){return setOfCachedUrls(e).then(function(t){return Promise.all(Array.from(urlsToCacheKeys.values()).map(function(a){if(!t.has(a))return e.add(new Request(a,{credentials:"same-origin"}))}))})}).then(function(){return self.skipWaiting()}))}),self.addEventListener("activate",function(e){var t=new Set(urlsToCacheKeys.values());e.waitUntil(caches.open(cacheName).then(function(e){return e.keys().then(function(a){return Promise.all(a.map(function(a){if(!t.has(a.url))return e["delete"](a)}))})}).then(function(){return self.clients.claim()}))}),self.addEventListener("fetch",function(e){if("GET"===e.request.method){var t,a=stripIgnoredUrlParameters(e.request.url,ignoreUrlParametersMatching);t=urlsToCacheKeys.has(a);var n="index.html";!t&&n&&(a=addDirectoryIndex(a,n),t=urlsToCacheKeys.has(a));var c="/";!t&&c&&"navigate"===e.request.mode&&isPathWhitelisted(["^((?!(static|api|local|service_worker.js|manifest.json)).)*$"],e.request.url)&&(a=new URL(c,self.location).toString(),t=urlsToCacheKeys.has(a)),t&&e.respondWith(caches.open(cacheName).then(function(e){return e.match(urlsToCacheKeys.get(a))})["catch"](function(t){return console.warn('Couldn\'t serve response for "%s" from cache: %O',e.request.url,t),fetch(e.request)}))}}),self.addEventListener("push",function(e){var t;e.data&&(t=e.data.json(),e.waitUntil(self.registration.showNotification(t.title,t).then(function(e){firePushCallback({type:"received",tag:t.tag,data:t.data},t.data.jwt)})))}),self.addEventListener("notificationclick",function(e){var t;notificationEventCallback("clicked",e),e.notification.close(),e.notification.data&&e.notification.data.url&&(t=e.notification.data.url,t&&e.waitUntil(clients.matchAll({type:"window"}).then(function(e){var a,n;for(a=0;a bool: - """Check http response for successful status.""" - return 'status' in response and response['status'] == 'succeeded' +def setup(hass, config): + """Setup Insteon Hub component. - -def filter_devices(devices: list, categories: list) -> list: - """Filter insteon device list by category/subcategory.""" - categories = (categories - if isinstance(categories, list) - else [categories]) - matching_devices = [] - for device in devices: - if any( - device.DevCat == c[DEVCAT] and - (SUBCAT not in c or device.SubCat in c[SUBCAT]) - for c in categories): - matching_devices.append(device) - return matching_devices - - -def setup(hass, config: dict) -> bool: - """Setup Insteon Hub component.""" + This will automatically import associated lights. + """ if not validate_config( config, {DOMAIN: [CONF_USERNAME, CONF_PASSWORD, CONF_API_KEY]}, _LOGGER): return False - from insteon import Insteon + import insteon username = config[DOMAIN][CONF_USERNAME] password = config[DOMAIN][CONF_PASSWORD] api_key = config[DOMAIN][CONF_API_KEY] global INSTEON - INSTEON = Insteon(username, password, api_key) + INSTEON = insteon.Insteon(username, password, api_key) if INSTEON is None: - _LOGGER.error('Could not connect to Insteon service.') + _LOGGER.error("Could not connect to Insteon service.") return - for device_class in DEVICE_CLASSES: - discovery.load_platform(hass, device_class, DOMAIN, {}, config) + discovery.load_platform(hass, 'light', DOMAIN, {}, config) + return True - - -class InsteonDevice(Entity): - """Represents an insteon device.""" - - def __init__(self: Entity, node: object) -> None: - """Initialize the insteon device.""" - self._node = node - - def update(self: Entity) -> None: - """Update state of the device.""" - pass - - @property - def name(self: Entity) -> str: - """Name of the insteon device.""" - return self._node.DeviceName - - @property - def unique_id(self: Entity) -> str: - """Unique identifier for the device.""" - return self._node.DeviceID - - @property - def supported_features(self: Entity) -> int: - """Supported feature flags.""" - return 0 - - def _send_command(self: Entity, command: str, level: int=None, - payload: dict=None) -> bool: - """Send command to insteon device.""" - resp = self._node.send_command(command, payload=payload, level=level, - wait=True) - return _is_successful(resp) diff --git a/homeassistant/components/light/insteon_hub.py b/homeassistant/components/light/insteon_hub.py index 29254735ced..70beadb6c1d 100644 --- a/homeassistant/components/light/insteon_hub.py +++ b/homeassistant/components/light/insteon_hub.py @@ -4,76 +4,74 @@ Support for Insteon Hub lights. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/insteon_hub/ """ -from homeassistant.components.insteon_hub import (INSTEON, InsteonDevice) +from homeassistant.components.insteon_hub import INSTEON from homeassistant.components.light import (ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) SUPPORT_INSTEON_HUB = SUPPORT_BRIGHTNESS -DEPENDENCIES = ['insteon_hub'] - def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Insteon Hub light platform.""" devs = [] for device in INSTEON.devices: if device.DeviceCategory == "Switched Lighting Control": - devs.append(InsteonLightDevice(device)) + devs.append(InsteonToggleDevice(device)) if device.DeviceCategory == "Dimmable Lighting Control": - devs.append(InsteonDimmableDevice(device)) + devs.append(InsteonToggleDevice(device)) add_devices(devs) -class InsteonLightDevice(InsteonDevice, Light): - """A representation of a light device.""" +class InsteonToggleDevice(Light): + """An abstract Class for an Insteon node.""" - def __init__(self, node: object) -> None: + def __init__(self, node): """Initialize the device.""" - super(InsteonLightDevice, self).__init__(node) + self.node = node self._value = 0 - def update(self) -> None: - """Update state of the device.""" - resp = self._node.send_command('get_status', wait=True) + @property + def name(self): + """Return the the name of the node.""" + return self.node.DeviceName + + @property + def unique_id(self): + """Return the ID of this insteon node.""" + return self.node.DeviceID + + @property + def brightness(self): + """Return the brightness of this light between 0..255.""" + return self._value / 100 * 255 + + def update(self): + """Update state of the sensor.""" + resp = self.node.send_command('get_status', wait=True) try: self._value = resp['response']['level'] except KeyError: pass @property - def is_on(self) -> None: + def is_on(self): """Return the boolean response if the node is on.""" return self._value != 0 - def turn_on(self, **kwargs) -> None: - """Turn device on.""" - if self._send_command('on'): - self._value = 100 - - def turn_off(self, **kwargs) -> None: - """Turn device off.""" - if self._send_command('off'): - self._value = 0 - - -class InsteonDimmableDevice(InsteonLightDevice): - """A representation for a dimmable device.""" - @property - def brightness(self) -> int: - """Return the brightness of this light between 0..255.""" - return round(self._value / 100 * 255, 0) # type: int - - @property - def supported_features(self) -> int: + def supported_features(self): """Flag supported features.""" return SUPPORT_INSTEON_HUB - def turn_on(self, **kwargs) -> None: + def turn_on(self, **kwargs): """Turn device on.""" - level = 100 # type: int if ATTR_BRIGHTNESS in kwargs: - level = round(kwargs[ATTR_BRIGHTNESS] / 255 * 100, 0) # type: int + self._value = kwargs[ATTR_BRIGHTNESS] / 255 * 100 + self.node.send_command('on', self._value) + else: + self._value = 100 + self.node.send_command('on') - if self._send_command('on', level=level): - self._value = level + def turn_off(self, **kwargs): + """Turn device off.""" + self.node.send_command('off') diff --git a/homeassistant/const.py b/homeassistant/const.py index a720c989907..f6f0cd7855d 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ # coding: utf-8 """Constants used by Home Assistant components.""" -__version__ = '0.27.1' +__version__ = '0.27.2' REQUIRED_PYTHON_VER = (3, 4) PLATFORM_FORMAT = '{}.{}' @@ -244,7 +244,7 @@ SERVICE_OPEN_COVER = 'open_cover' SERVICE_OPEN_COVER_TILT = 'open_cover_tilt' SERVICE_SET_COVER_POSITION = 'set_cover_position' SERVICE_SET_COVER_TILT_POSITION = 'set_cover_tilt_position' -SERVICE_STOP_COVER = 'stop' +SERVICE_STOP_COVER = 'stop_cover' SERVICE_STOP_COVER_TILT = 'stop_cover_tilt' SERVICE_MOVE_UP = 'move_up' diff --git a/requirements_all.txt b/requirements_all.txt index 934dec82bed..56d445daf54 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -204,7 +204,7 @@ https://github.com/wokar/pylgnetcast/archive/v0.2.0.zip#pylgnetcast==0.2.0 influxdb==3.0.0 # homeassistant.components.insteon_hub -insteon_hub==0.5.0 +insteon_hub==0.4.5 # homeassistant.components.media_player.kodi jsonrpc-requests==0.3 diff --git a/tests/components/fan/test_insteon_hub.py b/tests/components/fan/test_insteon_hub.py deleted file mode 100644 index dfdb4b7a9f0..00000000000 --- a/tests/components/fan/test_insteon_hub.py +++ /dev/null @@ -1,73 +0,0 @@ -"""Tests for the insteon hub fan platform.""" -import unittest - -from homeassistant.const import (STATE_OFF, STATE_ON) -from homeassistant.components.fan import (SPEED_LOW, SPEED_MED, SPEED_HIGH, - ATTR_SPEED) -from homeassistant.components.fan.insteon_hub import (InsteonFanDevice, - SUPPORT_SET_SPEED) - - -class Node(object): - """Fake insteon node.""" - - def __init__(self, name, id, dev_cat, sub_cat): - """Initialize fake insteon node.""" - self.DeviceName = name - self.DeviceID = id - self.DevCat = dev_cat - self.SubCat = sub_cat - self.response = None - - def send_command(self, command, payload, level, wait): - """Send fake command.""" - return self.response - - -class TestInsteonHubFanDevice(unittest.TestCase): - """Test around insteon hub fan device methods.""" - - _NODE = Node('device', '12345', '1', '46') - - def setUp(self): - """Initialize test data.""" - self._DEVICE = InsteonFanDevice(self._NODE) - - def tearDown(self): - """Tear down test data.""" - self._DEVICE = None - - def test_properties(self): - """Test basic properties.""" - self.assertEqual(self._NODE.DeviceName, self._DEVICE.name) - self.assertEqual(self._NODE.DeviceID, self._DEVICE.unique_id) - self.assertEqual(SUPPORT_SET_SPEED, self._DEVICE.supported_features) - - for speed in [STATE_OFF, SPEED_LOW, SPEED_MED, SPEED_HIGH]: - self.assertIn(speed, self._DEVICE.speed_list) - - def test_turn_on(self): - """Test the turning on device.""" - self._NODE.response = { - 'status': 'succeeded' - } - self.assertEqual(STATE_OFF, self._DEVICE.state) - self._DEVICE.turn_on() - - self.assertEqual(STATE_ON, self._DEVICE.state) - - self._DEVICE.turn_on(SPEED_MED) - - self.assertEqual(STATE_ON, self._DEVICE.state) - self.assertEqual(SPEED_MED, self._DEVICE.state_attributes[ATTR_SPEED]) - - def test_turn_off(self): - """Test turning off device.""" - self._NODE.response = { - 'status': 'succeeded' - } - self.assertEqual(STATE_OFF, self._DEVICE.state) - self._DEVICE.turn_on() - self.assertEqual(STATE_ON, self._DEVICE.state) - self._DEVICE.turn_off() - self.assertEqual(STATE_OFF, self._DEVICE.state) diff --git a/tests/components/test_emulated_hue.py b/tests/components/test_emulated_hue.py index c9efa6e9fda..9433aacc20b 100755 --- a/tests/components/test_emulated_hue.py +++ b/tests/components/test_emulated_hue.py @@ -1,3 +1,4 @@ +"""The tests for the emulated Hue component.""" import time import json import threading @@ -11,8 +12,7 @@ import homeassistant.components as core_components from homeassistant.components import emulated_hue, http, light, mqtt from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.components.emulated_hue import ( - HUE_API_STATE_ON, HUE_API_STATE_BRI -) + HUE_API_STATE_ON, HUE_API_STATE_BRI) from tests.common import get_test_instance_port, get_test_home_assistant @@ -27,6 +27,7 @@ mqtt_broker = None def setUpModule(): + """Setup things to be run when tests are started.""" global mqtt_broker mqtt_broker = MQTTBroker('127.0.0.1', MQTT_BROKER_PORT) @@ -34,12 +35,14 @@ def setUpModule(): def tearDownModule(): + """Stop everything that was started.""" global mqtt_broker mqtt_broker.stop() def setup_hass_instance(emulated_hue_config): + """Setup the Home Assistant instance to test.""" hass = get_test_home_assistant() # We need to do this to get access to homeassistant/turn_(on,off) @@ -55,15 +58,19 @@ def setup_hass_instance(emulated_hue_config): def start_hass_instance(hass): + """Start the Home Assistant instance to test.""" hass.start() time.sleep(0.05) class TestEmulatedHue(unittest.TestCase): + """Test the emulated Hue component.""" + hass = None @classmethod def setUpClass(cls): + """Setup the class.""" cls.hass = setup_hass_instance({ emulated_hue.DOMAIN: { emulated_hue.CONF_LISTEN_PORT: BRIDGE_SERVER_PORT @@ -73,9 +80,11 @@ class TestEmulatedHue(unittest.TestCase): @classmethod def tearDownClass(cls): + """Stop the class.""" cls.hass.stop() def test_description_xml(self): + """Test the description.""" import xml.etree.ElementTree as ET result = requests.get( @@ -91,6 +100,7 @@ class TestEmulatedHue(unittest.TestCase): self.fail('description.xml is not valid XML!') def test_create_username(self): + """Test the creation of an username.""" request_json = {'devicetype': 'my_device'} result = requests.post( @@ -107,6 +117,7 @@ class TestEmulatedHue(unittest.TestCase): self.assertTrue('username' in success_json['success']) def test_valid_username_request(self): + """Test request with a valid username.""" request_json = {'invalid_key': 'my_device'} result = requests.post( @@ -117,8 +128,11 @@ class TestEmulatedHue(unittest.TestCase): class TestEmulatedHueExposedByDefault(unittest.TestCase): + """Test class for emulated hue component.""" + @classmethod def setUpClass(cls): + """Setup the class.""" cls.hass = setup_hass_instance({ emulated_hue.DOMAIN: { emulated_hue.CONF_LISTEN_PORT: BRIDGE_SERVER_PORT, @@ -177,9 +191,11 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase): @classmethod def tearDownClass(cls): + """Stop the class.""" cls.hass.stop() def test_discover_lights(self): + """Test the discovery of lights.""" result = requests.get( BRIDGE_URL_BASE.format('/api/username/lights'), timeout=5) @@ -194,6 +210,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase): self.assertTrue('light.kitchen_light' not in result_json) def test_get_light_state(self): + """Test the getting of light state.""" # Turn office light on and set to 127 brightness self.hass.services.call( light.DOMAIN, const.SERVICE_TURN_ON, @@ -229,6 +246,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase): self.assertEqual(kitchen_result.status_code, 404) def test_put_light_state(self): + """Test the seeting of light states.""" self.perform_put_test_on_office_light() # Turn the bedroom light on first @@ -264,6 +282,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase): self.assertEqual(kitchen_result.status_code, 404) def test_put_with_form_urlencoded_content_type(self): + """Test the form with urlencoded content.""" # Needed for Alexa self.perform_put_test_on_office_light( 'application/x-www-form-urlencoded') @@ -278,6 +297,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase): self.assertEqual(result.status_code, 400) def test_entity_not_found(self): + """Test for entity which are not found.""" result = requests.get( BRIDGE_URL_BASE.format( '/api/username/lights/{}'.format("not.existant_entity")), @@ -293,6 +313,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase): self.assertEqual(result.status_code, 404) def test_allowed_methods(self): + """Test the allowed methods.""" result = requests.get( BRIDGE_URL_BASE.format( '/api/username/lights/{}/state'.format("light.office_light"))) @@ -313,6 +334,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase): self.assertEqual(result.status_code, 405) def test_proper_put_state_request(self): + """Test the request to set the state.""" # Test proper on value parsing result = requests.put( BRIDGE_URL_BASE.format( @@ -334,6 +356,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase): def perform_put_test_on_office_light(self, content_type='application/json'): + """Test the setting of a light.""" # Turn the office light off first self.hass.services.call( light.DOMAIN, const.SERVICE_TURN_OFF, @@ -361,6 +384,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase): self.assertEqual(office_light.attributes[light.ATTR_BRIGHTNESS], 56) def perform_get_light_state(self, entity_id, expected_status): + """Test the gettting of a light state.""" result = requests.get( BRIDGE_URL_BASE.format( '/api/username/lights/{}'.format(entity_id)), timeout=5) @@ -377,6 +401,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase): def perform_put_light_state(self, entity_id, is_on, brightness=None, content_type='application/json'): + """Test the setting of a light state.""" url = BRIDGE_URL_BASE.format( '/api/username/lights/{}/state'.format(entity_id)) @@ -432,6 +457,7 @@ class MQTTBroker(object): self._thread.join() def _run_loop(self): + """Run the loop.""" asyncio.set_event_loop(self._loop) self._loop.run_until_complete(self._broker_coroutine()) @@ -442,4 +468,5 @@ class MQTTBroker(object): @asyncio.coroutine def _broker_coroutine(self): + """The Broker coroutine.""" yield from self._broker.start()