-
Debugging and Polishing
09/14/2019 at 01:06 • 0 commentsI've got it almost 100% of the way, now - though it wasn't an easy road to get there. Let's see if I can list the things that went wrong...
- My UART was transmitting LSB-first, which... is probably universally true but I didn't anticipate it. Simple enough: change my char from 0x24 to 0x12 and transmit that instead. Woo!
- I was calculating parity wrong for the apple TV - my original code (which I will not post because it was absolutely awful) was XORing all the bits to the left, but then checking parity on the rightmost bit - again a simple fix; just had to change the bit-shift direction on each operation for the parity function.
- The apple remote spec is... interesting. It includes a provision for "Remote ID", which is 8 bits - so there are 255 possible remote IDs, and any given apple TV is listening for only one of them, I think. When making my code I chose an arbitrary "0x37", but turns out that 1/255 chance shot-in-the-dark was wrong. I ended up taking my apple remote and an IR demodulator to an oscilloscope (that I dearly wish I owned, but, alas, do not) and manually decoded the remote ID - turned out to be 0xAE.
- Had to sanity check every single remote code "fixer" function - I pass it just the command and brand, say "l08" for "LG, command 0x08" which represents power toggle on my TV. The function is supposed to "correct" it - take that minimal data and generate a uint32_t full of the right bits to transmit. Long story short: each of those was wrong because I screwed up my bitwise operations. All fixed now, thanks to some debugging time with Mr. Oscilloscope.
Once I actually got my very first bit of success - toggling the TV power - it felt like the end was in sight. I've spent some time polishing, and came to a few realizations:
- I didn't much like the way some of the UI elements were working - for instance, a single button to turn on the TV, change the input, turn on a device (apple TV or blu-ray player) just... wasn't working out; particularly since it was the only way to get to the remote for that device. I opted to make the buttons up top just bring me to the remote for the given device (unless there isn't one), and then include a button to "switch source here" on that specific device remote.
- The load times when making a new request to the ESP for another html page - say "tv.html" followed by "apple.html" - were frustratingly long. I changed the architecture to hold all the button data for each remote in a JSON object, then dynamically "build" the elements on the remote when requested - this way the remote load time is dependent on my device's javascript speed, rather than my ESP8266's wifi speed.
- I was having some intermittent issues with the websocket when I left the page alone - I changed it to verify the websocket connection (and re-establish it if necessary) each time I press or release a button.
Once I felt confident enough, I soldered it all onto some protoboard. Please note that I generally like to design circuit boards in KiCAD, tinker with them for 134 years, then get them manufactured so they're nice and orderly - my protoboard soldering is... not pretty. Fortunately, it IS functional. The trailing wire you see in the pictures below is my cut-up power cable from an old USB charger.
Oh, yeah - I only had a surface-mount 3.3V regulator so I improvised and made it a through-hold component by bodging on some 22-gauge solid core wire, then hot glueing and electrical taping the whole thing together. It's art, I tell you: art. Hard to tell in the pictures, but all the necessary resistors are under the ESP8266, which is, itself, in an 8-pin female header - so it can be removed for reprogramming/replacement.
I'll be trying it out and probably 3d-print a new case for it in the next few days!
-
(Almost) The Whole Thing in One Log
09/06/2019 at 16:49 • 0 commentsAfter 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: