Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions aioesphomeapi/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2025,6 +2025,7 @@ message VoiceAssistantAudio {

bytes data = 1 [(pointer_to_buffer) = true];
bool end = 2;
bytes data2 = 3 [(pointer_to_buffer) = true];
}

enum VoiceAssistantTimerEvent {
Expand Down
374 changes: 188 additions & 186 deletions aioesphomeapi/api_pb2.py

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions aioesphomeapi/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1807,7 +1807,7 @@ def subscribe_voice_assistant(
handle_stop: Callable[[bool], Coroutine[Any, Any, None]],
handle_audio: (
Callable[
[bytes],
[bytes, bytes | None],
Coroutine[Any, Any, None],
]
Comment thread
synesthesiam marked this conversation as resolved.
| None
Expand Down Expand Up @@ -1878,7 +1878,7 @@ def _on_voice_assistant_audio(msg: VoiceAssistantAudio) -> None:
if audio.end:
self._create_background_task(handle_stop(False))
else:
self._create_background_task(handle_audio(audio.data))
self._create_background_task(handle_audio(audio.data, audio.data2))

remove_callbacks.append(
connection.add_message_callback(
Expand Down
2 changes: 2 additions & 0 deletions aioesphomeapi/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ class VoiceAssistantFeature(enum.IntFlag):
TIMERS = 1 << 3
ANNOUNCE = 1 << 4
START_CONVERSATION = 1 << 5
MULTI_CHANNEL_AUDIO = 1 << 6


class ZWaveProxyFeature(enum.IntFlag):
Expand Down Expand Up @@ -1840,6 +1841,7 @@ class VoiceAssistantCommand(APIModelBase):
class VoiceAssistantAudioData(APIModelBase):
data: bytes = field(default_factory=bytes) # pylint: disable=invalid-field-call
end: bool = False
data2: bytes | None = None
Comment thread
synesthesiam marked this conversation as resolved.

Comment thread
synesthesiam marked this conversation as resolved.

@_frozen_dataclass_decorator
Expand Down
9 changes: 7 additions & 2 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4237,6 +4237,7 @@ async def test_subscribe_voice_assistant_api_audio(
stops = []
aborts = []
data_received = 0
data2_received = 0

async def handle_start(
conversation_id: str,
Expand All @@ -4253,9 +4254,11 @@ async def handle_stop(abort: bool) -> None:
else:
stops.append(True)

async def handle_audio(data: bytes) -> None:
nonlocal data_received
async def handle_audio(data: bytes, data2: bytes | None = None) -> None:
nonlocal data_received, data2_received
data_received += len(data)
if data2:
data2_received += len(data2)
Comment on lines +4257 to +4261
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use if data2 is not None: instead of if data2:

bytes is falsy when empty (b""), which is the default value for protobuf bytes fields. Using if data2: means the test callback silently drops a legitimately-received empty data2 payload, misrepresenting the production callback contract. Although the current test data is non-empty (so the assertion still passes), this introduces a divergence between what the test exercises and what callers must actually handle.

🐛 Proposed fix
-    if data2:
+    if data2 is not None:
         data2_received += len(data2)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def handle_audio(data: bytes, data2: bytes | None = None) -> None:
nonlocal data_received, data2_received
data_received += len(data)
if data2:
data2_received += len(data2)
async def handle_audio(data: bytes, data2: bytes | None = None) -> None:
nonlocal data_received, data2_received
data_received += len(data)
if data2 is not None:
data2_received += len(data2)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_client.py` around lines 4257 - 4261, The callback handle_audio
treats data2 as truthy which drops empty bytes payloads; change the conditional
in handle_audio to explicitly check for None (use "if data2 is not None:") so
empty b"" is counted while still skipping an absent None, keeping behavior
aligned with the protobuf bytes field contract and ensuring data2_received is
incremented for empty payloads.


unsub = client.subscribe_voice_assistant(
handle_start=handle_start, handle_stop=handle_stop, handle_audio=handle_audio
Expand Down Expand Up @@ -4298,10 +4301,12 @@ async def handle_audio(data: bytes) -> None:

response: message.Message = VoiceAssistantAudio(
data=bytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
data2=bytes([1, 2, 3, 4, 5]), # second audio channel
)
mock_data_received(protocol, generate_plaintext_packet(response))
await asyncio.sleep(0)
assert data_received == 10
assert data2_received == 5

response: message.Message = VoiceAssistantAudio(
end=True,
Expand Down
Loading