How to build GPL3+ WiFi IR Blaster

SUPPLIES AND COMPONENTS

  • Espressif ESP8266 ESP-01
  • Arduino Nano R3
  • General Purpose Transistor NPN
  • Adafruit IR LEDs

TOOLS AND MACHINES REQUIRED

  • Soldering iron (generic)

CONTENT AND ONLINE SERVICES

  • Arduino IDE
  • NodeMCU Firmware

THIS PROJECT’S CONTEXT

Moving Forward Two Steps

Since it appears like the main phone manufacturers are going away from integrated IR blasters, I’m searching for a solution to future-proof my “dumb” devices that don’t yet interact with my IoT system. I live in a smart home with a mesh network, but how can I switch on my TV without a real remote? There must be a better approach.

I just created an IR blaster that manages my house’s standalone air conditioners. So that I can push buttons on my phone and have IR signals relayed to my media centres, all I need to do is merge these two things into a web-connected IR blaster with a webapp interface. The uses for these are quite varied because they can be installed in every area and managed from a single interface.

Prototyping quickly

I assembled all the necessary wiring using some leftover gear so that I could connect to my WiFi, accept HTTP connections, and transmit IR signals.

This was successful in that I was able to request a certain URL using the ESP8266’s IP address and have the Arduino send the IR LED a “Power” signal. To have the URLs sent to the IR blaster and then to have those requests processed into certain IR codes, I just needed to figure out where to host the webapp. The ESP8266’s AT instructions were quite laborious to use for this, and there was a 1-2 second wait between delivering the request and the LED flashing.

NodeMCU

Because the NodeMCU ESP8266 development board features internal power regulation, a USB to serial interface, and a tonne of I/O pins in such a compact package (and is breadboard friendly), I decided to go with it. As it turns out, the standard IRremote library functions right out of the box and there is already an ESP8266 WebServer module that takes GET requests that I was able to adapt for delivering IR codes.

I will run the webapp on an underused C.H.I.P. $9 computer that is already connected to my home network as the ESP8266 on the NodeMCU isn’t strong enough to host it directly. The ESP8266’s 3.3v board is another drawback, making the IR LEDs powered directly from an I/O pin very faint and necessitating close proximity to the appliance it was sending a signal to. Instead, I drove a npn transistor using the I/O pin to turn the 5 volt supply voltage on and off.

REST

After some searching, I discovered a fantastic guide for setting up an Arduino REST API to control LEDs on Adafruit. I loaded it on the CHIP, which is already running apache but could just as well be hosted on the cloud, and utilised their JavaScript and PHP scripts to send the cURL queries. I made a simple HTML page for the remote control and then included the icon and manifest.json files so that it could function as a native webapp on an Android phone.

I simplified the webserver on the Arduino side so that it would just take a GET request, read the URL, send a 200 OK response, and then disconnect. The Arduino would communicate the IR code that was assigned to the pushed button based on the URL.

Universal Remonster

With no delays, everything is operating beautifully. I’ve learned that the IR LEDs I’m using have a pretty limited angle, so I have to be cautious about how they are oriented. I can install it behind a shelf, out of sight, and it will stay aimed at the IR receivers on the other end thanks to some industrial velcro. To allow for finer tuning, I attached additional thick gauge wire to the LED leads.

The transistor is powering the IR LEDs with an unregulated 5v because resistors rendered them nearly useless. This gives me the ability to reflect IR off walls and place the blaster across the room, but it will undoubtedly shorten the weapon’s lifespan. I’m hoping I won’t need to replace the LEDs for a while because they are used very seldom and the IR signals have extremely brief pulses (lower duty cycle than most dim PWM signals). For version 2, I made care to purchase some IR LEDs with a wider field of view and higher power, so when these eventually fail, I’ll replace them and add an inline resistor.

CODE

WiFi IR Blaster.ino

#include <ESP8266WiFi.h>
#include <IRremoteESP8266.h>
#include <ESP8266mDNS.h>

