[radio_frequency] Add radio_frequency entity type support#1564
Conversation
WalkthroughThis PR adds radio frequency entity support by introducing a new Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~18 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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
InfraredRFReceiveEventwithout 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: Givesupported_modulationsa safer shape.
RadioFrequencyModulation.OOKis0, butRadioFrequencyInfo.supported_modulationsis a bitmask. The obvious checkinfo.supported_modulations & RadioFrequencyModulation.OOKwill therefore always be false. A helper likesupports_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
📒 Files selected for processing (8)
aioesphomeapi/api.protoaioesphomeapi/api_options_pb2.pyaioesphomeapi/api_pb2.pyaioesphomeapi/client.pyaioesphomeapi/model.pyaioesphomeapi/model_conversions.pytests/test_client.pytests/test_model.py
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
96b3706 to
ebf3400
Compare
There was a problem hiding this comment.
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+ regeneratedapi_pb2.py) to includeListEntitiesRadioFrequencyResponseand RF-related guards/fields. - Wire entity listing conversion for
ListEntitiesRadioFrequencyResponseand 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 ListEntitiesRadioFrequencyResponse → RadioFrequencyInfo |
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.
|
PR description talks about |
|
|
|
Thanks 🙏🏻 |
What does this implement/fix?
Adds client-side support for the new
radio_frequencyentity type introduced in esphome/esphome#15556.What's included
model.py:RadioFrequencyModulation(IntEnum)—OOK = 0RadioFrequencyCapability(IntFlag)—TRANSMITTER = 1 << 0,RECEIVER = 1 << 1RadioFrequencyInfo(EntityInfo)—capabilities,frequency_min,frequency_max,supported_modulations; includessupports_modulation()helpermodel_conversions.py:ListEntitiesRadioFrequencyResponse → RadioFrequencyInfoadded toLIST_ENTITIES_SERVICES_RESPONSE_TYPESclient.py:radio_frequency_transmit_raw_timings(key, frequency, timings, modulation, repeat_count, device_id)— sendsInfraredRFTransmitRawTimingsRequestwithcarrier_frequencyandmodulationfields setapi.proto— same changes as the esphome repo:InfraredRFTransmitRawTimingsRequestgainsmodulationfield (field 6); guard broadened toUSE_IR_RF || USE_RADIO_FREQUENCYInfraredRFReceiveEventguard broadened toUSE_IR_RF || USE_RADIO_FREQUENCYListEntitiesRadioFrequencyResponse(ID 148) withcapabilities,frequency_min,frequency_max,supported_modulationstests/test_model.py— 4 new tests:test_radio_frequency_modulation_enumtest_radio_frequency_capability_flagtest_radio_frequency_info_conversiontest_radio_frequency_info_in_type_to_nametests/test_client.py:test_radio_frequency_transmit_raw_timings— confirmsInfraredRFTransmitRawTimingsRequestis sent with correctcarrier_frequencyandmodulationfieldsDesign note
RF receive events reuse the
InfraredRFReceiveEventwire message — there is no separatesubscribe_radio_frequency_receive()method. Callers use the existingsubscribe_infrared_rf_receive()which handles both IR and RF receive events on the shared stream. Thekeyfield in each event identifies which specific entity (infrared or radio_frequency) produced it, allowing callers to filter by entity if needed.Types of changes
Pull request in esphome (if applicable):
radio_frequencyentity type (base component + API) esphome#15556Checklist:
tests/folder).