Merge pull request #48896 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2021-04-08 15:35:17 -07:00 committed by GitHub
commit f4c3bdad7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1180 additions and 763 deletions

View File

@ -62,7 +62,7 @@ MANIFEST_JSON = {
"screenshots": [ "screenshots": [
{ {
"src": "/static/images/screenshots/screenshot-1.png", "src": "/static/images/screenshots/screenshot-1.png",
"sizes": "413×792", "sizes": "413x792",
"type": "image/png", "type": "image/png",
} }
], ],

View File

@ -3,7 +3,7 @@
"name": "Home Assistant Frontend", "name": "Home Assistant Frontend",
"documentation": "https://www.home-assistant.io/integrations/frontend", "documentation": "https://www.home-assistant.io/integrations/frontend",
"requirements": [ "requirements": [
"home-assistant-frontend==20210407.1" "home-assistant-frontend==20210407.2"
], ],
"dependencies": [ "dependencies": [
"api", "api",

View File

@ -501,6 +501,6 @@ class IcloudDevice:
return self._location return self._location
@property @property
def exta_state_attributes(self) -> dict[str, any]: def extra_state_attributes(self) -> dict[str, any]:
"""Return the attributes.""" """Return the attributes."""
return self._attrs return self._attrs

View File

@ -110,7 +110,7 @@ class IcloudTrackerEntity(TrackerEntity):
@property @property
def extra_state_attributes(self) -> dict[str, any]: def extra_state_attributes(self) -> dict[str, any]:
"""Return the device state attributes.""" """Return the device state attributes."""
return self._device.state_attributes return self._device.extra_state_attributes
@property @property
def device_info(self) -> dict[str, any]: def device_info(self) -> dict[str, any]:

View File

@ -93,7 +93,7 @@ class IcloudDeviceBatterySensor(SensorEntity):
@property @property
def extra_state_attributes(self) -> dict[str, any]: def extra_state_attributes(self) -> dict[str, any]:
"""Return default attributes for the iCloud device entity.""" """Return default attributes for the iCloud device entity."""
return self._device.state_attributes return self._device.extra_state_attributes
@property @property
def device_info(self) -> dict[str, any]: def device_info(self) -> dict[str, any]:

View File

@ -73,6 +73,20 @@ VALID_COLOR_MODES = {
COLOR_MODES_BRIGHTNESS = VALID_COLOR_MODES - {COLOR_MODE_ONOFF} COLOR_MODES_BRIGHTNESS = VALID_COLOR_MODES - {COLOR_MODE_ONOFF}
COLOR_MODES_COLOR = {COLOR_MODE_HS, COLOR_MODE_RGB, COLOR_MODE_XY} COLOR_MODES_COLOR = {COLOR_MODE_HS, COLOR_MODE_RGB, COLOR_MODE_XY}
def valid_supported_color_modes(color_modes):
"""Validate the given color modes."""
color_modes = set(color_modes)
if (
not color_modes
or COLOR_MODE_UNKNOWN in color_modes
or (COLOR_MODE_BRIGHTNESS in color_modes and len(color_modes) > 1)
or (COLOR_MODE_ONOFF in color_modes and len(color_modes) > 1)
):
raise vol.Error(f"Invalid supported_color_modes {sorted(color_modes)}")
return color_modes
# Float that represents transition time in seconds to make change. # Float that represents transition time in seconds to make change.
ATTR_TRANSITION = "transition" ATTR_TRANSITION = "transition"

View File

@ -183,10 +183,14 @@ class MotionSignalStrengthSensor(CoordinatorEntity, SensorEntity):
if self.coordinator.data is None: if self.coordinator.data is None:
return False return False
if not self.coordinator.data[KEY_GATEWAY][ATTR_AVAILABLE]: gateway_available = self.coordinator.data[KEY_GATEWAY][ATTR_AVAILABLE]
return False if self._device_type == TYPE_GATEWAY:
return gateway_available
return self.coordinator.data[self._device.mac][ATTR_AVAILABLE] return (
gateway_available
and self.coordinator.data[self._device.mac][ATTR_AVAILABLE]
)
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):

View File

@ -35,6 +35,7 @@ from homeassistant.components.light import (
SUPPORT_WHITE_VALUE, SUPPORT_WHITE_VALUE,
VALID_COLOR_MODES, VALID_COLOR_MODES,
LightEntity, LightEntity,
valid_supported_color_modes,
) )
from homeassistant.const import ( from homeassistant.const import (
CONF_BRIGHTNESS, CONF_BRIGHTNESS,
@ -130,7 +131,10 @@ PLATFORM_SCHEMA_JSON = vol.All(
vol.Optional(CONF_RGB, default=DEFAULT_RGB): cv.boolean, vol.Optional(CONF_RGB, default=DEFAULT_RGB): cv.boolean,
vol.Optional(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Inclusive(CONF_SUPPORTED_COLOR_MODES, "color_mode"): vol.All( vol.Inclusive(CONF_SUPPORTED_COLOR_MODES, "color_mode"): vol.All(
cv.ensure_list, [vol.In(VALID_COLOR_MODES)], vol.Unique() cv.ensure_list,
[vol.In(VALID_COLOR_MODES)],
vol.Unique(),
valid_supported_color_modes,
), ),
vol.Optional(CONF_WHITE_VALUE, default=DEFAULT_WHITE_VALUE): cv.boolean, vol.Optional(CONF_WHITE_VALUE, default=DEFAULT_WHITE_VALUE): cv.boolean,
vol.Optional(CONF_XY, default=DEFAULT_XY): cv.boolean, vol.Optional(CONF_XY, default=DEFAULT_XY): cv.boolean,

View File

@ -70,12 +70,12 @@ class MySensorsCover(mysensors.device.MySensorsEntity, CoverEntity):
else: else:
amount = 100 if self._values.get(set_req.V_LIGHT) == STATE_ON else 0 amount = 100 if self._values.get(set_req.V_LIGHT) == STATE_ON else 0
if amount == 0:
return CoverState.CLOSED
if v_up and not v_down and not v_stop: if v_up and not v_down and not v_stop:
return CoverState.OPENING return CoverState.OPENING
if not v_up and v_down and not v_stop: if not v_up and v_down and not v_stop:
return CoverState.CLOSING return CoverState.CLOSING
if not v_up and not v_down and v_stop and amount == 0:
return CoverState.CLOSED
return CoverState.OPEN return CoverState.OPEN
@property @property

View File

@ -48,7 +48,7 @@ class ProwlNotificationService(BaseNotificationService):
"description": message, "description": message,
"priority": data["priority"] if data and "priority" in data else 0, "priority": data["priority"] if data and "priority" in data else 0,
} }
if data.get("url"): if data and data.get("url"):
payload["url"] = data["url"] payload["url"] = data["url"]
_LOGGER.debug("Attempting call Prowl service at %s", url) _LOGGER.debug("Attempting call Prowl service at %s", url)

View File

@ -3,6 +3,8 @@
"name": "Speedtest.net", "name": "Speedtest.net",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/speedtestdotnet", "documentation": "https://www.home-assistant.io/integrations/speedtestdotnet",
"requirements": ["speedtest-cli==2.1.2"], "requirements": [
"speedtest-cli==2.1.3"
],
"codeowners": ["@rohankapoorcom", "@engrbm87"] "codeowners": ["@rohankapoorcom", "@engrbm87"]
} }

View File

@ -39,7 +39,12 @@ from .hls import async_setup_hls
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
STREAM_SOURCE_RE = re.compile("//(.*):(.*)@") STREAM_SOURCE_RE = re.compile("//.*:.*@")
def redact_credentials(data):
"""Redact credentials from string data."""
return STREAM_SOURCE_RE.sub("//****:****@", data)
def create_stream(hass, stream_source, options=None): def create_stream(hass, stream_source, options=None):
@ -176,9 +181,7 @@ class Stream:
target=self._run_worker, target=self._run_worker,
) )
self._thread.start() self._thread.start()
_LOGGER.info( _LOGGER.info("Started stream: %s", redact_credentials(str(self.source)))
"Started stream: %s", STREAM_SOURCE_RE.sub("//", str(self.source))
)
def update_source(self, new_source): def update_source(self, new_source):
"""Restart the stream with a new stream source.""" """Restart the stream with a new stream source."""
@ -244,9 +247,7 @@ class Stream:
self._thread_quit.set() self._thread_quit.set()
self._thread.join() self._thread.join()
self._thread = None self._thread = None
_LOGGER.info( _LOGGER.info("Stopped stream: %s", redact_credentials(str(self.source)))
"Stopped stream: %s", STREAM_SOURCE_RE.sub("//", str(self.source))
)
async def async_record(self, video_path, duration=30, lookback=5): async def async_record(self, video_path, duration=30, lookback=5):
"""Make a .mp4 recording from a provided stream.""" """Make a .mp4 recording from a provided stream."""

View File

@ -5,7 +5,7 @@ import logging
import av import av
from . import STREAM_SOURCE_RE from . import redact_credentials
from .const import ( from .const import (
AUDIO_CODECS, AUDIO_CODECS,
MAX_MISSING_DTS, MAX_MISSING_DTS,
@ -128,9 +128,7 @@ def stream_worker(source, options, segment_buffer, quit_event):
try: try:
container = av.open(source, options=options, timeout=STREAM_TIMEOUT) container = av.open(source, options=options, timeout=STREAM_TIMEOUT)
except av.AVError: except av.AVError:
_LOGGER.error( _LOGGER.error("Error opening stream %s", redact_credentials(str(source)))
"Error opening stream %s", STREAM_SOURCE_RE.sub("//", str(source))
)
return return
try: try:
video_stream = container.streams.video[0] video_stream = container.streams.video[0]

View File

@ -112,7 +112,7 @@ class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity):
self._state = ALARM_STATE_TO_HA.get( self._state = ALARM_STATE_TO_HA.get(
self.coordinator.data["alarm"]["statusType"] self.coordinator.data["alarm"]["statusType"]
) )
self._changed_by = self.coordinator.data["alarm"]["name"] self._changed_by = self.coordinator.data["alarm"].get("name")
super()._handle_coordinator_update() super()._handle_coordinator_update()
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:

View File

@ -54,6 +54,7 @@ class VerisureSmartcam(CoordinatorEntity, Camera):
): ):
"""Initialize Verisure File Camera component.""" """Initialize Verisure File Camera component."""
super().__init__(coordinator) super().__init__(coordinator)
Camera.__init__(self)
self.serial_number = serial_number self.serial_number = serial_number
self._directory_path = directory_path self._directory_path = directory_path

