4
0
Эх сурвалжийг харах

feat!(infrared): add action for sending learned remote commands

In legacy remote platform, we have an internal database of learned
commands. These are only accessible via the remote entity.

This new service makes the stored commands available to infrared
entities, not only tuya_local ones, but any others too in the spirit
of the new infrared platform.

- also updates the infrared platform for its new backward incompatible
  API in HA 2026.5
Jason Rumney 1 долоо хоног өмнө
parent
commit
df76a1ce5d
31 өөрчлөгдсөн 602 нэмэгдсэн , 32 устгасан
  1. 3 0
      custom_components/tuya_local/__init__.py
  2. 3 0
      custom_components/tuya_local/icons.json
  3. 38 13
      custom_components/tuya_local/infrared.py
  4. 8 16
      custom_components/tuya_local/remote.py
  5. 69 0
      custom_components/tuya_local/services.py
  6. 18 0
      custom_components/tuya_local/services.yaml
  7. 20 0
      custom_components/tuya_local/translations/bg.json
  8. 20 0
      custom_components/tuya_local/translations/ca.json
  9. 20 0
      custom_components/tuya_local/translations/cz.json
  10. 20 0
      custom_components/tuya_local/translations/de.json
  11. 20 0
      custom_components/tuya_local/translations/el.json
  12. 20 0
      custom_components/tuya_local/translations/en.json
  13. 20 0
      custom_components/tuya_local/translations/es.json
  14. 20 0
      custom_components/tuya_local/translations/fr.json
  15. 20 0
      custom_components/tuya_local/translations/hu.json
  16. 20 0
      custom_components/tuya_local/translations/id.json
  17. 20 0
      custom_components/tuya_local/translations/it.json
  18. 20 0
      custom_components/tuya_local/translations/ja.json
  19. 20 0
      custom_components/tuya_local/translations/no-NB.json
  20. 20 0
      custom_components/tuya_local/translations/pl.json
  21. 20 0
      custom_components/tuya_local/translations/pt-BR.json
  22. 20 0
      custom_components/tuya_local/translations/pt-PT.json
  23. 20 0
      custom_components/tuya_local/translations/ro.json
  24. 20 0
      custom_components/tuya_local/translations/ru.json
  25. 20 0
      custom_components/tuya_local/translations/sv.json
  26. 20 0
      custom_components/tuya_local/translations/uk.json
  27. 20 0
      custom_components/tuya_local/translations/ur.json
  28. 20 0
      custom_components/tuya_local/translations/zh-Hans.json
  29. 20 0
      custom_components/tuya_local/translations/zh-Hant.json
  30. 1 1
      hacs.json
  31. 2 2
      pyproject.toml

+ 3 - 0
custom_components/tuya_local/__init__.py

@@ -30,6 +30,7 @@ from .const import (
 )
 )
 from .device import async_delete_device, get_device_id, setup_device
 from .device import async_delete_device, get_device_id, setup_device
 from .helpers.device_config import get_config
 from .helpers.device_config import get_config
+from .services import async_setup_services
 
 
 _LOGGER = logging.getLogger(__name__)
 _LOGGER = logging.getLogger(__name__)
 NOT_FOUND = "Configuration file for %s not found"
 NOT_FOUND = "Configuration file for %s not found"
@@ -908,10 +909,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
         return False
         return False
 
 
     entities = set()
     entities = set()
+    has_remote = False
     for e in device_conf.all_entities():
     for e in device_conf.all_entities():
         entities.add(e.entity)
         entities.add(e.entity)
 
 
     await hass.config_entries.async_forward_entry_setups(entry, entities)
     await hass.config_entries.async_forward_entry_setups(entry, entities)
+    await async_setup_services(hass, entities)
 
 
     entry.add_update_listener(async_update_entry)
     entry.add_update_listener(async_update_entry)
 
 

+ 3 - 0
custom_components/tuya_local/icons.json

@@ -903,5 +903,8 @@
                 }
                 }
             }
             }
         }
         }
+    },
+    "services": {
+        "send_learned_ir_command": "mdi:remote"
     }
     }
 }
 }

+ 38 - 13
custom_components/tuya_local/infrared.py

@@ -5,6 +5,7 @@ Implementation of Tuya infrared control devices
 import asyncio
 import asyncio
 import json
 import json
 import logging
 import logging
+from typing import override
 
 
 from homeassistant.components.infrared import InfraredCommand, InfraredEntity
 from homeassistant.components.infrared import InfraredCommand, InfraredEntity
 from tinytuya.Contrib.IRRemoteControlDevice import IRRemoteControlDevice as IR
 from tinytuya.Contrib.IRRemoteControlDevice import IRRemoteControlDevice as IR
@@ -43,27 +44,29 @@ class TuyaLocalInfrared(TuyaLocalEntity, InfraredEntity):
 
 
     async def async_send_command(self, command: InfraredCommand) -> None:
     async def async_send_command(self, command: InfraredCommand) -> None:
         """Handle sending an infrared command."""
         """Handle sending an infrared command."""
