Add iTach IP2IR infrared integration using pyitach library#172811
Add iTach IP2IR infrared integration using pyitach library#172811orandasoft wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds the new iTach IP2IR integration (config flow, discovery, infrared entities, diagnostics, repairs, options flow) and wires it into Home Assistant’s generated registries and dependency lists.
Changes:
- Introduces the
itachip2irintegration implementation (setup/unload, discovery, entities, config/options flows, diagnostics, repairs, translations, documentation). - Adds comprehensive test coverage for the new integration across flows, discovery, entities, diagnostics, repairs, and setup/unload.
- Updates generated and repository metadata (integrations/config_flows/dhcp registries, requirements, CODEOWNERS, MDI icons metadata).
Reviewed changes
Copilot reviewed 27 out of 32 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| homeassistant/components/itachip2ir/init.py | Integration entry setup/unload, runtime data, discovery startup, repair issue creation. |
| homeassistant/components/itachip2ir/config_flow.py | User/DHCP/UDP discovery setup and reconfigure flow validation and entry creation. |
| homeassistant/components/itachip2ir/const.py | Domain/discovery keys and repair issue identifiers. |
| homeassistant/components/itachip2ir/diagnostics.py | Diagnostics payload including firmware version lookup + redaction. |
| homeassistant/components/itachip2ir/discovery.py | UDP beacon listener + throttling + host update behavior. |
| homeassistant/components/itachip2ir/infrared.py | Infrared entities, timing conversion, error mapping, availability handling. |
| homeassistant/components/itachip2ir/options_flow.py | Options flow action to refresh/reconcile IR ports and trigger reload. |
| homeassistant/components/itachip2ir/repairs.py | Repair issue creation/deletion and fix flow implementation. |
| homeassistant/components/itachip2ir/strings.json | Config/options/repair/exception/entity translations. |
| homeassistant/components/itachip2ir/icons.json | Entity icon translations. |
| homeassistant/components/itachip2ir/manifest.json | Integration manifest (requirements, discovery, metadata). |
| homeassistant/components/itachip2ir/README.md | User/developer documentation for features and troubleshooting. |
| homeassistant/components/itachip2ir/ARCHITECTURE.md | Architecture and runtime model documentation. |
| homeassistant/components/itachip2ir/quality_scale.yaml | Quality scale tracking and exemptions rationale. |
| homeassistant/generated/integrations.json | Registers the itachip2ir integration metadata. |
| homeassistant/generated/config_flows.py | Adds itachip2ir to the config flows registry. |
| homeassistant/generated/dhcp.py | Adds DHCP matcher for Global Caché MAC prefix. |
| requirements_all.txt | Adds pyitach==0.1.0 for the new integration. |
| requirements_test_all.txt | Adds full test dependency set including pyitach entry. |
| pylint/plugins/pylint_home_assistant/generated/mdi_icons.py | Updates frontend version for MDI icon validation. |
| CODEOWNERS | Assigns owners for the new integration and tests. |
| tests/components/itachip2ir/init.py | Marks integration test package. |
| tests/components/itachip2ir/conftest.py | Shared fixtures to enable custom integration and mock networking. |
| tests/components/itachip2ir/test_config_flow.py | Config flow test suite (manual/DHCP/UDP/reconfigure + errors). |
| tests/components/itachip2ir/test_diagnostics.py | Diagnostics redaction/firmware error handling tests. |
| tests/components/itachip2ir/test_discovery.py | Discovery helper + listener behavior tests. |
| tests/components/itachip2ir/test_import.py | Smoke test to ensure config flow imports. |
| tests/components/itachip2ir/test_infrared.py | Infrared entity conversion, error mapping, availability tests. |
| tests/components/itachip2ir/test_init.py | Integration setup/unload/reload/discovery startup tests. |
| tests/components/itachip2ir/test_options_flow.py | Options flow refresh behavior + error mapping tests. |
| tests/components/itachip2ir/test_repairs.py | Repair issue creation/deletion and fix flow tests. |
| client = ItachClient(host, port) | ||
|
|
||
| try: | ||
| ir_capability = await async_get_ir_capability(client) | ||
| ir_module = ir_capability.module | ||
| ir_ports = ir_capability.ports | ||
| client.max_connector = ir_ports | ||
| ir_enabled_ports = ir_capability.enabled_ports | ||
| ir_connector_modes = ir_capability.connector_modes | ||
| except ItachConnectionError as err: | ||
| await client.close() | ||
| async_create_repair_issue( | ||
| hass, | ||
| _issue_id(ISSUE_CANNOT_CONNECT, entry), | ||
| translation_key=ISSUE_CANNOT_CONNECT, | ||
| placeholders={"host": host, "entry_title": entry.title}, | ||
| ) | ||
| raise ConfigEntryNotReady( | ||
| translation_domain=DOMAIN, | ||
| translation_key=ISSUE_CANNOT_CONNECT, | ||
| ) from err | ||
| except ItachError as err: | ||
| await client.close() | ||
| async_create_repair_issue( | ||
| hass, | ||
| _issue_id(ISSUE_INVALID_CONFIG, entry), | ||
| translation_key=ISSUE_INVALID_CONFIG, | ||
| placeholders={ | ||
| "host": host, | ||
| "entry_title": entry.title, | ||
| "error": str(err), | ||
| }, | ||
| ) | ||
| raise ConfigEntryNotReady( | ||
| translation_domain=DOMAIN, | ||
| translation_key=ISSUE_INVALID_CONFIG, | ||
| ) from err |
| try: | ||
| result = await self._hass.config_entries.flow.async_init( | ||
| DOMAIN, | ||
| context={"source": SOURCE_DISCOVERY}, | ||
| data={ | ||
| CONF_HOST: beacon_host, | ||
| CONF_PORT: DEFAULT_PORT, | ||
| "unique_id": unique_id, | ||
| "model": model, | ||
| }, | ||
| ) | ||
| except Exception: | ||
| _LOGGER.exception( | ||
| "Failed starting discovery config flow for iTach host=%s unique_id=%s", | ||
| beacon_host, | ||
| unique_id, | ||
| ) | ||
| return |
| except Exception: | ||
| _LOGGER.exception("Unexpected error during iTach setup") | ||
| errors["base"] = "unknown" |
09ce253 to
0e60812
Compare
|
So fun fact, we already have an itach integration. Wouldn't it make more sense to extend that one? |
0e60812 to
a08ab75
Compare
| async def _async_start_discovery(hass: HomeAssistant) -> None: | ||
| """Start discovery once.""" | ||
| domain_data = hass.data.setdefault(DOMAIN, {}) | ||
| if DISCOVERY in domain_data: | ||
| return | ||
|
|
||
| _LOGGER.debug("Starting iTach discovery") | ||
|
|
||
| discovery = ItachDiscovery(hass) | ||
| await discovery.async_start() | ||
| domain_data[DISCOVERY] = discovery |
| await self.async_set_unique_id(unique_id) | ||
| self._abort_if_unique_id_configured(updates={CONF_HOST: host}) |
| # Target tier: gold. | ||
| # | ||
| # Notes: | ||
| # - Test coverage is 95%. |
3f6f1e5 to
aad284a
Compare
| async def async_send_command(self, command: InfraredCommand) -> None: | ||
| """Send an IR command via the iTach.""" | ||
| try: | ||
| carrier_frequency = int(command.modulation) | ||
| timings = self._command_to_gc_timings(command, carrier_frequency) | ||
| except (TypeError, ValueError) as err: | ||
| raise HomeAssistantError( | ||
| translation_domain=DOMAIN, | ||
| translation_key="itach_invalid_command", | ||
| translation_placeholders={"error": str(err)}, | ||
| ) from err |
| """Create the options entry preserving unrelated options.""" | ||
| options = dict(self._config_entry.options) | ||
| if force_reload: | ||
| options[CONF_LAST_PORT_REFRESH] = time.time() | ||
| return self.async_create_entry(title="", data=options) |
| if user_input is not None: | ||
| try: | ||
| await self._validate_current_infrared_ports() | ||
| except ItachConnectionError: | ||
| errors["base"] = "cannot_connect" | ||
| except ItachError: | ||
| errors["base"] = "unknown" | ||
| except ValueError: | ||
| errors["base"] = "no_ir_ports" | ||
| else: | ||
| return self._create_options_entry(force_reload=True) |
The existing itach integration is a legacy component built on a completely different architecture. It uses deprecated YAML configuration and an old, synchronous library to control devices strictly as remote entities. The new itachip2ir integration was built from scratch to use modern Home Assistant standards: it is fully asynchronous, configures entirely through the user interface (Config Flow), handles network changes automatically via DHCP/UDP discovery, and exposes the hardware natively as infrared output port entities. Forcing modern async infrastructure and dynamic port configuration into the old YAML-based itach component would require a complete rewrite. It would break existing user setups, bloat the codebase, and result in a massive PR that's hard to review. Starting fresh with a dedicated integration was the cleanest and most stable path forward. |
Yes but from a Home Assistant perspective we don't want 10 integrations for a single device. Instead we would like to upgrade the existing one. We also don't like big migration PRs, so we need to start with small steps, so first change to a new library, then we could add the config flow, then we can add the new entities. There will be hurdles, but it will deliver the best experience for the people already using the integration. |
| ir.async_create_issue( | ||
| hass, | ||
| DOMAIN, | ||
| issue_id, | ||
| is_fixable=is_fixable, | ||
| is_persistent=False, | ||
| severity=ir.IssueSeverity.ERROR, | ||
| translation_key=translation_key, | ||
| translation_placeholders=translation_placeholders, | ||
| issue_domain=DOMAIN, | ||
| ) |
| async_create_repair_issue( | ||
| hass, | ||
| _issue_id(ISSUE_CANNOT_CONNECT, entry), | ||
| translation_key=ISSUE_CANNOT_CONNECT, | ||
| placeholders={"host": host, "entry_title": entry.title}, | ||
| ) |
| if user_input is not None: | ||
| try: | ||
| await self._validate_current_infrared_ports() | ||
| except ItachConnectionError: | ||
| errors["base"] = "cannot_connect" | ||
| except ItachError: | ||
| errors["base"] = "unknown" | ||
| except ValueError: | ||
| errors["base"] = "no_ir_ports" | ||
| else: | ||
| return self._create_options_entry(force_reload=True) |
| def _is_already_configured(self, unique_id: str) -> bool: | ||
| """Return whether a canonical unique ID is already configured.""" | ||
| return self._configured_entry(unique_id) is not None |
How about this migration path? PR 1: tests for current YAML remote.py A question about the CI tests being run for this pull request. There seems to be unrelated database tests. There seems to be pytests being run on many integrations one of them causing the CI to fail. There are 10 versions of "CI / Run tests Python 3.14.5 (pull request)" being run. Is there something wrong with the pull request? |
Breaking change
Proposed change
This PR adds a new integration for the Global Caché iTach IP2IR network infrared controller.
The integration exposes Home Assistant
infrareddomain entities for ports detected on the hardware. Port capabilities are queried directly from the device at runtime instead of relying on a hardcoded default configuration.The integration supports:
Infrared entities are created dynamically based on the capabilities currently advertised by the device, allowing the integration to synchronize entity availability without requiring a full reinstall if the hardware configuration changes.
Type of change
Additional information
Checklist
ruff format homeassistant tests)If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
Updated and included derived files by running:
python3 -m script.hassfest.requirements_all.txt.Updated by running
python3 -m script.gen_requirements_all.To help with the load of incoming pull requests: