mirror of
https://github.com/esphome/aioesphomeapi.git
synced 2025-02-04 23:52:38 +01:00
Add initial voice assistant support (#412)
This commit is contained in:
parent
48792681f2
commit
15846c5896
@ -55,6 +55,8 @@ service APIConnection {
|
||||
rpc bluetooth_gatt_notify(BluetoothGATTNotifyRequest) returns (void) {}
|
||||
rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
|
||||
|
||||
rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -210,6 +212,8 @@ message DeviceInfoResponse {
|
||||
string manufacturer = 12;
|
||||
|
||||
string friendly_name = 13;
|
||||
|
||||
uint32 voice_assistant_version = 14;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
@ -1403,3 +1407,55 @@ message BluetoothDeviceClearCacheResponse {
|
||||
bool success = 2;
|
||||
int32 error = 3;
|
||||
}
|
||||
|
||||
// ==================== VOICE ASSISTANT ====================
|
||||
message SubscribeVoiceAssistantRequest {
|
||||
option (id) = 89;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_VOICE_ASSISTANT";
|
||||
|
||||
bool subscribe = 1;
|
||||
}
|
||||
|
||||
message VoiceAssistantRequest {
|
||||
option (id) = 90;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_VOICE_ASSISTANT";
|
||||
|
||||
bool start = 1;
|
||||
}
|
||||
|
||||
message VoiceAssistantResponse {
|
||||
option (id) = 91;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_VOICE_ASSISTANT";
|
||||
|
||||
uint32 port = 1;
|
||||
bool error = 2;
|
||||
}
|
||||
|
||||
enum VoiceAssistantEvent {
|
||||
VOICE_ASSISTANT_ERROR = 0;
|
||||
VOICE_ASSISTANT_RUN_START = 1;
|
||||
VOICE_ASSISTANT_RUN_END = 2;
|
||||
VOICE_ASSISTANT_STT_START = 3;
|
||||
VOICE_ASSISTANT_STT_END = 4;
|
||||
VOICE_ASSISTANT_INTENT_START = 5;
|
||||
VOICE_ASSISTANT_INTENT_END = 6;
|
||||
VOICE_ASSISTANT_TTS_START = 7;
|
||||
VOICE_ASSISTANT_TTS_END = 8;
|
||||
}
|
||||
|
||||
message VoiceAssistantEventData {
|
||||
string name = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message VoiceAssistantEventResponse {
|
||||
option (id) = 92;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_VOICE_ASSISTANT";
|
||||
|
||||
VoiceAssistantEvent event_type = 1;
|
||||
repeated VoiceAssistantEventData data = 2;
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -93,10 +93,15 @@ from .api_pb2 import ( # type: ignore
|
||||
SubscribeLogsRequest,
|
||||
SubscribeLogsResponse,
|
||||
SubscribeStatesRequest,
|
||||
SubscribeVoiceAssistantRequest,
|
||||
SwitchCommandRequest,
|
||||
SwitchStateResponse,
|
||||
TextSensorStateResponse,
|
||||
UnsubscribeBluetoothLEAdvertisementsRequest,
|
||||
VoiceAssistantEventData,
|
||||
VoiceAssistantEventResponse,
|
||||
VoiceAssistantRequest,
|
||||
VoiceAssistantResponse,
|
||||
)
|
||||
from .connection import APIConnection, ConnectionParams
|
||||
from .core import (
|
||||
@ -164,6 +169,8 @@ from .model import (
|
||||
TextSensorState,
|
||||
UserService,
|
||||
UserServiceArgType,
|
||||
VoiceAssistantCommand,
|
||||
VoiceAssistantEventType,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -1238,3 +1245,81 @@ class APIClient:
|
||||
if self._connection is None:
|
||||
return None
|
||||
return self._connection.api_version
|
||||
|
||||
async def subscribe_voice_assistant(
|
||||
self,
|
||||
handle_start: Callable[[], Coroutine[Any, Any, Optional[int]]],
|
||||
handle_stop: Callable[[], Coroutine[Any, Any, None]],
|
||||
) -> Callable[[], None]:
|
||||
"""Subscribes to voice assistant messages from the device.
|
||||
|
||||
handle_start: called when the devices requests a server to send audio data to.
|
||||
This callback is asyncronous and returns the port number the server is started on.
|
||||
|
||||
handle_stop: called when the device has stopped sending audio data and the pipeline should be closed.
|
||||
|
||||
Returns a callback to unsubscribe.
|
||||
"""
|
||||
self._check_authenticated()
|
||||
|
||||
t: Optional[asyncio.Task[Optional[int]]] = None
|
||||
|
||||
def _started(fut: asyncio.Task[Optional[int]]) -> None:
|
||||
if self._connection is not None and not fut.cancelled():
|
||||
port = fut.result()
|
||||
if port is not None:
|
||||
self._connection.send_message(VoiceAssistantResponse(port=port))
|
||||
else:
|
||||
_LOGGER.error("Server could not be started")
|
||||
self._connection.send_message(VoiceAssistantResponse(error=True))
|
||||
|
||||
def on_msg(msg: VoiceAssistantRequest) -> None:
|
||||
command = VoiceAssistantCommand.from_pb(msg)
|
||||
loop = asyncio.get_running_loop()
|
||||
if command.start:
|
||||
t = loop.create_task(handle_start())
|
||||
t.add_done_callback(_started)
|
||||
else:
|
||||
loop.create_task(handle_stop())
|
||||
|
||||
assert self._connection is not None
|
||||
|
||||
self._connection.send_message(SubscribeVoiceAssistantRequest(subscribe=True))
|
||||
|
||||
remove_callback = self._connection.add_message_callback(
|
||||
on_msg, (VoiceAssistantRequest,)
|
||||
)
|
||||
|
||||
def unsub() -> None:
|
||||
if self._connection is not None:
|
||||
remove_callback()
|
||||
self._connection.send_message(
|
||||
SubscribeVoiceAssistantRequest(subscribe=False)
|
||||
)
|
||||
|
||||
if t is not None and not t.cancelled():
|
||||
t.cancel()
|
||||
|
||||
return unsub
|
||||
|
||||
def send_voice_assistant_event(
|
||||
self, event_type: VoiceAssistantEventType, data: Optional[dict[str, str]]
|
||||
) -> None:
|
||||
self._check_authenticated()
|
||||
|
||||
req = VoiceAssistantEventResponse()
|
||||
req.event_type = event_type
|
||||
|
||||
data_args = []
|
||||
if data is not None:
|
||||
for name, value in data.items():
|
||||
arg = VoiceAssistantEventData()
|
||||
arg.name = name
|
||||
arg.value = value
|
||||
data_args.append(arg)
|
||||
|
||||
# pylint: disable=no-member
|
||||
req.data.extend(data_args)
|
||||
|
||||
assert self._connection is not None
|
||||
self._connection.send_message(req)
|
||||
|
@ -87,10 +87,14 @@ from .api_pb2 import ( # type: ignore
|
||||
SubscribeLogsRequest,
|
||||
SubscribeLogsResponse,
|
||||
SubscribeStatesRequest,
|
||||
SubscribeVoiceAssistantRequest,
|
||||
SwitchCommandRequest,
|
||||
SwitchStateResponse,
|
||||
TextSensorStateResponse,
|
||||
UnsubscribeBluetoothLEAdvertisementsRequest,
|
||||
VoiceAssistantEventResponse,
|
||||
VoiceAssistantRequest,
|
||||
VoiceAssistantResponse,
|
||||
)
|
||||
|
||||
TWO_CHAR = re.compile(r".{2}")
|
||||
@ -310,4 +314,8 @@ MESSAGE_TYPE_TO_PROTO = {
|
||||
86: BluetoothDeviceUnpairingResponse,
|
||||
87: UnsubscribeBluetoothLEAdvertisementsRequest,
|
||||
88: BluetoothDeviceClearCacheResponse,
|
||||
89: SubscribeVoiceAssistantRequest,
|
||||
90: VoiceAssistantRequest,
|
||||
91: VoiceAssistantResponse,
|
||||
92: VoiceAssistantEventResponse,
|
||||
}
|
||||
|
@ -119,6 +119,7 @@ class DeviceInfo(APIModelBase):
|
||||
project_version: str = ""
|
||||
webserver_port: int = 0
|
||||
bluetooth_proxy_version: int = 0
|
||||
voice_assistant_version: int = 0
|
||||
|
||||
|
||||
class EntityCategory(APIIntEnum):
|
||||
@ -1005,6 +1006,11 @@ class BluetoothDeviceRequestType(APIIntEnum):
|
||||
CLEAR_CACHE = 6
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class VoiceAssistantCommand(APIModelBase):
|
||||
start: bool = False
|
||||
|
||||
|
||||
class LogLevel(APIIntEnum):
|
||||
LOG_LEVEL_NONE = 0
|
||||
LOG_LEVEL_ERROR = 1
|
||||
@ -1014,3 +1020,15 @@ class LogLevel(APIIntEnum):
|
||||
LOG_LEVEL_DEBUG = 5
|
||||
LOG_LEVEL_VERBOSE = 6
|
||||
LOG_LEVEL_VERY_VERBOSE = 7
|
||||
|
||||
|
||||
class VoiceAssistantEventType(APIIntEnum):
|
||||
VOICE_ASSISTANT_ERROR = 0
|
||||
VOICE_ASSISTANT_RUN_START = 1
|
||||
VOICE_ASSISTANT_RUN_END = 2
|
||||
VOICE_ASSISTANT_STT_START = 3
|
||||
VOICE_ASSISTANT_STT_END = 4
|
||||
VOICE_ASSISTANT_INTENT_START = 5
|
||||
VOICE_ASSISTANT_INTENT_END = 6
|
||||
VOICE_ASSISTANT_TTS_START = 7
|
||||
VOICE_ASSISTANT_TTS_END = 8
|
||||
|
Loading…
Reference in New Issue
Block a user