+        if isinstance(command, TuyaRemoteCommand):
+            # If it's already a TuyaRemoteCommand, skip the round-trip conversion
+            tuya_command = command.code
+            _LOGGER.info("%s sending command: %s", self._config.config_id, tuya_command)
+            await self._ir_send(tuya_command)
+            return
+
         timings = command.get_raw_timings()
         timings = command.get_raw_timings()
         split = {}
         split = {}
         raw = []
         raw = []
         i = 0
         i = 0
         for timing in timings:
         for timing in timings:
-            if timing.high_us > 50000:
-                split[i] = timing.high_us - 5000
-                raw.append(5000)
-                raw.append(timing.low_us)
-            elif timing.low_us > 50000:
-                raw.append(timing.high_us)
+            utiming = abs(timing)
+            if utiming > 50000:
+                split[i] = utiming - 5000
                 raw.append(5000)
                 raw.append(5000)
-                split[i + 2] = timing.low_us - 5000
             else:
             else:
-                raw.append(timing.high_us)
-                raw.append(timing.low_us)
-            i += 2
+                raw.append(utiming)
+            i += 1
 
 
-        # HA's converter leaves the last low timing as 0, but Tuya seems to expect around 5 - 10 ms
-        if raw[-1] == 0:
-            raw[-1] = 5000
+        # HA format leaves off the last low timing, but Tuya needs it
+        if len(raw) % 2 == 1:
+            raw.append(5000)
 
 
         start = 0
         start = 0
         for s, t in split.items():
         for s, t in split.items():
@@ -107,3 +110,25 @@ class TuyaLocalInfrared(TuyaLocalEntity, InfraredEntity):
             f"{self._type_dp.id}": 0,
             f"{self._type_dp.id}": 0,
             f"{self._send_dp.id}": command,
             f"{self._send_dp.id}": command,
         }
         }
+
+
+# Used to send legacy remote learned commands via infrared entities.
+# This allows the learned commands to be sent via infrared entities
+# provided by other integrations.
+class TuyaRemoteCommand(InfraredCommand):
+    """Representation of a command in Tuya uncompressed format."""
+
+    def __init__(self, *, code: str):
+        super().__init__(modulation=38000, repeat_count=0)
+        self.code = code
+
+    @override
+    def get_raw_timings(self) -> list[int]:
+        """Get the raw timings for the command."""
+        # The code is expected to be an uncompressed Tuya IR code
+        try:
+            pulses = IR.base64_to_pulses(self.code)
+            even = True
+            return [p * -1 if (even := not even) else p for p in pulses]
+        except ValueError as err:
+            raise ValueError(f"Invalid code format: {repr(self.code)}") from err

+ 8 - 16
custom_components/tuya_local/remote.py

@@ -13,7 +13,7 @@ from itertools import product
 from typing import Any
 from typing import Any
 
 
 import voluptuous as vol
 import voluptuous as vol
