Latch Hristov
Published © MIT

Web-Based Universal Remote for Under $4 (Probably)

Don't buy a universal remote, make one. It's cheaper, funner, universal-er, kind of, and you get to learn the ESP8266, kind of.

BeginnerShowcase (no instructions)6 hours8,187
Web-Based Universal Remote for Under $4 (Probably)

Things used in this project

Hardware components

ESP8266 ESP-01
Espressif ESP8266 ESP-01
×1
IR LED and Photodiode
×1
AMS1117 Voltage Regulator 3.3V
×1
prototyping board
×1

Story

Read more

Schematics

Sch

Code

index.html

HTML
Interface page
<!DOCTYPE html>
<html>
<head>
<style>
body {
  background: #3f4d63;
}
.wrap {
    text-align: center;
}
.btn {
    color: white;
    padding: 32px 0px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    font-size: 32px;
    margin: 8px 0px;
    -webkit-transition-duration: 0.5s; /* Safari */
    transition-duration: 0.5s;
    cursor: pointer;
    border-radius: 16px;
    width: 200px;
    outline:0;
}
.btn:active{
   -webkit-transition-duration: 0.0s; /* Safari */
    transition-duration: 0.0s
   color: black;
   background-color: #a8a8a8;
}

@media only screen and (min-device-width: 800px) {
   .btn:hover{ border: 4px solid white; }
}
.grn {
    background-color: #4CAF50;
    border: 4px solid #4CAF50;
}
.blu {
   background-color: #008CBA;
   border: 4px solid #008CBA;
}
.red {
   background-color: #f44336;
   border: 4px solid #f44336;
}
.purp {
   background-color: #873b79;
   border: 4px solid #873b79;
}

</style>
</head>
<body>

<div class="wrap">
<button onclick="sendcmd('NEC-1637937167-32')" class="btn red">POWER</button>
<br>
<button onclick="sendcmd('NEC-1637931047-32')" class="btn blu">BACK</button>
<button onclick="sendcmd('NEC-1637886167-32')" class="btn blu">HOME</button>
<br>
<button onclick="sendcmd('NEC-1637892797-32')" class="btn purp">UP</button>
<br>
<button onclick="sendcmd('NEC-1637902487-32')" class="btn purp">LEFT</button>
<button onclick="sendcmd('NEC-1637882087-32')" class="btn purp">OK</button>
<button onclick="sendcmd('NEC-1637918807-32')" class="btn purp">RIGHT</button>
<br>
<button onclick="sendcmd('NEC-1637925437-32')" class="btn purp">DOWN</button>
<br>
<button onclick="sendcmd('NEC-1637888207-32')" class="btn grn">VOL+</button>
<button onclick="sendcmd('NEC-1637920847-32')" class="btn grn">VOL-</button>
<button onclick="sendcmd('NEC-1637904527-32')" class="btn grn">MUTE</button>
<br>
<button onclick="sendcmd('NEC-3258182271-32')" class="btn blu">RET</button>
<button onclick="sendcmd('NEC-1637926967-32')" class="btn blu">SLEEP</button>
<button onclick="sendcmd('NEC-1637935127-32')" class="btn blu">OPTIONS</button>
<br>
<button onclick="sendcmd('NEC-1637908097-32')" class="btn blu">RWD</button>
<button onclick="sendcmd('NEC-1637879537-32')" class="btn blu">PLAY</button>
<button onclick="sendcmd('NEC-1637924417-32')" class="btn blu">FWD</button>
</div>

<script>
function sendcmd(cmd) {
  var xhttp = new XMLHttpRequest();
  xhttp.open("POST", cmd, true);
  xhttp.send();
}
</script>
</body>
</html>

Main code

C/C++
/* So this is a running server to access a file in the SPIFFS files sytems
 *  made by FS.h.  Go to tools, ESP sketch data upload, and it loads a file system based on everything in the 'data' folder
 *  contained in the sketch's main folder.  Server is more stable than previous, with client and system timeouts managed at top.
 *  Now has shorter, and slightly smarter code, and does not crash (probably).
 */  

#include <ESP8266WiFi.h>
#include "FS.h"
#include <IRremoteESP8266.h>

#define CLIENT_TIMEOUT 5.0     //client timeout in seconds when waiting to become "ready"
const char* ssid = "yourwifi";
const char* password = "yourwifipassword";

// Globals
WiFiServer server(80);
WiFiClient client;
IRsend irsend(2);

