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 cdate = device_time.UTCDateTime
else: else:
tzone = ( 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 or dt_util.DEFAULT_TIME_ZONE
) )
cdate = device_time.LocalDateTime cdate = device_time.LocalDateTime

View File

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

View File

@ -78,7 +78,7 @@ class PlexSession:
if media.librarySectionID in SPECIAL_SECTIONS: if media.librarySectionID in SPECIAL_SECTIONS:
self.media_library_title = SPECIAL_SECTIONS[media.librarySectionID] 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 self.media_library_title = UNKNOWN_SECTION
_LOGGER.warning( _LOGGER.warning(
"Unknown library section ID (%s) for title '%s', please create an issue", "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( bridge = SamsungTVBridge.get_bridge(
self._reauth_entry.data[CONF_METHOD], self._reauth_entry.data[CONF_HOST] 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: if result == RESULT_SUCCESS:
new_data = dict(self._reauth_entry.data) new_data = dict(self._reauth_entry.data)
new_data[CONF_TOKEN] = bridge.token new_data[CONF_TOKEN] = bridge.token
self.hass.config_entries.async_update_entry( self.hass.config_entries.async_update_entry(
self._reauth_entry, data=new_data 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") return self.async_abort(reason="reauth_successful")
if result not in (RESULT_AUTH_MISSING, RESULT_CANNOT_CONNECT): if result not in (RESULT_AUTH_MISSING, RESULT_CANNOT_CONNECT):
return self.async_abort(reason=result) 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.config_entries import SOURCE_REAUTH
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, STATE_OFF, STATE_ON 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 import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.script import Script from homeassistant.helpers.script import Script
@ -50,6 +51,13 @@ SUPPORT_SAMSUNGTV = (
| SUPPORT_PLAY_MEDIA | 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): async def async_setup_entry(hass, entry, async_add_entities):
"""Set up the Samsung TV from a config entry.""" """Set up the Samsung TV from a config entry."""
@ -148,7 +156,12 @@ class SamsungTVDevice(MediaPlayerEntity):
"""Return the availability of the device.""" """Return the availability of the device."""
if self._auth_failed: if self._auth_failed:
return False 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 @property
def device_info(self): def device_info(self):
@ -187,7 +200,7 @@ class SamsungTVDevice(MediaPlayerEntity):
def turn_off(self): def turn_off(self):
"""Turn off media player.""" """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") self.send_key("KEY_POWEROFF")
# Force closing of remote session to provide instant UI feedback # 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): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up Shelly from a config entry.""" """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] = {}
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id][DEVICE] = None 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"] self.zone_name = speaker_info["zone_name"]
# Battery # Battery
self.battery_info: dict[str, Any] | None = None self.battery_info: dict[str, Any] = {}
self._last_battery_event: datetime.datetime | None = None self._last_battery_event: datetime.datetime | None = None
self._battery_poll_timer: Callable | 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 self.hass, f"{SONOS_SEEN}-{self.soco.uid}", self.async_seen
) )
if (battery_info := fetch_battery_info_or_none(self.soco)) is None: if battery_info := fetch_battery_info_or_none(self.soco):
self._platforms_ready.update({BINARY_SENSOR_DOMAIN, SENSOR_DOMAIN})
else:
self.battery_info = battery_info self.battery_info = battery_info
# Only create a polling task if successful, may fail on S1 firmware # Battery events can be infrequent, polling is still necessary
if battery_info: self._battery_poll_timer = self.hass.helpers.event.track_time_interval(
# Battery events can be infrequent, polling is still necessary self.async_poll_battery, BATTERY_SCAN_INTERVAL
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"
)
dispatcher_send(self.hass, SONOS_CREATE_BATTERY, self) 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(): if new_alarms := self.update_alarms_for_speaker():
dispatcher_send(self.hass, SONOS_CREATE_ALARM, self, new_alarms) 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: async def async_update_device_properties(self, event: SonosEvent) -> None:
"""Update device properties from an event.""" """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(",")) battery_dict = dict(x.split(":") for x in more_info.split(","))
await self.async_update_battery_info(battery_dict) await self.async_update_battery_info(battery_dict)
self.async_write_entity_states() self.async_write_entity_states()
@ -514,12 +508,19 @@ class SonosSpeaker:
if not self._battery_poll_timer: if not self._battery_poll_timer:
# Battery info received for an S1 speaker # Battery info received for an S1 speaker
new_battery = not self.battery_info
self.battery_info.update( self.battery_info.update(
{ {
"Level": int(battery_dict["BattPct"]), "Level": int(battery_dict["BattPct"]),
"PowerSource": "EXTERNAL" if is_charging else "BATTERY", "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 return
if is_charging == self.charging: if is_charging == self.charging:

View File

@ -5,7 +5,7 @@ from typing import Final
MAJOR_VERSION: Final = 2021 MAJOR_VERSION: Final = 2021
MINOR_VERSION: Final = 6 MINOR_VERSION: Final = 6
PATCH_VERSION: Final = "3" PATCH_VERSION: Final = "4"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0) 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.""" """Test reauthenticate websocket."""
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_WS_ENTRY) entry = MockConfigEntry(domain=DOMAIN, data=MOCK_WS_ENTRY)
entry.add_to_hass(hass) entry.add_to_hass(hass)
assert entry.state == config_entries.ConfigEntryState.NOT_LOADED
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"entry_id": entry.entry_id, "source": config_entries.SOURCE_REAUTH}, 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() await hass.async_block_till_done()
assert result2["type"] == "abort" assert result2["type"] == "abort"
assert result2["reason"] == "reauth_successful" assert result2["reason"] == "reauth_successful"
assert entry.state == config_entries.ConfigEntryState.LOADED
async def test_form_reauth_websocket_cannot_connect(hass, remotews: Mock): 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( assert await hass.services.async_call(
DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: ENTITY_ID_NOTURNON}, True 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) state = hass.states.get(ENTITY_ID_NOTURNON)
# Should be STATE_UNAVAILABLE since there is no way to turn it back on # Should be STATE_UNAVAILABLE since there is no way to turn it back on
assert state.state == STATE_UNAVAILABLE 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 import DOMAIN
from homeassistant.components.sonos.binary_sensor import ATTR_BATTERY_POWER_SOURCE 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 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() entity_registry = await hass.helpers.entity_registry.async_get_registry()
battery = entity_registry.entities["sensor.zone_a_battery"] assert "sensor.zone_a_battery" not in entity_registry.entities
battery_state = hass.states.get(battery.entity_id) assert "binary_sensor.zone_a_power" not in entity_registry.entities
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
# Update the speaker with a callback event # Update the speaker with a callback event
sub_callback(battery_event) sub_callback(battery_event)
await hass.async_block_till_done() await hass.async_block_till_done()
battery = entity_registry.entities["sensor.zone_a_battery"]
battery_state = hass.states.get(battery.entity_id) battery_state = hass.states.get(battery.entity_id)
assert battery_state.state == "100" assert battery_state.state == "100"
power = entity_registry.entities["binary_sensor.zone_a_power"]
power_state = hass.states.get(power.entity_id) power_state = hass.states.get(power.entity_id)
assert power_state.state == STATE_OFF assert power_state.state == STATE_OFF
assert power_state.attributes.get(ATTR_BATTERY_POWER_SOURCE) == "BATTERY" assert power_state.attributes.get(ATTR_BATTERY_POWER_SOURCE) == "BATTERY"