mirror of
https://github.com/esphome/aioesphomeapi.git
synced 2024-12-20 16:27:35 +01:00
Updated fork of PR for Text input components (#532)
Co-authored-by: Maurits <maurits@vloop.nl> Co-authored-by: Daniel Dunn <dannydunn@eternityforest.com> Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
ae03a831b9
commit
5a8c0d8e23
@ -40,6 +40,7 @@ service APIConnection {
|
||||
rpc climate_command (ClimateCommandRequest) returns (void) {}
|
||||
rpc number_command (NumberCommandRequest) returns (void) {}
|
||||
rpc select_command (SelectCommandRequest) returns (void) {}
|
||||
rpc text_command (TextCommandRequest) returns (void) {}
|
||||
rpc siren_command (SirenCommandRequest) returns (void) {}
|
||||
rpc button_command (ButtonCommandRequest) returns (void) {}
|
||||
rpc lock_command (LockCommandRequest) returns (void) {}
|
||||
@ -1555,3 +1556,48 @@ message AlarmControlPanelCommandRequest {
|
||||
AlarmControlPanelStateCommand command = 2;
|
||||
string code = 3;
|
||||
}
|
||||
|
||||
// ===================== TEXT =====================
|
||||
enum TextMode {
|
||||
TEXT_MODE_TEXT = 0;
|
||||
TEXT_MODE_PASSWORD = 1;
|
||||
}
|
||||
message ListEntitiesTextResponse {
|
||||
option (id) = 97;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_TEXT";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
string icon = 5;
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
|
||||
uint32 min_length = 8;
|
||||
uint32 max_length = 9;
|
||||
string pattern = 10;
|
||||
TextMode mode = 11;
|
||||
}
|
||||
message TextStateResponse {
|
||||
option (id) = 98;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_TEXT";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
string state = 2;
|
||||
// If the Text does not have a valid state yet.
|
||||
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
|
||||
bool missing_state = 3;
|
||||
}
|
||||
message TextCommandRequest {
|
||||
option (id) = 99;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_TEXT";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
string state = 2;
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -2,9 +2,8 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from collections.abc import Awaitable, Coroutine
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, Any, Callable, Union, cast
|
||||
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Coroutine, Union, cast
|
||||
|
||||
from google.protobuf import message
|
||||
|
||||
@ -69,6 +68,7 @@ from .api_pb2 import ( # type: ignore
|
||||
ListEntitiesServicesResponse,
|
||||
ListEntitiesSirenResponse,
|
||||
ListEntitiesSwitchResponse,
|
||||
ListEntitiesTextResponse,
|
||||
ListEntitiesTextSensorResponse,
|
||||
LockCommandRequest,
|
||||
LockStateResponse,
|
||||
@ -92,7 +92,9 @@ from .api_pb2 import ( # type: ignore
|
||||
SubscribeVoiceAssistantRequest,
|
||||
SwitchCommandRequest,
|
||||
SwitchStateResponse,
|
||||
TextCommandRequest,
|
||||
TextSensorStateResponse,
|
||||
TextStateResponse,
|
||||
UnsubscribeBluetoothLEAdvertisementsRequest,
|
||||
VoiceAssistantAudioSettings,
|
||||
VoiceAssistantEventData,
|
||||
@ -165,8 +167,10 @@ from .model import (
|
||||
SirenState,
|
||||
SwitchInfo,
|
||||
SwitchState,
|
||||
TextInfo,
|
||||
TextSensorInfo,
|
||||
TextSensorState,
|
||||
TextState,
|
||||
UserService,
|
||||
UserServiceArgType,
|
||||
VoiceAssistantCommand,
|
||||
@ -198,6 +202,7 @@ SUBSCRIBE_STATES_RESPONSE_TYPES: dict[Any, type[EntityState]] = {
|
||||
SensorStateResponse: SensorState,
|
||||
SirenStateResponse: SirenState,
|
||||
SwitchStateResponse: SwitchState,
|
||||
TextStateResponse: TextState,
|
||||
TextSensorStateResponse: TextSensorState,
|
||||
ClimateStateResponse: ClimateState,
|
||||
LockStateResponse: LockEntityState,
|
||||
@ -217,6 +222,7 @@ LIST_ENTITIES_SERVICES_RESPONSE_TYPES: dict[Any, type[EntityInfo] | None] = {
|
||||
ListEntitiesSensorResponse: SensorInfo,
|
||||
ListEntitiesSirenResponse: SirenInfo,
|
||||
ListEntitiesSwitchResponse: SwitchInfo,
|
||||
ListEntitiesTextResponse: TextInfo,
|
||||
ListEntitiesTextSensorResponse: TextSensorInfo,
|
||||
ListEntitiesServicesResponse: None,
|
||||
ListEntitiesCameraResponse: CameraInfo,
|
||||
@ -1338,6 +1344,15 @@ class APIClient:
|
||||
assert self._connection is not None
|
||||
self._connection.send_message(req)
|
||||
|
||||
async def text_command(self, key: int, state: str) -> None:
|
||||
self._check_authenticated()
|
||||
|
||||
req = TextCommandRequest()
|
||||
req.key = key
|
||||
req.state = state
|
||||
assert self._connection is not None
|
||||
self._connection.send_message(req)
|
||||
|
||||
async def execute_service(
|
||||
self, service: UserService, data: ExecuteServiceDataType
|
||||
) -> None:
|
||||
|
@ -71,6 +71,7 @@ from .api_pb2 import ( # type: ignore
|
||||
ListEntitiesServicesResponse,
|
||||
ListEntitiesSirenResponse,
|
||||
ListEntitiesSwitchResponse,
|
||||
ListEntitiesTextResponse,
|
||||
ListEntitiesTextSensorResponse,
|
||||
LockCommandRequest,
|
||||
LockStateResponse,
|
||||
@ -96,7 +97,9 @@ from .api_pb2 import ( # type: ignore
|
||||
SubscribeVoiceAssistantRequest,
|
||||
SwitchCommandRequest,
|
||||
SwitchStateResponse,
|
||||
TextCommandRequest,
|
||||
TextSensorStateResponse,
|
||||
TextStateResponse,
|
||||
UnsubscribeBluetoothLEAdvertisementsRequest,
|
||||
VoiceAssistantEventResponse,
|
||||
VoiceAssistantRequest,
|
||||
@ -340,4 +343,7 @@ MESSAGE_TYPE_TO_PROTO = {
|
||||
94: ListEntitiesAlarmControlPanelResponse,
|
||||
95: AlarmControlPanelStateResponse,
|
||||
96: AlarmControlPanelCommandRequest,
|
||||
97: ListEntitiesTextResponse,
|
||||
98: TextStateResponse,
|
||||
99: TextCommandRequest,
|
||||
}
|
||||
|
@ -2,10 +2,9 @@ from __future__ import annotations
|
||||
|
||||
import enum
|
||||
import sys
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
from functools import cache, lru_cache, partial
|
||||
from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast
|
||||
from typing import TYPE_CHECKING, Any, Callable, Iterable, Optional, TypeVar, cast
|
||||
from uuid import UUID
|
||||
|
||||
from .util import fix_float_single_double_conversion
|
||||
@ -757,6 +756,28 @@ class AlarmControlPanelEntityState(EntityState):
|
||||
)
|
||||
|
||||
|
||||
# ==================== TEXT ====================
|
||||
class TextMode(APIIntEnum):
|
||||
TEXT = 0
|
||||
PASSWORD = 1
|
||||
|
||||
|
||||
@_frozen_dataclass_decorator
|
||||
class TextInfo(EntityInfo):
|
||||
min_length: int = 0
|
||||
max_length: int = 255
|
||||
pattern: str = ""
|
||||
mode: Optional[TextMode] = converter_field(
|
||||
default=TextMode.TEXT, converter=TextMode.convert
|
||||
)
|
||||
|
||||
|
||||
@_frozen_dataclass_decorator
|
||||
class TextState(EntityState):
|
||||
state: str = ""
|
||||
missing_state: bool = False
|
||||
|
||||
|
||||
# ==================== INFO MAP ====================
|
||||
|
||||
COMPONENT_TYPE_TO_INFO: dict[str, type[EntityInfo]] = {
|
||||
@ -776,6 +797,7 @@ COMPONENT_TYPE_TO_INFO: dict[str, type[EntityInfo]] = {
|
||||
"lock": LockInfo,
|
||||
"media_player": MediaPlayerInfo,
|
||||
"alarm_control_panel": AlarmControlPanelInfo,
|
||||
"text": TextInfo,
|
||||
}
|
||||
|
||||
|
||||
@ -1126,6 +1148,7 @@ _TYPE_TO_NAME = {
|
||||
LockInfo: "lock",
|
||||
MediaPlayerInfo: "media_player",
|
||||
AlarmControlPanelInfo: "alarm_control_panel",
|
||||
TextInfo: "text_info",
|
||||
}
|
||||
|
||||
|
||||
|
@ -20,6 +20,7 @@ from aioesphomeapi.api_pb2 import (
|
||||
NumberCommandRequest,
|
||||
SelectCommandRequest,
|
||||
SwitchCommandRequest,
|
||||
TextCommandRequest,
|
||||
)
|
||||
from aioesphomeapi.client import APIClient
|
||||
from aioesphomeapi.model import (
|
||||
@ -559,3 +560,18 @@ async def test_alarm_panel_command(auth_client, cmd, req):
|
||||
|
||||
await auth_client.alarm_control_panel_command(**cmd)
|
||||
send.assert_called_once_with(AlarmControlPanelCommandRequest(**req))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"cmd, req",
|
||||
[
|
||||
(dict(key=1, state="hello world"), dict(key=1, state="hello world")),
|
||||
(dict(key=1, state="goodbye"), dict(key=1, state="goodbye")),
|
||||
],
|
||||
)
|
||||
async def test_text_command(auth_client, cmd, req):
|
||||
send = patch_send(auth_client)
|
||||
|
||||
await auth_client.text_command(**cmd)
|
||||
send.assert_called_once_with(TextCommandRequest(**req))
|
||||
|
@ -37,6 +37,7 @@ from aioesphomeapi.api_pb2 import (
|
||||
ServiceArgType,
|
||||
SwitchStateResponse,
|
||||
TextSensorStateResponse,
|
||||
TextStateResponse,
|
||||
)
|
||||
from aioesphomeapi.model import (
|
||||
_TYPE_TO_NAME,
|
||||
@ -74,8 +75,10 @@ from aioesphomeapi.model import (
|
||||
SirenInfo,
|
||||
SwitchInfo,
|
||||
SwitchState,
|
||||
TextInfo,
|
||||
TextSensorInfo,
|
||||
TextSensorState,
|
||||
TextState,
|
||||
UserService,
|
||||
UserServiceArg,
|
||||
UserServiceArgType,
|
||||
@ -236,6 +239,7 @@ def test_api_version_ord():
|
||||
(MediaPlayerEntityState, MediaPlayerStateResponse),
|
||||
(AlarmControlPanelInfo, ListEntitiesAlarmControlPanelResponse),
|
||||
(AlarmControlPanelEntityState, AlarmControlPanelStateResponse),
|
||||
(TextState, TextStateResponse),
|
||||
],
|
||||
)
|
||||
def test_basic_pb_conversions(model, pb):
|
||||
@ -346,6 +350,7 @@ def test_user_service_conversion():
|
||||
LockInfo,
|
||||
MediaPlayerInfo,
|
||||
AlarmControlPanelInfo,
|
||||
TextInfo,
|
||||
],
|
||||
)
|
||||
def test_build_unique_id(model):
|
||||
|
Loading…
Reference in New Issue
Block a user