第19章 Raspberry Pi 資料庫與環境監測:SQLite 儲存感測器歷史資料

發布日期: |更新日期:
文章主圖:第19章 Raspberry Pi 資料庫與環境監測:SQLite 儲存感測器歷史資料

在第 18 章中,我們已經使用 MQTT 建立 Raspberry Pi IoT 系統,讓 DHT11 / DHT22 的溫度與濕度資料可以即時發布到 MQTT Broker。

例如:

DHT22
  │
  ▼
Raspberry Pi
  │
  │ MQTT Publish
  ▼
Mosquitto
  │
  ▼
Subscriber

這種架構可以讓其他裝置知道:

目前溫度:28.6°C
目前濕度:67.2%

但是,新的問題隨之出現。

如果我們想知道:

  • 昨天最高溫是多少?
  • 今天凌晨的濕度是多少?
  • 過去 24 小時溫度變化如何?
  • 最近一星期有哪些時間超過 30°C?
  • 風扇啟動前的室內溫度是多少?
  • 某一天到底記錄了多少筆資料?

只靠 MQTT 即時訊息是不夠的。

我們還需要:

Database 資料庫

因此本章要為 Raspberry Pi 加入:

SQLite

讓 Raspberry Pi 可以把每一次感測器讀值儲存下來,形成真正的:

環境監測歷史資料庫。

本章最後將完成:

Raspberry Pi 溫溼度歷史資料庫

系統架構將變成:

DHT11 / DHT22
      │
      ▼
 Raspberry Pi
      │
      ▼
    Python
      │
      ├────────► SQLite
      │             │
      │             ▼
      │        History Data
      │
      └────────► MQTT
                    │
                    ▼
               Real-Time Data

這也是第 20 章「Raspberry Pi 智慧環境監控中心」最後一個重要資料層。

一、本章學習目標

完成本章後,你將了解:

  • 為什麼 IoT 系統需要 Database
  • Database 基本概念
  • SQLite 是什麼
  • Table 是什麼
  • Row 與 Column
  • Primary Key
  • SQL 基礎
  • CREATE TABLE
  • INSERT
  • SELECT
  • WHERE
  • ORDER BY
  • LIMIT
  • Timestamp
  • Python sqlite3
  • Parameterized Query
  • Sensor Data Logging
  • Historical Data Query
  • Aggregate Function
  • CSV 匯出
  • Database Backup 基本概念

並完成:

Raspberry Pi 溫溼度歷史資料庫

二、為什麼 Raspberry Pi IoT 需要資料庫?

假設 DHT22 每 60 秒讀一次資料:

08:00  27.8°C  68%
08:01  27.9°C  68%
08:02  28.0°C  67%
08:03  28.1°C  67%

如果只使用:

print()

資料只會出現在 Terminal。

程式停止之後,先前資料就不容易繼續分析。

如果只透過 MQTT:

home/room1/temperature

發布最新溫度,MQTT 比較偏向:

即時訊息傳輸。

而資料庫負責:

長期資料保存。

因此:

MQTT
=
現在發生什麼

SQLite
=
過去發生什麼

這兩者是非常好的組合。

三、什麼是 Database?

Database 中文稱為:

資料庫。

它的目的就是有系統地儲存資料。

例如 Raspberry Pi 環境監測系統可以保存:

環境監測資料範例
id timestamp temperature humidity
1 2026-08-18 08:00:00 27.8 68.0
2 2026-08-18 08:01:00 27.9 68.0
3 2026-08-18 08:02:00 28.0 67.5
4 2026-08-18 08:03:00 28.1 67.2

這些資料就可以永久保存在 Raspberry Pi 儲存裝置中。

之後即使:

Python 程式停止
Raspberry Pi 重新開機
MQTT Subscriber 離線

歷史資料仍然存在。

四、資料庫可以做什麼?

有了 Database 之後,可以回答很多問題。

例如:

目前溫度是多少?

可以查最新一筆。

今天最高溫是多少?

可以使用:

MAX()
今天平均溫度是多少?

可以使用:

AVG()
有哪些資料超過 30°C?

可以使用:

WHERE temperature >= 30
最近 20 筆資料?

可以使用:

ORDER BY
LIMIT

這就是 Database 相較於單純文字檔的重要價值。

五、為什麼選擇 SQLite?

Database 有很多種類。

例如:

SQLite
MySQL
MariaDB
PostgreSQL

對 Raspberry Pi 初學專案而言,SQLite 有非常大的優勢。

六、SQLite 的特色

SQLite 是一種:

Embedded Relational Database

它不像 MySQL 一定需要另外執行 Database Server Process。

SQLite 最重要的特色是:

Database
   ↓
一個檔案

例如:

environment.db

這個檔案本身就是資料庫。

因此整體架構很簡單:

Python
   │
   ▼
sqlite3
   │
   ▼
environment.db

非常適合:

  • Raspberry Pi
  • IoT
  • Sensor Logger
  • 小型 Web App
  • Edge Device
  • Local Data Cache
  • 教學專案

七、SQLite 和 MySQL 有什麼不同?

簡單比較:

SQLite 與 MySQL / MariaDB 比較
項目 SQLite MySQL / MariaDB
需要 Database Server 否 是
安裝複雜度 低 較高
Database 儲存 單一檔案 Server 管理
小型 IoT 很適合 適合大型系統
多使用者高併發 較有限 較適合
Raspberry Pi 入門 非常適合 較進階
Python 支援 內建 sqlite3 需 Driver

因此本章選用:

SQLite

八、什麼是 Table?

Relational Database 中,資料通常儲存在:

Table

也就是資料表。

我們建立:

environment_data

用來保存環境資料。

例如:

environment_data 資料表示例
id timestamp temperature humidity
1 2026-08-18 08:00:00 27.8 68.0
2 2026-08-18 08:01:00 27.9 67.8

Table 可以理解成一張有固定欄位結構的表格。