View File

@ -169,11 +169,13 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
THERMOSTAT_MODE_PROPERTY, THERMOSTAT_MODE_PROPERTY,
CommandClass.THERMOSTAT_FAN_MODE, CommandClass.THERMOSTAT_FAN_MODE,
add_to_watched_value_ids=True, add_to_watched_value_ids=True,
check_all_endpoints=True,
) )
self._fan_state = self.get_zwave_value( self._fan_state = self.get_zwave_value(
THERMOSTAT_OPERATING_STATE_PROPERTY, THERMOSTAT_OPERATING_STATE_PROPERTY,
CommandClass.THERMOSTAT_FAN_STATE, CommandClass.THERMOSTAT_FAN_STATE,
add_to_watched_value_ids=True, add_to_watched_value_ids=True,
check_all_endpoints=True,
) )
self._set_modes_and_presets() self._set_modes_and_presets()
self._supported_features = 0 self._supported_features = 0

View File

@ -1,7 +1,7 @@
"""Constants used by Home Assistant components.""" """Constants used by Home Assistant components."""
MAJOR_VERSION = 2021 MAJOR_VERSION = 2021
MINOR_VERSION = 4 MINOR_VERSION = 4
PATCH_VERSION = "0" PATCH_VERSION = "1"
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__ = f"{__short_version__}.{PATCH_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER = (3, 8, 0) REQUIRED_PYTHON_VER = (3, 8, 0)

View File

@ -16,7 +16,7 @@ defusedxml==0.6.0
distro==1.5.0 distro==1.5.0
emoji==1.2.0 emoji==1.2.0
hass-nabucasa==0.42.0 hass-nabucasa==0.42.0
home-assistant-frontend==20210407.1 home-assistant-frontend==20210407.2
httpx==0.17.1 httpx==0.17.1
jinja2>=2.11.3 jinja2>=2.11.3
netdisco==2.8.2 netdisco==2.8.2

View File

@ -763,7 +763,7 @@ hole==0.5.1
holidays==0.10.5.2 holidays==0.10.5.2
# homeassistant.components.frontend # homeassistant.components.frontend
home-assistant-frontend==20210407.1 home-assistant-frontend==20210407.2
# homeassistant.components.zwave # homeassistant.components.zwave
homeassistant-pyozw==0.1.10 homeassistant-pyozw==0.1.10
@ -2108,7 +2108,7 @@ sonarr==0.3.0
speak2mary==1.4.0 speak2mary==1.4.0
# homeassistant.components.speedtestdotnet # homeassistant.components.speedtestdotnet
speedtest-cli==2.1.2 speedtest-cli==2.1.3
# homeassistant.components.spider # homeassistant.components.spider
spiderpy==1.4.2 spiderpy==1.4.2

View File

