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](/iot-lab/assets/air-conditioner-ir-control-hero.png)
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
| Part | Qty | Unit price |
|---|---|---|
| Wemos D1 Mini | 1 pc | $1.50 |
| VS1838B IR receiver | 1 pc | $0.30 |
| IR LED 940nm | 1 pc | $0.20 |
| 2N2222 NPN transistor | 1 pc | $0.10 |
| 100Ω resistor | 1 pc | $0.10 |
| 10kΩ resistor | 1 pc | $0.10 |
| Jumper wires 6 pc | — | — |
| Total | $2 |
Pin wiring
| Sensor / module | Board pin |
|---|---|
| VCC | 3.3V |
| GND | GND |
| OUT | D1 (GPIO5) |
| D2 (GPIO4) | 2N2222 collector |
| 2N2222 emitter | GND |
| D2 | 2N2222 base |
Full code
#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); }
AC_ON_CODE: 0x880804F AC_OFF_CODE: 0x880846B
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
Connectedorconnectedappears, the Wi-Fi step passed. - If
HTTP 200or200 OKappears, 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.
| Symptom | Likely cause | Field fix |
|---|---|---|
| IR commands sometimes fail | Infrared is directional and sensitive to angle and reflection | Test ceiling or opposite-wall reflection, not only direct aim, and keep the most reliable angle. |
| Manual remote use makes server state wrong | IR control confirms command send, not actual device state | After sending, use outlet temperature or power change feedback to correct real ON/OFF state. |
| A specific model does not match the code | IR protocols differ by brand and model | Capture the raw signal from the original remote first and verify the library protocol for that model. |
| IR LED output is weak | Direct ESP8266 GPIO drive current is not enough | Drive the IR LED through a transistor and resistor with common GND. |
This article is part of the 20-channel Home IoT system built from the Part 2 central brain server and the distributed edge nodes in Parts 3-19. Use this map to check the slot and sensor flow across the series.
[Part 1] Home IoT Hub overview[Part 2] Central brain server · slots 1-20[Part 3] Stop guessing room comfort · slots 1/2[Part 4] Did I close the door? · slots 9[Part 5] Is the fridge really cooling? · slots 12[Part 6] Should I open the window? · slots 3[Part 7] Stop bathroom mold before it starts · slots 19[Part 8] Is the boiler actually running? · slots 5/17[Part 9] Did I leave the gas stove on? · slots 6[Part 10] Get warned before gas smell becomes obvious · slots 7[Part 11] Catch flame signs while away from home · slots 8[Part 12] Is bad air making you foggy? · slots 13/14[Part 13] Why is the power bill so high? · slots 15[Part 14] Is water leaking, and how much are we using? · slots 16[Part 15] Do not leave windows open in the rain · slots 18[Part 16] Are the lights off and the door locked? · slots 11/10[Part 17] Control the air conditioner from the IoT hub · slots 4[Part 18] Is the dog bowl empty? · slots 20[Part 19] Cool the room only when someone is there · slots 1/4[Part 20] The house finally fits on one screen · slots all