九、什麼是 Column?

Column 就是資料欄位。

本章建立:

id
timestamp
temperature
humidity

其中:

id

資料編號。

timestamp

資料產生時間。

temperature

溫度。

humidity

濕度。

十、什麼是 Row?

每一次 Sensor Reading 都是一筆:

Row

例如:

1 | 2026-08-18 08:00:00 | 27.8 | 68.0

下一次:

2 | 2026-08-18 08:01:00 | 27.9 | 67.8

因此:

Column
=
資料欄位

Row
=
一筆完整紀錄

十一、什麼是 Primary Key?

資料表中通常需要有一個可以唯一識別每一筆資料的欄位。

例如:

id

第一筆:

1

第二筆:

2

第三筆:

3

這個欄位就可以設計為:

Primary Key

例如 SQLite:

id INTEGER PRIMARY KEY AUTOINCREMENT

表示 SQLite 自動產生流水號。

十二、什麼是 SQL?

SQL 是:

Structured Query Language

中文通常稱為:

結構化查詢語言。

它用來操作 Relational Database。

常見指令:

CREATE TABLE
INSERT
SELECT
UPDATE
DELETE

本章最主要使用:

CREATE TABLE
INSERT
SELECT

十三、CREATE TABLE

建立資料表:

CREATE TABLE environment_data (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    timestamp TEXT NOT NULL,
    temperature REAL,
    humidity REAL
);

這代表建立:

environment_data

包含:

id
timestamp
temperature
humidity

四個欄位。

十四、SQLite 常見資料型別

SQLite 常見型別:

SQLite 常見資料型別
型別 用途
INTEGER 整數
REAL 浮點數
TEXT 文字
BLOB Binary Data
NULL 空值

本章:

id → INTEGER

temperature → REAL

humidity → REAL

timestamp → TEXT

十五、INSERT 是什麼?

INSERT 用來:

新增資料。

例如:

INSERT INTO environment_data
(
    timestamp,
    temperature,
    humidity
)
VALUES
(
    '2026-08-18 08:00:00',
    28.5,
    67.2
);

執行後資料表多一筆:

INSERT 後的資料表示例
id timestamp temperature humidity
1 2026-08-18 08:00:00 28.5 67.2

十六、SELECT 是什麼?

SELECT 用來:

讀取資料。

例如:

SELECT *
FROM environment_data;

代表:

取得 environment_data
所有欄位
所有資料

十七、SELECT 指定欄位

例如只查:

timestamp
temperature

可以:

SELECT
    timestamp,
    temperature
FROM environment_data;

這樣就不必取得不需要的欄位。

十八、WHERE 是什麼?

如果只想取得:

temperature >= 30

可以:

SELECT *
FROM environment_data
WHERE temperature >= 30;

這樣就能找到:

高溫紀錄。

十九、ORDER BY

如果要按照時間排序:

SELECT *
FROM environment_data
ORDER BY timestamp ASC;

ASC:

由小到大
由早到晚

如果:

ORDER BY timestamp DESC;

就是:

由大到小
由晚到早

因此查最新資料時很常使用:

ORDER BY timestamp DESC

二十、LIMIT

例如:

SELECT *
FROM environment_data
ORDER BY id DESC
LIMIT 10;

代表:

取得最新 10 筆紀錄。

對 Raspberry Pi Web Dashboard 非常實用。

二十一、什麼是 Timestamp?

Sensor Data 只有:

28.5°C

其實不夠。

因為我們不知道:

這個 28.5°C 是什麼時候測量的?

因此需要:

Timestamp

例如:

2026-08-18 08:45:00

完整資料:

Temperature:
28.5°C

Humidity:
67.2%

Timestamp:
2026-08-18 08:45:00

二十二、Python 如何取得時間?

可以使用:

from datetime import datetime

取得現在時間:

now = datetime.now()

格式化:

timestamp = now.strftime(
    "%Y-%m-%d %H:%M:%S"
)

結果例如:

2026-08-18 08:45:00

二十三、Python 內建 sqlite3

Python 標準函式庫就提供:

sqlite3

因此通常不需要額外使用:

pip install sqlite3

直接:

import sqlite3

即可操作 SQLite。

這是 Raspberry Pi 使用 SQLite 最大的便利之一。

二十四、建立專案目錄

先建立:

mkdir -p ~/environment_database

進入:

cd ~/environment_database

未來檔案結構:

environment_database/
│
├── environment.db
├── create_database.py
├── insert_test.py
├── query_data.py
├── sensor_logger.py
└── export_csv.py

二十五、建立第一個 SQLite Database

建立:

nano create_database.py

輸入:

import sqlite3


DB_FILE = "environment.db"


connection = sqlite3.connect(
    DB_FILE
)


cursor = connection.cursor()


cursor.execute(
    """
    CREATE TABLE IF NOT EXISTS environment_data
    (
        id INTEGER
            PRIMARY KEY
            AUTOINCREMENT,

        timestamp TEXT
            NOT NULL,

        temperature REAL,

        humidity REAL
    )
    """
)


connection.commit()

connection.close()


print(
    "Database initialized."
)

執行:

python3 create_database.py

應看到:

Database initialized.

接著:

ls -lh

會看到:

environment.db

二十六、IF NOT EXISTS 有什麼作用?

我們使用:

CREATE TABLE IF NOT EXISTS

表示:

如果 Table 不存在
→ 建立

如果已經存在
→ 不重複建立

因此每次程式啟動時都可以安全執行:

initialize_database()

不用擔心 Table 已存在而產生錯誤。

二十七、第一筆測試資料

建立:

nano insert_test.py

輸入:

import sqlite3

from datetime import datetime


DB_FILE = "environment.db"


temperature = 28.5

humidity = 67.2


timestamp = datetime.now().strftime(
    "%Y-%m-%d %H:%M:%S"
)


connection = sqlite3.connect(
    DB_FILE
)


cursor = connection.cursor()


cursor.execute(
    """
    INSERT INTO environment_data
    (
        timestamp,
        temperature,
        humidity
    )
    VALUES
    (
        ?,
        ?,
        ?
    )
    """,
    (
        timestamp,
        temperature,
        humidity
    )
)


connection.commit()

connection.close()


print(
    "Sensor data inserted."
)

執行:

python3 insert_test.py

二十八、為什麼 VALUES 使用問號?

程式:

VALUES (?, ?, ?)

然後另外傳入:

(
    timestamp,
    temperature,
    humidity
)

這叫做:

Parameterized Query

不要寫成:

sql = (
    "INSERT INTO environment_data "
    "VALUES ('"
    + user_data
    + "')"
)

Parameter Binding 可以:

  • 正確處理資料型別
  • 正確處理特殊字元
  • 降低 SQL Injection 風險
  • 讓程式更加清楚

因此 Python SQLite 操作應養成:

cursor.execute(
    SQL,
    parameters
)

的習慣。

二十九、為什麼需要 commit()?

SQL:

INSERT
UPDATE
DELETE

會修改 Database。

執行:

connection.commit()

表示:

正式提交 Transaction。

如果忘記:

connection.commit()

資料可能沒有真正寫進 Database。

三十、讀取 SQLite 資料

建立:

nano query_data.py

輸入:

import sqlite3


DB_FILE = "environment.db"


connection = sqlite3.connect(
    DB_FILE
)


cursor = connection.cursor()


cursor.execute(
    """
    SELECT
        id,
        timestamp,
        temperature,
        humidity

    FROM environment_data

    ORDER BY id DESC
    """
)


rows = cursor.fetchall()


for row in rows:

    print(row)


connection.close()

執行:

python3 query_data.py

可能看到:

(1, '2026-08-18 08:45:00', 28.5, 67.2)

三十一、fetchall() 是什麼?

執行:

cursor.fetchall()

表示取得:

所有查詢結果。

如果 SQL 查出:

1000 筆

fetchall() 就會一次取得這些資料。

對小型 Sensor Database 沒問題。

但大型系統不應每次都:

SELECT *

然後取得幾十萬筆紀錄。

應使用:

WHERE
LIMIT
Pagination
Aggregation

控制資料量。

三十二、取得最新一筆資料

可以:

cursor.execute(
    """
    SELECT
        timestamp,
        temperature,
        humidity

    FROM environment_data

    ORDER BY id DESC

    LIMIT 1
    """
)

接著:

row = cursor.fetchone()

這非常適合 Web Dashboard 顯示:

Current Temperature

Current Humidity

三十三、格式化顯示資料

例如:

if row:

    timestamp = row[0]
    temperature = row[1]
    humidity = row[2]

    print(
        f"Time: {timestamp}"
    )

    print(
        f"Temperature: "
        f"{temperature:.1f} °C"
    )

    print(
        f"Humidity: "
        f"{humidity:.1f} %"
    )

畫面:

Time: 2026-08-18 08:45:00
Temperature: 28.5 °C
Humidity: 67.2 %

三十四、查詢高溫紀錄

例如找:

>= 30°C

SQL:

SELECT
    timestamp,
    temperature,
    humidity
FROM environment_data
WHERE temperature >= 30
ORDER BY timestamp DESC;

Python:

cursor.execute(
    """
    SELECT
        timestamp,
        temperature,
        humidity

    FROM environment_data

    WHERE temperature >= ?

    ORDER BY timestamp DESC
    """,
    (30.0,)
)

這也示範:

SELECT 查詢一樣可以使用 Parameterized Query。

三十五、統計最高溫

SQL:

SELECT MAX(temperature)
FROM environment_data;

Python:

cursor.execute(
    """
    SELECT MAX(temperature)
    FROM environment_data
    """
)


row = cursor.fetchone()


print(
    "Maximum temperature:",
    row[0]
)

三十六、統計最低溫

SELECT MIN(temperature)
FROM environment_data;

三十七、計算平均溫度

SELECT AVG(temperature)
FROM environment_data;

例如:

Average temperature:
28.36°C

三十八、總共記錄多少筆?

SQL:

SELECT COUNT(*)
FROM environment_data;

這對 Dashboard 很實用。

例如:

Total Records

8640

三十九、完整環境統計 SQL

可以一次取得:

SELECT
    COUNT(*),
    MIN(temperature),
    MAX(temperature),
    AVG(temperature),
    MIN(humidity),
    MAX(humidity),
    AVG(humidity)
FROM environment_data;

如此可以得到:

Records:
1440

Temperature Min:
25.8°C

Temperature Max:
31.2°C

Temperature Avg:
28.1°C

Humidity Min:
54%

Humidity Max:
78%

Humidity Avg:
65%

四十、正式進入 Sensor Data Logging

現在將 SQLite 與 DHT11 / DHT22 整合。

系統:

DHT22
  │
  │ GPIO4
  ▼
Python
  │
  ▼
Temperature
Humidity
  │
  ▼
SQLite INSERT
  │
  ▼
environment.db

這就是:

Data Logger

四十一、DHT22 接線

延續前一章:

DHT22 與 Raspberry Pi 接線
DHT22 Raspberry Pi
VCC 3.3V
DATA GPIO4
GND GND

架構:

Raspberry Pi
   │
   ├── 3.3V ─────► DHT VCC
   │
   ├── GPIO4 ────► DHT DATA
   │
   └── GND ──────► DHT GND

如果使用:

DHT Module

通常模組已整合必要元件。

若使用裸 Sensor,請依模組規格確認 DATA Pull-up。

四十二、完整 SQLite Sensor Logger

建立:

nano sensor_logger.py

輸入:

import sqlite3
import time

from datetime import datetime

import board
import adafruit_dht


# ======================================
# Settings
# ======================================

DB_FILE = "environment.db"

