방에 에어컨을 달았는데, 마찬가지로 HomeKit 으로 제어해보고 싶었다.
리모컨이 있으니 전등에 비해 꼭 필요한건 아니였지만...
시리로 켜고 끈다거나,, 온도에 따라 자동 제어 같은걸 해보고 싶어서...
가장 먼저 에어컨 리모컨 신호를 받아서, 아두이노로 다시 보내서 테스트해보는걸 먼저 해보았다.

이번에 방에 설치한 에어컨.
LG WHISEN SUC066BC 모델...
이모가 쓰시던걸 받아왔다.
IR 은 예전에 적외선 송수신 킷을 구매했던걸 활용하였다.

구매처는 아트로봇, 링크
무려 13년도에 샀던건데, 구매내역에 잘 보관되어 있었다..
어떻게 연결하는지 등은 Hook-up Guide 글을 보자.
연결 후에는 아두이노에서 IRremote 라이브러리를 내려받고 사용하면 된다.
라이브러리를 받은 후에
libraries/IRremote/examples/ 폴더로 가면
IRsendRawDemo, IRrecvDumpV2 예제가 있으니, 이 스케치로 원래 리모컨의 신호를 읽고, 날려보면 된다.
예시 폴더에 LGACSendDemo.. 가 있는 것은 이 글을 보면서 발견했는데,,
https://github.com/chaeplin/lgwhisen 이런 오픈소스도 있지만 결과적으로 나는 제대로 작동하지 않아서, 다시 코드를 짰다.
여튼 돌아와서, IRrecvDumpV2 스케치를 업로드한 후에 리모컨으로 눌러보면

이런식으로 신호가 나오게된다.
rawData 를 복사해서 IRSendRawDemo 의 예시처럼,
irsend.sendRaw(rawData, sizeof(rawData) / sizeof(rawData[0]), khz)
이렇게 보내주면 그대로 복사하는거라고 하는데....
실제로 해보면 안된다. ㅡㅡ;
또한 에어컨 리모컨은 특성상, 각 신호가 온도, 바람세기 등의 정보를 가지고 있는데 저걸 어떻게 분석해야하나.. 난감했는데,
위의 LG 에어컨 코드들이 제대로 작동은 안할지언정 기본 베이스가 같아서 도움이 되었다.
위 코드와 IRremote 라이브러리의 ir_LG.cpp 등의 코드를 보면,
업다운 신호가 한 세트로, +500, -500 이면 0, +500, -1500 이면 1 이 되어서 28 bit 의 신호를 만들게 된다.
위 사진의 경우 1000 1000 1100 0000 0000 0101 0001 이 되겠지.
이제 IRrecvDumpV2 스케치에서 loop 함수를 이렇게(접어둠) 바꿔두고 리모컨을 눌러보며 신호를 분석해보면 된다.
void loop ( )
{
decode_results results; // Somewhere to store the results
if (irrecv.decode(&results)) { // Grab an IR code
for (int i = 2; i < results.rawlen; i=i+2) {
Serial.print((results.rawbuf[i] * USECPERTICK > 1000) ? '1' : '0');
if (i % 8 == 0) Serial.print(' ');
}
Serial.println("");
irrecv.resume(); // Prepare for the next value
}
}
git 에 https://github.com/chaeplin/lgwhisen/blob/master/decoding.md 이것도 참고 사항이 되긴 하지만...
리모컨 몇번 눌러보면 금방 파악이 되긴 한다.
나의 버전으로 다시 정리를 해보자면 28bit 가
1000 1000 (1) (2) (3) (4) CRC
이렇게 생겨먹었고, CRC 는 ( (1)+(2)+(3)+(4) ) & 0xFF
모드별로 보면
(2)
냉방: 0 => 0000 x000 xxxx xxxx
제습: 1 => 0000 x001 1001 xxxx
송풍: 2 => 0000 x010 0011 xxxx
인공지능: 3 => 0000 x011 0010 0101
ㄴ 0: 전원을 켤때, 1: 이미 켠 후 조작시
대충 이렇게 생겼다.
(3) 은 주로 온도. 온도에서 15를 뺀 값
(4) 는 바람 세기.
low: 0
mid: 2
hight: 4
auto: 5
이렇게였는데, 이건 위의 예제들의 분석과 동일하다.
여튼 이제 원하는 28bit 를 만들 수 있으니 이 신호를 보내면 된다.

아두이노를 두개 준비해서 보낸 신호를 확인해봐도 좋다.
먼저 적외선 LED 를 아두이노에 연결는데, 타이머를 사용해서 핀이 정해져있다.
Uno, Nano 등의 보드의 경우 PIN 3 을 사용하면 된다.
연결 후 스케치를 업로드 해보면 sendRaw 함수로는 여전히 작동을 안하는데..
28bit 를 long 으로 만들어서 irsend.sendLG 함수를 이용하면 된다.
다만 내가 쓴 위 적외선 송수신 킷의 경우, 신호가 많이 약해서 상당히 가까이,
약 30cm 거리에서 작동해서 처음에 안되는 줄 알았다..
앞으로 코드가 많이 수정되겠지만, 이 단계까지의 나의 코드는 아래와 같다.
#include <IRremote.h>
IRsend irsend;
/*
Arduino Uno, Nano, ...: PIN 3
1000 1000 (1) (2) (3) (4) CRC
(2) MODE
냉방: 0 0000 x000 xxxx xxxx
제습: 1 0000 x001 1001 xxxx
송풍: 2 0000 x010 0011 xxxx
인공지능: 3 0000 x011 0010 0101
ㄴ 0: with poweron, 1: already on
(3) TEMP
temp - 15
(4) FAN
low: 0
mid: 2
hight: 4
auto: 5
*/
void make_signal(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
a &= 0xF; b &= 0xF; c &= 0xF; d &= 0xF;
uint16_t val = (a << 12 | b << 8 | c << 4 | d) & 0xFFFF;
uint8_t crc = (a + b + c + d) & 0xF;
unsigned long data = 0x08800000;
data |= val << 4 | crc;
irsend.sendLG(data, 28);
}
void cooling(int temp, int fan) {
temp = min(max(18, temp), 30);
make_signal(0, 8, temp-15, fan);
}
void poweron(int temp, int fan) { // with cooling mode
temp = min(max(18, temp), 30);
make_signal(0, 0, temp-15, fan);
}
void poweroff() {
make_signal(12, 0, 0, 5);
}
//============================================================================//
void setup() {
Serial.begin(9600);
}
void loop() {
if (Serial.available() > 0) {
char c = Serial.read();
if (c == 'o') poweron(29, 0);
else if (c == 'p') poweroff();
else if (c == '6') cooling(26, 0);
else if (c == '7') cooling(27, 0);
else if (c == '8') cooling(28, 0);
else if (c == '9') cooling(29, 0);
else if (c == '0') cooling(30, 0);
}
}