Add lock entity (#158)

This commit is contained in:
Keilin Bickar 2022-01-10 20:29:19 -05:00 committed by GitHub
parent 014d81350a
commit d0d1e526f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 504 additions and 39 deletions

View File

@ -42,6 +42,7 @@ service APIConnection {
rpc select_command (SelectCommandRequest) returns (void) {} rpc select_command (SelectCommandRequest) returns (void) {}
rpc siren_command (SirenCommandRequest) returns (void) {} rpc siren_command (SirenCommandRequest) returns (void) {}
rpc button_command (ButtonCommandRequest) returns (void) {} rpc button_command (ButtonCommandRequest) returns (void) {}
rpc lock_command (LockCommandRequest) returns (void) {}
} }
@ -986,6 +987,58 @@ message SirenCommandRequest {
float volume = 9; float volume = 9;
} }
// ==================== LOCK ====================
enum LockState {
LOCK_STATE_NONE = 0;
LOCK_STATE_LOCKED = 1;
LOCK_STATE_UNLOCKED = 2;
LOCK_STATE_JAMMED = 3;
LOCK_STATE_LOCKING = 4;
LOCK_STATE_UNLOCKING = 5;
}
enum LockCommand {
LOCK_UNLOCK = 0;
LOCK_LOCK = 1;
LOCK_OPEN = 2;
}
message ListEntitiesLockResponse {
option (id) = 58;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_LOCK";
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;
bool assumed_state = 8;
bool supports_open = 9;
bool requires_code = 10;
string code_format = 11;
}
message LockStateResponse {
option (id) = 59;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_LOCK";
option (no_delay) = true;
fixed32 key = 1;
LockState state = 2;
}
message LockCommandRequest {
option (id) = 60;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_LOCK";
option (no_delay) = true;
fixed32 key = 1;
LockCommand command = 2;
bool has_code = 3;
string code = 4;
}
// ==================== BUTTON ==================== // ==================== BUTTON ====================
message ListEntitiesButtonResponse { message ListEntitiesButtonResponse {
option (id) = 61; option (id) = 61;

File diff suppressed because one or more lines are too long

View File

@ -41,6 +41,7 @@ from .api_pb2 import ( # type: ignore
ListEntitiesDoneResponse, ListEntitiesDoneResponse,
ListEntitiesFanResponse, ListEntitiesFanResponse,
ListEntitiesLightResponse, ListEntitiesLightResponse,
ListEntitiesLockResponse,
ListEntitiesNumberResponse, ListEntitiesNumberResponse,
ListEntitiesRequest, ListEntitiesRequest,
ListEntitiesSelectResponse, ListEntitiesSelectResponse,
@ -49,6 +50,8 @@ from .api_pb2 import ( # type: ignore
ListEntitiesSirenResponse, ListEntitiesSirenResponse,
ListEntitiesSwitchResponse, ListEntitiesSwitchResponse,
ListEntitiesTextSensorResponse, ListEntitiesTextSensorResponse,
LockCommandRequest,
LockStateResponse,
NumberCommandRequest, NumberCommandRequest,
NumberStateResponse, NumberStateResponse,
SelectCommandRequest, SelectCommandRequest,
@ -95,6 +98,9 @@ from .model import (
LegacyCoverCommand, LegacyCoverCommand,
LightInfo, LightInfo,
LightState, LightState,
LockCommand,
LockEntityState,
LockInfo,
LogLevel, LogLevel,
NumberInfo, NumberInfo,
NumberState, NumberState,
@ -233,6 +239,7 @@ class APIClient:
ListEntitiesServicesResponse: None, ListEntitiesServicesResponse: None,
ListEntitiesCameraResponse: CameraInfo, ListEntitiesCameraResponse: CameraInfo,
ListEntitiesClimateResponse: ClimateInfo, ListEntitiesClimateResponse: ClimateInfo,
ListEntitiesLockResponse: LockInfo,
} }
def do_append(msg: message.Message) -> bool: def do_append(msg: message.Message) -> bool:
@ -276,6 +283,7 @@ class APIClient:
SwitchStateResponse: SwitchState, SwitchStateResponse: SwitchState,
TextSensorStateResponse: TextSensorState, TextSensorStateResponse: TextSensorState,
ClimateStateResponse: ClimateState, ClimateStateResponse: ClimateState,
LockStateResponse: LockEntityState,
} }
image_stream: Dict[int, bytes] = {} image_stream: Dict[int, bytes] = {}
@ -606,6 +614,22 @@ class APIClient:
assert self._connection is not None assert self._connection is not None
await self._connection.send_message(req) await self._connection.send_message(req)
async def lock_command(
self,
key: int,
command: LockCommand,
code: Optional[str] = None,
) -> None:
self._check_authenticated()
req = LockCommandRequest()
req.key = key
req.command = command
if code is not None:
req.code = code
assert self._connection is not None
await self._connection.send_message(req)
async def execute_service( async def execute_service(
self, service: UserService, data: ExecuteServiceDataType self, service: UserService, data: ExecuteServiceDataType
) -> None: ) -> None:

View File

@ -32,6 +32,7 @@ from .api_pb2 import ( # type: ignore
ListEntitiesDoneResponse, ListEntitiesDoneResponse,
ListEntitiesFanResponse, ListEntitiesFanResponse,
ListEntitiesLightResponse, ListEntitiesLightResponse,
ListEntitiesLockResponse,
ListEntitiesNumberResponse, ListEntitiesNumberResponse,
ListEntitiesRequest, ListEntitiesRequest,
ListEntitiesSelectResponse, ListEntitiesSelectResponse,
@ -40,6 +41,8 @@ from .api_pb2 import ( # type: ignore
ListEntitiesSirenResponse, ListEntitiesSirenResponse,
ListEntitiesSwitchResponse, ListEntitiesSwitchResponse,
ListEntitiesTextSensorResponse, ListEntitiesTextSensorResponse,
LockCommandRequest,
LockStateResponse,
NumberCommandRequest, NumberCommandRequest,
NumberStateResponse, NumberStateResponse,
PingRequest, PingRequest,
@ -167,6 +170,9 @@ MESSAGE_TYPE_TO_PROTO = {
55: ListEntitiesSirenResponse, 55: ListEntitiesSirenResponse,
56: SirenStateResponse, 56: SirenStateResponse,
57: SirenCommandRequest, 57: SirenCommandRequest,
58: ListEntitiesLockResponse,
59: LockStateResponse,
60: LockCommandRequest,
61: ListEntitiesButtonResponse, 61: ListEntitiesButtonResponse,
62: ButtonCommandRequest, 62: ButtonCommandRequest,
} }

View File

@ -602,6 +602,38 @@ class ButtonInfo(EntityInfo):
device_class: str = "" device_class: str = ""
# ==================== LOCK ====================
class LockState(APIIntEnum):
NONE = 0
LOCKED = 1
UNLOCKED = 3
JAMMED = 3
LOCKING = 4
UNLOCKING = 5
class LockCommand(APIIntEnum):
UNLOCK = 0
LOCK = 1
OPEN = 2
@dataclass(frozen=True)
class LockInfo(EntityInfo):
supports_open: bool = False
assumed_state: bool = False
requires_code: bool = False
code_format: str = ""
@dataclass(frozen=True)
class LockEntityState(EntityState):
state: Optional[LockState] = converter_field(
default=LockState.NONE, converter=LockState.convert
)
# ==================== INFO MAP ==================== # ==================== INFO MAP ====================
COMPONENT_TYPE_TO_INFO: Dict[str, Type[EntityInfo]] = { COMPONENT_TYPE_TO_INFO: Dict[str, Type[EntityInfo]] = {
@ -618,6 +650,7 @@ COMPONENT_TYPE_TO_INFO: Dict[str, Type[EntityInfo]] = {
"select": SelectInfo, "select": SelectInfo,
"siren": SirenInfo, "siren": SirenInfo,
"button": ButtonInfo, "button": ButtonInfo,
"lock": LockInfo,
} }

View File

@ -16,6 +16,7 @@ from aioesphomeapi.api_pb2 import (
ListEntitiesBinarySensorResponse, ListEntitiesBinarySensorResponse,
ListEntitiesDoneResponse, ListEntitiesDoneResponse,
ListEntitiesServicesResponse, ListEntitiesServicesResponse,
LockCommandRequest,
NumberCommandRequest, NumberCommandRequest,
SelectCommandRequest, SelectCommandRequest,
SwitchCommandRequest, SwitchCommandRequest,
@ -33,6 +34,7 @@ from aioesphomeapi.model import (
FanDirection, FanDirection,
FanSpeed, FanSpeed,
LegacyCoverCommand, LegacyCoverCommand,
LockCommand,
UserService, UserService,
UserServiceArg, UserServiceArg,
UserServiceArgType, UserServiceArgType,
@ -356,6 +358,25 @@ async def test_number_command(auth_client, cmd, req):
send.assert_called_once_with(NumberCommandRequest(**req)) send.assert_called_once_with(NumberCommandRequest(**req))
@pytest.mark.asyncio
@pytest.mark.parametrize(
"cmd, req",
[
(dict(key=1, command=LockCommand.LOCK), dict(key=1, command=LockCommand.LOCK)),
(
dict(key=1, command=LockCommand.UNLOCK),
dict(key=1, command=LockCommand.UNLOCK),
),
(dict(key=1, command=LockCommand.OPEN), dict(key=1, command=LockCommand.OPEN)),
],
)
async def test_lock_command(auth_client, cmd, req):
send = patch_send(auth_client)
await auth_client.lock_command(**cmd)
send.assert_called_once_with(LockCommandRequest(**req))
@pytest.mark.asyncio @pytest.mark.asyncio
@pytest.mark.parametrize( @pytest.mark.parametrize(
"cmd, req", "cmd, req",

View File

@ -18,6 +18,7 @@ from aioesphomeapi.api_pb2 import (
ListEntitiesCoverResponse, ListEntitiesCoverResponse,
ListEntitiesFanResponse, ListEntitiesFanResponse,
ListEntitiesLightResponse, ListEntitiesLightResponse,
ListEntitiesLockResponse,
ListEntitiesNumberResponse, ListEntitiesNumberResponse,
ListEntitiesSelectResponse, ListEntitiesSelectResponse,
ListEntitiesSensorResponse, ListEntitiesSensorResponse,
@ -25,6 +26,7 @@ from aioesphomeapi.api_pb2 import (
ListEntitiesServicesResponse, ListEntitiesServicesResponse,
ListEntitiesSwitchResponse, ListEntitiesSwitchResponse,
ListEntitiesTextSensorResponse, ListEntitiesTextSensorResponse,
LockStateResponse,
NumberStateResponse, NumberStateResponse,
SelectStateResponse, SelectStateResponse,
SensorStateResponse, SensorStateResponse,
@ -51,6 +53,8 @@ from aioesphomeapi.model import (
LegacyCoverState, LegacyCoverState,
LightInfo, LightInfo,
LightState, LightState,
LockEntityState,
LockInfo,
NumberInfo, NumberInfo,
NumberState, NumberState,
SelectInfo, SelectInfo,
@ -220,6 +224,8 @@ def test_api_version_ord():
(UserServiceArg, ListEntitiesServicesArgument), (UserServiceArg, ListEntitiesServicesArgument),
(UserService, ListEntitiesServicesResponse), (UserService, ListEntitiesServicesResponse),
(ButtonInfo, ListEntitiesButtonResponse), (ButtonInfo, ListEntitiesButtonResponse),
(LockInfo, ListEntitiesLockResponse),
(LockEntityState, LockStateResponse),
], ],
) )
def test_basic_pb_conversions(model, pb): def test_basic_pb_conversions(model, pb):