LOG_INTERVAL = 60


# ======================================
# DHT22
# GPIO4
# ======================================

dht = adafruit_dht.DHT22(
    board.D4,
    use_pulseio=False
)


# ======================================
# Database Initialization
# ======================================

def initialize_database():

    connection = sqlite3.connect(
        DB_FILE
    )


    cursor = connection.cursor()


    cursor.execute(
        """
        CREATE TABLE IF NOT EXISTS
        environment_data
        (
            id INTEGER
                PRIMARY KEY
                AUTOINCREMENT,

            timestamp TEXT
                NOT NULL,

            temperature REAL
                NOT NULL,

            humidity REAL
                NOT NULL
        )
        """
    )


    connection.commit()

    connection.close()


# ======================================
# Save Sensor Data
# ======================================

def save_sensor_data(
    temperature,
    humidity
):

    timestamp = datetime.now().strftime(
        "%Y-%m-%d %H:%M:%S"
    )


    connection = sqlite3.connect(
        DB_FILE
    )


    cursor = connection.cursor()


    cursor.execute(
        """
        INSERT INTO environment_data
        (
            timestamp,
            temperature,
            humidity
        )

        VALUES
        (
            ?,
            ?,
            ?
        )
        """,
        (
            timestamp,
            temperature,
            humidity
        )
    )


    connection.commit()

    connection.close()


    print(
        "Saved:",
        timestamp,
        f"{temperature:.1f} C",
        f"{humidity:.1f} %"
    )


# ======================================
# Main
# ======================================

initialize_database()


print(
    "Raspberry Pi "
    "Environment Logger Started"
)


try:

    while True:

        try:

            temperature = (
                dht.temperature
            )

            humidity = (
                dht.humidity
            )


            if (
                temperature is not None
                and
                humidity is not None
            ):

                save_sensor_data(
                    temperature,
                    humidity
                )


        except RuntimeError as error:

            print(
                "DHT temporary error:",
                error
            )


        except Exception as error:

            print(
                "Sensor error:",
                error
            )


        time.sleep(
            LOG_INTERVAL
        )


except KeyboardInterrupt:

    print(
        "\nStopping logger..."
    )


finally:

    dht.exit()


    print(
        "Environment Logger Stopped"
    )

四十三、執行 Sensor Logger

如果你在前一章已建立 Virtual Environment:

source ~/mqtt_project/venv/bin/activate

進入:

cd ~/environment_database

執行:

python sensor_logger.py

正常可能看到:

Raspberry Pi Environment Logger Started

Saved:
2026-08-18 08:45:00
28.5 C
67.2 %

Saved:
2026-08-18 08:46:00
28.6 C
67.1 %

因為:

LOG_INTERVAL = 60

所以:

每 60 秒記錄一次

四十四、如果使用 DHT11

將:

dht = adafruit_dht.DHT22(
    board.D4,
    use_pulseio=False
)

改成:

dht = adafruit_dht.DHT11(
    board.D4,
    use_pulseio=False
)

其他 Database 程式不用改。

這就是把:

Sensor Layer

與:

Database Layer

分離的好處。

四十五、資料實際會變成什麼?

假設執行 5 分鐘:

08:45
08:46
08:47
08:48
08:49

Database:

感測器時間序列資料範例
id timestamp temperature humidity
1 2026-08-18 08:45:00 28.5 67.2
2 2026-08-18 08:46:00 28.6 67.1
3 2026-08-18 08:47:00 28.6 67.0
4 2026-08-18 08:48:00 28.7 66.8
5 2026-08-18 08:49:00 28.8 66.7

這就形成:

Time Series Data。

四十六、什麼是 Time Series Data?

Time Series Data 中文通常稱為:

時間序列資料。

例如:

時間              溫度
08:00             27.8
08:01             27.9
08:02             28.0
08:03             28.1

這種資料最重要的特徵就是:

每一筆資料都和時間有關。

IoT Sensor Data 幾乎都是典型 Time Series Data。

四十七、建立歷史資料查詢程式

建立:

nano history.py

輸入:

import sqlite3


DB_FILE = "environment.db"


connection = sqlite3.connect(
    DB_FILE
)


cursor = connection.cursor()


cursor.execute(
    """
    SELECT
        id,
        timestamp,
        temperature,
        humidity

    FROM environment_data

    ORDER BY id DESC

    LIMIT 20
    """
)


rows = cursor.fetchall()


print(
    "Latest 20 records"
)

print(
    "-" * 65
)


for row in rows:

    record_id = row[0]

    timestamp = row[1]

    temperature = row[2]

    humidity = row[3]


    print(
        f"{record_id:5d}  "
        f"{timestamp}  "
        f"{temperature:6.1f} C  "
        f"{humidity:6.1f} %"
    )


connection.close()

執行:

python3 history.py

可能看到:

Latest 20 records
-----------------------------------------------------------------
  125  2026-08-18 08:49:00    28.8 C    66.7 %
  124  2026-08-18 08:48:00    28.7 C    66.8 %
  123  2026-08-18 08:47:00    28.6 C    67.0 %

四十八、查今天的資料

因為 Timestamp 使用:

YYYY-MM-DD HH:MM:SS

可以用:

WHERE date(timestamp) = date('now', 'localtime')

例如:

cursor.execute(
    """
    SELECT
        timestamp,
        temperature,
        humidity

    FROM environment_data

    WHERE
        date(timestamp)
        =
        date(
            'now',
            'localtime'
        )

    ORDER BY timestamp ASC
    """
)

即可查詢:

今天的環境資料。

四十九、查最近一小時資料

可以使用:

SELECT *
FROM environment_data
WHERE datetime(timestamp)
>= datetime('now', '-1 hour', 'localtime');

這類查詢非常適合未來的:

Flask Dashboard

例如:

最近 1 小時
最近 6 小時
最近 24 小時

五十、建立環境統計程式

建立:

