mirror of
https://github.com/esphome/aioesphomeapi.git
synced 2024-11-25 12:35:19 +01:00
Add climate feature fan (#4)
* f1 * renamed to fan_mode * fixes, add back compat with ha * revert client_version - add swing
This commit is contained in:
parent
9be72930e6
commit
511cb62dd9
@ -653,6 +653,25 @@ enum ClimateMode {
|
||||
CLIMATE_MODE_AUTO = 1;
|
||||
CLIMATE_MODE_COOL = 2;
|
||||
CLIMATE_MODE_HEAT = 3;
|
||||
CLIMATE_MODE_FAN_ONLY = 4;
|
||||
CLIMATE_MODE_DRY = 5;
|
||||
}
|
||||
enum ClimateFanMode {
|
||||
CLIMATE_FAN_ON = 0;
|
||||
CLIMATE_FAN_OFF = 1;
|
||||
CLIMATE_FAN_AUTO = 2;
|
||||
CLIMATE_FAN_LOW = 3;
|
||||
CLIMATE_FAN_MEDIUM = 4;
|
||||
CLIMATE_FAN_HIGH = 5;
|
||||
CLIMATE_FAN_MIDDLE = 6;
|
||||
CLIMATE_FAN_FOCUS = 7;
|
||||
CLIMATE_FAN_DIFFUSE = 8;
|
||||
}
|
||||
enum ClimateSwingMode {
|
||||
CLIMATE_SWING_OFF = 0;
|
||||
CLIMATE_SWING_BOTH = 1;
|
||||
CLIMATE_SWING_VERTICAL = 2;
|
||||
CLIMATE_SWINT_HORIZONTAL = 3;
|
||||
}
|
||||
enum ClimateAction {
|
||||
CLIMATE_ACTION_OFF = 0;
|
||||
@ -678,6 +697,8 @@ message ListEntitiesClimateResponse {
|
||||
float visual_temperature_step = 10;
|
||||
bool supports_away = 11;
|
||||
bool supports_action = 12;
|
||||
repeated ClimateFanMode supported_fan_modes = 13;
|
||||
repeated ClimateSwingMode supported_swing_modes = 14;
|
||||
}
|
||||
message ClimateStateResponse {
|
||||
option (id) = 47;
|
||||
@ -693,6 +714,8 @@ message ClimateStateResponse {
|
||||
float target_temperature_high = 6;
|
||||
bool away = 7;
|
||||
ClimateAction action = 8;
|
||||
ClimateFanMode fan_mode = 9;
|
||||
ClimateSwingMode swing_mode = 10;
|
||||
}
|
||||
message ClimateCommandRequest {
|
||||
option (id) = 48;
|
||||
@ -711,4 +734,8 @@ message ClimateCommandRequest {
|
||||
float target_temperature_high = 9;
|
||||
bool has_away = 10;
|
||||
bool away = 11;
|
||||
bool has_fan_mode = 12;
|
||||
ClimateFanMode fan_mode = 13;
|
||||
bool has_swing_mode = 14;
|
||||
ClimateSwingMode swing_mode = 15;
|
||||
}
|
||||
|
@ -1,30 +1,27 @@
|
||||
# -*- 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
|
||||
from google.protobuf import reflection as _reflection
|
||||
from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf import reflection as _reflection
|
||||
from google.protobuf import message as _message
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf.internal import enum_type_wrapper
|
||||
import sys
|
||||
_b = sys.version_info[0] < 3 and (
|
||||
lambda x: x) or (lambda x: x.encode('latin1'))
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor.FileDescriptor(
|
||||
name='api_options.proto',
|
||||
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')
|
||||
,
|
||||
dependencies=[google_dot_protobuf_dot_descriptor__pb2.DESCRIPTOR,])
|
||||
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, ])
|
||||
|
||||
_APISOURCETYPE = _descriptor.EnumDescriptor(
|
||||
name='APISourceType',
|
||||
@ -149,20 +146,24 @@ DESCRIPTOR.extensions_by_name['log'] = log
|
||||
DESCRIPTOR.extensions_by_name['no_delay'] = no_delay
|
||||
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
|
||||
|
||||
void = _reflection.GeneratedProtocolMessageType('void', (_message.Message,), {
|
||||
'DESCRIPTOR' : _VOID,
|
||||
'__module__' : 'api_options_pb2'
|
||||
void = _reflection.GeneratedProtocolMessageType('void', (_message.Message,), dict(
|
||||
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)
|
||||
google_dot_protobuf_dot_descriptor__pb2.MethodOptions.RegisterExtension(needs_authentication)
|
||||
google_dot_protobuf_dot_descriptor__pb2.MethodOptions.RegisterExtension(
|
||||
needs_setup_connection)
|
||||
google_dot_protobuf_dot_descriptor__pb2.MethodOptions.RegisterExtension(
|
||||
needs_authentication)
|
||||
google_dot_protobuf_dot_descriptor__pb2.MessageOptions.RegisterExtension(id)
|
||||
source.enum_type = _APISOURCETYPE
|
||||
google_dot_protobuf_dot_descriptor__pb2.MessageOptions.RegisterExtension(source)
|
||||
google_dot_protobuf_dot_descriptor__pb2.MessageOptions.RegisterExtension(
|
||||
source)
|
||||
google_dot_protobuf_dot_descriptor__pb2.MessageOptions.RegisterExtension(ifdef)
|
||||
google_dot_protobuf_dot_descriptor__pb2.MessageOptions.RegisterExtension(log)
|
||||
google_dot_protobuf_dot_descriptor__pb2.MessageOptions.RegisterExtension(no_delay)
|
||||
google_dot_protobuf_dot_descriptor__pb2.MessageOptions.RegisterExtension(
|
||||
no_delay)
|
||||
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
File diff suppressed because one or more lines are too long
@ -50,7 +50,8 @@ class APIClient:
|
||||
raise
|
||||
except Exception as e:
|
||||
await _on_stop()
|
||||
raise APIConnectionError("Unexpected error while connecting: {}".format(e))
|
||||
raise APIConnectionError(
|
||||
"Unexpected error while connecting: {}".format(e))
|
||||
|
||||
connected = True
|
||||
|
||||
@ -326,6 +327,8 @@ class APIClient:
|
||||
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,
|
||||
) -> None:
|
||||
self._check_authenticated()
|
||||
|
||||
@ -346,6 +349,12 @@ class APIClient:
|
||||
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
|
||||
await self._connection.send_message(req)
|
||||
|
||||
async def execute_service(self, service: UserService, data: dict):
|
||||
@ -357,7 +366,8 @@ class APIClient:
|
||||
for arg_desc in service.args:
|
||||
arg = pb.ExecuteServiceArgument()
|
||||
val = data[arg_desc.name]
|
||||
int_type = 'int_' if self.api_version >= APIVersion(1, 3) else 'legacy_int'
|
||||
int_type = 'int_' if self.api_version >= APIVersion(
|
||||
1, 3) else 'legacy_int'
|
||||
map_single = {
|
||||
UserServiceArgType.BOOL: 'bool_',
|
||||
UserServiceArgType.INT: int_type,
|
||||
|
@ -120,10 +120,12 @@ class APIConnection:
|
||||
await asyncio.wait_for(coro, 30.0)
|
||||
except OSError as err:
|
||||
await self._on_error()
|
||||
raise APIConnectionError("Error connecting to {}: {}".format(sockaddr, err))
|
||||
raise APIConnectionError(
|
||||
"Error connecting to {}: {}".format(sockaddr, err))
|
||||
except asyncio.TimeoutError:
|
||||
await self._on_error()
|
||||
raise APIConnectionError("Timeout while connecting to {}".format(sockaddr))
|
||||
raise APIConnectionError(
|
||||
"Timeout while connecting to {}".format(sockaddr))
|
||||
|
||||
_LOGGER.debug("%s: Opened socket for", self._params.address)
|
||||
self._socket_reader, self._socket_writer = await asyncio.open_connection(sock=self._socket)
|
||||
@ -140,7 +142,8 @@ class APIConnection:
|
||||
_LOGGER.debug("%s: Successfully connected ('%s' API=%s.%s)",
|
||||
self._params.address, resp.server_info, resp.api_version_major,
|
||||
resp.api_version_minor)
|
||||
self._api_version = APIVersion(resp.api_version_major, resp.api_version_minor)
|
||||
self._api_version = APIVersion(
|
||||
resp.api_version_major, resp.api_version_minor)
|
||||
if self._api_version.major > 2:
|
||||
_LOGGER.error("%s: Incompatible version %s! Closing connection",
|
||||
self._api_version.major)
|
||||
@ -187,7 +190,8 @@ class APIConnection:
|
||||
await self._socket_writer.drain()
|
||||
except OSError as err:
|
||||
await self._on_error()
|
||||
raise APIConnectionError("Error while writing data: {}".format(err))
|
||||
raise APIConnectionError(
|
||||
"Error while writing data: {}".format(err))
|
||||
|
||||
async def send_message(self, msg: message.Message) -> None:
|
||||
for message_type, klass in MESSAGE_TYPE_TO_PROTO.items():
|
||||
@ -197,7 +201,8 @@ class APIConnection:
|
||||
raise ValueError
|
||||
|
||||
encoded = msg.SerializeToString()
|
||||
_LOGGER.debug("%s: Sending %s: %s", self._params.address, type(msg), str(msg))
|
||||
_LOGGER.debug("%s: Sending %s: %s",
|
||||
self._params.address, type(msg), str(msg))
|
||||
req = bytes([0])
|
||||
req += _varuint_to_bytes(len(encoded))
|
||||
req += _varuint_to_bytes(message_type)
|
||||
@ -231,7 +236,8 @@ class APIConnection:
|
||||
await asyncio.wait_for(fut, timeout)
|
||||
except asyncio.TimeoutError:
|
||||
if self._stopped:
|
||||
raise APIConnectionError("Disconnected while waiting for API response!")
|
||||
raise APIConnectionError(
|
||||
"Disconnected while waiting for API response!")
|
||||
raise APIConnectionError("Timeout while waiting for API response!")
|
||||
|
||||
try:
|
||||
@ -250,7 +256,8 @@ class APIConnection:
|
||||
res = await self.send_message_await_response_complex(
|
||||
send_msg, is_response, is_response, timeout=timeout)
|
||||
if len(res) != 1:
|
||||
raise APIConnectionError("Expected one result, got {}".format(len(res)))
|
||||
raise APIConnectionError(
|
||||
"Expected one result, got {}".format(len(res)))
|
||||
|
||||
return res[0]
|
||||
|
||||
@ -261,7 +268,8 @@ class APIConnection:
|
||||
try:
|
||||
ret = await self._socket_reader.readexactly(amount)
|
||||
except (asyncio.IncompleteReadError, OSError, TimeoutError) as err:
|
||||
raise APIConnectionError("Error while receiving data: {}".format(err))
|
||||
raise APIConnectionError(
|
||||
"Error while receiving data: {}".format(err))
|
||||
|
||||
return ret
|
||||
|
||||
@ -281,7 +289,8 @@ class APIConnection:
|
||||
|
||||
raw_msg = await self._recv(length)
|
||||
if msg_type not in MESSAGE_TYPE_TO_PROTO:
|
||||
_LOGGER.debug("%s: Skipping message type %s", self._params.address, msg_type)
|
||||
_LOGGER.debug("%s: Skipping message type %s",
|
||||
self._params.address, msg_type)
|
||||
return
|
||||
|
||||
msg = MESSAGE_TYPE_TO_PROTO[msg_type]()
|
||||
@ -289,7 +298,8 @@ class APIConnection:
|
||||
msg.ParseFromString(raw_msg)
|
||||
except Exception as e:
|
||||
raise APIConnectionError("Invalid protobuf message: {}".format(e))
|
||||
_LOGGER.debug("%s: Got message of type %s: %s", self._params.address, type(msg), msg)
|
||||
_LOGGER.debug("%s: Got message of type %s: %s",
|
||||
self._params.address, type(msg), msg)
|
||||
for msg_handler in self._message_handlers[:]:
|
||||
msg_handler(msg)
|
||||
await self._handle_internal_messages(msg)
|
||||
|
@ -194,6 +194,27 @@ class ClimateMode(enum.IntEnum):
|
||||
AUTO = 1
|
||||
COOL = 2
|
||||
HEAT = 3
|
||||
FAN_ONLY = 4
|
||||
DRY = 5
|
||||
|
||||
|
||||
class ClimateFanMode(enum.IntEnum):
|
||||
ON = 0
|
||||
OFF = 1
|
||||
AUTO = 2
|
||||
LOW = 3
|
||||
MEDIUM = 4
|
||||
HIGH = 5
|
||||
MIDDLE = 6
|
||||
FOCUS = 7
|
||||
DIFFUSE = 8
|
||||
|
||||
|
||||
class ClimateSwingMode(enum.IntEnum):
|
||||
OFF = 0
|
||||
BOTH = 1
|
||||
VERTICAL = 2
|
||||
HORIZONTAL = 3
|
||||
|
||||
|
||||
class ClimateAction(enum.IntEnum):
|
||||
@ -206,6 +227,14 @@ def _convert_climate_modes(value):
|
||||
return [ClimateMode(val) for val in value]
|
||||
|
||||
|
||||
def _convert_climate_fan_modes(value):
|
||||
return [ClimateFanMode(val) for val in value]
|
||||
|
||||
|
||||
def _convert_climate_swing_modes(value):
|
||||
return [ClimateSwingMode(val) for val in value]
|
||||
|
||||
|
||||
@attr.s
|
||||
class ClimateInfo(EntityInfo):
|
||||
supports_current_temperature = attr.ib(type=bool, default=False)
|
||||
@ -217,17 +246,31 @@ class ClimateInfo(EntityInfo):
|
||||
visual_temperature_step = attr.ib(type=float, default=0.0)
|
||||
supports_away = attr.ib(type=bool, default=False)
|
||||
supports_action = attr.ib(type=bool, default=False)
|
||||
supported_fan_modes = attr.ib(
|
||||
type=List[ClimateFanMode], converter=_convert_climate_fan_modes, factory=list
|
||||
)
|
||||
supported_swing_modes = attr.ib(
|
||||
type=List[ClimateSwingMode], converter=_convert_climate_swing_modes, factory=list
|
||||
)
|
||||
|
||||
|
||||
@attr.s
|
||||
class ClimateState(EntityState):
|
||||
mode = attr.ib(type=ClimateMode, converter=ClimateMode, default=ClimateMode.OFF)
|
||||
action = attr.ib(type=ClimateAction, converter=ClimateAction, default=ClimateAction.OFF)
|
||||
mode = attr.ib(type=ClimateMode, converter=ClimateMode,
|
||||
default=ClimateMode.OFF)
|
||||
action = attr.ib(type=ClimateAction, converter=ClimateAction,
|
||||
default=ClimateAction.OFF)
|
||||
current_temperature = attr.ib(type=float, default=0.0)
|
||||
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)
|
||||
fan_mode = attr.ib(
|
||||
type=ClimateFanMode, converter=ClimateFanMode, default=ClimateFanMode.AUTO
|
||||
)
|
||||
swing_mode = attr.ib(
|
||||
type=ClimateSwingMode, converter=ClimateSwingMode, default=ClimateSwingMode.OFF
|
||||
)
|
||||
|
||||
|
||||
COMPONENT_TYPE_TO_INFO = {
|
||||
|
@ -4,4 +4,4 @@
|
||||
protoc --python_out=aioesphomeapi -I aioesphomeapi aioesphomeapi/*.proto
|
||||
|
||||
# https://github.com/protocolbuffers/protobuf/issues/1491
|
||||
sed -i '' 's/import api_options_pb2 as api__options__pb2/from . import api_options_pb2 as api__options__pb2/' aioesphomeapi/api_pb2.py
|
||||
sed -i 's/import api_options_pb2 as api__options__pb2/from . import api_options_pb2 as api__options__pb2/' aioesphomeapi/api_pb2.py
|
||||
|
Loading…
Reference in New Issue
Block a user