forked from home-assistant/core
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
076227acbe | ||
|
|
34f266bfa6 | ||
|
|
0d351e4a0e | ||
|
|
79cfd444d9 | ||
|
|
67699e3c1f | ||
|
|
47560006b8 | ||
|
|
d6fd41bd03 | ||
|
|
c8e678a2c6 | ||
|
|
398fca3b9d | ||
|
|
f2bc69a653 | ||
|
|
fcb2c26a24 | ||
|
|
ee7a2b29ad | ||
|
|
dcc7bc10e8 | ||
|
|
8f34d1cf32 | ||
|
|
fcc66139f8 | ||
|
|
2535f5c155 | ||
|
|
97e36cd3c4 | ||
|
|
548e847453 | ||
|
|
60b89101e5 | ||
|
|
90d28e911c | ||
|
|
cfea8a9ad1 | ||
|
|
880fe82337 | ||
|
|
3a5f51ed7d | ||
|
|
cca1b426bb |
@@ -12,7 +12,7 @@ from homeassistant.const import (
|
||||
CONF_NAME,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
@@ -81,16 +81,11 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
self._sensor_data = _get_sensor_data(coordinator.data, forecast_day, kind)
|
||||
if forecast_day is None:
|
||||
self._description = SENSOR_TYPES[kind]
|
||||
self._sensor_data: dict[str, Any]
|
||||
if kind == "Precipitation":
|
||||
self._sensor_data = coordinator.data["PrecipitationSummary"][kind]
|
||||
else:
|
||||
self._sensor_data = coordinator.data[kind]
|
||||
else:
|
||||
self._description = FORECAST_SENSOR_TYPES[kind]
|
||||
self._sensor_data = coordinator.data[ATTR_FORECAST][forecast_day][kind]
|
||||
self._unit_system = API_METRIC if coordinator.is_metric else API_IMPERIAL
|
||||
self._name = name
|
||||
self.kind = kind
|
||||
@@ -182,3 +177,24 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
|
||||
def entity_registry_enabled_default(self) -> bool:
|
||||
"""Return if the entity should be enabled when first added to the entity registry."""
|
||||
return self._description[ATTR_ENABLED]
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle data update."""
|
||||
self._sensor_data = _get_sensor_data(
|
||||
self.coordinator.data, self.forecast_day, self.kind
|
||||
)
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
def _get_sensor_data(
|
||||
sensors: dict[str, Any], forecast_day: int | None, kind: str
|
||||
) -> Any:
|
||||
"""Get sensor data."""
|
||||
if forecast_day is not None:
|
||||
return sensors[ATTR_FORECAST][forecast_day][kind]
|
||||
|
||||
if kind == "Precipitation":
|
||||
return sensors["PrecipitationSummary"][kind]
|
||||
|
||||
return sensors[kind]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Daikin AC",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/daikin",
|
||||
"requirements": ["pydaikin==2.4.1"],
|
||||
"requirements": ["pydaikin==2.4.3"],
|
||||
"codeowners": ["@fredrike"],
|
||||
"zeroconf": ["_dkapi._tcp.local."],
|
||||
"quality_scale": "platinum",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
from datetime import date
|
||||
import logging
|
||||
|
||||
from garminconnect_aio import (
|
||||
from garminconnect_ha import (
|
||||
Garmin,
|
||||
GarminConnectAuthenticationError,
|
||||
GarminConnectConnectionError,
|
||||
@@ -13,7 +13,6 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
from .const import DEFAULT_UPDATE_INTERVAL, DOMAIN
|
||||
@@ -26,14 +25,13 @@ PLATFORMS = ["sensor"]
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Set up Garmin Connect from a config entry."""
|
||||
|
||||
websession = async_get_clientsession(hass)
|
||||
username: str = entry.data[CONF_USERNAME]
|
||||
password: str = entry.data[CONF_PASSWORD]
|
||||
|
||||
garmin_client = Garmin(websession, username, password)
|
||||
api = Garmin(username, password)
|
||||
|
||||
try:
|
||||
await garmin_client.login()
|
||||
await hass.async_add_executor_job(api.login)
|
||||
except (
|
||||
GarminConnectAuthenticationError,
|
||||
GarminConnectTooManyRequestsError,
|
||||
@@ -49,7 +47,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
_LOGGER.exception("Unknown error occurred during Garmin Connect login request")
|
||||
return False
|
||||
|
||||
garmin_data = GarminConnectData(hass, garmin_client)
|
||||
garmin_data = GarminConnectData(hass, api)
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.entry_id] = garmin_data
|
||||
|
||||
@@ -81,14 +79,20 @@ class GarminConnectData:
|
||||
today = date.today()
|
||||
|
||||
try:
|
||||
summary = await self.client.get_user_summary(today.isoformat())
|
||||
body = await self.client.get_body_composition(today.isoformat())
|
||||
summary = await self.hass.async_add_executor_job(
|
||||
self.client.get_user_summary, today.isoformat()
|
||||
)
|
||||
body = await self.hass.async_add_executor_job(
|
||||
self.client.get_body_composition, today.isoformat()
|
||||
)
|
||||
|
||||
self.data = {
|
||||
**summary,
|
||||
**body["totalAverage"],
|
||||
}
|
||||
self.data["nextAlarm"] = await self.client.get_device_alarms()
|
||||
self.data["nextAlarm"] = await self.hass.async_add_executor_job(
|
||||
self.client.get_device_alarms
|
||||
)
|
||||
except (
|
||||
GarminConnectAuthenticationError,
|
||||
GarminConnectTooManyRequestsError,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Config flow for Garmin Connect integration."""
|
||||
import logging
|
||||
|
||||
from garminconnect_aio import (
|
||||
from garminconnect_ha import (
|
||||
Garmin,
|
||||
GarminConnectAuthenticationError,
|
||||
GarminConnectConnectionError,
|
||||
@@ -11,7 +11,6 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
@@ -38,15 +37,14 @@ class GarminConnectConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
if user_input is None:
|
||||
return await self._show_setup_form()
|
||||
|
||||
websession = async_get_clientsession(self.hass)
|
||||
username = user_input[CONF_USERNAME]
|
||||
password = user_input[CONF_PASSWORD]
|
||||
|
||||
garmin_client = Garmin(websession, username, password)
|
||||
api = Garmin(username, password)
|
||||
|
||||
errors = {}
|
||||
try:
|
||||
await garmin_client.login()
|
||||
await self.hass.async_add_executor_job(api.login)
|
||||
except GarminConnectConnectionError:
|
||||
errors["base"] = "cannot_connect"
|
||||
return await self._show_setup_form(errors)
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"domain": "garmin_connect",
|
||||
"name": "Garmin Connect",
|
||||
"documentation": "https://www.home-assistant.io/integrations/garmin_connect",
|
||||
"requirements": ["garminconnect_aio==0.1.4"],
|
||||
"requirements": ["garminconnect_ha==0.1.6"],
|
||||
"codeowners": ["@cyberjunky"],
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling"
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"domain": "ialarm",
|
||||
"name": "Antifurto365 iAlarm",
|
||||
"documentation": "https://www.home-assistant.io/integrations/ialarm",
|
||||
"requirements": ["pyialarm==1.8.1"],
|
||||
"requirements": ["pyialarm==1.9.0"],
|
||||
"codeowners": ["@RyuzakiKK"],
|
||||
"config_flow": true,
|
||||
"iot_class": "local_polling"
|
||||
|
||||
@@ -160,7 +160,7 @@ def handle_push_notification_channel(hass, connection, msg):
|
||||
registered_channels = hass.data[DOMAIN][DATA_PUSH_CHANNEL]
|
||||
|
||||
if webhook_id in registered_channels:
|
||||
registered_channels.pop(webhook_id)()
|
||||
registered_channels.pop(webhook_id)
|
||||
|
||||
@callback
|
||||
def forward_push_notification(data):
|
||||
|
||||
@@ -153,8 +153,8 @@ class OmniLogicPumpControl(OmniLogicSwitch):
|
||||
state_key=state_key,
|
||||
)
|
||||
|
||||
self._max_speed = int(coordinator.data[item_id]["Max-Pump-Speed"])
|
||||
self._min_speed = int(coordinator.data[item_id]["Min-Pump-Speed"])
|
||||
self._max_speed = int(coordinator.data[item_id].get("Max-Pump-Speed", 100))
|
||||
self._min_speed = int(coordinator.data[item_id].get("Min-Pump-Speed", 0))
|
||||
|
||||
if "Filter-Type" in coordinator.data[item_id]:
|
||||
self._pump_type = PUMP_TYPES[coordinator.data[item_id]["Filter-Type"]]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, {})
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"domain": "rfxtrx",
|
||||
"name": "RFXCOM RFXtrx",
|
||||
"documentation": "https://www.home-assistant.io/integrations/rfxtrx",
|
||||
"requirements": ["pyRFXtrx==0.26.1"],
|
||||
"requirements": ["pyRFXtrx==0.27.0"],
|
||||
"codeowners": ["@danielhiversen", "@elupus", "@RobBie1221"],
|
||||
"config_flow": true,
|
||||
"iot_class": "local_push"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,8 +380,16 @@ 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(","))
|
||||
if "BattChg" not in battery_dict:
|
||||
_LOGGER.debug(
|
||||
"Unknown device properties update for %s (%s), please report an issue: '%s'",
|
||||
self.zone_name,
|
||||
self.model_name,
|
||||
more_info,
|
||||
)
|
||||
return
|
||||
await self.async_update_battery_info(battery_dict)
|
||||
self.async_write_entity_states()
|
||||
|
||||
@@ -514,12 +516,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:
|
||||
|
||||
@@ -118,6 +118,7 @@ class WhoisSensor(SensorEntity):
|
||||
expiration_date = response["expiration_date"]
|
||||
if isinstance(expiration_date, list):
|
||||
attrs[ATTR_EXPIRES] = expiration_date[0].isoformat()
|
||||
expiration_date = expiration_date[0]
|
||||
else:
|
||||
attrs[ATTR_EXPIRES] = expiration_date.isoformat()
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import Final
|
||||
|
||||
MAJOR_VERSION: Final = 2021
|
||||
MINOR_VERSION: Final = 6
|
||||
PATCH_VERSION: Final = "3"
|
||||
PATCH_VERSION: Final = "6"
|
||||
__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)
|
||||
|
||||
@@ -63,3 +63,7 @@ enum34==1000000000.0.0
|
||||
typing==1000000000.0.0
|
||||
uuid==1000000000.0.0
|
||||
|
||||
# httpcore 0.13.4 breaks several integrations
|
||||
# https://github.com/home-assistant/core/issues/51778
|
||||
httpcore==0.13.3
|
||||
|
||||
|
||||
@@ -635,7 +635,7 @@ gTTS==2.2.2
|
||||
garages-amsterdam==2.1.1
|
||||
|
||||
# homeassistant.components.garmin_connect
|
||||
garminconnect_aio==0.1.4
|
||||
garminconnect_ha==0.1.6
|
||||
|
||||
# homeassistant.components.geniushub
|
||||
geniushub-client==0.6.30
|
||||
@@ -1253,7 +1253,7 @@ pyMetEireann==0.2
|
||||
pyMetno==0.8.3
|
||||
|
||||
# homeassistant.components.rfxtrx
|
||||
pyRFXtrx==0.26.1
|
||||
pyRFXtrx==0.27.0
|
||||
|
||||
# homeassistant.components.switchmate
|
||||
# pySwitchmate==0.4.6
|
||||
@@ -1352,7 +1352,7 @@ pycsspeechtts==1.0.4
|
||||
# pycups==1.9.73
|
||||
|
||||
# homeassistant.components.daikin
|
||||
pydaikin==2.4.1
|
||||
pydaikin==2.4.3
|
||||
|
||||
# homeassistant.components.danfoss_air
|
||||
pydanfossair==0.1.0
|
||||
@@ -1464,7 +1464,7 @@ pyhomematic==0.1.72
|
||||
pyhomeworks==0.0.6
|
||||
|
||||
# homeassistant.components.ialarm
|
||||
pyialarm==1.8.1
|
||||
pyialarm==1.9.0
|
||||
|
||||
# homeassistant.components.icloud
|
||||
pyicloud==0.10.2
|
||||
|
||||
@@ -341,7 +341,7 @@ gTTS==2.2.2
|
||||
garages-amsterdam==2.1.1
|
||||
|
||||
# homeassistant.components.garmin_connect
|
||||
garminconnect_aio==0.1.4
|
||||
garminconnect_ha==0.1.6
|
||||
|
||||
# homeassistant.components.geo_json_events
|
||||
# homeassistant.components.usgs_earthquakes_feed
|
||||
@@ -693,7 +693,7 @@ pyMetEireann==0.2
|
||||
pyMetno==0.8.3
|
||||
|
||||
# homeassistant.components.rfxtrx
|
||||
pyRFXtrx==0.26.1
|
||||
pyRFXtrx==0.27.0
|
||||
|
||||
# homeassistant.components.tibber
|
||||
pyTibber==0.17.0
|
||||
@@ -747,7 +747,7 @@ pycomfoconnect==0.4
|
||||
pycoolmasternet-async==0.1.2
|
||||
|
||||
# homeassistant.components.daikin
|
||||
pydaikin==2.4.1
|
||||
pydaikin==2.4.3
|
||||
|
||||
# homeassistant.components.deconz
|
||||
pydeconz==79
|
||||
@@ -808,7 +808,7 @@ pyhiveapi==0.4.2
|
||||
pyhomematic==0.1.72
|
||||
|
||||
# homeassistant.components.ialarm
|
||||
pyialarm==1.8.1
|
||||
pyialarm==1.9.0
|
||||
|
||||
# homeassistant.components.icloud
|
||||
pyicloud==0.10.2
|
||||
|
||||
@@ -83,6 +83,10 @@ enum34==1000000000.0.0
|
||||
typing==1000000000.0.0
|
||||
uuid==1000000000.0.0
|
||||
|
||||
# httpcore 0.13.4 breaks several integrations
|
||||
# https://github.com/home-assistant/core/issues/51778
|
||||
httpcore==0.13.3
|
||||
|
||||
"""
|
||||
|
||||
IGNORE_PRE_COMMIT_HOOK_ID = (
|
||||
|
||||
@@ -673,3 +673,36 @@ async def test_sensor_imperial_units(hass):
|
||||
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
|
||||
assert state.attributes.get(ATTR_ICON) == "mdi:weather-fog"
|
||||
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == LENGTH_FEET
|
||||
|
||||
|
||||
async def test_state_update(hass):
|
||||
"""Ensure the sensor state changes after updating the data."""
|
||||
await init_integration(hass)
|
||||
|
||||
state = hass.states.get("sensor.home_cloud_ceiling")
|
||||
assert state
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
assert state.state == "3200"
|
||||
|
||||
future = utcnow() + timedelta(minutes=60)
|
||||
|
||||
current_condition = json.loads(
|
||||
load_fixture("accuweather/current_conditions_data.json")
|
||||
)
|
||||
current_condition["Ceiling"]["Metric"]["Value"] = 3300
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.accuweather.AccuWeather.async_get_current_conditions",
|
||||
return_value=current_condition,
|
||||
), patch(
|
||||
"homeassistant.components.accuweather.AccuWeather.requests_remaining",
|
||||
new_callable=PropertyMock,
|
||||
return_value=10,
|
||||
):
|
||||
async_fire_time_changed(hass, future)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.home_cloud_ceiling")
|
||||
assert state
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
assert state.state == "3300"
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
"""Test the Garmin Connect config flow."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from garminconnect_aio import (
|
||||
from garminconnect_ha import (
|
||||
GarminConnectAuthenticationError,
|
||||
GarminConnectConnectionError,
|
||||
GarminConnectTooManyRequestsError,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries, data_entry_flow
|
||||
from homeassistant.components.garmin_connect.const import DOMAIN
|
||||
@@ -21,37 +20,23 @@ MOCK_CONF = {
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(name="mock_garmin_connect")
|
||||
def mock_garmin():
|
||||
"""Mock Garmin Connect."""
|
||||
with patch(
|
||||
"homeassistant.components.garmin_connect.config_flow.Garmin",
|
||||
) as garmin:
|
||||
garmin.return_value.login.return_value = MOCK_CONF[CONF_ID]
|
||||
yield garmin.return_value
|
||||
|
||||
|
||||
async def test_show_form(hass):
|
||||
"""Test that the form is served with no input."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {}
|
||||
assert result["step_id"] == config_entries.SOURCE_USER
|
||||
|
||||
|
||||
async def test_step_user(hass):
|
||||
"""Test registering an integration and finishing flow works."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.garmin_connect.Garmin.login",
|
||||
return_value="my@email.address",
|
||||
), patch(
|
||||
"homeassistant.components.garmin_connect.async_setup_entry", return_value=True
|
||||
):
|
||||
), patch(
|
||||
"homeassistant.components.garmin_connect.config_flow.Garmin",
|
||||
) as garmin:
|
||||
garmin.return_value.login.return_value = MOCK_CONF[CONF_ID]
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_CONF
|
||||
)
|
||||
@@ -59,60 +44,69 @@ async def test_step_user(hass):
|
||||
assert result["data"] == MOCK_CONF
|
||||
|
||||
|
||||
async def test_connection_error(hass, mock_garmin_connect):
|
||||
async def test_connection_error(hass):
|
||||
"""Test for connection error."""
|
||||
mock_garmin_connect.login.side_effect = GarminConnectConnectionError("errormsg")
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_CONF
|
||||
)
|
||||
with patch(
|
||||
"homeassistant.components.garmin_connect.Garmin.login",
|
||||
side_effect=GarminConnectConnectionError("errormsg"),
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": "user"}, data=MOCK_CONF
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
|
||||
async def test_authentication_error(hass, mock_garmin_connect):
|
||||
async def test_authentication_error(hass):
|
||||
"""Test for authentication error."""
|
||||
mock_garmin_connect.login.side_effect = GarminConnectAuthenticationError("errormsg")
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_CONF
|
||||
)
|
||||
with patch(
|
||||
"homeassistant.components.garmin_connect.Garmin.login",
|
||||
side_effect=GarminConnectAuthenticationError("errormsg"),
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": "user"}, data=MOCK_CONF
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {"base": "invalid_auth"}
|
||||
|
||||
|
||||
async def test_toomanyrequest_error(hass, mock_garmin_connect):
|
||||
async def test_toomanyrequest_error(hass):
|
||||
"""Test for toomanyrequests error."""
|
||||
mock_garmin_connect.login.side_effect = GarminConnectTooManyRequestsError(
|
||||
"errormsg"
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_CONF
|
||||
)
|
||||
with patch(
|
||||
"homeassistant.components.garmin_connect.Garmin.login",
|
||||
side_effect=GarminConnectTooManyRequestsError("errormsg"),
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": "user"}, data=MOCK_CONF
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {"base": "too_many_requests"}
|
||||
|
||||
|
||||
async def test_unknown_error(hass, mock_garmin_connect):
|
||||
async def test_unknown_error(hass):
|
||||
"""Test for unknown error."""
|
||||
mock_garmin_connect.login.side_effect = Exception
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=MOCK_CONF
|
||||
)
|
||||
with patch(
|
||||
"homeassistant.components.garmin_connect.Garmin.login",
|
||||
side_effect=Exception,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": "user"}, data=MOCK_CONF
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {"base": "unknown"}
|
||||
|
||||
|
||||
async def test_abort_if_already_setup(hass):
|
||||
"""Test abort if already setup."""
|
||||
MockConfigEntry(
|
||||
domain=DOMAIN, data=MOCK_CONF, unique_id=MOCK_CONF[CONF_ID]
|
||||
).add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.garmin_connect.config_flow.Garmin", autospec=True
|
||||
) as garmin:
|
||||
garmin.return_value.login.return_value = MOCK_CONF[CONF_ID]
|
||||
|
||||
"homeassistant.components.garmin_connect.config_flow.Garmin",
|
||||
):
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN, data=MOCK_CONF, unique_id=MOCK_CONF[CONF_ID]
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": "user"}, data=MOCK_CONF
|
||||
)
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
@@ -136,6 +136,18 @@ async def test_notify_ws_works(
|
||||
sub_result = await client.receive_json()
|
||||
assert sub_result["success"]
|
||||
|
||||
# Subscribe twice, it should forward all messages to 2nd subscription
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 6,
|
||||
"type": "mobile_app/push_notification_channel",
|
||||
"webhook_id": "mock-webhook_id",
|
||||
}
|
||||
)
|
||||
|
||||
sub_result = await client.receive_json()
|
||||
assert sub_result["success"]
|
||||
|
||||
assert await hass.services.async_call(
|
||||
"notify", "mobile_app_test", {"message": "Hello world"}, blocking=True
|
||||
)
|
||||
@@ -144,13 +156,14 @@ async def test_notify_ws_works(
|
||||
|
||||
msg_result = await client.receive_json()
|
||||
assert msg_result["event"] == {"message": "Hello world"}
|
||||
assert msg_result["id"] == 6 # This is the new subscription
|
||||
|
||||
# Unsubscribe, now it should go over http
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 6,
|
||||
"id": 7,
|
||||
"type": "unsubscribe_events",
|
||||
"subscription": 5,
|
||||
"subscription": 6,
|
||||
}
|
||||
)
|
||||
sub_result = await client.receive_json()
|
||||
@@ -165,7 +178,7 @@ async def test_notify_ws_works(
|
||||
# Test non-existing webhook ID
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 7,
|
||||
"id": 8,
|
||||
"type": "mobile_app/push_notification_channel",
|
||||
"webhook_id": "non-existing",
|
||||
}
|
||||
@@ -180,7 +193,7 @@ async def test_notify_ws_works(
|
||||
# Test webhook ID linked to other user
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 8,
|
||||
"id": 9,
|
||||
"type": "mobile_app/push_notification_channel",
|
||||
"webhook_id": "webhook_id_2",
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ async def test_fire_event(hass, rfxtrx):
|
||||
"type_string": "Byron SX",
|
||||
"id_string": "00:90",
|
||||
"data": "0716000100900970",
|
||||
"values": {"Command": "Chime", "Rssi numeric": 7, "Sound": 9},
|
||||
"values": {"Command": "Sound 9", "Rssi numeric": 7, "Sound": 9},
|
||||
"device_id": device_id_2.id,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,38 @@ 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"
|
||||
|
||||
|
||||
async def test_device_payload_without_battery(
|
||||
hass, config_entry, config, soco, battery_event, caplog
|
||||
):
|
||||
"""Test device properties event update without battery info."""
|
||||
soco.get_battery_info.return_value = None
|
||||
|
||||
await setup_platform(hass, config_entry, config)
|
||||
|
||||
subscription = soco.deviceProperties.subscribe.return_value
|
||||
sub_callback = subscription.callback
|
||||
|
||||
bad_payload = "BadKey:BadValue"
|
||||
battery_event.variables["more_info"] = bad_payload
|
||||
|
||||
sub_callback(battery_event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert bad_payload in caplog.text
|
||||
|
||||
Reference in New Issue
Block a user