nano statistics.py

輸入:

import sqlite3


DB_FILE = "environment.db"


connection = sqlite3.connect(
    DB_FILE
)


cursor = connection.cursor()


cursor.execute(
    """
    SELECT
        COUNT(*),

        MIN(temperature),

        MAX(temperature),

        AVG(temperature),

        MIN(humidity),

        MAX(humidity),

        AVG(humidity)

    FROM environment_data
    """
)


row = cursor.fetchone()


if row:

    record_count = row[0]

    temp_min = row[1]

    temp_max = row[2]

    temp_avg = row[3]

    humidity_min = row[4]

    humidity_max = row[5]

    humidity_avg = row[6]


    print(
        "Total Records:",
        record_count
    )


    if temp_avg is not None:

        print(
            f"Temperature Min: "
            f"{temp_min:.1f} C"
        )

        print(
            f"Temperature Max: "
            f"{temp_max:.1f} C"
        )

        print(
            f"Temperature Avg: "
            f"{temp_avg:.1f} C"
        )

        print(
            f"Humidity Min: "
            f"{humidity_min:.1f} %"
        )

        print(
            f"Humidity Max: "
            f"{humidity_max:.1f} %"
        )

        print(
            f"Humidity Avg: "
            f"{humidity_avg:.1f} %"
        )


connection.close()

五十一、查詢高溫警報資料

例如:

30°C

以上屬於高溫。

SQL:

SELECT
    timestamp,
    temperature,
    humidity
FROM environment_data
WHERE temperature >= 30
ORDER BY timestamp DESC;

這可以回答:

最近一次超過 30°C 是什麼時候?

也能進一步配合第 20 章:

LED Warning
Buzzer
Relay Fan

五十二、將設備狀態一起存進 Database

第 20 章不只要存 Sensor。

還可以增加:

led_status
fan_status
alarm_status

資料表:

CREATE TABLE environment_data
(
    id INTEGER
        PRIMARY KEY
        AUTOINCREMENT,

    timestamp TEXT
        NOT NULL,

    temperature REAL,

    humidity REAL,

    led_status INTEGER,

    fan_status INTEGER,

    alarm_status INTEGER
);

例如:

設備狀態歷史資料範例
time temp humidity fan
09:00 28.5 67 0
09:05 30.2 66 1
09:10 30.8 65 1
09:15 29.4 64 0

如此未來可以分析:

哪一次高溫造成 Fan ON?

五十三、Camera 事件也可以存 Database

第 16 章有:

Picamera2

因此未來可以建立 Camera Event Table。

例如:

CREATE TABLE camera_events
(
    id INTEGER
        PRIMARY KEY
        AUTOINCREMENT,

    timestamp TEXT
        NOT NULL,

    filename TEXT
        NOT NULL,

    event_type TEXT
);

資料:

Camera Event Table 資料範例
id timestamp filename event_type
1 2026-08-18 09:00 photo001.jpg manual
2 2026-08-18 09:10 photo002.jpg high_temperature

這樣就能知道:

這張照片為什麼被拍攝。

五十四、MQTT 與 SQLite 同時使用

上一章的:

MQTT

不需要被 SQLite 取代。

正確架構是:

DHT22
                    │
                    ▼
                  Python
                    │
           ┌────────┴────────┐
           │                 │
           ▼                 ▼
       SQLite              MQTT
           │                 │
           ▼                 ▼
      History Data       Live Data

因此:

SQLite
=
Historical Storage

而:

MQTT
=
Real-Time Messaging

五十五、Flask + SQLite

第 17 章已學 Flask。

現在 Flask 就可以讀 SQLite。

架構:

SQLite
   │
   ▼
Python
   │
   ▼
Flask
   │
   ▼
HTML
   │
   ▼
Browser

例如 Web Page:

Raspberry Pi Environment Monitor

Current Temperature
28.6°C

Current Humidity
67.1%

Today's Maximum
31.2°C

Today's Minimum
25.9°C

Total Records
1438

這就是第 20 章 Dashboard 的資料來源。

五十六、不要讓 Web Page 直接操作 Sensor

比較好的架構不是:

Browser
  ↓
Flask
  ↓
每次重新讀 DHT22

而是:

Sensor Service
      │
      ▼
    SQLite
      │
      ▼
    Flask
      │
      ▼
   Browser

因為 Sensor Logger 可以:

持續記錄

而 Flask 只是:

讀取最新資料

兩個功能分開,架構更加清楚。

五十七、CSV 是什麼?

CSV 是:

Comma-Separated Values

例如:

id,timestamp,temperature,humidity
1,2026-08-18 08:45:00,28.5,67.2
2,2026-08-18 08:46:00,28.6,67.1
3,2026-08-18 08:47:00,28.6,67.0

它的優點是可以被很多軟體讀取。

例如:

  • Microsoft Excel
  • LibreOffice Calc
  • Python pandas
  • Google Sheets
  • Database Tool

因此 IoT Database 很常提供:

Export CSV

功能。

五十八、建立 CSV 匯出程式

建立:

nano export_csv.py

輸入:

import csv
import sqlite3

from datetime import datetime


DB_FILE = "environment.db"


timestamp = datetime.now().strftime(
    "%Y%m%d_%H%M%S"
)


CSV_FILE = (
    f"environment_"
    f"{timestamp}.csv"
)


connection = sqlite3.connect(
    DB_FILE
)


cursor = connection.cursor()


cursor.execute(
    """
    SELECT
        id,
        timestamp,
        temperature,
        humidity

    FROM environment_data

    ORDER BY id ASC
    """
)


rows = cursor.fetchall()


with open(
    CSV_FILE,
    "w",
    newline="",
    encoding="utf-8-sig"
) as csv_file:

    writer = csv.writer(
        csv_file
    )


    writer.writerow(
        [
            "ID",
            "Timestamp",
            "Temperature_C",
            "Humidity_Percent"
        ]
    )


    writer.writerows(
        rows
    )


