A while back, I tested and reviewed a Zigbee infrared blaster from the Tuya ecosystem, model ZS06. That particular model uses USB-C for power and integrates well with Zigbee2MQTT and ZHA in Home Assistant. It’s battery-powered counterpart, the Moes UFO-R11, does the same thing but uses 2xAAA batteries instead.

In this article, I’m testing a new Tuya Zigbee Infrared Blaster, model ZS05. This model also uses two AAA batteries for power, but is significantly smaller and much easier to hide out of sight. I bought this ZS05 IR remote control on AliExpress for less than $15.
Technical Specification
- Name: Tuya Zigbee IR Blaster
- Model: ZS05
- Connectivity: Zigbee 3.0, Infrared
- Infrared Frequency: 38KHz
- Infrared Range: ≤ 12 meters
- Battery Type: 2xAAA or 2xLR03
- Dimensions: 73x25x20mm
- Price: $14.20
Device Disassembly
The Tuya Zigbee Infrared Blaster ships in a small box containing the device, a user manual, a sticker for installation and a small lanyard for hanging the device. The device is very small and reminds me of the SwitchBot Outdoor Thermo-Hygrometer in shape and size.

This Zigbee IR remote is available in black and white color. There’s a small LED indicator on the face of the device, which blinks faintly whenever IR commands are learned or sent. On the backside, some basic information is printed, including the IR frequency (38KHz).


The device uses a set of AAA or LR03 batteries for power, which are not included in the package and you need to supply them yourself. These should last a very long time, considering the device is sleeping and not really doing anything when not blasting infrared codes. Under the battery cover, there is a small pairing button that you need to press and hold in order to pair the device to your coordinator.

The Tuya ZS05 Zigbee IR Remote uses the Tuya ZS3L [Datasheet] as it’s main Zigbee connectivity module. This chip is actually based on the latest gen EFR32MG21 [Datasheet] SoC by Silicon Labs and is embedded with a 32-bit low-power Arm Cortex-M33 core, 768-KB flash memory and 64-KB RAM.
I’ve encountered this SoC inside several devices, including the Zemismart 4-Button Scene Controller, the Moes Star Ring Scene Switch and this round-shaped Zigbee thermostat. It’s a very solid chip which operates perfectly fine in a mesh network.
The board also features several infrared LED diodes (6 to be exact), arranged to provide broad coverage in all directions. These are responsible for emitting the actual IR light pulses that control your appliances. The small chip labeled “BoyMicro 25032ESTIG” is likely a serial flash memory chip used to store the libraries of infrared codes for different devices, irrelevant when used with Zigbee2MQTT and ZHA in Home Assistant.

Home Assistant Integration
The Tuya ZS05 IR Remote Control is compatible with Zigbee2MQTT out of the box, but requires a custom quirk for ZHA. Since there is no official support in ZHA for this device, I managed to recycle and create a custom quirk that works great, but does require some more steps.
Zigbee2MQTT

Once paired to Zigbee2MQTT, the device is correctly identified as model ZS05 by manufacturer WMUN. Now, who or what WMUN is have absolutely no idea. Nor does it matter, since this device comes from the Tuya ecosystem and operates well in Zigbee2MQTT. It has a unique identifier set as _TZ3290_u9xac5rv with Zigbee model TS1201. It exposes the following entities:

The learn IR code toggle is a switch that enables the device to listen for new IR codes. Once it learns a new one, it populates the learned IR code field. You can copy paste this code in the field below, to send and test it. Remaining battery life is reported as a percentage in Zigbee2MQTT.
ZHA

To make the ZS05 work in ZHA, a custom quirk needs to be applied. I added the device signature to the ZS06 quirk and cleaned up the code a bit, so it works properly. Here’s how to enable the quirk before pairing the device:
- Create a directory in Home Assistant
config/custom_zha_quirks
- Add the path to yourÂ
configuration.yaml
 file
