Close

(Almost) The Whole Thing in One Log

A project log for ESP8266 Remote Control

I needed a remote. Rather than buy one for $5, I opted to build one for $15.

joshua-broekhuijsenJoshua Broekhuijsen 09/06/2019 at 16:490 Comments

After acquiring and repairing a "new" TV - sans remote control - I decided to improve my A/V setup with some custom automation, a-la-ESP8266 hosting a webserver to provide a phone-based remote control.

I spent some time researching IR protocols - how they work, differences between brands, etc - and decided that this should be quite possible.  For those curious, IR for remote controls is usually modulated in the 30-50khz range with a 30% or so duty cycle.  Unfortunately, the software PWM implementation available to me on the ESP-01 module can only reach 1Khz, which is not even close to sufficient.  With rather limited GPIO options - and a desire to avoid external hardware as much as possible - I started exploring.

My devices - an odd hodgepodge of whatever was cheap - include Sony, LG, and Apple components (OK, maybe not so much 'cheap' on the last one, but you get the point).  These have IR modulations of 38Khz, 38.2Khz, and 40Khz respectively.  Though lacking a sufficiently-powerful PWM, the ESP-01 module DOES have a UART that can operate pretty quickly....

After running some numbers, I determined that I could configure the UART in 7N1 mode - that is 7 data bits, no parity, and 1 stop bit.  There's also a start bit, so each data frame looks something like this: 0xxxxxxx1 - I fill in the seven "x"s with whatever I want - for my purposes, I chose 0x24, so my full frame looks like: 001001001 - if I transmit this at a baud rate of 114667, I get a pin modulating in a pretty-darn-passable impersonation of a 38.2Khz 30% PWM signal.  If I change the baud to 120K, I've got a pretty good 40Khz signal, too.

The hardware is pretty straightforward:  The ESP-01 module itself, a cannibalized USB power-only cable from who-knows-what, a 3.3V linear regulator so as to not fry my ESP module, 3 IR LEDs (one for each device I want to control) with a current-limiting resistor, and a MOSFET for switching them on and off (along with a gate resistor, just for safety - though depending on the hardware behind the UART, which I haven't bothered looking up, that may or may not be necessary).

The software is less straightforward: once embedded, I really don't want to have to take it out in order to change something if I get a different blu-ray player, or want it to control another device.  I've never actually developed on an ESP8266 before, despite owning several and reading a number of projects.  I'll just check to se--- yep, there's a library for OTA updates.  Great.  OK, well, I want the buttons to work just like a remote: press it to start sending the signal, hold it down and the signal should repeat.  That kind of prohibits a simplistic approach of just using links with GET parameters embedded, may some sort of AJAX or websocke-- yep, there's a websockets library.  OK.  What's this about inlining all the HTML to output?  That's kind of a nightmare, isn't it?  What if I could use some sort of filesy- oh, SPIFFS.  Neat.  I don't want to deal with assigning a static IP to the ESP, can I -- Oh, mDNS.  Wait, is there a library for friggin' everything?  Apparently.

The actual software development was a little bit annoying - I had to research the codes my remote(s) would send so I could simulate them all, and understand the protocols: my appleTV, though using the same modulation scheme as NEC encoding, doesn't follow the whole spec - it does whatever it wants, pretty arbitrarily, with the bits.  The LG tv is pretty good: follows spec AND publishes its remote codes in the manual!  Very nice.  Sony, of course, had to roll their own scheme entirely - but it's not too bad to implement.  There are libraries for remote controls, but given how I want mine to function I think it'll just be easier to write my own.  The general operation looks something like this:

The initial HTTP request on the ESP redirects you to "tv.html", which contains a basic TV remote, served via HTML and CSS (turns out flexbox/grid are really nice - I hadn't ever used those before!).  This has buttons to do basic TV things, but also buttons that link you to "bluray.html" and "apple.html" - each button has an attribute, "ir," that contains a string representing the hex for that button's command - for the LG Tv, "0x08" represents "power toggle" - of course, it takes some processing to encode it in the scheme used for each device, so I'll provide an identifier "l" for "LG".  Since I want to distinguish between button presses and releases, I'll need to send that, too - so when I press the "power toggle" button on the TV remote, it sends a websocket message of "+l08" - and when I release it, it'll send "-l08".

The websocket handler will parse the incoming string and create an item in a queue (I used a linked list) with all the details necessary to transmit.  The code loop will constantly check the queue to see if there's anything in it, and (if so), transmit it - waiting the appropriate amount of time to send repeat codes and what not.  Gotta be careful to avoid doing this synchronously - the ESP has a watchdog timer that will reset it if the wifi stack doesn't get serviced often enough.

This is nicely expandable, too - if I want a button to turn on the blu-ray player, tv, and switch the TVs input, I can just put them all into a string together and have it parse through the codes sequentially.  If TV power ON is 0x09 (it's not, but I'm too lazy to dig through my code to find it), the blu-ray's power ON is 0xAC, and the TV's switch input is 0x25, I can just send something like this:

"+l09sACl25"

Each "l" represents the beginning of a new LG command.  Each "s" the beginning of a "sony" command for my blu-ray player.  Of course, that's a bit simplistic - turning the TV on takes a bit, and it might not be ready to receive an IR code right away.  Let's try:

"+l25l09sAC*5l25"

There: start by sending the "switch input" command, in case the TV is already on.  Then send the power ON command for the TV and the blu-ray, then delay for 5 seconds (*5) - finally, send the "switch input" command once more.  Only a couple tinkering bits left - my apple TV remote has functionality where I can hold the play/pause button down for 5 seconds to sleep the device.  I COULD just hold down the play/pause button for 5 seconds on my remote, but... again, I'm lazy.  And that's hard.  So instead, I'll add one more bit of syntax: if "play pause" for the apple TV is 0x02 (which is might be, but I'm not going to bother looking it up again), I'll send:

"+p02_5"

Note that this starts with a "p" to identify apple rather than an "a", because "a" is used in hex codes - and I need to be able to distinguish for my simplistic parser to understand where the boundaries between commands.  The "_5" here represents a "minimum repeat" time - it will repeat the command for at least 5 seconds, but if I'm still holding down the button it'll keep going.

Now, defining a new button is pretty easy: I create a new html element, give it parameters to tell it how large to be and what icon to use, then assign an attribute like "p02_5".  When pressed, it'll send a websocket message of "+" and the IR attribute, and when released, it'll send "-" and the IR attribute.  The websocket handler on the ESP end of things parses the message, adds the relevant things into the queue.  The queue is checked every loop, the code is corrected to it's full version (more bits than just "0x08" in the protocol, depending on brand), and transmitted at the correct modulation on the TX pin by alternating between sending a bunch of UART packets of "$" (ascii for 0x24), and switching the TX pin to a GPIO and driving it low.  The pin drives the gate of a MOSFET, which turns the IR LEDs on and off, thus sending the exact IR commands a normal remote control would send - which should be interpreted by the device (TV, blu-ray player, or appleTV) in question, and handled from there.

That, at least, is the theory - I'm going to actually hook up the IR LEDs tonight to see if it works.

Attached is a picture of the HTML interface:

Discussions