I made an IRToWebThingy that reads common (and some not so common) infrared remote control codes and makes them available to any devices on the local WiFi network so you can script various things. For instance, you can fly a pig in Minecraft using a Syma helicopter remote!
The Thingy works with usual DVD/TV-type remotes, as well as Great Wolf Lodge Magiquest wands and some IR helicopter remotes.
Here are four use cases:
- have my daughter's Great Wolf Lodge Magiquest magic wand operate something (she's wanted that for a while)
- change the volume and pause/play video on my laptop with one of our many infrared remotes, say our DVD remote
- make it possible for my kids to write Python code interacting with Minecraft using an IR remote as input
- capture and decode infrared remote data.
Basically, with some scripting, the Thingy lets most IR remotes that you have around the house be used with any computers and many other WiFi-enabled devices. I made a little Python wrapper to make scripting easy.
I made the IRToWebThingy out of stuff I had lying around:
- ESP8266-01 (bought for an abandoned project; under $2 on aliexpress)
- IR demodulator (from a broken control unit of a toy; about $5 for a lot of 10 on aliexpress)
- two 1N4148 diodes (bought for an abandonded project; under $1 for a lot of 100 on aliexpress)
- protoboard (leftover part of a piece used for another project; about $1 on aliexpress)
- USB cable for power (scrap)
For flashing the unit, I used a Brainlink device (with the modified firmware supporting a Bluetooth to TTL serial bridge that I used here), but you can use an FTDI.
The firmware uses the ESP8266 board support for Arduino. The firmware adapts code from here and here.
This summer this and another ESP8266 project (that I still need to write up) took me out of my comfort zone: I haven't successfully made pieces of electronics in decades and I haven't used the Arduino IDE at all before.
Step 1: Electronics
You will need:
- ESP8266: Any flavor you can work with (solder, etc.) will work as the project only needs one GPIO pin. Moreover, the TSOP pulls up the pin in the absence of modulated IR signals, so you can use GPIO 0 or 2 which need to be pulled up on startup.
- Some protoboard.
- TSOP IR receiver. If you have a broken device that uses IR, you can extract it from there. I extracted mine from a broken Thames & Kosmos Remote Control Machines controller.
- 300mA at ~3.3V power supply. I used two 1N4148 diodes to step down 5V USB to within the 3.xV range the ESP8266 needs. For testing I used two AA batteries.
- ESP8266 GPIO 0 to TSOP signal pin (center)
- power supply ground to ESP8266 ground to TSOP ground (left)
- power supply +3.3V (approximate) to ESP8266 VCC to TSOP VCC (right)
- ESP8266 CH_PD to VCC
Temporary connections for programming:
- ESP8266 TX and RX to your 3.3V TTL serial unit (e.g., FTDI or Brainlink) RX and TX (TX-RX and RX-TX)
- ESP8266 GPIO 0 to ground for programming (I did this through a 220 ohm resistor so I could keep the TSOP in place)
Step 2: Programming Firmware With Arduino IDE
- Set up Arduino and 8266 board package. These instructions are nice, but select Generic ESP8266 module if using the ESP8266-01 (or select something else if using something else).
- Download the 8266ir package from here (you can clone or you can just download the zip). Make the main directory be a subdirectory of your Arduino library directory (e.g., My Documents\Arduino\libraries), and put the ir_to_web directory in your sketches directory.
- Edit ir_to_web.ino to include the credentials for your WiFi network. For instance, change the #if 0 to #if 1 and then change the "xxxx" and "yyyy" to the WPA2 ssid and psk.
- Attach IRToWebThingy to TTL-serial device, and make sure the serial port is set correctly in Arduino tools.
- Bridge GPIO 0 on ESP8266 to ground while powering up (e.g., using 220 ohm resistor you hold in place). Use Sketch | Upload.
I have to confess that it wasn't so simple for my ESP8266-01 device. It would just give error messages on uploading, even though I could tell it was working. If that happens, you can do this.
- Make sure you have python on your path and a working serial library.
- Download esptool.
- Check your serial port name (e.g., COM5 on Windows) and make sure that you get no error messages with: python esptool.py --port portname chip_id
- You can now use Sketch | Export compiled Binary in the IDE. Copy the ir_to_web.ino.generic.bin from your ir_to_web sketch directory to your esptool directory and then upload it with: python esptool.py --port portname flash 0x0 ir_to_web.ino.generic.bin
If that works, you may want to edit your Arduino15/packages/esp8266/hardware/esp8266/2.2.0/platform.txt settings (on Windows probably in c:\users\name\Local Settings) to use python esptool.py to upload binaries.
Step 3: Case
I drilled some ventilation holes and a USB power cord hole in a nice round see-through (for the IR detector) camera filter box that I didn't need, and screwed the box together.
Step 4: Finding and Configuring the Thingy
You now need to find the IP address of your Thingy. There are multiple ways of doing this:
- power up the Thingy and go to your router configuration (often: 192.168.1.1) and see what ESP device is connected to the internet
- run arp -a (works on Windows and Linux) and look at what devices are on the network; power up the Thingy and see what new device shows up
- watch the Arduino serial monitor while the Thingy is starting up: it should print something like IRToWebThingy on 192.168.1.123:5678
Once you have the IP address, you can test it. An easy way is to use:
telnet ip.address.of.thingy 5678
(On Windows, either do this from a commandline or Windows-R; you may need to install telnet first.)
Then point remote controls at the device and press buttons. If all goes well, when you point remote controls at the Thingy and press buttons you'll see a series of lines in the following format:
- encoding: a text string like "NEC", or "JVC" or "MAGIQUEST" identifying the encoding format
- milliseconds: time measured from the Thingy's powerup in milliseconds (decimal)
- bits: number of bits in the value (decimal)
- value: the value being sent (hex); on MAGIQUEST wands, this is the wand ID
- extras: additional data, given as a comma-separated list of the form key=x where x is in decimal. For instance, for MagiQuest wands, there is a magnitude value (allegedly measuring how hard the swing was, according web reports, but I couldn't see any such pattern myself), and for supported toy helicopter remotes (Syma, USeries and FastLane) there are keys like throttle and yaw.
You can now write your own code connecting to the Thingy's 5678 port. Up to five devices can simultaneously listen to the Thingy.
I strongly recommend using your WiFi router's DHCP reservation option to fix the Thingy's IP address so it doesn't change.
You an also send some configuration commands to the Thingy's 5678 port. These only work on the last device to have connected if you have multiple devices connected to the port. Note that there can be only one space between the command and the value, and if an optional value (indicated by brackets) is omitted, no space is to be included either.
- ssid [ssid-name]: Set the WiFi SSID. If omitted, instead the Thingy sets up as an access point.
- psk [psk]: Set the WiFi WPA2 password. Omission hasn't been tested.
- serial0|1: Echo IR codes to the serial port if 1; otherwise, don't. On the ESP8266-01, the blue LED flashes when data is written to the serial port, which is a nice feature of serial mode. But erial mode increases latency a little.
- unknown 0|1: Put out unknown codes (encoding=UNKNOWN, bits=32 and value is a hash of the raw data).
- raw 0|1: Include raw data, useful for developing support for unrecognized remotes or just for the fun of decoding their protocols. I used raw mode to decode the remote control for a counterfeit Syma helicopter we have at home.
- save: Save the settings to EEPROM so they'll be available on next startup as well.
- reboot: Reboot the device. You'll need to do this after changing SSID or password (and you'll need to save the settings, too). I've had some trouble with rebooting by software--if so, just pull the power.
If the Thingy cannot find the WiFi network it's supposed to connect to, it will set itself up an access point. You can connect your computer to that access point, and then access the Thingy using the IP address 192.168.4.1.
Step 5: Python Interface
The espremote.py script (included in the ir_to_web directory) provides an ESPRemote class. To start it up, do:
from espremote import ESPRemote remote = ESPRemote("thingy.ip.address")
You can also permanently configure your Thingy's IP address as the default in espremote.py (currently, that's 192.168.1.123). Then, there are three useful things available:
- remote.available(): checks if an IR event is available
- remote.getevent(): returns an IR event if one is available; otherwise waits until one is
- remote.getevents(): a generator that returns IR events, waiting for them as needed
An event returned by remote.getevent() or remote.getevents() that are returned has several attributes:
- event.format: format string, e.g., HELI_SYMA_R5 or NEC
- event.time: time in milliseconds from Thingy powerup to event completion
- event.bits: number of valid bits in event.data
- event.data: data value
- event.extras: dictionary containing additional entries; for instance, helicopters have normalized throttle, pitch, yaw and trim
You can view a live stream of events with:
python espremote [thingy.ip.address]
Step 6: Control Stuff in Windows
Install Python 2.7 for 32-bit Windows (even if you have 64-bit Windows) and pywin32 . Make sure you have espremote.py (included in the ir_to_web directory in my IR Thingy's github repository) in the same directory as you put your scripts (or else in your Python path). Then here is a simple script that works with our Philips DVD player remote and presses spacebar when you press PLAY, adjusts volume on UP/DOWN and exits on POWER:
import win32com.client from espremote import ESPRemote shell = win32com.client.Dispatch("WScript.Shell") lastTime = 0 for event in ESPRemote().getevents(): repeat = event.time < lastTime + 500 data = event.data & 0xFFF if data == 0x42c and not repeat: shell.SendKeys(" ") elif data == 0x458: shell.SendKeys("\xAF") elif data == 0x459: shell.SendKeys("\xAE") elif data == 0x40c: exit() else: print hex(event.data) lastTime = event.time
You'll have to edit the codes for your remote. You may also want to remove the "& 0xFFF": it's there as on our remote, as it uses the RC6 protocol where there is a bit that's toggled each time a button is pressed.
If the script sees a code it doesn't understand, it prints it, so you can use this script to get the codes you need.
My daughter enjoyed my making a script that let her start and stop movies with a wave of the Magiquest wand. (We were watching movies on the ceiling via a projector.) To do that with the above script, just revise the script:
if (data == 0x42c or event.format == "MAGIQUEST") and not repeat: shell.SendKeys(" ")
I include a slightly more complex script here. It uses spacebar to pause/play while Chrome is in Netflix and a screen click to pause/play while Chrome is in Acorn.
Once you have a remote control file you like, make a little batch file like ESPRemote.bat to launch it that says something like:
Step 7: Control Stuff on Rooted Android Tablets or Phones
If you have a rooted Android device with BusyBox installed you can use standard Linux scripting and the Thingy to control the device with an IR remote. For instance, a simple script to adjust volume using the UP/DOWN keys of our Philips DVD remote would be:
#!/system/bin/sh nc 192.168.1.123 5678 | while read line ; do if [[ $line == *458? ]] then echo "Volume Up" input keyevent KEYCODE_VOLUME_UP elif [[ $line == *459? ]] then echo "Volume Down" input keyevent KEYCODE_VOLUME_DOWN else echo $line fi done<br>
You will probably need to change the IP address and the hex codes in the $line == *xxx? conditions. (The final ? is because the Thingy writes data in Windows CRLF line ending format, and so the ? catches the CR.) When you put the script on your device, make sure you use Linux line-endings, either by using a text editor direct on the device or using Linux to write it or using dos2unix on the script.
I haven't found a script running app that I am happy with, so I just ran the script in a terminal emulator (however you run it, you need root/superuser):
su cd /storage/emulated/0 sh simple.sh
Make sure you edit the IP address and the remote codes and that the script uses Linux line-endings (use dos2unix if need be).
For some buttons, you want to disable auto-repeat. That's a bit tricky, but I do it in this Netflix control script for the play/pause button by leveraging the time data returned by the Thingy.
In the spirit of the wizarding competition, here's a script that pops up a lot of fireworks in Fireworks Arcade whenever the Magiquest wand is waved. You may need to adjust the coordinates for your screen.
#!/system/bin/sh nc 192.168.1.123 5678 | while read line ; do if [[ $line == *454? ]] then input tap 800 800 sleep 0.25 input swipe 600 1000 600 500 sleep 0.25 input tap 10 10 sleep 0.25 input swipe 600 500 600 1000 sleep 0.25 input tap 10 800 sleep 0.25 input tap 800 800 sleep 0.25 fi done
In general, when scripting taps and swipes, you may need to find out coordinates of buttons in apps. Enable the Developer Options on your device and then under Input, activate Show touches and Pointer location.
I have to say that on my devices, scripting using the input command is laggy.
Step 8: Control Stuff in Minecraft
Here you will need a Minecraft setup that can handle Python scripts that use the Minecraft PI API. If you have a Raspberry PI, you're all set. Otherwise, you'll need to set things up. I used my RaspberryJamMod with Forge on Windows: full instructions are here. (Other options: Bukkit server and RaspberryJuice, or Minecraft Pocket Edition and RaspberryJamMod, or even Minetest and RaspberryJamMod.)
Drop the espremote.py script from the Thingy's repository into your Minecraft python scripts directory (e.g., .minecraft/mcpipy) and edit the default IP address. Here's a simple 3D etch-a-sketch script working with our Philips DVD remote that my 11-year-old son wrote with some help:
from mcturtle import * from espremote import* t=Turtle() r=ESPRemote() t.gridalign() t.turtle(None) while True: event=r.getevent() if event.data&0xfff==0x458: t.go(1) elif event.data&0xfff==0x459: t.go(-1) elif event.data&0xfff==0x45b: t.yaw(90) t.go(1) t.yaw(-90) elif event.data&0xfff==0x45a: t.yaw(-90) t.go(1) t.yaw(90) elif event.data&0xfff==0x45c: t.pitch(90) t.go(1) t.pitch(-90) elif event.data&0xfff==0x42c: t.pitch(-90) t.go(1) t.pitch(90) else: t.mc.postToChat(hex(event.data))
The last line let him check what keys do what as he was developing the script. The remote control keys let him move the Minecraft turtle in all six directions.
Here's a simple script that draws a giant diamond ball under the player whenever the Magiquest wand is waved:
from mc import * from mcturtle import * from espremote import * r = ESPRemote() mc = Minecraft() lastEvent = -10000 for e in r.getevents(): if e.time < lastEvent + 500: next lastEvent = e.time if e.format == "MAGIQUEST": t = Turtle(mc) t.penwidth(30) t.penblock(DIAMOND_BLOCK) t.go(0) t.pitch(90) t.penup() t.go(32) t.pitch(-90)
A somewhat more complicated thing is my flyingpig.py script which lets you use our Syma S107 helicopter remote to control a flying pig in Minecraft. You can also specify a commandline argument of a different entity type (e.g., EntityHorse) or me to control the player (in flying mode).
Step 9: Decoding Unknown Remotes
Suppose I want to decode an unsupported remote. Then I run:
python espremote.py -raw -unknown
(If I don't want to be getting raw data all the time--which will introduce lag--afterwards, then I must turn it off later with python espremote.py -noraw as Thingy's settings are all persistent right now.)
I get a sequence of times in microseconds. Those starting with a plus sign are pulses and those with a minus are silences. Now you need to be clever and try to find the patterns. If the remote has multiple buttons, press different ones (perhaps in combination).
The example is from a Roomba 5xx virtual wall. (I feel it's missing a bit. This is something for future exploration.)