Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
376 changes: 190 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))

Comment thread
synesthesiam marked this conversation as resolved.
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