Home IoT Hub Series · 17

[Part 17] Control the air conditioner from the IoT hub - IR transmitter node

Build an IR sender node that can control an air conditioner.

This part learns and sends air-conditioner IR commands. It turns the ESP8266 node into a small universal remote.

IR work depends heavily on direction and distance. Place the IR LED where the air conditioner can see it clearly.

[Part 17] Control the air conditioner from the IoT hub - IR transmitter node hero image
System architecture and data pipeline note
This article is part of the Home IoT Hub system connected to the central NodeMCU server built in Part 2: Building the Brain. The IR transmit/receive node reflects air-conditioner state in the hub and reduces mismatch between manual remote use and automation. The collected data is sent over the local IoT_Hub_Net network and updates slot 4 for air-conditioner state on the central server (/set?no4=value), becoming part of the decision pipeline for the 20-channel smart-home controller.

Parts and cost

PartQtyUnit price
Wemos D1 Mini1 pc$1.50
VS1838B IR receiver1 pc$0.30
IR LED 940nm1 pc$0.20
2N2222 NPN transistor1 pc$0.10
100Ω resistor1 pc$0.10
10kΩ resistor1 pc$0.10
Jumper wires 6 pc
Total$2

Pin wiring

[Part 17] Control the air conditioner from the IoT hub - IR transmitter node wiring diagram
Sensor / moduleBoard pin
VCC3.3V
GNDGND
OUTD1 (GPIO5)
D2 (GPIO4)2N2222 collector
2N2222 emitterGND
D22N2222 base

Full code

[Part 17] Control the air conditioner from the IoT hub - IR transmitter node - Code 2
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>
#include <IRrecv.h>
#include <IRutils.h>

const char* ssid     = "IoT_Hub_Net";
const char* password = "iothub1234";
const char* hubURL   = "http://192.168.1.1";

#define IR_RECV  5    // D1 (GPIO5) - VS1838B receive
#define IR_SEND  4    // D2 (GPIO4) - IR LED send
#define SLOT_NO  4    // brain 4note= AC

// =============================================
// note!
// =============================================
// note
// note
unsigned long long AC_ON_CODE  = 0x0; // ← note
unsigned long long AC_OFF_CODE = 0x0; // ← note

IRrecv irrecv(IR_RECV);
IRsend irsend(IR_SEND);
decode_results results;

String lastState = "--";
bool codesLearned = false;

// =============================================
// setup
// =============================================
void setup() {
  Serial.begin(115200);
  irsend.begin();
  irrecv.enableIRIn();

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print("Connecting to IoT_Hub_Net");
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
  Serial.println("\nConnected! IP: " + WiFi.localIP().toString());

  // note
  if (AC_ON_CODE != 0x0 && AC_OFF_CODE != 0x0) {
    codesLearned = true;
    irrecv.disableIRIn();
    Serial.println("=== CODES ALREADY SET, SKIPPING LEARNING ===");
    Serial.print("AC_ON_CODE:  0x"); Serial.println((unsigned long)AC_ON_CODE, HEX);
    Serial.print("AC_OFF_CODE: 0x"); Serial.println((unsigned long)AC_OFF_CODE, HEX);
    Serial.println("Ready. Waiting for /set?no4=ON or OFF...");
    return;
  }

  // ── IR note──
  Serial.println();
  Serial.println("==========================================");
  Serial.println("  IR LEARNING MODE");
  Serial.println("==========================================");
  Serial.println("1. Point AC remote at VS1838B sensor.");
  Serial.println("2. Press ON button on the remote.");
  Serial.println("3. Press OFF button on the remote.");
  Serial.println("4. Copy the codes to AC_ON_CODE / AC_OFF_CODE");
  Serial.println("   in this sketch and re-upload.");
  Serial.println("==========================================");
  Serial.println();

  bool gotOn = false;
  bool gotOff = false;
  unsigned long startTime = millis();

  // note
  while (!gotOn || !gotOff) {
    if (millis() - startTime > 30000) {
      Serial.println("TIMEOUT: Learning mode ended.");
      break;
    }

    if (irrecv.decode(&results)) {
      unsigned long long code = results.value;
      uint16_t protocol = results.decode_type;

      if (!gotOn) {
        AC_ON_CODE = code;
        gotOn = true;
        Serial.print("AC_ON_CODE:  0x");
        Serial.println((unsigned long)code, HEX);
        Serial.println("  Now press OFF button...");
      } else if (!gotOff) {
        // OFF note
        if (code == AC_ON_CODE) {
          Serial.println("  WARNING: OFF code same as ON code!");
          Serial.println("  Your remote is toggle-type. Use the captured code for both.");
        }
        AC_OFF_CODE = code;
        gotOff = true;
        Serial.print("AC_OFF_CODE: 0x");
        Serial.println((unsigned long)code, HEX);
      }

      irrecv.resume();
    }
    delay(100);
  }

  irrecv.disableIRIn();
  codesLearned = (gotOn && gotOff);

  if (codesLearned) {
    Serial.println();
    Serial.println("=== LEARNING COMPLETE ===");
    Serial.println("Copy these two lines into the sketch and re-upload:");
    Serial.println();
    Serial.print("unsigned long long AC_ON_CODE  = 0x");
    Serial.println((unsigned long)AC_ON_CODE, HEX);
    Serial.print("unsigned long long AC_OFF_CODE = 0x");
    Serial.println((unsigned long)AC_OFF_CODE, HEX);
  } else {
    Serial.println("=== LEARNING FAILED ===");
    Serial.println("Restart the device and try again.");
  }
}

// =============================================
// loop
// =============================================
void loop() {
  if (!codesLearned) {
    Serial.println("Waiting for IR codes to be learned...");
    delay(5000);
    return;
  }

  // ── brainnote──
  WiFiClient client;
  HTTPClient http;
  String url = String(hubURL) + "/status";
  http.begin(client, url);
  int code = http.GET();

  if (code == 200) {
    String resp = http.getString();

    // no4 value note
    int pos = resp.indexOf("\"no4\":\"");
    if (pos > 0) {
      int start = pos + 7;
      int end = resp.indexOf("\"", start);
      String val = resp.substring(start, end);

      // statussend IR only when the value changes
      if (val != lastState) {
        Serial.print("State changed: ");
        Serial.print(lastState);
        Serial.print(" -> ");
        Serial.println(val);

        if (val == "ON") {
          Serial.print("Sending IR: AC ON  (0x");
          Serial.print((unsigned long)AC_ON_CODE, HEX);
          Serial.println(")");
          irsend.sendNEC(AC_ON_CODE, 32);
          delay(100);
        } else if (val == "OFF") {
          Serial.print("Sending IR: AC OFF (0x");
          Serial.print((unsigned long)AC_OFF_CODE, HEX);
          Serial.println(")");
          irsend.sendNEC(AC_OFF_CODE, 32);
          delay(100);
        }

        // IR note
        lastState = val;

        // note
        WiFiClient c2;
        HTTPClient h2;
        String fbUrl = String(hubURL) + "/set?no4=" + val;
        h2.begin(c2, fbUrl);
        h2.GET();
        h2.end();
        Serial.println("Feedback -> " + fbUrl);
      }
    }
  } else {
    Serial.print("HTTP ");
    Serial.print(code);
    Serial.println(" - retrying...");
  }

  http.end();
  delay(3000);
}
[Part 17] Control the air conditioner from the IoT hub - IR transmitter node - Code 3
AC_ON_CODE:  0x880804F
AC_OFF_CODE: 0x880846B
[Part 17] Control the air conditioner from the IoT hub - IR transmitter node - Code 4
unsigned long long AC_ON_CODE  = 0x880804F;  // copied value
unsigned long long AC_OFF_CODE = 0x880846B;  // copied value

Build notes

  • First decode the remote command, then replay it. If nothing happens, move the LED closer and check polarity.
  • Power on the hub brain first so the node can join IoT_Hub_Net.
  • Use the same slot number written in the source code.
  • Open the serial monitor at 115200 baud and confirm HTTP 200.
  • If the value does not appear on the hub, recheck wiring, GPIO number, and Wi-Fi connection.

Serial Monitor

After uploading, open Tools → Serial Monitor in the Arduino IDE and set the baud rate to 115200. The following is an example Serial Monitor flow for checking normal operation. This is not a real screenshot. It shows IR transmission, feedback checking, and slot 4 state updates.

[BOOT] Home IoT Node #17 - Air conditioner IR control
[IR] protocol loaded, target=LG_COOL_24C
[COMMAND] request=AC_ON
[IR] send result=OK, repeat=2
[FEEDBACK] vent_temp_drop=DETECTED
[HTTP] GET /set?no4=1
[HUB] 200 OK - slot no4=AC_ON
  • If Connected or connected appears, the Wi-Fi step passed.
  • If HTTP 200 or 200 OK appears, the value reached the brain server.
  • If the value does not update, check both the slot number in the URL and the sensor wiring.

Field operating checkpoints: troubleshooting, tips, and FAQ

The node code can look short, but real home installation is affected by placement, power, Wi-Fi quality, and sensor noise. Check these points before letting the node become a decision input for the brain server.

SymptomLikely causeField fix
IR commands sometimes failInfrared is directional and sensitive to angle and reflectionTest ceiling or opposite-wall reflection, not only direct aim, and keep the most reliable angle.
Manual remote use makes server state wrongIR control confirms command send, not actual device stateAfter sending, use outlet temperature or power change feedback to correct real ON/OFF state.
A specific model does not match the codeIR protocols differ by brand and modelCapture the raw signal from the original remote first and verify the library protocol for that model.
IR LED output is weakDirect ESP8266 GPIO drive current is not enoughDrive the IR LED through a transistor and resistor with common GND.