MQTT + Wemos D1 Mini로 에어컨 무선 원격제어하기


개요

이번 여름도 정말 덥습니다. 추워서 덜덜 떨던게 엊그제 같은데 사람은 망각의 동물인지 그런건 기억도 안나고

이번에 더운거만 또 생각납니다. 

미리 여름을 대비해서 에어컨을 달아 놨습니다. 그래서 집에 에어컨이 2개가 되었는데요.

원래 쓰던 에어컨은 금성 에어컨입니다. 현재는 LG로 이름이 바꼈는데 그게 1995년이니 진짜 오래된 유물이죠;

 

그래서 온도조절도 없고 추울때, 더울때만 있습니다. 그래도 출력 하난 무식하게 쌔서 요즘 나오는 에어컨 최대

출력하고 비교도 안될정도로 쌥니다.

2분만 틀어놔도 집이 서늘해져요.

 

어찌됬건 이 에어컨.. 제 방 밖에 있는데 키러 다니기가 너무 귀찮습니다.

그래서 삘을 받아서 MQTT + Wemos D1 Mini로 인터넷만 된다면

지구 어디서나 끄고 켤 수 있는 에어컨 무선제어를 만들었습니다.

 

작동구상

작동 방식은 다음과 같이 구상하였습니다. ESP8266 역시 MQTT SERVER에 연결되어 있습니다.

또한 DHT22를 추가해서 온습도를 실시간으로 모니터링 할 예정입니다.

서버

MQTT 서버는 Synology Docker의 eclipse-mosquitto 홈서버를 이용합니다.

docker에 mqtt 서버 여러가지가 있는데 eclipse mosquitto 가 가장 많이 받아서 써봤는데

 

용량도 적고(무려 5mb) 가벼워서 좋은데 기능을 다 뺀거 같습니다

ID,PW 같은거요.. 그래서 털릴 위험이 있습니다. 털려봤자 저희집 에어컨이 꺼졌다 커졌다 하는게 다겠지만요 ㅋㅋ..

 

회로도

회로도 그리기가 귀찮아서 걍 사진으로 대체했습니다

별건 없고 적외선 신호 보낼때 NPN 트랜지스터로 신호를 증폭시켜서 보내줍니다.

근데 Wemos D1 출력 전류가 약해서인지 증폭을 시켰는데도 적외선 신호가 더럽게 약합니다

 

별다른 선택지가 없어서 걍 했습니다

어짜피 에어컨 옆에 붙여둘거니..

 

+ DHT22 는 D5에 연결했습니다.

 

소스코드

//DHT_HUMIDITY
#include "DHTesp.h"
DHTesp dht;
String packet;
unsigned long lastSend = 0;
//OTA_SETTING

#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

#ifndef STASSID
#define STASSID "SSID"
#define STAPSK  "PW"
#endif

//const char* ssid = STASSID;
//const char* password = STAPSK;



//IR LED
#include <Arduino.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>

const uint16_t kIrLed = D2;  // ESP8266 GPIO pin to use. Recommended: 4 (D2).
IRsend irsend(kIrLed);  // Set the GPIO to be used to sending the message

/////

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

// MQTT 설정

const char* ssid = "SSID";
const char* password = "PW";
const char* mqtt_server = "Server";
const char* mqtt_topic = "UR_TOPIC";

WiFiClient espClient;
PubSubClient client(espClient);
unsigned long lastMsg = 0;
#define MSG_BUFFER_SIZE	(50)
char msg[MSG_BUFFER_SIZE];
int value = 0;

////////////////////////////////////

void setup_wifi() {

  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
   String msg = "";
  for (int i = 0; i < length; i++) {
    msg +=(char)payload[i];
  }
  Serial.println(msg);


  if (msg == "power_on") {
  char turn_on[] = "8166817E";   
  unsigned long turn_on_v = strtoul(turn_on, NULL, 16); 
  irsend.sendNEC(turn_on_v);
  Serial.println("send_ok");
  }

  
  if (msg == "power_off") {
  char turn_on[] = "8166817E";   
  unsigned long turn_on_v = strtoul(turn_on, NULL, 16); 
  irsend.sendNEC(turn_on_v);
  Serial.println("send_ok");
  }

  if (msg == "cold") {
  char turn_on[] = "816619E6";   
  unsigned long turn_on_v = strtoul(turn_on, NULL, 16); 
  irsend.sendNEC(turn_on_v);
  Serial.println("send_ok");
  }

    if (msg == "hot") {
  char turn_on[] = "8166E916";   
  unsigned long turn_on_v = strtoul(turn_on, NULL, 16); 
  irsend.sendNEC(turn_on_v);
  Serial.println("send_ok");
  }
  
   if (msg == "reserve_on") {
  char turn_on[] = "8166F906";   
  unsigned long turn_on_v = strtoul(turn_on, NULL, 16); 
  irsend.sendNEC(turn_on_v);
  Serial.println("send_ok");
  }

     if (msg == "reserve_off") {
  char turn_on[] = "8166F906";   
  unsigned long turn_on_v = strtoul(turn_on, NULL, 16); 
  irsend.sendNEC(turn_on_v);
  Serial.println("send_ok");
  }

  
     if (msg == "dry_on") {
  char turn_on[] = "8166D926";   
  unsigned long turn_on_v = strtoul(turn_on, NULL, 16); 
  irsend.sendNEC(turn_on_v);
  Serial.println("send_ok");
  }
  
       if (msg == "dry_off") {
  char turn_on[] = "8166D926";   
  unsigned long turn_on_v = strtoul(turn_on, NULL, 16); 
  irsend.sendNEC(turn_on_v);
  Serial.println("send_ok");
  }
  

}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str())) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("outTopic", "hello world");
      // ... and resubscribe
      client.subscribe(mqtt_topic);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void setup() {
    irsend.begin();
    dht.setup(D5, DHTesp::DHT22); // D5
// OTA SETUP ////////////////////////////////////////
    Serial.println("Booting");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }

  // Port defaults to 8266
  ArduinoOTA.setPort(8266);

  // Hostname defaults to esp8266-[ChipID]
  ArduinoOTA.setHostname("myesp8266");

  ArduinoOTA.onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH) {
      type = "sketch";
    } else { // U_FS
      type = "filesystem";
    }

    // NOTE: if updating FS this would be the place to unmount FS using FS.end()
    Serial.println("Start updating " + type);
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) {
      Serial.println("Auth Failed");
    } else if (error == OTA_BEGIN_ERROR) {
      Serial.println("Begin Failed");
    } else if (error == OTA_CONNECT_ERROR) {
      Serial.println("Connect Failed");
    } else if (error == OTA_RECEIVE_ERROR) {
      Serial.println("Receive Failed");
    } else if (error == OTA_END_ERROR) {
      Serial.println("End Failed");
    }
  });
  ArduinoOTA.begin();
  Serial.println("Ready");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  

  //SERIAL BAUDRATE 

  /*
  pinMode(BUILTIN_LED, OUTPUT);     // Initialize the BUILTIN_LED pin as an output
  #if ESP8266
  Serial.begin(115200, SERIAL_8N1, SERIAL_TX_ONLY);
#else  // ESP8266
  Serial.begin(115200, SERIAL_8N1);
#endif  // ESP8266
*/
  Serial.begin(115200);

// MQTT SETUP 

  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);

}

void Get_th(){
    
  delay(dht.getMinimumSamplingPeriod());
  float humidity = dht.getHumidity();
  float temperature = dht.getTemperature();
  packet = "온도 : " + String(temperature) + "*C \n" + "습도 : " + String(humidity) + "%" ; 
  
  client.publish("sensor/th",  (char*) packet.c_str());
  client.publish("sensor/t",   (char*) String(temperature).c_str());
  client.publish("sensor/h",  (char*) String(humidity).c_str());
}

void loop() {
  ArduinoOTA.handle();
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

    if ( millis() - lastSend > 1000 ) { // Update and send only after 1 seconds
    Get_th();
    lastSend = millis();
  }

  }

동작하기 위해 필요한 라이브러리는 DHT sensor library for ESPx(아두이노 IDE 라이브러리 매니저에서 설치가능)

PubSubClient가 필요합니다.

 

2초마다 온습도값을 측정해서 sensor/th 토픽에 전송합니다.

이외에 지정된 메세지가 들어오면 ir led 를 작동시켜서 에어컨을 제어합니다.

 

소스가 OTA와 합쳐져 있기 때문에 와이파이로 스케치에서 무선 업로드가 가능합니다.

 

작동영상

 

 

생각한대로 잘 작동합니다.. 적외선 신호가 약해서 좀 애를 먹었습니다.

다만 DHT22 를 센싱하면서 2초 딜레이가 있는데 이게 MQTT 통신에 영향을 주는거 같습니다.

누르면 조금 늦게 반응을 합니다.

구조상 loop에서 MQTT를 유지하면서 이벤트를 받아오는거같은데 

dht 센싱도 같이 하기 때문에 동기적으로 처리가 이루어지고 있는듯 합니다.

Blynk 사용시 timer 를 이용해 해결했는데 아두이노에 thread 사용법이 있는지 찾아봐야겠네요.

 

그리고 좀 큰 문제점이 에어컨이 하도 오래되서 ON/OFF 신호가 동일합니다

요즘 에어컨은 ON/OFF신호를 구분해서 보내는데 이건 같은신호로 보내서 토글시키는 방식입니다

구현 할땐 간단했는데 이게 mqtt로 제어를 하다가 외부에서 껐다 키면 전원상태 동기화가 안되는 문제가 있습니다. 제습, 1시간 예약도 마찬가지구요. 조도센서를 쓰거나 LED가 들어오는 부분을 납땜해서 선을 따고

digitalRead로 읽으면 될듯합니다.


일단 끄고 켜는건 성공했으니 문제점들은 나중에 보완하기로.. ^^

혹시 전원 동기화에 대해 더 좋은 아이디어 있으면 댓글로 의견좀 부탁합니다.

 

어쨌든 성공~

COMMENT WRITE