Skip to content

Add iTach IP2IR infrared integration using pyitach library#172811

Draft
orandasoft wants to merge 3 commits into
home-assistant:devfrom
orandasoft:add-itachip2ir
Draft

Add iTach IP2IR infrared integration using pyitach library#172811
orandasoft wants to merge 3 commits into
home-assistant:devfrom
orandasoft:add-itachip2ir

Conversation

@orandasoft
Copy link
Copy Markdown

@orandasoft orandasoft commented Jun 2, 2026

Breaking change

Proposed change

This PR adds a new integration for the Global Caché iTach IP2IR network infrared controller.

The integration exposes Home Assistant infrared domain 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:

  • DHCP discovery
  • Global Caché UDP beacon discovery
  • Automatic detection of infrared-capable output ports
  • Runtime port reconciliation via the Options Flow after hardware-side reconfiguration
  • Automated diagnostics and repair flows
  • Automatic host/IP reconciliation for rediscovered devices

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

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Checklist

  • I understand the code I am submitting and can explain how it works.
  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.
  • Any generated code has been carefully reviewed for correctness and compliance with project standards.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies a diff between library versions and ideally a link to the changelog/release notes is added to the PR description.

To help with the load of incoming pull requests:

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

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 itachip2ir integration 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.

Comment on lines +123 to +159
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
Comment on lines +183 to +200
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
Comment on lines +282 to +284
except Exception:
_LOGGER.exception("Unexpected error during iTach setup")
errors["base"] = "unknown"
@joostlek
Copy link
Copy Markdown
Member

joostlek commented Jun 2, 2026

So fun fact, we already have an itach integration. Wouldn't it make more sense to extend that one?

Copilot AI review requested due to automatic review settings June 2, 2026 08:24
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

Copilot reviewed 26 out of 31 changed files in this pull request and generated 3 comments.

Comment on lines +73 to +83
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
Comment on lines +372 to +373
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%.
Copilot AI review requested due to automatic review settings June 2, 2026 08:59
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

Copilot reviewed 26 out of 30 changed files in this pull request and generated 3 comments.

Comment on lines +103 to +113
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
Comment on lines +96 to +100
"""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)
Comment on lines +50 to +60
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)
@orandasoft
Copy link
Copy Markdown
Author

So fun fact, we already have an itach integration. Wouldn't it make more sense to extend that one?

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.

@joostlek
Copy link
Copy Markdown
Member

joostlek commented Jun 2, 2026

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.

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.

Copilot AI review requested due to automatic review settings June 2, 2026 11:42
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

Copilot reviewed 26 out of 30 changed files in this pull request and generated 4 comments.

Comment on lines +43 to +53
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,
)
Comment on lines +134 to +139
async_create_repair_issue(
hass,
_issue_id(ISSUE_CANNOT_CONNECT, entry),
translation_key=ISSUE_CANNOT_CONNECT,
placeholders={"host": host, "entry_title": entry.title},
)
Comment on lines +50 to +60
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)
Comment on lines +222 to +224
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
@orandasoft
Copy link
Copy Markdown
Author

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.

How about this migration path?

PR 1: tests for current YAML remote.py
PR 2: refactor iTach remote platform without behavior changes
PR 3: config flow + YAML import/migration, still remote-focused
PR 4: switch from pyitachip2ir to async pyitach + diagnostics
PR 5: add native infrared entities
PR 6: convert legacy remotes to use infrared entities + add options flow
PR 7: discovery + repairs + YAML deprecation discussion

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants