Add initial voice assistant support (#412)

This commit is contained in:
Jesse Hills 2023-04-11 15:57:35 +12:00 committed by GitHub
parent 48792681f2
commit 15846c5896
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 454 additions and 216 deletions

View File

@ -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

View File

@ -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)

View File

@ -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,
}

View File

@ -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