Climate preset, custom fan mode and custom preset (#42)

Co-authored-by: Otto Winter <otto@otto-winter.com>
Co-authored-by: Lumpusz <marton.keri@gmail.com>
This commit is contained in:
Otto Winter 2021-06-23 23:40:41 +02:00 committed by GitHub
parent 313aa2d56f
commit b1b36754ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 1042 additions and 663 deletions

View File

@ -672,11 +672,12 @@ message CameraImageRequest {
// ==================== CLIMATE ====================
enum ClimateMode {
CLIMATE_MODE_OFF = 0;
CLIMATE_MODE_AUTO = 1;
CLIMATE_MODE_HEAT_COOL = 1;
CLIMATE_MODE_COOL = 2;
CLIMATE_MODE_HEAT = 3;
CLIMATE_MODE_FAN_ONLY = 4;
CLIMATE_MODE_DRY = 5;
CLIMATE_MODE_AUTO = 6;
}
enum ClimateFanMode {
CLIMATE_FAN_ON = 0;
@ -704,6 +705,16 @@ enum ClimateAction {
CLIMATE_ACTION_DRYING = 5;
CLIMATE_ACTION_FAN = 6;
}
enum ClimatePreset {
CLIMATE_PRESET_NONE = 0;
CLIMATE_PRESET_HOME = 1;
CLIMATE_PRESET_AWAY = 2;
CLIMATE_PRESET_BOOST = 3;
CLIMATE_PRESET_COMFORT = 4;
CLIMATE_PRESET_ECO = 5;
CLIMATE_PRESET_SLEEP = 6;
CLIMATE_PRESET_ACTIVITY = 7;
}
message ListEntitiesClimateResponse {
option (id) = 46;
option (source) = SOURCE_SERVER;
@ -720,10 +731,15 @@ message ListEntitiesClimateResponse {
float visual_min_temperature = 8;
float visual_max_temperature = 9;
float visual_temperature_step = 10;
bool supports_away = 11;
// for older peer versions - in new system this
// is if CLIMATE_PRESET_AWAY exists is supported_presets
bool legacy_supports_away = 11;
bool supports_action = 12;
repeated ClimateFanMode supported_fan_modes = 13;
repeated ClimateSwingMode supported_swing_modes = 14;
repeated string supported_custom_fan_modes = 15;
repeated ClimatePreset supported_presets = 16;
repeated string supported_custom_presets = 17;
}
message ClimateStateResponse {
option (id) = 47;
@ -737,10 +753,14 @@ message ClimateStateResponse {
float target_temperature = 4;
float target_temperature_low = 5;
float target_temperature_high = 6;
bool away = 7;
// For older peers, equal to preset == CLIMATE_PRESET_AWAY
bool legacy_away = 7;
ClimateAction action = 8;
ClimateFanMode fan_mode = 9;
ClimateSwingMode swing_mode = 10;
string custom_fan_mode = 11;
ClimatePreset preset = 12;
string custom_preset = 13;
}
message ClimateCommandRequest {
option (id) = 48;
@ -757,10 +777,17 @@ message ClimateCommandRequest {
float target_temperature_low = 7;
bool has_target_temperature_high = 8;
float target_temperature_high = 9;
bool has_away = 10;
bool away = 11;
// legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset
bool has_legacy_away = 10;
bool legacy_away = 11;
bool has_fan_mode = 12;
ClimateFanMode fan_mode = 13;
bool has_swing_mode = 14;
ClimateSwingMode swing_mode = 15;
bool has_custom_fan_mode = 16;
string custom_fan_mode = 17;
bool has_preset = 18;
ClimatePreset preset = 19;
bool has_custom_preset = 20;
string custom_preset = 21;
}

View File

@ -1,9 +1,8 @@
# type: ignore
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: api_options.proto
import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf.internal import enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
@ -22,7 +21,8 @@ DESCRIPTOR = _descriptor.FileDescriptor(
package='',
syntax='proto2',
serialized_options=None,
serialized_pb=_b('\n\x11\x61pi_options.proto\x1a google/protobuf/descriptor.proto\"\x06\n\x04void*F\n\rAPISourceType\x12\x0f\n\x0bSOURCE_BOTH\x10\x00\x12\x11\n\rSOURCE_SERVER\x10\x01\x12\x11\n\rSOURCE_CLIENT\x10\x02:E\n\x16needs_setup_connection\x12\x1e.google.protobuf.MethodOptions\x18\x8e\x08 \x01(\x08:\x04true:C\n\x14needs_authentication\x12\x1e.google.protobuf.MethodOptions\x18\x8f\x08 \x01(\x08:\x04true:/\n\x02id\x12\x1f.google.protobuf.MessageOptions\x18\x8c\x08 \x01(\r:\x01\x30:M\n\x06source\x12\x1f.google.protobuf.MessageOptions\x18\x8d\x08 \x01(\x0e\x32\x0e.APISourceType:\x0bSOURCE_BOTH:/\n\x05ifdef\x12\x1f.google.protobuf.MessageOptions\x18\x8e\x08 \x01(\t:3\n\x03log\x12\x1f.google.protobuf.MessageOptions\x18\x8f\x08 \x01(\x08:\x04true:9\n\x08no_delay\x12\x1f.google.protobuf.MessageOptions\x18\x90\x08 \x01(\x08:\x05\x66\x61lse')
create_key=_descriptor._internal_create_key,
serialized_pb=b'\n\x11\x61pi_options.proto\x1a google/protobuf/descriptor.proto\"\x06\n\x04void*F\n\rAPISourceType\x12\x0f\n\x0bSOURCE_BOTH\x10\x00\x12\x11\n\rSOURCE_SERVER\x10\x01\x12\x11\n\rSOURCE_CLIENT\x10\x02:E\n\x16needs_setup_connection\x12\x1e.google.protobuf.MethodOptions\x18\x8e\x08 \x01(\x08:\x04true:C\n\x14needs_authentication\x12\x1e.google.protobuf.MethodOptions\x18\x8f\x08 \x01(\x08:\x04true:/\n\x02id\x12\x1f.google.protobuf.MessageOptions\x18\x8c\x08 \x01(\r:\x01\x30:M\n\x06source\x12\x1f.google.protobuf.MessageOptions\x18\x8d\x08 \x01(\x0e\x32\x0e.APISourceType:\x0bSOURCE_BOTH:/\n\x05ifdef\x12\x1f.google.protobuf.MessageOptions\x18\x8e\x08 \x01(\t:3\n\x03log\x12\x1f.google.protobuf.MessageOptions\x18\x8f\x08 \x01(\x08:\x04true:9\n\x08no_delay\x12\x1f.google.protobuf.MessageOptions\x18\x90\x08 \x01(\x08:\x05\x66\x61lse'
,
dependencies=[google_dot_protobuf_dot_descriptor__pb2.DESCRIPTOR,])
@ -31,19 +31,23 @@ _APISOURCETYPE = _descriptor.EnumDescriptor(
full_name='APISourceType',
filename=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key,
values=[
_descriptor.EnumValueDescriptor(
name='SOURCE_BOTH', index=0, number=0,
serialized_options=None,
type=None),
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='SOURCE_SERVER', index=1, number=1,
serialized_options=None,
type=None),
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='SOURCE_CLIENT', index=2, number=2,
serialized_options=None,
type=None),
type=None,
create_key=_descriptor._internal_create_key),
],
containing_type=None,
serialized_options=None,
@ -64,7 +68,7 @@ needs_setup_connection = _descriptor.FieldDescriptor(
has_default_value=True, default_value=True,
message_type=None, enum_type=None, containing_type=None,
is_extension=True, extension_scope=None,
serialized_options=None, file=DESCRIPTOR)
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key)
NEEDS_AUTHENTICATION_FIELD_NUMBER = 1039
needs_authentication = _descriptor.FieldDescriptor(
name='needs_authentication', full_name='needs_authentication', index=1,
@ -72,7 +76,7 @@ needs_authentication = _descriptor.FieldDescriptor(
has_default_value=True, default_value=True,
message_type=None, enum_type=None, containing_type=None,
is_extension=True, extension_scope=None,
serialized_options=None, file=DESCRIPTOR)
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key)
ID_FIELD_NUMBER = 1036
id = _descriptor.FieldDescriptor(
name='id', full_name='id', index=2,
@ -80,7 +84,7 @@ id = _descriptor.FieldDescriptor(
has_default_value=True, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=True, extension_scope=None,
serialized_options=None, file=DESCRIPTOR)
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key)
SOURCE_FIELD_NUMBER = 1037
source = _descriptor.FieldDescriptor(
name='source', full_name='source', index=3,
@ -88,15 +92,15 @@ source = _descriptor.FieldDescriptor(
has_default_value=True, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=True, extension_scope=None,
serialized_options=None, file=DESCRIPTOR)
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key)
IFDEF_FIELD_NUMBER = 1038
ifdef = _descriptor.FieldDescriptor(
name='ifdef', full_name='ifdef', index=4,
number=1038, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=True, extension_scope=None,
serialized_options=None, file=DESCRIPTOR)
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key)
LOG_FIELD_NUMBER = 1039
log = _descriptor.FieldDescriptor(
name='log', full_name='log', index=5,
@ -104,7 +108,7 @@ log = _descriptor.FieldDescriptor(
has_default_value=True, default_value=True,
message_type=None, enum_type=None, containing_type=None,
is_extension=True, extension_scope=None,
serialized_options=None, file=DESCRIPTOR)
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key)
NO_DELAY_FIELD_NUMBER = 1040
no_delay = _descriptor.FieldDescriptor(
name='no_delay', full_name='no_delay', index=6,
@ -112,7 +116,7 @@ no_delay = _descriptor.FieldDescriptor(
has_default_value=True, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=True, extension_scope=None,
serialized_options=None, file=DESCRIPTOR)
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key)
_VOID = _descriptor.Descriptor(
@ -121,6 +125,7 @@ _VOID = _descriptor.Descriptor(
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
],
extensions=[
@ -149,11 +154,11 @@ DESCRIPTOR.extensions_by_name['log'] = log
DESCRIPTOR.extensions_by_name['no_delay'] = no_delay
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
void = _reflection.GeneratedProtocolMessageType('void', (_message.Message,), dict(
DESCRIPTOR = _VOID,
__module__ = 'api_options_pb2'
void = _reflection.GeneratedProtocolMessageType('void', (_message.Message,), {
'DESCRIPTOR' : _VOID,
'__module__' : 'api_options_pb2'
# @@protoc_insertion_point(class_scope:void)
))
})
_sym_db.RegisterMessage(void)
google_dot_protobuf_dot_descriptor__pb2.MethodOptions.RegisterExtension(needs_setup_connection)

File diff suppressed because one or more lines are too long

View File

@ -59,6 +59,7 @@ from aioesphomeapi.model import (
ClimateFanMode,
ClimateInfo,
ClimateMode,
ClimatePreset,
ClimateState,
ClimateSwingMode,
CoverInfo,
@ -471,9 +472,11 @@ class APIClient:
target_temperature: Optional[float] = None,
target_temperature_low: Optional[float] = None,
target_temperature_high: Optional[float] = None,
away: Optional[bool] = None,
fan_mode: Optional[ClimateFanMode] = None,
swing_mode: Optional[ClimateSwingMode] = None,
custom_fan_mode: Optional[str] = None,
preset: Optional[ClimatePreset] = None,
custom_preset: Optional[str] = None,
) -> None:
self._check_authenticated()
@ -491,15 +494,26 @@ class APIClient:
if target_temperature_high is not None:
req.has_target_temperature_high = True
req.target_temperature_high = target_temperature_high
if away is not None:
req.has_away = True
req.away = away
if fan_mode is not None:
req.has_fan_mode = True
req.fan_mode = fan_mode
if swing_mode is not None:
req.has_swing_mode = True
req.swing_mode = swing_mode
if custom_fan_mode is not None:
req.has_custom_fan_mode = True
req.custom_fan_mode = custom_fan_mode
if preset is not None:
apiv = cast(APIVersion, self.api_version)
if apiv < APIVersion(1, 5):
req.has_legacy_away = True
req.legacy_away = preset == ClimatePreset.AWAY
else:
req.has_preset = True
req.preset = preset
if custom_preset is not None:
req.has_custom_preset = True
req.custom_preset = custom_preset
assert self._connection is not None
await self._connection.send_message(req)

View File

@ -251,11 +251,12 @@ class CameraState(EntityState):
# ==================== CLIMATE ====================
class ClimateMode(APIIntEnum):
OFF = 0
AUTO = 1
HEAT_COOL = 1
COOL = 2
HEAT = 3
FAN_ONLY = 4
DRY = 5
AUTO = 6
class ClimateFanMode(APIIntEnum):
@ -286,6 +287,17 @@ class ClimateAction(APIIntEnum):
FAN = 6
class ClimatePreset(APIIntEnum):
NONE = 0
HOME = 1
AWAY = 2
BOOST = 3
COMFORT = 4
ECO = 5
SLEEP = 6
ACTIVITY = 7
@attr.s
class ClimateInfo(EntityInfo):
supports_current_temperature = attr.ib(type=bool, default=False)
@ -298,7 +310,7 @@ class ClimateInfo(EntityInfo):
visual_min_temperature = attr.ib(type=float, default=0.0)
visual_max_temperature = attr.ib(type=float, default=0.0)
visual_temperature_step = attr.ib(type=float, default=0.0)
supports_away = attr.ib(type=bool, default=False)
legacy_supports_away = attr.ib(type=bool, default=False)
supports_action = attr.ib(type=bool, default=False)
supported_fan_modes = attr.ib(
type=List[ClimateFanMode],
@ -310,6 +322,20 @@ class ClimateInfo(EntityInfo):
converter=ClimateSwingMode.convert_list, # type: ignore
factory=list,
)
supported_custom_fan_modes = attr.ib(type=List[str], converter=list, factory=list)
supported_presets = attr.ib(
type=List[ClimatePreset], converter=ClimatePreset.convert_list, factory=list # type: ignore
)
supported_custom_presets = attr.ib(type=List[str], converter=list, factory=list)
def supported_presets_compat(self, api_version: APIVersion) -> List[ClimatePreset]:
if api_version < APIVersion(1, 5):
return (
[ClimatePreset.HOME, ClimatePreset.AWAY]
if self.legacy_supports_away
else []
)
return self.supported_presets
@attr.s
@ -328,7 +354,7 @@ class ClimateState(EntityState):
target_temperature = attr.ib(type=float, default=0.0)
target_temperature_low = attr.ib(type=float, default=0.0)
target_temperature_high = attr.ib(type=float, default=0.0)
away = attr.ib(type=bool, default=False)
legacy_away = attr.ib(type=bool, default=False)
fan_mode = attr.ib(
type=Optional[ClimateFanMode],
converter=ClimateFanMode.convert, # type: ignore
@ -339,6 +365,18 @@ class ClimateState(EntityState):
converter=ClimateSwingMode.convert, # type: ignore
default=ClimateSwingMode.OFF,
)
custom_fan_mode = attr.ib(type=str, default="")
preset = attr.ib(
type=Optional[ClimatePreset],
converter=ClimatePreset.convert, # type: ignore
default=ClimatePreset.HOME,
)
custom_preset = attr.ib(type=str, default="")
def preset_compat(self, api_version: APIVersion) -> Optional[ClimatePreset]:
if api_version < APIVersion(1, 5):
return ClimatePreset.AWAY if self.legacy_away else ClimatePreset.HOME
return self.preset
COMPONENT_TYPE_TO_INFO = {