I bought a cheap IR blaster (the"Tuya WiFi IR Remote Control Hub WiFi Smart Home Infrared Universal Remote Controller For Alexa Google Home Air Conditioner TV") for under $15AUD on aliexpress.

The first hurdle to overcome was to remove the default Tuya firmware (I never even installed their app) and install Tasmota on there. That’s probably a subject for another blog, but the short version is:

So now it works, I configured MQTT and could successfully both capture IR codes and send them.

Cheap as the hardware is, it seems to be quite successful in sending IR signals, and has good coverage.

Now, to the actual problem, and the reason for this work. I’d like to be able to do more home automation of the TV, mostly to work around the awfulness of HDMI CEC. With multiple HDMI devices connected, the HDMI CEC should “just work” but in practice it doesn’t. Some examples:

Ultimately then, I’d like to be able to independently of any other devices turn the TV to the correct on/off state and change to the correct input.

(I also got the IR blaster to be able to control my aircon, but that’s the next project).

While I can learn any command I want via the Tasmota device, the problem is that the Hisense remote does not offer either discrete power buttons, or discrete input selection.

(The latter is particularly vexing, as the input selection is very awkward, with a slow popup menu in a grid format).

With some searching I did find a reference with codes in it, but they do not seem to be for my model, perhaps an older model?

The basics are easy enough, the remote sends using the “NEC” protocol, with a single 32-bit number for each command. For example, here is capturing the “volume up” button via MQTT:

topic: tele/tasmota_50930A/RESULT


  "IrReceived": {

I thought it might be useful to see patterns in the groups (ie the relation between “channel up” and “channel down” in the old documentation vs how it works on my device), but it seems that these are not particularly consistent.

For example, pressing the digits 1-0 and capturing the codes results in (hex and binary):

  hex                   bin
00FD807F | 00000000111111011000000001111111
00FD40BF | 00000000111111010100000010111111
00FDC03F | 00000000111111011100000000111111
00FD20DF | 00000000111111010010000011011111
00FDA05F | 00000000111111011010000001011111
00FD609F | 00000000111111010110000010011111
00FDE01F | 00000000111111011110000000011111
00FD10EF | 00000000111111010001000011101111
00FD906F | 00000000111111011001000001101111
00FD00FF | 00000000111111010000000011111111

There’s something like 12 bits changing around across there, so I have no idea how they related to the digits. I’m not much of an embedded person so there is probably a sensible reason that I can’t see :-)

Now in theory, I can just run through ever code to find the ones I need. There are a few problems:

Luckily there are a few ameliorating factors:

So, to search 16 bit of space is only 18 hours or so. Still annoyingly big, but manageable.

Obviously we need some automation to do this though, so I wrote a small script to help me:

#!/usr/bin/env perl
use strict;
use warnings;

use JSON;
use Net::MQTT::Simple qw/localhost/;

my $start = shift;
my $end   = shift;

$start = hex($start);
$end   = hex($end);

my $num = $end - $start;

printf("This will take %.2f minutes\n", $num/60);

foreach my $i (0..$num) {
  my $data = $start+$i;
  printf("Trying 0x%X - (remaining %.2fm)\n", $data, ($num-$i)/60);
  my $d = JSON::encode_json({ Protocol => "NEC", Bits => 32, Data => $data });
  publish("cmnd/tasmota_50930A/IRSend" => $d);
  sleep 1;

It takes two arguments, the start and end code (in hex). It’ll run through the codes, one per second, zapping them out, and I get to sit there and see what happens.

$ ./test_ir.pl 0xFDE000  0xFDEFFF
This will take 68.25 minutes
Trying 0xFDE000 - (remaining 68.25m)
Trying 0xFDE001 - (remaining 68.23m)
Trying 0xFDE002 - (remaining 68.22m)

(Doing 0xFFF at a time is a reasonable “chunk” in terms of time).

I can verify it’s working by having another window running mosqitto_sub or similar to watch the response topic:

cmnd/tasmota_50930A/IRSend {"Data":16637954,"Bits":32,"Protocol":"NEC"}
stat/tasmota_50930A/RESULT {"IRSend":"Done"}

(If I didn’t see the “IRSend Done” I would know my MQTT messages were not being received and actioned by the Tasmota. This is important because you can’t see Infrared beams to confirm it’s actually doing anything, and most of these codes are going to have no other visible impact).

Once it’s running, it’s just a matter of sitting back and waiting. When something happens, I just press CTRL+C and re-run the script with a smaller range to narrow down the exact code and what it’s doing, document it, and restart the script again.

One super strange oddity, once I discovered a couple of the undocumented codes I thought I’d search the web for them to see what I came up with.

So far, I got a single hit, on a Romanian web page, presumably selling the same remote as I have. The weird part is the hex code I searched for is on that page, but in white on white text so as to be hidden from view for humans. There was only a small handful of codes that I’d already discovered via learning them directly from my remote, so it wasn’t useful as a reference.

At one point I found that the TV unexpectedly turned off (while playing a Switch game). I was initially jubilant, as I thought I’d discovered the power off signal. However I “rewound” a dozen or so codes and played them again - no luck. It seemed like I’d been wrong.

The next day I returned to it and looked further. I then looked at the MQTT subscriber and found that the Tasmota was no longer responding to IRSend commands. They were going into a vacuum! Looking more, I found the device was back on factory defaults (AP mode) and had lost all of its configuration.

This started a frustrating walk to reconfigure the device, where I found I had not documented the GPIO allocations for IRSend and IRRecv. I eventually found them again (and wrote them down this time). But while doing this I formulated a theory on what had happened.

My theory is that I had found the power off command. Unfortunately, the Tasmota device was connected to the Nintendo Switch’s USB connector. When the TV turned off, the Switch detected that via HDMI CEC and also shut itself off. This would have corresponded to the time that the MQTT responses started not coming back. Unfortunately I believe the unexpected shutdown also killed the Tasmota configuration - possibly a combination of right after an IRSend where it updated it’s NVRAM or similar caused it to corrupt and go back to factory settings.

Since this incident I have confirmed the found code for power off does work (and it is the discrete one, not the toggle), and now I’ve got the TV turned off and am walking through the range again looking for the power on command.

Here’s the codes I’ve found so far, either from learning from my remote or via brute force. As you can see, some of the functions are not entirely clear to me :-)

Finding the discrete power on and power off was crucial, but I have so far not had any luck finding any of the HDMI inputs - I hope they exist.

hex        function
0xFDFA05 | apps
0xFD12ED | back
0xFDAA55 | blue
0xFD52AD | channel +
0xFDD22D | channel -
0xFD9A65 | channel list
0xFD00FF | digit 0
0xFD807F | digit 1
0xFD40BF | digit 2
0xFDC03F | digit 3
0xFD20DF | digit 4
0xFDA05F | digit 5
0xFD609F | digit 6
0xFDE01F | digit 7
0xFD10EF | digit 8
0xFD906F | digit 9
0xFDE817 | direction down
0xFD18E7 | direction left
0xFD9867 | direction right
0xFD6897 | direction up
0xFDB847 | epg
0xFD3AC5 | exit
0xFDCA35 | green
0xFD04FB | home
0xFD30CF | info
0xFD48B7 | input
0xFDD827 | live tv
0xFDB24D | media?
0xFDCC33 | media?
0xFDBA45 | media?
0xFD708F | mute
0xFDE21D | Netflix
0xFDA857 | ok/enter
0xFD847B | ok/enter?
0xFD956A | pause
0xFD08F7 | picture mode
0xFD15EA | play
0xFD8B74 | power off
0xFD0BF4 | power on
0xFDB04F | power toggle
0xFD4AB5 | red
0xFD1AE5 | rewind
0xFD28D7 | settings (cog)
0xFD8877 | sound mode
0xFD11EE | spinning blue circle then nothing
0xFD5AA5 | stop
0xFDF807 | subtitles
0xFDA25D | teletext?
0xFD8A75 | time shift
0xFDC23D | volume down
0xFD22DD | volume up
0xFD2AD5 | yellow
0xFD55AA | YouTube

Update October 2020

A kind reader (thank you D. Smith) has informed me of how the NEC protocol works, and how the address space is effectively only 8-bit.

Unfortunately this means that I have searched without finding codes for the HDMI inputs 😆

I do have a workaround, which is to use the “input” code, the “direction” codes and “enter” to choose the input. The inputs are re-ordered based on the current input (sigh) but I can workaround that by first switching to “Live TV”, which will then present the inputs in a fixed order.

Tags: hisense  tv  discrete  ir  perl