아두이노 센서 데이터 대시보드 구현 (3/7) – Arduino

Published Sep 8, 2024 | Updated Sep 17, 2024 | 0 comments

아두이노 온습도 센서에서 수집된 데이터로 대시보드를 구성하는 과정을 살펴보겠습니다
총 7개의 포스트로 이루어지며 아래 글목록을 보시면 확인이 가능합니다.

하드웨어 정보

개인적으로 사용할 용도라 최소한의 가성비를 고려해서 선택하게 되었습니다.

구매 정보

보드를 먼저 선택한 상태에서 나머지는 동일한 스토어에서 적당한 상품으로 구매하게 되었습니다.
보드 구매 페이지 : https://smartstore.naver.com/3dfuns/products/6122841077

ESP8266 WeMos D1 Mini (C 타입)

블루투스 없이 WIFI 만 사용 가능한 소형 보드 중에 선택하였습니다.

미니 브레드 보드

크기가 굳이 클 필요가 없어 소형으로 사용했습니다.

온습도 센서 AHT10

온도와 습도를 모두 측정할 수 있는 센서 중에 선택하였습니다.

LED + 저항

며칠 사용하다 보니 제대로 동작하는지 알고 싶어 지는 상황이 있어
문제가 되는 경우 표시하기 위한 용도로 붉은색 LED 를 선택했습니다.

5V 2A 어댑터 + C 타입 케이블

보드가 입력 전압이 5V 라서 집에서 굴러 다니던 구형 스마트폰 어댑터를 사용하게 되었습니다.
케이블은 짧지만 연결상태 표시가 되는 것으로 구매하였습니다.

아두이노 코드

1분 단위로 온도와 습도 데이터를 측정해서 MQTT broker 로 전송합니다.
아두이노 개발이 처음이다 보니 아직은 불필요한 로그 및 기능도 포함하고 있습니다.
RoomNum 의 경우 방 위치를 구분하는 용도로 사용합니다.
TopicPrefix 과 같이 수정해서 방 위치를 구분합니다.
중괄호({{}}) 부분은 수정해서 사용해야 합니다.

// 온습도 센서(AHT10), LED 조합
// 데이터를 Mqtt 로 전송하고
// WIFI 또는 온습도 센서의 상태 및 측정된 온습도의 값에 따라 문제가 있는 경우 LED ON

// 방 번호에 따라 같이 수정 필요
const char* RoomNum = "R1";
const char* TopicPrefix = "DRS/R1/";

const char* TopicSep = "/";

#include <TimeLib.h>
#include <TimeAlarms.h>

bool logMqtt = true; // 로그 유형(콘솔 or Mqtt)

#pragma region Wifi & MQTT

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

const char* ssid = "{{SSID}}";
const char* password = "{{PWD}}";
const char* mqtt_server = "{{SERVER}}";
const unsigned int mqtt_port = {{PORT}};
const char* mqtt_user = "{{USER}}";
const char* mqtt_pwd = "{{PWD}}";


#pragma region Command
const char* TopicCommand = "CMD";
//const char* TopicCmdReset = "RST";
const char* TopicCmdAng = "ANG"; // 모터 각도
const char* TopicCmdLog = "LOG"; // LOG
#pragma endregion comment

const char* TopicLog = "LOG"; // For Log
const char* TopicWifi = "WIFI";
const char* TopicLed = "LED";
const char* TopicFreeHeap = "HEAP";
const char* TopicAht = "AHT";
const char* TopicTemp = "TEMP";
const char* TopicHumidity = "HUM";

WiFiClient espClient;
PubSubClient client(espClient);

const unsigned long delayWifi = 1000 * 5; // 5초
unsigned long prevWifi = 0;

void connectMqtt(bool checkDelay = true) {
    if (checkDelay) {
        if (millis() - prevWifi < delayWifi) {
            return;
        }
    }
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = String("AHT") + RoomNum + String(random(0xffff), HEX);

    // Attempt to connect
    if (client.connect(clientId.c_str(), mqtt_user, mqtt_pwd)) {
        Serial.println("connected");
        // ... and resubscribe
        String topic = String(TopicPrefix) + TopicCommand + TopicSep + "#";
        client.subscribe(topic.c_str());
        String wifiTopic = String(TopicPrefix) + TopicWifi;
        client.publish(wifiTopic.c_str(), String(HIGH).c_str(), true);
        prevWifi = millis();
    }
    else {
        Serial.print("failed, rc=");
        Serial.print(client.state());
    }
}

void setup_wifi() {
    // We start by connecting to a WiFi network
    Serial.println("");
    Serial.print("Connecting to ");
    Serial.println(ssid);

    WiFi.begin(ssid, password);

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

    randomSeed(micros());

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

    client.setServer(mqtt_server, mqtt_port);
    client.setCallback(mqttCallback);

    connectMqtt(false);
}

int callbackAngle = -1;
const int maxTopicParts = 10; // 배열의 최대 크기
char* topicParts[maxTopicParts];  // 문자열을 저장할 배열
void mqttCallback(char* topic, byte* payload, unsigned int length) {
    Serial.print("Message arrived [");
    Serial.print(topic);
    Serial.print("] ");
    char payloadStr[length + 1];
    for (int i = 0; i < length; i++) {
        payloadStr[i] = (char)payload[i];
    }
    payloadStr[length] = '\0'; // Null-terminate the char array
    Serial.println(payloadStr);

    // Topic 을 '/' 단위로 문자열 구분하여 배열에 저장
    char topicStr[strlen(topic) + 1];
    strcpy(topicStr, topic);

    int startIndex = 0;
    int endIndex = 0;
    int partIndex = 0;
    while (topicStr[endIndex] != '\0' && partIndex < maxTopicParts) {
        if (topicStr[endIndex] == '/') {
            topicStr[endIndex] = '\0';
            topicParts[partIndex++] = &topicStr[startIndex];
            startIndex = endIndex + 1;
        }
        endIndex++;
    }
    if (partIndex < maxTopicParts) {
        topicParts[partIndex] = &topicStr[startIndex];
    }

    if (strcmp(topicParts[2], TopicCmdAng) == 0) {
        int ang = abs(atoi(payloadStr));
        if (ang > 180) ang = 180;
        callbackAngle = ang;
    }
    else if (strcmp(topicParts[2], TopicCmdLog) == 0) {
        logMqtt = !logMqtt;
    }
}

void CustomLog(const char* msg) {
    if (logMqtt) {
        if (client.connected()) {
            client.publish((String(TopicPrefix) + TopicLog).c_str(), msg);
        }
    }
    Serial.println(msg);
}

#pragma endregion comment

#pragma region 온/습도

#include <Adafruit_AHTX0.h>                              // AHTX0 라이브러리 포함

Adafruit_AHTX0 aht;                                      // aht 객체 생성

void setup_AHT() {
    int tryCount = 0;
    Serial.print("Try AHT Connection .");
    bool isAhtOn = false;
    isAhtOn = aht.begin();
    while (!isAhtOn) {
        Serial.print("°");
        delay(300);
        isAhtOn = aht.getStatus() != 0xFF;
        if (isAhtOn)
            continue;

        tryCount++;
        if (tryCount > 9) {
            break;
        }
    }
    Serial.println();
    bool isAhtOn = aht.getStatus() != 0xFF;
    Serial.println(isAhtOn ? "온/습도 센서에 연결되었습니다." : "온/습도 센서에 연결되지 않았습니다.");
    if (client.connected()) {
        client.publish((String(TopicPrefix) + TopicAht).c_str(), String(isAhtOn ? HIGH : LOW).c_str(), true);
    }
}

float tempLast = 0;
float humiLast = 0;

