ESP32: How to create a Infrared Remote Control Webserver

ESP32 Tutorial: Infrared Remote Control Webserver with ESP32 and KY-005

Abstract

This article guides you through creating a self-hosted web application on an ESP32 microcontroller. This application serves a simple webpage with virtual remote control keys (Power, Volume, Channel, etc.) and translates key presses into Infrared (IR) signals using the KY-005 IR Transmitter module.

1. Introduction: Infrared Remote Control Webserver with ESP32 and KY-005

In the modern connected home, traditional Infrared (IR) remote controls often represent a gap between legacy appliances (like TVs, stereos, and air conditioners) and the convenience of network-based control. This project bridges that gap using the powerful ESP32 microcontroller as a Smart Home Gateway.

This tutorial will guide you through creating a self-hosted, full-featured Web Server on the ESP32. This server hosts a sleek, responsive web page—essentially a virtual remote control—that can be accessed and operated from any web browser on your local network (e.g., your smartphone or laptop).

The core function of the application is to translate digital input (a button click on the web page) into a physical, modulated Infrared signal using the affordable KY-005 IR Transmitter module and the specialized IRremote library. By mapping specific Universal Resource Identifiers (URIs) on the web server to corresponding IR hex codes (like those for Power, Volume, and Channel), you will learn how to send commands that can instantly control any IR-compatible device, bringing your older electronics into the age of Wi-Fi control. This project provides a practical foundation for understanding how to integrate network communication and physical I/O control in a single IoT device.

2. Prerequisites and Wiring

2.1 Hardware

  • ESP32 Development Board (any model, e.g., ESP32-WROOM-32)
  • KY-005 IR Transmitter Module (or any 3-pin IR LED module)
  • Breadboard and Jumper Wires
  • Micro-USB cable for power and programming

2.2 Software

  • Arduino IDE (or VS Code with PlatformIO)
  • ESP32 Board Support Package installed in the Arduino IDE.
  • Required Libraries:
    1. ESP WebServer: For creating the HTTP web server on the ESP32.
    2. IRremote: A powerful library for sending and receiving IR signals that works with several boards, including the ESP devices.

To install the libraries, open the Arduino IDE, go to Sketch > Include Library > Manage Libraries…, and search for the names above.

2.3 Wiring the KY-005 IR Transmitter

The KY-005 module typically has three pins: Signal (S), VCC (+), and GND (-).

KY-005 Pin

ESP32 Pin

Note

S (Signal)

GPIO 4

This is the dedicated IR output pin in the code below.

+ (VCC)

5V

Power supply for the module.

– (GND)

GND

Ground connection.

Important Note: GPIO 4 is used as a standard example. You can change the IR_SEND_PIN in the code to any suitable ESP32 GPIO pin.

Add a 100-220 Ohm resistor at R1 position on KY-005.

3. The ESP32 Arduino Sketch

The code has three main parts: WiFi & Server Setup, the HTML webpage, and the IR code sending logic. This example will rely on a Samsung TV, but other models and patterns can be used, just edit the sendIrCommand() function.

3.1 Define WiFi and IR Settings

First, include the necessary libraries and define your network credentials and the GPIO pin for the IR LED.

				
					#include <WiFi.h>
#include <WebServer.h>
#include <IRremote.h>
#include "image_data.h"

// --- USER CONFIGURATION ---
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const int IR_TX_PIN = 4; // KY-005 Signal pin connected to GPIO 4
// --------------------------

// --- IR CODES (NEC PROTOCOL EXAMPLE) ---
// --- IR CODES (NEC PROTOCOL) ---
// Codes extracted from your switch-case block
#define POWER_TOGGLE_CODE         0xFD020707 // power toggle: Universal Remote
#define VOL_UP_CODE               0xF8070707 // volume up
#define VOL_DOWN_CODE             0xF40B0707 // volume down
#define MUTE_CODE                 0xF00F0707 // mute / vol push
#define CH_UP_CODE                0xED120707 // channel up - universal
#define CH_DOWN_CODE              0xEF100707 // channel down - universal
#define CH_LIST_CODE              0x946B0707 // channel list (using the 4-byte version)
#define UP_CODE                   0x9F600707 // up - Universal
#define DOWN_CODE                 0x9E610707 // down - Universal
#define LEFT_CODE                 0x9A650707 // left - Universal
#define RIGHT_CODE                0x9D620707 // right - Universal
#define OK_ENTER_CODE             0x97680707 // ok / enter - universal
#define MENU_CODE                 0xE51A0707 // menu - universal
#define RETURN_GO_BACK_CODE       0xEC130707 // return / go back - universal (using the 4-byte version)
#define EXIT_CODE                 0xD22D0707 // exit - universal
#define INPUT_CODE                0xFE010707 // input - universal
#define DIGITAL_1_CODE            0xFB040707 // digital 1
#define DIGITAL_2_CODE            0xFA050707 // digital 2
#define DIGITAL_3_CODE            0xF9060707 // digital 3
#define DIGITAL_4_CODE            0xF7080707 // digital 4
#define DIGITAL_5_CODE            0xF6090707 // digital 5
#define DIGITAL_6_CODE            0xF50A0707 // digital 6
#define DIGITAL_7_CODE            0xF30C0707 // digital 7
#define DIGITAL_8_CODE            0xF20D0707 // digital 8
#define DIGITAL_9_CODE            0xF10E0707 // digital 9
#define DIGITAL_0_CODE            0xEE110707 // digital 0

