Skip to content

[radio_frequency] Add radio_frequency entity type support#1564

Merged
bdraco merged 8 commits intomainfrom
20260407-radio-frequency
Apr 23, 2026
Merged

[radio_frequency] Add radio_frequency entity type support#1564
bdraco merged 8 commits intomainfrom
20260407-radio-frequency

Conversation

@kbx81
Copy link
Copy Markdown
Member

@kbx81 kbx81 commented Apr 8, 2026

What does this implement/fix?

Adds client-side support for the new radio_frequency entity type introduced in esphome/esphome#15556.

What's included

model.py:

  • RadioFrequencyModulation(IntEnum)OOK = 0
  • RadioFrequencyCapability(IntFlag)TRANSMITTER = 1 << 0, RECEIVER = 1 << 1
  • RadioFrequencyInfo(EntityInfo)capabilities, frequency_min, frequency_max, supported_modulations; includes supports_modulation() helper

model_conversions.py:

  • ListEntitiesRadioFrequencyResponse → RadioFrequencyInfo added to LIST_ENTITIES_SERVICES_RESPONSE_TYPES

client.py:

  • radio_frequency_transmit_raw_timings(key, frequency, timings, modulation, repeat_count, device_id) — sends InfraredRFTransmitRawTimingsRequest with carrier_frequency and modulation fields set

api.proto — same changes as the esphome repo:

  • InfraredRFTransmitRawTimingsRequest gains modulation field (field 6); guard broadened to USE_IR_RF || USE_RADIO_FREQUENCY
  • InfraredRFReceiveEvent guard broadened to USE_IR_RF || USE_RADIO_FREQUENCY
  • ListEntitiesRadioFrequencyResponse (ID 148) with capabilities, frequency_min, frequency_max, supported_modulations

tests/test_model.py — 4 new tests:

  • test_radio_frequency_modulation_enum
  • test_radio_frequency_capability_flag
  • test_radio_frequency_info_conversion
  • test_radio_frequency_info_in_type_to_name

tests/test_client.py:

  • test_radio_frequency_transmit_raw_timings — confirms InfraredRFTransmitRawTimingsRequest is sent with correct carrier_frequency and modulation fields

Design note

RF receive events reuse the InfraredRFReceiveEvent wire message — there is no separate subscribe_radio_frequency_receive() method. Callers use the existing subscribe_infrared_rf_receive() which handles both IR and RF receive events on the shared stream. The key field in each event identifies which specific entity (infrared or radio_frequency) produced it, allowing callers to filter by entity if needed.

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

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 8, 2026

Walkthrough

This PR adds radio frequency entity support by introducing a new ListEntitiesRadioFrequencyResponse protobuf message, extending the InfraredRFTransmitRawTimingsRequest message with modulation support, defining RF-specific model types and enums, registering entity conversion mappings, and providing comprehensive test coverage for the new functionality.

Changes

Cohort / File(s) Summary
Protocol Buffer Definitions
aioesphomeapi/api.proto
Adds ListEntitiesRadioFrequencyResponse message (id 148) with frequency and modulation fields. Modifies InfraredRFTransmitRawTimingsRequest and InfraredRFReceiveEvent compilation guards to include USE_RADIO_FREQUENCY. Adds modulation field (uint32) to InfraredRFTransmitRawTimingsRequest.
Client API
aioesphomeapi/client.py
Introduces radio_frequency_transmit_raw_timings() public method supporting frequency, timings, modulation (defaulting to OOK), repeat count, and device ID parameters.
Model Types and Enums
aioesphomeapi/model.py
Defines RadioFrequencyModulation enum, RadioFrequencyCapability flag enum, and new RadioFrequencyInfo dataclass with capability/frequency fields and supports_modulation() method. Updates COMPONENT_TYPE_TO_INFO and _TYPE_TO_NAME registries.
Entity Conversion Registration
aioesphomeapi/model_conversions.py, aioesphomeapi/core.py
Registers ListEntitiesRadioFrequencyResponseRadioFrequencyInfo mapping in conversion registry. Adds message type 148 to MESSAGE_TYPE_TO_PROTO dispatch table.
Test Coverage
tests/test_client.py, tests/test_model.py
Adds client method test validating protobuf message field mapping. Adds model tests verifying enum values, RadioFrequencyInfo.from_pb() conversion, default field behavior, and registry consistency.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~18 minutes

Possibly related PRs

Suggested labels

new-feature, minor

Suggested reviewers

  • bdraco
  • jesserockz
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title '[radio_frequency] Add radio_frequency entity type support' clearly and concisely summarizes the main change: adding support for a new radio_frequency entity type to the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 91.67% which is sufficient. The required threshold is 80.00%.
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.
Description check ✅ Passed The pull request description is comprehensive and clearly related to the changeset, detailing new radio_frequency entity type support with specific implementation details across all modified files.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 20260407-radio-frequency

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.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 8, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (8cbc4fe) to head (217dd9a).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #1564   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           25        25           
  Lines         4010      4032   +22     