const char* ssid = "AP_SSID";
const char* password = "AP_Pass";
MDNSResponder mdns;

int khz = 38; // 38kHz carrier frequency for both NEC and Samsung

IRsend irsend(4); //an IR led is connected to GPIO4 (pin D2 on NodeMCU)

  // Insert RAW IR signal for "TV Power"
unsigned int irTVpwr[] = {4650,4250, 700,1550, 650,1550, 700,1550, 650,450, 650,500, 600,500, 600,500, 600,550, 550,1700, 550,1650, 600,1650, 550,550, 600,500, 600,550, 550,550, 600,500, 600,550, 550,1650, 600,550, 550,550, 600,500, 600,550, 550,550, 600,500, 600,1650, 600,500, 600,1650, 550,1700, 550,1650, 600,1650, 550,1650, 600,1650, 600};  // SAMSUNG E0E040BF

  // Insert RAW IR signal for "TV Source"
unsigned int irTVsrc[] = {4600,4300, 700,1550, 650,1550, 650,1600, 650,450, 650,450, 600,550, 550,550, 600,500, 600,1650, 550,1650, 600,1650, 550,550, 600,500, 600,550, 550,550, 550,550, 600,1650, 550,550, 550,550, 600,500, 600,500, 600,550, 550,550, 600,500, 600,550, 550,1650, 550,1700, 550,1650, 600,1600, 600,1650, 600,1600, 600,1650, 550};  // SAMSUNG E0E0807F
  
  // Insert RAW IR signal for "TV Mute"
unsigned int irTVmute[] = {4650,4250, 700,1550, 650,1550, 700,1550, 650,450, 650,500, 600,500, 600,500, 600,500, 600,1650, 600,1600, 600,1650, 550,550, 600,500, 600,550, 550,550, 600,500, 600,1650, 550,1650, 600,1650, 550,1650, 600,550, 550,550, 550,550, 600,500, 600,550, 550,550, 550,550, 600,500, 600,1650, 550,1650, 600,1650, 550,1650, 600};  // SAMSUNG E0E0F00F
  
  // Insert RAW IR signal for "TV Volume Down"
unsigned int irTVvdn[] = {4650,4250, 700,1550, 650,1550, 700,1550, 650,450, 650,450, 650,450, 600,550, 550,550, 600,1650, 550,1650, 550,1650, 600,550, 550,550, 550,550, 600,500, 600,500, 600,1650, 600,1600, 600,500, 600,1650, 550,550, 600,500, 600,500, 600,550, 550,550, 600,500, 600,1650, 550,550, 550,1650, 600,1650, 550,1650, 600,1650, 550};  // SAMSUNG E0E0D02F
  
  // Insert RAW IR signal for "TV Volume Up"
unsigned int irTVvup[] = {4600,4300, 650,1600, 650,1550, 650,1600, 600,500, 600,550, 600,500, 600,550, 550,550, 550,1700, 550,1650, 600,1650, 550,550, 600,500, 600,550, 550,550, 600,500, 600,1650, 600,1650, 550,1650, 600,550, 550,550, 600,500, 600,550, 550,550, 600,500, 600,550, 550,550, 600,1600, 600,1650, 600,1650, 550,1650, 600,1650, 600};  // SAMSUNG E0E0E01F
  
  // Insert RAW IR signal for "TV Channel Up"
unsigned int irTVchup[] = {4650,4250, 700,1550, 650,1600, 650,1550, 650,500, 600,500, 600,500, 650,500, 600,500, 600,1650, 550,1650, 600,1650, 600,500, 600,500, 600,550, 550,550, 600,550, 550,550, 550,1650, 600,550, 600,500, 600,1650, 550,550, 600,500, 600,550, 550,1650, 600,550, 550,1650, 600,1650, 600,500, 600,1650, 600,1600, 600,1650, 600};  // SAMSUNG E0E048B7
  
  // Insert RAW IR signal for "TV Channel Down"