-from homeassistant.components import persistent_notification
+from homeassistant.components import infrared, persistent_notification
 from homeassistant.components.remote import (
 from homeassistant.components.remote import (
     ATTR_ALTERNATIVE,
     ATTR_ALTERNATIVE,
     ATTR_COMMAND_TYPE,
     ATTR_COMMAND_TYPE,
@@ -21,29 +21,23 @@ from homeassistant.components.remote import (
     ATTR_DEVICE,
     ATTR_DEVICE,
     ATTR_NUM_REPEATS,
     ATTR_NUM_REPEATS,
     DEFAULT_DELAY_SECS,
     DEFAULT_DELAY_SECS,
+    DOMAIN as RM_DOMAIN,
     SERVICE_DELETE_COMMAND,
     SERVICE_DELETE_COMMAND,
     SERVICE_LEARN_COMMAND,
     SERVICE_LEARN_COMMAND,
     SERVICE_SEND_COMMAND,
     SERVICE_SEND_COMMAND,
     RemoteEntity,
     RemoteEntity,
     RemoteEntityFeature,
     RemoteEntityFeature,
 )
 )
-from homeassistant.components.remote import (
-    DOMAIN as RM_DOMAIN,
-)
 from homeassistant.const import ATTR_COMMAND
 from homeassistant.const import ATTR_COMMAND
 from homeassistant.helpers import config_validation as cv
 from homeassistant.helpers import config_validation as cv
 from homeassistant.helpers.storage import Store
 from homeassistant.helpers.storage import Store
 from homeassistant.util import dt as dt_util
 from homeassistant.util import dt as dt_util
 
 
-# from tinytuya.Contrib.IRRemoteControlDevice import (
-#     base64_to_pulses,
-#     pulses_to_pronto,
-#     pulses_to_width_encoded,
-# )
 from .device import TuyaLocalDevice
 from .device import TuyaLocalDevice
 from .entity import TuyaLocalEntity
 from .entity import TuyaLocalEntity
 from .helpers.config import async_tuya_setup_platform
 from .helpers.config import async_tuya_setup_platform
 from .helpers.device_config import TuyaEntityConfig
 from .helpers.device_config import TuyaEntityConfig
+from .infrared import TuyaRemoteCommand
 
 
 _LOGGER = logging.getLogger(__name__)
 _LOGGER = logging.getLogger(__name__)
 
 
@@ -257,7 +251,7 @@ class TuyaLocalRemote(TuyaLocalEntity, RemoteEntity):
         kwargs = SERVICE_SEND_SCHEMA(kwargs)
         kwargs = SERVICE_SEND_SCHEMA(kwargs)
         subdevice = kwargs.get(ATTR_DEVICE)
         subdevice = kwargs.get(ATTR_DEVICE)
         repeat = kwargs.get(ATTR_NUM_REPEATS)
         repeat = kwargs.get(ATTR_NUM_REPEATS)
-        delay = kwargs.get(ATTR_DELAY_SECS, DEFAULT_DELAY_SECS) * 1000
+        delay = kwargs.get(ATTR_DELAY_SECS, DEFAULT_DELAY_SECS)
         service = f"{RM_DOMAIN}.{SERVICE_SEND_COMMAND}"
         service = f"{RM_DOMAIN}.{SERVICE_SEND_COMMAND}"
         if not self._storage_loaded:
         if not self._storage_loaded:
             await self._async_load_storage()
             await self._async_load_storage()
@@ -271,17 +265,18 @@ class TuyaLocalRemote(TuyaLocalEntity, RemoteEntity):
         at_least_one_sent = False
         at_least_one_sent = False
         for _, codes in product(range(repeat), code_list):
         for _, codes in product(range(repeat), code_list):
             if at_least_one_sent:
             if at_least_one_sent:
-                await asyncio.sleep(delay / 1000)  # delay is in ms
+                await asyncio.sleep(delay)
 
 
             if len(codes) > 1:
             if len(codes) > 1:
                 code = codes[self._flags[subdevice]]
                 code = codes[self._flags[subdevice]]
             else:
             else:
                 code = codes[0]
                 code = codes[0]
 
 
+            # Tuya delay is in milliseconds
             if code.startswith("rf:"):
             if code.startswith("rf:"):
-                dps_to_set = self._encode_send_code(code[3:], delay, is_rf=True)
+                dps_to_set = self._encode_send_code(code[3:], delay * 1000, is_rf=True)
             else:
             else:
-                dps_to_set = self._encode_send_code(code, delay)
+                dps_to_set = self._encode_send_code(code, delay * 1000)
             _LOGGER.info(
             _LOGGER.info(
                 "%s sending command %s to %s",
                 "%s sending command %s to %s",
                 self._config.config_id,
                 self._config.config_id,
@@ -314,9 +309,6 @@ class TuyaLocalRemote(TuyaLocalEntity, RemoteEntity):
             for command in commands:
             for command in commands:
                 code = await self._async_learn_command(command, is_rf=is_rf)
                 code = await self._async_learn_command(command, is_rf=is_rf)
                 _LOGGER.info("Learning %s for %s: %s", command, subdevice, code)
                 _LOGGER.info("Learning %s for %s: %s", command, subdevice, code)
-                # pulses = base64_to_pulses(code)
-                # _LOGGER.debug("= pronto code: %s", pulses_to_pronto(pulses))
-                # _LOGGER.debug("= width encoded: %s", pulses_to_width_encoded(pulses))
                 if toggle:
                 if toggle:
                     code = [code, await self._async_learn_command(command, is_rf=is_rf)]
                     code = [code, await self._async_learn_command(command, is_rf=is_rf)]
                 self._codes.setdefault(subdevice, {}).update({command: code})
                 self._codes.setdefault(subdevice, {}).update({command: code})

+ 69 - 0
custom_components/tuya_local/services.py

@@ -0,0 +1,69 @@
+"""Services for Tuya Local integration."""
+
+import voluptuous as vol
+
+from homeassistant.components.remote import (
+    DOMAIN as REMOTE_DOMAIN,
+    ATTR_DELAY_SECS,
+    DEFAULT_DELAY_SECS,
+)
+from homeassistant.core import HomeAssistant, ServiceCall
+from homeassistant.helpers import entity_registry as er, service
+
+from .const import DOMAIN
+from .remote import TuyaLocalRemote
+from .infrared import TuyaRemoteCommand
+
+REMOTE_SEND_IR_COMMAND_SCHEMA = vol.Schema(
+    {
+        vol.Required("emitter_entity_id"): str,
+        vol.Required("code"): str,
+        vol.Optional("device"): str,
+    }
+)
+
+
+async def async_setup_services(hass: HomeAssistant, entities: list[str]):
+    """Set up services for the Tuya Local integration."""
+    if "remote" in entities:
+        service.async_register_platform_entity_service(
+            hass,
+            DOMAIN,
+            "send_learned_ir_command",
+            entity_domain=REMOTE_DOMAIN,
+            schema=REMOTE_SEND_IR_COMMAND_SCHEMA,
+            func=async_handle_send_ir_command,
+        )
+    return True
+
+
+async def async_handle_send_ir_command(entity, call: ServiceCall):
+    """Action to send a saved remote command."""
+    _LOGGER.info("Sending saved remote command: %s", call.data)
+
+    if not isinstance(entity, TuyaLocalRemote):
+        raise ValueError("Entity must be a tuya-local remote")
+    if not entity._storage_loaded:
+        await entity._async_load_storage()
+
+    emitter = call.data.get("emitter")
+    device = call.data.get("device")
+    command = call.data.get("command")
+    delay = call.data.get(ATTR_DELAY_SECS, DEFAULT_DELAY_SECS)
+    entity._extract_codes([command], subdevice=device)  # Validate command and get code
+    at_least_one_sent = False
+    for _, codes in product(range(repeat), code_list):
+        if at_least_one_sent:
+            await asyncio.sleep(delay)
+        if len(codes) > 1:
+            code = codes[entity._flags[subdevice]]
+            self._flags[subdevice] ^= 1
+        else:
+            code = codes[0]
+        if code.startswith("rf:"):
+            _LOGGER.error("RF emitters are not yet supported by this service")
+            continue
+        await infrared.async_send_command(
+            entity.hass, emitter, cmd=TuyaRemoteCommand(code=code)
+        )
+        at_least_one_sent = True

+ 18 - 0
custom_components/tuya_local/services.yaml

@@ -0,0 +1,18 @@
+send_learned_ir_command:
+  target:
+    entity:
+      domain: remote
+      integration: tuya_local
+  fields:
+    command:
+      required: true
+      example: "Play"
+      selector:
+        object:
+    device:
+      example: "TV"
+      selector:
+        text:
+    emitter_entity_id:
+      entity:
+        domain: infrared

+ 20 - 0
custom_components/tuya_local/translations/bg.json

@@ -106,6 +106,26 @@
             "not_supported": "Съжаляваме, няма поддръжка за това устройство, все още"
             "not_supported": "Съжаляваме, няма поддръжка за това устройство, все още"
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "Изпратете научена ИК команда",
+            "description": "Изпраща ИК команда, която е научена от устройството.",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "ИК излъчвател",
+                    "description": "ИК излъчвател, който ще изпрати командата. Ако не е посочено, ще се използва същият ИК излъчвател като за обучението на командата."
+                },
+                "command": {
+                    "name": "Команда",
+                    "description": "Команда, която да изпратите. Това трябва да е команда, която преди това е била научена от интеграцията за това дистанционно управление."
+                },
+                "device": {
+                    "name": "Устройство",
+                    "description": "Устройство, към което да изпратите командата. Това е незадължително и е необходимо само ако командата е запазена с устройство."
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/ca.json

@@ -106,6 +106,26 @@
             "connection": "No s'ha pogut conectar al vostre dispositiu con esos detalles. Podría ser un problema intermitente, o podrían ser incorrectos. Si seleccionó una versión de protocolo específica, intente 'auto' en su lugar."
             "connection": "No s'ha pogut conectar al vostre dispositiu con esos detalles. Podría ser un problema intermitente, o podrían ser incorrectos. Si seleccionó una versión de protocolo específica, intente 'auto' en su lugar."
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "Enviar comando IR aprendido",
+            "description": "Envía un comando IR que ha sido aprendido por un dispositivo Tuya Local compatible.",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "Emisor",
+                    "description": "La entidad del transmisor IR que se utilizará para enviar el comando."
+                },
+                "command": {
+                    "name": "Comando",
+                    "description": "El comando IR a enviar. Este debe ser el mismo comando que fue aprendido por el dispositivo."
+                },
+                "device": {
+                    "name": "Dispositivo",
+                    "description": "El dispositivo IR al que enviar el comando. Esto es opcional y solo se necesita si el comando se guardó con un dispositivo."
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/cz.json

@@ -106,6 +106,26 @@
             "not_supported": "Omlouváme se, toto zažízení není podporováno."
             "not_supported": "Omlouváme se, toto zažízení není podporováno."
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "Odeslat naučený IR příkaz",
+            "description": "Odeslat dálkový příkaz, který byl dříve naučen entitou dálkového ovládání, na jakýkoli infračervený vysílač.",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "Vysílač",
+                    "description": "Entita, která obsahuje naučený IR příkaz, který chcete odeslat."
+                },
+                "command": {
+                    "name": "Příkaz",
+                    "description": "Naučený IR příkaz, který chcete odeslat. Pokud je zadán, bude použit místo příkazu obsaženého ve vysílači."
+                },
+                "device": {
+                    "name": "Zařízení",
+                    "description": "Zařízení, na které chcete příkaz odeslat. Toto je volitelné a je potřeba pouze v případě, že příkaz byl uložen s konkrétním zařízením."
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/de.json

@@ -106,6 +106,26 @@
             "not_supported": "Dieses Gerät wird leider nicht unterstützt."
             "not_supported": "Dieses Gerät wird leider nicht unterstützt."
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "Gelernten IR-Befehl senden",
+            "description": "Senden Sie einen zuvor von der Fernbedienungsentität gelernten Fernbefehl an einen Infrarot-Emitter.",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "Emitter-Entität",
+                    "description": "Die Entität eines Infrarot-Emitters, der den Befehl senden soll."
+                },
+                "command": {
+                    "name": "Befehl",
+                    "description": "Befehl zum Senden. Dies muss ein Befehl sein, der zuvor von der Integration für diese Fernbedienung gelernt wurde."
+                },
+                "device": {
+                    "name": "Gerät",
+                    "description": "Das IR-Gerät, an das der Befehl gesendet werden soll. Dies ist optional und nur erforderlich, wenn der Befehl mit einem Gerät gespeichert wurde."
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/el.json

@@ -106,6 +106,26 @@
             "not_supported": "Λυπούμαστε, αυτή η συσκευή δεν υποστηρίζεται."
             "not_supported": "Λυπούμαστε, αυτή η συσκευή δεν υποστηρίζεται."
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "Αποστολή μαθεμένης εντολής IR",
+            "description": "Αποστολή μιας εντολής τηλεχειριστηρίου που μαθεύτηκε προηγουμένως από την οντότητα του τηλεχειριστηρίου σε οποιοδήποτε εκπομπό υπέρυθρων.",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "Εκπομπός υπέρυθρων",
+                    "description": "Το entity ID της οντότητας υπέρυθρων στην οποία θα αποσταλεί η εντολή."
+                },
+                "command": {
+                    "name": "Εντολή",
+                    "description": "Η εντολή που θα αποσταλεί. Πρέπει να είναι μια εντολή που μαθεύτηκε προηγουμένως από την ενσωμάτωση για αυτό το τηλεχειριστήριο."
+                },
+                "device": {
+                    "name": "Συσκευή",
+                    "description": "Η συσκευή στην οποία θα αποσταλεί η εντολή. Αυτό είναι προαιρετικό και χρειάζεται μόνο αν η εντολή αποθηκεύτηκε με μια συσκευή."
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/en.json

@@ -106,6 +106,26 @@
             "not_supported": "Sorry, there is no support for this device."
             "not_supported": "Sorry, there is no support for this device."
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "Send a learned remote command to infrared",
+            "description": "Send a remote command that was previously learned by the remote entity to any infrared emitter.",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "Emitter",
+                    "description": "The entity ID of the infrared entity to send the command to."
+                },
+                "command": {
+                    "name": "Command",
+                    "description": "The command to send. This must be a command that was previously learned by the integration for this remote."
+                },
+                "device": {
+                    "name": "Device",
+                    "description": "The IR device to send the command to. This is optional and only needed if the command was saved with a device."
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/es.json

@@ -106,6 +106,26 @@
             "not_supported": "Lo sentimos, no hay soporte para este dispositivo."
             "not_supported": "Lo sentimos, no hay soporte para este dispositivo."
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "Enviar comando IR aprendido",
+            "description": "Enviar un comando remoto que fue aprendido previamente por la entidad remota a cualquier emisor infrarrojo.",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "Emisor",
+                    "description": "El ID de entidad de la entidad infrarroja a la que se enviará el comando."
+                },
+                "command": {
+                    "name": "Comando",
+                    "description": "El comando a enviar. Este debe ser un comando que fue aprendido previamente por la integración para este control remoto."
+                },
+                "device": {
+                    "name": "Dispositivo",
+                    "description": "El dispositivo IR al que se enviará el comando. Esto es opcional y solo es necesario si el comando se guardó con un dispositivo."
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/fr.json

@@ -106,6 +106,26 @@
             "not_supported": "Désolé, il n'y a pas de support pour cet appareil."
             "not_supported": "Désolé, il n'y a pas de support pour cet appareil."
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "Envoyer une commande IR apprise",
+            "description": "Envoyer une commande à distance qui a été précédemment apprise par l'entité de télécommande à n'importe quel émetteur infrarouge.",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "Émetteur IR",
+                    "description": "ID de l'entité de l'émetteur infrarouge auquel envoyer la commande."
+                },
+                "command": {
+                    "name": "Commande",
+                    "description": "Commande à envoyer. Il doit s'agir d'une commande qui a été précédemment apprise par l'intégration pour cette télécommande."
+                },
+                "device": {
+                    "name": "Appareil",
+                    "description": "L'appareil IR auquel envoyer la commande. Ceci est optionnel et n'est nécessaire que si la commande a été enregistrée avec un appareil."
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/hu.json

@@ -106,6 +106,26 @@
             "not_supported": "Sajnálom, ez az eszköz nem támogatott."
             "not_supported": "Sajnálom, ez az eszköz nem támogatott."
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "Tanult IR parancs küldése",
+            "description": "Tanult IR parancs küldése egy infravörös emitternek, amelyet korábban a távirányító entitás tanult meg.",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "IR emitter entitás azonosítója",
+                    "description": "Az infravörös entitás azonosítója, amelynek küldeni szeretnéd a parancsot."
+                },
+                "command": {
+                    "name": "Tanult IR parancs",
+                    "description": "Küldendő tanult IR parancs. Ez egy olyan parancs lehet, amelyet korábban a távirányító entitás tanult meg."
+                },
+                "device": {
+                    "name": "Eszköz",
+                    "description": "Az IR eszköz, amelynek küldeni szeretnéd a parancsot. Ez opcionális, és csak akkor szükséges, ha a parancs egy eszközzel lett elmentve."
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/id.json

@@ -106,6 +106,26 @@
             "not_supported": "Maaf, perangkat ini belum didukung."
             "not_supported": "Maaf, perangkat ini belum didukung."
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "Kirim perintah IR yang dipelajari",
+            "description": "Kirim perintah remote yang sebelumnya dipelajari oleh entitas remote ke emitter inframerah mana pun.",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "Pengirim",
+                    "description": "ID entitas dari entitas inframerah untuk mengirim perintah ke."
+                },
+                "command": {
+                    "name": "Perintah",
+                    "description": "Perintah untuk dikirim. Ini harus perintah yang sebelumnya dipelajari oleh integrasi untuk remote ini."
+                },
+                "device": {
+                    "name": "Perangkat",
+                    "description": "Perangkat IR untuk mengirim perintah. Ini opsional dan hanya diperlukan jika perintah disimpan dengan perangkat."
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/it.json

@@ -106,6 +106,26 @@
             "not_supported": "Spiacente, questo dispositivo non è supportato."
             "not_supported": "Spiacente, questo dispositivo non è supportato."
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "Invia comando IR appreso",
+            "description": "Invia un comando remoto che è stato precedentemente appreso dall'entità remota a qualsiasi emettitore a infrarossi.",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "Entità emettitore",
+                    "description": "ID dell'entità dell'emettitore a cui inviare il comando."
+                },
+                "command": {
+                    "name": "Comando",
+                    "description": "Il comando da inviare. Questo deve essere un comando che è stato precedentemente appreso dall'integrazione per questo telecomando."
+                },
+                "device": {
+                    "name": "Dispositivo",
+                    "description": "Il dispositivo IR a cui inviare il comando. Questo è opzionale e necessario solo se il comando è stato salvato con un dispositivo."
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/ja.json

@@ -106,6 +106,26 @@
             "not_supported": "申し訳ございませんが、このデバイスはサポートされていません。"
             "not_supported": "申し訳ございませんが、このデバイスはサポートされていません。"
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "学習した赤外線コマンドを送信",
+            "description": "以前にリモートエンティティによって学習されたリモートコマンドを、任意の赤外線エミッタに送信します。",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "赤外線エミッタ",
+                    "description": "コマンドを送信する赤外線エンティティのエンティティID。"
+                },
+                "command": {
+                    "name": "コマンド",
+                    "description": "送信するコマンド。これは、このリモートの統合によって以前に学習されたコマンドでなければなりません。"
+                },
+                "device": {
+                    "name": "デバイス",
+                    "description": "コマンドを送信するIRデバイス。これはオプションで、コマンドがデバイスとともに保存された場合にのみ必要です。"
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/no-NB.json

@@ -106,6 +106,26 @@
             "not_supported": "Beklager, det er ingen støtte for denne enheten"
             "not_supported": "Beklager, det er ingen støtte for denne enheten"
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "Send lært IR-fjernkontrollsignal",
+            "description": "Send en fjernkontrollkommando som tidligere ble lært av fjernkontrollentiteten til en hvilken som helst infrarød sender.",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "IR sender",
+                    "description": "Enhet-ID for den infrarøde enheten som skal sende kommandoen til."
+                },
+                "command": {
+                    "name": "Kommando",
+                    "description": "Kommandot som skal sendes. Dette må være en kommando som tidligere ble lært av integrasjonen for denne fjernkontrollen."
+                },
+                "device": {
+                    "name": "Enhet",
+                    "description": "Den IR-enheten som kommandoen skal sendes til. Dette er valgfritt og bare nödvendig om kommandoen sparades med en enhet."
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/pl.json

@@ -106,6 +106,26 @@
             "not_supported": "Przepraszam, to urządzenie nie jest wspierane."
             "not_supported": "Przepraszam, to urządzenie nie jest wspierane."
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "Wyślij nauczoną komendę IR",
+            "description": "Wyślij zdalną komendę, która została wcześniej nauczona przez encję pilota do dowolnego emitera podczerwieni.",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "Emiter",
+                    "description": "ID encji emitera podczerwieni, do którego zostanie wysłana komenda."
+                },
+                "command": {
+                    "name": "Komenda",
+                    "description": "Komenda do wysłania. Musi to być komenda, która została wcześniej nauczona przez integrację dla tego pilota."
+                },
+                "device": {
+                    "name": "Urządzenie",
+                    "description": "Urządzenie do którego zostanie wysłana komenda. Jest to opcjonalne i jest potrzebne tylko jeśli komenda została zapisana z urządzeniem."
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/pt-BR.json

@@ -106,6 +106,26 @@
             "not_supported": "Desculpe, não há suporte para este dispositivo."
             "not_supported": "Desculpe, não há suporte para este dispositivo."
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "Enviar comando IR aprendido",
+            "description": "Enviar um comando remoto que foi aprendido anteriormente pela entidade remota para qualquer emissor infravermelho.",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "Emissor infravermelho",
+                    "description": "O ID da entidade da entidade infravermelha para a qual enviar o comando."
+                },
+                "command": {
+                    "name": "Comando remoto",
+                    "description": "Comando a ser enviado. Este deve ser um comando que foi aprendido anteriormente pela integração para este controle remoto."
+                },
+                "device": {
+                    "name": "Dispositivo",
+                    "description": "O dispositivo IR para o qual enviar o comando. Isso é opcional e só é necessário se o comando foi salvo com um dispositivo."
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/pt-PT.json

@@ -106,6 +106,26 @@
             "not_supported": "Desculpe, não há suporte para este dispositivo."
             "not_supported": "Desculpe, não há suporte para este dispositivo."
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "Enviar comando IR aprendido",
+            "description": "Enviar um comando remoto que foi aprendido anteriormente pela entidade remota para qualquer emissor infravermelho.",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "Entidade do dispositivo de controle remoto",
+                    "description": "O ID da entidade da entidade infravermelha para a qual enviar o comando."
+                },
+                "command": {
+                    "name": "Comando IR",
+                    "description": "Comando a ser enviado. Este deve ser um comando que foi aprendido anteriormente pela integração para este controle remoto."
+                },
+                "device": {
+                    "name": "Dispositivo",
+                    "description": "Dispositivo para o qual enviar o comando. Isso é opcional e só é necessário se o comando foi salvo com um dispositivo."
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/ro.json

@@ -106,6 +106,26 @@
             "not_supported": "Ne pare rău, acest dispozitiv nu este suportat."
             "not_supported": "Ne pare rău, acest dispozitiv nu este suportat."
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "Trimite comandă IR învățată",
+            "description": "Trimite o comandă de telecomandă care a fost învățată anterior de entitatea de telecomandă către orice emițător infraroșu.",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "Entitate",
+                    "description": "ID-ul entității entității infraroșii către care se va trimite comanda."
+                },
+                "command": {
+                    "name": "Comandă",
+                    "description": "Comanda de trimis. Aceasta trebuie să fie o comandă care a fost învățată anterior de integrare pentru această telecomandă."
+                },
+                "device": {
+                    "name": "Dispozitiv",
+                    "description": "Dispozitivul IR către care se va trimite comanda. Acesta este opțional și este necesar doar dacă comanda a fost salvată cu un dispozitiv."
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/ru.json

@@ -106,6 +106,26 @@
             "not_supported": "К сожалению, это устройство не поддерживается."
             "not_supported": "К сожалению, это устройство не поддерживается."
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "Отправить изученную ИК-команду",
+            "description": "Отправить удаленную команду, которая была ранее изучена сущностью пульта дистанционного управления, любому ИК-излучателю.",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "ИК-излучатель",
+                    "description": "Идентификатор сущности ИК-излучателя, которому будет отправлена команда."
+                },
+                "command": {
+                    "name": "Команда",
+                    "description": "Команда для отправки. Это должна быть команда, которая была ранее изучена интеграцией для этого пульта дистанционного управления."
+                },
+                "device": {
+                    "name": "Устройство",
+                    "description": "ИК-устройство, которому будет отправлена команда. Это необязательно и требуется только в том случае, если команда была сохранена с устройством."
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/sv.json

@@ -106,6 +106,26 @@
             "not_supported": "Tyvärr finns det inget stöd för denna enhet."
             "not_supported": "Tyvärr finns det inget stöd för denna enhet."
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "Skicka inlärd IR-kommandotjänst",
+            "description": "Skicka ett fjärrkommando som tidigare lärts in av fjärrenheten till vilken infraröd sändare som helst.",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "Sändarens enhets-ID",
+                    "description": "Enhets-ID för den infraröda enheten som kommandot ska skickas till."
+                },
+                "command": {
+                    "name": "Kommandot",
+                    "description": "Kommandot som ska skickas. Detta måste vara ett kommando som tidigare lärts in av integrationen för denna fjärrkontroll."
+                },
+                "device": {
+                    "name": "Enhet",
+                    "description": "Den IR-enhet som kommandot ska skickas till. Detta är valfritt och endast nödvändigt om kommandot sparades med en enhet." 
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/uk.json

@@ -106,6 +106,26 @@
             "not_supported": "На жаль, цей пристрій не підтримується."
             "not_supported": "На жаль, цей пристрій не підтримується."
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "Надіслати изученную ИК-команду",
+            "description": "Отправьте удаленную команду, которая была ранее изучена сущностью пульта дистанционного управления, любому ИК-излучателю.",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "ИК-излучатель",
+                    "description": "Сущность ИК-излучателя, которому будет отправлена команда."
+                },
+                "command": {
+                    "name": "Команда",
+                    "description": "Команда для отправки. Это должна быть команда, которая была ранее изучена интеграцией для этого пульта дистанционного управления."
+                },
+                "device": {
+                    "name": "Устройство",
+                    "description": "Пристрій, якому будет отправлена команда. Это необязательно и нужно только в том случае, если команда была сохранена с устройством."
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/ur.json

@@ -107,6 +107,26 @@
             "not_supported": "معذرت، اس آلہ کے لیے کوئی تعاون نہیں ہے۔."
             "not_supported": "معذرت، اس آلہ کے لیے کوئی تعاون نہیں ہے۔."
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "سیکھے ہوئے IR کمانڈ بھیجیں",
+            "description": "پہلے سے سیکھے ہوئے ریموٹ کمانڈ کو کسی بھی انفرا ریڈ ایمیٹر پر بھیجیں۔",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "ایمیٹر آلہ",
+                    "description": "کمانڈ بھیجنے کے لیے انفرا ریڈ آلہ کی entity ID۔"
+                },
+                "command": {
+                    "name": "کمانڈ",
+                    "description": "بھیجنے کے لیے کمانڈ۔ یہ ایک ایسی کمانڈ ہونی چاہیے جسے اس ریموٹ کے لیے انضمام نے پہلے سیکھا ہو۔"
+                },
+                "device": {
+                    "name": "ڈیوائس",
+                    "description": "کمانڈ بھیجنے کے لیے IR ڈیوائس۔ یہ اختیاری ہے اور صرف اس صورت میں ضروری ہے جب کمانڈ کو کسی ڈیوائس کے ساتھ محفوظ کیا گیا ہو۔"
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/zh-Hans.json

@@ -106,6 +106,26 @@
             "not_supported": "抱歉,不支持此设备。"
             "not_supported": "抱歉,不支持此设备。"
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "发送学习到的红外命令",
+            "description": "发送之前由遥控器实体学习的远程命令到任何红外发射器。",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "发射器",
+                    "description": "要发送命令的红外实体的实体ID。"
+                },
+                "command": {
+                    "name": "命令",
+                    "description": "要发送的命令。必须是之前由此集成为此遥控器学习的命令。" 
+                },
+                "device": {
+                    "name": "设备",
+                    "description": "要发送命令的红外设备。这是可选的,仅当命令与设备一起保存时才需要。"
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 20 - 0
custom_components/tuya_local/translations/zh-Hant.json

@@ -106,6 +106,26 @@
             "not_supported": "抱歉,不支援此設備。"
             "not_supported": "抱歉,不支援此設備。"
         }
         }
     },
     },
