#include <WiFi.h>
#include <WebServer.h>
#include <IRremoteESP8266.h>
#include <EEPROM.h> // For storing configuration
const char* ssid = "Wokwi-GUEST";
const char* password = "";
WebServer server(80);
IRsend irsend(D5); // IR emitter pin
const int RECV_PIN = D4; // IR receiver pin
IRrecv IrReceiver(RECV_PIN);
decode_results results;
// Structure to hold IR data for a button
struct IRData {
unsigned long code;
uint8_t bits;
};
// Structure to represent a remote button
struct RemoteButton {
String name;
IRData data;
};
// Map to store device-specific buttons
std::map<String, std::vector<RemoteButton>> devices;
String currentDevice = "TV"; // Default device
// EEPROM addresses for configuration (adjust as needed)
#define EEPROM_WIFI_SSID_ADDR 0
#define EEPROM_WIFI_PASS_ADDR 32
#define EEPROM_DEVICE_COUNT_ADDR 64
#define EEPROM_DEVICE_NAMES_START_ADDR 65
#define EEPROM_BUTTONS_START_ADDR 200
// Function to load configuration from EEPROM (basic example)
void loadConfig() {
// Load Wi-Fi credentials (simplified)
EEPROM.begin(512);
String storedSSID = "";
for (int i = 0; i < 32; ++i) {
char c = EEPROM.read(EEPROM_WIFI_SSID_ADDR + i);
if (c == 0) break;
storedSSID += c;
}
if (!storedSSID.isEmpty()) {
ssid = strdup(storedSSID.c_str());
}
// Load device names and buttons (more complex, needs more robust implementation)
}
// Function to save configuration to EEPROM (basic example)
void saveConfig() {
EEPROM.writeString(EEPROM_WIFI_SSID_ADDR, ssid);
EEPROM.commit();
}
void handleRoot() {
String html = "<!DOCTYPE html><html><head><title>ESP8266 Remote</title></head><body>";
html += "<h1>Universal Remote</h1>";
// Device Selection
html += "<label for='device'>Select Device:</label>";
html += "<select id='device' onchange='window.location.href=\"/?device=\" + this.value'>";
for (const auto& pair : devices) {
html += "<option value='" + pair.first + "'" + (pair.first == currentDevice ? " selected" : "") + ">" + pair.first + "</option>";
}
html += "</select><br><br>";
// Buttons for the current device
html += "<h2>" + currentDevice + " Remote</h2>";
if (devices.count(currentDevice)) {
for (const auto& button : devices[currentDevice]) {
html += "<button onclick=\"window.location.href='/send?code=" + String(button.data.code, HEX) + "&bits=" + String(button.data.bits) + "';\">" + button.name + "</button> ";
}
} else {
html += "<p>No buttons configured for this device.</p>";
}
// Configuration and Learning Mode Links
html += "<br><br><a href='/config'>Configuration</a> | <a href='/learn'>Learn New Code</a>";
html += "</body></html>";
server.send(200, "text/html", html);
}
void handleSend() {
if (server.hasArg("code") && server.hasArg("bits")) {
String codeStr = server.arg("code");
String bitsStr = server.arg("bits");
unsigned long code = strtoul(codeStr.c_str(), NULL, 16);
uint8_t bits = bitsStr.toInt();
Serial.printf("Sending code for %s: 0x%lX with %d bits\n", currentDevice.c_str(), code, bits);
irsend.sendNEC(code, bits); // Assuming NEC protocol
server.send(200, "text/plain", "OK");
} else {
server.send(400, "text/plain", "Invalid request");
}
}
void handleConfig() {
String html = "<!DOCTYPE html><html><head><title>Configuration</title></head><body>";
html += "<h1>Configuration</h1>";
html += "<form action='/saveconfig' method='post'>";
html += "<label for='ssid'>Wi-Fi SSID:</label><input type='text' id='ssid' name='ssid' value='" + String(ssid) + "'><br><br>";
html += "<label for='password'>Wi-Fi Password:</label><input type='password' id='password' name='password' value='" + String(password) + "'><br><br>";
// Add more configuration options here (e.g., manage devices, add/remove buttons)
html += "<input type='submit' value='Save Configuration'>";
html += "</form>";
html += "<br><a href='/'>Back to Remote</a>";
html += "</body></html>";
server.send(200, "text/html", html);
}
void handleSaveConfig() {
if (server.hasArg("ssid") && server.hasArg("password")) {
free((void*)ssid);
ssid = strdup(server.arg("ssid").c_str());
free((void*)password);
password = strdup(server.arg("password").c_str());
saveConfig();
server.send(200, "text/plain", "Configuration saved. ESP will restart.");
delay(1000);
ESP.restart();
} else {
server.send(400, "text/plain", "Invalid configuration data.");
}
}
void handleLearn() {
String html = "<!DOCTYPE html><html><head><title>Learn New Code</title></head><body>";
html += "<h1>Learn New IR Code</h1>";
html += "<p>Point your remote at the ESP8266 IR receiver and press the button you want to learn.</p>";
html += "<div id='learningStatus'></div>";
html += "<form id='learnForm'>";
html += "<label for='deviceName'>Device Name:</label><input type='text' id='deviceName' name='deviceName'><br><br>";
html += "<label for='buttonName'>Button Name:</label><input type='text' id='buttonName' name='buttonName'><br><br>";
html += "<input type='hidden' id='learnedCode' name='learnedCode'>";
html += "<input type='hidden' id='learnedBits' name='learnedBits'>";
html += "<button type='button' onclick='sendLearnedCode()'>Save Learned Code</button>";
html += "</form>";
html += "<br><a href='/'>Back to Remote</a>";
html += "<script>";
html += "var learningInterval;";
html += "function startLearning() {";
html += " document.getElementById('learningStatus').innerText = 'Listening for IR code...';";
html += " learningInterval = setInterval(checkLearnedCode, 1000);"; // Poll for learned code
html += " fetch('/start_learn'); // Tell ESP to start listening";
html += "}";
html += "function checkLearnedCode() {";
html += " fetch('/get_learned_code')";
html += " .then(response => response.json())";
html += " .then(data => {";
html += " if (data.code !== 0) {";
html += " clearInterval(learningInterval);";
html += " document.getElementById('learningStatus').innerText = 'Code received: 0x' + data.code.toString(16) + ' (' + data.bits + ' bits)';";
html += " document.getElementById('learnedCode').value = data.code.toString(16);";
html += " document.getElementById('learnedBits').value = data.bits;";
html += " }";
html += " });";
html += "}";
html += "function sendLearnedCode() {";
html += " var deviceName = document.getElementById('deviceName').value;";
html += " var buttonName = document.getElementById('buttonName').value;";
html += " var learnedCode = document.getElementById('learnedCode').value;";
html += " var learnedBits = document.getElementById('learnedBits').value;";
html += " fetch('/save_learned', {";
html += " method: 'POST',";
html += " headers: {'Content-Type': 'application/x-www-form-urlencoded'},";
html += " body: 'deviceName=' + encodeURIComponent(deviceName) + '&buttonName=' + encodeURIComponent(buttonName) + '&code=' + encodeURIComponent(learnedCode) + '&bits=' + encodeURIComponent(learnedBits)";
html += " })";
html += " .then(response => response.text())";
html += " .then(data => {";
html += " document.getElementById('learningStatus').innerText = data;";
html += " // Optionally reload the page to show the new button";
html += " setTimeout(function(){ window.location.href='/'; }, 1500);";
html += " });";
html += "}";
html += "startLearning();"; // Automatically start learning when the page loads
html += "</script>";
html += "</body></html>";
server.send(200, "text/html", html);
}
// State variable for learning mode
bool isLearning = false;
IRData learnedData = {0, 0};
void handleStartLearn() {
isLearning = true;
learnedData = {0, 0};
IrReceiver.resume(); // Enable receiver
server.send(200, "text/plain", "Learning started");
}
void handleGetLearnedCode() {
if (IrReceiver.decode(&results)) {
learnedData.code = results.value;
learnedData.bits = results.bits;
IrReceiver.resume(); // Receive the next value
}
DynamicJsonDocument jsonBuffer(128);
jsonBuffer["code"] = learnedData.code;
jsonBuffer["bits"] = learnedData.bits;
String jsonString;
serializeJson(jsonBuffer, jsonString);
server.send(200, "application/json", jsonString);
}
void handleSaveLearned() {
if (server.hasArg("deviceName") && server.hasArg("buttonName") && server.hasArg("code") && server.hasArg("bits")) {
String deviceName = server.arg("deviceName");
String buttonName = server.arg("buttonName");
unsigned long code = strtoul(server.arg("code").c_str(), NULL, 16);
uint8_t bits = server.arg("bits").toInt();
devices[deviceName].push_back({buttonName, {code, bits}});
// In a real application, you would save this to EEPROM or SPIFFS
server.send(200, "text/plain", "Code saved for " + deviceName + " - " + buttonName);
} else {
server.send(400, "text/plain", "Invalid learning data.");
}
}
void handleNotFound() {
server.send(404, "text/plain", "Not found");
}
void setup() {
Serial.begin(115200);
loadConfig(); // Load saved configuration
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
irsend.begin(); // Initialize IR sender
IrReceiver.begin(RECV_PIN, ENABLE_IR_INVERSE); // Initialize IR receiver
// Initialize with a default TV device and some buttons (for demonstration)
devices["TV"].push_back({"Power", {0xE0E020DF, 32}});
devices["TV"].push_back({"VolumeUp", {0xE0E0E01F, 32}});
server.on("/", handleRoot);
server.on("/send", handleSend);
server.on("/config", handleConfig);
server.on("/saveconfig", HTTP_POST, handleSaveConfig);
server.on("/learn", handleLearn);
server.on("/start_learn", handleStartLearn);
server.on("/get_learned_code", handleGetLearnedCode);
server.on("/save_learned", HTTP_POST, handleSaveLearned);
server.onNotFound(handleNotFound);
server.begin();
Serial.println("HTTP server started");
}
void loop() {
server.handleClient();
}