unsigned int irTVchdn[] = {4600,4350, 650,1550, 650,1600, 650,1600, 600,500, 600,500, 600,550, 550,550, 600,550, 550,1650, 600,1650, 550,1700, 550,550, 550,550, 600,500, 600,550, 550,550, 600,500, 600,550, 550,550, 550,550, 600,1650, 600,500, 600,500, 600,550, 550,1650, 600,1650, 600,1650, 550,1650, 600,550, 550,1650, 600,1650, 600,1650, 550};  // SAMSUNG E0E008F7
  
  // Insert RAW IR signal for "Receiver Power"
unsigned int irRECpwr[] = {9050,4350, 650,500, 600,1600, 600,500, 650,500, 600,1600, 600,550, 600,1600, 600,1650, 550,550, 600,500, 600,1600, 650,1600, 600,500, 600,1650, 600,1600, 600,500, 600,1650, 600,1600, 600,550, 600,1600, 600,500, 600,550, 600,1600, 600,1600, 650,500, 600,500, 600,1600, 650,500, 600,1600, 600,1650, 600,500, 600,500, 600};  // NEC 4B36D32C

  // Insert RAW IR signal for "Receiver Power On"
unsigned int irRECpwrON[] = {9000,4400, 600,550, 600,1600, 600,500, 600,550, 600,1600, 600,500, 600,1600, 650,1600, 600,1600, 600,500, 650,1600, 600,1600, 600,500, 650,1600, 600,1600, 600,500, 600,550, 600,500, 600,1600, 600,550, 600,500, 600,500, 650,500, 600,500, 600,1600, 650,1600, 600,500, 600,1600, 650,1600, 600,1600, 600,1600, 600,1600, 650};  // NEC 4BB620DF
  
  // Insert RAW IR signal for "Receiver Power Off"
unsigned int irRECpwrOFF[] = {9000,4400, 600,550, 550,1650, 600,550, 550,550, 600,1650, 550,550, 600,1650, 550,1650, 600,550, 550,550, 550,1650, 600,1650, 600,550, 550,1650, 600,1650, 550,550, 600,1650, 550,1650, 600,1650, 600,500, 600,550, 550,550, 600,1650, 550,550, 600,500, 600,550, 550,550, 550,1700, 550,1650, 600,1650, 550,550, 600,1650, 550};  // NEC 4B36E21D

  // Insert RAW IR signal for "Receiver Mute"
unsigned int irRECmute[] = {9000,4400, 650,450, 650,1600, 600,500, 600,500, 650,1600, 600,500, 600,1650, 600,1600, 600,1600, 650,500, 600,1600, 650,1600, 600,500, 600,1600, 650,1600, 600,500, 600,1650, 600,500, 600,1600, 650,500, 600,500, 600,500, 600,500, 650,500, 600,500, 600,1600, 650,500, 600,1600, 600,1600, 650,1600, 600,1650, 600,1600, 600};  // NEC 4BB6A05F
  
  // Insert RAW IR signal for "Receiver Volume Down"
unsigned int irRECvdn[] = {9150,4250, 750,350, 700,1550, 700,400, 700,450, 650,1550, 700,450, 600,1600, 650,1600, 600,1650, 600,500, 600,1650, 600,1600, 600,550, 600,1600, 600,1650, 600,500, 600,1650, 600,1600, 650,500, 600,500, 600,500, 650,500, 600,500, 600,500, 600,550, 600,500, 600,1650, 600,1600, 600,1650, 600,1650, 600,1600, 600,1650, 600};  // NEC 4BB6C03F
  
  // Insert RAW IR signal for "Receiver Volume Up"
unsigned int irRECvup[] = {9050,4400, 650,500, 600,1600, 600,550, 600,500, 600,1650, 600,500, 600,1600, 650,1600, 600,1600, 600,550, 600,1600, 600,1600, 650,500, 600,1600, 650,1600, 600,500, 600,550, 600,1600, 600,550, 600,500, 600,550, 600,500, 600,550, 600,500, 600,1600, 650,500, 600,1600, 600,1650, 600,1600, 600,1650, 600,1600, 600,1600, 600};  // NEC 4BB640BF

  // Insert RAW IR signal for "Receiver Source CBL/SAT"
