mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 03:37:07 +00:00
Merge pull request #56366 from home-assistant/rc
This commit is contained in:
commit
9ffdc2594f
10
build.json
10
build.json
@ -2,11 +2,11 @@
|
||||
"image": "homeassistant/{arch}-homeassistant",
|
||||
"shadow_repository": "ghcr.io/home-assistant",
|
||||
"build_from": {
|
||||
"aarch64": "ghcr.io/home-assistant/aarch64-homeassistant-base:2021.08.0",
|
||||
"armhf": "ghcr.io/home-assistant/armhf-homeassistant-base:2021.08.0",
|
||||
"armv7": "ghcr.io/home-assistant/armv7-homeassistant-base:2021.08.0",
|
||||
"amd64": "ghcr.io/home-assistant/amd64-homeassistant-base:2021.08.0",
|
||||
"i386": "ghcr.io/home-assistant/i386-homeassistant-base:2021.08.0"
|
||||
"aarch64": "ghcr.io/home-assistant/aarch64-homeassistant-base:2021.09.0",
|
||||
"armhf": "ghcr.io/home-assistant/armhf-homeassistant-base:2021.09.0",
|
||||
"armv7": "ghcr.io/home-assistant/armv7-homeassistant-base:2021.09.0",
|
||||
"amd64": "ghcr.io/home-assistant/amd64-homeassistant-base:2021.09.0",
|
||||
"i386": "ghcr.io/home-assistant/i386-homeassistant-base:2021.09.0"
|
||||
},
|
||||
"labels": {
|
||||
"io.hass.type": "core",
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Google Cast",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/cast",
|
||||
"requirements": ["pychromecast==9.2.0"],
|
||||
"requirements": ["pychromecast==9.2.1"],
|
||||
"after_dependencies": [
|
||||
"cloud",
|
||||
"http",
|
||||
|
@ -224,6 +224,12 @@ class GenericThermostat(ClimateEntity, RestoreEntity):
|
||||
):
|
||||
self._async_update_temp(sensor_state)
|
||||
self.async_write_ha_state()
|
||||
switch_state = self.hass.states.get(self.heater_entity_id)
|
||||
if switch_state and switch_state.state not in (
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
):
|
||||
self.hass.create_task(self._check_switch_initial_state())
|
||||
|
||||
if self.hass.state == CoreState.running:
|
||||
_async_startup()
|
||||
@ -267,14 +273,6 @@ class GenericThermostat(ClimateEntity, RestoreEntity):
|
||||
if not self._hvac_mode:
|
||||
self._hvac_mode = HVAC_MODE_OFF
|
||||
|
||||
# Prevent the device from keep running if HVAC_MODE_OFF
|
||||
if self._hvac_mode == HVAC_MODE_OFF and self._is_device_active:
|
||||
await self._async_heater_turn_off()
|
||||
_LOGGER.warning(
|
||||
"The climate mode is OFF, but the switch device is ON. Turning off device %s",
|
||||
self.heater_entity_id,
|
||||
)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return the polling state."""
|
||||
@ -408,12 +406,24 @@ class GenericThermostat(ClimateEntity, RestoreEntity):
|
||||
await self._async_control_heating()
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def _check_switch_initial_state(self):
|
||||
"""Prevent the device from keep running if HVAC_MODE_OFF."""
|
||||
if self._hvac_mode == HVAC_MODE_OFF and self._is_device_active:
|
||||
_LOGGER.warning(
|
||||
"The climate mode is OFF, but the switch device is ON. Turning off device %s",
|
||||
self.heater_entity_id,
|
||||
)
|
||||
await self._async_heater_turn_off()
|
||||
|
||||
@callback
|
||||
def _async_switch_changed(self, event):
|
||||
"""Handle heater switch state changes."""
|
||||
new_state = event.data.get("new_state")
|
||||
old_state = event.data.get("old_state")
|
||||
if new_state is None:
|
||||
return
|
||||
if old_state is None:
|
||||
self.hass.create_task(self._check_switch_initial_state())
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
@ -433,7 +443,6 @@ class GenericThermostat(ClimateEntity, RestoreEntity):
|
||||
if not self._active and None not in (
|
||||
self._cur_temp,
|
||||
self._target_temp,
|
||||
self._is_device_active,
|
||||
):
|
||||
self._active = True
|
||||
_LOGGER.info(
|
||||
|
@ -47,7 +47,7 @@ class GrowattServerConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
if not login_response["success"] and login_response["errCode"] == "102":
|
||||
return self._async_show_user_form({"base": "invalid_auth"})
|
||||
self.user_id = login_response["userId"]
|
||||
self.user_id = login_response["user"]["id"]
|
||||
|
||||
self.data = user_input
|
||||
return await self.async_step_plant()
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Growatt",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/growatt_server/",
|
||||
"requirements": ["growattServer==1.0.1"],
|
||||
"requirements": ["growattServer==1.1.0"],
|
||||
"codeowners": ["@indykoning", "@muppet3000", "@JasperPlant"],
|
||||
"iot_class": "cloud_polling"
|
||||
}
|
||||
|
@ -861,7 +861,7 @@ def get_device_list(api, config):
|
||||
if not login_response["success"] and login_response["errCode"] == "102":
|
||||
_LOGGER.error("Username, Password or URL may be incorrect!")
|
||||
return
|
||||
user_id = login_response["userId"]
|
||||
user_id = login_response["user"]["id"]
|
||||
if plant_id == DEFAULT_PLANT_ID:
|
||||
plant_info = api.plant_list(user_id)
|
||||
plant_id = plant_info["data"][0]["plantId"]
|
||||
|
@ -245,7 +245,7 @@ class Thermostat(HomeAccessory):
|
||||
def _set_chars(self, char_values):
|
||||
_LOGGER.debug("Thermostat _set_chars: %s", char_values)
|
||||
events = []
|
||||
params = {}
|
||||
params = {ATTR_ENTITY_ID: self.entity_id}
|
||||
service = None
|
||||
state = self.hass.states.get(self.entity_id)
|
||||
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
@ -285,12 +285,20 @@ class Thermostat(HomeAccessory):
|
||||
target_hc = hc_fallback
|
||||
break
|
||||
|
||||
service = SERVICE_SET_HVAC_MODE_THERMOSTAT
|
||||
hass_value = self.hc_homekit_to_hass[target_hc]
|
||||
params = {ATTR_HVAC_MODE: hass_value}
|
||||
params[ATTR_HVAC_MODE] = self.hc_homekit_to_hass[target_hc]
|
||||
events.append(
|
||||
f"{CHAR_TARGET_HEATING_COOLING} to {char_values[CHAR_TARGET_HEATING_COOLING]}"
|
||||
)
|
||||
# Many integrations do not actually implement `hvac_mode` for the
|
||||
# `SERVICE_SET_TEMPERATURE_THERMOSTAT` service so we made a call to
|
||||
# `SERVICE_SET_HVAC_MODE_THERMOSTAT` before calling `SERVICE_SET_TEMPERATURE_THERMOSTAT`
|
||||
# to ensure the device is in the right mode before setting the temp.
|
||||
self.async_call_service(
|
||||
DOMAIN_CLIMATE,
|
||||
SERVICE_SET_HVAC_MODE_THERMOSTAT,
|
||||
params.copy(),
|
||||
", ".join(events),
|
||||
)
|
||||
|
||||
if CHAR_TARGET_TEMPERATURE in char_values:
|
||||
hc_target_temp = char_values[CHAR_TARGET_TEMPERATURE]
|
||||
@ -357,7 +365,6 @@ class Thermostat(HomeAccessory):
|
||||
)
|
||||
|
||||
if service:
|
||||
params[ATTR_ENTITY_ID] = self.entity_id
|
||||
self.async_call_service(
|
||||
DOMAIN_CLIMATE,
|
||||
service,
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "kodi",
|
||||
"name": "Kodi",
|
||||
"documentation": "https://www.home-assistant.io/integrations/kodi",
|
||||
"requirements": ["pykodi==0.2.5"],
|
||||
"requirements": ["pykodi==0.2.6"],
|
||||
"codeowners": ["@OnFreund", "@cgtobi"],
|
||||
"zeroconf": ["_xbmc-jsonrpc-h._tcp.local."],
|
||||
"config_flow": true,
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "OpenUV",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/openuv",
|
||||
"requirements": ["pyopenuv==2.2.0"],
|
||||
"requirements": ["pyopenuv==2.2.1"],
|
||||
"codeowners": ["@bachya"],
|
||||
"iot_class": "cloud_polling"
|
||||
}
|
||||
|
@ -213,9 +213,12 @@ class PhilipsTVMediaPlayer(CoordinatorEntity, MediaPlayerEntity):
|
||||
|
||||
async def async_turn_off(self):
|
||||
"""Turn off the device."""
|
||||
if self._state == STATE_ON:
|
||||
await self._tv.sendKey("Standby")
|
||||
self._state = STATE_OFF
|
||||
await self._async_update_soon()
|
||||
else:
|
||||
_LOGGER.debug("Ignoring turn off when already in expected state")
|
||||
|
||||
async def async_volume_up(self):
|
||||
"""Send volume up command."""
|
||||
|
@ -4,7 +4,7 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/plex",
|
||||
"requirements": [
|
||||
"plexapi==4.7.0",
|
||||
"plexapi==4.7.1",
|
||||
"plexauth==0.0.6",
|
||||
"plexwebsocket==0.0.13"
|
||||
],
|
||||
|
@ -189,7 +189,7 @@ class RainMachineEntity(CoordinatorEntity):
|
||||
self._attr_device_info = {
|
||||
"identifiers": {(DOMAIN, controller.mac)},
|
||||
"connections": {(dr.CONNECTION_NETWORK_MAC, controller.mac)},
|
||||
"name": controller.name,
|
||||
"name": str(controller.name),
|
||||
"manufacturer": "RainMachine",
|
||||
"model": (
|
||||
f"Version {controller.hardware_version} "
|
||||
|
@ -2,3 +2,4 @@
|
||||
|
||||
DOMAIN = "sms"
|
||||
SMS_GATEWAY = "SMS_GATEWAY"
|
||||
SMS_STATE_UNREAD = "UnRead"
|
||||
|
@ -6,7 +6,7 @@ from gammu.asyncworker import GammuAsyncWorker # pylint: disable=import-error
|
||||
|
||||
from homeassistant.core import callback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import DOMAIN, SMS_STATE_UNREAD
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -14,24 +14,39 @@ _LOGGER = logging.getLogger(__name__)
|
||||
class Gateway:
|
||||
"""SMS gateway to interact with a GSM modem."""
|
||||
|
||||
def __init__(self, worker, hass):
|
||||
def __init__(self, config, hass):
|
||||
"""Initialize the sms gateway."""
|
||||
self._worker = worker
|
||||
self._worker = GammuAsyncWorker(self.sms_pull)
|
||||
self._worker.configure(config)
|
||||
self._hass = hass
|
||||
self._first_pull = True
|
||||
|
||||
async def init_async(self):
|
||||
"""Initialize the sms gateway asynchronously."""
|
||||
await self._worker.init_async()
|
||||
try:
|
||||
await self._worker.set_incoming_sms_async()
|
||||
except gammu.ERR_NOTSUPPORTED:
|
||||
_LOGGER.warning("Your phone does not support incoming SMS notifications!")
|
||||
_LOGGER.warning("Falling back to pulling method for SMS notifications")
|
||||
except gammu.GSMError:
|
||||
_LOGGER.warning(
|
||||
"GSM error, your phone does not support incoming SMS notifications!"
|
||||
"GSM error, falling back to pulling method for SMS notifications"
|
||||
)
|
||||
else:
|
||||
await self._worker.set_incoming_callback_async(self.sms_callback)
|
||||
|
||||
def sms_pull(self, state_machine):
|
||||
"""Pull device.
|
||||
|
||||
@param state_machine: state machine
|
||||
@type state_machine: gammu.StateMachine
|
||||
"""
|
||||
state_machine.ReadDevice()
|
||||
|
||||
_LOGGER.debug("Pulling modem")
|
||||
self.sms_read_messages(state_machine, self._first_pull)
|
||||
self._first_pull = False
|
||||
|
||||
def sms_callback(self, state_machine, callback_type, callback_data):
|
||||
"""Receive notification about incoming event.
|
||||
|
||||
@ -45,7 +60,15 @@ class Gateway:
|
||||
_LOGGER.debug(
|
||||
"Received incoming event type:%s,data:%s", callback_type, callback_data
|
||||
)
|
||||
entries = self.get_and_delete_all_sms(state_machine)
|
||||
self.sms_read_messages(state_machine)
|
||||
|
||||
def sms_read_messages(self, state_machine, force=False):
|
||||
"""Read all received SMS messages.
|
||||
|
||||
@param state_machine: state machine which invoked action
|
||||
@type state_machine: gammu.StateMachine
|
||||
"""
|
||||
entries = self.get_and_delete_all_sms(state_machine, force)
|
||||
_LOGGER.debug("SMS entries:%s", entries)
|
||||
data = []
|
||||
|
||||
@ -53,13 +76,16 @@ class Gateway:
|
||||
decoded_entry = gammu.DecodeSMS(entry)
|
||||
message = entry[0]
|
||||
_LOGGER.debug("Processing sms:%s,decoded:%s", message, decoded_entry)
|
||||
sms_state = message["State"]
|
||||
_LOGGER.debug("SMS state:%s", sms_state)
|
||||
if sms_state == SMS_STATE_UNREAD:
|
||||
if decoded_entry is None:
|
||||
text = message["Text"]
|
||||
else:
|
||||
text = ""
|
||||
for inner_entry in decoded_entry["Entries"]:
|
||||
if inner_entry["Buffer"] is not None:
|
||||
text = text + inner_entry["Buffer"]
|
||||
text += inner_entry["Buffer"]
|
||||
|
||||
event_data = {
|
||||
"phone": message["Number"],
|
||||
@ -161,10 +187,7 @@ class Gateway:
|
||||
async def create_sms_gateway(config, hass):
|
||||
"""Create the sms gateway."""
|
||||
try:
|
||||
worker = GammuAsyncWorker()
|
||||
worker.configure(config)
|
||||
await worker.init_async()
|
||||
gateway = Gateway(worker, hass)
|
||||
gateway = Gateway(config, hass)
|
||||
await gateway.init_async()
|
||||
return gateway
|
||||
except gammu.GSMError as exc:
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "SMS notifications via GSM-modem",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/sms",
|
||||
"requirements": ["python-gammu==3.1"],
|
||||
"requirements": ["python-gammu==3.2.3"],
|
||||
"codeowners": ["@ocalvo"],
|
||||
"iot_class": "local_polling"
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Switcher",
|
||||
"documentation": "https://www.home-assistant.io/integrations/switcher_kis/",
|
||||
"codeowners": ["@tomerfi","@thecode"],
|
||||
"requirements": ["aioswitcher==2.0.5"],
|
||||
"requirements": ["aioswitcher==2.0.6"],
|
||||
"iot_class": "local_push",
|
||||
"config_flow": true
|
||||
}
|
||||
|
@ -171,12 +171,23 @@ async def async_create_miio_device_and_coordinator(
|
||||
|
||||
async def async_update_data():
|
||||
"""Fetch data from the device using async_add_executor_job."""
|
||||
try:
|
||||
|
||||
async def _async_fetch_data():
|
||||
"""Fetch data from the device."""
|
||||
async with async_timeout.timeout(10):
|
||||
state = await hass.async_add_executor_job(device.status)
|
||||
_LOGGER.debug("Got new state: %s", state)
|
||||
return state
|
||||
|
||||
try:
|
||||
return await _async_fetch_data()
|
||||
except DeviceException as ex:
|
||||
if getattr(ex, "code", None) != -9999:
|
||||
raise UpdateFailed(ex) from ex
|
||||
_LOGGER.info("Got exception while fetching the state, trying again: %s", ex)
|
||||
# Try to fetch the data a second time after error code -9999
|
||||
try:
|
||||
return await _async_fetch_data()
|
||||
except DeviceException as ex:
|
||||
raise UpdateFailed(ex) from ex
|
||||
|
||||
|
@ -70,8 +70,8 @@ ACTION_RECOVER = "recover"
|
||||
ACTION_STAY = "stay"
|
||||
ACTION_OFF = "off"
|
||||
|
||||
ACTIVE_MODE_NIGHTLIGHT = "1"
|
||||
ACTIVE_COLOR_FLOWING = "1"
|
||||
ACTIVE_MODE_NIGHTLIGHT = 1
|
||||
ACTIVE_COLOR_FLOWING = 1
|
||||
|
||||
NIGHTLIGHT_SWITCH_TYPE_LIGHT = "light"
|
||||
|
||||
@ -610,7 +610,7 @@ class YeelightDevice:
|
||||
# Only ceiling lights have active_mode, from SDK docs:
|
||||
# active_mode 0: daylight mode / 1: moonlight mode (ceiling light only)
|
||||
if self._active_mode is not None:
|
||||
return self._active_mode == ACTIVE_MODE_NIGHTLIGHT
|
||||
return int(self._active_mode) == ACTIVE_MODE_NIGHTLIGHT
|
||||
|
||||
if self._nightlight_brightness is not None:
|
||||
return int(self._nightlight_brightness) > 0
|
||||
@ -620,7 +620,7 @@ class YeelightDevice:
|
||||
@property
|
||||
def is_color_flow_enabled(self) -> bool:
|
||||
"""Return true / false if color flow is currently running."""
|
||||
return self._color_flow == ACTIVE_COLOR_FLOWING
|
||||
return int(self._color_flow) == ACTIVE_COLOR_FLOWING
|
||||
|
||||
@property
|
||||
def _active_mode(self):
|
||||
|
@ -5,7 +5,7 @@ from typing import Final
|
||||
|
||||
MAJOR_VERSION: Final = 2021
|
||||
MINOR_VERSION: Final = 9
|
||||
PATCH_VERSION: Final = "6"
|
||||
PATCH_VERSION: Final = "7"
|
||||
__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)
|
||||
|
@ -243,7 +243,7 @@ aiorecollect==1.0.8
|
||||
aioshelly==0.6.4
|
||||
|
||||
# homeassistant.components.switcher_kis
|
||||
aioswitcher==2.0.5
|
||||
aioswitcher==2.0.6
|
||||
|
||||
# homeassistant.components.syncthing
|
||||
aiosyncthing==0.5.1
|
||||
@ -739,7 +739,7 @@ greeneye_monitor==2.1
|
||||
greenwavereality==0.5.1
|
||||
|
||||
# homeassistant.components.growatt_server
|
||||
growattServer==1.0.1
|
||||
growattServer==1.1.0
|
||||
|
||||
# homeassistant.components.gstreamer
|
||||
gstreamer-player==1.1.2
|
||||
@ -1200,7 +1200,7 @@ pillow==8.2.0
|
||||
pizzapi==0.0.3
|
||||
|
||||
# homeassistant.components.plex
|
||||
plexapi==4.7.0
|
||||
plexapi==4.7.1
|
||||
|
||||
# homeassistant.components.plex
|
||||
plexauth==0.0.6
|
||||
@ -1373,7 +1373,7 @@ pycfdns==1.2.1
|
||||
pychannels==1.0.0
|
||||
|
||||
# homeassistant.components.cast
|
||||
pychromecast==9.2.0
|
||||
pychromecast==9.2.1
|
||||
|
||||
# homeassistant.components.pocketcasts
|
||||
pycketcasts==1.0.0
|
||||
@ -1551,7 +1551,7 @@ pykira==0.1.1
|
||||
pykmtronic==0.3.0
|
||||
|
||||
# homeassistant.components.kodi
|
||||
pykodi==0.2.5
|
||||
pykodi==0.2.6
|
||||
|
||||
# homeassistant.components.kraken
|
||||
pykrakenapi==0.1.8
|
||||
@ -1668,7 +1668,7 @@ pyobihai==1.3.1
|
||||
pyombi==0.1.10
|
||||
|
||||
# homeassistant.components.openuv
|
||||
pyopenuv==2.2.0
|
||||
pyopenuv==2.2.1
|
||||
|
||||
# homeassistant.components.opnsense
|
||||
pyopnsense==0.2.0
|
||||
@ -1856,7 +1856,7 @@ python-family-hub-local==0.0.2
|
||||
python-forecastio==1.4.0
|
||||
|
||||
# homeassistant.components.sms
|
||||
# python-gammu==3.1
|
||||
# python-gammu==3.2.3
|
||||
|
||||
# homeassistant.components.gc100
|
||||
python-gc100==1.0.3a0
|
||||
|
@ -164,7 +164,7 @@ aiorecollect==1.0.8
|
||||
aioshelly==0.6.4
|
||||
|
||||
# homeassistant.components.switcher_kis
|
||||
aioswitcher==2.0.5
|
||||
aioswitcher==2.0.6
|
||||
|
||||
# homeassistant.components.syncthing
|
||||
aiosyncthing==0.5.1
|
||||
@ -423,7 +423,7 @@ googlemaps==2.5.1
|
||||
greeclimate==0.11.8
|
||||
|
||||
# homeassistant.components.growatt_server
|
||||
growattServer==1.0.1
|
||||
growattServer==1.1.0
|
||||
|
||||
# homeassistant.components.profiler
|
||||
guppy3==3.1.0
|
||||
@ -678,7 +678,7 @@ pilight==0.1.1
|
||||
pillow==8.2.0
|
||||
|
||||
# homeassistant.components.plex
|
||||
plexapi==4.7.0
|
||||
plexapi==4.7.1
|
||||
|
||||
# homeassistant.components.plex
|
||||
plexauth==0.0.6
|
||||
@ -788,7 +788,7 @@ pybotvac==0.0.22
|
||||
pycfdns==1.2.1
|
||||
|
||||
# homeassistant.components.cast
|
||||
pychromecast==9.2.0
|
||||
pychromecast==9.2.1
|
||||
|
||||
# homeassistant.components.climacell
|
||||
pyclimacell==0.18.2
|
||||
@ -891,7 +891,7 @@ pykira==0.1.1
|
||||
pykmtronic==0.3.0
|
||||
|
||||
# homeassistant.components.kodi
|
||||
pykodi==0.2.5
|
||||
pykodi==0.2.6
|
||||
|
||||
# homeassistant.components.kraken
|
||||
pykrakenapi==0.1.8
|
||||
@ -966,7 +966,7 @@ pynx584==0.5
|
||||
pynzbgetapi==0.2.0
|
||||
|
||||
# homeassistant.components.openuv
|
||||
pyopenuv==2.2.0
|
||||
pyopenuv==2.2.1
|
||||
|
||||
# homeassistant.components.opnsense
|
||||
pyopnsense==0.2.0
|
||||
|
@ -42,6 +42,7 @@ from homeassistant.util.unit_system import METRIC_SYSTEM
|
||||
from tests.common import (
|
||||
assert_setup_component,
|
||||
async_fire_time_changed,
|
||||
async_mock_service,
|
||||
mock_restore_cache,
|
||||
)
|
||||
from tests.components.climate import common
|
||||
@ -1174,14 +1175,15 @@ async def test_custom_setup_params(hass):
|
||||
assert state.attributes.get("temperature") == TARGET_TEMP
|
||||
|
||||
|
||||
async def test_restore_state(hass):
|
||||
@pytest.mark.parametrize("hvac_mode", [HVAC_MODE_OFF, HVAC_MODE_HEAT, HVAC_MODE_COOL])
|
||||
async def test_restore_state(hass, hvac_mode):
|
||||
"""Ensure states are restored on startup."""
|
||||
mock_restore_cache(
|
||||
hass,
|
||||
(
|
||||
State(
|
||||
"climate.test_thermostat",
|
||||
HVAC_MODE_OFF,
|
||||
hvac_mode,
|
||||
{ATTR_TEMPERATURE: "20", ATTR_PRESET_MODE: PRESET_AWAY},
|
||||
),
|
||||
),
|
||||
@ -1206,7 +1208,7 @@ async def test_restore_state(hass):
|
||||
state = hass.states.get("climate.test_thermostat")
|
||||
assert state.attributes[ATTR_TEMPERATURE] == 20
|
||||
assert state.attributes[ATTR_PRESET_MODE] == PRESET_AWAY
|
||||
assert state.state == HVAC_MODE_OFF
|
||||
assert state.state == hvac_mode
|
||||
|
||||
|
||||
async def test_no_restore_state(hass):
|
||||
@ -1332,6 +1334,66 @@ async def test_restore_will_turn_off_(hass):
|
||||
assert hass.states.get(heater_switch).state == STATE_ON
|
||||
|
||||
|
||||
async def test_restore_will_turn_off_when_loaded_second(hass):
|
||||
"""Ensure that restored state is coherent with real situation.
|
||||
|
||||
Switch is not available until after component is loaded
|
||||
"""
|
||||
heater_switch = "input_boolean.test"
|
||||
mock_restore_cache(
|
||||
hass,
|
||||
(
|
||||
State(
|
||||
"climate.test_thermostat",
|
||||
HVAC_MODE_HEAT,
|
||||
{ATTR_TEMPERATURE: "18", ATTR_PRESET_MODE: PRESET_NONE},
|
||||
),
|
||||
State(heater_switch, STATE_ON, {}),
|
||||
),
|
||||
)
|
||||
|
||||
hass.state = CoreState.starting
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.get(heater_switch) is None
|
||||
|
||||
_setup_sensor(hass, 16)
|
||||
|
||||
await async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
{
|
||||
"climate": {
|
||||
"platform": "generic_thermostat",
|
||||
"name": "test_thermostat",
|
||||
"heater": heater_switch,
|
||||
"target_sensor": ENT_SENSOR,
|
||||
"target_temp": 20,
|
||||
"initial_hvac_mode": HVAC_MODE_OFF,
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("climate.test_thermostat")
|
||||
assert state.attributes[ATTR_TEMPERATURE] == 20
|
||||
assert state.state == HVAC_MODE_OFF
|
||||
|
||||
calls_on = async_mock_service(hass, ha.DOMAIN, SERVICE_TURN_ON)
|
||||
calls_off = async_mock_service(hass, ha.DOMAIN, SERVICE_TURN_OFF)
|
||||
|
||||
assert await async_setup_component(
|
||||
hass, input_boolean.DOMAIN, {"input_boolean": {"test": None}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
# heater must be switched off
|
||||
assert len(calls_on) == 0
|
||||
assert len(calls_off) == 1
|
||||
call = calls_off[0]
|
||||
assert call.domain == HASS_DOMAIN
|
||||
assert call.service == SERVICE_TURN_OFF
|
||||
assert call.data["entity_id"] == "input_boolean.test"
|
||||
|
||||
|
||||
async def test_restore_state_uncoherence_case(hass):
|
||||
"""
|
||||
Test restore from a strange state.
|
||||
|
@ -40,7 +40,7 @@ GROWATT_PLANT_LIST_RESPONSE = {
|
||||
},
|
||||
"success": True,
|
||||
}
|
||||
GROWATT_LOGIN_RESPONSE = {"userId": 123456, "userLevel": 1, "success": True}
|
||||
GROWATT_LOGIN_RESPONSE = {"user": {"id": 123456}, "userLevel": 1, "success": True}
|
||||
|
||||
|
||||
async def test_show_authenticate_form(hass):
|
||||
|
@ -560,6 +560,119 @@ async def test_thermostat_auto(hass, hk_driver, events):
|
||||
)
|
||||
|
||||
|
||||
async def test_thermostat_mode_and_temp_change(hass, hk_driver, events):
|
||||
"""Test if accessory where the mode and temp change in the same call."""
|
||||
entity_id = "climate.test"
|
||||
|
||||
# support_auto = True
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
HVAC_MODE_OFF,
|
||||
{
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE
|
||||
| SUPPORT_TARGET_TEMPERATURE_RANGE,
|
||||
ATTR_HVAC_MODES: [
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
HVAC_MODE_FAN_ONLY,
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_OFF,
|
||||
HVAC_MODE_AUTO,
|
||||
],
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
acc = Thermostat(hass, hk_driver, "Climate", entity_id, 1, None)
|
||||
hk_driver.add_accessory(acc)
|
||||
|
||||
await acc.run()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert acc.char_cooling_thresh_temp.value == 23.0
|
||||
assert acc.char_heating_thresh_temp.value == 19.0
|
||||
|
||||
assert acc.char_cooling_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP
|
||||
assert acc.char_cooling_thresh_temp.properties[PROP_MIN_VALUE] == 7.0
|
||||
assert acc.char_cooling_thresh_temp.properties[PROP_MIN_STEP] == 0.1
|
||||
assert acc.char_heating_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP
|
||||
assert acc.char_heating_thresh_temp.properties[PROP_MIN_VALUE] == 7.0
|
||||
assert acc.char_heating_thresh_temp.properties[PROP_MIN_STEP] == 0.1
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
HVAC_MODE_COOL,
|
||||
{
|
||||
ATTR_TARGET_TEMP_HIGH: 23.0,
|
||||
ATTR_TARGET_TEMP_LOW: 19.0,
|
||||
ATTR_CURRENT_TEMPERATURE: 21.0,
|
||||
ATTR_HVAC_ACTION: CURRENT_HVAC_COOL,
|
||||
ATTR_HVAC_MODES: [
|
||||
HVAC_MODE_HEAT,
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
HVAC_MODE_FAN_ONLY,
|
||||
HVAC_MODE_COOL,
|
||||
HVAC_MODE_OFF,
|
||||
HVAC_MODE_AUTO,
|
||||
],
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_heating_thresh_temp.value == 19.0
|
||||
assert acc.char_cooling_thresh_temp.value == 23.0
|
||||
assert acc.char_current_heat_cool.value == HC_HEAT_COOL_COOL
|
||||
assert acc.char_target_heat_cool.value == HC_HEAT_COOL_COOL
|
||||
assert acc.char_current_temp.value == 21.0
|
||||
assert acc.char_display_units.value == 0
|
||||
|
||||
# Set from HomeKit
|
||||
call_set_temperature = async_mock_service(hass, DOMAIN_CLIMATE, "set_temperature")
|
||||
call_set_hvac_mode = async_mock_service(hass, DOMAIN_CLIMATE, "set_hvac_mode")
|
||||
|
||||
char_heating_thresh_temp_iid = acc.char_heating_thresh_temp.to_HAP()[HAP_REPR_IID]
|
||||
char_cooling_thresh_temp_iid = acc.char_cooling_thresh_temp.to_HAP()[HAP_REPR_IID]
|
||||
char_target_heat_cool_iid = acc.char_target_heat_cool.to_HAP()[HAP_REPR_IID]
|
||||
|
||||
hk_driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_heating_thresh_temp_iid,
|
||||
HAP_REPR_VALUE: 20.0,
|
||||
},
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_cooling_thresh_temp_iid,
|
||||
HAP_REPR_VALUE: 25.0,
|
||||
},
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_target_heat_cool_iid,
|
||||
HAP_REPR_VALUE: HC_HEAT_COOL_AUTO,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
assert call_set_hvac_mode[0]
|
||||
assert call_set_hvac_mode[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_set_hvac_mode[0].data[ATTR_HVAC_MODE] == HVAC_MODE_HEAT_COOL
|
||||
assert call_set_temperature[0]
|
||||
assert call_set_temperature[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_set_temperature[0].data[ATTR_TARGET_TEMP_LOW] == 20.0
|
||||
assert call_set_temperature[0].data[ATTR_TARGET_TEMP_HIGH] == 25.0
|
||||
assert acc.char_heating_thresh_temp.value == 20.0
|
||||
assert acc.char_cooling_thresh_temp.value == 25.0
|
||||
assert len(events) == 2
|
||||
assert events[-2].data[ATTR_VALUE] == "TargetHeatingCoolingState to 3"
|
||||
assert (
|
||||
events[-1].data[ATTR_VALUE]
|
||||
== "TargetHeatingCoolingState to 3, CoolingThresholdTemperature to 25.0°C, HeatingThresholdTemperature to 20.0°C"
|
||||
)
|
||||
|
||||
|
||||
async def test_thermostat_humidity(hass, hk_driver, events):
|
||||
"""Test if accessory and HA are updated accordingly with humidity."""
|
||||
entity_id = "climate.test"
|
||||
|
Loading…
x
Reference in New Issue
Block a user