@ -412,7 +412,7 @@ hole==0.5.1
holidays==0.10.5.2 holidays==0.10.5.2
# homeassistant.components.frontend # homeassistant.components.frontend
home-assistant-frontend==20210407.1 home-assistant-frontend==20210407.2
# homeassistant.components.zwave # homeassistant.components.zwave
homeassistant-pyozw==0.1.10 homeassistant-pyozw==0.1.10
@ -1095,7 +1095,7 @@ sonarr==0.3.0
speak2mary==1.4.0 speak2mary==1.4.0
# homeassistant.components.speedtestdotnet # homeassistant.components.speedtestdotnet
speedtest-cli==2.1.2 speedtest-cli==2.1.3
# homeassistant.components.spider # homeassistant.components.spider
spiderpy==1.4.2 spiderpy==1.4.2

View File

@ -188,6 +188,33 @@ async def test_fail_setup_if_color_mode_deprecated(hass, mqtt_mock, deprecated):
assert hass.states.get("light.test") is None assert hass.states.get("light.test") is None
@pytest.mark.parametrize(
"supported_color_modes", [["onoff", "rgb"], ["brightness", "rgb"], ["unknown"]]
)
async def test_fail_setup_if_color_modes_invalid(
hass, mqtt_mock, supported_color_modes
):
"""Test if setup fails if supported color modes is invalid."""
config = {
light.DOMAIN: {
"brightness": True,
"color_mode": True,
"command_topic": "test_light_rgb/set",
"name": "test",
"platform": "mqtt",
"schema": "json",
"supported_color_modes": supported_color_modes,
}
}
assert await async_setup_component(
hass,
light.DOMAIN,
config,
)
await hass.async_block_till_done()
assert hass.states.get("light.test") is None
async def test_rgb_light(hass, mqtt_mock): async def test_rgb_light(hass, mqtt_mock):
"""Test RGB light flags brightness support.""" """Test RGB light flags brightness support."""
assert await async_setup_component( assert await async_setup_component(

View File

@ -266,4 +266,4 @@ async def test_recorder_log(hass, caplog):
with patch.object(hass.config, "is_allowed_path", return_value=True): with patch.object(hass.config, "is_allowed_path", return_value=True):
await stream.async_record("/example/path") await stream.async_record("/example/path")
assert "https://abcd:efgh@foo.bar" not in caplog.text assert "https://abcd:efgh@foo.bar" not in caplog.text
assert "https://foo.bar" in caplog.text assert "https://****:****@foo.bar" in caplog.text

View File

@ -588,4 +588,4 @@ async def test_worker_log(hass, caplog):
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert "https://abcd:efgh@foo.bar" not in caplog.text assert "https://abcd:efgh@foo.bar" not in caplog.text
assert "https://foo.bar" in caplog.text assert "https://****:****@foo.bar" in caplog.text

View File

@ -348,7 +348,9 @@ async def test_thermostat_different_endpoints(
"""Test an entity with values on a different endpoint from the primary value.""" """Test an entity with values on a different endpoint from the primary value."""
state = hass.states.get(CLIMATE_RADIO_THERMOSTAT_ENTITY) state = hass.states.get(CLIMATE_RADIO_THERMOSTAT_ENTITY)
assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.5 assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.8
assert state.attributes[ATTR_FAN_MODE] == "Auto low"
assert state.attributes[ATTR_FAN_STATE] == "Idle / off"
async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integration): async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integration):

View File

@ -495,7 +495,7 @@ async def test_poll_value(
assert args["valueId"] == { assert args["valueId"] == {
"commandClassName": "Thermostat Mode", "commandClassName": "Thermostat Mode",
"commandClass": 64, "commandClass": 64,
"endpoint": 0, "endpoint": 1,
"property": "mode", "property": "mode",
"propertyName": "mode", "propertyName": "mode",
"metadata": { "metadata": {
@ -503,19 +503,16 @@ async def test_poll_value(
"readable": True, "readable": True,
"writeable": True, "writeable": True,
"min": 0, "min": 0,
"max": 31, "max": 255,
"label": "Thermostat mode", "label": "Thermostat mode",
"states": { "states": {
"0": "Off", "0": "Off",
"1": "Heat", "1": "Heat",
"2": "Cool", "2": "Cool",
"3": "Auto",
"11": "Energy heat",
"12": "Energy cool",
}, },
}, },
"value": 1, "value": 2,
"ccVersion": 2, "ccVersion": 0,
} }
client.async_send_command.reset_mock() client.async_send_command.reset_mock()
@ -531,7 +528,7 @@ async def test_poll_value(
}, },
blocking=True, blocking=True,
) )
assert len(client.async_send_command.call_args_list) == 8 assert len(client.async_send_command.call_args_list) == 7
# Test polling against an invalid entity raises ValueError # Test polling against an invalid entity raises ValueError
with pytest.raises(ValueError): with pytest.raises(ValueError):