스마트 홈/esp32

오래간 만에 돌아온 하우스 개폐기... (블라인드 적용가능)

다자바무커 2021. 12. 24. 11:26
반응형

이번에 방학을 맞이 하고 마침 아버지가 시간이 생기셔서 미루고 미루던 비닐 하우스 개폐기를 달러 가게되었다..

결론은... 실패지만!!! 이제 완전히 수정했다고 생각하고 믿는다 음! 음!

 

처음에 설치 하기 전에는 나의 코드는 거의 완벽이라 숫자만 조율하면 된다고 생각했다.

그랬다...

근데 설치하다보니 정지를 할 일이 생기는데 esp32의 코드는 delay로 가동했던거라

delay순간에는 mqtt subscribe가 되지 않았다.

그래서 멈출 방법이 없었다.

그렇다고 그 자리에서 수정하자니 코드를 많이 수정해야할 것 같아서 그만두었다.

그래도 해보고자 어느정도 테스트용으로 수정을 하고 업로드 하는 도중에 

포트가 잡히지 않아 실수로 포트를 usbserial로 했더니 나의 친구 맥이 방전되어서 켜지지 않는 모습을 보여 주었다...

분명 배터리 적어도 60% 이상이었는데... 배터리 갈아라고 알림은 오지만....

그래서 어쩔 수 없이 기계적인 부분만 아버지 주도로 실행해다.

나는 결국 박스만 달았다...

비닐 하우스가 따뜻하다보니 모터 드라이브가 타버리지 않을까 하는 걱정은 있지만 

나의 코드 문제가 더 심각하여 걱정은 접어두었고 아버지의 약만이 남았다.

전체적인 구조나 그런건 설치 완료후 말하도록 하겠다.

 

 

결국 나는 실패하고 집으로 돌아와 폭풍 코딩을 했고 

방금!!!! 막 수정 완료했다.

거의 많은 부분이 달라졌다.

전체 코드


 

//하우스 개폐기

#include <WiFi.h>
#include <PubSubClient.h>
#include <OneWire.h>
#include <DallasTemperature.h>

#define ONE_WIRE_BUS 4
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

const char *ssid =  "와이파이 이름";   
const char *password =  "와이파이 비밀번호"; 

const char* ID = "걔폐기";
const char* TOPIC = "house/right/motor/pos"; //MQTT 개폐기 Publish 토픽
const char* INTOPIC = "house/right/motor/set"; //MQTT 개폐기 Subscribe 토픽
const char* TEMPTOPIC = "house/temperature"; //하우스 온도 달라스 센서 Publish 토픽
const char* mqttUser = "MQTT 이름";
const char* mqttPassword = "MQTT 비밀번호";
const char* broker = "Home Assistant 서버 외부 아이피";

WiFiClient wclient;
PubSubClient client(wclient); 
char messages[50];
char tempers[50];
int SW_state = 0;
int Motor_open1 = 12; //모터 드라이브에 연결 가능한 보터가 2개라 있을 뿐 지금은 사용 안함
int Motor_close1 = 13; // 마찬가지
int Motor_open2 = 26; //모터회전 방향에 따라 열릴 때 사용
int Motor_close2 = 27; //모터회전 방향에 따라 닫힐 때 사용
unsigned long setTime = 0; //delay를 사용하지 않고 millis() 사용
unsigned long preTime = 0;  //delay를 사용하지 않고 millis() 사용
float tem = 0; 
float tem1 = 0;  
char val[20];
unsigned long time1 = 0;  //millis 초기화가 오류가 나서 넣음
unsigned long time2 = 0;  //millis 초기화가 오류가 나서 넣음
unsigned long rollover = 0; //millis 초기화가 오류가 나서 넣음
String msg = ""; 

// Connect to WiFi network 이건 배낌 와이파이 연결해줌
void setup_wifi() {
  Serial.print("\nConnecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password); 
  while (WiFi.status() != WL_CONNECTED) { 
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}
//코드 반복 방지용 publish 코드
void sendMessage() {
  time1 = setTime - preTime;
  preTime += time1;
  Serial.println(time1);
  snprintf(messages, 75, "%ld", preTime/100);
  client.publish(TOPIC, messages);
}

// subscribe 코드
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Received messages: ");
  Serial.println(INTOPIC);
  msg = "";
  for(int i=0; i<length; i++){
    //Serial.println((char)payload[i]);
    msg += (char)payload[i];
  }
  Serial.println(msg);
  rollover = millis();
}


// Reconnect to client 요것또 와이파이 배낌
void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect(ID, mqttUser, mqttPassword)) {
      Serial.println("connected");
      Serial.print("Publishing to: ");
      Serial.println(TOPIC);
      Serial.println('\n');
      client.subscribe(INTOPIC);

    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println("\n try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200); 
  pinMode(2, INPUT);
  pinMode(4, INPUT);
  pinMode(Motor_open1, OUTPUT);
  pinMode(Motor_close1, OUTPUT);
  pinMode(Motor_open2, OUTPUT);
  pinMode(Motor_close2, OUTPUT);  
  delay(100);
  setup_wifi(); // Connect to network
  client.setServer(broker, 1883);
  client.setCallback(callback);
  sensors.begin();
}

// 원래 Subscribe 코드 있던 부분 옮겨옴, 온도 센서 Publish
void loop() {
  if (!client.connected()){
    reconnect();
  }
  client.loop();
  if (msg == "STOP"){
    digitalWrite(Motor_open2, LOW);
    digitalWrite(Motor_close2, LOW);
    if (setTime > preTime) {
      preTime += time1;
    } else {
      preTime -= time1;
    }
    Serial.println(time1);
    snprintf(messages, 75, "%ld", preTime/100);
    client.publish(TOPIC, messages);
    msg = "dont";
  } else if (msg == "dont") {
    
  }else{
    setTime = 300 * msg.toInt();
    time2 = millis();
    time1 = time2 - rollover;
    if (setTime > preTime) {
      digitalWrite(Motor_open2, HIGH);
      if (time1 >= (setTime - preTime)){
        Serial.println(time1);
        digitalWrite(Motor_open2, LOW);
        sendMessage();
      }
    } else if (setTime < preTime) {
      digitalWrite(Motor_close2, HIGH);
      if (time1 >= (preTime - setTime)){
        Serial.println(time1);
        digitalWrite(Motor_close2, LOW);
        sendMessage();
      }
    }
  }
  sensors.requestTemperatures();
  tem = sensors.getTempCByIndex(0);
  if (tem != tem1) {
    if (tem < -55) {
      tem = tem1;
    }
    tem1 = tem;
    Serial.println(tempers);
    dtostrf(tem, 5, 1, val);
    snprintf(tempers, 75, "%f", val);
    client.publish(TEMPTOPIC, tempers);
  }
}

 

부분 코드


#define ONE_WIRE_BUS 4
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

 다랄스 센서로 부터 온도를 읽어 오기 위한 코드라 그대로 예제에 있는 것을 가져왔다.

const char *ssid =  "와이파이 이름";   
const char *password =  "와이파이 비밀번호"; 

const char* ID = "걔폐기";
const char* TOPIC = "house/right/motor/pos"; //MQTT 개폐기 Publish 토픽
const char* INTOPIC = "house/right/motor/set"; //MQTT 개폐기 Subscribe 토픽
const char* TEMPTOPIC = "house/temperature"; //하우스 온도 달라스 센서 Publish 토픽
const char* mqttUser = "MQTT 이름";
const char* mqttPassword = "MQTT 비밀번호";
const char* broker = "Home Assistant 서버 외부 아이피";

Esp 32가 와이파이 모듈이고 와이파이에 연결하기 위해 이름로 비빌번호를 입력한다.

밑 단락은 Home Assistant MQTT를 사용하기 위한 코드이다.

나는 Home Assistant를 이용하여 나의 아이폰과 아이패드를 통해 조절하는데

아두이노로 조절하기 위해서는 사용할 수 있고 생각보다 간단한 방법이 MQTT 였다.

간단하게 값을 받고 주고가 눈에 띄게 간단했다.

브로커는 공유기 외부아이피 주소를 넣으면 된다. 나머지는 HomeAssistant Mqtt가 다 알아서 해준다.

처음에 Home Assistant  유저 이름과 비빌번호만 잘 만들어 두면 된다.

ID는 되게 중요한데 여러게를 만들다 보면 이름이 같을 경우 서로 혼선이 일어나 알지 못할 오류가 많이 생긴다.

WiFiClient wclient;
PubSubClient client(wclient); 
char messages[50];
char tempers[50];
int SW_state = 0;
int Motor_open1 = 12; //모터 드라이브에 연결 가능한 보터가 2개라 있을 뿐 지금은 사용 안함
int Motor_close1 = 13; // 마찬가지
int Motor_open2 = 26; //모터회전 방향에 따라 열릴 때 사용
int Motor_close2 = 27; //모터회전 방향에 따라 닫힐 때 사용
unsigned long setTime = 0; //delay를 사용하지 않고 millis() 사용
unsigned long preTime = 0;  //delay를 사용하지 않고 millis() 사용
float tem = 0; 
float tem1 = 0;  
char val[20];
unsigned long time1 = 0;  //millis 초기화가 오류가 나서 넣음
unsigned long time2 = 0;  //millis 초기화가 오류가 나서 넣음
unsigned long rollover = 0; //millis 초기화가 오류가 나서 넣음
String msg = "";

대부분의 변수를 전역변수로 설정해서 여기저기서 이용가능하게 했다.

// subscribe 코드
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Received messages: ");
  Serial.println(INTOPIC);
  msg = "";
  for(int i=0; i<length; i++){
    //Serial.println((char)payload[i]);
    msg += (char)payload[i];
  }
  Serial.println(msg);
  rollover = millis();
}

mqtt가 값이 하나씩 날라오기 때문에 반복문으로 그 하나 하나의 값을 더해줘서 하나의 문자로 만들어 주어야 우리가 사용기 가능하다.

rollover 변수는 나중에 한번더 나올 때 같이 설명하겠다.

void setup() {
  Serial.begin(115200); 
  pinMode(2, INPUT);
  pinMode(4, INPUT);
  pinMode(Motor_open1, OUTPUT);
  pinMode(Motor_close1, OUTPUT);
  pinMode(Motor_open2, OUTPUT);
  pinMode(Motor_close2, OUTPUT);  
  delay(100);
  setup_wifi(); // Connect to network
  client.setServer(broker, 1883);
  client.setCallback(callback);
  sensors.begin();
}

어떤 핀을 사용할지 알려주어야 해서 선언하고 와이파이와 Mqtt와의 통신을 시작하고 달라스 센서도 사용을 선언해준다.

 

루프는 나누어 설명하겠다.

  if (!client.connected()){
    reconnect();
  }

와이파이 끊어질 경우 다시 연결

}else{
    setTime = 300 * msg.toInt();
    time2 = millis();
    time1 = time2 - rollover;
    if (setTime > preTime) {
      digitalWrite(Motor_open2, HIGH);
      if (time1 >= (setTime - preTime)){
        Serial.println(time1);
        digitalWrite(Motor_open2, LOW);
        sendMessage();
      }
    } else if (setTime < preTime) {
      digitalWrite(Motor_close2, HIGH);
      if (time1 >= (preTime - setTime)){
        Serial.println(time1);
        digitalWrite(Motor_close2, LOW);
        sendMessage();
      }
    }
  }

mqtt에서는 값이 1~100의 정수와 "STOP" 이렇게 두 종류로 날라온다.

100은 100% 열어라, 0은 0%열어라니까 닫으라는 거다. 나는 여기서 전기를 공급하는 시간을 기준으로 얼마만큼 열릴지를 조절하도록했다.

setTime은 msg를 문자이기 때문에 정수로 변한하고 날라오는 값이 0~100이기 때문에 값을 곱하여 시간을 조절한다.