zha:
custom_quirks_path: /config/custom_zha_quirks/
- Create a file in this directory and name it
ts1201.py
- Open the file with a code editor and paste the contents from the custom quirk
- Save and restart Home Assistant
Tuya Zigbee ZS05 IR Remote Control ZHA Custom Quirk
"""Tuya TS1201 IR blaster.
Heavily inspired by work from @mak-42
https://github.com/Koenkk/zigbee-herdsman-converters/blob/9d5e7b902479582581615cbfac3148d66d4c675c/lib/zosung.js
"""
import base64
import logging
from typing import Any, Final, Optional, Union
from zigpy.profiles import zgp, zha
from zigpy.quirks import CustomCluster, CustomDevice
import zigpy.types as t
from zigpy.zcl import BaseAttributeDefs, BaseCommandDefs, foundation
from zigpy.zcl.clusters.general import (
Basic,
GreenPowerProxy,
Groups,
Identify,
OnOff,
Ota,
PowerConfiguration,
Scenes,
Time,
)
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
)
_LOGGER = logging.getLogger(__name__)
class Bytes(bytes):
"""Bytes serializable class."""
def serialize(self):
"""Serialize Bytes."""
return self
@classmethod
def deserialize(cls, data):
"""Deserialize Bytes."""
return cls(data), b""
class ZosungIRControl(CustomCluster):
"""Zosung IR Control Cluster (0xE004)."""
name = "Zosung IR Control Cluster"
cluster_id = 0xE004
ep_attribute = "zosung_ircontrol"
class AttributeDefs(BaseAttributeDefs):
"""Attribute definitions."""
last_learned_ir_code: Final = foundation.ZCLAttributeDef(
id=0x0000, type=t.CharacterString, access="r", mandatory=True
)
class ServerCommandDefs(BaseCommandDefs):
"""Server command definitions."""
data: Final = foundation.ZCLCommandDef(
id=0x00,
schema={"data": Bytes},
direction=foundation.Direction.Server_to_Client,
is_manufacturer_specific=True,
)
IRLearn: Final = foundation.ZCLCommandDef(
id=0x01,
schema={"on_off": t.Bool},
direction=foundation.Direction.Server_to_Client,
is_manufacturer_specific=True,
)
IRSend: Final = foundation.ZCLCommandDef(
id=0x02,
schema={"code": t.CharacterString},
direction=foundation.Direction.Server_to_Client,
is_manufacturer_specific=True,
)
async def read_attributes(
self, attributes, allow_cache=False, only_cache=False, manufacturer=None
):
"""Read attributes ZCL foundation command."""
if (
self.AttributeDefs.last_learned_ir_code.id in attributes
or "last_learned_ir_code" in attributes
):
return {0: self.endpoint.device.last_learned_ir_code}, {}
else:
return {}, {0: foundation.Status.UNSUPPORTED_ATTRIBUTE}
async def command(
self,
command_id: Union[foundation.GeneralCommand, int, t.uint8_t],
*args,
manufacturer: Optional[Union[int, t.uint16_t]] = None,
expect_reply: bool = True,
tsn: Optional[Union[int, t.uint8_t]] = None,
**kwargs: Any,
):
"""Override the default cluster command."""
if command_id == self.ServerCommandDefs.IRLearn.id:
if kwargs["on_off"]:
cmd_args = {Bytes(b'{"study":0}')}
else:
cmd_args = {Bytes(b'{"study":1}')}
return await super().command(
0x00,
*cmd_args,
manufacturer=manufacturer,
expect_reply=True,
tsn=tsn,
)
elif command_id == self.ServerCommandDefs.IRSend.id:
ir_msg = (
f'{{"key_num":1,"delay":300,"key1":'
f'{{"num":1,"freq":38000,"type":1,"key_code":"{kwargs["code"]}"}}}}'
)
_LOGGER.debug(
"Sending IR code: %s to %s", ir_msg, self.endpoint.device.ieee
)
seq = self.endpoint.device.next_seq()
self.endpoint.device.ir_msg_to_send = {seq: ir_msg}
self.create_catching_task(
self.endpoint.zosung_irtransmit.command(
0x00,
seq=seq,
length=len(ir_msg),
unk1=0x00000000,
clusterid=0xE004,
unk2=0x01,
cmd=0x02,
unk3=0x0000,
expect_reply=False,
tsn=tsn,
)
)
else:
return await super().command(
command_id,
*args,
manufacturer=manufacturer,
expect_reply=expect_reply,
tsn=tsn,
)
class ZosungIRTransmit(CustomCluster):
"""Zosung IR Transmit Cluster (0xED00)."""
name = "Zosung IR Transmit Cluster"
cluster_id = 0xED00
ep_attribute = "zosung_irtransmit"
current_position = 0
msg_length = 0
ir_msg = []
class ServerCommandDefs(BaseCommandDefs):
"""Server command definitions."""
receive_ir_frame_00: Final = foundation.ZCLCommandDef(
id=0x00,
schema={
"seq": t.uint16_t,
"length": t.uint32_t,
"unk1": t.uint32_t,
"clusterid": t.uint16_t,
"unk2": t.uint8_t,
"cmd": t.uint8_t,
"unk3": t.uint16_t,
},
direction=foundation.Direction.Server_to_Client,
is_manufacturer_specific=True,
)
receive_ir_frame_01: Final = foundation.ZCLCommandDef(
id=0x01,
schema={
"zero": t.uint8_t,
"seq": t.uint16_t,
"length": t.uint32_t,
"unk1": t.uint32_t,
"clusterid": t.uint16_t,
"unk2": t.uint8_t,
"cmd": t.uint8_t,
"unk3": t.uint16_t,
},
direction=foundation.Direction.Server_to_Client,
is_manufacturer_specific=True,
)
receive_ir_frame_02: Final = foundation.ZCLCommandDef(
id=0x02,
schema={
"seq": t.uint16_t,
"position": t.uint32_t,
"maxlen": t.uint8_t,
},
direction=foundation.Direction.Server_to_Client,
is_manufacturer_specific=True,
)
receive_ir_frame_03: Final = foundation.ZCLCommandDef(
id=0x03,
schema={
"zero": t.uint8_t,
"seq": t.uint16_t,
"position": t.uint32_t,
"msgpart": t.LVBytes,
"msgpartcrc": t.uint8_t,
},
direction=foundation.Direction.Client_to_Server,
is_manufacturer_specific=False,
)
receive_ir_frame_04: Final = foundation.ZCLCommandDef(
id=0x04,
schema={
"zero0": t.uint8_t,
"seq": t.uint16_t,
"zero1": t.uint16_t,
},
direction=foundation.Direction.Server_to_Client,
is_manufacturer_specific=True,
)
receive_ir_frame_05: Final = foundation.ZCLCommandDef(
id=0x05,
schema={
"seq": t.uint16_t,
"zero": t.uint16_t,
},
direction=foundation.Direction.Server_to_Client,
is_manufacturer_specific=True,
)
class ClientCommandDefs(BaseCommandDefs):
"""Client command definitions."""
resp_ir_frame_03: Final = foundation.ZCLCommandDef(
id=0x03,
schema={
"zero": t.uint8_t,
"seq": t.uint16_t,
"position": t.uint32_t,
"msgpart": t.LVBytes,
"msgpartcrc": t.uint8_t,
},
direction=foundation.Direction.Client_to_Server,
is_manufacturer_specific=False,
)
resp_ir_frame_05: Final = foundation.ZCLCommandDef(
id=0x05,
schema={
"seq": t.uint16_t,
"zero": t.uint16_t,
},
direction=foundation.Direction.Server_to_Client,
is_manufacturer_specific=True,
)
def handle_cluster_request(
self,
hdr: foundation.ZCLHeader,
args: list[Any],
*,
dst_addressing: Optional[
Union[t.Addressing.Group, t.Addressing.IEEE, t.Addressing.NWK]
] = None,
):
"""Handle a cluster request."""
# send default response to avoid repeated zcl frame from device
if not hdr.frame_control.disable_default_response:
_LOGGER.debug("Sending default response to %s", self.endpoint.device.ieee)
self.send_default_rsp(hdr, status=foundation.Status.SUCCESS)
if hdr.command_id == self.ServerCommandDefs.receive_ir_frame_00.id:
_LOGGER.debug("Received IR frame 0x00 from %s", self.endpoint.device.ieee)
self.current_position = 0
self.ir_msg.clear()
self.msg_length = args.length
cmd_01_args = {
"zero": 0,
"seq": args.seq,
"length": args.length,
"unk1": args.unk1,
"clusterid": args.clusterid,
"unk2": args.unk2,
"cmd": args.cmd,
"unk3": args.unk3,
}
self.create_catching_task(
super().command(0x01, **cmd_01_args, expect_reply=True)
)
cmd_02_args = {"seq": args.seq, "position": 0, "maxlen": 0x38}
self.create_catching_task(
super().command(0x02, **cmd_02_args, expect_reply=True)
)
elif hdr.command_id == self.ServerCommandDefs.receive_ir_frame_01.id:
_LOGGER.debug(
"IR-Message-Code01 received, sequence: %s, from %s",
args.seq,
self.endpoint.device.ieee,
)
_LOGGER.debug(
"Message to send: %s, to %s",
self.endpoint.device.ir_msg_to_send[args.seq],
self.endpoint.device.ieee,
)
elif hdr.command_id == self.ServerCommandDefs.receive_ir_frame_02.id:
position = args.position
seq = args.seq
maxlen = args.maxlen
irmsg = self.endpoint.device.ir_msg_to_send[seq]
msgpart = irmsg[position : position + maxlen]
calculated_crc = 0
for x in msgpart:
calculated_crc = (calculated_crc + ord(x)) % 0x100
_LOGGER.debug(
"Received IR frame 0x02 from %s, msgsrc: %s, position: %s, msgpart: %s",
self.endpoint.device.ieee,
calculated_crc,
position,
msgpart,
)
cmd_03_args = {
"zero": 0,
"seq": seq,
"position": position,
"msgpart": msgpart.encode("utf-8"),
"msgpartcrc": calculated_crc,
}
self.create_catching_task(
super().command(0x03, **cmd_03_args, expect_reply=True)
)
elif hdr.command_id == self.ServerCommandDefs.receive_ir_frame_03.id:
msg_part_crc = args.msgpartcrc
calculated_crc = 0
for x in args.msgpart:
calculated_crc = (calculated_crc + x) % 0x100
_LOGGER.debug(
"Received IR frame 0x03 from %s, msgcrc: %s, "
"calculated_crc: %s, position: %s",
self.endpoint.device.ieee,
msg_part_crc,
calculated_crc,
args.position,
)
self.ir_msg[args.position :] = args.msgpart
if args.position + len(args.msgpart) < self.msg_length:
cmd_02_args = {
"seq": args.seq,
"position": args.position + len(args.msgpart),
"maxlen": 0x38,
}
self.create_catching_task(
super().command(0x02, **cmd_02_args, expect_reply=False)
)
else:
_LOGGER.debug(
"IR message completely received from %s", self.endpoint.device.ieee
)
cmd_04_args = {"zero0": 0, "seq": args.seq, "zero1": 0}
self.create_catching_task(
super().command(0x04, **cmd_04_args, expect_reply=False)
)
elif hdr.command_id == self.ServerCommandDefs.receive_ir_frame_04.id:
seq = args.seq
_LOGGER.debug(
"IR code has been sent to %s (seq:%s)", self.endpoint.device.ieee, seq
)
cmd_05_args = {"seq": seq, "zero": 0}
self.create_catching_task(
super().command(0x05, **cmd_05_args, expect_reply=False)
)
elif hdr.command_id == self.ServerCommandDefs.receive_ir_frame_05.id:
self.endpoint.device.last_learned_ir_code = base64.b64encode(
bytes(self.ir_msg)
).decode()
_LOGGER.info(
"IR message really totally received: %s, from %s",
self.endpoint.device.last_learned_ir_code,
self.endpoint.device.ieee,
)
_LOGGER.debug(
"Stopping learning mode on device %s", self.endpoint.device.ieee
)
self.create_catching_task(
self.endpoint.zosung_ircontrol.command(
0x01, on_off=False, expect_reply=False
)
)
else:
_LOGGER.debug(
"Unhandled command: %s, from %s",
hdr.command_id,
self.endpoint.device.ieee,
)
class ZosungIRBlaster(CustomDevice):
"""Zosung IR Blaster."""
seq = -1
ir_msg_to_send = {}
last_learned_ir_code = t.CharacterString("")
def __init__(self, *args, **kwargs):
"""Init device."""
self.seq = 0
super().__init__(*args, **kwargs)
def next_seq(self):
"""Next local sequence."""
self.seq = (self.seq + 1) % 0x10000
return self.seq
signature = {
# "node_descriptor": "NodeDescriptor(logical_type=<LogicalType.EndDevice: 2>,
# complex_descriptor_available=0, user_descriptor_available=0, reserved=0,
# aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>,
# mac_capability_flags=<MACCapabilityFlags.AllocateAddress: 128>,
# manufacturer_code=4098, maximum_buffer_size=82,
# maximum_incoming_transfer_size=82, server_mask=11264,
# maximum_outgoing_transfer_size=82,
# descriptor_capability_field=<DescriptorCapability.NONE: 0>,
# *allocate_address=True, *is_alternate_pan_coordinator=False,
# *is_coordinator=False, *is_end_device=True, *is_full_function_device=False,
# *is_mains_powered=False, *is_receiver_on_when_idle=False, *is_router=False,
# *is_security_capable=False)",
# input_clusters=[0x0000, 0x0001, 0x0003, 0x0004, 0x0005, 0x0006,
# 0xe004, 0xed00]
# output_clusters=[0x000a, 0x0019]
# <SimpleDescriptor endpoint=1, profile=260, device_type=61440
# device_version=1
# input_clusters=[0, 1, 3, 4, 5, 6, 57348, 60672]
# output_clusters=[10, 25]>
MODELS_INFO: [
("_TZ3290_ot6ewjvmejq5ekhl", "TS1201"),
("_TZ3290_j37rooaxrcdcqo5n", "TS1201"),
("_TZ3290_u9xac5rv", "TS1201"),
],
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: 0xF000,
INPUT_CLUSTERS: [
Basic.cluster_id,
ZosungIRTransmit.cluster_id,
ZosungIRControl.cluster_id,
Groups.cluster_id,
Identify.cluster_id,
OnOff.cluster_id,
PowerConfiguration.cluster_id,
Scenes.cluster_id,
],
OUTPUT_CLUSTERS: [
Time.cluster_id,
Ota.cluster_id,
],
},
},
}
replacement = {
ENDPOINTS: {
1: {
INPUT_CLUSTERS: [
Basic.cluster_id,
ZosungIRTransmit,
ZosungIRControl,
Groups.cluster_id,
Identify.cluster_id,
OnOff.cluster_id,
PowerConfiguration.cluster_id,
Scenes.cluster_id,
],
OUTPUT_CLUSTERS: [
Time.cluster_id,
Ota.cluster_id,
],
},
},
}
class ZosungIRBlaster_ZS06(ZosungIRBlaster):
"""Zosung IR Blaster ZS06."""
signature = {
# <SimpleDescriptor endpoint=1, profile=260, device_type=61440
# device_version=1
# input_clusters=[0, 3, 4, 5, 6, 57348, 60672]
# output_clusters=[10, 25]>
# <SimpleDescriptor endpoint=242, profile=41440, device_type=97
# device_version=1
# input_clusters=[]
# output_clusters=[33]>
MODELS_INFO: [
("_TZ3290_7v1k4vufotpowp9z", "TS1201"),
("_TZ3290_acv1iuslxi3shaaj", "TS1201"),
("_TZ3290_gnl5a6a5xvql7c2a", "TS1201"),
],
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: 0xF000,
INPUT_CLUSTERS: [
Basic.cluster_id,
ZosungIRTransmit.cluster_id,
ZosungIRControl.cluster_id,
Groups.cluster_id,
Identify.cluster_id,
OnOff.cluster_id,
Scenes.cluster_id,
],
OUTPUT_CLUSTERS: [
Time.cluster_id,
Ota.cluster_id,
],
},
242: {
PROFILE_ID: zgp.PROFILE_ID,
DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [
GreenPowerProxy.cluster_id,
],
},
},
}
replacement = {
ENDPOINTS: {
1: {
INPUT_CLUSTERS: [
Basic.cluster_id,
ZosungIRTransmit,
ZosungIRControl,
Groups.cluster_id,
Identify.cluster_id,
OnOff.cluster_id,
Scenes.cluster_id,
],
OUTPUT_CLUSTERS: [
Time.cluster_id,
Ota.cluster_id,
],
},
242: {
PROFILE_ID: zgp.PROFILE_ID,
DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [
GreenPowerProxy.cluster_id,
],
},
},
}
Once Home Assistant reboots, pair the device to your coordinator. You can confirm the quirk is applied by expanding the Zigbee info arrow and checking the Quirk field, it should say ts1201.ZosungIRBlaster
. The device can now be used with ZHA in Home Assistant. Now, to learn codes from your IR remotes and send them, follow this procedure step by step:

