Add media player entities (#214)

This commit is contained in:
Jesse Hills 2022-05-18 13:28:40 +12:00 committed by GitHub
parent 616e5c6621
commit fcd529bb32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 557 additions and 40 deletions

View File

@ -43,6 +43,7 @@ service APIConnection {
rpc siren_command (SirenCommandRequest) returns (void) {}
rpc button_command (ButtonCommandRequest) returns (void) {}
rpc lock_command (LockCommandRequest) returns (void) {}
rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
}
@ -1018,7 +1019,7 @@ message ListEntitiesLockResponse {
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;
@ -1066,3 +1067,63 @@ message ButtonCommandRequest {
fixed32 key = 1;
}
// ==================== MEDIA PLAYER ====================
enum MediaPlayerState {
MEDIA_PLAYER_STATE_NONE = 0;
MEDIA_PLAYER_STATE_IDLE = 1;
MEDIA_PLAYER_STATE_PLAYING = 2;
MEDIA_PLAYER_STATE_PAUSED = 3;
}
enum MediaPlayerCommand {
MEDIA_PLAYER_COMMAND_PLAY = 0;
MEDIA_PLAYER_COMMAND_PAUSE = 1;
MEDIA_PLAYER_COMMAND_STOP = 2;
MEDIA_PLAYER_COMMAND_MUTE = 3;
MEDIA_PLAYER_COMMAND_UNMUTE = 4;
}
message ListEntitiesMediaPlayerResponse {
option (id) = 63;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_MEDIA_PLAYER";
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 supports_pause = 8;
bool supports_volume = 9;
bool supports_mute = 10;
}
message MediaPlayerStateResponse {
option (id) = 64;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_MEDIA_PLAYER";
option (no_delay) = true;
fixed32 key = 1;
MediaPlayerState state = 2;
float volume = 3;
bool muted = 4;
}
message MediaPlayerCommandRequest {
option (id) = 65;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_MEDIA_PLAYER";
option (no_delay) = true;
fixed32 key = 1;
bool has_command = 2;
MediaPlayerCommand command = 3;
bool has_volume = 4;
float volume = 5;
bool has_media_url = 6;
string media_url = 7;
}

File diff suppressed because one or more lines are too long

View File

@ -42,6 +42,7 @@ from .api_pb2 import ( # type: ignore
ListEntitiesFanResponse,
ListEntitiesLightResponse,
ListEntitiesLockResponse,
ListEntitiesMediaPlayerResponse,
ListEntitiesNumberResponse,
ListEntitiesRequest,
ListEntitiesSelectResponse,
@ -52,6 +53,8 @@ from .api_pb2 import ( # type: ignore
ListEntitiesTextSensorResponse,
LockCommandRequest,
LockStateResponse,
MediaPlayerCommandRequest,
MediaPlayerStateResponse,
NumberCommandRequest,
NumberStateResponse,
SelectCommandRequest,
@ -102,6 +105,9 @@ from .model import (
LockEntityState,
LockInfo,
LogLevel,
MediaPlayerCommand,
MediaPlayerEntityState,
MediaPlayerInfo,
NumberInfo,
NumberState,
SelectInfo,
@ -265,6 +271,7 @@ class APIClient:
ListEntitiesCameraResponse: CameraInfo,
ListEntitiesClimateResponse: ClimateInfo,
ListEntitiesLockResponse: LockInfo,
ListEntitiesMediaPlayerResponse: MediaPlayerInfo,
}
def do_append(msg: message.Message) -> bool:
@ -309,6 +316,7 @@ class APIClient:
TextSensorStateResponse: TextSensorState,
ClimateStateResponse: ClimateState,
LockStateResponse: LockEntityState,
MediaPlayerStateResponse: MediaPlayerEntityState,
}
image_stream: Dict[int, bytes] = {}
@ -655,6 +663,30 @@ class APIClient:
assert self._connection is not None
await self._connection.send_message(req)
async def media_player_command(
self,
key: int,
*,
command: Optional[MediaPlayerCommand] = None,
volume: Optional[float] = None,
media_url: Optional[str] = None,
) -> None:
self._check_authenticated()
req = MediaPlayerCommandRequest()
req.key = key
if command is not None:
req.command = command
req.has_command = True
if volume is not None:
req.volume = volume
req.has_volume = True
if media_url is not None:
req.media_url = media_url
req.has_media_url = True
assert self._connection is not None
await self._connection.send_message(req)
async def execute_service(
self, service: UserService, data: ExecuteServiceDataType
) -> None:

View File

@ -33,6 +33,7 @@ from .api_pb2 import ( # type: ignore
ListEntitiesFanResponse,
ListEntitiesLightResponse,
ListEntitiesLockResponse,
ListEntitiesMediaPlayerResponse,
ListEntitiesNumberResponse,
ListEntitiesRequest,
ListEntitiesSelectResponse,
@ -43,6 +44,8 @@ from .api_pb2 import ( # type: ignore
ListEntitiesTextSensorResponse,
LockCommandRequest,
LockStateResponse,
MediaPlayerCommandRequest,
MediaPlayerStateResponse,
NumberCommandRequest,
NumberStateResponse,
PingRequest,
@ -183,4 +186,7 @@ MESSAGE_TYPE_TO_PROTO = {
60: LockCommandRequest,
61: ListEntitiesButtonResponse,
62: ButtonCommandRequest,
63: ListEntitiesMediaPlayerResponse,
64: MediaPlayerStateResponse,
65: MediaPlayerCommandRequest,
}

View File

@ -634,6 +634,40 @@ class LockEntityState(EntityState):
)
# ==================== MEDIA PLAYER ====================
class MediaPlayerState(APIIntEnum):
NONE = 0
IDLE = 1
PLAYING = 2
PAUSED = 3
class MediaPlayerCommand(APIIntEnum):
PLAY = 0
PAUSE = 1
STOP = 2
MUTE = 3
UNMUTE = 4
@dataclass(frozen=True)
class MediaPlayerInfo(EntityInfo):
supports_pause: bool = False
supports_volume: bool = False
supports_mute: bool = False
@dataclass(frozen=True)
class MediaPlayerEntityState(EntityState):
state: Optional[MediaPlayerState] = converter_field(
default=MediaPlayerState.NONE, converter=MediaPlayerState.convert
)
volume: float = converter_field(
default=0.0, converter=fix_float_single_double_conversion
)
muted: bool = False
# ==================== INFO MAP ====================
COMPONENT_TYPE_TO_INFO: Dict[str, Type[EntityInfo]] = {
@ -651,6 +685,7 @@ COMPONENT_TYPE_TO_INFO: Dict[str, Type[EntityInfo]] = {
"siren": SirenInfo,
"button": ButtonInfo,
"lock": LockInfo,
"media_player": MediaPlayerInfo,
}

View File

@ -17,6 +17,7 @@ from aioesphomeapi.api_pb2 import (
ListEntitiesDoneResponse,
ListEntitiesServicesResponse,
LockCommandRequest,
MediaPlayerCommandRequest,
NumberCommandRequest,
SelectCommandRequest,
SwitchCommandRequest,
@ -35,6 +36,7 @@ from aioesphomeapi.model import (
FanSpeed,
LegacyCoverCommand,
LockCommand,
MediaPlayerCommand,
UserService,
UserServiceArg,
UserServiceArgType,
@ -392,6 +394,31 @@ async def test_select_command(auth_client, cmd, req):
send.assert_called_once_with(SelectCommandRequest(**req))
@pytest.mark.asyncio
@pytest.mark.parametrize(
"cmd, req",
[
(
dict(key=1, command=MediaPlayerCommand.MUTE),
dict(key=1, has_command=True, command=MediaPlayerCommand.MUTE),
),
(
dict(key=1, volume=1.0),
dict(key=1, has_volume=True, volume=1.0),
),
(
dict(key=1, media_url="http://example.com"),
dict(key=1, has_media_url=True, media_url="http://example.com"),
),
],
)
async def test_media_player_command(auth_client, cmd, req):
send = patch_send(auth_client)
await auth_client.media_player_command(**cmd)
send.assert_called_once_with(MediaPlayerCommandRequest(**req))
@pytest.mark.asyncio
async def test_execute_service(auth_client):
send = patch_send(auth_client)

View File

@ -19,6 +19,7 @@ from aioesphomeapi.api_pb2 import (
ListEntitiesFanResponse,
ListEntitiesLightResponse,
ListEntitiesLockResponse,
ListEntitiesMediaPlayerResponse,
ListEntitiesNumberResponse,
ListEntitiesSelectResponse,
ListEntitiesSensorResponse,
@ -27,6 +28,7 @@ from aioesphomeapi.api_pb2 import (
ListEntitiesSwitchResponse,
ListEntitiesTextSensorResponse,
LockStateResponse,
MediaPlayerStateResponse,
NumberStateResponse,
SelectStateResponse,
SensorStateResponse,
@ -55,6 +57,8 @@ from aioesphomeapi.model import (
LightState,
LockEntityState,
LockInfo,
MediaPlayerEntityState,
MediaPlayerInfo,
NumberInfo,
NumberState,
SelectInfo,
@ -220,6 +224,8 @@ def test_api_version_ord():
(ButtonInfo, ListEntitiesButtonResponse),
(LockInfo, ListEntitiesLockResponse),
(LockEntityState, LockStateResponse),
(MediaPlayerInfo, ListEntitiesMediaPlayerResponse),
(MediaPlayerEntityState, MediaPlayerStateResponse),
],
)
def test_basic_pb_conversions(model, pb):