unsigned int irRECsrc[] = {8950,4450, 600,500, 600,1650, 600,500, 600,500, 600,1650, 600,500, 600,1600, 600,1650, 600,1600, 600,550, 600,1600, 600,1650, 600,500, 600,1600, 600,1650, 600,500, 600,500, 600,1650, 600,1600, 600,1650, 600,500, 600,500, 600,500, 650,500, 600,1600, 600,500, 600,550, 600,500, 600,1600, 600,1650, 600,1600, 600,1650, 600};  // NEC 4BB6708F

// Create an instance of the server
// specify the port to listen on as an argument
WiFiServer server(80);


void setup() {
  Serial.begin(115200);
  delay(10);

  irsend.begin();
  
  // Connect to WiFi network
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi Connected");

  // Start the server
  server.begin();
  Serial.println("HTTP Server Started");

  // Print the IP address
  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());

  if (mdns.begin("IRBlasterLR", WiFi.localIP())) {
    Serial.println("MDNS Responder Started");
  }

  Serial.println();
  Serial.println();
}

void loop() {
  // Check if a client has connected
  WiFiClient client = server.available();
  if (!client) {
    return;
  }
  
  // Wait until the client sends some data
  Serial.println("new client");
  while(!client.available()){
    delay(1);
  }
  
  // Read the first line of the request
  String req = client.readStringUntil('\r');
  Serial.println(req);
  client.flush();
  
  // Match the request
  if (req.indexOf("/irTVpwr") != -1){
      irsend.sendRaw(irTVpwr, sizeof(irTVpwr) / sizeof(irTVpwr[0]), khz);   
      Serial.println("IRreq irTVpwr sent");
  }
  else if (req.indexOf("/irTVsrc") != -1){
      irsend.sendRaw(irTVsrc, sizeof(irTVsrc) / sizeof(irTVsrc[0]), khz);   
      Serial.println("IRreq irTVsrc sent");
  }
  else if (req.indexOf("/irTVmute") != -1){
      irsend.sendRaw(irTVmute, sizeof(irTVmute) / sizeof(irTVmute[0]), khz);   
      Serial.println("IRreq irTVmute sent");
  }
  else if (req.indexOf("/irTVvdn") != -1){
      irsend.sendRaw(irTVvdn, sizeof(irTVvdn) / sizeof(irTVvdn[0]), khz);   
      Serial.println("IRreq irTVvdn sent");
  }
  else if (req.indexOf("/irTVvup") != -1){
      irsend.sendRaw(irTVvup, sizeof(irTVvup) / sizeof(irTVvup[0]), khz);   
      Serial.println("IRreq irTVvup sent");
  }
  else if (req.indexOf("/irTVchup") != -1){
      irsend.sendRaw(irTVchup, sizeof(irTVchup) / sizeof(irTVchup[0]), khz);   
      Serial.println("IRreq irTVchup sent");
  }
  else if (req.indexOf("/irTVchdn") != -1){
      irsend.sendRaw(irTVchdn, sizeof(irTVchdn) / sizeof(irTVchdn[0]), khz);   
      Serial.println("IRreq irTVchdn sent");
  }
  else if (req.indexOf("/irALLpwr") != -1){
      irsend.sendRaw(irRECpwrON, sizeof(irRECpwrON) / sizeof(irRECpwrON[0]), khz);   
      irsend.sendRaw(irTVpwr, sizeof(irTVpwr) / sizeof(irTVpwr[0]), khz);   
      delay(2000);
      irsend.sendRaw(irRECsrc, sizeof(irRECsrc) / sizeof(irRECsrc[0]), khz);         
      Serial.println("IRreq irALLpwr sent");
  }
  else if (req.indexOf("/irRECpwr") != -1){
      irsend.sendRaw(irRECpwr, sizeof(irRECpwr) / sizeof(irRECpwr[0]), khz);   
      Serial.println("IRreq irRECpwr sent");
  }
  else if (req.indexOf("/irRECpwrON") != -1){
      irsend.sendRaw(irRECpwrON, sizeof(irRECpwrON) / sizeof(irRECpwrON[0]), khz);   
      Serial.println("IRreq irRECpwrON sent");
  }
  else if (req.indexOf("/irRECpwrOFF") != -1){
      irsend.sendRaw(irRECpwrOFF, sizeof(irRECpwrOFF) / sizeof(irRECpwrOFF[0]), khz);   
      Serial.println("IRreq irRECpwrOFF sent");
  }
  else if (req.indexOf("/irRECmute") != -1){
      irsend.sendRaw(irRECmute, sizeof(irRECmute) / sizeof(irRECmute[0]), khz);   
      Serial.println("IRreq irRECmute sent");
  }  
  else if (req.indexOf("/irRECvdn") != -1){
      irsend.sendRaw(irRECvdn, sizeof(irRECvdn) / sizeof(irRECvdn[0]), khz);   
      Serial.println("IRreq irRECvdn sent");
  }
  else if (req.indexOf("/irRECvup") != -1){
      irsend.sendRaw(irRECvup, sizeof(irRECvup) / sizeof(irRECvup[0]), khz);   
      Serial.println("IRreq irRECvup sent");
  } 
  else {
    Serial.println("invalid request");
    client.stop();
    return;
  }
  
  client.flush();
   
  // Send the response to the client
  //client.print(s);
  client.print("HTTP/1.1 200 OK\r\n");
  delay(1);
  Serial.println("Client Disconnected");
  Serial.println();
  // The client will actually be disconnected 
  // when the function returns and 'client' object is detroyed
}