void loop_AHT() {
    bool isAhtOn = aht.getStatus() != 0xFF;
    if (!isAhtOn) {
        setup_AHT();
        return;
    }

    sensors_event_t humi, temp;                           // sensors_event_t 타입의 temp와 humidity 변수 선언  
    aht.getEvent(&humi, &temp);                           // 온도, 습도 데이터 갱신

    float tempNow = temp.temperature;
    float humiNow = humi.relative_humidity;

    tempLast = tempNow;
    humiLast = humiNow;

    CustomLog((String("온도 : ") + tempNow + " | 습도 : " + humiNow).c_str());

    if (client.connected()) {
        client.publish((String(TopicPrefix) + TopicTemp).c_str(), String(tempNow).c_str());
        client.publish((String(TopicPrefix) + TopicHumidity).c_str(), String(humiNow).c_str());
    }
}

#pragma endregion comment

#pragma region CheckFreeHeap

void loop_CheckFreeHeap() {
    uint32_t freeHeap = ESP.getFreeHeap();
    Serial.print("Free Heap: ");
    Serial.println(freeHeap);
    if (client.connected()) {
        char freeHeapStr[10];
        itoa(freeHeap, freeHeapStr, 10); // freeHeap 값을 문자열로 변환
        client.publish((String(TopicPrefix) + TopicFreeHeap).c_str(), freeHeapStr);
    }
}

#pragma endregion comment

#pragma region LED

#define LED_PIN 14        // LED PIN
void setup_Led() {
    pinMode(LED_PIN, OUTPUT);
}
bool ledStatus = true;
void loop_Led() {
    const char* errMsg = "";
    // 온습도 센서가 꺼져 있거나 WIFI 가 연결되지 않은 경우 불이 켜짐
    bool status = client.connected();
    if(status) {
      status = aht.getStatus() != 0xFF;
      if(status) {
        // 온도는 0 ~ 45 도 사이
        // 습도는 30 ~ 85
        status = (tempLast > 0 && tempLast < 45) && (humiLast > 30 && humiLast < 85);
        if(!status) {
          errMsg = "AHT data invalid";
        }
      } else {
        errMsg = "AHT OFF";
      }
    } else {
      errMsg = "MQTT OFF";
    }
    if(ledStatus != status) {
      digitalWrite(LED_PIN, !status ? HIGH : LOW);
      if (client.connected()) {
          client.publish((String(TopicPrefix) + TopicLed).c_str(), String(status ? HIGH : LOW).c_str(), true);
      }
      CustomLog((String(!status ? "LED ON : " : "LED OFF") + errMsg).c_str());
    }
    ledStatus = status;
}
#pragma endregion comment

#pragma region Default

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

    setTime(8, 29, 0, 1, 1, 11); // set time to Saturday 8:29:00am Jan 1 2011

    setup_Led();

    setup_wifi();

    setup_AHT();

    loop_Led();
    Alarm.timerRepeat(1, loop_Led);

    loop_AHT();
    Alarm.timerRepeat(60, loop_AHT);

    loop_CheckFreeHeap();
    Alarm.timerRepeat(60, loop_CheckFreeHeap);
}

void loop() {

    if (!client.connected()) {
        connectMqtt();
    }
    if (client.connected()) {
        client.loop();
    }
    
    loopCheck();

    Alarm.delay(15);
}

const unsigned long delayLoopCheck = 1000 * 60;
unsigned long prevLoopCheck = 0;
void loopCheck() {
    if (millis() - prevLoopCheck < delayLoopCheck) return;

    CustomLog((String("LoopCheck : ") + millis()).c_str());

    prevLoopCheck = millis();
}

#pragma endregion comment

실제 사용 사진

Learn more on this topic

Related Blog Posts

Join in the conversation

Leave a Comment

0 Comments

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

무료 온라인 전광판

전광판

텍스트를 입력하고 텍스트 효과 및 배경효과 를 변경해서 전체화면으로 표시할 수 있는 전광판 용도로 사용하실 수 있습니다. 각종 스포츠 및 공연 관람시 응원 용도로 사용이 가능합니다.

Carousel

여러개의 슬라이드를 추가하여 프레젠테이션 및 이미지 슬라이드 용도로 사용하실 수 있습니다. 브라우저가 포함된 IT 기기로 큰 모니터에 연결하여 매장 내 공지사항 및 메뉴소개를 이미지로 표시할 수 있습니다.

Pin It on Pinterest

Shares
Share This

Share This

Share this post with your friends!

Shares