이전 글에서 소켓으로 수신한 센서 데이터를 실시간 그래프로 표시하는 Tab4를 구현했습니다.
이번 글에서는 SQLite 데이터베이스를 활용해 센서 데이터를 저장하고, 저장된 데이터를 조회하여 테이블과 그래프로 표시하는 Tab5SensorDatabase를 구현하는 과정을 정리합니다.

QSqlDatabase, QSqlQuery 등 데이터베이스 관련 클래스를 사용하기 위해 .pro 파일에 sql 모듈을 추가합니다.
QT += widgets network charts sql
AiotClient 프로젝트 우클릭 → Add New → Qt → Qt Widgets Designer Form Class → Widget 선택
Class name을 Tab5SensorDatabase로 입력하면 아래 파일이 자동 생성됩니다.
tab5sensordatabase.htab5sensordatabase.cpptab5sensordatabase.uitab5sensordatabase.ui를 Qt Designer에서 열고 아래와 같이 위젯을 배치합니다.

=================================================================
| pDateTimeEditFrom | pDateTimeEditTo | [조회] | [삭제] |
|-----------------------------------------------------------------|
| | |
| pTBsensor | pChartViewLayout |
| (ID/날짜/조도/온도/습도) | (차트 표시 영역) |
| | |
=================================================================
| 위젯 | objectName | 역할 |
|---|---|---|
| QDateTimeEdit | pDateTimeEditFrom | 조회 시작 시각 |
| QDateTimeEdit | pDateTimeEditTo | 조회 종료 시각 |
| QPushButton | pPBDbSearch | DB 조회 |
| QPushButton | pPBDbDelete | 테이블/차트 초기화 |
| QTableWidget | pTBsensor | 조회 결과 테이블 표시 |
| QWidget | pChartViewLayout | 차트 뷰를 추가할 레이아웃 영역 |
Tab4와 차트 관련 멤버 변수는 동일하며, 데이터베이스 관련 멤버로 QSqlDatabase가 추가됩니다.
#ifndef TAB5SENSORDATABASE_H
#define TAB5SENSORDATABASE_H
#include <QWidget>
#include <QChartView>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <qsqlerror.h>
#include <QLineSeries>
#include <QDateTimeAxis>
#include <QDate>
#include <QTime>
namespace Ui {
class Tab5SensorDatabase;
}
class Tab5SensorDatabase : public QWidget
{
Q_OBJECT
public:
explicit Tab5SensorDatabase(QWidget *parent = nullptr);
~Tab5SensorDatabase();
void updateLastDateTimeSql(bool);
private slots:
void tab5RecvDataSlot(QStringList&);
void on_pPBDbDelete_clicked();
void on_pPBDbSearch_clicked();
private:
Ui::Tab5SensorDatabase *ui;
QSqlDatabase qSqlDatabase;
QLineSeries *illuLine;
QLineSeries *humiLine;
QLineSeries *tempLine;
QChart *pQChart;
QChartView *pQChartView;
QDateTimeAxis *pQDateTimeAxis;
QDateTime firstDateTime;
QDateTime lastDateTime;
};
#endif // TAB5SENSORDATABASE_H
생성자에서 SQLite DB를 열고 sensor_tb 테이블을 생성합니다.
테이블이 이미 존재하면 create table 쿼리는 실패하지만 이후 동작에는 영향이 없습니다.
Tab5SensorDatabase::Tab5SensorDatabase(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Tab5SensorDatabase)
{
ui->setupUi(this);
// SQLite DB 연결
qSqlDatabase = QSqlDatabase::addDatabase("QSQLITE");
qSqlDatabase.setDatabaseName("aiot.db");
if(qSqlDatabase.open())
qDebug() << "success open sqlDatabse";
else
qDebug() << "failed to open sqlDatabse";
// 테이블 생성
QString strQuery = "create table sensor_tb ("
"name varchar(10),"
"date DATETIME primary key,"
"illu varchar(10),"
"temp varchar(10),"
"humi varchar(10))";
QSqlQuery QSqlQuery;
if(QSqlQuery.exec(strQuery))
qDebug() << "Create Table";
// 차트 초기화 (Tab4와 동일)
illuLine = new QLineSeries(this);
illuLine->setName("조도");
// ... (Tab4와 동일한 QPen 설정 생략)
pQChart = new QChart();
pQChart->addSeries(illuLine);
pQChart->addSeries(humiLine);
pQChart->addSeries(tempLine);
pQChart->createDefaultAxes();
pQChart->axes(Qt::Vertical).constFirst()->setRange(0, 100);
pQChartView = new QChartView(pQChart);
pQDateTimeAxis = new QDateTimeAxis;
pQDateTimeAxis->setFormat("hh:mm");
updateLastDateTimeSql(false);
ui->pChartViewLayout->layout()->addWidget(pQChartView);
pQChartView->chart()->setAxisX(pQDateTimeAxis, illuLine);
pQChartView->chart()->setAxisX(pQDateTimeAxis, humiLine);
pQChartView->chart()->setAxisX(pQDateTimeAxis, tempLine);
}
aiot.db파일은 앱 실행 시 자동으로 생성됩니다.
Tab4의 updateLastDateTime과 달리, Tab5에서는 UI의 pDateTimeEditFrom, pDateTimeEditTo 값을 기준으로 X축 범위를 설정합니다.
void Tab5SensorDatabase::updateLastDateTimeSql(bool bFlag)
{
QDateTime firstDateTime;
QDateTime lastDateTime;
QDateTime fromDateTime = ui->pDateTimeEditFrom->dateTime();
QDateTime toDateTime = ui->pDateTimeEditTo->dateTime();
if(!bFlag)
{
firstDateTime = fromDateTime;
lastDateTime = toDateTime;
}
else
{
firstDateTime = ui->pDateTimeEditFrom->dateTime();
lastDateTime = QDateTime::currentDateTime().addSecs(60);
}
pQDateTimeAxis->setRange(firstDateTime, lastDateTime);
}
소켓으로 데이터를 수신하면 DB에 INSERT하고 차트에도 실시간으로 추가합니다.
INSERT 성공 시 pDateTimeEditTo를 현재 시각으로 업데이트합니다.
void Tab5SensorDatabase::tab5RecvDataSlot(QStringList& strList)
{
QDateTime dateTime = QDateTime::currentDateTime();
QString name = strList[1];
QString strIllu = strList[3];
QString strTemp = strList[4];
QString strHumi = strList[5];
if(lastDateTime.toSecsSinceEpoch() < dateTime.toSecsSinceEpoch())
updateLastDateTimeSql(true);
// DB INSERT
QString strQuery = "insert into sensor_tb(name, date, illu, temp, humi) values('"
+ name + "' , '"
+ dateTime.toString("yyyy/MM/dd hh:mm:ss") + "' , '"
+ strIllu + "' , '"
+ strTemp + "' , '"
+ strHumi + "' ) ";
QSqlQuery qSqlQuery;
if(qSqlQuery.exec(strQuery))
{
qDebug() << "Insert Query OK";
ui->pDateTimeEditTo->setDateTime(dateTime);
pQDateTimeAxis->setMax(dateTime);
}
// 차트에 실시간 추가
illuLine->append(dateTime.toMSecsSinceEpoch(), strIllu.toInt());
humiLine->append(dateTime.toMSecsSinceEpoch(), strTemp.toDouble());
tempLine->append(dateTime.toMSecsSinceEpoch(), strHumi.toDouble());
}
조회 버튼을 누르면 DB에서 pDateTimeEditFrom ~ pDateTimeEditTo 범위의 데이터를 SELECT하여 pTBsensor에 표시하고 차트도 갱신합니다.
void Tab5SensorDatabase::on_pPBDbSearch_clicked()
{
// DB에서 가장 오래된 데이터 시각을 찾아 From에 설정
QSqlQuery minQuery;
if(minQuery.exec("select min(date) from sensor_tb") && minQuery.next())
{
QString strMinDate = minQuery.value(0).toString();
QDateTime minDateTime = QDateTime::fromString(strMinDate, "yyyy/MM/dd hh:mm:ss");
if(minDateTime.isValid())
{
ui->pDateTimeEditFrom->setDateTime(minDateTime);
ui->pDateTimeEditTo->setDateTime(QDateTime::currentDateTime());
}
}
illuLine->clear();
humiLine->clear();
tempLine->clear();
updateLastDateTimeSql(false);
// 범위 조건으로 SELECT
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 = "select * from sensor_tb where '"
+ strFromDateTime + "' <= date AND date < '"
+ strToDateTime + "' ";
int rowCount = 0;
QSqlQuery qSqlQuery;
if(qSqlQuery.exec(strQuery))
{
qDebug() << "Select Query Ok";
while(qSqlQuery.next())
{
rowCount++;
ui->pTBsensor->setRowCount(rowCount);
QTableWidgetItem *pId = new QTableWidgetItem();
QTableWidgetItem *pDate = new QTableWidgetItem();
QTableWidgetItem *pIllu = new QTableWidgetItem();
QTableWidgetItem *pTemp = new QTableWidgetItem();
QTableWidgetItem *pHumi = new QTableWidgetItem();
pId->setText(qSqlQuery.value("name").toString());
pDate->setText(qSqlQuery.value("date").toString());
pIllu->setText(qSqlQuery.value("illu").toString());
pTemp->setText(qSqlQuery.value("temp").toString());
pHumi->setText(qSqlQuery.value("humi").toString());
ui->pTBsensor->setItem(rowCount-1, 0, pId);
ui->pTBsensor->setItem(rowCount-1, 1, pDate);
ui->pTBsensor->setItem(rowCount-1, 2, pIllu);
ui->pTBsensor->setItem(rowCount-1, 3, pTemp);
ui->pTBsensor->setItem(rowCount-1, 4, pHumi);
QDateTime xValue = QDateTime::fromString(pDate->text(), "yyyy/MM/dd hh:mm:ss");
illuLine->append(xValue.toMSecsSinceEpoch(), pIllu->text().toInt());
humiLine->append(xValue.toMSecsSinceEpoch(), pHumi->text().toDouble());
tempLine->append(xValue.toMSecsSinceEpoch(), pTemp->text().toDouble());
}
ui->pTBsensor->resizeColumnToContents(0);
ui->pTBsensor->resizeColumnToContents(1);
ui->pTBsensor->resizeColumnToContents(2);
ui->pTBsensor->resizeColumnToContents(3);
ui->pTBsensor->resizeColumnToContents(4);
}
}
void Tab5SensorDatabase::on_pPBDbDelete_clicked()
{
ui->pTBsensor->clearContents();
illuLine->clear();
humiLine->clear();
tempLine->clear();
}
삭제 버튼은 화면에 표시된 테이블과 차트만 초기화합니다. DB에 저장된 데이터는 유지됩니다.
#include <tab5sensordatabase.h>
Tab5SensorDatabase *pTab5SensorDatabase;
pTab5SensorDatabase = new Tab5SensorDatabase(ui->pTab5);
ui->pTab5->setLayout(pTab5SensorDatabase->layout());
connect(pTab2SocketClient, SIGNAL(tab5RecvDataSig(QStringList&)),
pTab5SensorDatabase, SLOT(tab5RecvDataSlot(QStringList&)));
| Tab4 | Tab5 | |
|---|---|---|
| 데이터 저장 | 없음 (메모리) | SQLite DB 저장 |
| X축 기준 | 현재 시각 자동 | DateTimeEdit 기준 |
| 조회 기능 | 없음 | DB SELECT + 테이블 표시 |
| 초기화 | 차트만 | 테이블 + 차트 |
.pro에 sql 모듈 추가 (QT += sql)
↓
Tab5SensorDatabase 클래스 생성 (Qt Widgets Designer Form Class)
↓
UI 구성 (DateTimeEdit x2 + 조회/삭제 버튼 + QTableWidget + 차트 영역)
↓
생성자 : SQLite DB 연결 → sensor_tb 테이블 생성 → 차트 초기화
↓
tab5RecvDataSlot : 수신 데이터 DB INSERT → 차트 실시간 추가
↓
조회 버튼 : DB SELECT → QTableWidget 표시 → 차트 갱신
↓
삭제 버튼 : 화면 테이블/차트 초기화 (DB 데이터 유지)
↓
mainwidget에 Tab5 추가 및 Tab2 수신 시그널 연결