Review: Tuya TS1201 Zigbee IR Blaster
TL;DR This blaster works. If you can get it for the right price, it’s worth picking up.
After getting my Bosch air con talking to Home Assistant through a HOBEIAN ZG-IR01, I picked up a second IR blaster for the master bedroom. This time I went for a Tuya TS1201.
What It Is
I bought it from AliExpress for £8.99. It’s a flat, round black puck that reports itself to ZHA as manufacturer _TZ3290_7v1k4vufotpowp9z, model TS1201.
It’s powered over USB-C rather than batteries, which means no more battery swapping, but does mean it needs to be near a power source. The ZG-IR01 runs off two AAAs, but does have the advantage of a couple of extra sensors.
Pairing with ZHA
This is where it beats the ZG-IR01; it worked first time: it paired, and ZHA’s built-in ZosungIRBlaster_ZS06 quirk (from zhaquirks.tuya.ts1201) picked it up straight away. There’s no custom quirk to write, no full restart of Home Assistant, and no sequence-number bug to bodge around1.
ZHA creates three entities:
- a switch (I don’t know what this does…)
- a firmware update entity (it’s on firmware
0x00000043) - an identify button
Learning Codes
Learning works the same way as the ZG-IR01: issue IRLearn on the IR Control cluster (0xE004), point the remote at the blaster, press a button and read back last_learned_ir_code. Once it was in learn mode, it read the remote’s codes without any fuss.
The Code Format
What comes back is different though. The ZG-IR01 gives Broadlink-style JgD… codes: a header, then pulse lengths in ticks of 1/32768 s. The TS1201 returns Tuya’s own format, which mildsunrise has documented in a gist. Working from the code back to the signal:
- Base64-decode the string.
- Decompress the bytes. It’s FastLZ (level 1), a small LZ77-style scheme, which the gist’s author spotted in a later update2.
- Read the result as 16-bit little-endian numbers. Each one is a duration in microseconds, alternating mark (IR on) and space (IR off), starting with a mark.
So the start of the air con’s tilt code, after decompressing, looks something like this (with the timings rounded):
3011header mark: 4400 µs3011header space: 4400 µsF401bit mark: 500 µs6806space: 1640 µs, a 1The first four timings, two bytes each, least significant byte first. 0x1130 is 4400 µs, 0x01F4 is 500 µs and 0x0668 is 1640 µs.
Drawn out as the signal, those four numbers look like this:
1The same four timings to scale: the IR is on for the raised parts (marks) and off for the rest (spaces). A long space after a bit’s mark makes it a 1, a short one a 0.
That’s a much more sensible unit than the Broadlink ticks, but it means codes from one blaster won’t simply paste into the other. Decoded, the timings matched the ZG-IR01’s captures of the same remote, which is how I captured the air con’s extra buttons. There’s a post on those coming soon.
Sending Codes
The same format goes the other way too. The TS1201 expects the codes it’s sent with IRSend to be in Tuya’s format, not the Broadlink-style codes the ZG-IR01 takes. My bosch_ir integration generates every code itself rather than replaying learned ones, and it only knew how to write the ZG-IR01’s format, so I had to update it to write Tuya codes for this blaster too3.
Once that was sorted, sending just worked. I’d only put the blaster on my desk for testing, without trying to line it up with the AC or anything, and it still got through fine.
Pros and Cons
The Good
- Works with ZHA out of the box, with no custom quirk
- No firmware bug to work around
- Learning codes works first time
- Sending reached the AC from my desk, without lining anything up
- Powered over USB-C, so no batteries to flatten
- £8.99
The Not So Good
- No temperature or humidity sensors, unlike the ZG-IR01
- Codes, learned and sent, are in Tuya’s format, not the Broadlink-style codes the ZG-IR01 uses, so my integration needed updating
Final Thoughts
It works. If you just want a USB-powered IR blaster, get this one. If you want the extra sensors or batteries, consider the ZG-IR01, though you’ll need to add the seq 0 fix yourself.
-
The ZG-IR01 always asks for chunks of sequence number 0, so the stock quirk silently drops every send. The fix is in the first post. ↩︎
-
The decompressor is short. Each block starts with a header byte
h, split into a lengthL = h >> 5andD = h & 0x1F. IfLis 0, copy the nextD + 1bytes as they are. Otherwise it’s a back-reference: ifLis 7, add the next byte toL, then take the byte after as the low 8 bits of a distance whose top bits areD, and copyL + 2bytes from that distance plus one back. The result is little-endian 16-bit timings. ↩︎ -
That change isn’t on GitHub yet. I’ll release a new version once I’ve finished learning some new commands. ↩︎