Merge pull request #51768 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2021-06-11 22:18:37 -07:00 committed by GitHub
commit 2535f5c155
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 71 additions and 30 deletions

View File

@ -169,7 +169,9 @@ class ONVIFDevice:
cdate = device_time.UTCDateTime
else:
tzone = (
dt_util.get_time_zone(device_time.TimeZone)
dt_util.get_time_zone(
device_time.TimeZone or str(dt_util.DEFAULT_TIME_ZONE)
)
or dt_util.DEFAULT_TIME_ZONE
)
cdate = device_time.LocalDateTime

View File

@ -64,7 +64,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
name="sensor",
update_method=async_update_data,
# Polling interval. Will only be polled if there are subscribers.
update_interval=timedelta(seconds=300),
update_interval=timedelta(seconds=3600),
)
hass.data.setdefault(DOMAIN, {})

View File

@ -78,7 +78,7 @@ class PlexSession:
if media.librarySectionID in SPECIAL_SECTIONS:
self.media_library_title = SPECIAL_SECTIONS[media.librarySectionID]
elif media.librarySectionID < 1:
elif media.librarySectionID and media.librarySectionID < 1:
self.media_library_title = UNKNOWN_SECTION
_LOGGER.warning(
"Unknown library section ID (%s) for title '%s', please create an issue",

View File

@ -291,13 +291,14 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
bridge = SamsungTVBridge.get_bridge(
self._reauth_entry.data[CONF_METHOD], self._reauth_entry.data[CONF_HOST]
)
result = bridge.try_connect()
result = await self.hass.async_add_executor_job(bridge.try_connect)
if result == RESULT_SUCCESS:
new_data = dict(self._reauth_entry.data)
new_data[CONF_TOKEN] = bridge.token
self.hass.config_entries.async_update_entry(
self._reauth_entry, data=new_data
)
await self.hass.config_entries.async_reload(self._reauth_entry.entry_id)
return self.async_abort(reason="reauth_successful")
if result not in (RESULT_AUTH_MISSING, RESULT_CANNOT_CONNECT):
return self.async_abort(reason=result)

View File

@ -21,6 +21,7 @@ from homeassistant.components.media_player.const import (
)
from homeassistant.config_entries import SOURCE_REAUTH
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, STATE_OFF, STATE_ON
from homeassistant.helpers import entity_component
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.script import Script
@ -50,6 +51,13 @@ SUPPORT_SAMSUNGTV = (
| SUPPORT_PLAY_MEDIA
)
# Since the TV will take a few seconds to go to sleep
# and actually be seen as off, we need to wait just a bit
# more than the next scan interval
SCAN_INTERVAL_PLUS_OFF_TIME = entity_component.DEFAULT_SCAN_INTERVAL + timedelta(
seconds=5
)
async def async_setup_entry(hass, entry, async_add_entities):
"""Set up the Samsung TV from a config entry."""
@ -148,7 +156,12 @@ class SamsungTVDevice(MediaPlayerEntity):
"""Return the availability of the device."""
if self._auth_failed:
return False
return self._state == STATE_ON or self._on_script or self._mac
return (
self._state == STATE_ON
or self._on_script
or self._mac
or self._power_off_in_progress()
)
@property
def device_info(self):
@ -187,7 +200,7 @@ class SamsungTVDevice(MediaPlayerEntity):
def turn_off(self):
"""Turn off media player."""
self._end_of_power_off = dt_util.utcnow() + timedelta(seconds=15)
self._end_of_power_off = dt_util.utcnow() + SCAN_INTERVAL_PLUS_OFF_TIME
self.send_key("KEY_POWEROFF")
# Force closing of remote session to provide instant UI feedback

View File

@ -68,6 +68,18 @@ async def async_setup(hass: HomeAssistant, config: dict):
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up Shelly from a config entry."""
# The custom component for Shelly devices uses shelly domain as well as core
# integration. If the user removes the custom component but doesn't remove the
# config entry, core integration will try to configure that config entry with an
# error. The config entry data for this custom component doesn't contain host
# value, so if host isn't present, config entry will not be configured.
if not entry.data.get(CONF_HOST):
_LOGGER.warning(
"The config entry %s probably comes from a custom integration, please remove it if you want to use core Shelly integration",
entry.title,
)
return False
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id] = {}
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][DEVICE] = None

View File

@ -173,7 +173,7 @@ class SonosSpeaker:
self.zone_name = speaker_info["zone_name"]
# Battery
self.battery_info: dict[str, Any] | None = None
self.battery_info: dict[str, Any] = {}
self._last_battery_event: datetime.datetime | None = None
self._battery_poll_timer: Callable | None = None
@ -208,21 +208,15 @@ class SonosSpeaker:
self.hass, f"{SONOS_SEEN}-{self.soco.uid}", self.async_seen
)
if (battery_info := fetch_battery_info_or_none(self.soco)) is None:
self._platforms_ready.update({BINARY_SENSOR_DOMAIN, SENSOR_DOMAIN})
else:
if battery_info := fetch_battery_info_or_none(self.soco):
self.battery_info = battery_info
# Only create a polling task if successful, may fail on S1 firmware
if battery_info:
# Battery events can be infrequent, polling is still necessary
self._battery_poll_timer = self.hass.helpers.event.track_time_interval(
self.async_poll_battery, BATTERY_SCAN_INTERVAL
)
else:
_LOGGER.warning(
"S1 firmware detected, battery sensor may update infrequently"
)
# Battery events can be infrequent, polling is still necessary
self._battery_poll_timer = self.hass.helpers.event.track_time_interval(
self.async_poll_battery, BATTERY_SCAN_INTERVAL
)
dispatcher_send(self.hass, SONOS_CREATE_BATTERY, self)
else:
self._platforms_ready.update({BINARY_SENSOR_DOMAIN, SENSOR_DOMAIN})
if new_alarms := self.update_alarms_for_speaker():
dispatcher_send(self.hass, SONOS_CREATE_ALARM, self, new_alarms)
@ -386,7 +380,7 @@ class SonosSpeaker:
async def async_update_device_properties(self, event: SonosEvent) -> None:
"""Update device properties from an event."""
if (more_info := event.variables.get("more_info")) is not None:
if more_info := event.variables.get("more_info"):
battery_dict = dict(x.split(":") for x in more_info.split(","))
await self.async_update_battery_info(battery_dict)
self.async_write_entity_states()
@ -514,12 +508,19 @@ class SonosSpeaker:
if not self._battery_poll_timer:
# Battery info received for an S1 speaker
new_battery = not self.battery_info
self.battery_info.update(
{
"Level": int(battery_dict["BattPct"]),
"PowerSource": "EXTERNAL" if is_charging else "BATTERY",
}
)
if new_battery:
_LOGGER.warning(
"S1 firmware detected on %s, battery info may update infrequently",
self.zone_name,
)
async_dispatcher_send(self.hass, SONOS_CREATE_BATTERY, self)
return
if is_charging == self.charging:

View File

@ -5,7 +5,7 @@ from typing import Final
MAJOR_VERSION: Final = 2021
MINOR_VERSION: Final = 6
PATCH_VERSION: Final = "3"
PATCH_VERSION: Final = "4"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)

View File

@ -905,6 +905,8 @@ async def test_form_reauth_websocket(hass, remotews: Mock):
"""Test reauthenticate websocket."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_WS_ENTRY)
entry.add_to_hass(hass)
assert entry.state == config_entries.ConfigEntryState.NOT_LOADED
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"entry_id": entry.entry_id, "source": config_entries.SOURCE_REAUTH},
@ -920,6 +922,7 @@ async def test_form_reauth_websocket(hass, remotews: Mock):
await hass.async_block_till_done()
assert result2["type"] == "abort"
assert result2["reason"] == "reauth_successful"
assert entry.state == config_entries.ConfigEntryState.LOADED
async def test_form_reauth_websocket_cannot_connect(hass, remotews: Mock):

View File

@ -419,6 +419,18 @@ async def test_state_without_turnon(hass, remote):
assert await hass.services.async_call(
DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: ENTITY_ID_NOTURNON}, True
)
state = hass.states.get(ENTITY_ID_NOTURNON)
# Should be STATE_UNAVAILABLE after the timer expires
assert state.state == STATE_OFF
next_update = dt_util.utcnow() + timedelta(seconds=20)
with patch(
"homeassistant.components.samsungtv.bridge.Remote",
side_effect=OSError,
), patch("homeassistant.util.dt.utcnow", return_value=next_update):
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID_NOTURNON)
# Should be STATE_UNAVAILABLE since there is no way to turn it back on
assert state.state == STATE_UNAVAILABLE

View File

@ -3,7 +3,7 @@ from pysonos.exceptions import NotSupportedException
from homeassistant.components.sonos import DOMAIN
from homeassistant.components.sonos.binary_sensor import ATTR_BATTERY_POWER_SOURCE
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.setup import async_setup_component
@ -68,21 +68,18 @@ async def test_battery_on_S1(hass, config_entry, config, soco, battery_event):
entity_registry = await hass.helpers.entity_registry.async_get_registry()
battery = entity_registry.entities["sensor.zone_a_battery"]
battery_state = hass.states.get(battery.entity_id)
assert battery_state.state == STATE_UNAVAILABLE
power = entity_registry.entities["binary_sensor.zone_a_power"]
power_state = hass.states.get(power.entity_id)
assert power_state.state == STATE_UNAVAILABLE
assert "sensor.zone_a_battery" not in entity_registry.entities
assert "binary_sensor.zone_a_power" not in entity_registry.entities
# Update the speaker with a callback event
sub_callback(battery_event)
await hass.async_block_till_done()
battery = entity_registry.entities["sensor.zone_a_battery"]
battery_state = hass.states.get(battery.entity_id)
assert battery_state.state == "100"
power = entity_registry.entities["binary_sensor.zone_a_power"]
power_state = hass.states.get(power.entity_id)
assert power_state.state == STATE_OFF
assert power_state.attributes.get(ATTR_BATTERY_POWER_SOURCE) == "BATTERY"