산업 제조
산업용 사물 인터넷 | 산업자재 | 장비 유지 보수 및 수리 | 산업 프로그래밍 |
home  MfgRobots >> 산업 제조 >  >> Manufacturing Technology >> 제조공정

와이파이 지원 IR 블라스터: ESP8266과 아두이노로 DIY 스마트 리모컨 만들기

부품 및 소모품

부품 수량 비고
ESP8266 ESP-01 (또는 NodeMCU 개발 키트) 1 NodeMCU가 전원 레귤레이터, USB-시리얼 내장으로 더 편리
아두이노 나노 R3 1
범용 NPN 트랜지스터 (예: 2N2222) 1 IR LED 구동용
Adafruit IR LED 1

필요 도구

앱 및 온라인 서비스

프로젝트 소개

시작 동기

최신 스마트폰에서 IR 블라스터가 사라지면서, IoT 인프라와 연동 안 되는 '구형' 가전제품을 제어할 방법이 필요했습니다. 메쉬 네트워크 스마트 홈을 구축했지만 TV 켜려면 리모컨을 찾아야 하는 상황, 더 나은 방법이 있어야죠.

이미 에어컨용 IR 블라스터와 안드로이드 웹앱 연동 HVAC 벤트 프로젝트를 진행한 바 있습니다. 이 둘을 합쳐 웹 연결 IR 블라스터 + 웹앱 프론트엔드를 만들면 스마트폰 버튼 하나로 집안 모든 IR 기기 제어 가능. 방마다 설치해 단일 인터페이스로 통합 제어하면 활용도가 매우 넓습니다.

빠른 프로토타이핑

와이파이 지원 IR 블라스터: ESP8266과 아두이노로 DIY 스마트 리모컨 만들기
초기 브레드보드 프로토타입

ESP8266에 AT 명령으로 와이파이 연결, HTTP 요청 수신, IR 신호 송출까지 구현했으나 1~2초 지연 발생. AT 명령 기반이라 번거로움.

와이파이 지원 IR 블라스터: ESP8266과 아두이노로 DIY 스마트 리모컨 만들기
시리얼 모니터상 지연 현상

NodeMCU로 전환

NodeMCU ESP8266 개발 보드 채택: 온보드 전원 레귤레이터, USB-시리얼, 풍부한 I/O, 브레드보드 친화적. ESP8266WebServer 라이브러리로 GET 요청 수신, IRremote 라이브러리로 IR 송출 모두 바로 작동.

와이파이 지원 IR 블라스터: ESP8266과 아두이노로 DIY 스마트 리모컨 만들기
NodeMCU 개발 보드

REST-ish API 설계

Adafruit의 아두이노 REST API 튜토리얼 참고. 자바스크립트/PHP(cURL)로 요청 전송, 안드로이드 네이티브 웹앱으로 설치 가능하도록 manifest.json, 아이콘 추가.

와이파이 지원 IR 블라스터: ESP8266과 아두이노로 DIY 스마트 리모컨 만들기
웹앱 리모컨 UI
와이파이 지원 IR 블라스터: ESP8266과 아두이노로 DIY 스마트 리모컨 만들기
버튼 배치 상세
와이파이 지원 IR 블라스터: ESP8266과 아두이노로 DIY 스마트 리모컨 만들기
manifest.json으로 네이티브 앱처럼 설치

아두이노 측: GET 요청 수신 → URL 파싱 → 200 OK 응답 → 연결 종료 → 매핑된 RAW IR 코드 송출. 지연 제로 달성.

유니버설 리몬스터 (Universal Remonster)

와이파이 지원 IR 블라스터: ESP8266과 아두이노로 DIY 스마트 리모컨 만들기
선반 아래 벨크로로 고정, IR 수신부 정향
와이파이 지원 IR 블라스터: ESP8266과 아두이노로 DIY 스마트 리모컨 만들기
두꺼운 게이지 와이어로 LED 각도 미세 조정
와이파이 지원 IR 블라스터: ESP8266과 아두이노로 DIY 스마트 리모컨 만들기
완성된 IR 블라스터

IR LED 협각이라 정향 중요. 저항 제거하고 트랜지스터로 5V 직결해 벽면 반사 신호도 수신 가능하게 함. 수명 단축 우려 있으나 듀티 사이클이 극히 낮아(펄스 폭 짧음) 당분간 괜찮을 듯. V2에서는 광각/고출력 IR LED와 인라인 저항 적용 예정.

3개월 사용 후기 & 향후 계획

매일 사용 중, 무결점 작동. 거실 에어컨/TV 제어용 2대 추가 설치.

이 작은 커넥티드 IR 블라스터가 스마트 홈과 레거시 가전 사이의 간극을 점점 메워가고 있습니다.

코드

아두이노 스케치 (WiFi IR Blaster for ESP8266)

/* WiFi IR Blaster by Buddy Crotty */
#include <ESP8266WiFi.h>
#include <IRremoteESP8266.h>
#include <ESP8266mDNS.h>

const char* ssid = "AP_SSID";
const char* password = "AP_Pass";
MDNSResponder mdns;
int khz = 38; // 38kHz 캐리어
IRsend irsend(4); // GPIO4 (NodeMCU D2)

/* RAW IR 코드들 (삼성 TV, NEC 리시버 등) */
unsigned int irTVpwr[] = {4650,4250, 700,1550, ... };
unsigned int irTVsrc[] = {4600,4300, 700,1550, ... };
// ... (생략: irTVmute, irTVvdn, irTVvup, irTVchup, irTVchdn,
//      irRECpwr, irRECpwrON, irRECpwrOFF, irRECmute, irRECvdn, irRECvup, irRECsrc)

WiFiServer server(80);

void setup() {
  Serial.begin(115200); delay(10);
  irsend.begin();
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
  Serial.println(""); Serial.println("WiFi Connected");
  server.begin(); Serial.println("HTTP Server Started");
  Serial.print("IP Address: "); Serial.println(WiFi.localIP());
  if (mdns.begin("IRBlasterLR", WiFi.localIP())) Serial.println("MDNS Responder Started");
}

void loop() {
  WiFiClient client = server.available();
  if (!client) return;
  Serial.println("new client");
  while(!client.available()) delay(1);
  String req = client.readStringUntil('\r');
  Serial.println(req); client.flush();

  if (req.indexOf("/irTVpwr") != -1) { irsend.sendRaw(irTVpwr, sizeof(irTVpwr)/sizeof(irTVpwr[0]), khz); Serial.println("IRreq irTVpwr sent"); }
  else if (req.indexOf("/irTVsrc") != -1) { irsend.sendRaw(irTVsrc, sizeof(irTVsrc)/sizeof(irTVsrc[0]), khz); Serial.println("IRreq irTVsrc sent"); }
  else if (req.indexOf("/irTVmute") != -1) { irsend.sendRaw(irTVmute, sizeof(irTVmute)/sizeof(irTVmute[0]), khz); Serial.println("IRreq irTVmute sent"); }
  else if (req.indexOf("/irTVvdn") != -1) { irsend.sendRaw(irTVvdn, sizeof(irTVvdn)/sizeof(irTVvdn[0]), khz); Serial.println("IRreq irTVvdn sent"); }
  else if (req.indexOf("/irTVvup") != -1) { irsend.sendRaw(irTVvup, sizeof(irTVvup)/sizeof(irTVvup[0]), khz); Serial.println("IRreq irTVvup sent"); }
  else if (req.indexOf("/irTVchup") != -1) { irsend.sendRaw(irTVchup, sizeof(irTVchup)/sizeof(irTVchup[0]), khz); Serial.println("IRreq irTVchup sent"); }
  else if (req.indexOf("/irTVchdn") != -1) { irsend.sendRaw(irTVchdn, sizeof(irTVchdn)/sizeof(irTVchdn[0]), khz); Serial.println("IRreq irTVchdn sent"); }
  else if (req.indexOf("/irALLpwr") != -1) { irsend.sendRaw(irRECpwrON, sizeof(irRECpwrON)/sizeof(irRECpwrON[0]), khz); irsend.sendRaw(irTVpwr, sizeof(irTVpwr)/sizeof(irTVpwr[0]), khz); delay(2000); irsend.sendRaw(irRECsrc, sizeof(irRECsrc)/sizeof(irRECsrc[0]), khz); Serial.println("IRreq irALLpwr sent"); }
  else if (req.indexOf("/irRECpwr") != -1) { irsend.sendRaw(irRECpwr, sizeof(irRECpwr)/sizeof(irRECpwr[0]), khz); Serial.println("IRreq irRECpwr sent"); }
  else if (req.indexOf("/irRECpwrON") != -1) { irsend.sendRaw(irRECpwrON, sizeof(irRECpwrON)/sizeof(irRECpwrON[0]), khz); Serial.println("IRreq irRECpwrON sent"); }
  else if (req.indexOf("/irRECpwrOFF") != -1) { irsend.sendRaw(irRECpwrOFF, sizeof(irRECpwrOFF)/sizeof(irRECpwrOFF[0]), khz); Serial.println("IRreq irRECpwrOFF sent"); }
  else if (req.indexOf("/irRECmute") != -1) { irsend.sendRaw(irRECmute, sizeof(irRECmute)/sizeof(irRECmute[0]), khz); Serial.println("IRreq irRECmute sent"); }
  else if (req.indexOf("/irRECvdn") != -1) { irsend.sendRaw(irRECvdn, sizeof(irRECvdn)/sizeof(irRECvdn[0]), khz); Serial.println("IRreq irRECvdn sent"); }
  else if (req.indexOf("/irRECvup") != -1) { irsend.sendRaw(irRECvup, sizeof(irRECvup)/sizeof(irRECvup[0]), khz); Serial.println("IRreq irRECvup sent"); }
  else { Serial.println("invalid request"); client.stop(); return; }

  client.flush();
  client.print("HTTP/1.1 200 OK\r\n"); delay(1);
  Serial.println("Client Disconnected");
}

script.js (웹앱 자바스크립트, jQuery 필요)

function buttonClick(clicked_id) {
  var ip = "192.168.1.62"; // ESP8266 IP
  if (clicked_id == "irTVpwr") { $.get("curl.php", { room: ip, button: "irTVpwr" }); }
  else if (clicked_id == "irTVsrc") { $.get("curl.php", { room: ip, button: "irTVsrc" }); }
  else if (clicked_id == "irTVmute") { $.get("curl.php", { room: ip, button: "irTVmute" }); }
  else if (clicked_id == "irTVvdn") { $.get("curl.php", { room: ip, button: "irTVvdn" }); }
  else if (clicked_id == "irTVvup") { $.get("curl.php", { room: ip, button: "irTVvup" }); }
  else if (clicked_id == "irTVchup") { $.get("curl.php", { room: ip, button: "irTVchup" }); }
  else if (clicked_id == "irTVchdn") { $.get("curl.php", { room: ip, button: "irTVchdn" }); }
  else if (clicked_id == "irRECpwr") { $.get("curl.php", { room: ip, button: "irRECpwr" }); }
  else if (clicked_id == "irALLpwr") { $.get("curl.php", { room: ip, button: "irALLpwr" }); }
  else if (clicked_id == "irRECpwrON") { $.get("curl.php", { room: ip, button: "irRECpwrON" }); }
  else if (clicked_id == "irRECpwrOFF") { $.get("curl.php", { room: ip, button: "irRECpwrOFF" }); }
  else if (clicked_id == "irRECmute") { $.get("curl.php", { room: ip, button: "irRECmute" }); }
  else if (clicked_id == "irRECvdn") { $.get("curl.php", { room: ip, button: "irRECvdn" }); }
  else if (clicked_id == "irRECvup") { $.get("curl.php", { room: ip, button: "irRECvup" }); }
}

index.html (웹앱 UI)

<html>
<head>
  <link href="style.css" rel="stylesheet" type="text/css" />
  <script src="jquery-2.0.3.min.js"></script>
  <script src="script.js"></script>
  <link rel="manifest" href="manifest.json">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
</head>
<body>
  <div align="center" class="div_text_shadow">
    <button class="btn2" id="irALLpwr" onClick="buttonClick(this.id)">Power Up Everything</button><br><br><br>
    <h1>TV</h1>
    <button class="btn" id="irTVpwr" onClick="buttonClick(this.id)">Power</button><br><br>
    <button class="btn" id="irTVvup" onClick="buttonClick(this.id)">Vol +</button>
    <button class="btn" id="irTVsrc" onClick="buttonClick(this.id)">Source</button>
    <button class="btn" id="irTVchup" onClick="buttonClick(this.id)">Ch +</button><br><br>
    <button class="btn" id="irTVvdn" onClick="buttonClick(this.id)">Vol -</button>
    <button class="btn" id="irTVmute" onClick="buttonClick(this.id)">Mute</button>
    <button class="btn" id="irTVchdn" onClick="buttonClick(this.id)">Ch -</button>
    <br><br><br>
    <h1>Receiver</h1>
    <button class="btn" id="irRECpwr" onClick="buttonClick(this.id)">Power</button><br><br>
    <button class="btn" id="irRECmute" onClick="buttonClick(this.id)">Mute</button>
    <button class="btn" id="irRECvdn" onClick="buttonClick(this.id)">Vol -</button>
    <button class="btn" id="irRECvup" onClick="buttonClick(this.id)">Vol +</button>
  </div>
</body>
</html>

curl.php (프록시 PHP)

<?php
$room = $_GET['room'];
$button = $_GET['button'];
$service_url = 'https://' . $room . '/' . $button;
$curl = curl_init($service_url);
curl_setopt($curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
$curl_response = curl_exec($curl);
curl_close($curl);
echo $curl_response;
?>

manifest.json (안드로이드 웹앱 매니페스트)

{
  "name": "WiFi Remote",
  "icons": [
    {"src": "remote_icon_36.png", "sizes": "36x36", "type": "image/png", "density": 0.75},
    {"src": "remote_icon_48.png", "sizes": "48x48", "type": "image/png", "density": 1.0},
    {"src": "remote_icon_128.png", "sizes": "128x128", "type": "image/png", "density": 1.0},
    {"src": "remote_icon_192.png", "sizes": "192x192", "type": "image/png", "density": 1.0}
  ],
  "scope": "/remote/",
  "start_url": "/remote/index.html",
  "display": "fullscreen",
  "orientation": "portrait"
}

회로도

와이파이 지원 IR 블라스터: ESP8266과 아두이노로 DIY 스마트 리모컨 만들기
5V 전원에서 NPN 트랜지스터로 직렬 IR LED 2개 구동 (저항 없음)
와이파이 지원 IR 블라스터: ESP8266과 아두이노로 DIY 스마트 리모컨 만들기
여러 IR LED 병렬 구동용: NPN이 PNP 트랜지스터들 스위칭 (전압 강하 최소화)

제조공정

  1. Wi‑Fi 실시간 위치 시스템(RTLS) 핵심 한계 6가지
  2. 2015년 떠오르는 무선 기술: M2M & IoT를 위한 가이드
  3. 3달러 ESP8266에 TLS 웹 서버 30분 만에 배포하기
  4. NodeMCU DHT22 센서 + 라즈베리 파이 3 B+ MQTT 브로커 연동 가이드
  5. 라즈베리 파이 & 안드로이드 스마트폰으로 WiFi 제어 로봇 만들기
  6. 파이썬으로 와이파이 제어 라즈베리파이 로봇 만들기 – 단계별 가이드
  7. 와이파이 통화(WiFi Calling) 완벽 가이드: 작동 원리와 장점
  8. 스마트 IoT 자 제작: ESP8266, 아두이노 & 초음파 센서로 실시간 모니터링
  9. 아두이노와 ESP8266으로 만드는 와이파이 스마트 도어락 - 스마트폰으로 잠금 해제
  10. 비드 블라스팅 완벽 가이드: 매끄럽고 세련된 마감을 위한 핵심 노하우