YOGYUI

힐스테이트 광교산::싱크대 절수페달 IoT 연동하기 - (3) 본문

홈네트워크(IoT)/힐스테이트 광교산

힐스테이트 광교산::싱크대 절수페달 IoT 연동하기 - (3)

요겨 2022. 9. 18. 21:09
반응형

7. MQTT 연동 프로토타이핑 (EPS32)

WiFi가 내장된 MCU를 뭘 쓸까 10초쯤 고민하다가, 그냥 쓰기 쉬운 ESP-32를 쓰기로 결정!

(집에 devkit이 서너개 굴러다니고 있어서 프로토타이핑 비용이 안든다는게 가장 큰 선정이유 ㅋㅋ)

 

프로토타이핑 schematic은 아래와 같이 구성해봤다

(초창기 설계에 릴레이가 추가됐는데, 추가 이유는 아래 세부항목에 기술)

  • 릴레이(Relay)의 Common에 JATA 본체 커넥터와 연결하고, NC(Normal Close)에 풋스위치 케이블 연결
    - 릴레이가 작동중이지 않을 때 기존과 동일하게 JATA 본체와 풋스위치가 연결되어 있는 효과
    - MCU가 작동중이지 않을 때 (전원 비인가 등)도 절수페달은 정상동작 가능
  • 릴레이 NO(Normal Open)에 ESP 보드의 GND 연결
    - 멀티미터 측정 결과 풋스위치는 동작 시 접점이 붙으면서 신호를 GND에 순간적으로 단락시켜주는 역할
    - 따라서 JATA 본체 커넥터의 신호를 MCU에 의해 릴레이가 동작하면 GND에 단락시켜주면 됨
    - JATA 본체 밸브의 동작 모드를 Toggle시켜주는 역할
  • 릴레이 동작 신호는 ESP-32의 GPIO 2번에 연결 (출력으로 사용)
  • 유량계 YF-B2의 펄스 신호는 ESP-32의 GPIO 4번에 연결 (입력, 인터럽트로 사용)
  • ESP-32 Devkit 보드에서는 DC +5V 핀이 없기 때문에 USB 커넥터에서 +5V를 따와서 연결
    - 릴레이, YF-B2 모두 +5V 이상에서 동작하는 모듈을 사용함

뭔가 간단한 회로에 비해 부연설명이 길다.. ㅋㅋ

간단한 회로에 그렇지 못한 실물 사진... ㅋㅋㅋ

빨리 테스트끝내고 PCB 만들어버리고 싶은 생각뿐!

 

프로토타입 코드은 Arduino IDE로 간단하게 짜버렸다

(ESP-IDF나 FreeRTOS로 구현하는건 너무 번거롭다.. 나중에 OTA나 블루투스 프로비저닝까지 도입하게 되면 정식으로 짜보던가 해야지~)

MQTT 연동을 위해 PubSubClient 라이브러리랑 json 파싱을 위해 ArduinoJson 라이브러리를 사용한게 주요사항

#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>

const int PIN_RELAY_SIG = 2;   // 풋스위치 릴레이 signal
const int PIN_SENSOR_SIG = 4;  // 유량계 펄스 시그널

const char* WIFI_SSID = "와이파이 SSID";
const char* WIFI_PW = "와이파이 비밀번호";
WiFiClient wifi_client;

const char* MQTT_BROKER_HOST = "MQTT 브로커(mosquitto 등) 호스트 주소";
const int MQTT_BROKER_PORT = 1883;
const char* MQTT_AUTH_ID = "MQTT 브로커 사용자 ID";
const char* MQTT_AUTH_PW = "MQTT 브로커 사용자 비밀번호";
char publish_msg[256];
StaticJsonDocument<256> json_doc;
PubSubClient mqtt_client(wifi_client);

volatile int flow_pulse_cnt = 0;
int flow_state = 0; // 0 = no flow, 1 = flow
int flow_state_prev = 0;
int flow_rate = 0;
unsigned long tick_current;
unsigned long clooptime;
unsigned long tick_user_cmd = 0;

void toggleRelaySignal() {
  digitalWrite(PIN_RELAY_SIG, HIGH);
  delay(100);
  digitalWrite(PIN_RELAY_SIG, LOW);
}

void isr_flow_sensor() {
  flow_pulse_cnt++;
}

void establish_mqtt_connection() {
  if (mqtt_client.connected())
    return;
  
  while (!mqtt_client.connected()) {
    Serial.println("Try to connect MQTT broker");
    if (mqtt_client.connect("ESP_KITCHEN_SINK", MQTT_AUTH_ID, MQTT_AUTH_PW)) {
      Serial.println("connected");
      mqtt_client.subscribe("home/hillstate/sinkvalve/command");
    } else {
      Serial.println("failed, rc=");
      Serial.print(mqtt_client.state());
      delay(2000);
    }
  }
  
  clooptime = millis();
}

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

  if (!strcmp(topic, "home/hillstate/sinkvalve/command")) {
    StaticJsonDocument<64> doc;
    DeserializationError error = deserializeJson(doc, payload);
    if (error) {
      Serial.print("deserializeJson() failed: ");
      Serial.println(error.f_str());
    }

    if (doc.containsKey("state")) {
      toggleRelaySignal();
      int target_state = doc["state"];
      if (target_state == 0) {  // 홈네트워크 플랫폼에 의한 끄기 명령
        tick_user_cmd = millis();
      }
    }
  }
}

void publish_flow_state() {
  json_doc["state"] = flow_state;
  json_doc["flow_rate"] = flow_rate;
  size_t n = serializeJson(json_doc, publish_msg);
  mqtt_client.publish("home/hillstate/sinkvalve/state", publish_msg, n);
  Serial.print("Published (home/hillstate/sinkvalve/state): ");
  Serial.println(publish_msg);
}

void setup() {
  Serial.begin(115200);
  
  WiFi.begin(WIFI_SSID, WIFI_PW);
  Serial.println("\nWiFi Connecting");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.print("Connected, IP address: ");
  Serial.println(WiFi.localIP());
  Serial.printf("MAC address: %s\n", WiFi.softAPmacAddress().c_str());

  mqtt_client.setServer(MQTT_BROKER_HOST, MQTT_BROKER_PORT);
  mqtt_client.setCallback(mqtt_callback);

  pinMode(PIN_RELAY_SIG, OUTPUT);
  pinMode(PIN_SENSOR_SIG, INPUT_PULLUP);
  digitalWrite(PIN_RELAY_SIG, LOW);
  attachInterrupt(digitalPinToInterrupt(PIN_SENSOR_SIG), isr_flow_sensor, RISING);
  sei();

  clooptime = millis();
}

void loop() {
  establish_mqtt_connection();
  mqtt_client.loop();

  tick_current = millis();
  if (tick_current >= (clooptime + 1000)) {
    clooptime = millis();
    flow_rate = (flow_pulse_cnt * 60 / 11);
    flow_pulse_cnt = 0;
    if (flow_rate != 0) {
      Serial.printf("flow rate: %d\n", flow_rate);
    }
    flow_state = (int)(flow_rate != 0);
    if (flow_state != flow_state_prev || flow_state) {
      // 외부 명령으로 밸브를 닫았을 때, 잔여 유량으로 인해 state가 1인 경우가 있는데, 
      // 이 경우 IoT 플랫폼 액세서리가 순간적으로 다시 On되어 디스플레이되는 경우가 있으므로
      // 충분히 딜레이를 줘서 publish
      if (tick_current >= (tick_user_cmd + 2000)) {
        publish_flow_state();
      }
    }
    flow_state_prev = flow_state;
  }
}

간단한 테스트를 위해 Homebridge에 액세서리도 하나 추가해주자

{
    "accessory": "mqttthing",
    "type": "valve",
    "valveType": "faucet",
    "name": "Sink Valve (MQTT)",
    "url": "mqtt broker address",
    "username": "mqtt broker id",
    "password": "mqtt broker password%",
    "topics": {
        "setActive": {
            "topic": "home/hillstate/sinkvalve/command",
            "apply": "return JSON.stringify({state: message});"
        },
        "getActive": {
            "topic": "home/hillstate/sinkvalve/state",
            "apply": "return JSON.parse(message).state;"
        },
        "getInUse": {
            "topic": "home/hillstate/sinkvalve/state",
            "apply": "return JSON.parse(message).state;"
        }
    },
    "integerValue": true,
    "onValue": 1,
    "offValue": 0,
    "logMqtt": false
}

아이폰 홈 앱으로 테스트해봤다

(1) 절수페달 풋스위치로 작동 시 상태 업데이트

1초에 한번씩 YF-B2의 펄스 신호 카운트 수를 환산해서 0이 아니면 작동중(state=1)으로 업데이트하게 되는데, MCU 내 loop 레이턴시와 mqtt 커뮤니케이션 레이턴시 및 애플 홈킷 허브(Apple TV)와의 커뮤니케이션 레이턴시 등이 합쳐져서 상태 업데이트가 약간 지연이 있다

그래도 JATA 밸브가 작동중인지 여부를 애플 홈과 연동하는데 성공~

(2) 애플 홈 앱에서 밸브 제어

신기하게도 역방향 통신은 레이턴시가 거의 없다...

결국 MCU 내부 유량 계산 딜레이가 가장 큰 팩터라고 생각하면 될 것 같다

어쨌든 애플 홈 환경에서 싱크대 밸브 제어도 성공! (시리를 통한 음성제어도 당연히 가능!)

 

블루투스 프로비저닝이랑 OTA도 넣으면 상품화도 가능할 정도랄까...

 

아쉬운 점은, 수도꼭지를 잠근 상태라면 JATA 밸브의 작동 유무에 상관없이 유량은 0이기 때문에 애플 홈 앱에서는 OFF 상태로 나타난다는 점?

