AutoOfficeIR
Joe Angell
Update: December 2021
I replaced my Sony receiver with a Yamaha one and had to update the remote to support it. First, I had to fix a problem where one of the IR LED connections broke and wasn’t controlling the a TV anymore, which broke when I disassembled it so I could get to the ESP32’s buttons. I created a circuit diagram to help debug this, which I neglected to make the first time.
The volume control was updated to use raw codes I found on RemoteCentral.com. They didn’t have the Yamaha RX-V4A specifically, but the volume codes for another RX receiver worked fine. The input codes were trickier. The remote only has next/previous input buttons, not discrete buttons. However, it has a concept of “scenes”, which is an input some other settings like the listening mode. I simply assigned a different input to each scene, and used the individual scene buttons n the remote to switch inputs. I couldn’t find a raw code for those, so I had to capture those myself.
I also mapped the old Samsung TV input codes to fire when I held the button down for longer than a second, while the Yamaha input codes fire if you just press and release the button. I also have the Samsung HDMI 4 code fire whenever you do a Yamaha code so that it’s always on the right TV input.
Everything is working great now, although it did take longer than it should of.
Update: January 2021
As part of switching to HomeKit, I updated the software to report the current TV on/off state using the protocol defined by homebridge-http-webhooks. On the hardware side, I drilled out the screw holes and added brass heat-set inserts, which greatly improve the grip of the screws and only took a couple of minutes to install.
This update is part of my new Auto Office through HomeKit.
A while back I built my AutoOffice, which I still use. It consists of a few discrete parts:
ESP8266 with a glowing button mounted to my desk, and running a web server. This controlled the lights in my office, although that functionality is currently disabled until my new office is ready.
Vapor-based web server running on my iMac. This wakes or sleeps the Mac’s monitors when the ESP8266 button is pushed, and can also sleep or wake other devices when it sleeps or wakes.
Web server running on my Windows machine. This only listens for wake/sleep events and wakes/sleeps its monitors.
SmartThings-based switches and SmartApp for turning on/off monitors and TVs that couldn’t be controlled more directly (ie: lacking DPMS for computer wake/sleep control).
One hole in this system was the TVs that I use as monitors. Since TVs lack DPMS, I had to rely on cutting power to them and restoring it via SmartThings switches/appliance modules. Luckily, the displays remember their previous power states and come back on automatically. The problem is that the computers (mostly the Windows one) get confused about the monitor losing power, instead thinking it has been disconnected and moves all the windows to the other screen. This is very, very annoying.
For a while I used an EDID spoofer to kludge around this, but one day it simply stopped working, and I never found a way to fix it.
I recently got a new A/V receiver that supports 4K HDR. I only really need to switch inputs and change the volume with it, but the remote isn’t great and the volume button position is awkward. I decided to kill two birds with one stone and build a device to solve both problems.
The Goal
The new device would support a few key functions:
Use a rotary encoder to change the volume on my A/V receiver via IR remote control commands.
Run a web server that supports AutoOffice, which would send discrete power on/off codes to the Samsung TVs I use as monitors.
One or more buttons for other IR controls.
Multiple IR outputs that can be placed where they are needed most.
3D printed, weighted base so it doesn’t shift around on my desk.
This all seemed pretty feasible, so I decided to spend a weekend on it.
Variants
I built two of these. The first had just a clickable dial, while the second model had a larger case and four buttons to change inputs. I documented the original model first, and at the end I’ll cover the changes in the second model, which builds heavily off the first.
The Hardware
Microcontroller
I was going to use an ESP8266, but one of the IR libraries I found only supported the ESP32, so I used one of those instead. I wrote the code using the Arduino development system, although I mostly did so using Visual Studio Code with the Arduino plug-in.
Rotary Encoder
I bought a five pack of rotary encoders on Amazon, along with a 1.25” diameter knob, mostly because I couldn’t find anything larger. The encoders have detents and click into position as you turn it, but otherwise will turn continuously. I used the ESPRotary Arduino library to read the knob. This tutorial notes that a standard mechanical encoder sends four pulses per click, which I found to be true when I got exactly that for each rotation. ESPRotary lets you set the number of pulses per click, so that worked out well. The encoder wiring is simple and documented in the tutorial linked above.
The encoder can also be pushed like button by wiring the remaining two pins to the microcontroller, which also set up. Currently it’s being used to manually turn the TVs on and off.
IR LEDs
Years ago I had bought a five pack of IR LEDs and receivers from Amazon that I had never used. I hooked three of them up to their own 100 Ohm resistors, wired in parallel to a single P-channel MOSFET, a BS250KL, to avoid putting to much strain on the ESP32’s pin. While breadboarding my prototype I used an N-channel MOSFET, but that resulted in the LED being on all the time and flashing off when trying to send a code (I used a colored LED so I could see it flash for these tests). I swapped in a P-channel and everything worked fine.
The IR LEDs are at the end of 22 gauge two conductor cables in a black jacket. I used heat shrink tubing on both ends for strain relief and to keep things neat. I then simply taped them near the IR sensors on the TVs and A/V recovers.
Assembly
After breadboarding the basic setup, I mounted the ESP on the bottom of a small perfboard, and mounted the rotary encoder on the other side. This odd arrangement sought to minimize the mount of physical space that the assembly would require. I was then able to solder down the MOSFET and resistors, and attach a six-pin 90 degree header strip to the end of the board. Each pair of pins would drive one IR LED placed near the IR receiver of a device to control. Once soldered, I cut down the perfboard to keep the final footprint as small as possible.
The Software
I found IR libraries to have less than great documentation. I could find examples, but they were a bit tough to parse as well. I finally went with an ESP32 variant of the IRremote library, Arduino-IRRemote. Due to naming conflicts, the built-in RobotIremote library has to be removed from the application bundle (on the Mac, which is the install dir on Windows) to get it to build properly. Getting IR to work took the most time of the development, from testing different libraries to figuring out what the codes actually work and how to get them into a format that the IR libraries would accept.
It’s very important to note that when I started this project, the official Arduino Library didn’t support the ESP32, but by the time I finished it did. The library I used is based on an older version of IRRemote. Somewhat annoyingly, if you click the “releases” link from the repository above, it goes to the official library that has had some significant changes and will not work with my existing code. It took me way too long to realize this when I had to update the remote for my new Yamaha receiver. I finally just downloaded the a zip of the repository so that I had the correct version. I’ll have to update the code for the new library someday…
Sony Codes
Finding codes was its own challenge. After some searching, I finally just plugged an IR receiver into an Arduino Uno I had lying around and loaded IRremote’s IRrecvdemo onto it. I was able to read the volume up and volume down codes for my A/V receiver and pass those to the sendSony() method. This particular device uses 15 bit codes, as evidenced by the length of the hex codes: 0x240C for volume up and 0x640C for volume down.
Sony codes need to be transmitted three times for the device to accept them, with a short delay between. I originally used 40 ms, but this caused a lot of turns of the dial to miss, as sending the codes and the delay are done in a blocking section of code. I was able to get it down to a 5 ms delay with no ill effects, which made the knob much more responsive. It is still easy to miss steps, though, but in general use it’s fine, as I only tend to urn the dial small amounts anyway. However, you still need to turn it slowly to avoid missing steps too often.
Another detail I discovered is the first code sent to the receiver simply shows the current volume level on the VFD or TV overlay, rather than changing the volume level. This means that no matter what you need to go two steps for anything to happen.
Samsung Codes
The Samsung TV power codes were a bit trickier because of what I wanted to have it do. The important thing here is to get discrete on and off codes, rather than the single toggle code that the remote control sends. This meant that I couldn’t just record the code from the remote, which is just an on/off toggle, and had to find another source. A particularly useful site for this is RemoteCentral.com, which has a slew of raw codes for various devices, including Samsung TVs. I converted those codes into a list of integers that could be copied into a C array using IrSscrutinizer (just paste them into the window and hit Scrutinize), which could then be transmitted with sendRaw().
Yamaha Codes
I later replaced the Sony receiver with a Yamaha RX-V4A receiver. This specific model isn’t listened on RemoteCentral.com, but I was able to use some of the raw codes fro another RX class receiver for volume control. I couldn’t find HDMI input codes, though, and the remote doesn’t have discrete input buttons, just next/previous buttons and four “scene” buttons. I would up mapping the scenes to different inputs, and using an ESP32 with an IR receiver and IRRecvDumpV2 from the IRRemote library to capture the NEC codes directly. I mapped these codes to the four input buttons, with the Samsung codes mapped to if the button is held for more than a second. I also have Samsung HDMI 4 fire when using any of the Yamaha codes to ensure that it’s on the correct TV input.
Repeat Codes
I also tried adding support for repeat codes via irsend.sendNEC( REPEAT, 15 ) if successive ticks from the encoder came in fast enough, but this seemed to do nothing at all. I tried different code lengths in place of 15, but nothing worked. I was recording 0xFFFFFF (the repeat code) from the IRrecvdemo, so it definitely supports a repeat code, but I’m not sure how to transmit it. Luckily there are relatively few lost ticks unless you spin the knob quickly, so I decided to just let leave out repeats, at least for the time being. Apparently NEC repeat codes are sent every 110 ms, so even then I could spin the dial faster than it could transmit codes, assuming Sony uses the same repeat period. I didn’t try using the repeat codes with the Yamaha receiver.
Encoder Button
ESPRotary handled direction reporting from the rotary encoder. I read the encoder’s button with the InputDebounce library to easily deal with debouncing, and configured it to turn the TVs on and off.
HTTP Server
The web server was mostly copied from the AutoOffice-ESP8266 code. However, ArduinoJSON has since been updated fairly significantly and I need to make some code changes to get my sketch to build. I also had to make some tweaks to the web server startup sequence to keep it from crashing on boot, but nothing too major.
The final software (including the version 2 input switching functions) can be found on Github.
The Enclosure
I used MODO to design a simple case for the hardware, leaving holes for the USB port, LED headers and rotary encoder. I built in small vent holes in the side to help keep the ESP cool. The case was designed in two halves with screw holes to mate them. I printed this in ABS on my Ender 3 3D printer. I made a second version of the case that is more sloped, and a final third version that has space for the input switching buttons for the Version 2 design described further down.
LXO (all versions)
STL Original (V1) Base Top
STL Sloped (V2) Base Top
STL With Input Buttons (V3) Base Top
I left empty space below the ESP32 to fill with metal, which in this case was some bolts. Lead shot would have been better, but is surprisingly pricey. Tungsten would have been really cool, but even more expensive. Lead Pinewood Derby car would also be an option, but kits are only four ounces and kind of expensive for what they are. I stuck with the bolts, but something heavier would have been nice. A redesign of the enclosure would have made it easier to fit more in. I used some packing tape to ensure that they didn’t short the ESP32.
To keep it from sliding around my desk I bought some adhesive-backed rubber pads by 3M from Amazon, which were sold as “2-Piece Self-Stick Rubber Anti-Skid Pad Furniture and Floor Protectors (Black) by ROOS”. I (poorly) cut it down to the size of the base with cut-outs for the screw holes. It significantly improved how well it stayed in place on the desk, although more weight would have helped even more.
Power
Power can be provided from any USB port; I used some in-desk ones I’d previously installed purely for powering USB devices like this, which worked great. I can plug it into a computer if I need to debug it, but there’s no need for it to tie up a USB port most of the time.
Originally I had separate two-pin header connectors for each LED. The problem was the heat shrink I added, which caused them to not fit together very well. I popped out the pins and put them in a six-pin connector, which holds in place much better, although it does make it a bit harder to reposition the LED wires if I need to later.
Version Two: Input Switching and Aluminum
The second version of the remote adds four buttons on the face to change the inputs on one of the Samsung TVs. This required a few changes to the hardware.
Adding Input Switching Buttons
The most obvious one is adding the four buttons. I bought an assortment of four-pin microswitches from Amazon with different length plungers, evenly spaced them on a strip of perf board, then designed a new case in MODO using procedural modeling that included the extra space needed for the board. I cut tabs into the model that can flex to push the microswitches so that it would look a bit prettier. After doing a test print, I positioned the microswitch board and swapped out switches until the case tabs rested on top of the buttons without pressing them, and confirmed that pressing the tab actually clicks the button.
I soldered the switches to short wires that ran to the ESP32 and updated the software to send IR codes when the buttons were pushed. I used RemoteCentral.com to get the discrete input codes for HDMI 1, 2, 3 and 4, ran them though IrSscrutinizer, and added them as a C array to my Arduino sketch, just liked I’d done for power and volume control.
Modifying the IR Wiring
I only wanted to apply input switching to one of my TVs, but they’re both Samsung and both respond to the same codes. To fix this, I rewired one of the LED connectors with an extra transistor. When I need to send input codes, I turn that transistor off so that the other TV won’t be sent any codes. When I need to send power codes, I turn that LED on again, and the codes go to both TVs.
Case Weights and Anti-Slip
As with the original design I used spare hardware as weights, and adhesive-backed anti-slip on the bottom to keep it from sliding around on my desk.
Aluminum Knob
The original black box would get lost on my desk, so I decided to go with an aluminum finish this time. I also wanted a larger knob, but without any kind of position marker on it. Finding large knobs is difficult, and finding ones without markings are nearly impossible.
In the end I bought three knobs in different sizes, all aluminum. One was smaller, polished until shiny, with knurled edges at 36mm and 16mm tall, but didn’t have any markings on the top, which was nice. The second was similar, but 38mm and 22mm tall, and did have a small indent to mark the current position on one edge. The one I wound up using was a textured aluminum at 48mm and 22mm tall. It has a small embossed circle to mark the current position that I don’t need, but the design was the best of them. The listing description was “1pcs 48mmx22mm SOLID Aluminum VOLUME CONTROL AMPLIFIER AUDIO CD PLAYER TEST KNOB”. Each cost about $10 from ebay, plus shipping.
Case Finish
For the case, I started with a prototype print with some aluminum PLA. The PLA seemed thinner than the black ABS I normally used, and didn’t quite print as tightly for some reason. It looked surprisingly similar to aluminum, but it was also five times more expensive than normal ABS or PLA.
I decided to try an experiment with paint instead. At Home Depot I bought a can of filler spray primer and aluminum spray paint. I printed a final version of the case, sanded it with progressively finer grades of sand paper, did two coats of primer, and then two coats of paint. The aluminum finish looked very good, so I decided to just go that route and skip the more specialized filaments.
Even better, the aluminum paint almost exactly matches the aluminum knob I bought. At a glance the entire thing looks like they’re made of the same material.
Other random notes
Ultimaker Cura opens every serial port it can find, apparently, which made it difficult to program the ESP while Cura was running. Since I write my prints to an SD card, I disabled the USB support from the Marketplace window in Cura and no longer have problems with that.
The ESP32 WebServer can’t be started until a wifi connection is made. If you try, it will crash on boot. The ESP8266 didn’t have this limitation, oddly enough.