# Qt) AiotClient - SensorManKYM 시험 정리

mommers·2026년 4월 11일

QT

목록 보기
15/15

이전 글 : Qt) Designer로 UI 구성하기(8) - Tab5SensorDatabase

이전 글까지 AiotClient의 Tab1~Tab7을 구현하는 과정을 정리했습니다.

이번 글에서는 수업에서 배운 내용을 바탕으로 진행한 간단한 시험 내용에 대해 정리해보려합니다.
기존 AiotClient의 소켓 클라이언트(Tab2)와 센서 차트(Tab4), SQLite DB(Tab5) 기능을 새 프로젝트에 Tab1, Tab2, Tab3으로 재구성하는 내용입니다.
시험때 캡쳐해둔 내용도 없고, 교수님 서버가 지금은 꺼져있어서, 내용으로만 정리해보겠습니다.


트러블슈팅 - charts 모듈 추가 오류

과제를 진행하면서 Ubuntu(VirtualBox) 환경에서 문제가 발생했습니다.

새 프로젝트를 생성하고 Tab1, Tab2, Tab3 클래스를 추가한 뒤 .pro 파일에 모듈을 추가하는 과정에서 문제가 생겼습니다.

QT += widgets network charts sql

기존에 작업하던 위젯 파일들은 정상적으로 동작했지만, 새로 추가한 파일들에서 charts 모듈을 추가하면 계속 빌드 오류가 발생했습니다.

교수님께 말씀드렸고, 교수님 컴퓨터에서 새 파일에 charts만 추가해서 정상 동작하는 것을 확인한 후 파일을 보내주셨습니다. 하지만 해당 파일을 제 Ubuntu 환경에서 실행하면 동일한 오류가 발생했습니다.

원인을 파악하지 못한 채로, 교수님 지시에 따라 Windows 환경의 Qt Creator로 과제를 진행했습니다.


1. 프로젝트 구조

AiotClient와 비교하면 아래와 같이 탭 구성이 변경됐습니다.

AiotClientSensorManKYM기능
Tab2SocketClientTab1Socket소켓 연결/수신/송신
Tab4SensorChartTab2Sensor실시간 센서 차트
Tab5SensorDatabaseTab3SqliteSQLite DB 저장/조회

AiotClient에서는 Tab2가 메시지를 파싱한 뒤 mainwidget을 통해 각 탭으로 라우팅하는 구조였습니다.
이번 과제에서는 Tab1이 직접 파싱하여 Tab2, Tab3으로 동시에 emit하는 구조로 단순화했습니다.

수신 메시지 : [HM_CON]SENSOR@71@26.6@51.2
                  ↓
            Tab1에서 파싱
                  ↓
    tab2RecvDataSig + tab3RecvDataSig 동시 emit
           ↓               ↓
       Tab2Sensor      Tab3Sqlite

2. .pro 파일 모듈 설정

QT += widgets network charts sql

3. Tab1Socket

UI 구성

==============================================
|  [서버연결(Checkable)]                     |
|----------------------------------------------|
|         수신 데이터 표시 (읽기전용)          |
|----------------------------------------------|
|  수신ID입력창  |  송신데이터입력창  | [Send] |
|----------------------------------------------|
|                              [수신Clear]     |
==============================================

위젯objectName역할
QPushButtonpPBserverConnect서버 연결/해제 (Checkable)
QTextEditpTErecvData수신 데이터 표시 (읽기전용)
QLineEditpLErecvId수신 ID 입력
QLineEditpLEsendData송신 데이터 입력
QPushButtonpPBsend송신 (초기 비활성화)
QPushButtonpPBrecvClear수신 창 초기화

AiotClient의 Tab2SocketClient와 비교해 수신 창 Clear 버튼(pPBrecvClear)이 추가됐습니다.

tab1socket.h

#ifndef TAB1SOCKET_H
#define TAB1SOCKET_H

#include <QWidget>
#include <QTime>
#include "socketclient.h"

namespace Ui {
class Tab1Socket;
}

class Tab1Socket : public QWidget
{
    Q_OBJECT

public:
    explicit Tab1Socket(QWidget *parent = nullptr);
    ~Tab1Socket();

private slots:
    void updateRecvDataSlot(QString);
    void on_pPBserverConnect_clicked(bool checked);
    void on_pPBsend_clicked();
    void on_pPBrecvClear_clicked();

public slots:
    void socketWriteDataSlot(QString);

private:
    Ui::Tab1Socket *ui;
    SocketClient *pSocketClient;

signals:
    void tab2RecvDataSig(QStringList&);
    void tab3RecvDataSig(QStringList&);
};

#endif // TAB1SOCKET_H

tab1socket.cpp

생성자에서 SocketClient를 생성하고 수신 시그널을 연결합니다.

Tab1Socket::Tab1Socket(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Tab1Socket)
{
    ui->setupUi(this);
    pSocketClient = new SocketClient(this);
    ui->pPBsend->setEnabled(false);
    connect(pSocketClient, SIGNAL(socketRecvDataSig(QString)), this, SLOT(updateRecvDataSlot(QString)));
}

수신 데이터를 파싱하여 메시지 종류가 SENSOR이면 Tab2와 Tab3으로 동시에 emit합니다.

void Tab1Socket::updateRecvDataSlot(QString strRecvData)
{
    strRecvData.chop(1);
    QTime time = QTime::currentTime();
    QString strTime = time.toString() + " " + strRecvData;
    ui->pTErecvData->append(strTime);

    strRecvData.replace("[", "@");
    strRecvData.replace("]", "@");
    QStringList strList = strRecvData.split("@");

    if(strList[2] == "SENSOR")
    {
        emit tab2RecvDataSig(strList);
        emit tab3RecvDataSig(strList);
    }
}

서버 연결/해제, 송신, 수신 Clear 버튼 슬롯입니다.

void Tab1Socket::on_pPBserverConnect_clicked(bool checked)
{
    bool bFlag;
    if(checked)
    {
        pSocketClient->connectToServerSlot(bFlag);
        if(bFlag)
        {
            ui->pPBserverConnect->setText("서버해제");
            ui->pPBsend->setEnabled(true);
        }
        else
            ui->pPBserverConnect->setChecked(false);
    }
    else
    {
        pSocketClient->socketClosedServerSlot();
        ui->pPBserverConnect->setText("서버연결");
        ui->pPBsend->setEnabled(false);
    }
}

void Tab1Socket::on_pPBsend_clicked()
{
    QString strRecvId = ui->pLErecvId->text();
    QString strSendData = ui->pLEsendData->text();
    if(strSendData.isEmpty())
        return;
    if(strRecvId.isEmpty())
        strSendData = "[ALLMSG]" + strSendData;
    else
        strSendData = "[" + strRecvId + "]" + strSendData;
    pSocketClient->socketWriteDataSlot(strSendData);
    ui->pLEsendData->clear();
}

void Tab1Socket::on_pPBrecvClear_clicked()
{
    ui->pTErecvData->clear();
}

4. Tab2Sensor

AiotClient의 Tab4SensorChart와 동일한 구조입니다.
자세한 구현 내용은 아래 글을 참고해 주세요.

Qt) Designer로 UI 구성하기(7) - Tab4SensorChart

슬롯 이름만 tab4RecvDataSlottab2RecvDataSlot으로 변경됐습니다.


5. Tab3Sqlite

AiotClient의 Tab5SensorDatabase와 대부분 동일하며, 삭제 버튼 동작에 차이가 있습니다.

Qt) Designer로 UI 구성하기(8) - Tab5SensorDatabase

Tab5SensorDatabase와의 차이점:

Tab5의 삭제 버튼은 화면(테이블, 차트)만 초기화했지만, Tab3Sqlite에서는 DB에서도 실제로 DELETE 쿼리를 실행합니다.

void Tab3Sqlite::on_pPBDbDelete_clicked()
{
    QString strFromDateTime = ui->pDateTimeEditFrom->dateTime().toString("yyyy/MM/dd hh:mm:ss");
    QString strToDateTime = ui->pDateTimeEditTo->dateTime().toString("yyyy/MM/dd hh:mm:ss");

    QString strQuery = "delete from sensor_tb where '"
                     + strFromDateTime + "' <= date AND date < '"
                     + strToDateTime + "' ";
    QSqlQuery qSqlQuery;
    if(qSqlQuery.exec(strQuery))
        qDebug() << "delete query ok";

    ui->pTBsensor->clearContents();
    illuLine->clear();
    humiLine->clear();
    tempLine->clear();
}

6. mainwidget 연결

mainwidget.h

#include <tab1socket.h>
#include <tab2sensor.h>
#include <tab3sqlite.h>

Tab1Socket *pTab1Socket;
Tab2Sensor *pTab2Sensor;
Tab3Sqlite *pTab3Sqlite;

mainwidget.cpp

Tab1이 파싱과 라우팅을 담당하므로 mainwidget에서의 connect 구조가 단순해졌습니다.

pTab1Socket = new Tab1Socket(ui->pTab1);
ui->pTab1->setLayout(pTab1Socket->layout());

pTab2Sensor = new Tab2Sensor(ui->pTab2);
ui->pTab2->setLayout(pTab2Sensor->layout());

pTab3Sqlite = new Tab3Sqlite(ui->pTab3);
ui->pTab3->setLayout(pTab3Sqlite->layout());

ui->tabWidget->setCurrentIndex(0);

connect(pTab1Socket, SIGNAL(tab2RecvDataSig(QStringList&)),
        pTab2Sensor, SLOT(tab2RecvDataSlot(QStringList&)));
connect(pTab1Socket, SIGNAL(tab3RecvDataSig(QStringList&)),
        pTab3Sqlite, SLOT(tab3RecvDataSlot(QStringList&)));

전체 흐름 요약

새 프로젝트 생성 (SensorManKYM, qmake)
    ↓
.pro에 모듈 추가 (widgets network charts sql)
    ↓
Ubuntu 환경 charts 오류 발생 → Windows Qt Creator로 환경 전환
    ↓
Tab1Socket 구현 (소켓 연결/수신/송신/파싱/라우팅)
    ↓
Tab2Sensor 구현 (실시간 센서 차트)
    ↓
Tab3Sqlite 구현 (SQLite DB 저장/조회/삭제)
    ↓
mainwidget에 Tab1~3 추가 및 Signal/Slot 연결
    ↓
Tab1 수신 → SENSOR 메시지 파싱 → Tab2, Tab3 동시 emit

이제 다음 시간부터는 터틀봇을 제공받고, ROS2 수업에 대해 정리할 예정입니다...

profile
임베디드 개발자가 되기 위해 공부중입니다!

0개의 댓글