Skip to content

Base IR platform - #867

Merged
puddly merged 7 commits into
zigpy:devfrom
puddly:puddly/base-ir-platform
Sep 29, 2026
Merged

puddly merged 7 commits into
zigpy:devfrom
puddly:puddly/base-ir-platform

Conversation

@puddly

@puddly puddly commented Aug 14, 2026

Copy link
Copy Markdown
Member

Like #864, this is a base platform with no discovery hooks. There are very few IR devices on the market (almost all of them Tuya) so this can be used as a base for a more complex entity that decouples Tuya from fake ZCL commands.

@Hedda

Hedda commented Aug 22, 2026 •

Copy link
Copy Markdown

I assume it is in response to this PR to instead add an infrared base platform to zha and integrate this
zha-device-handlers/quirks:

Also opened a seperate discussion for that concept here in the zha repo's discussions section:

FYI, for additional real world use cases check out this Zigbee IR blaster wrapper custom component and this general discussion for signals from the Home Assistent's community on how they would like to use a Zigbee infrared emitters/reciever bridge/proxy. See:

For reference, latest release of Zigbee2MQTT also have native support for Tuya based Zigbee IR blasters/bridges (such as Tuya ZS06, Tuya UFO-R11, UFO-R4Z, QAIRZPRO), which should work with the new MQTT infrared platform in Home Assistant. See:

Maybe also easyiot ZB-IR01?

@slavonnet

Copy link
Copy Markdown

Any updates?

@puddly

puddly commented Sep 13, 2026

Copy link
Copy Markdown
Member Author

@slavonnet What sort of updates? IR doesn't have receive capabilities in Home Assistant so this platform is at most half complete until that feature is added architecturally.

@Hedda

Hedda commented Sep 14, 2026 •

Copy link
Copy Markdown

IR doesn't have receive capabilities in Home Assistant so this platform is at most half complete until that feature is added architecturally.

If I have not misunderstood, the infrared entity platform in Home Assistant has been updated to support ceive capabilities?

Infrared entity documentation at least seem to indicate that it now supports an receiver that captures incoming IR signals?

There looks to be an InfraredReceiverEntity base class that wraps a piece of IR receiver hardware, so need clearification? Meaning that there are now standard InfraredEmitterEntity and InfraredReceiverEntity instances, so need both those.

Taken literally, the statement seems out of date? Home Assistant core's infrared platform does have receive support now.

However, as I understand it, the upstream Home Assistant core's 's receiver model is passive, so the hardware is assumed to be always listening, which may not be possible to work with for Tuya's IR recievers that are currently on the market there is no way to tell a receiver to start listening or stop listening? Missing is some kind of learn mode commands in upstream Home Assistant core?

slavonnet's home-assistant/core#175403 sidesteps this by building learn/receive flow into a separate ZHA-infrared integration.

PS: Also check out this new roadmap discussion about shaping the way forward for a unified universal remote integration that uses Home Assistant's infrared component (which might lead to the upstream architectural changes needed for this?):

@Hedda

Hedda commented Sep 14, 2026 •

Copy link
Copy Markdown

FYI, Tuya UFO-R11 connected to Zigbee2MQTT can apparently now work as a native emitter and receiver with Home Assistant 2026.8 + MQTT infrared platform. Not tested myself but supposedly custom HAIR component discover it like other hardware:

@slavonnet

Copy link
Copy Markdown

Zigbee2MQTT

As I understand it, Zigbee2MQTT doesn’t work with ZHA? There’s no simple way to connect a device to ZHA but still broadcast it to MQTT?

@emrikol

emrikol commented Sep 25, 2026

Copy link
Copy Markdown

Thanks for working on this. I recently implemented a similar architecture in a Home Assistant custom integration, temporarily adapting two ZHA IR devices into standard InfraredEmitterEntity and InfraredReceiverEntity instances. Everything above that layer uses only Home Assistant's generic Infrared helpers, so a few implementation details may be useful here:

  • Core's receiver subscription API worked for hardware that needs an explicit learning mode: arm on the first subscriber, stop on the last, and clean up on success, timeout, cancellation, unload, or cancellation while arming.
  • The TS1201 quirk exposes each learned capture as one cached vendor payload, so I baseline the previous value and wait for it to change. The payload has no carrier metadata, so I expose modulation=None rather than treating an assumed 38 kHz value as measured.
  • For Zosung sends, I wait for the matching transfer-completion frame. Keeping transport separate from payload encoding also mattered: another device I tested uses the same Zosung transfer protocol with a Broadlink-format payload.

This suggests Home Assistant's existing subscription contract can support explicitly armed receivers while device-specific arming, rearming, conversion, and transfer behavior remains in the ZHA provider. Once ZHA exposes standard Infrared entities, the temporary compatibility layer can disappear without affecting consumers.

If any of this is useful, I'd be glad to contribute a focused patch for optional modulation and receiver lifecycle/teardown handling, either against this draft or as a follow-up PR.

@puddly

puddly commented Sep 28, 2026

Copy link
Copy Markdown
Member Author

@emrikol Definitely! I'm going to get this base platform finished up (it's just a mirror of HA's side with no consumers) but if you want to share your findings with existing devices, please feel free, we'll likely run into the same issues one way or another 😄

Architecturally, any existing IR device quirks we have will need to be modified to directly implement these entities instead of relying on fake ZCL clusters and other hacks, then retain their old feature set for backwards compatibility. The one IR device I have is battery powered but uses a compressed Tuya payload, enters learning mode for 30s only after being asked, etc. The battery powered device I think is exposing the API limitation, as it genuinely needs a dedicated learning mode in Core I think?

@codecov

codecov Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.22%. Comparing base (d600abc) to head (97c3903).

Additional details and impacted files
@@           Coverage Diff           @@
##              dev     #867   +/-   ##
=======================================
  Coverage   97.21%   97.22%           
=======================================
  Files          59       61    +2     
  Lines       10617    10654   +37     
=======================================
+ Hits        10321    10358   +37     
  Misses        296      296           

☔ View full report in Codecov by Harness.
📢 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.

@puddly

puddly commented Sep 28, 2026 •

Copy link
Copy Markdown
Member Author

I think for battery-powered IR receivers (like my one Tuya device), the best we can do to conform to Core's entity model is have the device expose both an emitter and a receiver entity but also have an "Enter learning mode for 30s" button? When it's pressed, the IR receiver turns on and will emit received codes.

@puddly
puddly marked this pull request as ready for review September 28, 2026 20:05
@puddly
puddly requested review from zigpy-review-bot and a lite review from Copilot September 28, 2026 20:05

@zigpy-review-bot zigpy-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Clean base platform that mirrors the merged event base (#864) and HA core's infrared entity split; one small must-address below, otherwise this looks good to go.

Must-address

  • BaseInfraredEmitter doesn't mix in ABC, so its @abstractmethod is inert (inline comment).

Optional

  • A docstring note on what repeat_count means relative to timings would save the first quirk author a double-repeat bug (inline comment).

Re: battery-powered receivers with a timed learning window (puddly's last comment)
The "emitter + receiver + a 'learn for 30s' button" shape seems like the right fit for the device you describe. HA's receiver contract is passive: InfraredReceiverEntity only offers async_subscribe_received_signal, with no arm/disarm hook, so the alternative is overriding that subscribe callback to arm the device on first subscriber (the approach emrikol described). For a sleepy device with a 30 s window, that turns "somebody is subscribed" into "keep re-waking the device every 30 s", which costs battery and hides from the user when capture is actually live. An explicit button keeps the arming user-driven and fits core's current model without new API; subscriber-driven arming stays available for mains-powered receivers that can listen indefinitely. None of this has to land in this PR, since the base classes don't constrain either approach.

Verified (5 checks)
  • tests/test_platform_infrared.py + tests/test_platform_event.py pass locally at 88aaf33 (9 passed); mypy in the worktree venv (real deps installed) is clean.
  • PlatformEntity's metaclass is plain type; inspect.isabstract(BaseInfraredEmitter) is False and __abstractmethods__ is unset. With ABC mixed in, a subclass missing async_send_command raises TypeError at instantiation, and the test's FakeEmitter is unaffected.
  • Shapes against HA core (homeassistant/components/infrared/entity.py): InfraredDeviceClass values match; InfraredSignal(timings, modulation, repeat_count) covers both infrared_protocols.commands.Command (get_raw_timings(), modulation, repeat_count) and InfraredReceivedSignal(timings, modulation). HA's receiver state (last-received timestamp) is maintained HA-side, so emitting an event without a state change here is consistent.
  • _handle_received_signal → EntityInfraredSignalReceivedEvent(**self.identifiers.__dict__, ...) matches the event platform's EntityEventTriggeredEvent construction.
  • discovery.PLATFORMS gains INFRARED like EVENT did; nothing registers entities for it yet, so there is no snapshot/regen impact.

Copilot AI left a comment

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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@puddly
puddly merged commit 0e42637 into zigpy:dev Sep 29, 2026
10 checks passed
@abmantis

abmantis commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Regarding the discussions here around receivers, currently the Core architecture for receivers is meant for devices that can do "live" signal receiving. The "press-to-learn" ones, that only have a short window of learning time, are not supported. There should be no workarounds in quirks or zigpy for those.

So most Zigbee devices' infrared entities for now would only support emitting, not receiving.

In the context of an "Universal Remote" integration (see OpenHomeFoundation/roadmap#226 ), it might make sense to extend the architecture to support "press-to-learn" devices, though. But that will require its own architecture proposal first.

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.

7 participants