인터페이스는 정보를 전달하고 제어를 가능하게 합니다. 대부분의 홈 오토메이션 플랫폼은 '가상 인터페이스'에 의존합니다. 스마트폰 앱을 열어 집 상태를 확인하고 조명을 켜고 끕니다. 외출 시엔 괜찮지만, 가상 화면을 보고 정보를 파악하는 건 집중이 필요하고 직관적이지 않습니다.
저는 '물리적 인터페이스'를 만들고 싶었습니다. 실제 집 상태를 물리적으로 모방하는 작은 모델 하우스입니다. 실제 차고 문이 열리면 모델의 차고 문도 열립니다. 이 모델을 거실 탁자나 사무실 책상에 두면 한눈에 차고 문이나 현관문 개폐 상태를 알 수 있습니다. VPN으로 집과 연결하면 원격에서도 가능하죠. 이 물리적 인터페이스는 만드는 이의 창의성만큼 유연하게 확장 가능합니다.
구현 계획
모델 하우스 제작: 문 위치, 에너지 사용량, 조명 온오프 상태 표시
에너지 모니터 제작: OpenEnergyMonitor 아두이노 라이브러리 사용, 모델 하우스와 OpenHAB로 데이터 전송
문/창문 위치 센서 연동: Wink 허브와 Wink 'Tripper' 접점 센서 데이터를 DIY 홈 오토메이션에 활용
출력 제어: Wink와 아두이노로 실제 차고 문 개폐, 조명 온오프 제어
모델 하우스에는 서보와 LED가 아두이노 컨트롤러에 연결돼 있습니다. 이 컨트롤러는 MQTT 메시지(문 위치, 에너지 사용량)를 구독해 서보를 작동시킵니다. 조명 표시 LED도 마찬가지. 센서 정보를 MQTT 브로커로 보내는 방법은 여러 가지라 나중에 자세히 설명합니다. 중심엔 라즈베리파이가 MQTT 브로커(Mosquitto)와 OpenHAB을 돌립니다. OpenHAB은 모델 하우스 필수는 아니지만 스마트폰 앱 인터페이스와 원격 모니터링/제어를 위해 필요합니다. 물리적 인터페이스를 원한다고 가상 인터페이스를 버리는 건 아니니까요.
모델 하우스엔 버튼 2개도 있습니다. 하나는 지그비 전구 온오프 토글, 다른 하나는 실제 집 차고 문 개폐 제어.
박스 측면 날개 하나를 잘라 다른 쪽(아직 붙어 있는) 날개에 붙여 지붕 형성. 직각 유지를 위해 작은 박스로 받치고 나중에 레고 조각으로 보강. 집 주변 물건로 직각 만들면 됩니다.
문/창문
표시하고 싶은 문 위치에 구멍 내고 경첩으로 문 달기(양면 테이프 활용). 화려하진 않지만 집 주변 재료로 improvisation 하세요. 3M 폼 양면 테이프(훅용)로 서보 모터도 고정했습니다. 차고 문은 서보 암이 밀어서 열고 중력으로 닫히게. 현관문은 경첩 달린 문에 실 묶어 서보 암으로 당겨 열리게. 보이는 노란 레고 조각은 서보를 경첩에서 떨어뜨리는 오프셋용.
에너지 미터
화살표 모양 잘라 서보 암에 붙이고, 관목 모양으로 킬로와트 눈금 표시. 서보를 박스에 마찰 고정(관목 사이에 끼움). 에너지 서보 위에 빨간 LED 테이프로 부착.
조명/차고 문 제어용 순간 버튼 2개. 너트로 박스 벽에 고정, 반대편 금속 접점에 전선 납땜.
2단계: 모델 하우스 - 배선 및 코드
배선
박스 안 배선은 엉망이라 배선도로 대체. 핀 할당만 보면:
상태 표시 LED: 핀 7
조명 표시 LED: 핀 4
서보 1 (현관문): 핀 3
서보 2 (차고 문): 핀 5
서보 3 (에너지 미터): 핀 6
버튼 1 (조명 온오프): 핀 8
버튼 2 (차고 문 개폐): 핀 9
컨트롤러 옵션
편한 대로 선택. 저렴한($20) 옵션: 아두이노 우노 클론 + 이더넷 실드. 처음엔 이랬는데 랜선 때문에 제약이 있어 아두이노 윤($65)으로 변경. 와이파이 자유도 확보. 윤 설정은 간단: 1) 공유기에 랜선 연결, 2) 윤 웹페이지 접속, 3) 비밀번호 및 공유기 고정 IP 설정, 4) 윤 스케치 업로드.
아래 아두이노 우노용/윤용 스케치 첨부. 이더넷 브리지 부분 빼곤 거의 동일. 윤으로 바꾼 후 버튼 2개 추가돼 우노 스케치엔 버튼 코드 없음.
1단계 영상은 윤 버전. 우노+이더넷 실드 버전 영상도 있음(버튼 미장착 상태지만 나머지 동일).
첨부 파일: smarthome_ethernet.ino, smarthome_yun.ino
3단계: 센서 - 에너지 모니터
모델 하우스로 에너지 사용량 표시 준비됐으니, 이제 집 전력 소비를 읽어 MQTT 브로커로 발행할 센서 노드 제작. 방법 여러 가지지만 전 아두이노 윤 사용(가장 단순, 최저가는 아님). 아두이노 우노+이더넷 실드+와이파이 브리지 공유기나, 라즈베리파이+OpenEnergyMonitor 패키지도 가능. 여기선 윤 방식만 다룸.
이베이 $7 전류 변환기(YHDC SCT-013-000 100A) 사용. 위 배선도대로 연결하고 하단 아두이노 윤 스케치 업로드. MQTT 브로커 IP 주소 코드에 반영 필수. OpenEnergyMonitor 라이브러리 참고: https://github.com/openenergymonitor/EmonLib
이상적 부품값:
부담 저항 (Burden Resistor) = 33Ω
분압 저항 = 10kΩ
커패시터 = 10μF
제 에너지 모니터 사진은 회로도와 다릅니다. 정확한 부품이 없어 68Ω 저항 2개 병렬(≈33Ω)로 대체, 10μF 커패시터 대신 22μF 2개 직렬(≈11μF)로 대체. 실측 시 시판 암페어미터보다 꾸준히 1A 높게 나와 코드에서 1A 보정.
OpenHAB 연동
데이터 들어오니 OpenHAB에도 표시해 그래프로 보게 설정.
아이템 정의:
Number itm_smarthome_energy_amps "Energy (amps) [%.1f]" {mqtt="<[mymosquitto:2853:state:default]"}
Number itm_smarthome_energy_watts "Energy (Watts) [%.1f]" {mqtt="<[mymosquitto:2852:state:default]"}
사이트맵:
Text label="Energy" icon="firstfloor" {
Frame label="Energy Usage" {
Text item=itm_smarthome_energy_amps
Text item=itm_smarthome_energy_watts
Chart item=itm_smarthome_energy_watts period=h refresh=5000
}
}
이전 프로젝트에서 $5 무선 트랜시버로 MQTT 게이트웨이에 센서 데이터 전송 방식 사용 중. 차고 문 상태를 OpenHAB로 보내고, 데모 영상에서 모델 하우스 차고 문이 실제 차고 문 상태 반영하는 게 이 방식. 상세 코드/회로는 이전 인스트럭터블 참조. 차고라 외관 상관없음.
조도 센서로 조명 온오프 상태 감지. 위 프로젝트의 '우버 센서' 일부로 포토레지스터로 밝기 정보 무선 전송. 여기선 MQTT 토픽(조도) 구독만 함.
배터리 구동 리드 스위치 센서도 동일 트랜시버/게이트웨이로 만들었으나 외관 좋지 않아 실내 문/창엔 부적합. 그래서 외관 좋은 소비자용 Wink 리드 스위치 센서 사용 → 다음 단계.
5단계: 센서 - Wink 허브
초기 Wink 허브 사용자가 허브 루팅 후 PHP 익스플로잇으로 'aprontest' 유틸리티 실행해 페어링된 기기 제어하는 블로그 글 발견. 이 방법으로 OpenHAB 인터페이스에서 조명 제어 성공.
가장 큰 장점: 로컬 제어 가능. Wink 허브/API는 원래 인터넷 통해 Wink 서버 거쳐야 조명 제어/센서 상태 조회 가능. PHP 익스플로잇으로 로컬 LAN 내에서 모든 처리 가능. 훌륭합니다.
라즈베리파이에서 돌리는 PHP 스크립트 첨부(파일 열기 안 되면 링크 시도). 이 스크립트는 Wink 허브를 폴링해 Wink Tripper 리드 스위치 2개 상태 조회 후 OpenHAB REST API로 전송. OpenHAB이 MQTT 토픽 발행→모델 하우스 컨트롤러가 구독해 문 개폐 반영.
첨부된 OpenHAB 설정 파일 3개(items, sitemap, rules) 필요. 폴링 스크립트가 REST로 상호작용하는 접점 아이템 정의, 지그비 전구 제어 예제 스크립트도 포함.
앞선 단계 설정 파일들로 '커넥티드 전구'를 물리적 인터페이스와 OpenHAB 양쪽에서 제어 가능해짐. Wink 허브 지원 전구면 됨. Cree Connected Bulb, GE Wink 테스트 완료. 조도 센서와 버튼으로 스마트 전구 제어하는 영상 참조.
7단계: 출력 - 차고 문 오프너
5단계 설정 파일에 차고 문 제어용 OpenHAB 아이템 대부분 포함. 실제 차고 문 개폐 장치는 별도 필요. 이전 인스트럭터블 부품으로 제작:
아두이노 클론 ($10)
이더넷 실드 ($7)
리드 릴레이 ($2)
여분 차고 문 리모컨 ($22)
아두이노가 리드 릴레이 제어→건식 접점 개폐→여분 리모컨 버튼과 병렬 연결→리모컨 버튼 눌림 효과. 아두이노 핀 5와 GND를 리드 릴레이 코일 양단(외측 핀 2개)에 연결, 릴레이 내부 접점(내측 핀 2개)을 리모컨 버튼에 연결.
아두이노가 MQTT 토픽 구독하다 개폐 명령 수신 시 리드 릴레이 순간 작동→리모컨 버튼 닫힘→차고 문 작동. 아두이노 스케치 첨부. 5단계 rules 외에 garage_monitor_rules.txt 추가 필요. 초음파 센서 방식 안 쓰면 해당 부분 수정.
차고 문 소음 적거나 다른 곳에서도 개폐 알림 원하면 오디오 파일 재생 규칙 추가(첫 데모 영상 1분 30초 부근에서 확인 가능).
첨부: garage_monitor_rules.txt, grage_opener.ino
8단계: 기타 생각들
이 프로젝트의 결정들은 제가 익숙한 도구, 가진 부품, 우선순위 반영입니다. 여러분은 다른 도구, 부품, 아이디어가 있을 겁니다.
MQTT와 OpenHAB은 유연성이 높아 제 방식대로 안 해도 됩니다. 모델 하우스 컨트롤러는 아두이노, 라즈베리파이, 스파크코어 등 아무거나 가능. DIY 센서도 아두이노나 독립형 ESP8266 가능. 제약은 센서 데이터가 MQTT 브로커로 발행돼야 한다는 것뿐. OpenHAB에 USB Z-wave 스틱 있으면 Z-wave→MQTT 브리지로 Z-wave 센서 활용 가능.
Wink 개폐 센서도 루팅 없이 Wink API로 상태 가져와 OpenHAB이나 MQTT로 포워딩 가능(윙크 서버 경유하지만 루팅 제약 제거).
다양한 접근법이 있습니다. 전 아두이노와 종이 박스 같은 '최소 공통 분모' 하드웨어를 선호합니다.
코드
smarthome_yun.ino
smarthome_ethernet.ino
energy_monitor_yun.ino
smarthome_yun.ino (전문)
// Eric Tsai // 2015-04-13 // Arduino Yun code for controlling physical smart home interface
// Modify MQTT topic names to match your use // Modify MQTT broker IP address
#include
#include
#include
#include
unsigned long keepalivetime=0; unsigned long MQTT_reconnect=0;
YunClient yun; PubSubClient client("192.168.1.101", 1883, callback, yun);
bool conn_ok;
int ledPin = 4; int statusPin = 7;
int servo1Pin = 3; int servo2Pin = 5; int servo3Pin = 6;
Servo servo1; Servo servo2; Servo servo3;
int button1 = 8; int button2 = 9;
int servo1_target = 5; int servo2_target = 5; int servo3_target = 180;
int servo1_pos_cur = 5; int servo2_pos_cur = 5; int servo3_pos_cur = 180;
int servo1Opened = 5; int servo1Closed = 130;
int servo2Opened = 5; int servo2Closed = 150;
unsigned long servo1_time = 0; unsigned long servo2_time = 0; unsigned long servo3_time = 0;
unsigned long button1_db = 0; unsigned long button2_db = 0;
int button=0; int callback_flag = 0; char buff_message[12];
void callback(char* topic, byte* payload, unsigned int length) {
int mytopic = atoi(topic);
payload[length] = '\0';
float mymsg = atof((const char *) payload);
if (mytopic == 2822) { digitalWrite(ledPin, mymsg == 1 ? HIGH : LOW); }
if (mytopic == 2832) { servo1_target = (mymsg == 1) ? servo1Opened : servo1Closed; }
if (mytopic == 2842) { servo2_target = (mymsg == 1) ? servo2Opened : servo2Closed; }
if (mytopic == 2852) {
if (mymsg > 6000) mymsg = 6000; if (mymsg < 0) mymsg = 0;
servo3_target = 180-((1-((6000-mymsg)/6000))*180)-25; callback_flag=1;
}
}
void setup() {
pinMode(ledPin, OUTPUT); pinMode(statusPin, OUTPUT);
pinMode(button1, INPUT_PULLUP); pinMode(button2, INPUT_PULLUP);
digitalWrite(ledPin, HIGH); Bridge.begin();
client.connect("yun smarthome"); client.publish("watch", "Smart Home Connected!");
keepalivetime=millis(); MQTT_reconnect = millis();
client.subscribe("2822"); client.subscribe("2832"); client.subscribe("2842"); client.subscribe("2852");
digitalWrite(ledPin, LOW);
servo1.attach(servo1Pin); servo1.write(90);
servo2.attach(servo2Pin); servo2.write(90);
servo3.attach(servo3Pin); servo3.write(5);
}
void loop() {
client.loop();
if ((millis() - servo1_time) > 20) {
if (servo1_pos_cur > servo1_target) servo1_pos_cur--;
else if (servo1_pos_cur < servo1_target) servo1_pos_cur++;
servo1.write(servo1_pos_cur); servo1_time = millis();
}
if ((millis() - servo2_time) > 20) {
if (servo2_pos_cur > servo2_target) servo2_pos_cur--;
else if (servo2_pos_cur < servo2_target) servo2_pos_cur++;
servo2.write(servo2_pos_cur); servo2_time = millis();
}
if ((millis() - servo3_time) > 20) {
if (servo3_pos_cur > servo3_target) servo3_pos_cur--;
else if (servo3_pos_cur < servo3_target) servo3_pos_cur++;
servo3.write(servo3_pos_cur); servo3_time = millis();
}
if (callback_flag == 1) { callback_flag = 0; dtostrf(servo3_target, 4, 5, buff_message); client.publish("watch", buff_message); }
button = digitalRead(button1);
if (button == 0) { if ((millis() - button1_db > 3000) || (button1_db > millis())) { button1_db = millis(); client.publish("2862", "button1 yaya"); } }
button = digitalRead(button2);
if (button == 0) { if ((millis() - button2_db > 3000) || (button2_db > millis())) { button2_db = millis(); client.publish("2872", "button2 yaya"); } }
if ((millis() - MQTT_reconnect) > 60000) {
conn_ok = client.connected();
digitalWrite(statusPin, conn_ok ? HIGH : LOW);
if (!conn_ok) { client.disconnect(); delay(5000); while (client.connect("smarthouse") != 1) { digitalWrite(statusPin, LOW); delay(4000); } digitalWrite(statusPin, HIGH); }
MQTT_reconnect = millis(); client.publish("watch","smart home heartbeat!");
}
}