Skip to content

Add second audio channel for voice#1625

Open
synesthesiam wants to merge 2 commits intoesphome:mainfrom
synesthesiam:synesthesiam-20260505-voice-2-channels
Open

Add second audio channel for voice#1625
synesthesiam wants to merge 2 commits intoesphome:mainfrom
synesthesiam:synesthesiam-20260505-voice-2-channels

Conversation

@synesthesiam
Copy link
Copy Markdown
Contributor

What does this implement/fix?

Adds a second audio channel to voice API audio. This allows for providing the best audio for different parts of the voice pipeline: home-assistant/architecture#1364

When audio is streamed to Home Assistant, data from both microphone sources is sent in the VoiceAssistantAudio message (as data and data2). The handle_audio method has been updated to pass the second channel to Home Assistant.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Code quality improvements to existing code or addition of tests
  • Other

Related issue or feature (if applicable):

  • fixes

Pull request in esphome (if applicable):

Checklist:

  • The code change is tested and works locally.
  • If api.proto was modified, a linked pull request has been made to esphome with the same changes.
  • Tests have been added to verify that the new code works (under tests/ folder).

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 5, 2026

Merging this PR will not alter performance

✅ 11 untouched benchmarks


Comparing synesthesiam:synesthesiam-20260505-voice-2-channels (aade17f) with main (4142434)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (f7f8640) during the generation of this report, so 4142434 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 5, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (4142434) to head (aade17f).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #1625   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           25        25           
  Lines         4040      4042    +2     
=========================================
+ Hits          4040      4042    +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends the ESPHome voice assistant API audio stream to support a second audio channel by adding a new data2 field to VoiceAssistantAudio, plumbing it through the Python client callback, and adding test coverage for receiving the second channel.

Changes:

  • Add data2 to the VoiceAssistantAudio protobuf message to carry a second audio stream.
  • Update the voice assistant audio subscription path to forward data2 to the handle_audio callback.
  • Extend models/tests to represent and validate multi-channel audio reception.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/test_client.py Updates voice assistant subscription test to validate data2 reception.
aioesphomeapi/model.py Adds MULTI_CHANNEL_AUDIO feature flag and data2 field to the voice audio model.
aioesphomeapi/client.py Changes handle_audio callback signature and forwards audio.data2 to it.
aioesphomeapi/api.proto Adds bytes data2 = 3 to VoiceAssistantAudio.
aioesphomeapi/api_pb2.py Regenerated protobuf output reflecting the updated schema (but currently inconsistent with api.proto).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread aioesphomeapi/client.py
Comment thread aioesphomeapi/model.py
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 2026

Warning

Rate limit exceeded

@synesthesiam has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 50 minutes and 39 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6830ac1e-0fae-49f4-8651-918076ebc835

📥 Commits

Reviewing files that changed from the base of the PR and between d9bb424 and aade17f.

📒 Files selected for processing (1)
  • aioesphomeapi/api_pb2.py

Walkthrough

This PR adds multi-channel audio support to the voice assistant API by introducing a second audio data channel (data2) to the protocol, data models, and callback signature. The handle_audio callback now receives both channels as separate byte sequences.

Changes

Multi-Channel Voice Assistant Audio

Layer / File(s) Summary
Data Shape
aioesphomeapi/api.proto, aioesphomeapi/model.py
Proto message VoiceAssistantAudio gains data2 field (tag 3); VoiceAssistantAudioData dataclass adds optional data2: bytes | None field; new MULTI_CHANNEL_AUDIO flag added to VoiceAssistantFeature enum.
Callback Wiring
aioesphomeapi/client.py
subscribe_voice_assistant method signature updated: handle_audio callback now accepts two arguments (bytes, bytes | None) instead of one; invocation site passes both audio.data and audio.data2.
Tests
tests/test_client.py
Test setup adds data2_received counter; handle_audio test callback signature extended to accept optional data2 parameter; test case now constructs VoiceAssistantAudio with both data and data2 payloads and asserts both channels are received correctly.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

  • esphome/aioesphomeapi#954: Both PRs modify the voice assistant callback mechanism in subscribe_voice_assistant; this one adds multi-channel audio support while the other adds a separate announcement-finished callback.

Suggested labels

new-feature

Suggested reviewers

  • jesserockz
  • bdraco
  • balloob
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely describes the main feature being added: a second audio channel for the voice assistant API, which is the primary change across all modified files.
Description check ✅ Passed The description clearly explains what the PR implements (second audio channel support), the purpose/motivation, the specific changes made (data2 field, handle_audio signature update), and references related issues and testing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with 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.

Inline comments:
In `@aioesphomeapi/client.py`:
- Around line 1809-1812: The handle_audio callback is now required to accept two
positional args which breaks existing one-arg consumers; update the type and
invocation to be backward-compatible by allowing a second optional bytes arg and
calling the callback with either one or two args depending on its signature.
Change the Callable annotation around handle_audio to indicate the second
parameter is optional (bytes | None), and when invoking handle_audio (the call
sites around the current definitions and at the later usage near lines
~1876-1882), detect the callback's parameter count via inspect.signature (or
fallback to a try/except TypeError) and call either handle_audio(audio.data) for
single-arg callbacks or handle_audio(audio.data, audio.data2) for two-arg
callbacks, passing None for audio.data2 when not available.

In `@aioesphomeapi/model.py`:
- Around line 1841-1845: The field VoiceAssistantAudioData.data2 is declared as
bytes | None but from_pb currently yields bytes (often b"") for absent
channel-2, so normalize the API by either changing the type to bytes or ensuring
from_pb sets data2 to b"" when the protobuf has no channel-2; update the
conversion in the VoiceAssistantAudioData.from_pb (or the factory that builds
this class from the protobuf) to always populate data2 as bytes (use b"" for
absent data) and adjust the model typing accordingly so callers can depend on a
single semantic.

In `@tests/test_client.py`:
- Around line 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.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4d08ab95-b2c6-45fc-8073-3247b30e3bd5

📥 Commits

Reviewing files that changed from the base of the PR and between f7f8640 and d9bb424.

📒 Files selected for processing (5)
  • aioesphomeapi/api.proto
  • aioesphomeapi/api_pb2.py
  • aioesphomeapi/client.py
  • aioesphomeapi/model.py
  • tests/test_client.py

Comment thread aioesphomeapi/client.py
Comment thread aioesphomeapi/model.py
Comment thread tests/test_client.py
Comment on lines +4257 to +4261
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)
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants