IR Learning Hub is a local Home Assistant custom integration for learning, generating, storing, testing, organizing, exporting, importing, and replaying infrared commands through a ZHA-connected Tuya TS1201 / MOES UFO-R11 IR blaster.
It turns the TS1201 from a low-level ZHA device into a usable remote-control hub: learn a button from a physical remote, test it, save it under a stable command ID, replay it from Home Assistant services or native remote entities, and manage the command library from a Lovelace card.
The integration is intentionally local-first. It uses Home Assistant's native ZHA runtime and does not require Tuya Cloud, Smart Life, Zigbee2MQTT, SmartIR, IR Wrapper, scripts, or helper entities for the main command path.
- Discovers supported ZHA IR transmitters during setup, with manual setup as a fallback.
- Starts TS1201 learning mode and reads the learned IR code from ZHA.
- Sends raw learned codes for testing before saving.
- Generates Sony SIRC commands, including a tested Sony STR-DB840 profile workflow, without requiring the original remote for every button.
- Stores verified commands in Home Assistant storage.
- Organizes commands as
Location -> IR device -> Command. - Exposes a stable Home Assistant service API for automations, scripts, and Developer Tools.
- Exposes each configured TS1201 as a native Home Assistant
infraredemitter entity. - Exposes registry IR devices as native Home Assistant
remote,media_player, andswitchentities that send through the emitter — directly usable by Assist, voice, and the LLM API. - Infers
media_player/switchcapabilities from an explicit per-commandfeaturerole, so any command naming works. - Supports multiple transmitters via the canonical hub + config-subentries model.
- Provides a bundled Lovelace card for day-to-day use.
- Supports command rename, relearn, delete, and optional
mdi:*icons without replacing the stored IR code. - Exports and imports one device profile as JSON, so a command set can be moved to another logical remote without relearning.
- Provides localized setup, services, sensor states, errors, and card UI in English, Russian, and Ukrainian.
IR Learning Hub v0.3.4 is the latest published Home Assistant custom integration release for the confirmed TS1201 / MOES UFO-R11 ZHA path. The current working tree prepares the next dispatcher release, v0.4.0.
Prepared next-release metadata:
{ "version": "0.4.0" }Latest published release tag: v0.3.4. Publish v0.4.0 only after the release metadata commit is tagged and a non-draft GitHub Release exists.
The implemented flow covers the full command lifecycle:
- Set up the hub and its first transmitter in the config flow (add more transmitters later as config subentries).
- Create locations and IR devices.
- Learn a command from a physical remote.
- Test the captured IR code.
- Save it with a stable ID and display name.
- Replay it from the Lovelace card, Home Assistant services, automations, scripts, or the generated
remoteentity. - Maintain the library with rename, relearn, icon, delete, export, and import actions.
Saved commands survive Home Assistant restarts because they are stored through Home Assistant's Store API. The TS1201's last-learned-code attribute is volatile, so read_last_code can return code_empty after a restart until a new button is learned. This does not affect already saved commands.
The integration currently targets one confirmed hardware/profile combination. The architecture keeps the transport layer isolated so additional ZHA IR transmitter profiles can be added later without changing the registry, UI model, or entity projection.
remote, media_player, and switch consumer entities are all shipped. The
domain is chosen per device from its preferred_domain and the capabilities
inferred from each command's feature role.
The confirmed profile is:
| Field | Value |
|---|---|
| Device | Tuya TS1201 / MOES UFO-R11 |
| Manufacturer | _TZ3290_ot6ewjvmejq5ekhl |
| Quirk | zhaquirks.tuya.ts1201.ZosungIRBlaster |
| Endpoint | 1 |
| IR control cluster | 0xE004 / 57348 |
| Learn command | 1 |
| Send command | 2 |
| Learned-code attribute | 0 |
Cluster 0xED00 / 60672 may also appear on the device, but the integration uses cluster 0xE004 because learn and send are confirmed there for the supported profile.
The bundled card is the intended daily interface.
It provides:
- a tree of locations and IR devices;
- inline add forms with name-first entry and automatic ID generation;
- remote-style command buttons;
- one-click sending;
- command relearn from the command menu;
- a three-step learning wizard;
- command icon selection with
mdi:*support; - inline rename actions;
- delete confirmations;
- device-profile export/import;
- a compact status indicator with tooltip for idle, learning, sending, code received, and error states.
Example card configuration:
type: custom:ir-learning-hub-card
title: IR Learning Hub
status_entity: sensor.ir_learning_hub_status
timeout: 60
poll_interval: 2The card uses only ir_learning_hub.* services. It does not talk to ZHA directly.
IR Learning Hub exposes services under the ir_learning_hub domain:
learnread_last_codelearn_and_readtest_codegenerate_codesave_commandsend_commandlist_commandsadd_locationadd_deviceupdate_deviceadd_commandupdate_commandrename_locationrename_devicerename_commanddelete_locationdelete_devicedelete_command
The Lovelace card is built on top of the same services that automations and scripts can use.
IR Learning Hub creates Home Assistant entities:
- one
infraredemitter entity per transmitter (config subentry); - one consumer entity per registry IR device —
remote,media_player, orswitch, chosen from the device'spreferred_domainand capabilities.
The send path is:
remote / media_player / switch -> Home Assistant infrared helper -> IR Learning Hub emitter -> ZHA TS1201 transport
Consumer entities do not call ZHA directly. They resolve the stored code, wrap it as a zosung_base64 payload, and send through the emitter entity.
remote:remote.send_commandexpects storedcommand_idvalues (raw passthrough), not display labels; plusremote.turn_on/off/toggle.media_player: standardmedia_player.*services; features andsource_listare built from each command'sfeaturerole (media_player.select_sourcetakes the source label).switch: on/off for pure power devices.
Capabilities come from an explicit per-command feature role (e.g. play, volume_up, source, power_toggle), set when saving a command or via ir_learning_hub.update_command / the Lovelace card. The free-text command_id is never interpreted, so any naming works. See docs/SERVICES.md for the role vocabulary.
Power state is assumed because IR has no feedback channel. A device with only power_toggle can become out of sync if it is changed outside Home Assistant.
The entities are registered normally and can be exposed to Assist, voice assistants, and the LLM API. Exposure is controlled by Home Assistant:
Settings -> Voice assistants -> Expose
Enable exposure for the new remote entities, or enable Home Assistant's "expose new entities automatically" option for the assistant you use. The integration does not force exposure.
Example saved-command replay:
service: ir_learning_hub.send_command
data:
location_id: cabinet
ir_device_id: cd_player
command_id: open_closeFor complete schemas, response data, and error codes, see docs/SERVICES.md.
See docs/INSTALLATION.md for detailed installation, update, and HACS notes.
Add this repository as a HACS custom repository with category Integration:
https://github.com/slawa19/IR-Learning-Hub
Install the latest available release, then restart Home Assistant. Dispatcher builds require v0.4.0 or newer once that GitHub Release exists.
Add the bundled card as a Lovelace resource:
url: /ir_learning_hub/ir-learning-hub-card.js
type: moduleAfter updating the integration, restart Home Assistant. The integration now auto-syncs the Lovelace card resource URL with a version query string, so normal upgrades do not require manually editing the resource URL.
When updating a live system to 0.3.0 or newer, a one-time migration reshapes the old
one-entry-per-transmitter setup into a single hub entry with transmitter
subentries. Learned commands are preserved (the registry store is untouched; the
migration only changes config-entry shape). Verify after restart that:
- the integration shows one "IR Learning Hub" entry with a transmitter subentry;
- the existing
sensor.ir_learning_hub_statusstill exists; - each transmitter has an
infraredemitter entity; - each registry IR device has its consumer entity (
remote/media_player/switch); - existing
ir_learning_hub.*services still send saved commands.
Recommended card flow:
- Add a location.
- Add an IR device inside that location.
- Add a command. Enter the display name first; the card generates the ID automatically.
- Start learning.
- Point the physical remote at the TS1201 and press the desired button.
- Test the learned code or skip testing.
- Save the command.
- Send it from the command grid.
Service-only flow:
- Call
ir_learning_hub.learn_and_readwith response data enabled. - Point the physical IR remote at the TS1201 and press the desired button.
- Use the returned
codewithir_learning_hub.test_code. - If the target device responds, save it with
ir_learning_hub.save_commandandverified: true. - Replay the saved command with
ir_learning_hub.send_command.
Native entity flow:
- Create or import a registry IR device and commands; set each command's
featurerole (in the card or viaupdate_command). - The consumer entity (
remote/media_player/switch) materializes immediately on the registry update signal — no restart needed. - Control it with the standard domain services (
media_player.*,remote.send_command,switch.*). - Optionally expose the entity to Assist in Home Assistant voice settings.
Example save_command data:
location_id: cabinet
ir_device_id: cd_player
command_id: open_close
name: Open/Close
code: "<base64-code>"
verified: trueIDs must match [a-z0-9_]+. Display names can be normal human-readable text.
IR Learning Hub can generate Sony SIRC commands and convert them into the same zosung_base64 payload that the TS1201 sends. This was validated on a Sony STR-DB840 receiver with the Power command.
Example service call:
service: ir_learning_hub.generate_code
data:
protocol: sony_sirc
command: 21
device: 16
bits: "12"
repeats: 3Use the returned code with ir_learning_hub.test_code, then save it with save_command if the device responds.
The repository also includes a small local utility that generates a card-importable Sony STR-DB840 profile:
python tools/generate_sony_str_db840_profile.py --output sony_str_db840.json
Import the generated JSON from the device menu in the Lovelace card. Use --verified only after you have confirmed the generated commands work with your receiver.
The registry is structured like this:
transmitter
location
IR device
command
Command records store the learned or generated code, display name, format, verification flag, update timestamp, optional display icon, and optional source/provenance data. Relearning a command replaces the IR code but preserves its icon.
The device profile export/import feature exports one IR device's commands as JSON. Import writes those commands into the device selected from the menu and uses the same backend services as the card.
- Storage: Home Assistant
Store. - Main transport: Home Assistant ZHA.
- Frontend: bundled Lovelace custom card served by the integration.
- Status: translated diagnostic sensor.
- Localization:
strings.jsonandtranslations/*.jsonfor backend surfaces, plus a card-side translation table. - HACS: release/tag based installation with repository icon assets under
brand/.
The TS1201 exposes useful IR functionality through vendor-specific ZHA behavior rather than a polished Home Assistant entity model. IR Learning Hub keeps the transport local and uses the ZHA path already available in Home Assistant:
- learn and send are handled by the confirmed Zosung control cluster;
- the learned code is read from the backend ZHA device object;
- saved commands live in Home Assistant storage instead of volatile device attributes;
- services make the workflow available to the UI, automations, scripts, and agents.
The current architecture intentionally does not use:
- Tuya Cloud or Smart Life as the runtime;
- Zigbee2MQTT transport;
- SmartIR as the sender;
- Home Assistant helper entities as primary storage;
- Home Assistant core patches or ZHA monkey patching;
- a public IR code database.
SmartIR-compatible export and additional transmitter transports may be considered later, but they are not required for the current ZHA command workflow.
custom_components/ir_learning_hub/
__init__.py # integration setup, services, frontend paths
brand/ # integration brand assets
capabilities.py # pure capability inference from command feature roles
config_flow.py # hub config flow + transmitter subentry flow
const.py # constants and service names
consumer.py # shared consumer-entity base, manager, send helpers
device_profiles.py # supported transmitter profile definitions
dispatcher.py # per-transmitter IR send queue, TTL, status
errors.py # localized integration error type
icon.png # local Home Assistant integration icon
infrared.py # infrared emitter entity platform (per subentry)
ir_command.py # opaque Zosung command wrapper
media_player.py # registry-backed media_player consumer entities
registry_runtime.py # pure registry-to-entity projection helpers
remote.py # registry-backed remote consumer entities
sensor.py # diagnostic status sensor platform
services.yaml # service field structure for Home Assistant
status.py # in-memory status model
storage.py # Store-backed command registry
storage_migration.py # pure storage migrations (v1..v4)
switch.py # registry-backed switch consumer entities
strings.json # English source strings for HA translations
transmitter_identity.py # canonical transmitter id normalization
translations/ # backend translations
ir_formats/ # pure IR format/protocol conversion helpers
zha_adapter.py # ZHA learn/read/send adapter
zha_compat.py # private ZHA proxy/cluster compatibility layer
www/ir-learning-hub-card.js
brand/ # HACS repository icon assets
hacs.json
tools/
generate_sony_str_db840_profile.py
docs/
ARCHITECTURE.md
INSTALLATION.md
SERVICES.md
TROUBLESHOOTING.md
ROADMAP.md
ТЗ Native ZHA IR Learning Hub.md
- Installation
- Services
- Architecture
- Troubleshooting
- Roadmap
- Contributing
- Security
- Changelog
- Current Russian technical specification
The integration keeps normal IR sends behind dispatcher.py, ZHA transport logic in zha_adapter.py, private ZHA proxy traversal in zha_compat.py, registry logic in storage.py, setup logic in config_flow.py, and user workflow logic in the Lovelace card. The UI should call integration services and should not reimplement ZHA reads or Zigbee cluster traversal.
When publishing a release, keep manifest.json, the Lovelace card version, README examples, installation docs, changelog, the git tag, and the GitHub Release in sync.
No license file is currently included. Until a license is added, this repository is not licensed for redistribution or reuse by default.
