어항 수온을 집 밖에서도 관리하기

어항 수온을 집 밖에서도 관리하기

간만에 여유가 있어, 오래전 홀린듯이 사두었던 Arduino Uno WiFi Rev2 보드로 어항 수온 센서를 만들었다. 어항 수온계는 제품이 워낙 많지만, Home Assistant 같은 IoT 플랫폼에서 데이터를 수집할 수 있는 제품은 발견치 못했다. 만들만 하다고 생각했다.

준비물

구상

  • 수온을 프로브로 일정주기(예: 3초)마다 측정한다.
  • 측정된 수온을 디지털 액정에 보여준다.
  • 측정된 수온을 MQTT Broker에 전달하여 Home Assistant에 표현한다.

수온 측정하기

언제 왜 샀는지도 모르는 DS18B20 모듈의 제품 설명에서 풀업 저항을 달아서 쓰라 하기에, 다른 사용례를 보고 4.7kΩ의 풀업 저항을 달았다.

DS18B20

Platform IO에서 OneWire, DallasTemperature 라이브러리를 설치했다. 사실 알리익스프레스에서 구한 중국산이다 보니 라이브러리가 있을까 걱정했는데, 동일한 제품들의 모조품이라 그런지 라이브러리도 그대로 작동!

간단한 소스를 만들어 바로 물에 담가 테스트 시작.

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

#define ONE_WIRE_BUS 2

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

void setup(void)
{
  Serial.begin(9600);
  sensors.begin();
}

void loop(void)
{
  sensors.requestTemperatures();
  float temp = round(sensors.getTempCByIndex(0) * 10) / 10;
  Serial.println(temp);
  delay(3000);
}

스타벅스 속의 프로브…

Probe in Starbucks

DS18B20

얼추 따뜻한 물과 찬물 구분이 되는걸 확인했다. 근데 아무래도 중국산이다보니 측정되는 숫자를 못 믿겠어서 믿을만한 다른 수온계와 비교측정을 했다. 내가 사용할 섭씨 20~35도 구간에서 1도 정도 높게 측정되는 걸 출력값에 반영토록 보정했다.

OLED 액정에 보여주기

데이터는 장치에서 바로 보여주어야 시인성도 높다. 어디서 언제 산지 모른 SSD1306 모듈을 추가로 달고, Adafruit SSD1306, Adafruit_GFX 라이브러리를 추가했다.

연결은 다음과 같이…

DS18B20

추가되는 소스 만 보자.

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET     4
#define ONE_WIRE_BUS   2

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void setup(void)
{
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  Serial.begin(9600);
}

void loop(void)
{
  display.clearDisplay();
  display.setTextSize(1.5);
  display.setTextColor(WHITE);
  display.setCursor(0,28);
  display.println("Temperature: " + String(temp));
  display.display();

  delay(3000);
}

점점 모양을 갖춰간다. 이제 컴퓨터에 연결하여 시리얼 모니터를 볼 필요 없이 OLED 액정을 보면 된다. 3초마다 측정된 온도가 나타난다. 추후 다른 센서 모듈을 추가하면 계기판처럼 쓸 수도 있겠다.

SSD1306

Wi-Fi에 연결하자

WiFiNINA 라이브러리를 추가했다. 펌웨어 업데이트 중 실수로 보드 WiFi 모듈이 벽돌이 되어버렸다. 많이 해맸는데 커맨드라인 툴로 업데이트하면서 해결됐다. Arduino Uno WiFi Rev2의 보드명은 arduino:megaavr:uno2018이다.

./arduino-fwuploader firmware flash -b arduino:megaavr:uno2018 -a COM3

추가될 부분만 살펴보자.

#include <SPI.h>
#include <WiFiNINA.h>

#define WIFI_SSID     "<Wi-Fi 네트워크 SSID 이름>"
#define WIFI_PASSWORD "<Wi-Fi 네트워크 비밀번호>"

WiFiClient        client;

void setup(void)
{
  Serial.begin(9600);

  while (WiFi.status() != WL_CONNECTED) {
      Serial.print(".");
      WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
      delay(3000);
  }

  Serial.println("Connected to the Wi-Fi network");
}

MQTT Broker에 메시지를 보내자

Home Assistant가 인식할 수 있는 MQTT 메시지 포맷을 쉽게 구성해주는 라이브러리가 있을 것 같았다. 바퀴를 다시 발명할 필요 없다. 검색된 라이브러리 중 사용법이 간단한 home-assistant-integration를 선택했다.

Home Assistant 관련 라이브러리