=========================================
+ Hits          4010      4032   +22     

☔ 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.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 8, 2026

Merging this PR will not alter performance

✅ 11 untouched benchmarks


Comparing 20260407-radio-frequency (217dd9a) with main (8cbc4fe)

Open in CodSpeed

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: 2

🧹 Nitpick comments (2)
tests/test_client.py (1)

2829-2853: Add a coexistence test for dual subscriptions on the same wire event.

This test validates the RF callback path, but it does not cover the key design behavior where IR and RF subscribers should both receive the same InfraredRFReceiveEvent without interfering with each other.

🧪 Suggested additional test
+async def test_subscribe_infrared_and_radio_frequency_receive_independent(
+    api_client: tuple[
+        APIClient, APIConnection, asyncio.Transport, APIPlaintextFrameHelper
+    ],
+) -> None:
+    client, _connection, _transport, protocol = api_client
+    ir_msgs: list[InfraredRFReceiveEvent] = []
+    rf_msgs: list[InfraredRFReceiveEvent] = []
+
+    client.subscribe_infrared_rf_receive(ir_msgs.append)
+    client.subscribe_radio_frequency_receive(rf_msgs.append)
+    await asyncio.sleep(0)
+
+    response: message.Message = InfraredRFReceiveEventPb(
+        key=777, device_id=9, timings=[100, -100]
+    )
+    mock_data_received(protocol, generate_plaintext_packet(response))
+
+    assert len(ir_msgs) == 1
+    assert len(rf_msgs) == 1
+    assert ir_msgs[0].key == 777
+    assert rf_msgs[0].key == 777
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_client.py` around lines 2829 - 2853, The test
test_subscribe_radio_frequency_receive only verifies the RF subscriber path; add
a coexistence check by registering a second handler using
client.subscribe_infrared_receive (or the IR subscription method) alongside
client.subscribe_radio_frequency_receive, have both handlers append to separate
lists, emit the same InfraredRFReceiveEvent (as currently done with
InfraredRFReceiveEventPb), and assert that both lists receive exactly one
identical event (compare key, device_id, timings) to ensure IR and RF
subscribers both get the same message without interference.
aioesphomeapi/model.py (1)

171-177: Give supported_modulations a safer shape.

RadioFrequencyModulation.OOK is 0, but RadioFrequencyInfo.supported_modulations is a bitmask. The obvious check info.supported_modulations & RadioFrequencyModulation.OOK will therefore always be false. A helper like supports_modulation() or a dedicated bitmask type would make the new read-side API much less error-prone.

Also applies to: 1280-1287

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@aioesphomeapi/model.py` around lines 171 - 177, RadioFrequencyModulation is
defined as an IntEnum with OOK == 0 but supported_modulations on
RadioFrequencyInfo is treated as a bitmask, so checks like
info.supported_modulations & RadioFrequencyModulation.OOK always fail; fix by
giving modulations a proper bitmask type or a helper: either convert
RadioFrequencyModulation to enum.IntFlag (so values are powers-of-two) or add a
method on RadioFrequencyInfo named supports_modulation(mod:
RadioFrequencyModulation) that interprets supported_modulations as a bitmask
(e.g., return bool(self.supported_modulations & int(mod))). Update references to
use supports_modulation(...) or the new IntFlag values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@aioesphomeapi/api.proto`:
- Around line 2568-2585: The ListEntitiesRadioFrequencyResponse message lost the
standard field options: add the same max_data_length options to object_id and
name and restore the conditional guard (field_ifdef = "USE_ENTITY_ICON") on icon
so the RF entity metadata matches other ListEntities* responses (see
ListEntitiesInfraredResponse for the exact option names and values); update the
message definition for ListEntitiesRadioFrequencyResponse to reapply those
options to object_id, name, and icon accordingly.

In `@aioesphomeapi/client.py`:
- Around line 518-529: subscribe_radio_frequency_receive currently registers the
same InfraredRFReceiveEvent callback as subscribe_infrared_rf_receive, so RF
subscribers will also receive IR packets; update
subscribe_radio_frequency_receive to add key-based filtering before dispatch
(e.g., wrap/replace the partial passed to _get_connection().add_message_callback
so it checks the message's identifying key/type and only calls
on_infrared_rf_receive_event/on_receive when the key indicates RF), or
alternatively document in the method docstring that it is a shared IR/RF stream
and not RF-only; reference subscribe_radio_frequency_receive,
subscribe_infrared_rf_receive, InfraredRFReceiveEvent,
on_infrared_rf_receive_event, and _get_connection().add_message_callback to
locate where to apply the filter or doc change.

---

Nitpick comments:
In `@aioesphomeapi/model.py`:
- Around line 171-177: RadioFrequencyModulation is defined as an IntEnum with
OOK == 0 but supported_modulations on RadioFrequencyInfo is treated as a
bitmask, so checks like info.supported_modulations &
RadioFrequencyModulation.OOK always fail; fix by giving modulations a proper
bitmask type or a helper: either convert RadioFrequencyModulation to
enum.IntFlag (so values are powers-of-two) or add a method on RadioFrequencyInfo
named supports_modulation(mod: RadioFrequencyModulation) that interprets
supported_modulations as a bitmask (e.g., return bool(self.supported_modulations
& int(mod))). Update references to use supports_modulation(...) or the new
IntFlag values.

In `@tests/test_client.py`:
- Around line 2829-2853: The test test_subscribe_radio_frequency_receive only
verifies the RF subscriber path; add a coexistence check by registering a second
handler using client.subscribe_infrared_receive (or the IR subscription method)
alongside client.subscribe_radio_frequency_receive, have both handlers append to
separate lists, emit the same InfraredRFReceiveEvent (as currently done with
InfraredRFReceiveEventPb), and assert that both lists receive exactly one
identical event (compare key, device_id, timings) to ensure IR and RF
subscribers both get the same message without interference.
🪄 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: 49cf56a2-a6a1-4177-9795-957d489b5611

📥 Commits

Reviewing files that changed from the base of the PR and between f031dab and 07aa661.

📒 Files selected for processing (8)
  • aioesphomeapi/api.proto
  • aioesphomeapi/api_options_pb2.py
  • aioesphomeapi/api_pb2.py
  • aioesphomeapi/client.py
  • aioesphomeapi/model.py
  • aioesphomeapi/model_conversions.py
  • tests/test_client.py
  • tests/test_model.py

Comment thread aioesphomeapi/api.proto
Comment thread aioesphomeapi/client.py Outdated
@kbx81 kbx81 marked this pull request as draft April 8, 2026 06:20
@kbx81 kbx81 force-pushed the 20260407-radio-frequency branch from 96b3706 to ebf3400 Compare April 15, 2026 02:01
@kbx81 kbx81 marked this pull request as ready for review April 22, 2026 20:38
@kbx81 kbx81 requested a review from bdraco April 22, 2026 20:58
@bdraco bdraco requested a review from Copilot April 23, 2026 01:26
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

Adds client-side support for the new radio_frequency entity type (per esphome/esphome#15556), including protobuf schema updates, model definitions, entity conversion wiring, and a client transmit helper.

Changes:

  • Add RadioFrequencyInfo (+ modulation/capability enums) and register the new entity type in model type maps.
  • Extend protobuf schema (api.proto + regenerated api_pb2.py) to include ListEntitiesRadioFrequencyResponse and RF-related guards/fields.
  • Wire entity listing conversion for ListEntitiesRadioFrequencyResponse and add tests for model conversion + RF transmit request.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
aioesphomeapi/model.py Adds RF enums/info model and registers radio_frequency in info/type maps
aioesphomeapi/model_conversions.py Maps ListEntitiesRadioFrequencyResponseRadioFrequencyInfo
aioesphomeapi/client.py Adds radio_frequency_transmit_raw_timings helper (but missing receive subscribe API described in PR)
aioesphomeapi/core.py Registers message type ID 148 to the new proto message
aioesphomeapi/api.proto Adds RF list-entities response + broadens IR/RF guards; adds modulation field
aioesphomeapi/api_pb2.py Regenerated protobuf bindings reflecting api.proto changes
tests/test_model.py Adds RF model/unit tests for enums, conversion, and _TYPE_TO_NAME registration
tests/test_client.py Adds transmit test for radio_frequency_transmit_raw_timings (but missing receive subscribe test described in PR)

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

Comment thread aioesphomeapi/client.py
Comment thread tests/test_client.py
@bdraco
Copy link
Copy Markdown
Member

bdraco commented Apr 23, 2026

PR description talks about subscribe_radio_frequency_receive but its not in here?

@kbx81
Copy link
Copy Markdown
Member Author

kbx81 commented Apr 23, 2026

subscribe_radio_frequency_receive was intentionally removed...and now removed from the description, too. 🙂

@bdraco bdraco merged commit 281dd32 into main Apr 23, 2026
19 checks passed
@bdraco bdraco deleted the 20260407-radio-frequency branch April 23, 2026 01:50
@bdraco bdraco added the minor label Apr 23, 2026
@kbx81
Copy link
Copy Markdown
Member Author

kbx81 commented Apr 23, 2026

Thanks 🙏🏻

@github-actions github-actions Bot locked and limited conversation to collaborators Apr 24, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants