Merge branch 'main' into feature/fan_presets

This commit is contained in:
J. Nick Koston 2023-11-30 14:56:17 -10:00
commit a1766c939c
No known key found for this signature in database
6 changed files with 240 additions and 177 deletions

View File

@ -846,6 +846,10 @@ message ListEntitiesClimateResponse {
string icon = 19;
EntityCategory entity_category = 20;
float visual_current_temperature_step = 21;
bool supports_current_humidity = 22;
bool supports_target_humidity = 23;
float visual_min_humidity = 24;
float visual_max_humidity = 25;
}
message ClimateStateResponse {
option (id) = 47;
@ -867,6 +871,8 @@ message ClimateStateResponse {
string custom_fan_mode = 11;
ClimatePreset preset = 12;
string custom_preset = 13;
float current_humidity = 14;
float target_humidity = 15;
}
message ClimateCommandRequest {
option (id) = 48;
@ -896,6 +902,8 @@ message ClimateCommandRequest {
ClimatePreset preset = 19;
bool has_custom_preset = 20;
string custom_preset = 21;
bool has_target_humidity = 22;
float target_humidity = 23;
}
// ==================== NUMBER ====================

File diff suppressed because one or more lines are too long

View File

@ -349,7 +349,7 @@ class APIClient:
"""Execute a coroutine and reset the _connection if it fails."""
try:
await coro
except Exception: # pylint: disable=broad-except
except (Exception, asyncio.CancelledError): # pylint: disable=broad-except
self._connection = None
raise
@ -1039,7 +1039,7 @@ class APIClient:
async def switch_command(self, key: int, state: bool) -> None:
self._get_connection().send_message(SwitchCommandRequest(key=key, state=state))
async def climate_command(
async def climate_command( # pylint: disable=too-many-branches
self,
key: int,
mode: ClimateMode | None = None,
@ -1051,6 +1051,7 @@ class APIClient:
custom_fan_mode: str | None = None,
preset: ClimatePreset | None = None,
custom_preset: str | None = None,
target_humidity: float | None = None,
) -> None:
req = ClimateCommandRequest(key=key)
if mode is not None:
@ -1087,6 +1088,9 @@ class APIClient:
if custom_preset is not None:
req.has_custom_preset = True
req.custom_preset = custom_preset
if target_humidity is not None:
req.has_target_humidity = True
req.target_humidity = target_humidity
self._get_connection().send_message(req)
async def number_command(self, key: int, state: float) -> None:

View File

@ -548,6 +548,10 @@ class ClimateInfo(EntityInfo):
supported_custom_presets: list[str] = converter_field(
default_factory=list, converter=list
)
supports_current_humidity: bool = False
supports_target_humidity: bool = False
visual_min_humidity: float = 0
visual_max_humidity: float = 0
def supported_presets_compat(self, api_version: APIVersion) -> list[ClimatePreset]:
if api_version < APIVersion(1, 5):
@ -591,6 +595,8 @@ class ClimateState(EntityState):
default=ClimatePreset.NONE, converter=ClimatePreset.convert
)
custom_preset: str = ""
current_humidity: float = 0
target_humidity: float = 0
def preset_compat(self, api_version: APIVersion) -> ClimatePreset | None:
if api_version < APIVersion(1, 5):

View File

@ -11,7 +11,7 @@ with open(os.path.join(here, "README.rst"), encoding="utf-8") as readme_file:
long_description = readme_file.read()
VERSION = "19.1.8"
VERSION = "19.2.2"
PROJECT_NAME = "aioesphomeapi"
PROJECT_PACKAGE_NAME = "aioesphomeapi"
PROJECT_LICENSE = "MIT"

View File

@ -1,6 +1,7 @@
from __future__ import annotations
import asyncio
import contextlib
import itertools
import logging
from functools import partial
@ -212,6 +213,37 @@ async def test_finish_connection_wraps_exceptions_as_unhandled_api_error() -> No
await cli.finish_connection(False)
@pytest.mark.asyncio
async def test_connection_released_if_connecting_is_cancelled() -> None:
"""Verify connection is unset if connecting is cancelled."""
cli = APIClient("1.2.3.4", 1234, None)
loop = asyncio.get_event_loop()
with patch.object(loop, "sock_connect", side_effect=partial(asyncio.sleep, 1)):
start_task = asyncio.create_task(cli.start_connection())
await asyncio.sleep(0)
assert cli._connection is not None
start_task.cancel()
with contextlib.suppress(BaseException):
await start_task
assert cli._connection is None
with patch(
"aioesphomeapi.client.APIConnection", PatchableAPIConnection
), patch.object(loop, "sock_connect"):
await cli.start_connection()
await asyncio.sleep(0)
assert cli._connection is not None
task = asyncio.create_task(cli.finish_connection(False))
await asyncio.sleep(0)
task.cancel()
with contextlib.suppress(BaseException):
await task
assert cli._connection is None
@pytest.mark.asyncio
async def test_request_while_handshaking(event_loop) -> None:
"""Test trying a request while handshaking raises."""
@ -528,6 +560,10 @@ async def test_climate_command_legacy(
dict(key=1, custom_preset="asdf"),
dict(key=1, has_custom_preset=True, custom_preset="asdf"),
),
(
dict(key=1, target_humidity=60.0),
dict(key=1, has_target_humidity=True, target_humidity=60.0),
),
],
)
async def test_climate_command(
@ -1825,6 +1861,15 @@ async def test_bluetooth_device_connect(
await asyncio.sleep(0)
assert states == [(True, 23, 0), (False, 23, 7)]
# Make sure cancel is safe to call again
cancel()
await client.disconnect(force=True)
await asyncio.sleep(0)
assert not client._connection
# Make sure cancel is safe to call after disconnect
cancel()
@pytest.mark.asyncio
async def test_bluetooth_device_connect_and_disconnect_times_out(