script.js

// Function to send IR commands
function buttonClick(clicked_id){

    if (clicked_id == "irTVpwr"){
        $.get( "curl.php", {
        room: "192.168.1.62", button: "irTVpwr"} );  
    } 

    if (clicked_id == "irTVsrc"){
        $.get( "curl.php", {
        room: "192.168.1.62", button: "irTVsrc"} );  
    } 

    if (clicked_id == "irTVmute"){
        $.get( "curl.php", {
        room: "192.168.1.62", button: "irTVmute"} );  
    } 

    if (clicked_id == "irTVvdn"){
        $.get( "curl.php", {
        room: "192.168.1.62", button: "irTVvdn"} );  
    } 

	if (clicked_id == "irTVvup"){
        $.get( "curl.php", {
        room: "192.168.1.62", button: "irTVvup"} );  
    } 
	
    if (clicked_id == "irTVchup"){
        $.get( "curl.php", {
        room: "192.168.1.62", button: "irTVchup"} );  
    } 

	if (clicked_id == "irTVchdn"){
        $.get( "curl.php", {
        room: "192.168.1.62", button: "irTVchdn"} );  
    } 
	
    if (clicked_id == "irRECpwr"){
        $.get( "curl.php", {
        room: "192.168.1.62", button: "irRECpwr"} );  
    } 

    if (clicked_id == "irALLpwr"){
        $.get( "curl.php", {
        room: "192.168.1.62", button: "irALLpwr"} );  
    } 

	if (clicked_id == "irRECpwrON"){
        $.get( "curl.php", {
        room: "192.168.1.62", button: "irRECpwrON"} );  
    } 

	if (clicked_id == "irRECpwrOFF"){
        $.get( "curl.php", {
        room: "192.168.1.62", button: "irRECpwrOFF"} );  
    } 

    if (clicked_id == "irRECmute"){
        $.get( "curl.php", {
        room: "192.168.1.62", button: "irRECmute"} );  
    } 

    if (clicked_id == "irRECvdn"){
        $.get( "curl.php", {
        room: "192.168.1.62", button: "irRECvdn"} );  
    } 

	if (clicked_id == "irRECvup"){
        $.get( "curl.php", {
        room: "192.168.1.62", button: "irRECvup"} );  
    } 
 
}