+    "services": {
+        "send_learned_ir_command": {
+            "name": "發送學習到的紅外線命令",
+            "description": "將先前由遙控實體學習到的遠程命令發送到任何紅外線發射器。",
+            "fields": {
+                "emitter_entity_id": {
+                    "name": "發射器實體ID",
+                    "description": "要發送命令的紅外線實體ID。"
+                },
+                "command": {
+                    "name": "命令",
+                    "description": "要發送的命令。這必須是之前由此遙控器的集成學習到的命令。"
+                },
+                "device": {
+                    "name": "設備",
+                    "description": "要發送命令的紅外線設備。這是可選的,僅在命令保存時帶有設備时需要。" 
+                }
+            }
+        }
+    },
     "entity": {
     "entity": {
         "binary_sensor": {
         "binary_sensor": {
             "direction": {
             "direction": {

+ 1 - 1
hacs.json

@@ -1,5 +1,5 @@
 {
 {
     "name": "Tuya Local",
     "name": "Tuya Local",
-    "homeassistant": "2026.4.0",
+    "homeassistant": "2026.5.0",
     "hacs": "2.0.0"
     "hacs": "2.0.0"
 }
 }

+ 2 - 2
pyproject.toml

@@ -28,10 +28,10 @@ entities = "util.entities:main"
 [dependency-groups]
 [dependency-groups]
 dev = [
 dev = [
   "fuzzywuzzy",
   "fuzzywuzzy",
-  "infrared-protocols~=1.1",
+  "infrared-protocols~=2.0",
   "levenshtein",
   "levenshtein",
   "PyTurboJPEG~=1.8.0",
   "PyTurboJPEG~=1.8.0",
-  "pytest-homeassistant-custom-component==0.13.322",
+  "pytest-homeassistant-custom-component==0.13.329",
   "pytest",
   "pytest",
   "pytest-asyncio",
   "pytest-asyncio",
   "pytest-cov",
   "pytest-cov",