어차피 수도꼭지를 푸는 순간 유량이 0이 아니게 되어 MQTT로 {state=1} 메시지를 즉시 publish하게 되어 홈앱에도 정상적으로 ON 상태로 반영되기에 큰 문제는 아니지만... "실제 하드웨어가 ON인 상태를 반영하지 못하는 상황이 있다"는 점은 약간 껄끄럽긴 하다 (찝찝해도 해결 방법은 딱히 없는게 함정 ㅋㅋ)

 

원래 목표였던 "로봇청소기가 켰을 때 일정 시간 후 자동으로 꺼지게 하는 기능"은 워낙 간단한 로직이라 지금 단계에서는 추가하지 않고, 하드웨어가 확정되면 그 때 추가하도록 하자 (지금 코드는 깃허브에 올리지도 않을 생각)


이제 프로토타이핑도 모두 끝났으니, 정식으로 하드웨어를 하나 만들어보자 (PCB artwork)

BOM은 대충 이정도면 되지 않을까 싶다

  • ESP32-WROOM-32 (이건 SoC가 집에 5개 정도 있어서 추가구매할 필요는 없을 듯)
  • CP2102 (USB-to-UART, ESP32 플래싱용)
  • USB Connector (Micro-B 타입이면 싸게 먹힐듯)
  • SPDT Relay (어차피 YF-B2 때문에 +5V 전원은 필요하기 때문에 +5V 동작전원인 싼 녀석 찾으면 될듯)
  • JATA 본체 커넥터 및 풋스위치 케이블 호환용 PCB 커넥터 및 케이블 (몰렉스려나...)
  • YF-B2 케이블 호환용 PCB 커넥터 (몰렉스려나2...)

애기 손바닥만한 사이즈로 만들 수 있을 것 같은데, 어차피 아트웍이란게 코딩처럼 금방금방 되는게 아니니 천천히 여유를 갖고 꼼꼼하게 디자인해보도록 하자!

8. PCB 아트웍 (09/21 추가)

지난 주말에 ESP32 devkit으로 프로토타이핑 끝낸 뒤에, PCB 설계를 해봤다 

워낙에 기능이 간단한 회로라 회로도 그리고 아트웍 마무리하는데 5시간정도? 쓴 것 같다

저렴하고 빠른 수급 가능(1주일 이내)한 부품들을 찾느라 하루 이상 소요된게 함정

schematics

  • ESP32 devkit과 마찬가지로 CP2102 USB-to-UART 사용해서 자동 플래싱 모드 진입 기능 추가
  • ESP32 리셋 및 부트모드 셀렉션 택트 스위치 추가 (좀 큰걸로..)
  • 소형 릴레이(AGN2004H) 사용 (DPDT인게 아쉬워서 pole 하나 따로 어디 쓸 수 있을까 고민해봤는데... 없더라 ㅋㅋ)
  • 외부 I2C 디바이스 연동을 위한 핀헤더(5핀) 배치

한가지 아쉬운건, YF-B2 유량계랑 풋스위치 모두 JST의 SM 계열 케이블 커넥터를 쓰고 있는데, 이녀석은 PCB마운팅이 되는 녀석을 찾을 수가 없었다 ㅠㅠ (PC 조립하는 사람에게는 굉장히 익숙한 커넥터)

JST SMR, SMP 계열 커넥터

결국 범용으로 많이 쓰이는 몰렉스 커넥터를 장착한 뒤에 케이블 변환하기로 결정~

(이거 조사한다고 시간 엄청 많이 잡아먹었다...)

 

  • 손으로 납땜하기 쉽게 하기 위해 널찍널찍하게 설계했더니 보드 크기가 생각보다 좀 커졌다 
    (모든 부품을 top면에 실장하는게 목표였기 때문에 보드 사이즈 줄이는데 한계가 있었다)
    "가로 50mm x 세로 43mm"니까 그래도 성인 손바닥만한 크기라 할 수 있겠다
  • 보드 고정을 위해 코너에 3mm hole도 뚫어줬다
  • 결선이 간단해서 Top/Bottom 2층으로 마무리!
  • Voltage Regulator쪽은 저렇게 만들면 안되는데... ㅋㅋㅋ 이정도 회로는 문제없이 동작하지 않을까 싶어서 경험삼아 대충 만들어봤다

2층 보드라 샘플 PCB 5장 제작하는데 2만5천원이면 충분하다 (디바이스마트에서 주문 완료)

 

필요한 부품들도 주문 완료했으니, 이제 1주일 정도는 게임만 하면서 놀아야겠다!

To be continued...

 

[시리즈]

힐스테이트 광교산::싱크대 절수페달 IoT 연동하기 - (1)

힐스테이트 광교산::싱크대 절수페달 IoT 연동하기 - (2)

힐스테이트 광교산::싱크대 절수페달 IoT 연동하기 - (3)

힐스테이트 광교산::싱크대 절수페달 IoT 연동하기 - Final

반응형