작년 겨울 첫아이가 태어나 전기 히터로 아기 주변만 따뜻하게 하려 했습니다. 방을 옮길 때마다 히터를 들고 다니며 포인트 온도 제어로 효율적이라 생각했는데, 한 달 뒤 전기 요금이 평소의 두 배로 나와 가스 보일러로 집 전체를 24시간 난방하는 게 더 싸다는 걸 깨달았습니다.
다시 겨울이 와 WiFi 온도조절기로 업그레이드해 이불 속이나 아래층에서도 난방 켤 수 있게 했습니다. 편리하지만 여전히 대부분 한 공간에만 있는데 집 전체를 난방합니다.
더 스마트하게
조명을 제어하는 모션 센서 로그를 보니 평일 저녁 4-5시간 가족이 아래층에서 보내고 9시쯤 위층 침실로 올라가 아침까지 잡니다. 주말은 다르지만 평일은 10개월 아기의 엄격한 수면 일정 덕분에 시계처럼 정해져 있습니다.
다중 존 온도 모니터링과 무선 제어 덕트 댐퍼가 있는 HVAC 시스템을 봤지만 하드웨어가 매우 비싸고 소프트웨어는 사후 고려사항처럼 보였습니다(기술 발전에 따른 지원/업데이트 부족). 이미 WiFi 온도 센서를 점유형 AC 컨트롤러용으로 보유하고 있고 소형 서보를 프로젝트에 써보고 싶었습니다. HVAC 시스템의 덕트 벤트를 여닫는 WiFi 연결 서보만 있으면 됩니다. 충분히 쉬워 보였습니다.
MediaTek의 LinkIt™ Smart 7688
MediaTek의 새로운 LinkIt Smart 7688 Duo HDK는 OpenWRT를 실행하는 WiFi 연결 마이크로프로세서에 온보드 Arduino 호환 마이크로컨트롤러를 탑재했습니다. 이 프로젝트에 완벽한데, 여러 센서와 서보 모터에 필요한 모든 I/O 핀과 기존 온도 센서와 통신할 WiFi 기능이 있습니다. 또한 OpenWRT에 uHTTPd가 미리 설치되어 웹앱을 LinkIt Smart 7688 HDK에서 직접 실행해 완전히 독립적인 패키지로 만들 수 있습니다.
LinkIt Smart 7688 HDK 설정
내장 WiFi 보드는 처음이라 학습 곡선이 가파를 줄 알았는데, 설정은 말 그대로 3단계뿐입니다:
LinkIt Smart 7688 전원 켜고 컴퓨터에서 WiFi 액세스 포인트 연결
웹 인터페이스 로그인 후 내 WiFi 액세스 포인트 설정 입력
Arduino IDE에 "LinkIt Smart 7688 Duo" 보드 추가
끝입니다! Arduino IDE에서 "LinkIt Smart 7688 Duo" 보드 선택 시 다른 보드처럼 시리얼로 스케치 업로드 가능, 또는 WiFi로 업로드 가능(나중에 매우 유용). LinkIt Smart 7688 시작 가이드와 개발자 가이드도 참고하세요.
Arduino 스케치에서 마이크로컨트롤러가 WiFi를 직접 사용하려면(Yun 브리지 라이브러리 호환성 활성화), HDK에 SSH 접속해 비트 하나만 뒤집으면 됩니다.
여기서 라인 복사/붙여넣기만 하면 됩니다.
WiFi 클라이언트 및 서보 컨트롤러
내장 Yun 브리지 라이브러리 호환성과 Arduino IDE 사용으로 기존 코드 적용이 매우 쉽습니다. 브리지 라이브러리로 HDK가 온보드 웹 서버의 벤트 설정(위층, 아래층, 전체 집)을 폴링해 두 서보를 조정합니다. 아래 영상에서는 Raspberry Pi 2에서 실행했지만 웹앱을 LinkIt Smart 7688로 포팅해 더 효율적으로 만들었습니다(코드는 마지막에 포함).
웹앱이 PHP로 되어 있어 opkg 패키지 매니저로 설치했습니다(OpenWRT에 포함).
그 후 /etc/config/uhttpd 파일을 수정해 PHP 해석 줄 추가:
uHTTPd 재시작
하드웨어
서보 혼을 벤트에 직접 부착하고 알루미늄 브래킷을 제작해 스테인리스 스탠드오프로 서보를 고정했습니다. 아직 약간의 유격이 있어 지퍼 타이로 더 영구적으로 보강할 예정입니다.
소프트웨어
이 빌드는 세 가지 구성 요소로 요약됩니다:
집 안의 온도 센서들이 LinkIt Smart 7688에서 실행되는 uHTTPd 웹 서버로 주기적 업데이트 전송
LinkIt Smart 7688에서 실행되는 웹앱으로 현재 상태 표시 및 벤트 수동 재설정 가능
LinkIt Smart 7688의 MCU가 시간대에 따라 위층 또는 아래층으로 따뜻한 공기 유도하도록 벤트 제어
온도 센서는 ATtiny85나 Arduino Nano에 DHT11/DHT22 연결하고 ESP8266으로 소프트웨어 시리얼을 통해 웹 서버로 온습도 정보 전송. 이 센서들은 원래 포인트형 에어컨 프로젝트 2단계로 설계/개발되어 별도 포스팅에 자세히 다뤘습니다(이 부분은 선택사항이지만 시스템 튜닝과 자동화에 활용할 추가 메트릭 제공).
LinkIt Smart 7688 HDK는 다락방에 설치되어 덕트 벤트에 직접 연결. OpenWRT 측에서 크론잡으로 벤트 상태 텍스트 파일 업데이트. MCU 측 Arduino 스케치가 이 텍스트 파일을 주기적으로 확인해 벤트 조정.
벤트 상태 확인과 수동 재설정을 위해 LinkIt Smart 7688에서 직접 호스팅되는 소규모 웹앱 제작. DDNS와 포트 포워딩으로 집 밖에서도 조회/제어 가능.
개선 여지
밤이 이미 추워져 급히 작동시켜야 해 신속 배포를 위해 일부 타협했습니다. 하드웨어 측면에선 서보용 제대로 된 마운트를 3D 프린트할 예정입니다(현재 임시 마운트에 유격 있음). 데모에 쓴 소형 RC 서보가 플라스틱 기어라 수년 버티기 어려워, 다락방에 다시 오르고 싶지 않아 금속 기어 대형 서보로 교체할 계획입니다.
나중엔 각 방 벤트에 추가 WiFi 서보를 달고 모두 단일 LinkIt Smart 7688에 연결할 수도 있습니다.
현재 온도 프로브는 CSV 파일에 기록만 해 추적/그래프화 용도. 집이 특히 추우면 히터를 일찍 켜고 싶지만 WiFi 온도조절기에 API가 없습니다. 모션 센서에서 실시간 실내 점유율을 도출해 시간 기반이 아닌 실시간으로 벤트가 자동 조정되도록 홈 오토메이션 시스템과 연동하고 싶습니다(특히 주말엔 일정이 유동적이라).
코드
LinkIt Smart Duo WiFi 벤트 (C/C++)
Arduino Yun의 Bridge와 HTTP Client 라이브러리를 사용해 웹 서버를 폴링하고 필요에 따라 HVAC 벤트 개폐.
/* Sweep 서보 예제(BARRAGAN) 및 Yun HTTP Client 예제(Tom Igoe) 기반
둘 다 퍼블릭 도메인.
HAI(Home Automation w/ Intelligence)용으로 Buddy Crotty가 수정.
이 스케치는 웹 서버에 모든 무거운 처리를 맡김. 라즈베리 파이 같은 별도 장치이거나
LinkIt Smart의 MPU 측 OpenWRT에서 실행 가능.
웹 서버는 집 안팎 온도 판독값 수집, 시간대/외부 날씨(예보 포함)/실내 점유율 기반으로
온풍/냉풍 보내야 할 위치 결정 후 'upstairs', 'downstairs', 'wholehouse' 중 하나로 출력. */
#include <Servo.h>
#include <Bridge.h>
#include <HttpClient.h>
Servo upservo; // 위층 서보
Servo downservo; // 아래층 서보
int uppos = 0, downpos = 0;
const char* host = "192.168.1.80"; // 홈 오토메이션 웹서버 내부 IP
int dopenangle = 80, dcloseangle = 0; // 서보 개폐 각도
int uopenangle = 140, ucloseangle = 0;
void setup() {
upservo.attach(9); downservo.attach(10);
pinMode(13, OUTPUT); digitalWrite(13, LOW);
Bridge.begin(); digitalWrite(13, HIGH);
}
void wholehouse() {
if (downpos <= dcloseangle) {
for(downpos = dcloseangle; downpos <= dopenangle; downpos += 1) {
downservo.write(downpos); delay(20);
}
}
if (uppos <= uopenangle) {
for(uppos = ucloseangle; uppos <= uopenangle; uppos += 1) {
upservo.write(uppos); delay(20);
}
}
}
void downstairs() {
if (downpos <= dopenangle) {
for(downpos = dcloseangle; downpos <= dopenangle; downpos += 1) {
downservo.write(downpos); delay(20);
}
}
if (uppos >= ucloseangle) {
for(uppos = uopenangle; uppos >= ucloseangle; uppos -= 1) {
upservo.write(uppos); delay(20);
}
}
}
void upstairs() {
if (uppos <= uopenangle) {
for(uppos = ucloseangle; uppos <= uopenangle; uppos += 1) {
upservo.write(uppos); delay(20);
}
}
if (downpos >= dcloseangle) {
for(downpos = dopenangle; downpos >= dcloseangle; downpos -= 1) {
downservo.write(downpos); delay(20);
}
}
}
void loop() {
HttpClient client;
String cmd = "https://"; cmd += host; cmd += "/vent.txt";
client.get(cmd);
while (client.available()) {
int c = client.read();
if (c == '2') upstairs();
if (c == '1') downstairs();
if (c == '0') wholehouse();
}
delay(60000);
}
WiFi 온도 센서 (C/C++)
연결된 온습도 센서. 데이터를 중앙 홈 오토메이션 서버로 로깅.
/* HAI(Home Automation w/ Intelligence)용으로 Buddy Crotty가 수정
HTTP GET 요청으로 HAI 서버에 데이터 전송
연결은 ESP8266 WiFi 모듈로 소프트웨어 시리얼 연결, 이미 와이파이 설정됨
SSID/비번 재설정하려면 주석 해제 */
const char* host = "192.168.1.80";
const char* devID = "downstairs";
#include <SoftwareSerial.h>
SoftwareSerial ser(10, 11); // ESP8266 제어용 소프트웨어 시리얼
long utime = 300000; // 업데이트 간격 (5분, AC 전원용)
// 900000 = 15분 (배터리/태양광), 5000 = 5초 (테스트)
// ESP8266 슬립 모드 해제 위해 10000ms 이상 필요 (배터리 모드)
#include <DHT.h>
#define DHTPIN 2
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(9600); ser.begin(9600);
ser.println("AT+RST"); delay(10);
dht.begin();
Serial.println("Giving everything a chance to warm up");
Serial.println("|-------------------------|"); Serial.print("|");
for(int x = 0; x < 25; x++){ Serial.print("#"); delay(1000); }
Serial.println("|"); Serial.println("Done");
}
float t = 0; int h = 0; float hi = 0;
void loop() {
Serial.println();
t = dht.readTemperature(true); h = dht.readHumidity();
hi = dht.computeHeatIndex(t, h);
Serial.print("Temperature: "); Serial.print(t); Serial.print(" *F\t");
Serial.print("Heat Index: "); Serial.print(hi); Serial.println(" *F");
Serial.print("Humidity: "); Serial.print(h); Serial.println("%\t");
Serial.print("...connecting to "); Serial.println(host);
String cmd = "AT+CIPSTART=\"TCP\",\""; cmd += host; cmd += "\",80";
ser.println(cmd); delay(1000);
if(ser.find("Error")){ Serial.println("AT+CIPSTART error"); return; }
String getStr = "GET /tempupdate.php?ID="; getStr += devID;
getStr += "&field1="; getStr += t; // 온도
getStr += "&field2="; getStr += h; // 습도
getStr += "&field3="; getStr += hi; // 열지수
getStr += "\r\n\r\n";
Serial.print("Sending data URL: "); Serial.println(getStr);
cmd = "AT+CIPSEND="; cmd += String(getStr.length());
ser.println(cmd);
if(ser.find(">")){ ser.print(getStr); Serial.println("Success!"); }
else{ ser.println("AT+CIPCLOSE"); Serial.println("Connection Failed"); }
Serial.print("Next update in "); Serial.print(utime / 1000); Serial.println(" seconds");
delay(utime);
}
Vent.php (PHP)
상태 확인 및 벤트 구성 수동 오버라이드.
<html> <head> <style>
.myButton { /* 버튼 스타일 생략 */ }
</style> <link rel="manifest" href="manifest.json"> </head>
<body> <div align="center">
<a href="vent.php?zone=2" class="myButton">Upstairs Only</a><br><br>
<a href="vent.php?zone=0" class="myButton">Whole House </a><br><br>
<a href="vent.php?zone=1" class="myButton">Downstairs Only</a> <br><br>
<?php
$myfile = fopen("vent.txt", "r") or die("Unable to open file!");
$status = fgets($myfile);
if ($status != "") print "<h1>Vents currently open for ";
if ($status === "0") print "the Whole House";
if ($status === "1") print "Downstairs Only";
if ($status === "2") print "Upstairs Only";
print "<br>"; fclose($myfile);
if ($_GET["zone"] == ""); else {
$myfile = fopen("vent.txt", "w+") or die("Unable to open file!");
if ($_GET["zone"] != "") print "<h1>Sending command to open vents for ";
if ($_GET["zone"] === "0") print "the Whole House";
if ($_GET["zone"] === "1") print "Downstairs Only";
if ($_GET["zone"] === "2") print "Upstairs Only";
print "</h1>"; fwrite($myfile, $_GET['zone']); fclose($myfile);
}
?> </div> </body> </html>