diff --git a/.github/main.workflow b/.github/main.workflow new file mode 100644 index 00000000000..54869682e1c --- /dev/null +++ b/.github/main.workflow @@ -0,0 +1,41 @@ +workflow "Python 3.7 - tox" { + resolves = ["Python 3.7 - tests"] + on = "push" +} + +action "Python 3.7 - tests" { + uses = "home-assistant/actions/py37-tox@master" + args = "-e py37" +} + +workflow "Python 3.6 - tox" { + resolves = ["Python 3.6 - tests"] + on = "push" +} + +action "Python 3.6 - tests" { + uses = "home-assistant/actions/py36-tox@master" + args = "-e py36" +} + +workflow "Python 3.5 - tox" { + resolves = ["Pyton 3.5 - typing"] + on = "push" +} + +action "Python 3.5 - tests" { + uses = "home-assistant/actions/py35-tox@master" + args = "-e py35" +} + +action "Python 3.5 - lints" { + uses = "home-assistant/actions/py35-tox@master" + needs = ["Python 3.5 - tests"] + args = "-e lint" +} + +action "Pyton 3.5 - typing" { + uses = "home-assistant/actions/py35-tox@master" + args = "-e typing" + needs = ["Python 3.5 - lints"] +} diff --git a/homeassistant/components/blink/__init__.py b/homeassistant/components/blink/__init__.py index 8e95f967396..488209e3689 100644 --- a/homeassistant/components/blink/__init__.py +++ b/homeassistant/components/blink/__init__.py @@ -10,7 +10,7 @@ from homeassistant.const import ( CONF_BINARY_SENSORS, CONF_SENSORS, CONF_FILENAME, CONF_MONITORED_CONDITIONS, TEMP_FAHRENHEIT) -REQUIREMENTS = ['blinkpy==0.12.1'] +REQUIREMENTS = ['blinkpy==0.13.1'] _LOGGER = logging.getLogger(__name__) @@ -44,7 +44,7 @@ BINARY_SENSORS = { SENSORS = { TYPE_TEMPERATURE: ['Temperature', TEMP_FAHRENHEIT, 'mdi:thermometer'], TYPE_BATTERY: ['Battery', '%', 'mdi:battery-80'], - TYPE_WIFI_STRENGTH: ['Wifi Signal', 'bars', 'mdi:wifi-strength-2'], + TYPE_WIFI_STRENGTH: ['Wifi Signal', 'dBm', 'mdi:wifi-strength-2'], } BINARY_SENSOR_SCHEMA = vol.Schema({ diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index d7c1aabdb49..0e674740269 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -21,7 +21,7 @@ from homeassistant.loader import bind_hass from .storage import async_setup_frontend_storage -REQUIREMENTS = ['home-assistant-frontend==20190305.0'] +REQUIREMENTS = ['home-assistant-frontend==20190305.1'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', diff --git a/homeassistant/components/homematicip_cloud/__init__.py b/homeassistant/components/homematicip_cloud/__init__.py index fd07356d7fb..6f785565661 100644 --- a/homeassistant/components/homematicip_cloud/__init__.py +++ b/homeassistant/components/homematicip_cloud/__init__.py @@ -60,11 +60,14 @@ async def async_setup_entry(hass, entry): # Register hap as device in registry. device_registry = await dr.async_get_registry(hass) home = hap.home + # Add the HAP name from configuration if set. + hapname = home.label \ + if not home.name else "{} {}".format(home.label, home.name) device_registry.async_get_or_create( config_entry_id=home.id, identifiers={(DOMAIN, home.id)}, manufacturer='eQ-3', - name=home.label, + name=hapname, model=home.modelType, sw_version=home.currentAPVersion, ) diff --git a/homeassistant/components/homematicip_cloud/binary_sensor.py b/homeassistant/components/homematicip_cloud/binary_sensor.py index 4b82a500bde..d6ce4152001 100644 --- a/homeassistant/components/homematicip_cloud/binary_sensor.py +++ b/homeassistant/components/homematicip_cloud/binary_sensor.py @@ -4,6 +4,8 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.homematicip_cloud import ( DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice) +from homeassistant.components.homematicip_cloud.device import ( + ATTR_GROUP_MEMBER_UNREACHABLE) DEPENDENCIES = ['homematicip_cloud'] @@ -31,8 +33,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities): AsyncWaterSensor, AsyncRotaryHandleSensor, AsyncMotionDetectorPushButton) - from homematicip.group import ( - SecurityGroup, SecurityZoneGroup) + from homematicip.aio.group import ( + AsyncSecurityGroup, AsyncSecurityZoneGroup) home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home devices = [] @@ -48,9 +50,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): devices.append(HomematicipWaterDetector(home, device)) for group in home.groups: - if isinstance(group, SecurityGroup): + if isinstance(group, AsyncSecurityGroup): devices.append(HomematicipSecuritySensorGroup(home, group)) - elif isinstance(group, SecurityZoneGroup): + elif isinstance(group, AsyncSecurityZoneGroup): devices.append(HomematicipSecurityZoneSensorGroup(home, group)) if devices: @@ -137,27 +139,37 @@ class HomematicipSecurityZoneSensorGroup(HomematicipGenericDevice, """Return the class of this sensor.""" return 'safety' + @property + def available(self): + """Security-Group available.""" + # A security-group must be available, and should not be affected by + # the individual availability of group members. + return True + @property def device_state_attributes(self): """Return the state attributes of the security zone group.""" attr = super().device_state_attributes if self._device.motionDetected: - attr.update({ATTR_MOTIONDETECTED: True}) + attr[ATTR_MOTIONDETECTED] = True if self._device.presenceDetected: - attr.update({ATTR_PRESENCEDETECTED: True}) + attr[ATTR_PRESENCEDETECTED] = True from homematicip.base.enums import WindowState if self._device.windowState is not None and \ self._device.windowState != WindowState.CLOSED: - attr.update({ATTR_WINDOWSTATE: str(self._device.windowState)}) - + attr[ATTR_WINDOWSTATE] = str(self._device.windowState) + if self._device.unreach: + attr[ATTR_GROUP_MEMBER_UNREACHABLE] = True return attr @property def is_on(self): """Return true if security issue detected.""" if self._device.motionDetected or \ - self._device.presenceDetected: + self._device.presenceDetected or \ + self._device.unreach or \ + self._device.sabotage: return True from homematicip.base.enums import WindowState if self._device.windowState is not None and \ @@ -180,29 +192,30 @@ class HomematicipSecuritySensorGroup(HomematicipSecurityZoneSensorGroup, attr = super().device_state_attributes if self._device.powerMainsFailure: - attr.update({ATTR_POWERMAINSFAILURE: True}) + attr[ATTR_POWERMAINSFAILURE] = True if self._device.moistureDetected: - attr.update({ATTR_MOISTUREDETECTED: True}) + attr[ATTR_MOISTUREDETECTED] = True if self._device.waterlevelDetected: - attr.update({ATTR_WATERLEVELDETECTED: True}) + attr[ATTR_WATERLEVELDETECTED] = True from homematicip.base.enums import SmokeDetectorAlarmType if self._device.smokeDetectorAlarmType is not None and \ self._device.smokeDetectorAlarmType != \ SmokeDetectorAlarmType.IDLE_OFF: - attr.update({ATTR_SMOKEDETECTORALARM: str( - self._device.smokeDetectorAlarmType)}) + attr[ATTR_SMOKEDETECTORALARM] = \ + str(self._device.smokeDetectorAlarmType) return attr @property def is_on(self): - """Return true if security issue detected.""" + """Return true if safety issue detected.""" parent_is_on = super().is_on from homematicip.base.enums import SmokeDetectorAlarmType if parent_is_on or \ self._device.powerMainsFailure or \ self._device.moistureDetected or \ - self._device.waterlevelDetected: + self._device.waterlevelDetected or \ + self._device.lowBat: return True if self._device.smokeDetectorAlarmType is not None and \ self._device.smokeDetectorAlarmType != \ diff --git a/homeassistant/components/homematicip_cloud/device.py b/homeassistant/components/homematicip_cloud/device.py index 85cc3c0c77a..9940e6960db 100644 --- a/homeassistant/components/homematicip_cloud/device.py +++ b/homeassistant/components/homematicip_cloud/device.py @@ -21,6 +21,7 @@ ATTR_OPERATION_LOCK = 'operation_lock' ATTR_SABOTAGE = 'sabotage' ATTR_STATUS_UPDATE = 'status_update' ATTR_UNREACHABLE = 'unreachable' +ATTR_GROUP_MEMBER_UNREACHABLE = 'group_member_unreachable' class HomematicipGenericDevice(Entity): diff --git a/homeassistant/components/homematicip_cloud/switch.py b/homeassistant/components/homematicip_cloud/switch.py index f129febb5e7..74f50f87b25 100644 --- a/homeassistant/components/homematicip_cloud/switch.py +++ b/homeassistant/components/homematicip_cloud/switch.py @@ -3,6 +3,8 @@ import logging from homeassistant.components.homematicip_cloud import ( DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice) +from homeassistant.components.homematicip_cloud.device import ( + ATTR_GROUP_MEMBER_UNREACHABLE) from homeassistant.components.switch import SwitchDevice DEPENDENCIES = ['homematicip_cloud'] @@ -30,7 +32,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): AsyncOpenCollector8Module, ) - from homematicip.group import SwitchingGroup + from homematicip.aio.group import AsyncSwitchingGroup home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home devices = [] @@ -50,7 +52,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): devices.append(HomematicipMultiSwitch(home, device, channel)) for group in home.groups: - if isinstance(group, SwitchingGroup): + if isinstance(group, AsyncSwitchingGroup): devices.append( HomematicipGroupSwitch(home, group)) @@ -92,6 +94,23 @@ class HomematicipGroupSwitch(HomematicipGenericDevice, SwitchDevice): """Return true if group is on.""" return self._device.on + @property + def available(self): + """Switch-Group available.""" + # A switch-group must be available, and should not be affected by the + # individual availability of group members. + # This allows switching even when individual group members + # are not available. + return True + + @property + def device_state_attributes(self): + """Return the state attributes of the switch-group.""" + attr = {} + if self._device.unreach: + attr[ATTR_GROUP_MEMBER_UNREACHABLE] = True + return attr + async def async_turn_on(self, **kwargs): """Turn the group on.""" await self._device.turn_on() diff --git a/homeassistant/components/neato/vacuum.py b/homeassistant/components/neato/vacuum.py index ff78a087de8..990c79552b4 100644 --- a/homeassistant/components/neato/vacuum.py +++ b/homeassistant/components/neato/vacuum.py @@ -186,10 +186,11 @@ class NeatoConnectedVacuum(StateVacuumDevice): self._battery_level = self._state['details']['charge'] if self._robot_has_map: - robot_map_id = self._robot_maps[self._robot_serial][0]['id'] + if self._state['availableServices']['maps'] != "basic-1": + robot_map_id = self._robot_maps[self._robot_serial][0]['id'] - self._robot_boundaries = self.robot.get_map_boundaries( - robot_map_id).json() + self._robot_boundaries = self.robot.get_map_boundaries( + robot_map_id).json() @property def name(self): diff --git a/homeassistant/components/sensor/google_travel_time.py b/homeassistant/components/sensor/google_travel_time.py index 1f4d8425d6e..86b1a7aff44 100644 --- a/homeassistant/components/sensor/google_travel_time.py +++ b/homeassistant/components/sensor/google_travel_time.py @@ -67,7 +67,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ })) }) -TRACKABLE_DOMAINS = ['device_tracker', 'sensor', 'zone'] +TRACKABLE_DOMAINS = ['device_tracker', 'sensor', 'zone', 'person'] DATA_KEY = 'google_travel_time' diff --git a/homeassistant/components/water_heater/econet.py b/homeassistant/components/water_heater/econet.py index 69fde44bdd2..efc21798859 100644 --- a/homeassistant/components/water_heater/econet.py +++ b/homeassistant/components/water_heater/econet.py @@ -13,7 +13,7 @@ from homeassistant.const import ( TEMP_FAHRENHEIT) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pyeconet==0.0.8'] +REQUIREMENTS = ['pyeconet==0.0.9'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index a6cbfbae99d..35c3c456680 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -14,7 +14,7 @@ from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP) + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP) from homeassistant.const import ( CONF_CUSTOMIZE, CONF_FILENAME, CONF_HOST, CONF_NAME, CONF_TIMEOUT, STATE_OFF, STATE_PAUSED, STATE_PLAYING) @@ -36,7 +36,7 @@ WEBOSTV_CONFIG_FILE = 'webostv.conf' SUPPORT_WEBOSTV = SUPPORT_TURN_OFF | \ SUPPORT_NEXT_TRACK | SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | \ - SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_STEP | \ + SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_STEP | \ SUPPORT_SELECT_SOURCE | SUPPORT_PLAY_MEDIA | SUPPORT_PLAY MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) diff --git a/homeassistant/components/xiaomi_aqara/__init__.py b/homeassistant/components/xiaomi_aqara/__init__.py index 19d7aaaa30d..66fc1fa13dd 100644 --- a/homeassistant/components/xiaomi_aqara/__init__.py +++ b/homeassistant/components/xiaomi_aqara/__init__.py @@ -16,7 +16,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util.dt import utcnow -REQUIREMENTS = ['PyXiaomiGateway==0.12.0'] +REQUIREMENTS = ['PyXiaomiGateway==0.12.2'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/const.py b/homeassistant/const.py index 5b943ddb3cf..c1ec2da41c7 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 89 -PATCH_VERSION = '0' +PATCH_VERSION = '1' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3) diff --git a/homeassistant/scripts/__init__.py b/homeassistant/scripts/__init__.py index 3050379a496..070d907a7d9 100644 --- a/homeassistant/scripts/__init__.py +++ b/homeassistant/scripts/__init__.py @@ -52,15 +52,10 @@ def run(args: List) -> int: hass = HomeAssistant(loop) pkgload = PackageLoadable(hass) for req in getattr(script, 'REQUIREMENTS', []): - try: - loop.run_until_complete(pkgload.loadable(req)) + if loop.run_until_complete(pkgload.loadable(req)): continue - except ImportError: - pass - returncode = install_package(req, **_pip_kwargs) - - if not returncode: + if not install_package(req, **_pip_kwargs): print('Aborting script, could not install dependency', req) return 1 diff --git a/requirements_all.txt b/requirements_all.txt index 72185c594cd..765bf4d0e35 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -67,7 +67,7 @@ PyRMVtransport==0.1.3 PyTransportNSW==0.1.1 # homeassistant.components.xiaomi_aqara -PyXiaomiGateway==0.12.0 +PyXiaomiGateway==0.12.2 # homeassistant.components.rpi_gpio # RPi.GPIO==0.6.5 @@ -206,7 +206,7 @@ bellows-homeassistant==0.7.1 bimmer_connected==0.5.3 # homeassistant.components.blink -blinkpy==0.12.1 +blinkpy==0.13.1 # homeassistant.components.light.blinksticklight blinkstick==1.1.8 @@ -539,7 +539,7 @@ hole==0.3.0 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190305.0 +home-assistant-frontend==20190305.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.2 @@ -1004,7 +1004,7 @@ pydukeenergy==0.0.6 pyebox==1.1.4 # homeassistant.components.water_heater.econet -pyeconet==0.0.8 +pyeconet==0.0.9 # homeassistant.components.switch.edimax pyedimax==0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 33a88ac4391..7f69e2a500a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -120,7 +120,7 @@ hdate==0.8.7 holidays==0.9.9 # homeassistant.components.frontend -home-assistant-frontend==20190305.0 +home-assistant-frontend==20190305.1 # homeassistant.components.homekit_controller homekit==0.12.2