- Click the three dots menu
- Click Manage Zigbee Device
- Under
Clusters
, selectZosungIRControl
- Click
Commands
and selectIRLearn
from the list - Set
on_off
totrue
and clickIssue Zigbee Command
- The device should turn on the blue indicator
- Press a button on your remote to learn the IR code
- The device should turn off the blue indicator
- Click
Attributes
and selectlast_learned_ir_code
- Click
Read Attribute
and theValue
field will populate with your code - Copy the code and select
Commands
again - Select
IRSend
from the menu and paste the code in thecode
field - Click
Issue Zigbee Command
- Done!
Creating Automations, Scripts and Switches
Once the device was up and running in my Zigbee2MQTT instance, I tested some AC control codes and created a few scripts and automations for controlling my air conditioner. The procedure is quite simple: enable the Learn IR code toggle, aim the remote towards the device and press the button you want to learn. With the captured code, create a script or automation sending it on demand.
For example, here’s my code for turning on my AC:
alias: "AC Remote: Turn ON"
sequence:
- service: mqtt.publish
data:
topic: zigbee2mqtt/Tuya ZS05 Zigbee IR Blaster/set
payload: '{"ir_code_to_send": "DZoRmhGEAioGRgLrAUYCQAdAA0ALAesB4AEXA+sBRgLgAReAC4APgCPAC+ABDwOEAioGgBdAB+ABF0AL4A0DCSoGhALrAYQCKgZAC4ADQA8D6wFGAkAPA+sBhAKAB0ALgANAD0ADAZoRQAEBRgJAC0AbQAdAAwLrAYQgAwFGAoALAUYCQAPgBwvAD0AHQANAI0ADQAvgBwPgAxdAC+ALA0AzQBdAB8ADQA9AA0AP4AMHwAFAF0ADB2oUmhGaEUYCQAtAAwNGAusBgAcBRgLgAQcD6wFGAoABgAsB6wHAB0ALAUYCQAFAB8ADQAHAC+APB0AB4A8b4AkXQG/gCwMHRgJGAioG6wE="}'
mode: single
Under the topic variable, the Tuya ZS05 Zigbee IR Blaster
is the exact device name from Zigbee2MQTT. Make sure this matches your naming before you send the code, otherwise you will get an error. If you get an invalid message error in Zigbee2MQTT, double check the quotation and brackets as well as the correct ir_code_to_send
payload. This same code can also be send through automations:
alias: AC Turn at 7 AM
description: AC Turn at 7 AM with ZS05 IR Blaster
triggers:
- trigger: time
at: "00:07:00"
conditions: []
actions:
- action: mqtt.publish
data:
topic: zigbee2mqtt/Tuya ZS05 Zigbee IR Blaster/set
payload: >-
{"ir_code_to_send":
"DZoRmhGEAioGRgLrAUYCQAdAA0ALAesB4AEXA+sBRgLgAReAC4APgCPAC+ABDwOEAioGgBdAB+ABF0AL4A0DCSoGhALrAYQCKgZAC4ADQA8D6wFGAkAPA+sBhAKAB0ALgANAD0ADAZoRQAEBRgJAC0AbQAdAAwLrAYQgAwFGAoALAUYCQAPgBwvAD0AHQANAI0ADQAvgBwPgAxdAC+ALA0AzQBdAB8ADQA9AA0AP4AMHwAFAF0ADB2oUmhGaEUYCQAtAAwNGAusBgAcBRgLgAQcD6wFGAoABgAsB6wHAB0ALAUYCQAFAB8ADQAHAC+APB0AB4A8b4AkXQG/gCwMHRgJGAioG6wE="}
mode: single
You can also create a template switch in Home Assistant, that would execute each script and send the correct code with a simple toggle. This is the easiest way to simply automate an AC for ON and OFF control. To do this, navigate to Settings > Devices & Services > Helpers > + Create Helper. Look for the Template helper and choose Template a switch. Copy paste the captured codes, give it a name and click Submit.

Summary and Buying Links
This Tuya ZS05 Infrared Remote Control is a smaller, rectangular shaped version of the popular ZS06 IR Blaster. Both work well and are easy to use in Home Assistant via Zigbee2MQTT. As usual, ZHA requires a bit more work, including adding a custom quirk to handle the device properly.
If you need an infrared blaster to automate your old AC, a sound system or whatever else you have that uses infrared, the ZS05 is a great choice. For comparison, the popular Broadlink RM4 Pro Remote also work well with Home Assistant, but require Wi-Fi to operate – a big NO for me. What can be Zigbee, must be Zigbee.
As an alternative, the Moes UFO-R11 is another battery powered IR Blaster that works great with Home Assistant. Here’s where you can get all three: