diff --git a/homeassistant/components/braviatv/manifest.json b/homeassistant/components/braviatv/manifest.json index cde236b4ca4..98c1dca08d2 100644 --- a/homeassistant/components/braviatv/manifest.json +++ b/homeassistant/components/braviatv/manifest.json @@ -2,7 +2,7 @@ "domain": "braviatv", "name": "Sony Bravia TV", "documentation": "https://www.home-assistant.io/integrations/braviatv", - "requirements": ["bravia-tv==1.0.2"], + "requirements": ["bravia-tv==1.0.3"], "codeowners": ["@robbiet480", "@bieniu"], "config_flow": true } diff --git a/homeassistant/components/canary/camera.py b/homeassistant/components/canary/camera.py index 1631038f81a..870256ffcff 100644 --- a/homeassistant/components/canary/camera.py +++ b/homeassistant/components/canary/camera.py @@ -81,7 +81,7 @@ class CanaryCamera(Camera): async def async_camera_image(self): """Return a still image response from the camera.""" - self.renew_live_stream_session() + await self.hass.async_add_executor_job(self.renew_live_stream_session) ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop) image = await asyncio.shield( diff --git a/homeassistant/components/cloud/manifest.json b/homeassistant/components/cloud/manifest.json index 9b2541eedd0..de5496cfd99 100644 --- a/homeassistant/components/cloud/manifest.json +++ b/homeassistant/components/cloud/manifest.json @@ -2,7 +2,7 @@ "domain": "cloud", "name": "Home Assistant Cloud", "documentation": "https://www.home-assistant.io/integrations/cloud", - "requirements": ["hass-nabucasa==0.34.1"], + "requirements": ["hass-nabucasa==0.34.2"], "dependencies": ["http", "webhook", "alexa"], "after_dependencies": ["google_assistant"], "codeowners": ["@home-assistant/cloud"] diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 9759c38af7d..0ed1697bf42 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20200427.1"], + "requirements": ["home-assistant-frontend==20200427.2"], "dependencies": [ "api", "auth", diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 35620e9e11d..b2aeea85c94 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -275,13 +275,12 @@ def get_accessory(hass, driver, state, aid, config): elif state.domain == "media_player": device_class = state.attributes.get(ATTR_DEVICE_CLASS) - feature_list = config.get(CONF_FEATURE_LIST) + feature_list = config.get(CONF_FEATURE_LIST, []) if device_class == DEVICE_CLASS_TV: a_type = "TelevisionMediaPlayer" - else: - if feature_list and validate_media_player_features(state, feature_list): - a_type = "MediaPlayer" + elif validate_media_player_features(state, feature_list): + a_type = "MediaPlayer" elif state.domain == "sensor": device_class = state.attributes.get(ATTR_DEVICE_CLASS) diff --git a/homeassistant/components/homekit/type_media_players.py b/homeassistant/components/homekit/type_media_players.py index 78c11fc41f9..76f4532b868 100644 --- a/homeassistant/components/homekit/type_media_players.py +++ b/homeassistant/components/homekit/type_media_players.py @@ -65,6 +65,7 @@ from .const import ( SERV_TELEVISION, SERV_TELEVISION_SPEAKER, ) +from .util import get_media_player_features _LOGGER = logging.getLogger(__name__) @@ -84,10 +85,12 @@ MEDIA_PLAYER_KEYS = { # 15: "Information", } +# Names may not contain special characters +# or emjoi (/ is a special character for Apple) MODE_FRIENDLY_NAME = { FEATURE_ON_OFF: "Power", - FEATURE_PLAY_PAUSE: "Play/Pause", - FEATURE_PLAY_STOP: "Play/Stop", + FEATURE_PLAY_PAUSE: "Play-Pause", + FEATURE_PLAY_STOP: "Play-Stop", FEATURE_TOGGLE_MUTE: "Mute", } @@ -106,7 +109,9 @@ class MediaPlayer(HomeAccessory): FEATURE_PLAY_STOP: None, FEATURE_TOGGLE_MUTE: None, } - feature_list = self.config[CONF_FEATURE_LIST] + feature_list = self.config.get( + CONF_FEATURE_LIST, get_media_player_features(state) + ) if FEATURE_ON_OFF in feature_list: name = self.generate_service_name(FEATURE_ON_OFF) @@ -214,7 +219,7 @@ class MediaPlayer(HomeAccessory): self.chars[FEATURE_PLAY_STOP].set_value(hk_state) if self.chars[FEATURE_TOGGLE_MUTE]: - current_state = new_state.attributes.get(ATTR_MEDIA_VOLUME_MUTED) + current_state = bool(new_state.attributes.get(ATTR_MEDIA_VOLUME_MUTED)) _LOGGER.debug( '%s: Set current state for "toggle_mute" to %s', self.entity_id, @@ -240,9 +245,7 @@ class TelevisionMediaPlayer(HomeAccessory): # Add additional characteristics if volume or input selection supported self.chars_tv = [] self.chars_speaker = [] - features = self.hass.states.get(self.entity_id).attributes.get( - ATTR_SUPPORTED_FEATURES, 0 - ) + features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) if features & (SUPPORT_PLAY | SUPPORT_PAUSE): self.chars_tv.append(CHAR_REMOTE_KEY) @@ -253,7 +256,8 @@ class TelevisionMediaPlayer(HomeAccessory): if features & SUPPORT_VOLUME_SET: self.chars_speaker.append(CHAR_VOLUME) - if features & SUPPORT_SELECT_SOURCE: + source_list = state.attributes.get(ATTR_INPUT_SOURCE_LIST, []) + if source_list and features & SUPPORT_SELECT_SOURCE: self.support_select_source = True serv_tv = self.add_preload_service(SERV_TELEVISION, self.chars_tv) @@ -298,9 +302,7 @@ class TelevisionMediaPlayer(HomeAccessory): ) if self.support_select_source: - self.sources = self.hass.states.get(self.entity_id).attributes.get( - ATTR_INPUT_SOURCE_LIST, [] - ) + self.sources = source_list self.char_input_source = serv_tv.configure_char( CHAR_ACTIVE_IDENTIFIER, setter_callback=self.set_input_source ) @@ -380,14 +382,13 @@ class TelevisionMediaPlayer(HomeAccessory): hk_state = 0 if current_state not in ("None", STATE_OFF, STATE_UNKNOWN): hk_state = 1 - _LOGGER.debug("%s: Set current active state to %s", self.entity_id, hk_state) if self.char_active.value != hk_state: self.char_active.set_value(hk_state) # Set mute state if CHAR_VOLUME_SELECTOR in self.chars_speaker: - current_mute_state = new_state.attributes.get(ATTR_MEDIA_VOLUME_MUTED) + current_mute_state = bool(new_state.attributes.get(ATTR_MEDIA_VOLUME_MUTED)) _LOGGER.debug( "%s: Set current mute state to %s", self.entity_id, current_mute_state, ) @@ -395,20 +396,16 @@ class TelevisionMediaPlayer(HomeAccessory): self.char_mute.set_value(current_mute_state) # Set active input - if self.support_select_source: + if self.support_select_source and self.sources: source_name = new_state.attributes.get(ATTR_INPUT_SOURCE) - if self.sources: - _LOGGER.debug( - "%s: Set current input to %s", self.entity_id, source_name + _LOGGER.debug("%s: Set current input to %s", self.entity_id, source_name) + if source_name in self.sources: + index = self.sources.index(source_name) + if self.char_input_source.value != index: + self.char_input_source.set_value(index) + else: + _LOGGER.warning( + "%s: Sources out of sync. Restart Home Assistant", self.entity_id, ) - if source_name in self.sources: - index = self.sources.index(source_name) - if self.char_input_source.value != index: - self.char_input_source.set_value(index) - else: - _LOGGER.warning( - "%s: Sources out of sync. Restart Home Assistant", - self.entity_id, - ) - if self.char_input_source.value != 0: - self.char_input_source.set_value(0) + if self.char_input_source.value != 0: + self.char_input_source.set_value(0) diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index b8d98ad2304..9c8e7e8c053 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -96,6 +96,40 @@ SWITCH_TYPE_SCHEMA = BASIC_INFO_SCHEMA.extend( ) +HOMEKIT_CHAR_TRANSLATIONS = { + 0: " ", # nul + 10: " ", # nl + 13: " ", # cr + 33: "-", # ! + 34: " ", # " + 36: "-", # $ + 37: "-", # % + 40: "-", # ( + 41: "-", # ) + 42: "-", # * + 43: "-", # + + 47: "-", # / + 58: "-", # : + 59: "-", # ; + 60: "-", # < + 61: "-", # = + 62: "-", # > + 63: "-", # ? + 64: "-", # @ + 91: "-", # [ + 92: "-", # \ + 93: "-", # ] + 94: "-", # ^ + 95: " ", # _ + 96: "-", # ` + 123: "-", # { + 124: "-", # | + 125: "-", # } + 126: "-", # ~ + 127: "-", # del +} + + def validate_entity_config(values): """Validate config entry for CONF_ENTITY.""" if not isinstance(values, dict): @@ -133,8 +167,8 @@ def validate_entity_config(values): return entities -def validate_media_player_features(state, feature_list): - """Validate features for media players.""" +def get_media_player_features(state): + """Determine features for media players.""" features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported_modes = [] @@ -148,6 +182,20 @@ def validate_media_player_features(state, feature_list): supported_modes.append(FEATURE_PLAY_STOP) if features & media_player.const.SUPPORT_VOLUME_MUTE: supported_modes.append(FEATURE_TOGGLE_MUTE) + return supported_modes + + +def validate_media_player_features(state, feature_list): + """Validate features for media players.""" + supported_modes = get_media_player_features(state) + + if not supported_modes: + _LOGGER.error("%s does not support any media_player features", state.entity_id) + return False + + if not feature_list: + # Auto detected + return True error_list = [] for feature in feature_list: @@ -155,7 +203,9 @@ def validate_media_player_features(state, feature_list): error_list.append(feature) if error_list: - _LOGGER.error("%s does not support features: %s", state.entity_id, error_list) + _LOGGER.error( + "%s does not support media_player features: %s", state.entity_id, error_list + ) return False return True @@ -247,6 +297,16 @@ def convert_to_float(state): return None +def cleanup_name_for_homekit(name): + """Ensure the name of the device will not crash homekit.""" + # + # This is not a security measure. + # + # UNICODE_EMOJI is also not allowed but that + # likely isn't a problem + return name.translate(HOMEKIT_CHAR_TRANSLATIONS) + + def temperature_to_homekit(temperature, unit): """Convert temperature to Celsius for HomeKit.""" return round(temp_util.convert(temperature, unit, TEMP_CELSIUS), 1) diff --git a/homeassistant/components/melcloud/climate.py b/homeassistant/components/melcloud/climate.py index e2d1fdd984d..9630d1bb855 100644 --- a/homeassistant/components/melcloud/climate.py +++ b/homeassistant/components/melcloud/climate.py @@ -31,7 +31,6 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.typing import HomeAssistantType -from homeassistant.util.temperature import convert as convert_temperature from . import MelCloudDevice from .const import ( @@ -44,7 +43,6 @@ from .const import ( DOMAIN, SERVICE_SET_VANE_HORIZONTAL, SERVICE_SET_VANE_VERTICAL, - TEMP_UNIT_LOOKUP, ) SCAN_INTERVAL = timedelta(seconds=60) @@ -169,7 +167,7 @@ class AtaDeviceClimate(MelCloudClimate): @property def temperature_unit(self) -> str: """Return the unit of measurement used by the platform.""" - return TEMP_UNIT_LOOKUP.get(self._device.temp_unit, TEMP_CELSIUS) + return TEMP_CELSIUS @property def hvac_mode(self) -> str: @@ -281,9 +279,7 @@ class AtaDeviceClimate(MelCloudClimate): if min_value is not None: return min_value - return convert_temperature( - DEFAULT_MIN_TEMP, TEMP_CELSIUS, self.temperature_unit - ) + return DEFAULT_MIN_TEMP @property def max_temp(self) -> float: @@ -292,9 +288,7 @@ class AtaDeviceClimate(MelCloudClimate): if max_value is not None: return max_value - return convert_temperature( - DEFAULT_MAX_TEMP, TEMP_CELSIUS, self.temperature_unit - ) + return DEFAULT_MAX_TEMP class AtwDeviceZoneClimate(MelCloudClimate): @@ -331,7 +325,7 @@ class AtwDeviceZoneClimate(MelCloudClimate): @property def temperature_unit(self) -> str: """Return the unit of measurement used by the platform.""" - return TEMP_UNIT_LOOKUP.get(self._device.temp_unit, TEMP_CELSIUS) + return TEMP_CELSIUS @property def hvac_mode(self) -> str: @@ -391,7 +385,7 @@ class AtwDeviceZoneClimate(MelCloudClimate): MELCloud API does not expose radiator zone temperature limits. """ - return convert_temperature(10, TEMP_CELSIUS, self.temperature_unit) + return 10 @property def max_temp(self) -> float: @@ -399,4 +393,4 @@ class AtwDeviceZoneClimate(MelCloudClimate): MELCloud API does not expose radiator zone temperature limits. """ - return convert_temperature(30, TEMP_CELSIUS, self.temperature_unit) + return 30 diff --git a/homeassistant/components/melcloud/const.py b/homeassistant/components/melcloud/const.py index d58f483d441..27cffb75223 100644 --- a/homeassistant/components/melcloud/const.py +++ b/homeassistant/components/melcloud/const.py @@ -1,7 +1,4 @@ """Constants for the MELCloud Climate integration.""" -from pymelcloud.const import UNIT_TEMP_CELSIUS, UNIT_TEMP_FAHRENHEIT - -from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT DOMAIN = "melcloud" @@ -15,9 +12,3 @@ ATTR_VANE_VERTICAL_POSITIONS = "vane_vertical_positions" SERVICE_SET_VANE_HORIZONTAL = "set_vane_horizontal" SERVICE_SET_VANE_VERTICAL = "set_vane_vertical" - -TEMP_UNIT_LOOKUP = { - UNIT_TEMP_CELSIUS: TEMP_CELSIUS, - UNIT_TEMP_FAHRENHEIT: TEMP_FAHRENHEIT, -} -TEMP_UNIT_REVERSE_LOOKUP = {v: k for k, v in TEMP_UNIT_LOOKUP.items()} diff --git a/homeassistant/components/melcloud/sensor.py b/homeassistant/components/melcloud/sensor.py index 9dee01c2fba..13cc5a94a18 100644 --- a/homeassistant/components/melcloud/sensor.py +++ b/homeassistant/components/melcloud/sensor.py @@ -12,11 +12,11 @@ from homeassistant.const import ( from homeassistant.helpers.entity import Entity from . import MelCloudDevice -from .const import DOMAIN, TEMP_UNIT_LOOKUP +from .const import DOMAIN ATTR_MEASUREMENT_NAME = "measurement_name" ATTR_ICON = "icon" -ATTR_UNIT_FN = "unit_fn" +ATTR_UNIT = "unit" ATTR_DEVICE_CLASS = "device_class" ATTR_VALUE_FN = "value_fn" ATTR_ENABLED_FN = "enabled" @@ -25,7 +25,7 @@ ATA_SENSORS = { "room_temperature": { ATTR_MEASUREMENT_NAME: "Room Temperature", ATTR_ICON: "mdi:thermometer", - ATTR_UNIT_FN: lambda x: TEMP_UNIT_LOOKUP.get(x.device.temp_unit, TEMP_CELSIUS), + ATTR_UNIT: TEMP_CELSIUS, ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, ATTR_VALUE_FN: lambda x: x.device.room_temperature, ATTR_ENABLED_FN: lambda x: True, @@ -33,7 +33,7 @@ ATA_SENSORS = { "energy": { ATTR_MEASUREMENT_NAME: "Energy", ATTR_ICON: "mdi:factory", - ATTR_UNIT_FN: lambda x: ENERGY_KILO_WATT_HOUR, + ATTR_UNIT: ENERGY_KILO_WATT_HOUR, ATTR_DEVICE_CLASS: None, ATTR_VALUE_FN: lambda x: x.device.total_energy_consumed, ATTR_ENABLED_FN: lambda x: x.device.has_energy_consumed_meter, @@ -43,7 +43,7 @@ ATW_SENSORS = { "outside_temperature": { ATTR_MEASUREMENT_NAME: "Outside Temperature", ATTR_ICON: "mdi:thermometer", - ATTR_UNIT_FN: lambda x: TEMP_UNIT_LOOKUP.get(x.device.temp_unit, TEMP_CELSIUS), + ATTR_UNIT: TEMP_CELSIUS, ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, ATTR_VALUE_FN: lambda x: x.device.outside_temperature, ATTR_ENABLED_FN: lambda x: True, @@ -51,7 +51,7 @@ ATW_SENSORS = { "tank_temperature": { ATTR_MEASUREMENT_NAME: "Tank Temperature", ATTR_ICON: "mdi:thermometer", - ATTR_UNIT_FN: lambda x: TEMP_UNIT_LOOKUP.get(x.device.temp_unit, TEMP_CELSIUS), + ATTR_UNIT: TEMP_CELSIUS, ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, ATTR_VALUE_FN: lambda x: x.device.tank_temperature, ATTR_ENABLED_FN: lambda x: True, @@ -61,7 +61,7 @@ ATW_ZONE_SENSORS = { "room_temperature": { ATTR_MEASUREMENT_NAME: "Room Temperature", ATTR_ICON: "mdi:thermometer", - ATTR_UNIT_FN: lambda x: TEMP_UNIT_LOOKUP.get(x.device.temp_unit, TEMP_CELSIUS), + ATTR_UNIT: TEMP_CELSIUS, ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, ATTR_VALUE_FN: lambda zone: zone.room_temperature, ATTR_ENABLED_FN: lambda x: True, @@ -131,7 +131,7 @@ class MelDeviceSensor(Entity): @property def unit_of_measurement(self): """Return the unit of measurement.""" - return self._def[ATTR_UNIT_FN](self._api) + return self._def[ATTR_UNIT] @property def device_class(self): diff --git a/homeassistant/components/melcloud/water_heater.py b/homeassistant/components/melcloud/water_heater.py index fa7aff2b640..ce1b1ae15cc 100644 --- a/homeassistant/components/melcloud/water_heater.py +++ b/homeassistant/components/melcloud/water_heater.py @@ -18,7 +18,7 @@ from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.typing import HomeAssistantType from . import DOMAIN, MelCloudDevice -from .const import ATTR_STATUS, TEMP_UNIT_LOOKUP +from .const import ATTR_STATUS async def async_setup_entry( @@ -80,7 +80,7 @@ class AtwWaterHeater(WaterHeaterDevice): @property def temperature_unit(self) -> str: """Return the unit of measurement used by the platform.""" - return TEMP_UNIT_LOOKUP.get(self._device.temp_unit, TEMP_CELSIUS) + return TEMP_CELSIUS @property def current_operation(self) -> Optional[str]: diff --git a/homeassistant/components/roomba/manifest.json b/homeassistant/components/roomba/manifest.json index a164509bc99..b9e521ffc12 100644 --- a/homeassistant/components/roomba/manifest.json +++ b/homeassistant/components/roomba/manifest.json @@ -3,7 +3,7 @@ "name": "iRobot Roomba", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/roomba", - "requirements": ["roombapy==1.5.1"], + "requirements": ["roombapy==1.5.2"], "dependencies": [], "codeowners": ["@pschmitt", "@cyr-ius", "@shenxn"] } diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index 8c17de5e997..b3c9f66c8da 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -2,6 +2,7 @@ from homeassistant.const import ( DATA_MEGABYTES, DATA_RATE_KILOBYTES_PER_SECOND, + DATA_TERABYTES, UNIT_PERCENTAGE, ) @@ -34,8 +35,8 @@ UTILISATION_SENSORS = { STORAGE_VOL_SENSORS = { "volume_status": ["Status", None, "mdi:checkbox-marked-circle-outline"], "volume_device_type": ["Type", None, "mdi:harddisk"], - "volume_size_total": ["Total Size", None, "mdi:chart-pie"], - "volume_size_used": ["Used Space", None, "mdi:chart-pie"], + "volume_size_total": ["Total Size", DATA_TERABYTES, "mdi:chart-pie"], + "volume_size_used": ["Used Space", DATA_TERABYTES, "mdi:chart-pie"], "volume_percentage_used": ["Volume Used", UNIT_PERCENTAGE, "mdi:chart-pie"], "volume_disk_temp_avg": ["Average Disk Temp", None, "mdi:thermometer"], "volume_disk_temp_max": ["Maximum Disk Temp", None, "mdi:thermometer"], diff --git a/homeassistant/components/synology_dsm/manifest.json b/homeassistant/components/synology_dsm/manifest.json index bb3c5ce3aec..f57f1843f45 100644 --- a/homeassistant/components/synology_dsm/manifest.json +++ b/homeassistant/components/synology_dsm/manifest.json @@ -2,7 +2,7 @@ "domain": "synology_dsm", "name": "Synology DSM", "documentation": "https://www.home-assistant.io/integrations/synology_dsm", - "requirements": ["python-synology==0.7.3"], + "requirements": ["python-synology==0.8.0"], "codeowners": ["@ProtoThis", "@Quentame"], "config_flow": true, "ssdp": [ diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index 6e5a486ab89..b6a88fe5a5a 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -7,11 +7,13 @@ from homeassistant.const import ( CONF_DISKS, DATA_MEGABYTES, DATA_RATE_KILOBYTES_PER_SECOND, + DATA_TERABYTES, TEMP_CELSIUS, ) from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.util.temperature import celsius_to_fahrenheit from . import SynoApi from .const import ( @@ -63,7 +65,7 @@ async def async_setup_entry( class SynoNasSensor(Entity): - """Representation of a Synology NAS Sensor.""" + """Representation of a Synology NAS sensor.""" def __init__( self, @@ -142,47 +144,51 @@ class SynoNasSensor(Entity): class SynoNasUtilSensor(SynoNasSensor): - """Representation a Synology Utilisation Sensor.""" + """Representation a Synology Utilisation sensor.""" @property def state(self): """Return the state.""" - if self._unit == DATA_RATE_KILOBYTES_PER_SECOND or self._unit == DATA_MEGABYTES: - attr = getattr(self._api.utilisation, self.sensor_type)(False) + attr = getattr(self._api.utilisation, self.sensor_type) + if callable(attr): + attr = attr() + if not attr: + return None - if attr is None: - return None + # Data (RAM) + if self._unit == DATA_MEGABYTES: + return round(attr / 1024.0 ** 2, 1) - if self._unit == DATA_RATE_KILOBYTES_PER_SECOND: - return round(attr / 1024.0, 1) - if self._unit == DATA_MEGABYTES: - return round(attr / 1024.0 / 1024.0, 1) - else: - return getattr(self._api.utilisation, self.sensor_type) + # Network + if self._unit == DATA_RATE_KILOBYTES_PER_SECOND: + return round(attr / 1024.0, 1) + + return attr class SynoNasStorageSensor(SynoNasSensor): - """Representation a Synology Storage Sensor.""" + """Representation a Synology Storage sensor.""" @property def state(self): """Return the state.""" - if self.monitored_device: - if self.sensor_type in TEMP_SENSORS_KEYS: - attr = getattr(self._api.storage, self.sensor_type)( - self.monitored_device - ) + attr = getattr(self._api.storage, self.sensor_type)(self.monitored_device) + if not attr: + return None - if attr is None: - return None + # Data (disk space) + if self._unit == DATA_TERABYTES: + return round(attr / 1024.0 ** 4, 2) - if self._api.temp_unit == TEMP_CELSIUS: - return attr + # Temperature + if self._api.temp_unit == TEMP_CELSIUS: + # Celsius + return attr + if self.sensor_type in TEMP_SENSORS_KEYS: + # Fahrenheit + return celsius_to_fahrenheit(attr) - return round(attr * 1.8 + 32.0, 1) - - return getattr(self._api.storage, self.sensor_type)(self.monitored_device) - return None + return attr @property def device_info(self) -> Dict[str, any]: diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index e91001e51be..38d37560952 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -32,7 +32,12 @@ from .const import ( LOGGER, ) from .controller import get_controller -from .errors import AlreadyConfigured, AuthenticationRequired, CannotConnect +from .errors import ( + AlreadyConfigured, + AuthenticationRequired, + CannotConnect, + NoLocalUser, +) DEFAULT_PORT = 8443 DEFAULT_SITE_ID = "default" @@ -135,6 +140,8 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): for site in self.sites.values(): if desc == site["desc"]: + if "role" not in site: + raise NoLocalUser self.config[CONF_SITE_ID] = site["name"] break @@ -153,6 +160,9 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): except AlreadyConfigured: return self.async_abort(reason="already_configured") + except NoLocalUser: + return self.async_abort(reason="no_local_user") + if len(self.sites) == 1: self.desc = next(iter(self.sites.values()))["desc"] return await self.async_step_site(user_input={}) @@ -189,7 +199,12 @@ class UnifiOptionsFlowHandler(config_entries.OptionsFlow): self.options.update(user_input) return await self.async_step_client_control() - ssid_filter = {wlan: wlan for wlan in self.controller.api.wlans} + ssids = list(self.controller.api.wlans) + [ + f"{wlan.name}{wlan.name_combine_suffix}" + for wlan in self.controller.api.wlans.values() + if not wlan.name_combine_enabled + ] + ssid_filter = {ssid: ssid for ssid in sorted(ssids)} return self.async_show_form( step_id="device_tracker", diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index 33e7fc3836b..2500d6b4106 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -188,7 +188,7 @@ class UniFiController: elif signal == SIGNAL_DATA and data: if DATA_EVENT in data: - if data[DATA_EVENT].event in ( + if next(iter(data[DATA_EVENT])).event in ( WIRELESS_CLIENT_CONNECTED, WIRELESS_GUEST_CONNECTED, ): diff --git a/homeassistant/components/unifi/errors.py b/homeassistant/components/unifi/errors.py index c90c4956312..e0da64f245c 100644 --- a/homeassistant/components/unifi/errors.py +++ b/homeassistant/components/unifi/errors.py @@ -22,5 +22,9 @@ class LoginRequired(UnifiException): """Component got logged out.""" +class NoLocalUser(UnifiException): + """No local user.""" + + class UserLevel(UnifiException): """User level too low.""" diff --git a/homeassistant/components/unifi/manifest.json b/homeassistant/components/unifi/manifest.json index 0a5ba84cdb3..8c05d195316 100644 --- a/homeassistant/components/unifi/manifest.json +++ b/homeassistant/components/unifi/manifest.json @@ -3,7 +3,7 @@ "name": "Ubiquiti UniFi", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/unifi", - "requirements": ["aiounifi==18"], + "requirements": ["aiounifi==20"], "codeowners": ["@kane610"], "quality_scale": "platinum" } diff --git a/homeassistant/components/unifi/strings.json b/homeassistant/components/unifi/strings.json index 6c142d371c9..df7fc267a43 100644 --- a/homeassistant/components/unifi/strings.json +++ b/homeassistant/components/unifi/strings.json @@ -20,6 +20,7 @@ }, "abort": { "already_configured": "Controller site is already configured", + "no_local_user": "No local user found, configure a local account on controller and try again", "user_privilege": "User needs to be administrator" } }, diff --git a/homeassistant/components/unifi/translations/en.json b/homeassistant/components/unifi/translations/en.json index 618c393b7aa..d61dba3b5ef 100644 --- a/homeassistant/components/unifi/translations/en.json +++ b/homeassistant/components/unifi/translations/en.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Controller site is already configured", + "no_local_user": "No local user found, configure a local account on controller and try again", "user_privilege": "User needs to be administrator" }, "error": { diff --git a/homeassistant/const.py b/homeassistant/const.py index 9e3be432dfa..91d6fe39921 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 109 -PATCH_VERSION = "2" +PATCH_VERSION = "3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py index abf39972186..d0fac953ac1 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -210,6 +210,9 @@ async def async_get_component_strings( else: files_to_load[loaded] = path + if not files_to_load: + return translations + # Load files load_translations_job = hass.async_add_executor_job( load_translations_files, files_to_load @@ -218,12 +221,12 @@ async def async_get_component_strings( loaded_translations = await load_translations_job # Translations that miss "title" will get integration put in. - for loaded, translations in loaded_translations.items(): + for loaded, loaded_translation in loaded_translations.items(): if "." in loaded: continue - if "title" not in translations: - translations["title"] = integrations[loaded].name + if "title" not in loaded_translation: + loaded_translation["title"] = integrations[loaded].name translations.update(loaded_translations) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 938427eadd4..b7e70914714 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -11,8 +11,8 @@ ciso8601==2.1.3 cryptography==2.9 defusedxml==0.6.0 distro==1.5.0 -hass-nabucasa==0.34.1 -home-assistant-frontend==20200427.1 +hass-nabucasa==0.34.2 +home-assistant-frontend==20200427.2 importlib-metadata==1.6.0 jinja2>=2.11.1 netdisco==2.6.0 diff --git a/requirements_all.txt b/requirements_all.txt index 9c6eddf3196..461f4d27d6d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -212,7 +212,7 @@ aiopylgtv==0.3.3 aioswitcher==1.1.0 # homeassistant.components.unifi -aiounifi==18 +aiounifi==20 # homeassistant.components.wwlln aiowwlln==2.0.2 @@ -359,7 +359,7 @@ bomradarloop==0.1.4 boto3==1.9.252 # homeassistant.components.braviatv -bravia-tv==1.0.2 +bravia-tv==1.0.3 # homeassistant.components.broadlink broadlink==0.13.2 @@ -683,7 +683,7 @@ habitipy==0.2.0 hangups==0.4.9 # homeassistant.components.cloud -hass-nabucasa==0.34.1 +hass-nabucasa==0.34.2 # homeassistant.components.mqtt hbmqtt==0.9.5 @@ -713,7 +713,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200427.1 +home-assistant-frontend==20200427.2 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 @@ -1680,7 +1680,7 @@ python-sochain-api==0.0.2 python-songpal==0.11.2 # homeassistant.components.synology_dsm -python-synology==0.7.3 +python-synology==0.8.0 # homeassistant.components.tado python-tado==0.8.1 @@ -1825,7 +1825,7 @@ rocketchat-API==0.6.1 roku==4.1.0 # homeassistant.components.roomba -roombapy==1.5.1 +roombapy==1.5.2 # homeassistant.components.rova rova==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 215fafcd467..56a3ac96310 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -95,7 +95,7 @@ aiopylgtv==0.3.3 aioswitcher==1.1.0 # homeassistant.components.unifi -aiounifi==18 +aiounifi==20 # homeassistant.components.wwlln aiowwlln==2.0.2 @@ -141,7 +141,7 @@ bellows-homeassistant==0.15.2 bomradarloop==0.1.4 # homeassistant.components.braviatv -bravia-tv==1.0.2 +bravia-tv==1.0.3 # homeassistant.components.broadlink broadlink==0.13.2 @@ -273,7 +273,7 @@ ha-ffmpeg==2.0 hangups==0.4.9 # homeassistant.components.cloud -hass-nabucasa==0.34.1 +hass-nabucasa==0.34.2 # homeassistant.components.mqtt hbmqtt==0.9.5 @@ -291,7 +291,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200427.1 +home-assistant-frontend==20200427.2 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 @@ -650,7 +650,7 @@ python-miio==0.5.0.1 python-nest==4.1.0 # homeassistant.components.synology_dsm -python-synology==0.7.3 +python-synology==0.8.0 # homeassistant.components.tado python-tado==0.8.1 @@ -701,7 +701,7 @@ ring_doorbell==0.6.0 roku==4.1.0 # homeassistant.components.roomba -roombapy==1.5.1 +roombapy==1.5.2 # homeassistant.components.yamaha rxv==0.6.0 diff --git a/tests/components/homekit/test_type_media_players.py b/tests/components/homekit/test_type_media_players.py index 5fe8c438ca1..50385bdd880 100644 --- a/tests/components/homekit/test_type_media_players.py +++ b/tests/components/homekit/test_type_media_players.py @@ -368,6 +368,26 @@ async def test_media_player_television_basic(hass, hk_driver, events, caplog): assert not caplog.messages or "Error" not in caplog.messages[-1] +async def test_media_player_television_supports_source_select_no_sources( + hass, hk_driver, events, caplog +): + """Test if basic tv that supports source select but is missing a source list.""" + entity_id = "media_player.television" + + # Supports turn_on', 'turn_off' + hass.states.async_set( + entity_id, + None, + {ATTR_DEVICE_CLASS: DEVICE_CLASS_TV, ATTR_SUPPORTED_FEATURES: 3469}, + ) + await hass.async_block_till_done() + acc = TelevisionMediaPlayer(hass, hk_driver, "MediaPlayer", entity_id, 2, None) + await acc.run_handler() + await hass.async_block_till_done() + + assert acc.support_select_source is False + + async def test_tv_restore(hass, hk_driver, events): """Test setting up an entity from state in the event registry.""" hass.state = CoreState.not_running diff --git a/tests/components/homekit/test_util.py b/tests/components/homekit/test_util.py index 41b134c10a5..45ad042238f 100644 --- a/tests/components/homekit/test_util.py +++ b/tests/components/homekit/test_util.py @@ -22,6 +22,7 @@ from homeassistant.components.homekit.const import ( from homeassistant.components.homekit.util import ( HomeKitSpeedMapping, SpeedRange, + cleanup_name_for_homekit, convert_to_float, density_to_air_quality, dismiss_setup_message, @@ -172,6 +173,19 @@ def test_convert_to_float(): assert convert_to_float(None) is None +def test_cleanup_name_for_homekit(): + """Ensure name sanitize works as expected.""" + + assert cleanup_name_for_homekit("abc") == "abc" + assert cleanup_name_for_homekit("a b c") == "a b c" + assert cleanup_name_for_homekit("ab_c") == "ab c" + assert ( + cleanup_name_for_homekit('ab!@#$%^&*()-=":.,>