간만에 여유가 있어, 오래전 홀린듯이 사두었던 Arduino Uno WiFi Rev2 보드로 어항 수온 센서를 만들었다. 어항 수온계는 제품이 워낙 많지만, Home Assistant 같은 IoT 플랫폼에서 데이터를 수집할 수 있는 제품은 발견치 못했다. 만들만 하다고 생각했다.
준비물
구상
- 수온을 프로브로 일정주기(예: 3초)마다 측정한다.
- 측정된 수온을 디지털 액정에 보여준다.
- 측정된 수온을 MQTT Broker에 전달하여 Home Assistant에 표현한다.
수온 측정하기
언제 왜 샀는지도 모르는 DS18B20 모듈
의 제품 설명에서 풀업 저항을 달아서 쓰라 하기에, 다른 사용례를 보고 4.7kΩ의 풀업 저항을 달았다.
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);
}
스타벅스 속의 프로브…
얼추 따뜻한 물과 찬물 구분이 되는걸 확인했다. 근데 아무래도 중국산이다보니 측정되는 숫자를 못 믿겠어서 믿을만한 다른 수온계와 비교측정을 했다. 내가 사용할 섭씨 20~35도 구간에서 1도 정도 높게 측정되는 걸 출력값에 반영토록 보정했다.
OLED 액정에 보여주기
데이터는 장치에서 바로 보여주어야 시인성도 높다.
어디서 언제 산지 모른 SSD1306 모듈
을 추가로 달고, Adafruit SSD1306
, Adafruit_GFX
라이브러리를 추가했다.
연결은 다음과 같이…
추가되는 소스 만 보자.
#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초마다 측정된 온도가 나타난다. 추후 다른 센서 모듈을 추가하면 계기판처럼 쓸 수도 있겠다.
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
를 선택했다.
예외처리를 만들고 프로그램 완결성을 높였다.
이름은 거창하게 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지만, 사용성이 높게 수정해나가야 한다.
TBD
- 빵판에 주렁주렁 달아놓은 전선을 적절한 박스로 포장한다.
- 집에 PH 센서와 프로브 남는 것이 있는데 이것도 달아야 겠다.
- 용존산소 등 다른 센서도 알아본다.
- UI를 더 직관적이고 이쁘게 꾸민다.
- 겨울철을 앞두고 이 값을 기반으로 수온이 적정범위를 벗어났을 때, 알림을 뜨도록 하고 여유 히터를 작동토록 하는 자동화를 만들 생각이다.