// ------------- SIMPLIFICATION---------------
int sendpage(String filename){
  
  File f = SPIFFS.open(filename, "r");
  if (!f) {
      Serial.print(filename);
      Serial.println(": open failed");
      return 1;
  }
  String s;
  if (filename.indexOf(".jpg") != -1)  {  //if it's a jpeg
     s = "HTTP/1.1 200 OK\r\nContent-Type: image/jpg\r\nContent-Transfer-Encoding: binary\r\n\r\n";
  }
  else if (filename.indexOf(".htm") != -1) {
     s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
  }
  else {
    s= "";
  }
  
  long buf_size = 500;
  long bufi = 0;
  for (int i = 0; i < f.size(); i++){
     bufi++;
     s+= (char)f.read();
     if (bufi > buf_size-1){
        bufi = 0;
        client.print(s);
        s="";
     }
  }
  client.print(s);
  f.close();
  delay(1);
  return 0;
}
int wait_for_client(){    //but not indefinitedly
  // Check if a client has connected
  client = server.available();
  if (!client) {
    delay(100);
    return 1;
  }
  long ite = CLIENT_TIMEOUT * 10;          //ite is iteration, ten per second
  while(!client.available()){
    delay(100);
    ite -= 1;
    if (ite < 1) {
      Serial.println("Client timeout");     //client connected but never became "ready."  This happens for some reason.
      client.stop();
      return 1; 
    }
  } 
  //while(1) {delay(100);}
  return 0;
}
// --------------IR --------------
bool send_ir_cmd(String cmd){     //returns true if command was sent sucessfully, obviously.  accepts format "IRFORMAT-Data-datalengthinbits" example: "NEC-5132547896-32"
  if (cmd.indexOf("NEC") != -1){   //okay, NEC, we know howo to do this
      long data = parse_long_from_string(cmd, cmd.indexOf("-")+1, cmd.lastIndexOf("-")-1);
      int datalen = parse_int_from_string(cmd, cmd.lastIndexOf("-")+1, cmd.length() -1);
      irsend.sendNEC(data, datalen);
      delay(40);
  }
  else {                         // elseifs for other than NEC belong here somewhere
    return false;
  }
} 
int parse_int_from_string(String inputstr, int startindex, int endindex) {
  int i = startindex;
  int res = 0;
  while (i <= endindex)  {
    res = res * 10 + (uint8_t)inputstr.charAt(i) - '0';
    i++;
  }
  return res;
}
long parse_long_from_string(String inputstr, int startindex, int endindex) {
  int i = startindex;
  long res = 0;
  while (i <= endindex)  {
    res = res * 10 + (uint8_t)inputstr.charAt(i) - '0';
    i++;
  }
  return res;
}
//------------------------------SETUP----------------------
void setup() {
  //IR
  irsend.begin();
  delay(10);
    
  //Serial
  Serial.begin(74880);
  delay(10);
  
  //WiFi
  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");
  server.begin();
  Serial.println("Server started");
  Serial.println(WiFi.localIP());

  //file system
  if (SPIFFS.begin()){
    Serial.println("File system mounted successfully");
  }
  Dir dir = SPIFFS.openDir("/");
  while (dir.next()) {
    Serial.print(dir.fileName());
    File f = dir.openFile("r");
    Serial.print(" : ");
    Serial.println(f.size());
    f.close();
    }
   
}
//-------------------------------------MAIN LOOP-------------------
void loop() {
  //waiting for connection
  if (wait_for_client()) return;   //wait_for_client returns non-zero if the wait was not fruitful

  // A client!
  // Read the request
  String req = client.readStringUntil(' ');
  req = client.readStringUntil(' ');
  Serial.println(req);
  client.flush();

  if (req.indexOf("favicon.ico") !=-1){      //google chrome sends several of these annoying requests, i ignore them
    client.stop();
    return;
  }
  else if(req.length() == 1){                //if the request is only a "/", then just serve the index page
     req = "/index.htm";
     sendpage(req);
  }
  else if(req.indexOf(".") == -1){// if there is not "." so it's not a file in the system.. probably
      if (!send_ir_cmd(req)){
         //not an ir command
         Serial.println("Not recognized" );
      }
      else {
          //req = "/index.htm";
          //sendpage(req);
      }
  }
  else {
     if (sendpage(req)){    //if page not found
        req = "/not_found.htm";
        sendpage(req);
     }
  }

  client.stop();
  // The client will actually be disconnected 
  // when the function returns and 'client' object is detroyed
}

Credits

Latch Hristov

Latch Hristov

1 project • 4 followers
Bored, with a cheap hobby.

Comments