connection.close()


print(
    "CSV exported:",
    CSV_FILE
)

五十九、為什麼使用 utf-8-sig?

程式:

encoding="utf-8-sig"

主要是為了讓某些 Windows / Excel 環境在開啟包含中文欄位的 CSV 時,比單純 UTF-8 更容易正確辨識文字編碼。

如果全部欄位都是英文,也可以:

encoding="utf-8"

六十、執行 CSV Export

執行:

python3 export_csv.py

可能得到:

CSV exported:
environment_20260818_084500.csv

查看:

ls -lh *.csv

六十一、CSV 可以做什麼?

把 CSV 複製到電腦後,可以使用 Excel 建立:

Temperature Chart
Humidity Chart
Daily Average
Daily Maximum
Trend Analysis

例如:

Temperature
31 ┤                 ╭──╮
30 ┤             ╭───╯  ╰─
29 ┤        ╭────╯
28 ┤───────╯
27 ┤
   └──────────────────────
        Time

這就從:

Sensor Reading

進一步變成:

Data Analysis。

六十二、SQLite Database 可以直接用 CLI 查看嗎?

如果系統安裝 SQLite Command Line Tool,可以:

sudo apt install -y sqlite3

然後:

sqlite3 environment.db

進入 SQLite Console。

輸入:

.tables

查看 Table。

六十三、查看 Table Schema

輸入:

.schema environment_data

可以看到資料表結構。

六十四、直接執行 SELECT

在 SQLite Console:

SELECT *
FROM environment_data
ORDER BY id DESC
LIMIT 10;

即可查看最新 10 筆紀錄。

離開:

.quit

六十五、讓 SQLite 顯示更容易閱讀

SQLite CLI 中可以:

.headers on

以及:

.mode column

接著:

SELECT
    id,
    timestamp,
    temperature,
    humidity
FROM environment_data
ORDER BY id DESC
LIMIT 10;

顯示會更加整齊。

六十六、Database 檔案放在哪裡?

目前:

DB_FILE = "environment.db"

代表 Database 建立在:

目前 Working Directory。

如果希望永遠放在固定位置,可以:

from pathlib import Path


DB_FILE = (
    Path.home()
    / "environment_database"
    / "environment.db"
)

這樣不論從哪裡執行 Python,都會使用:

/home/使用者/environment_database/environment.db

正式專案通常建議使用:

Absolute Path

避免程式因 Working Directory 不同而產生多個 Database。

六十七、Database 檔案權限

SQLite 不是只有:

environment.db

檔案可讀寫就一定沒問題。

實際寫入時,程式也可能需要對:

Database 所在目錄

有寫入權限。

因為 SQLite 某些模式可能需要建立:

Journal
WAL
SHM

等輔助檔案。

所以正式使用 systemd Service 時,需要確認:

Service User
Database Directory Owner
Directory Permission

一致。

這是 Raspberry Pi 專案常見的部署問題。

六十八、不要使用 sudo 解決所有權限問題

如果 Python 出現:

Permission denied

不建議第一個反應就是:

sudo python3 sensor_logger.py

更好的方式是檢查:

ls -ld ~/environment_database

以及:

ls -l ~/environment_database

確認:

  • Database Owner
  • Directory Owner
  • Service User
  • File Permission

正確設定權限,比長期讓 Sensor Service 使用 root 執行更合理。

六十九、Database 會不會越來越大?

會。

假設:

每分鐘 1 筆

一天:

60 × 24
=
1440 筆

一年:

1440 × 365
=
525600 筆

SQLite 處理這類規模通常不成問題,但還是應從系統設計考慮:

  • Logging Interval
  • Retention Policy
  • Database Backup
  • Archive
  • Cleanup
  • Index

七十、記錄頻率應該設多少?

不是越快越好。

例如室內溫度:

每 10 毫秒

沒有太大意義。

常見參考:

環境監測記錄頻率建議
用途 建議 Interval
Demo 2~10 秒
一般室內監測 30~60 秒
長期環境監測 1~5 分鐘
非快速變化環境 5~15 分鐘

如果:

LOG_INTERVAL = 60

代表每分鐘一筆。

通常已經非常適合一般環境監測。

七十一、避免儲存完全相同的資料

更進階的設計可以判斷:

Temperature Change
Humidity Change

例如只有:

Temperature 改變 >= 0.2°C

才新增資料。

或者:

每 5 分鐘至少記一筆
+
有重大變化立即記錄

如此可以兼顧:

歷史完整性
+
Database Size

七十二、什麼是 Index?

如果 Database 變大,常常需要快速依時間查詢。

可以建立:

CREATE INDEX IF NOT EXISTS
idx_environment_timestamp
ON environment_data(timestamp);

如此大量資料環境中:

WHERE timestamp ...

或:

ORDER BY timestamp

可能獲得更好的查詢效率。

對本章幾百、幾千筆實驗資料不是必須,但這是未來系統擴充的重要 Database 概念。

七十三、完整初始化函式可以加入 Index

例如:

cursor.execute(
    """
    CREATE INDEX IF NOT EXISTS
    idx_environment_timestamp
    ON environment_data(timestamp)
    """
)

因此 Database 初始化包含:

CREATE TABLE
+
CREATE INDEX

七十四、SQLite Transaction

如果每次只寫一筆:

connect
INSERT
commit
close

非常容易理解。

如果一次匯入大量資料,可以使用:

Transaction

將多筆 INSERT 一次提交,提高效率。

例如:

connection = sqlite3.connect(
    DB_FILE
)

cursor = connection.cursor()


for item in sensor_data:

    cursor.execute(
        SQL,
        item
    )


connection.commit()

connection.close()

不要每筆都重新開啟 Database。

七十五、SQLite Context Manager

Python 還可以使用:

with sqlite3.connect(
    DB_FILE
) as connection:

例如:

def save_sensor_data(
    temperature,
    humidity
):

    timestamp = datetime.now().strftime(
        "%Y-%m-%d %H:%M:%S"
    )


    with sqlite3.connect(
        DB_FILE
    ) as connection:

        connection.execute(
            """
            INSERT INTO environment_data
            (
                timestamp,
                temperature,
                humidity
            )
            VALUES
            (
                ?,
                ?,
                ?
            )
            """,
            (
                timestamp,
                temperature,
                humidity
            )
        )

這種寫法更適合後續整理大型專案。

七十六、NULL Sensor Data 不應亂寫入

如果 DHT Reading 失敗:

temperature = None

不要直接把:

NULL

當成有效測量紀錄寫進 Database。

本章已使用:

if (
    temperature is not None
    and
    humidity is not None
):

確認兩個值都有效才:

INSERT

這是 Data Quality 很重要的一環。

七十七、Sensor Error 可以另建 Event Table

進階系統可以建立:

CREATE TABLE system_events
(
    id INTEGER
        PRIMARY KEY
        AUTOINCREMENT,

    timestamp TEXT
        NOT NULL,

    event_type TEXT,

    message TEXT
);

例如:

2026-08-18 09:20:00
SENSOR_ERROR
DHT timeout

或:

2026-08-18 09:30:00
FAN_ON
Temperature reached 30.2 C

因此 Database 不只是:

Sensor Database

也可以成為:

System Event Log。

七十八、Database 與 Log File 的差別

文字 Log:

2026-08-18 08:45 Temp=28.5 Humidity=67.2
2026-08-18 08:46 Temp=28.6 Humidity=67.1

很適合:

Debug
System Log

Database:

timestamp
temperature
humidity

則更加適合:

搜尋
排序
篩選
統計
Web Dashboard
報表
CSV Export

完整系統可以同時使用兩者。

七十九、備份 SQLite Database

因為 SQLite Database 是檔案,所以最簡單的備份可以是:

cp environment.db \
environment_backup.db

也可以加入時間:

cp environment.db \
environment_$(date +%Y%m%d_%H%M%S).db

例如:

environment_20260818_084500.db

八十、不要只依賴 microSD

如果 Raspberry Pi 長期運作,而且資料非常重要,不應只依賴單一 microSD Card。

可以考慮:

Raspberry Pi
   │
   ├── Local SQLite
   │
   └── Backup
         │
         ▼
      NAS / Server

或:

SQLite
  ↓
定期 CSV Export
  ↓
Remote Storage

第 20 章可以先以本機 SQLite 為主。

正式長期系統則需要額外 Backup Strategy。

八十一、如何搭配 MQTT?

可以把 Sensor Worker 設計成:

Read DHT22
   │
   ├── SQLite INSERT
   │
   └── MQTT Publish

概念:

temperature = read_temperature()

humidity = read_humidity()


save_sensor_data(
    temperature,
    humidity
)


mqtt_client.publish(
    TEMP_TOPIC,
    temperature
)

這樣一次測量就同時提供:

Live Data
+
History Data

八十二、如何搭配 Flask?

Flask 首頁可以執行:

SELECT
    timestamp,
    temperature,
    humidity
FROM environment_data
ORDER BY id DESC
LIMIT 1;

取得最新資料。

歷史頁可以:

SELECT *
FROM environment_data
ORDER BY timestamp DESC
LIMIT 100;

統計頁:

SELECT
    MIN(temperature),
    MAX(temperature),
    AVG(temperature)
FROM environment_data;

因此 Flask 很自然就變成:

SQLite Database Frontend。

八十三、第 20 章最終架構已經形成

目前第 16~19 章:

第 16 章
Camera
Picamera2

        │

第 17 章
Flask
Web Server

        │

第 18 章
MQTT
Mosquitto

        │

第 19 章
SQLite
Database

現在可以合併:

Raspberry Pi
                      │
       ┌──────────────┼──────────────┐
       │              │              │
     Sensor         Camera          GPIO
       │              │              │
       ▼              ▼              ▼
    DHT22         Picamera2     LED/Relay
       │              │              │
       └──────────────┼──────────────┘
                      │
                    Python
                      │
       ┌──────────────┼──────────────┐
       │              │              │
       ▼              ▼              ▼
     Flask           MQTT          SQLite
       │              │              │
       ▼              ▼              ▼
     Web          IoT Message   History Data

這就是第 20 章:

Raspberry Pi 智慧環境監控中心

的完整軟體骨架。

八十四、本章完整資料流程

Sensor Reading:

DHT22
  │
  ▼
GPIO4
  │
  ▼
Python
  │
  ▼
temperature
humidity

加入時間:

datetime.now()
      │
      ▼
2026-08-18 08:45:00

寫入:

Timestamp
Temperature
Humidity
      │
      ▼
SQL INSERT
      │
      ▼
SQLite
      │
      ▼
environment.db

查詢:

environment.db
      │
      ▼
SQL SELECT
      │
      ▼
Python
      │
      ▼
Terminal / Flask / CSV

這就是完整:

Sensor Data Logging Pipeline

八十五、本章常用 SQL 整理

建立 Table:

CREATE TABLE IF NOT EXISTS environment_data
(
    id INTEGER
        PRIMARY KEY
        AUTOINCREMENT,

    timestamp TEXT
        NOT NULL,

    temperature REAL,

    humidity REAL
);

新增:

INSERT INTO environment_data
(
    timestamp,
    temperature,
    humidity
)
VALUES
(
    ?,
    ?,
    ?
);

全部查詢:

SELECT *
FROM environment_data;

最新 10 筆:

SELECT *
FROM environment_data
ORDER BY id DESC
LIMIT 10;

高溫:

SELECT *
FROM environment_data
WHERE temperature >= 30;

平均:

SELECT AVG(temperature)
FROM environment_data;

最高:

SELECT MAX(temperature)
FROM environment_data;

最低:

SELECT MIN(temperature)
FROM environment_data;

總筆數:

SELECT COUNT(*)
FROM environment_data;

八十六、本章常用指令整理

建立專案:

mkdir -p ~/environment_database

進入:

cd ~/environment_database

建立資料庫:

python3 create_database.py

寫入測試資料:

python3 insert_test.py

查詢:

python3 query_data.py

啟動 Sensor Logger:

python sensor_logger.py

查看歷史:

python3 history.py

查看統計:

python3 statistics.py

匯出 CSV:

python3 export_csv.py

安裝 SQLite CLI:

sudo apt update
sudo apt install -y sqlite3

開啟 Database:

sqlite3 environment.db

八十七、本章常見錯誤:no such table

如果出現:

sqlite3.OperationalError:
no such table: environment_data

代表目前開啟的 Database 中沒有:

environment_data

檢查:

Database 路徑是否正確
Table 是否初始化
是否開到了另一個 environment.db

這也是為什麼正式程式應在啟動時執行:

initialize_database()

八十八、常見錯誤:database is locked

如果出現:

database is locked

可能原因:

  • 另一個程式長時間持有 Transaction
  • Connection 沒有正常關閉
  • 多個 Process 同時大量寫入
  • 程式發生 Exception 後未正確 Cleanup

簡單 Sensor Logger 通常不容易遇到。

但應養成:

connection.commit()
connection.close()

或使用:

with sqlite3.connect(...)

的習慣。

八十九、常見錯誤:unable to open database file

如果:

sqlite3.OperationalError:
unable to open database file

常見原因:

目錄不存在
目錄無寫入權限
Database Path 錯誤
Service User 權限不足

先:

ls -ld ~/environment_database

再:

ls -l ~/environment_database

確認檔案與目錄權限。

九十、常見錯誤:資料一直沒有增加

先確認 Sensor:

是否正常讀到 Temperature / Humidity

再確認:

save_sensor_data()

是否真的執行。

最後確認:

connection.commit()

是否存在。

可以先加入:

print(
    temperature,
    humidity
)

確認資料流。

九十一、Database Debug 建議順序

出問題時不要一次檢查全部系統。

建議:

1. Sensor Test
      ↓
2. SQLite CREATE
      ↓
3. 手動 INSERT
      ↓
4. SELECT
      ↓
5. Sensor Logger
      ↓
6. Flask
      ↓
7. MQTT

也就是:

分層測試。

這會比直接 Debug 完整 IoT 系統容易很多。

九十二、實作成果

本章完成:

Raspberry Pi 溫溼度歷史資料庫

具備:

  1. Raspberry Pi 讀取 DHT11 / DHT22
  2. GPIO4 接收 Sensor Data
  3. Python 建立 Timestamp
  4. SQLite 建立 Database
  5. 自動建立 Table
  6. 定期 INSERT 溫度
  7. 定期 INSERT 濕度
  8. SELECT 查詢歷史資料
  9. 查詢最新資料
  10. 查詢高溫紀錄
  11. MAX / MIN / AVG 統計
  12. COUNT 計算資料筆數
  13. CSV 匯出
  14. Database Backup 基本概念
  15. 為 Flask Dashboard 提供資料
  16. 為第 20 章完整 IoT 專題提供 History Layer

九十三、本章重點整理

本章最核心的觀念是:

Sensor Data
不只需要即時傳送
還需要長期保存

MQTT 解決:

資料如何即時傳送

SQLite 解決:

資料如何長期保存與查詢

完整架構:

Sensor
   │
   ▼
Python
   │
   ├────► MQTT
   │       │
   │       ▼
   │    Real-Time
   │
   └────► SQLite
           │
           ▼
        Historical

這是一個非常典型的:

IoT Data Architecture。

九十四、下一章預告

下一章將正式進入本系列最重要的:

第 20 章 Raspberry Pi 綜合專題:智慧環境監控與遠端控制系統

我們會把前面章節全部整合。

包含:

Raspberry Pi
Raspberry Pi OS
Linux
Python
GPIO
DHT11 / DHT22
LCD1602 I²C
LED
Buzzer
Relay
Camera
Flask
MQTT
SQLite
SSH
systemd

建立:

Raspberry Pi 智慧環境監控中心

功能包含:

  1. 讀取室內溫度、濕度
  2. LCD 即時顯示
  3. 溫度過高 LED 警示
  4. 蜂鳴器警報
  5. 自動啟動繼電器/風扇
  6. SQLite 記錄歷史資料
  7. Web 顯示目前狀態
  8. 手機遠端控制設備
  9. MQTT 發布感測資料
  10. Camera 遠端拍照
  11. SSH 遠端維護
  12. 開機自動啟動 Service

整體將形成:

Raspberry Pi
                            │
             ┌──────────────┼──────────────┐
             │              │              │
           DHT22          Camera          GPIO
             │              │              │
             ▼              ▼              ▼
        Temp/Humidity    Picamera2    LED/Buzzer/Relay
             │              │              │
             └──────────────┼──────────────┘
                            │
                          Python
                            │
          ┌─────────────────┼─────────────────┐
          │                 │                 │
          ▼                 ▼                 ▼
        Flask             MQTT              SQLite
          │                 │                 │
          ▼                 ▼                 ▼
      Smartphone       IoT Client       History Data

這將真正完成一套:

Raspberry Pi IoT 智慧環境監控系統。

下一步學習

不要停在這一篇,接著把內容做成自己的實作

目前主題:Raspberry Pi。從完整學習路線、免費資源、會員下載,再接到實作所需材料。

同系列:Raspberry-Pi |  已是會員?直接前往 Getfile
相關資源與會員文章下載
文章可免費閱讀;下載完整 DOCX 或 PDF 需登入免費基本會員。
查看相關資源 會員免費下載 DOCX 會員免費 PDF/列印

← 回文章列表