300을 곱하면 전기 공급 시간이 0~30초다.

time2 변수는 millis로 하여 아두이노의 가동 시간을 가져온다.

time1은 가동 중인 시간에서 subscribe한 시간(rollover)를 빼주어 모터가 얼마나 돌아가는지 계산해주는데

사실 timer0_millis 변수를 조절하여 원래 시간을 초기화하는 것이 가능하지만 esp32에서 오류가 나서 초기화가 불가능하다.

그래서 subscribe  한 후 시간을 구하기 위해 이런 방법을 사용했다.

setTime은 열마만큼 열지 입력받은 값이고 preTime은 원래 열려있던 만큼이 얼마인지 알려준다.

만약 원래 30% 열려 있었는데 70%로 더 열어야 한다면 preTime(30%) 보다 setTime(70%)가 40% 더 큼으로 40% 더 열리면 된다.

이제 delay를 쓰지 않으니 그 시간 만큼의 모터 가동을 해주기 위해 subscribe 부터 시간을 loop로 계속 구하다가 40%보다 크거나 같아지는 순간 모터 가동을 중단하고 서버에 그만큼 열었다고 알림을 준다.

//코드 반복 방지용 publish 코드
void sendMessage() {
  time1 = setTime - preTime;
  preTime += time1;
  Serial.println(time1);
  snprintf(messages, 75, "%ld", preTime/100);
  client.publish(TOPIC, messages);
}

이 코드가 반복이 좀 있어서 하나의 함수로 설정했다.

time1 변수가 계속 커지면 모터 가동 시간보다 더 많이 열렸다고 서버에 알리 수도 있어서 더 열린 정도의 값을 고정한 뒤 preTime에 더하거나 빼서 열린 정도를 넣어주고 서버에 열린 정도를 알려준다.

if (msg == "STOP"){
    digitalWrite(Motor_open2, LOW);
    digitalWrite(Motor_close2, LOW);
    if (setTime > preTime) {
      preTime += time1;
    } else {
      preTime -= time1;
    }
    Serial.println(time1);
    snprintf(messages, 75, "%ld", preTime/100);
    client.publish(TOPIC, messages);
    msg = "dont";
  } else if (msg == "dont") {
    
  }

열거나 닫는 도중에 멈춰야 할 경우가 생길 경우 이때문에 delay가 아닌 millis를 사용하여 중간 중간에 stop을 받아오도록 했는데

stop 메세지가 오면 모타 가동을 미리 중단하고 얼마만큼 움직였는지를 서버에 알린다.

이후 메세지를 바꾸지 않으면 루프 때문에 계속 코드가 실행 되어 모터는 움직이지도 않았는데 반복 때문에 닫히거나 열렸다고 서버에 잘못된 정보를 넘겨준다. 그래서 메세지를 변형하여 dont로 바꾸고 메세지가 dont면 아무것도 하지 않도록 했다.

sensors.requestTemperatures();
  tem = sensors.getTempCByIndex(0);
  if (tem != tem1) {
    if (tem < -55) {
      tem = tem1;
    }
    tem1 = tem;
    Serial.println(tempers);
    dtostrf(tem, 5, 1, val);
    snprintf(tempers, 75, "%f", val);
    client.publish(TEMPTOPIC, tempers);
  }

온도 값을 읽어서 서버로 전달한다.


온도 센서나 다른 코드들은 전 글에서 설명했다. homeassistnat 코드도 있다.

블라인드도 개폐기랑 같은거라 코드는 동일하고 수치만 조절하면 된다.

시리도 가능함요 ㅋ

내가 농대생이라 취미로 하는거라 오류도 있고 복잡한 면도 있을 텐데 

혹시나 모르시는 부분있으시면 댓글 남겨주세요

취미로 공학하는 농대생 뭔가 간지나네 ㅋㅋㅋㅋㅋㅋㅋㅋㅋ

 

이제 다시 가서 설치해보는 일만 남았다!!!

 

반응형