Merge pull request #56366 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2021-09-18 08:29:25 -07:00 committed by GitHub
commit 9ffdc2594f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 309 additions and 80 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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(

View File

@ -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()

View File

@ -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"
}

View File

@ -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"]

View File

@ -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,

View File

@ -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,

View File

@ -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"
}

View File

@ -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."""

View File

@ -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"
],

View File

@ -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} "

View File

@ -2,3 +2,4 @@
DOMAIN = "sms"
SMS_GATEWAY = "SMS_GATEWAY"
SMS_STATE_UNREAD = "UnRead"

View File

@ -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:

View File

@ -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"
}

View File

@ -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
}

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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):

View File

@ -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"