index.html

<html>
<head>
<LINK href="style.css" rel="stylesheet" type="text/css" />

<script type="text/javascript" src="jquery-2.0.3.min.js"></script>
<script type="text/javascript" src="script.js"></script>

<link rel="manifest" href="manifest.json">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel="icon" type="image/png" href="favicon.ico" />

</head>

<body>
	<div align="center" class="div_text_shadow">
	  <button class="btn2" type="button" id="irALLpwr" onClick="buttonClick(this.id)">Power Up Everything</button><BR><BR><BR>
		<H1>TV</H1>
			<button class="btn" type="button" id="irTVpwr" onClick="buttonClick(this.id)">Power</button><BR><BR>
			<button class="btn" type="button" id="irTVvup" onClick="buttonClick(this.id)">Vol +</button>
			<button class="btn" type="button" id="irTVsrc" onClick="buttonClick(this.id)">Source</button>
			<button class="btn" type="button" id="irTVchup" onClick="buttonClick(this.id)">Ch +</button><BR><BR>
			<button class="btn" type="button" id="irTVvdn" onClick="buttonClick(this.id)">Vol -</button>
			&nbsp;&nbsp;
			<button class="btn" type="button" id="irTVmute" onClick="buttonClick(this.id)">&nbsp;Mute&nbsp;</button>
			&nbsp;&nbsp;
			<button class="btn" type="button" id="irTVchdn" onClick="buttonClick(this.id)">Ch -</button>
			<br>
			<br>
			<br>
		<H1>Receiver</H1>
			<button class="btn" type="button" id="irRECpwr" onClick="buttonClick(this.id)">Power</button><BR><BR>
			<button class="btn" type="button" id="irRECmute" onClick="buttonClick(this.id)">Mute</button>
			<button class="btn" type="button" id="irRECvdn" onClick="buttonClick(this.id)">Vol -</button>
			<button class="btn" type="button" id="irRECvup" onClick="buttonClick(this.id)">Vol +</button>
			<br>
			<br>
	</div>
</body>
</html>

curl.php

<?php

  $room = $_GET['room'];
  $button = $_GET['button'];

  // Create cURL call
  $service_url = 'http://' . $room . '/' . $button;
  $curl = curl_init($service_url);
   
  // Send cURL to Yun board
  curl_setopt($curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); 
  $curl_response = curl_exec($curl);
  curl_close($curl);

  //Print answer
  echo $curl_response;

?>

manifest.json

{
  "name": "WiFi Remote",
  "icons": [
	{
      "src": "remote_icon_36.png",
      "sizes": "36x36",
      "type": "image/png",
      "density": 0.75
    },
    {
      "src": "remote_icon_48.png",
      "sizes": "48x48",
      "type": "image/png",
      "density": 1.0
    },
    {
      "src": "remote_icon_128.png",
      "sizes": "128x128",
      "type": "image/png",
      "density": 1.0
    },
    {
      "src": "remote_icon_192.png",
      "sizes": "192x192",
      "type": "image/png",
      "density": 1.0
    }
  ],
  "scope": "/remote/",
  "start_url": "/remote/index.html",
  "display": "fullscreen",
  "orientation": "portrait"
}

SCHEMATICS

There isn’t much to it; all you need is electricity and one pin linked to an NPN transistor to run two IR LEDs in series (without resistors) from a 5 volt supply.

You may use an NPN transistor to switch several PNP transistors if you need to operate more LEDs or require less voltage loss from a 3.3v source (one per LED). You may use as many LEDs in this set up as your source voltage can support in terms of current flow.

Good things take a time, Make it count

KP

“Aim the best, Be the Best” – KP

Giving is Winning

KP

Helping Hands.

About the author

pondabrothers

View all posts

Leave a Reply

Your email address will not be published.