예외처리를 만들고 프로그램 완결성을 높였다. 이름은 거창하게 Reef Sense로 지었다.

#include <OneWire.h>
#include <WiFiNINA.h>
#include <DallasTemperature.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ArduinoHA.h>

#define ONE_WIRE_BUS 2

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET     4
#define ONE_WIRE_BUS   2

#define BROKER_ADDR         IPAddress(192,168,<IP>,<주소>)
#define BROKER_USERNAME     "<MQTT BROKER USERNAME>"
#define BROKER_PASSWORD     "<MQTT BROKER PASSWORD>"

#define WIFI_SSID     "<Wi-Fi 네트워크 SSID 이름>"
#define WIFI_PASSWORD "<Wi-Fi 네트워크 비밀번호>"

#define VERSION "2022.9.25"

unsigned long lastSentAt = millis();
byte mac[] = {0x00, "<MAC>", "<주소>", "<16진수로>", "<넣기>", 0x00};

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
WiFiClient client;
HADevice device(mac, sizeof(mac));
HAMqtt mqtt(client, device);
HASensor temperature_sensor("reefer_temperature");

float measure_temperature(void) {
  sensors.requestTemperatures();
  float temperature = (round(sensors.getTempCByIndex(0) * 10) / 10) - 1;
  return temperature;
}

void print_display(float temperature) {
  display.clearDisplay();
  display.setTextSize(1.5);
  display.setCursor(0, 0);
  display.setTextColor(WHITE);
  
  display.print("Temperature: ");
  display.println(temperature);
  display.println("");

  if (WiFi.status() == WL_CONNECTED) {
    display.println("Wi-Fi:   Connected");
  } else {
    display.println("Wi-Fi:   Disconnected");
  }

  if (mqtt.isConnected()) {
    display.println("MQTT:    Connected");
  } else {
    display.println("MQTT:    Disconnected");
  }

  display.print("Version: ");
  display.println(VERSION);

  display.display();
}

void setup() {
  Serial.begin(9600);
  Serial.println("[0] Serial Initiated...");
  
  sensors.begin();
  temperature_sensor.setName("Reefer Temperature");
  temperature_sensor.setDeviceClass("temperature");
  temperature_sensor.setUnitOfMeasurement("℃");
  temperature_sensor.setIcon("mdi:water-thermometer");
  Serial.println("[1] Sensor Initiated...");

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  Serial.println("[2] Display Initiated...");

  while (WiFi.status() != WL_CONNECTED) {
    Serial.println("[3] Wi-fi Failed to connect...");
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    delay(3000);
  }
  Serial.println("[3] Wi-Fi Connected...");

  device.setName("Arduino Reef Sense");
  device.setSoftwareVersion(VERSION);
  device.setModel("Arduino Uno WiFi Rev2");
  device.setManufacturer("Jihun Roh");

  mqtt.begin(BROKER_ADDR, BROKER_USERNAME, BROKER_PASSWORD);
  Serial.println("[4] MQTT Connected...");
}

void loop() {
  sensors.requestTemperatures();
  float temperature = measure_temperature();
  print_display(temperature);

  if ((millis() - lastSentAt) >= 5000) {
    lastSentAt = millis();
    temperature_sensor.setValue(temperature);
  }
  mqtt.loop();
  delay(1000);
}

마무리

최종 작동 모습이다. 주말 틈틈히 작업한 보람이 LED에 보인다. LED에는 수온 뿐만 아니라 Wi-Fi 연결, MQTT 연결이 잘 되었는지도 표현되게 하였다.

최종 작동모습

이렇게 준비한 이른바 Reef Sense를 어항 밑 섬프항에 놓아두었다. 프로브는 히터나 모터 등 열을 발산하는 곳으로부터 멀리 두어야 신뢰할만한 값을 측정할 수 있다. 나는 본수조로부터 섬프항으로 내려오는 파이프 앞에 프로브를 두었다.

실제 장착모습

Home Assistant에 UI를 달았다. 아직 기본적인 UI지만, 사용성이 높게 수정해나가야 한다.

Home Assistant Lovelace 모습

TBD

  • 빵판에 주렁주렁 달아놓은 전선을 적절한 박스로 포장한다.
  • 집에 PH 센서와 프로브 남는 것이 있는데 이것도 달아야 겠다.
  • 용존산소 등 다른 센서도 알아본다.
  • UI를 더 직관적이고 이쁘게 꾸민다.
  • 겨울철을 앞두고 이 값을 기반으로 수온이 적정범위를 벗어났을 때, 알림을 뜨도록 하고 여유 히터를 작동토록 하는 자동화를 만들 생각이다.