// Server object on port 80 (standard HTTP)
WebServer server(80);

				
			

3.2 The Webpage HTML (handleRoot())

This function generates the main webpage with the remote control buttons. Each button is an <a> tag that links to a specific URI (e.g., /power), which the server will intercept.

				
					// Function to send a specific IR command
void sendIrCommand(uint32_t code, const char* name) {
// Extract the 16-bit command (upper 16 bits of the 32-bit code)
  uint16_t command = (uint16_t)(code >> 16);
 
  // Extract the 16-bit address (lower 16 bits of the 32-bit code)
  uint16_t address = (uint16_t)(code & 0xFFFF);

  // Send the code using the Samsung protocol
  // The address and command are sent once (1 repeat)
  IrSender.sendSamsung(address, command, 1);

  // Print a more informative message for debugging
  Serial.printf("Sent Samsung IR Code - Address: 0x%X, Command: 0x%X for command: %s\n",
                (unsigned int)address, (unsigned int)command, name);
}

// HTML content for the web server
String get_html_page() {
  String html = R"rawliteral(
    <!DOCTYPE html>
    <html>
    <head>
      <title>ESP32 Hacker Remote</title>
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <style>
        body {
          font-family: 'Consolas', monospace;
          text-align: center;
          margin: 0;
          padding: 20px;
          background-color: #2b2b2b; /* Dark background */
          color: #00f5a0; /* Green text */
        }
        h1, h2 {
          color: #00f5a0;
          text-shadow: 0 0 5px rgba(0,245,160,0.5); /* Subtle glow */
        }
        .logo-container {
          margin-bottom: 20px;
        }
        .logo {
          max-width: 100px; /* Adjust as needed */
          height: auto;
          filter: drop-shadow(0 0 8px #00f5a0); /* Glow effect for the logo */
        }
        .remote-section {
          display: grid;
          grid-template-columns: repeat(3, 1fr);
          gap: 12px;
          max-width: 380px; /* Slightly wider for better spacing */
          margin: 25px auto;
          padding: 20px;
          border: 1px solid #007bff; /* Blue border */
          border-radius: 15px;
          background-color: #2b2b2b;
          box-shadow: 0 0 15px rgba(0,123,255,0.5); /* Blue glow around sections */
        }
        .btn {
          padding: 18px 10px;
          font-size: 16px;
          text-decoration: none;
          color: #00f5a0; /* Green text on buttons */
          border-radius: 8px;
          transition: background-color 0.2s, box-shadow 0.2s;
          box-shadow: 0 0 8px rgba(0,245,160,0.3); /* Subtle green glow */
          background-color: #3a3a3a; /* Darker button background */
          border: 1px solid #00f5a0; /* Green border for buttons */
        }
        .power {
          background-color: #e74c3c; /* Red for power */
          border: 1px solid #e74c3c;
          grid-column: 1 / 4;
          margin-bottom: 10px;
          color: white; /* White text for power button */
          box-shadow: 0 0 10px rgba(231,76,60,0.5); /* Red glow */
        }
        .nav-btn { background-color: #007bff; border: 1px solid #007bff; color: white; box-shadow: 0 0 8px rgba(0,123,255,0.5); } /* Blue navigation */
        .num-btn { background-color: #3a3a3a; border: 1px solid #00f5a0; } /* Standard dark green button */
        .func-btn { background-color: #3a3a3a; border: 1px solid #00f5a0; } /* Standard dark green button */

        /* NEW STYLE FOR NAVIGATION GRID ALIGNMENT */
        .nav-grid {
          /* Set up a 3x3 grid for navigation */
          grid-template-rows: repeat(3, 1fr);
          grid-template-columns: repeat(3, 1fr);
          height: 300px; /* Adjust height to match the desired cross layout */
          align-items: center; /* Vertically center the items */
        }
        .nav-grid .btn {
          /* Ensure nav buttons fill the space */
          height: 100%;
          width: 100%;
        }
        /* END NEW STYLE */

        .btn:active {
          background-color: #00f5a0; /* Green on active */
          color: #2b2b2b; /* Dark text on active */
          box-shadow: 0 0 15px rgba(0,245,160,0.8); /* Stronger green glow */
          transform: translateY(1px); /* Slight press effect */
        }
        .power:active {
          background-color: #c0392b; /* Darker red on active */
          box-shadow: 0 0 15px rgba(192,57,43,0.8);
        }
        .nav-btn:active {
          background-color: #0056b3; /* Darker blue on active */
          box-shadow: 0 0 15px rgba(0,86,179,0.8);
        }
      </style>
    </head>
    <body>
      <div class="logo-container">
        <img decoding="async" src="/logo.png" alt="Hacker Logo" class="logo">
      </div>
      <h1>ESP32 Hacker Remote</h1>

      <div class="remote-section">
        <a href="/power" class="btn power">POWER</a>

        <a href="/menu" class="btn func-btn">MENU</a>
        <a href="/ch_up" class="btn func-btn">CH+</a>
        <a href="/input" class="btn func-btn">INPUT</a>

        <a href="/vol_up" class="btn func-btn">VOL+</a>
        <a href="/mute" class="btn func-btn">MUTE</a>
        <a href="/vol_down" class="btn func-btn">VOL-</a>

        <a href="/return" class="btn func-btn">BACK</a>
        <a href="/ch_down" class="btn func-btn">CH-</a>
        <a href="/exit" class="btn func-btn">EXIT</a>
      </div>

      <h2>Navigation</h2>
      <div class="remote-section nav-grid">
        <a href="/up" class="btn nav-btn" style="grid-row: 1; grid-column: 2;">UP</a>
       
        <a href="/left" class="btn nav-btn" style="grid-row: 2; grid-column: 1;">LEFT</a>
       
        <a href="/ok" class="btn nav-btn" style="grid-row: 2; grid-column: 2;">OK</a>
       
        <a href="/right" class="btn nav-btn" style="grid-row: 2; grid-column: 3;">RIGHT</a>
       
        <a href="/down" class="btn nav-btn" style="grid-row: 3; grid-column: 2;">DOWN</a>
      </div>
     
      <h2>Number Pad</h2>
      <div class="remote-section">
        <a href="/d1" class="btn num-btn">1</a>
        <a href="/d2" class="btn num-btn">2</a>
        <a href="/d3" class="btn num-btn">3</a>
        <a href="/d4" class="btn num-btn">4</a>
        <a href="/d5" class="btn num-btn">5</a>
        <a href="/d6" class="btn num-btn">6</a>
        <a href="/d7" class="btn num-btn">7</a>
        <a href="/d8" class="btn num-btn">8</a>
        <a href="/d9" class="btn num-btn">9</a>
        <div style="grid-column: 2 / 3;">
          <a href="/d0" class="btn num-btn">0</a>
        </div>
      </div>
    </body>
    </html>
  )rawliteral";
  return html;
}

// Handler for the root path ("/")
void handleRoot() {
  server.send(200, "text/html", get_html_page());
}

				
			

3.3 Server Handlers and Main Logic

These functions define what happens when the server receives a request for a specific command URI (e.g., /vol_up).

				
					// --- Handlers for remote control keys ---
// Note: Each handler calls handleRoot() at the end to refresh the page after sending the command.

// Power and Volume/Channel
void handlePower()   { sendIrCommand(POWER_TOGGLE_CODE, "POWER");    handleRoot(); }
void handleVolUp()   { sendIrCommand(VOL_UP_CODE, "VOL UP");       handleRoot(); }
void handleVolDown() { sendIrCommand(VOL_DOWN_CODE, "VOL DOWN");   handleRoot(); }
void handleMute()    { sendIrCommand(MUTE_CODE, "MUTE");         handleRoot(); }
void handleChUp()    { sendIrCommand(CH_UP_CODE, "CH UP");        handleRoot(); }
void handleChDown()  { sendIrCommand(CH_DOWN_CODE, "CH DOWN");    handleRoot(); }

// Navigation
void handleUp()      { sendIrCommand(UP_CODE, "UP");             handleRoot(); }
void handleDown()    { sendIrCommand(DOWN_CODE, "DOWN");         handleRoot(); }
void handleLeft()    { sendIrCommand(LEFT_CODE, "LEFT");         handleRoot(); }
void handleRight()   { sendIrCommand(RIGHT_CODE, "RIGHT");        handleRoot(); }
void handleOk()      { sendIrCommand(OK_ENTER_CODE, "OK/ENTER");   handleRoot(); }

// Function Buttons
void handleMenu()    { sendIrCommand(MENU_CODE, "MENU");         handleRoot(); }
void handleReturn()  { sendIrCommand(RETURN_GO_BACK_CODE, "RETURN/BACK"); handleRoot(); }
void handleExit()    { sendIrCommand(EXIT_CODE, "EXIT");         handleRoot(); }
void handleInput()   { sendIrCommand(INPUT_CODE, "INPUT");        handleRoot(); }

// Number Pad
void handleD0()      { sendIrCommand(DIGITAL_0_CODE, "0"); handleRoot(); }
void handleD1()      { sendIrCommand(DIGITAL_1_CODE, "1"); handleRoot(); }
void handleD2()      { sendIrCommand(DIGITAL_2_CODE, "2"); handleRoot(); }
void handleD3()      { sendIrCommand(DIGITAL_3_CODE, "3"); handleRoot(); }
void handleD4()      { sendIrCommand(DIGITAL_4_CODE, "4"); handleRoot(); }
void handleD5()      { sendIrCommand(DIGITAL_5_CODE, "5"); handleRoot(); }
void handleD6()      { sendIrCommand(DIGITAL_6_CODE, "6"); handleRoot(); }
void handleD7()      { sendIrCommand(DIGITAL_7_CODE, "7"); handleRoot(); }
void handleD8()      { sendIrCommand(DIGITAL_8_CODE, "8"); handleRoot(); }
void handleD9()      { sendIrCommand(DIGITAL_9_CODE, "9"); handleRoot(); }

void setup() {
  Serial.begin(115200);
  IrSender.begin(IR_TX_PIN);

  // 1. Connect to WiFi
  Serial.print("Connecting to WiFi..");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected.");
  Serial.print("Web Server IP: ");
  Serial.println(WiFi.localIP());

  // Serve the logo image
  server.on("/logo.png", HTTP_GET, []() {
    server.send_P(200, "image/png", (const char*) logo_hacker_png, logo_hacker_png_len);
  });

  // 2. Setup Server Routes
  server.on("/", handleRoot);
  server.on("/power", handlePower);
  server.on("/vol_up", handleVolUp);
  server.on("/vol_down", handleVolDown);
  server.on("/mute", handleMute);
  server.on("/ch_up", handleChUp);
  server.on("/ch_down", handleChDown);

  // Navigation
  server.on("/up", handleUp);
  server.on("/down", handleDown);
  server.on("/left", handleLeft);
  server.on("/right", handleRight);
  server.on("/ok", handleOk);

  // Function Buttons
  server.on("/menu", handleMenu);
  server.on("/return", handleReturn);
  server.on("/exit", handleExit);
  server.on("/input", handleInput);
 
  // Number Pad
  server.on("/d0", handleD0);
  server.on("/d1", handleD1);
  server.on("/d2", handleD2);
  server.on("/d3", handleD3);
  server.on("/d4", handleD4);
  server.on("/d5", handleD5);
  server.on("/d6", handleD6);
  server.on("/d7", handleD7);
  server.on("/d8", handleD8);
  server.on("/d9", handleD9);

  // 3. Start the Server
  server.begin();
  Serial.println("HTTP Server started.");
}

void loop() {
  // Main server loop to handle client requests
  server.handleClient();

				
			

4. Source Code

The entire code for an easier copy and paste:

				
					#include <WiFi.h>
#include <WebServer.h>
#include <IRremote.h>
#include "image_data.h"
// --- USER CONFIGURATION ---
const char* ssid = "SSDI";
const char* password = "PASSWORD";
const int IR_TX_PIN = 4; // KY-005 Signal pin connected to GPIO 4
// --------------------------

// --- IR CODES (NEC PROTOCOL) ---
// Codes extracted from your switch-case block
#define POWER_TOGGLE_CODE         0xFD020707 // power toggle: Universal Remote
#define VOL_UP_CODE               0xF8070707 // volume up
#define VOL_DOWN_CODE             0xF40B0707 // volume down
#define MUTE_CODE                 0xF00F0707 // mute / vol push
#define CH_UP_CODE                0xED120707 // channel up - universal
#define CH_DOWN_CODE              0xEF100707 // channel down - universal
#define CH_LIST_CODE              0x946B0707 // channel list (using the 4-byte version)
#define UP_CODE                   0x9F600707 // up - Universal
#define DOWN_CODE                 0x9E610707 // down - Universal
#define LEFT_CODE                 0x9A650707 // left - Universal
#define RIGHT_CODE                0x9D620707 // right - Universal
#define OK_ENTER_CODE             0x97680707 // ok / enter - universal
#define MENU_CODE                 0xE51A0707 // menu - universal
#define RETURN_GO_BACK_CODE       0xEC130707 // return / go back - universal (using the 4-byte version)
#define EXIT_CODE                 0xD22D0707 // exit - universal
#define INPUT_CODE                0xFE010707 // input - universal
#define DIGITAL_1_CODE            0xFB040707 // digital 1
#define DIGITAL_2_CODE            0xFA050707 // digital 2
#define DIGITAL_3_CODE            0xF9060707 // digital 3
#define DIGITAL_4_CODE            0xF7080707 // digital 4
#define DIGITAL_5_CODE            0xF6090707 // digital 5
#define DIGITAL_6_CODE            0xF50A0707 // digital 6
#define DIGITAL_7_CODE            0xF30C0707 // digital 7
#define DIGITAL_8_CODE            0xF20D0707 // digital 8
#define DIGITAL_9_CODE            0xF10E0707 // digital 9
#define DIGITAL_0_CODE            0xEE110707 // digital 0

// Server object on port 80 (standard HTTP)
WebServer server(80);

// Function to send a specific IR command
void sendIrCommand(uint32_t code, const char* name) {
// Extract the 16-bit command (upper 16 bits of the 32-bit code)
  uint16_t command = (uint16_t)(code >> 16);
 
  // Extract the 16-bit address (lower 16 bits of the 32-bit code)
  uint16_t address = (uint16_t)(code & 0xFFFF);

  // Send the code using the Samsung protocol
  // The address and command are sent once (1 repeat)
  IrSender.sendSamsung(address, command, 1);

  // Print a more informative message for debugging
  Serial.printf("Sent Samsung IR Code - Address: 0x%X, Command: 0x%X for command: %s\n",
                (unsigned int)address, (unsigned int)command, name);
}

// HTML content for the web server
String get_html_page() {
  String html = R"rawliteral(
    <!DOCTYPE html>
    <html>
    <head>
      <title>ESP32 Hacker Remote</title>
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <style>
        body {
          font-family: 'Consolas', monospace;
          text-align: center;
          margin: 0;
          padding: 20px;
          background-color: #2b2b2b; /* Dark background */
          color: #00f5a0; /* Green text */
        }
        h1, h2 {
          color: #00f5a0;
          text-shadow: 0 0 5px rgba(0,245,160,0.5); /* Subtle glow */
        }
        .logo-container {
          margin-bottom: 20px;
        }
        .logo {
          max-width: 100px; /* Adjust as needed */
          height: auto;
          filter: drop-shadow(0 0 8px #00f5a0); /* Glow effect for the logo */
        }
        .remote-section {
          display: grid;
          grid-template-columns: repeat(3, 1fr);
          gap: 12px;
          max-width: 380px; /* Slightly wider for better spacing */
          margin: 25px auto;
          padding: 20px;
          border: 1px solid #007bff; /* Blue border */
          border-radius: 15px;
          background-color: #2b2b2b;
          box-shadow: 0 0 15px rgba(0,123,255,0.5); /* Blue glow around sections */
        }
        .btn {
          padding: 18px 10px;
          font-size: 16px;
          text-decoration: none;
          color: #00f5a0; /* Green text on buttons */
          border-radius: 8px;
          transition: background-color 0.2s, box-shadow 0.2s;
          box-shadow: 0 0 8px rgba(0,245,160,0.3); /* Subtle green glow */
          background-color: #3a3a3a; /* Darker button background */
          border: 1px solid #00f5a0; /* Green border for buttons */
        }
        .power {
          background-color: #e74c3c; /* Red for power */
          border: 1px solid #e74c3c;
          grid-column: 1 / 4;
          margin-bottom: 10px;
          color: white; /* White text for power button */
          box-shadow: 0 0 10px rgba(231,76,60,0.5); /* Red glow */
        }
        .nav-btn { background-color: #007bff; border: 1px solid #007bff; color: white; box-shadow: 0 0 8px rgba(0,123,255,0.5); } /* Blue navigation */
        .num-btn { background-color: #3a3a3a; border: 1px solid #00f5a0; } /* Standard dark green button */
        .func-btn { background-color: #3a3a3a; border: 1px solid #00f5a0; } /* Standard dark green button */

        /* NEW STYLE FOR NAVIGATION GRID ALIGNMENT */
        .nav-grid {
          /* Set up a 3x3 grid for navigation */
          grid-template-rows: repeat(3, 1fr);
          grid-template-columns: repeat(3, 1fr);
          height: 300px; /* Adjust height to match the desired cross layout */
          align-items: center; /* Vertically center the items */
        }
        .nav-grid .btn {
          /* Ensure nav buttons fill the space */
          height: 100%;
          width: 100%;
        }
        /* END NEW STYLE */

        .btn:active {
          background-color: #00f5a0; /* Green on active */
          color: #2b2b2b; /* Dark text on active */
          box-shadow: 0 0 15px rgba(0,245,160,0.8); /* Stronger green glow */
          transform: translateY(1px); /* Slight press effect */
        }
        .power:active {
          background-color: #c0392b; /* Darker red on active */
          box-shadow: 0 0 15px rgba(192,57,43,0.8);
        }
        .nav-btn:active {
          background-color: #0056b3; /* Darker blue on active */
          box-shadow: 0 0 15px rgba(0,86,179,0.8);
        }
      </style>
    </head>
    <body>
      <div class="logo-container">
        <img decoding="async" src="/logo.png" alt="Hacker Logo" class="logo">
      </div>
      <h1>ESP32 Hacker Remote</h1>

      <div class="remote-section">
        <a href="/power" class="btn power">POWER</a>

        <a href="/menu" class="btn func-btn">MENU</a>
        <a href="/ch_up" class="btn func-btn">CH+</a>
        <a href="/input" class="btn func-btn">INPUT</a>

        <a href="/vol_up" class="btn func-btn">VOL+</a>
        <a href="/mute" class="btn func-btn">MUTE</a>
        <a href="/vol_down" class="btn func-btn">VOL-</a>

        <a href="/return" class="btn func-btn">BACK</a>
        <a href="/ch_down" class="btn func-btn">CH-</a>
        <a href="/exit" class="btn func-btn">EXIT</a>
      </div>

      <h2>Navigation</h2>
      <div class="remote-section nav-grid">
        <a href="/up" class="btn nav-btn" style="grid-row: 1; grid-column: 2;">UP</a>
       
        <a href="/left" class="btn nav-btn" style="grid-row: 2; grid-column: 1;">LEFT</a>
       
        <a href="/ok" class="btn nav-btn" style="grid-row: 2; grid-column: 2;">OK</a>
       
        <a href="/right" class="btn nav-btn" style="grid-row: 2; grid-column: 3;">RIGHT</a>
       
        <a href="/down" class="btn nav-btn" style="grid-row: 3; grid-column: 2;">DOWN</a>
      </div>
     
      <h2>Number Pad</h2>
      <div class="remote-section">
        <a href="/d1" class="btn num-btn">1</a>
        <a href="/d2" class="btn num-btn">2</a>
        <a href="/d3" class="btn num-btn">3</a>
        <a href="/d4" class="btn num-btn">4</a>
        <a href="/d5" class="btn num-btn">5</a>
        <a href="/d6" class="btn num-btn">6</a>
        <a href="/d7" class="btn num-btn">7</a>
        <a href="/d8" class="btn num-btn">8</a>
        <a href="/d9" class="btn num-btn">9</a>
        <div style="grid-column: 2 / 3;">
          <a href="/d0" class="btn num-btn">0</a>
        </div>
      </div>
    </body>
    </html>
  )rawliteral";
  return html;
}

// Handler for the root path ("/")
void handleRoot() {
  server.send(200, "text/html", get_html_page());
}

// --- Handlers for remote control keys ---
// Note: Each handler calls handleRoot() at the end to refresh the page after sending the command.

// Power and Volume/Channel
void handlePower()   { sendIrCommand(POWER_TOGGLE_CODE, "POWER");    handleRoot(); }
void handleVolUp()   { sendIrCommand(VOL_UP_CODE, "VOL UP");       handleRoot(); }
void handleVolDown() { sendIrCommand(VOL_DOWN_CODE, "VOL DOWN");   handleRoot(); }
void handleMute()    { sendIrCommand(MUTE_CODE, "MUTE");         handleRoot(); }
void handleChUp()    { sendIrCommand(CH_UP_CODE, "CH UP");        handleRoot(); }
void handleChDown()  { sendIrCommand(CH_DOWN_CODE, "CH DOWN");    handleRoot(); }

// Navigation
void handleUp()      { sendIrCommand(UP_CODE, "UP");             handleRoot(); }
void handleDown()    { sendIrCommand(DOWN_CODE, "DOWN");         handleRoot(); }
void handleLeft()    { sendIrCommand(LEFT_CODE, "LEFT");         handleRoot(); }
void handleRight()   { sendIrCommand(RIGHT_CODE, "RIGHT");        handleRoot(); }
void handleOk()      { sendIrCommand(OK_ENTER_CODE, "OK/ENTER");   handleRoot(); }

// Function Buttons
void handleMenu()    { sendIrCommand(MENU_CODE, "MENU");         handleRoot(); }
void handleReturn()  { sendIrCommand(RETURN_GO_BACK_CODE, "RETURN/BACK"); handleRoot(); }
void handleExit()    { sendIrCommand(EXIT_CODE, "EXIT");         handleRoot(); }
void handleInput()   { sendIrCommand(INPUT_CODE, "INPUT");        handleRoot(); }

// Number Pad
void handleD0()      { sendIrCommand(DIGITAL_0_CODE, "0"); handleRoot(); }
void handleD1()      { sendIrCommand(DIGITAL_1_CODE, "1"); handleRoot(); }
void handleD2()      { sendIrCommand(DIGITAL_2_CODE, "2"); handleRoot(); }
void handleD3()      { sendIrCommand(DIGITAL_3_CODE, "3"); handleRoot(); }
void handleD4()      { sendIrCommand(DIGITAL_4_CODE, "4"); handleRoot(); }
void handleD5()      { sendIrCommand(DIGITAL_5_CODE, "5"); handleRoot(); }
void handleD6()      { sendIrCommand(DIGITAL_6_CODE, "6"); handleRoot(); }
void handleD7()      { sendIrCommand(DIGITAL_7_CODE, "7"); handleRoot(); }
void handleD8()      { sendIrCommand(DIGITAL_8_CODE, "8"); handleRoot(); }
void handleD9()      { sendIrCommand(DIGITAL_9_CODE, "9"); handleRoot(); }


void setup() {
  Serial.begin(115200);
  IrSender.begin(IR_TX_PIN);

  // 1. Connect to WiFi
  Serial.print("Connecting to WiFi..");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected.");
  Serial.print("Web Server IP: ");
  Serial.println(WiFi.localIP());

  // Serve the logo image
  server.on("/logo.png", HTTP_GET, []() {
    server.send_P(200, "image/png", (const char*) logo_hacker_png, logo_hacker_png_len);
  });

  // 2. Setup Server Routes
  server.on("/", handleRoot);
  server.on("/power", handlePower);
  server.on("/vol_up", handleVolUp);
  server.on("/vol_down", handleVolDown);
  server.on("/mute", handleMute);
  server.on("/ch_up", handleChUp);
  server.on("/ch_down", handleChDown);

  // Navigation
  server.on("/up", handleUp);
  server.on("/down", handleDown);
  server.on("/left", handleLeft);
  server.on("/right", handleRight);
  server.on("/ok", handleOk);

  // Function Buttons
  server.on("/menu", handleMenu);
  server.on("/return", handleReturn);
  server.on("/exit", handleExit);
  server.on("/input", handleInput);
 
  // Number Pad
  server.on("/d0", handleD0);
  server.on("/d1", handleD1);
  server.on("/d2", handleD2);
  server.on("/d3", handleD3);
  server.on("/d4", handleD4);
  server.on("/d5", handleD5);
  server.on("/d6", handleD6);
  server.on("/d7", handleD7);
  server.on("/d8", handleD8);
  server.on("/d9", handleD9);

  // 3. Start the Server
  server.begin();
  Serial.println("HTTP Server started.");
}

void loop() {
  // Main server loop to handle client requests
  server.handleClient();
}

				
			

4.1 Code Explanation: IR Remote Webserver

This project transforms the ESP32 into a Smart Home Bridge that converts HTTP requests (button clicks on a web page) into Infrared (IR) signals via the KY-005 module.

4.2 Core Mechanism

The ESP32 runs an HTTP server hosting a page that looks like a remote control. Each button on the page is a link to a unique URL (a URI, e.g., /power). When a user clicks a button, the ESP32 intercepts the request, maps the URI to a specific IR Hex Code, and transmits that code using the IR LED.

4.3 Key Components and Logic

Component/FunctionRole in the CodeDetails
IR Codes (#define ...)Protocol DefinitionDefines hexadecimal constants for every remote function (e.g., POWER_TOGGLE_CODE). These codes are based on a specific protocol (e.g., Samsung) and must be custom-validated by the user.
IR_TX_PIN (4)Physical OutputDefines the GPIO 4 pin used to connect the KY-005 module. The IrSender.begin(IR_TX_PIN); command initializes the IRremote library for output on this pin.
get_html_page()Presentation LayerGenerates the entire HTML/CSS of the virtual remote control. The key element is the use of <a> tags where the href attribute links to the command URIs (e.g., <a href="/power" class="btn power">POWER</a>).
server.on("/power", handlePower);URI Mapping (Routing)In setup(), this line tells the web server: “If you receive an HTTP request for the path /power, execute the function handlePower().” This creates a REST-like endpoint for every button.
handlePower() (and others)Execution LayerThese are the specific handler functions. When executed, they call sendIrCommand(...) with the relevant code and then immediately refresh the page using handleRoot().
sendIrCommand()Physical ControlTakes the 32-bit IR code, splits it into the 16-bit Address and 16-bit Command (as required by the protocol), and calls IrSender.sendSamsung(address, command, 1); to physically pulse the IR LED, sending the signal to the target device.
server.handleClient()Main LoopPlaced in loop(), this ensures the ESP32 is continuously listening for incoming HTTP requests from the connected browser, making the system responsive.

4.4 Execution Flow

  1. Start: ESP32 connects to Wi-Fi and prints its IP address.

  2. User Action: A user navigates a smartphone browser to the ESP32’s IP address and sees the remote control page.

  3. Command Sent: The user clicks “VOL+”. The browser requests the URI: http://[ESP32_IP_ADDRESS]/vol_up.

  4. Server Action: server.handleClient() detects the request and executes handleVolUp().

  5. IR Transmitted: handleVolUp() calls sendIrCommand(VOL_UP_CODE, "VOL UP"). The IRremote library pulses the LED on GPIO 4 with the correct Samsung protocol pattern.

  6. Page Refresh: handleVolUp() finishes by calling handleRoot(), which immediately sends the HTML page back to the browser, confirming the action visually.

5. Usage Instructions

  1. Find Your Codes: The most critical step is replacing the placeholder IR codes (e.g., POWER_CODE, VOL_UP_CODE) with the actual SAMSUNG, NEC or other protocol codes for your target device (TV, stereo, etc.).
    1. Use the IRrecvDumpV2 example from the IRremoteESP8266 library with an IR Receiver Module (like the KY-022) to capture the codes from your original remote.
  2. Upload the Code: Update the ssid and password variables, then upload the complete sketch to your ESP32 board.
  3. Access the Webpage: Open the Serial Monitor in the Arduino IDE to find the IP address printed by the ESP32 (e.g., Web Server IP: 192.168.1.100).
  4. Remote Control: Navigate to that IP address in any web browser on the same network (e.g., your smartphone or computer). You will see the web remote interface.
  5. Control Your Device: Click the buttons on the webpage. Each click will send the corresponding IR signal via the KY-005 module, controlling your IR-compatible device.
  6. The terminal will display the command sent

6. Lab Recap

This project successfully integrates three core IoT concepts: Networking, Web Services, and Physical Control.

6.1 The Three-Layer Architecture

Layer

Component

Role

Key Technology

Presentation (The UI)

Web Page (HTML/CSS)

Provides a user-friendly interface for control.

HTTP (HyperText Transfer Protocol)

Logic (The Brain)

ESP32 Sketch

Manages the WiFi connection, runs the web server, and maps URIs to functions.

WiFi.h & WebServer.h

Physical (The Output)

KY-005 IR Transmitter

Converts the digital command into a physical, modulated infrared light signal. Remember to add a 110-220 Ohm resistor at R1.

IRremoteESP8266.h

6.2 Key Takeaways from the Lab

  1. Event-Driven Nature of the Server: The ESP32 doesn’t constantly check for button presses. It only acts when the server.handleClient() function in the loop() detects an incoming HTTP request (a click on a link like /power).
  2. URI to Function Mapping: The server.on(“/command”, handlerFunction) structure is critical. It defines a route where a specific URL path (/command) is tied directly to a specific action (the handlerFunction), creating a REST-like endpoint for each key.
  3. IR Protocol Importance: The project relies on knowing the exact IR protocol (e.g., NEC, RC5) and the corresponding hex code for the device. Sending the wrong protocol or code, even with the right hardware, will fail to control the target device. This is the main point of customization for the user.
  4. Signal Repetition: Notice that irsend.sendNEC(code, 32, 3); sends the code three times. This repetition is a standard practice in IR protocols to ensure the receiving device reliably captures the signal, especially across distance or line-of-sight variations.

This complete loop, from a smartphone click to an IR pulse, demonstrates the ESP32’s powerful capability as a Smart Home bridge between IP networks and legacy physical control systems.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top