---
# System prepended metadata

title: 嵌入式系統

---

# 嵌入式系統
# 設定
要記得調upload speed
![截圖 2025-03-04 凌晨3.43.05](https://hackmd.io/_uploads/HJQc8Ymoyl.png)
![截圖 2025-03-04 凌晨3.43.35](https://hackmd.io/_uploads/rkX9UYmo1e.png)
![截圖 2025-03-04 凌晨3.40.23](https://hackmd.io/_uploads/rk-78FQi1x.jpg)
# 使用
![image](https://hackmd.io/_uploads/r1iUV5Isyg.png)
![image](https://hackmd.io/_uploads/rJrYNc8syx.png)
pin2是板子自己本身的led
# 範例
## 溫濕度與rfid與光敏
光敏電阻要接pin腳26
```cpp
#include "DHT.h"
#define DHTPIN 27 
#define DHTTYPE DHT11 
#include <SPI.h>
#include <MFRC522.h>
// **這一行是關鍵，宣告 dht1 變數**
DHT dht1(DHTPIN, DHTTYPE);	 
String read_id;
MFRC522 rfid(/*SS_PIN*/ 5, /*RST_PIN*/ 22);
void setup()
{
  Serial.begin(115200);
  dht1.begin(); 
  SPI.begin();
  rfid.PCD_Init();
  pinMode(26, INPUT);//光敏
}

String mfrc522_readID()
{
  String ret;
  if (rfid.PICC_IsNewCardPresent() && rfid.PICC_ReadCardSerial())  
   //讀取並確認不是重複卡片
  {
    MFRC522::PICC_Type piccType = rfid.PICC_GetType(rfid.uid.sak);
    for (byte i = 0; i < rfid.uid.size; i++) {
      ret += (rfid.uid.uidByte[i] < 0x10 ? "0" : "");
      ret += String(rfid.uid.uidByte[i], HEX);
    }//轉成16進制儲存
  }
  rfid.PICC_HaltA();
  // Stop encryption on PCD
  rfid.PCD_StopCrypto1();
  return ret;//回傳已完成轉存之id
}


void loop()
{
  read_id = mfrc522_readID(); //呼叫函式取得16進制id
  if (read_id != "") {
    Serial.print("相對溼度,");
    Serial.print(dht1.readHumidity());
    Serial.print(",攝氏溫度,");
    Serial.print(dht1.readTemperature());
    Serial.print(",華氏溫度,");
    Serial.print(dht1.readTemperature(true));
    Serial.print(",RFID,");
    Serial.print(read_id);
    Serial.print(",光敏電阻,");
    Serial.println(analogRead(26));//光敏
  }
  delay(1000);
}

```
## led矩陣
![image](https://hackmd.io/_uploads/rkwqa9Ii1x.png)
如果你只有 1 個 LED 矩陣，可以改成：

```cpp=
#define NBR_MTX 1
```

```cpp=
#include <LedControlMS.h>  // 引入 LedControlMS 函式庫，用於控制 MAX7219 LED 矩陣

#define NBR_MTX 2  // 定義使用的 LED 矩陣數量（這裡是 2 個）

// 初始化 LedControl 物件
// 參數 (DIN, CLK, CS, NBR_MTX)
// DIN  -> GPIO27（數據輸入）
// CLK  -> GPIO26（時鐘信號）
// CS   -> GPIO25（片選信號）
// NBR_MTX -> 2，表示有 2 個 LED 矩陣模組
LedControl lc = LedControl(27, 26, 25, NBR_MTX);

int digitCounter = 0;  // 計數變數（目前沒用到）
unsigned long delaytime = 300;  // 延遲時間（目前沒用到）

void setup() {
  Serial.begin(115200);  // 設定序列埠通訊速率為 115200，方便 debug
  Serial.println("Setup");  // 印出 "Setup"，表示程式開始運行

  digitCounter = 0;  // 設定計數變數為 0

  // 迴圈設定每個 LED 矩陣
  for (int i = 0; i < NBR_MTX; i++) {
    lc.shutdown(i, false);  // 取消 MAX7219 省電模式，讓 LED 矩陣開啟
    lc.setIntensity(i, 5);  // 設定 LED 亮度（範圍 0~15，這裡設定為 5）
    lc.clearDisplay(i);  // 清除 LED 矩陣的畫面
  }
}

void loop() { 
  // LED 矩陣 (0) 的四個角落 LED 依序開啟
  Serial.println("LED0: 0 0");
  lc.setLed(0, 0, 0, true);  // 開啟 第 1 個 LED 矩陣 的 (0,0)
  delay(1000);

  Serial.println("LED0: 0 7");
  lc.setLed(0, 0, 7, true);  // 開啟 第 1 個 LED 矩陣 的 (0,7)
  delay(1000);

  Serial.println("LED0: 7 0");
  lc.setLed(0, 7, 0, true);  // 開啟 第 1 個 LED 矩陣 的 (7,0)
  delay(1000);

  Serial.println("LED0: 7 7");  
  lc.setLed(0, 7, 7, true);  // 開啟 第 1 個 LED 矩陣 的 (7,7)
  delay(1000);

  // LED 矩陣 (0) 的四個角落 LED 依序關閉
  Serial.println("LED0: 0 0 off");
  lc.setLed(0, 0, 0, false);  // 關閉 第 1 個 LED 矩陣 的 (0,0)
  delay(1000);

  Serial.println("LED0: 0 7 off");
  lc.setLed(0, 0, 7, false);  // 關閉 第 1 個 LED 矩陣 的 (0,7)
  delay(1000);

  Serial.println("LED0: 7 0 off");
  lc.setLed(0, 7, 0, false);  // 關閉 第 1 個 LED 矩陣 的 (7,0)
  delay(1000);

  Serial.println("LED0: 7 7 off");  
  lc.setLed(0, 7, 7, false);  // 關閉 第 1 個 LED 矩陣 的 (7,7)
  delay(1000);  
}
```
# 資料庫
下載xampp管理資料庫
![image](https://hackmd.io/_uploads/H1SnqRGTyl.png)
**windows**
![image](https://hackmd.io/_uploads/SyjBjRzTJe.png =400x)
![image](https://hackmd.io/_uploads/S1VFs0GTJl.png =400x)
![image](https://hackmd.io/_uploads/r1HooRMakg.png)
![image](https://hackmd.io/_uploads/rJW_30zT1l.png =400x)


**mac**
接著開啟他的程式
![image](https://hackmd.io/_uploads/Hyv0cCMTJl.png)
開啟MySQL跟Apache
![image](https://hackmd.io/_uploads/HyFlhAG61l.png)

到網頁輸入localhost
![image](https://hackmd.io/_uploads/SJTCj0GT1e.png)
看到這個代表成功
![image](https://hackmd.io/_uploads/SkPB30zTJg.png)
輸入這個進入圖形管理頁面
```
http://localhost/phpmyadmin/
```
![截圖 2025-03-27 晚上10.11.35](https://hackmd.io/_uploads/rJJCnAf61g.jpg)
新增使用者
![截圖 2025-03-27 晚上10.13.35](https://hackmd.io/_uploads/H1K76Cfp1g.png)
![image](https://hackmd.io/_uploads/SyHiaRzaJl.png)

主機名稱記得要改 並且要勾權限那些東西
![image](https://hackmd.io/_uploads/rJiwTRGpJl.png)

然後到python
```python=
pip install pymysql
```
**測試是否連上**
```python=
import pymysql

# 資料庫連接的參數設定
db = pymysql.connect(host="localhost", user="testt",
                     password="test", db="testt")

# 連接上資料庫後，設定游標
cursor = db.cursor()

# 輸入 SQL 語法指令
cursor.execute("SELECT VERSION()")

# 如該指令有回傳值能用此接收回傳
data = cursor.fetchone()

# 顯示回傳的資訊
print("Database version : %s " % data)

# 關閉連接
db.close()

```

**建立新的表格**
```python=
import pymysql

# 資料庫連接的參數設定
db = pymysql.connect(host="localhost", user="testt",
                     password="test", db="testt")

# 連接上資料庫後，設定游標
cursor = db.cursor()

# 輸入 SQL 語法指令：如果有名叫 ID 的資料表就刪除它
cursor.execute("DROP TABLE IF EXISTS ID")

# 用變數的方式儲存指令，此為建立一個表格，有兩個欄位
sql = """CREATE TABLE ID(
             NAME CHAR(20) NOT NULL,
             ID CHAR(50))"""

# 將變數儲存的指令送出
cursor.execute(sql)

# 用變數的方式儲存指令，建立第二個表格
sql2 = """CREATE TABLE TEST(
             fname CHAR(50) NOT NULL,
             lname CHAR(50),
             age INT(50),
             sex CHAR(50),
             income INT(50))"""

cursor.execute(sql2)


print("創建完成")

# 關閉連接
db.close()

```
預覽結果
![image](https://hackmd.io/_uploads/S1N1JymTkl.png)

**資料寫入**
```python=
import pymysql

# 資料庫連接的參數設定
db = pymysql.connect(host="localhost", user="testt",
                     password="test", db="testt")

# 連接上資料庫後，設定游標
cursor = db.cursor()

# 用變數的方式儲存指令，對 TEST 的特定欄位輸入資料
sql01 = """INSERT INTO TEST (fname, lname, age, sex, income)
           VALUES('Ming', 'Fong', 29, 'man', 999999)"""

try:
    cursor.execute(sql01)
    db.commit()
    print("DONE")
except:
    db.rollback()
    print("ERROR")

# 關閉連接
db.close()

```

## arduino作業
arduino作業
```cpp
#include "DHT.h"
#define DHTPIN 27 
#define DHTTYPE DHT11 
#include <SPI.h>
#include <MFRC522.h>
// **這一行是關鍵，宣告 dht1 變數**
DHT dht1(DHTPIN, DHTTYPE);	 
String read_id;
MFRC522 rfid(/*SS_PIN*/ 5, /*RST_PIN*/ 22);
void setup()
{
  Serial.begin(115200);
  dht1.begin(); 
  SPI.begin();
  rfid.PCD_Init();
}

String mfrc522_readID()
{
  String ret;
  if (rfid.PICC_IsNewCardPresent() && rfid.PICC_ReadCardSerial())  
   //讀取並確認不是重複卡片
  {
    MFRC522::PICC_Type piccType = rfid.PICC_GetType(rfid.uid.sak);
    for (byte i = 0; i < rfid.uid.size; i++) {
      ret += (rfid.uid.uidByte[i] < 0x10 ? "0" : "");
      ret += String(rfid.uid.uidByte[i], HEX);
    }//轉成16進制儲存
  }
  rfid.PICC_HaltA();
  // Stop encryption on PCD
  rfid.PCD_StopCrypto1();
  return ret;//回傳已完成轉存之id
}


void loop()
{
  read_id = mfrc522_readID(); //呼叫函式取得16進制id
  if (read_id != "") {
    Serial.print("相對溼度,");
    Serial.print(dht1.readHumidity());
    Serial.print(",攝氏溫度,");
    Serial.print(dht1.readTemperature());
    Serial.print(",華氏溫度,");
    Serial.print(dht1.readTemperature(true));
    Serial.print(",RFID,");
    Serial.println(read_id); 
  }
  delay(1000);
}
```

建立
```python
import pymysql

# 資料庫連接的參數設定
db = pymysql.connect(host="localhost", user="testt", password="test", database="testt")
cursor1 = db.cursor()

# 建立 dht 資料表（如果不存在）
create_table_query = """
CREATE TABLE IF NOT EXISTS dht (
    id INT AUTO_INCREMENT PRIMARY KEY,
    Celsius VARCHAR(10),
    Fahrenheit VARCHAR(10),
    Humidity VARCHAR(10),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
"""
cursor1.execute(create_table_query)
print("資料表 dht 檢查完成，若不存在則已建立。")

# 插入資料的範例
Celsius_data = "29.00"
Fahrenheit_data = "84.20"
Humidity_data = "60.50"

# 使用參數化 SQL 插入資料
insert_query = "INSERT INTO dht (Celsius, Fahrenheit, Humidity) VALUES (%s, %s, %s)"
cursor1.execute(insert_query, (Celsius_data, Fahrenheit_data, Humidity_data))
db.commit()

insert_query = "ALTER TABLE dht MODIFY COLUMN Celsius VARCHAR(20);"
cursor1.execute(insert_query)

print("資料已成功插入！")

cursor1.execute("ALTER TABLE dht ADD COLUMN Rfid VARCHAR(20);")
db.commit()

# 關閉資料庫連接
cursor1.close()
db.close()

```
讀取板子資料並存到資料庫
```python=
import sys
import pymysql
import serial
import time
import re

COM_PORT = '/dev/cu.usbserial-140'
BAUD_RATES = 115200
ser = serial.Serial(COM_PORT, BAUD_RATES)

Celsius_data = ''
Fahrenheit_data = ''
Humidity_data = ''

# host='127.0.0.1'
# host = '192.168.43.246'

try:
    db = pymysql.connect(host="localhost", user="testt", password="test", database="testt")
    cursor1 = db.cursor()
    
    while True:
        while ser.in_waiting:
            try:
                msg = ser.readline().decode()
                if (msg==""):
                    continue
                else:
                    print("msg:{}".format(msg))
                    msg_data=msg.split(',')

                    # if '相對溼度' in str(msg_data[2]) and '華氏溫度' in str(msg_data[4]):
                    if ("攝氏溫度" in str(msg_data[2])) and ("華氏溫度" in str(msg_data[4])) \
                    and ("相對溼度" in str(msg_data[0]) and ("RFID" in str(msg_data[6]))):
                        Celsius_data = msg_data[1]
                        Fahrenheit_data = msg_data[3]
                        Humidity_data = msg_data[5]
                        Rfid = msg_data[7].replace("\r\n", "")

                        cursor1.execute("INSERT INTO dht (Celsius, Fahrenheit, Humidity, Rfid) \
                                        VALUES ('%s', '%s', '%s', '%s')" % (Celsius_data, Fahrenheit_data, Humidity_data, Rfid))

                        db.commit()
                    else:
                        continue
                    print("完成上傳")
                    time.sleep(1)

            except OSError as er:
                # 如果發生錯誤，回報錯誤訊息
                db.rollback()
                db.close()
                print(er)

except KeyboardInterrupt:
    ser.close()
    db.close()
    print('closed!')
```
# php
到這個路徑
```
/Applications/XAMPP/htdocs/
```
![image](https://hackmd.io/_uploads/B1TZfiKxxe.png)
可以添加php檔案或直接編輯index.php
upload.php
```php
<?php
$host = "localhost";
$db = "nodeRedTest";
$user = "testt"; // 根據你的設定
$pass = "test";  // 密碼

// 連接資料庫
$conn = new mysqli($host, $user, $pass, $db);
if ($conn->connect_error) {
    die("資料庫連線失敗: " . $conn->connect_error);
}

// 查詢正確欄位名稱
$sql = "SELECT id, Celsius, Humidity, created_at FROM dht ORDER BY created_at ASC";
$result = $conn->query($sql);

// 判斷是否為 AJAX/程式存取（回傳 JSON），或一般網頁存取（顯示表格）
$is_json = isset($_GET['json']);

if ($is_json) {
    $data = [];
    while ($row = $result->fetch_assoc()) {
        $data[] = $row;
    }
    header('Content-Type: application/json');
    echo json_encode($data);
} else {
    // ✅ 加上 HTML 標頭與自動刷新
    echo "<!DOCTYPE html>";
    echo "<html><head><meta charset='UTF-8'>";
    echo "<meta http-equiv='refresh' content='5'>"; // ← 每 5 秒刷新
    echo "<title>溫溼度資料</title></head><body>";
    
    echo "<h2>溫溼度資料列表（每 5 秒自動更新）</h2>";
    echo "<table border='1' cellpadding='5'><tr><th>ID</th><th>溫度 (°C)</th><th>濕度 (%)</th><th>時間</th></tr>";
    while ($row = $result->fetch_assoc()) {
        echo "<tr>
                <td>{$row['id']}</td>
                <td>{$row['Celsius']}</td>
                <td>{$row['Humidity']}</td>
                <td>{$row['created_at']}</td>
              </tr>";
    }
    echo "</table>";
    echo "</body></html>"; // ✅ 補上 HTML 結尾
}

$conn->close();
?>
```
到網頁開啟127.0.0.1/upload.php
![image](https://hackmd.io/_uploads/BkV6MsKxeg.png)

# python gui
## 範例
老師的範例程式
按三個扭之後 可以回傳指定的資料

**解釋：**
**SerialWrite(b'a') 之中 `b'a'` 是什麼？**
是 Python 的 **bytes 物件**，代表「字元 `a` 的 ASCII 位元組」：

python
```python
import serial
import tkinter
from time import sleep

# 設定串口 要記得看接口有沒有一樣
ser = serial.Serial('/dev/cu.usbserial-1120', 115200, timeout=2)
condition = False
temparature = 0

# 串口發送指令
def SerialWrite(command):
    ser.write(command)                  # 發送指令到 Arduino
    rv = ser.readline()                 # 讀取 Arduino 回應
    data = rv.decode('utf-8').strip()   # 轉換為字串並去除換行符號
    print(data)                         # 在終端機輸出資料
    sleep(1)                            # 暫停 1 秒
    ser.flushInput()                    # 清除輸入緩衝區
    return data                         # 返回資料
# 發送 'a' 指令（開啟 LED）
def SendCmdA():
    global condition
    condition = False                     # 停止持續讀取
    SerialWrite(b'a')                     # 發送 'a' 指令 b'a' 是什麼？這是 Python 的 bytes 物件，代表「字元 a 的 ASCII 位元組」：
    LabelA.config(text='已發送指令 "a" 至 Arduino')  # 更新介面顯示文字
    LabelA.update_idletasks()            # 立即更新 UI

# 發送 'b' 指令（關閉 LED）
def SendCmdB():
    global condition
    condition = False                     # 停止持續讀取
    SerialWrite(b'b')                     # 發送 'b' 指令
    LabelA.config(text='已發送指令 "b" 至 Arduino')  # 更新介面顯示文字
    LabelA.update_idletasks()            # 立即更新 UI

# 發送 'c' 指令（讀取 DHT11 溫濕度感測器數據）
def SendCmdC():
    global condition
    condition = True                      # 開啟持續讀取模式
    SerialWrite(b'c')                     # 發送 'c' 指令

    while condition:
        rv1 = ser.readline()              # 讀取 Arduino 回應
        data = rv1.decode('utf-8', errors='ignore').strip()  # 轉換為字串並忽略錯誤
        print(data)                       # 在終端機輸出資料

        # 先用空白分開濕度與溫度
        humidity_str, temperature_str = data.split()
        # 去掉單位並轉換為 float
        tmp_humidity = float(humidity_str.strip('%'))
        tmp_temperature = float(temperature_str.strip('C'))
        print(temparature)
        if tmp_temperature > temparature :
            LabelA.config(text="超過溫度")          # 更新介面顯示文字
            LabelA.update_idletasks()        # 立即更新 
        else: 
            LabelA.config(text=data)          # 更新介面顯示文字
            LabelA.update_idletasks()        # 立即更新 UI
        if rv1 != "" :
            break

# 連接 Arduino
def Serial_Connect():
    print('正在連接 Arduino..........')
    LabelA.config(text='正在連接 Arduino..........')  # 更新介面顯示文字
    LabelA.update_idletasks()
    sleep(1)  # 暫停 1 秒

    for i in range(10):  # 嘗試 10 次連線
        rv1 = ser.readline()                     # 讀取 Arduino 回應
        data = rv1.decode('utf-8').strip()       # 轉換為字串
        print('載入中...')

        if data.startswith("Ready"):             # 如果 Arduino 回應 "Ready"
            print('Arduino 已準備就緒！')
            LabelA.config(text='Arduino 已準備就緒！')
            buttonStart.config(state='disable')  # 停用連接按鈕！防止重複連接
            break

        sleep(1)  # 暫停 1 秒後繼續嘗試

def insertEntry():
    global temparature
    var=entry.get()
    temparature=float(var)
    LabelA.config(text=var)  # 更新介面顯示文字
    LabelA.update_idletasks()            # 立即更新 UI
    

# 退出程式
def Exit():
    print('退出程式...')
    LabelA.config(text='退出程式...')           # 更新介面顯示文字
    LabelA.update_idletasks()
    sleep(1)                                     # 暫停 1 秒
    SerialWrite(b'\x1b')                         # 發送 ESC 碼（0x1B）來通知 Arduino 停止
    ser.close()                                  # 關閉串口連接
    Tkwindow.destroy()                           # 關閉 Tkinter 視窗
# 創建 Tkinter 視窗
Tkwindow = tkinter.Tk()
Tkwindow.title('Python 與 Arduino 通訊')     # 設定視窗標題
Tkwindow.minsize(600, 400)                   # 設定視窗最小大小

# 顯示 Arduino 狀態的標籤
LabelA = tkinter.Label(Tkwindow, bg='white', fg='black', text='請按「連接」開始', width=30, height=10)
LabelA.pack(side=tkinter.TOP)

#輸入框
entrytext=tkinter.StringVar()#打包他的字
entrytext.set('insert tempature')

entry=tkinter.Entry(master = Tkwindow)
entry.pack()

buttoEntry=tkinter.Button(master=Tkwindow,textvariable=entrytext,width=15,height=2,command=insertEntry)
buttoEntry.pack()

# LED 開啟按鈕
buttonA = tkinter.Button(Tkwindow, text='LED ON', width=10, command=SendCmdA)
buttonA.pack(side=tkinter.LEFT)

# LED 關閉按鈕
buttonB = tkinter.Button(Tkwindow, text='LED OFF', width=10, command=SendCmdB)
buttonB.pack(side=tkinter.LEFT)

# 讀取 DHT11 溫濕度數據按鈕
buttonC = tkinter.Button(Tkwindow, text='顯示溫濕度', width=10, command=SendCmdC)
buttonC.pack(side=tkinter.LEFT)

# 連接 Arduino 按鈕
buttonStart = tkinter.Button(Tkwindow, text='連接', width=10, command=Serial_Connect)
buttonStart.pack(side=tkinter.RIGHT)

# 退出按鈕
buttonEnd = tkinter.Button(Tkwindow, text='退出', width=10, command=Exit)
buttonEnd.pack(side=tkinter.RIGHT)


# 開始 Tkinter 事件迴圈
Tkwindow.mainloop()
```
arduino
```cpp
#include "DHT.h"

#define DHTPIN 27
#define DHTTYPE DHT11
#define Led 2

String Str01 = "";
DHT dht1(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(115200);    // 初始化 Serial 串口通訊
  dht1.begin();            // 初始化 DHT 感測器
  Serial.println("Ready"); // 回傳訊號給 Python 程式知道「準備完成」
  pinMode(Led, OUTPUT);    // 設定 LED 腳位為輸出
  digitalWrite(Led, LOW);  // 預設 LED 關閉
}
void loop() {
  if (Serial.available()) {
    Str01 = "";
    delay(10);

    while (Serial.available()) {
      char c = Serial.read();
      if (c == '\n') break; // 遇到換行符號就停止
      Str01 += c;
    }

    Serial.println(Str01); // 印出收到的指令
  }

  // a 指令 → 開 LED 0.5 秒、關 0.5 秒
  if (Str01 == "a") {
    digitalWrite(Led, HIGH);
    delay(500);
    digitalWrite(Led, LOW);
    delay(500);
  }

  // b 指令 → 關 LED
  if (Str01 == "b") {
    digitalWrite(Led, LOW);
  }

  // c 指令 → 讀取 DHT11 濕度與溫度，並透過 Serial 傳出
  if (Str01 == "c") {
    float humidity = dht1.readHumidity();
    float temperature = dht1.readTemperature();

    if (isnan(humidity) || isnan(temperature)) {
      Serial.println("Failed to read from DHT sensor!");
    } else {
      Serial.print(humidity);
      Serial.print("% ");
      Serial.print(temperature);
      Serial.println("C");
    }
  }
}
```

## 作業：要逼卡才可以控制燈
應該不是因為多線程才對 而是把無限迴圈的bug修復才對
arduino
```cpp
#include "DHT.h"
#include <SPI.h>
#include <MFRC522.h>

#define DHTPIN 27
#define DHTTYPE DHT11
#define Led 2

// RFID 模組接腳定義
#define SS_PIN 5
#define RST_PIN 22

String Str01 = "";
DHT dht1(DHTPIN, DHTTYPE);

// RFID 初始化
MFRC522 rfid(SS_PIN, RST_PIN);
bool rfidEnabled = false;

// 讀取 RFID 卡片 UID
String mfrc522_readID()
{
  String ret = "";
  // 檢查是否有新卡片
  if (rfid.PICC_IsNewCardPresent() && rfid.PICC_ReadCardSerial())
  {
    Serial.println("檢測到卡片!");
    // 獲取卡片類型
    MFRC522::PICC_Type piccType = rfid.PICC_GetType(rfid.uid.sak);
    
    // 讀取 UID
    for (byte i = 0; i < rfid.uid.size; i++) {
      ret += (rfid.uid.uidByte[i] < 0x10 ? "0" : "");
      ret += String(rfid.uid.uidByte[i], HEX);
    }
    
    // 停止讀卡
    rfid.PICC_HaltA();
    rfid.PCD_StopCrypto1();
    
    Serial.print("CARD:");
    Serial.println(ret);
  }
  return ret;
}

void setup(){
  // 初始化串口
  Serial.begin(115200);
  delay(1000); // 等待串口穩定
  
  // 初始化 DHT11
  dht1.begin();
  
  // 初始化 LED
  pinMode(Led, OUTPUT);
  digitalWrite(Led, LOW);
  
  // 初始化 SPI 和 RFID
  SPI.begin();
  rfid.PCD_Init();
  
  Serial.println("STATUS:Ready");
  Serial.println("初始化完成，等待命令...");
}

void loop(){
  // 讀取串口命令
  if (Serial.available()){
    Str01 = "";
    delay(10);
    while (Serial.available()){
      char c = Serial.read();
      if(c == '\n' || c == '\r') break;
      Str01 += c;
    }
    
    // 打印收到的命令
    Serial.print("收到命令: ");
    Serial.println(Str01);
    
    // 處理命令
    processCommand(Str01);
  }
  
  // 如果 RFID 讀取已啟用
  if(rfidEnabled) {
    String id = mfrc522_readID(); // 讀取 RFID 卡片
    if (id != "") {
      Serial.print("卡片 ID: ");
      Serial.println(id);
      // RFID 讀取後自動關閉
      rfidEnabled = false;
    }
    delay(100); // 短暫延遲，避免過度頻繁讀取
  }
}

// 處理串口命令
void processCommand(String command) {
  if(command == "a"){
    digitalWrite(Led, HIGH);
    Serial.println("LED:ON");
  }
  else if(command == "b"){
    digitalWrite(Led, LOW);
    Serial.println("LED:OFF");
  }
  else if(command == "c"){
    readDHT();
  }
  else if(command == "d"){
    Serial.println("啟動 RFID 讀取...");
    rfidEnabled = true; // 啟用 RFID 讀取
  }
  else {
    Serial.println("未知命令");
  }
}

// 讀取 DHT11 溫濕度
void readDHT() {
  float humidity = dht1.readHumidity();
  float temperature = dht1.readTemperature();

  if(isnan(humidity) || isnan(temperature)){
    Serial.println("DHT:Error");
  }
  else{
    Serial.print("DHT:");
    Serial.print(humidity);
    Serial.print(",");
    Serial.println(temperature);
  }
}
```
python
```python
import serial
import tkinter
from time import sleep
import threading
import queue

# 創建數據隊列，用於線程間通信
data_queue = queue.Queue()

# 設定串口
SERIAL_PORT = '/dev/cu.usbserial-140'  # 修正串口名稱
ser = None
connect_status = False
condition = False
authorized = False  # 新增 RFID 授權狀態追踪

# 串口讀取線程
def serial_reader():
    global ser, connect_status
    while connect_status:
        try:
            if ser and ser.is_open and ser.in_waiting:
                data = ser.readline().decode('utf-8', errors='ignore').strip()
                if data:
                    print(f"收到數據: {data}")
                    data_queue.put(data)
            sleep(0.1)  # 短暫休眠，避免CPU使用率過高
        except Exception as e:
            print(f"讀取錯誤: {e}")
            sleep(1)

# 處理數據隊列
def process_queue():
    try:
        while not data_queue.empty():
            data = data_queue.get_nowait()
            process_response(data)
    except Exception as e:
        print(f"處理數據錯誤: {e}")
    finally:
        # 每100毫秒檢查一次隊列
        Tkwindow.after(100, process_queue)

# 串口發送指令
def SerialWrite(command):
    global ser
    if not ser or not ser.is_open:
        print("串口未連接")
        return None
        
    try:
        ser.write(command)  # 發送指令到 Arduino
        print(f"發送命令: {command}")
        return True
    except serial.SerialException as e:
        print(f"串口通信錯誤: {e}")
        return None

# 處理 Arduino 回應
def process_response(data):
    global authorized
    
    if data.startswith("STATUS:"):
        status = data.split(":")[1]
        print(f"收到狀態: {status}")
        if status == "Authorized":
            authorized = True
            buttonA.config(state='normal')
            buttonB.config(state='normal')
            LabelA.config(text="卡片驗證成功！可以控制 LED")
        elif status == "Unauthorized":
            authorized = False
            buttonA.config(state='disabled')
            buttonB.config(state='disabled')
            LabelA.config(text="卡片未授權！")
        elif status == "Need_Auth":
            LabelA.config(text="請先刷授權卡片！")
        elif status == "Ready":
            LabelA.config(text="Arduino 已就緒！")
            buttonC.config(state='normal')  # 啟用溫濕度按鈕
            buttonD.config(state='normal')  # 啟用 RFID 按鈕
    
    elif data.startswith("LED:"):
        status = data.split(":")[1]
        LabelA.config(text=f"LED 狀態：{status}")
    
    elif data.startswith("DHT:"):
        print("收到溫濕度數據")
        if data == "DHT:Error":
            print("溫濕度讀取錯誤")
            LabelA.config(text="溫濕度感測器讀取錯誤！")
        else:
            try:
                values = data.split(":")[1].split(",")
                humidity = float(values[0])
                temperature = float(values[1])
                print(f"溫度: {temperature}°C, 濕度: {humidity}%")
                LabelA.config(text=f"濕度: {humidity}%\n溫度: {temperature}°C")
            except Exception as e:
                print(f"解析溫濕度數據錯誤: {e}")
                print(f"原始數據: {data}")
                LabelA.config(text="數據格式錯誤！")
    
    elif data.startswith("CARD:"):
        card_uid = data.split(":")[1]
        print(f"讀取到卡片 UID: {card_uid}")
        
        # 檢查卡片 UID，如果是 228a11d2，則啟用 LED 控制按鈕
        if card_uid.lower() == "228a11d2":
            print("授權卡片！啟用 LED 控制")
            authorized = True
            buttonA.config(state='normal')
            buttonB.config(state='normal')
            LabelA.config(text=f"授權卡片 {card_uid}！\n可以控制 LED")
        else:
            print("非授權卡片")
            authorized = False
            buttonA.config(state='disabled')
            buttonB.config(state='disabled')
            LabelA.config(text=f"非授權卡片: {card_uid}")
    
    # 處理 Arduino 發送的普通消息
    elif "卡片 ID:" in data and "228a11d2" in data.lower():
        print("接收到授權卡片信息")
        authorized = True
        buttonA.config(state='normal')
        buttonB.config(state='normal')
        LabelA.config(text="授權卡片！可以控制 LED")
    
    # 更新界面
    LabelA.update()

# 發送 'a' 指令（開啟 LED）
def SendCmdA():
    global condition
    condition = False  # 停止持續讀取
    SerialWrite(b'a\n')  # 發送 'a' 指令

# 發送 'b' 指令（關閉 LED）
def SendCmdB():
    global condition
    condition = False  # 停止持續讀取
    SerialWrite(b'b\n')  # 發送 'b' 指令

# 發送 'c' 指令（讀取 DHT11 溫濕度感測器數據）
def SendCmdC():
    global condition, ser
    if not ser or not ser.is_open:
        LabelA.config(text="串口未連接！")
        return
        
    SerialWrite(b'c\n')  # 發送 'c' 指令，添加換行符
    LabelA.config(text="正在讀取溫濕度...")

# 發送 'd' 指令（啟用 RFID 檢測）
def SendCmdD():
    global condition, ser
    if not ser or not ser.is_open:
        LabelA.config(text="串口未連接！")
        return
        
    SerialWrite(b'd\n')  # 發送 'd' 指令，添加換行符
    LabelA.config(text="正在檢測 RFID...")

# 連接 Arduino
def Serial_Connect():
    global ser, connect_status
    print('正在連接 Arduino..........')
    LabelA.config(text='正在連接 Arduino..........')
    LabelA.update_idletasks()
    
    # 如果串口已經打開，先關閉
    if ser and ser.is_open:
        connect_status = False
        sleep(1)  # 給讀取線程時間退出
        ser.close()
        sleep(1)  # 等待串口完全關閉
    
    try:
        ser = serial.Serial(SERIAL_PORT, 115200, timeout=0.5)
        print("串口打開成功")
        # 啟動讀取線程
        connect_status = True
        threading.Thread(target=serial_reader, daemon=True).start()
        
        # 初始化按鈕狀態
        buttonA.config(state='disabled')
        buttonB.config(state='disabled')
        buttonC.config(state='normal')
        buttonD.config(state='normal')
        buttonStart.config(state='disabled')
        
        # 發送初始命令
        SerialWrite(b'\n')
        LabelA.config(text='Arduino 已連接！等待狀態...')
        return True
        
    except serial.SerialException as e:
        print(f"串口連接錯誤: {e}")
        LabelA.config(text='串口連接失敗！請檢查設備連接')
        return False

# 退出程式
def Exit():
    global ser, connect_status
    print('退出程式....')
    LabelA.config(text='退出程式...')
    LabelA.update_idletasks()
    
    # 停止讀取線程
    connect_status = False
    sleep(1)
    
    try:
        if ser and ser.is_open:
            SerialWrite(b'\x1b\n')  # 發送 ESC 碼 (0x1B) 來通知 Arduino 停止
            ser.close()
    except serial.SerialException as e:
        print(f"關閉串口時發生錯誤: {e}")
    finally:
        Tkwindow.destroy()

# 創建 Tkinter 視窗
Tkwindow = tkinter.Tk()
Tkwindow.title('Python 與 Arduino 通訊')
Tkwindow.minsize(600, 400)

# 顯示 Arduino 狀態的標籤
LabelA = tkinter.Label(Tkwindow, bg='white', fg='black', text='請按 "連接" 開始', width=30, height=10)
LabelA.pack(side=tkinter.TOP)

# 建立框架排列按鈕
button_frame = tkinter.Frame(Tkwindow)
button_frame.pack(side=tkinter.BOTTOM, fill=tkinter.X, padx=10, pady=10)

left_frame = tkinter.Frame(button_frame)
left_frame.pack(side=tkinter.LEFT)

right_frame = tkinter.Frame(button_frame)
right_frame.pack(side=tkinter.RIGHT)

# LED 開啟按鈕（初始為禁用狀態）
buttonA = tkinter.Button(left_frame, text='LED ON', width=10, command=SendCmdA, state='disabled')
buttonA.pack(side=tkinter.LEFT, padx=5)

# LED 關閉按鈕（初始為禁用狀態）
buttonB = tkinter.Button(left_frame, text='LED OFF', width=10, command=SendCmdB, state='disabled')
buttonB.pack(side=tkinter.LEFT, padx=5)

# 讀取 DHT11 溫濕度按鈕
buttonC = tkinter.Button(left_frame, text='顯示溫濕度', width=12, command=SendCmdC, state='disabled')
buttonC.pack(side=tkinter.LEFT, padx=5)

# RFID 檢測按鈕
buttonD = tkinter.Button(left_frame, text='RFID', width=10, command=SendCmdD)
buttonD.pack(side=tkinter.LEFT, padx=5)

# 連接 Arduino 按鈕
buttonStart = tkinter.Button(right_frame, text='連接', width=10, command=Serial_Connect)
buttonStart.pack(side=tkinter.RIGHT, padx=5)

# 退出按鈕
buttonEnd = tkinter.Button(right_frame, text='退出', width=10, command=Exit)
buttonEnd.pack(side=tkinter.RIGHT, padx=5)

# 啟動數據處理
Tkwindow.after(100, process_queue)

# 開始 Tkinter 事件迴圈
Tkwindow.mainloop()
```

## 作業：設定溫度之後 超過溫度要上傳資料庫
python
```python
import serial
import tkinter
import pymysql
from time import sleep

# 設定串口 要記得看接口有沒有一樣
ser = serial.Serial('/dev/cu.usbserial-1140', 115200, timeout=2)
condition = False
temparature = 0
try:
    db = pymysql.connect(host="localhost", user="testt", password="test", database="testt")
    cursor1 = db.cursor()
except Exception as e:
    print("資料庫連線失敗！", e)
    exit(1)  # 如果資料庫連線失敗就直接中止程式

# 串口發送指令
def SerialWrite(command):
    ser.write(command)                  # 發送指令到 Arduino
    rv = ser.readline()                 # 讀取 Arduino 回應
    data = rv.decode('utf-8').strip()   # 轉換為字串並去除換行符號
    print(data)                         # 在終端機輸出資料
    sleep(1)                            # 暫停 1 秒
    ser.flushInput()                    # 清除輸入緩衝區
    return data                         # 返回資料
# 發送 'a' 指令（開啟 LED）
def SendCmdA():
    global condition
    condition = False                     # 停止持續讀取
    SerialWrite(b'a')                     # 發送 'a' 指令 b'a' 是什麼？這是 Python 的 bytes 物件，代表「字元 a 的 ASCII 位元組」：
    LabelA.config(text='已發送指令 "a" 至 Arduino')  # 更新介面顯示文字
    LabelA.update_idletasks()            # 立即更新 UI

# 發送 'b' 指令（關閉 LED）
def SendCmdB():
    global condition
    condition = False                     # 停止持續讀取
    SerialWrite(b'b')                     # 發送 'b' 指令
    LabelA.config(text='已發送指令 "b" 至 Arduino')  # 更新介面顯示文字
    LabelA.update_idletasks()            # 立即更新 UI

# 發送 'c' 指令（讀取 DHT11 溫濕度感測器數據）
def SendCmdC():
    global condition
    condition = True                      # 開啟持續讀取模式
    SerialWrite(b'c')                     # 發送 'c' 指令

    while condition:
        rv1 = ser.readline()              # 讀取 Arduino 回應
        data = rv1.decode('utf-8', errors='ignore').strip()  # 轉換為字串並忽略錯誤
        print(data)                       # 在終端機輸出資料

        # 先用空白分開濕度與溫度
        humidity_str, temperature_str = data.split()
        # 去掉單位並轉換為 float
        tmp_humidity = float(humidity_str.strip('%'))
        tmp_temperature = float(temperature_str.strip('C'))
        print(temparature)
        if tmp_temperature > temparature :
            LabelA.config(text="超過溫度")          # 更新介面顯示文字
            LabelA.update_idletasks()        # 立即更新
            cursor1.execute("INSERT INTO temparature (Temparature) \
                                        VALUES ('%d')" % (tmp_temperature))
            db.commit()
        else: 
            LabelA.config(text=data)          # 更新介面顯示文字
            LabelA.update_idletasks()        # 立即更新 UI
        if rv1 != "" :
            break

# 連接 Arduino
def Serial_Connect():
    print('正在連接 Arduino..........')
    LabelA.config(text='正在連接 Arduino..........')  # 更新介面顯示文字
    LabelA.update_idletasks()
    sleep(1)  # 暫停 1 秒

    for i in range(10):  # 嘗試 10 次連線
        rv1 = ser.readline()                     # 讀取 Arduino 回應
        data = rv1.decode('utf-8').strip()       # 轉換為字串
        print('載入中...')

        if data.startswith("Ready"):             # 如果 Arduino 回應 "Ready"
            print('Arduino 已準備就緒！')
            LabelA.config(text='Arduino 已準備就緒！')
            buttonStart.config(state='disable')  # 停用連接按鈕！防止重複連接
            break

        sleep(1)  # 暫停 1 秒後繼續嘗試

def insertEntry():
    global temparature
    var=entry.get()
    temparature=float(var)
    LabelA.config(text=var)  # 更新介面顯示文字
    LabelA.update_idletasks()            # 立即更新 UI
    

# 退出程式
def Exit():
    print('退出程式...')
    LabelA.config(text='退出程式...')           # 更新介面顯示文字
    LabelA.update_idletasks()
    sleep(1)                                     # 暫停 1 秒
    SerialWrite(b'\x1b')                         # 發送 ESC 碼（0x1B）來通知 Arduino 停止
    ser.close()                                  # 關閉串口連接
    Tkwindow.destroy()                           # 關閉 Tkinter 視窗
# 創建 Tkinter 視窗
Tkwindow = tkinter.Tk()
Tkwindow.title('Python 與 Arduino 通訊')     # 設定視窗標題
Tkwindow.minsize(600, 400)                   # 設定視窗最小大小

# 顯示 Arduino 狀態的標籤
LabelA = tkinter.Label(Tkwindow, bg='white', fg='black', text='請按「連接」開始', width=30, height=10)
LabelA.pack(side=tkinter.TOP)

#輸入框
entrytext=tkinter.StringVar()#打包他的字
entrytext.set('insert tempature')

entry=tkinter.Entry(master = Tkwindow)
entry.pack()

buttoEntry=tkinter.Button(master=Tkwindow,textvariable=entrytext,width=15,height=2,command=insertEntry)
buttoEntry.pack()

# LED 開啟按鈕
buttonA = tkinter.Button(Tkwindow, text='LED ON', width=10, command=SendCmdA)
buttonA.pack(side=tkinter.LEFT)

# LED 關閉按鈕
buttonB = tkinter.Button(Tkwindow, text='LED OFF', width=10, command=SendCmdB)
buttonB.pack(side=tkinter.LEFT)

# 讀取 DHT11 溫濕度數據按鈕
buttonC = tkinter.Button(Tkwindow, text='顯示溫濕度', width=10, command=SendCmdC)
buttonC.pack(side=tkinter.LEFT)

# 連接 Arduino 按鈕
buttonStart = tkinter.Button(Tkwindow, text='連接', width=10, command=Serial_Connect)
buttonStart.pack(side=tkinter.RIGHT)

# 退出按鈕
buttonEnd = tkinter.Button(Tkwindow, text='退出', width=10, command=Exit)
buttonEnd.pack(side=tkinter.RIGHT)


# 開始 Tkinter 事件迴圈
Tkwindow.mainloop()
```
arduino
```cpp
#include "DHT.h"

#define DHTPIN 27
#define DHTTYPE DHT11
#define Led 2

String Str01 = "";
DHT dht1(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(115200);    // 初始化 Serial 串口通訊
  dht1.begin();            // 初始化 DHT 感測器
  Serial.println("Ready"); // 回傳訊號給 Python 程式知道「準備完成」
  pinMode(Led, OUTPUT);    // 設定 LED 腳位為輸出
  digitalWrite(Led, LOW);  // 預設 LED 關閉
}
void loop() {
  if (Serial.available()) {
    Str01 = "";
    delay(10);

    while (Serial.available()) {
      char c = Serial.read();
      if (c == '\n') break; // 遇到換行符號就停止
      Str01 += c;
    }

    Serial.println(Str01); // 印出收到的指令
  }

  // a 指令 → 開 LED 0.5 秒、關 0.5 秒
  if (Str01 == "a") {
    digitalWrite(Led, HIGH);
    delay(500);
    digitalWrite(Led, LOW);
    delay(500);
  }

  // b 指令 → 關 LED
  if (Str01 == "b") {
    digitalWrite(Led, LOW);
  }

  // c 指令 → 讀取 DHT11 濕度與溫度，並透過 Serial 傳出
  if (Str01 == "c") {
    float humidity = dht1.readHumidity();
    float temperature = dht1.readTemperature();

    if (isnan(humidity) || isnan(temperature)) {
      Serial.println("Failed to read from DHT sensor!");
    } else {
      Serial.print(humidity);
      Serial.print("% ");
      Serial.print(temperature);
      Serial.println("C");
    }
  }
}

```
創建資料庫
```python
import pymysql

# 資料庫連接的參數設定
db = pymysql.connect(host="localhost", user="testt", password="test", database="testt")
cursor1 = db.cursor()

# 建立  資料表（如果不存在）
create_table_query = """
CREATE TABLE IF NOT EXISTS temparature (
    id INT AUTO_INCREMENT PRIMARY KEY,
    Temparature VARCHAR(10),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
"""
cursor1.execute(create_table_query)
print("資料表檢查完成，若不存在則已建立。")

# 關閉資料庫連接
cursor1.close()
db.close()
```

# django api
![IMG_3603](https://hackmd.io/_uploads/SkD4QEeege.jpg =350x)
![IMG_3605](https://hackmd.io/_uploads/Sy5dVVlegl.jpg =400x)

## api請求
在python3.10虛擬環境之下
```bash
pip install django==3.2 #一定要3.2版
pip install PyMySQL
```
```bash
django-admin startproject chumpower_api
cd chumpower_api
python manage.py startapp gps

brew install mysql
```
到`chumpower_api/chumpower_api`
打開`settings.py`貼上gps
![截圖 2025-04-24 上午11.35.33](https://hackmd.io/_uploads/S1ws-4Dklx.png)
執行變更
```
python manage.py makemigrations #確認
python manage.py migrate #執行更動
```
到`chumpower_api/urls.py`新增
```python
from django.contrib import admin
from django.urls import path
from gps import views

urlpatterns = [
    path('admin/', admin.site.urls),
    #（新增）
    path('G000/', views.G000, name='G000'),
    path('G001/', views.G001, name='G001'),
]
```
到`chumpower_api/gps/views.py`新增
```python
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
import pymysql, json, cv2
import numpy as np

# 圖片轉檔用
def convertToBinaryData(filename):
    pass  # TODO: 這裡的內容未補上

# 開始新增需要的功能：建立資料表
@csrf_exempt
def G000(request):
    # 判斷是否使用 GET 方法才處理
    if request.method == "GET":
        # 資料庫建立語法
        sql = (
            "CREATE TABLE GPS (ID Int AUTO_INCREMENT,"
            "Longitude FLOAT,"
            "Latitude FLOAT,"
            "Map BLOB,"
            "PRIMARY KEY (ID))"
        )

        try:
            # 資料庫連線
            db = pymysql.connect(
                host="127.0.0.1",
                user="testt",
                password="test",
                database="chumpower_gps"
            )

            # 使用 cursor() 方法建立一個游標對象 cursor
            cursor = db.cursor()

            # 使用 execute() 方法執行 SQL，如果資料表存在則刪除
            cursor.execute("DROP TABLE IF EXISTS GPS")

            # 執行建立資料表語法
            cursor.execute(sql)

            # 提交變更
            db.commit()
            print("創建完成")
            status = "OK"

        except:
            # 資料庫回傳錯誤訊息
            db.rollback()
            print("創建失敗")
            status = "ERROR"

        # 關閉資料庫連線
        db.close()

        # 回傳狀態
        return HttpResponse(status)
```
開啟apache
![image](https://hackmd.io/_uploads/SkyfwEDkgx.png)
在chumpower_api
```python
python manage.py runserver
```

另外在自己資料夾開一個python檔去測試有沒有成功抓到網頁的東西並創建資料庫
```python
import requests
import json

# -------------------- API_G000_GET_TEST --------------------

# 輸入要 GET 的 API 路徑
get_rdata = requests.get('http://127.0.0.1:8000/G000/')

# 將 Response 的內容以文字形式輸出
print(get_rdata.text)
```
成功
![image](https://hackmd.io/_uploads/Bk8f5Xeggg.png)
創建資料庫資料
![image](https://hackmd.io/_uploads/HJSP5Qggll.png =300x)

到`chumpower_api/gps/views.py`新增
```python
@csrf_exempt
def G001(request):
    if request.method == "POST":
        try:
            data = json.loads(request.body)
            print(data['Longitude'], data['Latitude'])

            # 使用參數化避免 SQL injection
            sql = "INSERT INTO GPS (Longitude, Latitude) VALUES (%s, %s)"
            values = (data['Longitude'], data['Latitude'])

            # 資料庫連線
            db = pymysql.connect(
                host="127.0.0.1",
                user="testt",
                password="test",
                database="chumpower_GPS"
            )
            cursor = db.cursor()
            cursor.execute(sql, values)
            db.commit()
            print("上傳完成")
            status = "OK"
        except Exception as e:
            db.rollback()
            print("上傳失敗", str(e))
            status = "ERROR"
        finally:
            db.close()

        return HttpResponse(status)
    else:
        return HttpResponse("Only POST allowed", status=405)
```
在chumpower_api
```python
python manage.py runserver
```

另外在自己資料夾開一個python檔去測試有沒有成功上傳資料
```python
import requests

# -------------------- API_G001_POST_TEST --------------------

# 傳送的參數
payload = {
    "Longitude": 125.5,
    "Latitude": 37.5
}

# POST 到 G001 API
r = requests.post("http://127.0.0.1:8000/G001/", json=payload)
# 顯示結果
print("上傳完成，伺服器回應：", r.text)

```
![image](https://hackmd.io/_uploads/SyLscQxglx.png =300x)
![image](https://hackmd.io/_uploads/HJqa97exxe.png)
![截圖 2025-05-01 清晨6.59.21](https://hackmd.io/_uploads/SkQenXexxg.png)
![截圖 2025-05-01 清晨6.59.29](https://hackmd.io/_uploads/ry4W2Qxgeg.png)
![截圖 2025-05-01 清晨6.59.51](https://hackmd.io/_uploads/Byhbh7exgl.png)
![截圖 2025-05-01 清晨6.59.56](https://hackmd.io/_uploads/ryxMhmxxxe.png)
![截圖 2025-05-01 清晨7.00.17](https://hackmd.io/_uploads/HJQzhmgelx.png)
![截圖 2025-05-01 清晨7.00.26](https://hackmd.io/_uploads/HkwM2Xxxle.png)
![截圖 2025-05-01 清晨7.00.32](https://hackmd.io/_uploads/SJjMnmggex.png)
![截圖 2025-05-01 清晨7.00.42](https://hackmd.io/_uploads/SyzQ27llel.png)
![截圖 2025-05-01 清晨7.00.50](https://hackmd.io/_uploads/SkB727exxx.png)
![截圖 2025-05-01 清晨7.00.55](https://hackmd.io/_uploads/rJs73Xxegl.png)
![截圖 2025-05-01 清晨7.01.01](https://hackmd.io/_uploads/HkRmnmleel.png)
![截圖 2025-05-01 清晨7.01.06](https://hackmd.io/_uploads/BJWVh7glxx.png)
![截圖 2025-05-01 清晨7.01.14](https://hackmd.io/_uploads/S1V42Xggxx.png)

## 網頁
### 功能1
```bash
pip install django==3.2
django-admin --version #檢查
pip install PyMySQL
#千萬不要用老師說的sqlclient
```
開啟資料庫
![image](https://hackmd.io/_uploads/HkWsELPJex.png)


![圖片 1](https://hackmd.io/_uploads/Syjt48Dyge.png)
![截圖 2025-04-24 下午2.04.18](https://hackmd.io/_uploads/SkstNUvJel.png =300x)
![截圖 2025-04-24 下午2.04.24](https://hackmd.io/_uploads/HyiFNLP1xg.png =300x)

```bash
mkdir apipython
cd apipython
Django-admin.py startproject apitest
cd apitest
python manage.py startapp app
# app是應用名稱
 ```
 ![image](https://hackmd.io/_uploads/BykbOIPyex.png =150x)
![image](https://hackmd.io/_uploads/BJwWKIPkgl.png =400x)
![image](https://hackmd.io/_uploads/S1A_F8vkxl.png =150x)
![image](https://hackmd.io/_uploads/Skxz9Iwyxe.png =400x)

建立資料庫
![image](https://hackmd.io/_uploads/SJjqsLPyxl.png)
apitest/apitest/settings.py加上應用名稱
![截圖 2025-04-24 下午2.30.32](https://hackmd.io/_uploads/B1Pi58DJle.png =300x)
apitest/apitest/settings.py再加上這行
```python
# DATABASES = { 改掉的地方
#     'default': {
#         'ENGINE': 'django.db.backends.sqlite3',
#         'NAME': BASE_DIR / 'db.sqlite3',
#     }
# }
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'embedding', # 資料庫名稱
        'USER': 'testt', # 使用者
        'PASSWORD': 'test',  # 密碼
        'HOST': '127.0.0.1',  # 使用 127.0.0.1 而不是 localhost
        'PORT': '3306', # mysql 預設port
        'OPTIONS': {
            'charset': 'utf8mb4',
            'use_unicode': True,
            'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
        }
    }
}
```
在apitest/app/models貼上
```python
from django.db import models

class dht(models.Model):
    temperature = models.FloatField()  # 溫度 (°C)
    humidity = models.FloatField()     # 濕度 (%)
    recorded_at = models.DateTimeField(auto_now_add=True)  # 自動紀錄時間

    def __str__(self):
        return f"{self.recorded_at}｜溫度: {self.temperature}°C｜濕度: {self.humidity}%"
```
```
python manage.py makemigrations #確認
python manage.py migrate #執行更動
```
成功
![image](https://hackmd.io/_uploads/BkBRgdvkxe.png =300x)


到apitest/app/views貼上
```python
from django.shortcuts import render, redirect
from .models import dht  # 從models.py載入需要的資料表

# 上傳資料
def sensor_upload(request):
    if request.method == 'POST':
        temp = request.POST.get('temperature')
        hum = request.POST.get('humidity')
        dht.objects.create(temperature=temp, humidity=hum)
        return redirect('sensor_list')
    return render(request, 'sensor_upload.html')
# 查詢資料
def sensor_data(request):
    data = dht.objects.all().order_by('-recorded_at')
    return render(request, 'sensor_data.html', {'data': data})

```
到apitest/apitest/urls.py貼上
```python
from django.contrib import admin
from django.urls import path
from app import views #從view載入所有api功能

urlpatterns = [
    path('admin/', admin.site.urls),
    path('sensor/upload/', views.sensor_upload, name='sensor_upload'), #剛剛view中的
    path('sensor/list/', views.sensor_data, name='sensor_list'),
    path('sensor/data/', views.sensor_data, name='sensor_data'),
    #都是網頁的網址 例如把sensor_data導到http://127.0.0.1:8000/sensor/data/
]
```
到apitest/apitest/template之下新增sensor_data.html與sensor_upload.html
**sensor_data.html**
```html
<!DOCTYPE html>
<html>
<head>
    <title>感測資料紀錄</title>
</head>
<body>
    <h2>感測資料列表</h2>
    <table border="1">
        <tr>
            <th>時間</th>
            <th>溫度 (℃)</th>
            <th>濕度 (%)</th>
        </tr>
        {% for item in data %}
        <tr>
            <td>{{ item.recorded_at }}</td>
            <td>{{ item.temperature }}</td>
            <td>{{ item.humidity }}</td>
        </tr>
        {% endfor %}
    </table>
    <a href="/sensor/upload/">返回上傳表單</a>
</body>
</html>
```
**sensor_upload.html**
```html
<!DOCTYPE html>
<html>
<head>
    <title>上傳感測資料</title>
</head>
<body>
    <h2>輸入感測資料</h2>
    <form method="POST">
        {% csrf_token %}
        溫度 (℃): 
        <input type="number" name="temperature" step="0.1" /><br />
        濕度 (%): 
        <input type="number" name="humidity" step="0.1" /><br />
        <button type="submit">上傳</button>
    </form>
    <a href="/sensor/list/">查看所有資料</a>
</body>
</html>
```

到apitest/之下執行
```bash
python manage.py runserver 127.0.0.1:8000
```
成功
![image](https://hackmd.io/_uploads/B16yd-eexl.png)

在網頁打`http://127.0.0.1:8000`會得到
![image](https://hackmd.io/_uploads/Sk4ttWlell.png)
輸入`http://127.0.0.1:8000/sensor/upload/`進入資料上傳頁面
![image](https://hackmd.io/_uploads/rkEVqZeeeg.png =250x)

輸入`http://127.0.0.1:8000/sensor/data/`進入資料查詢頁面
![image](https://hackmd.io/_uploads/HJhXefggxx.png =350x)
`http://localhost/phpmyadmin/`中可以看到新增的資料
![image](https://hackmd.io/_uploads/B1b0kMeleg.png)

### 功能2
apitest/apitest/settings.py再加上這些
```python
import os
#要在BASE_DIR = ... 下面
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
```
放的位置長這樣
![截圖 2025-05-01 清晨5.30.51](https://hackmd.io/_uploads/HJ92LMelxx.png)

apitest/app/views再加上一些東西
```python
from django.shortcuts import render, redirect
from .models import dht  # 從models.py載入需要的資料表
#（新增）
from django.conf import settings
from django.http import FileResponse, Http404
import os

# 上傳資料
def sensor_upload(request):
    if request.method == 'POST':
        temp = request.POST.get('temperature')
        hum = request.POST.get('humidity')
        dht.objects.create(temperature=temp, humidity=hum)
        return redirect('sensor_list')
    return render(request, 'sensor_upload.html')
# 查詢資料
def sensor_data(request):
    data = dht.objects.all().order_by('-recorded_at')
    return render(request, 'sensor_data.html', {'data': data})
# 圖片下載（新增）
def image_download(request, filename):
    file_path = os.path.join(settings.MEDIA_ROOT, filename)
    if os.path.exists(file_path):
        return FileResponse(open(file_path, 'rb'), as_attachment=True)
    raise Http404("圖片不存在")
# 上傳圖片（新增）
def image_upload(request):
    # 若找不到 media 資料夾，自動建立
    if not os.path.exists(settings.MEDIA_ROOT):
        os.makedirs(settings.MEDIA_ROOT)

    message = ''
    if request.method == 'POST' and request.FILES.get('image'):
        image = request.FILES['image']
        file_path = os.path.join(settings.MEDIA_ROOT, image.name)

        with open(file_path, 'wb+') as destination:
            for chunk in image.chunks():
                destination.write(chunk)

        message = '圖片已成功上傳！'

    # 列出所有檔案名稱
    files = os.listdir(settings.MEDIA_ROOT)
    return render(request, 'image_upload.html', {
        'files': files,
        'message': message
    })
```
apitest/apitest/urls.py再加上一些東西
```python
from django.contrib import admin
from django.urls import path
from app import views #從view載入所有api功能
#（新增）
from django.conf import settings
from django.conf.urls.static import static


urlpatterns = [
    path('admin/', admin.site.urls),
    path('sensor/upload/', views.sensor_upload, name='sensor_upload'), #剛剛view中的,
    path('sensor/list/', views.sensor_data, name='sensor_list'),
    path('sensor/data/', views.sensor_data, name='sensor_data'),
    #（新增）
    path('image/upload/', views.image_upload, name='image_upload'),
    path('image/download/<str:filename>/', views.image_download, name='image_download'),
]

#這行要放在所有程式的底下
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
```

到apitest/之下執行
```bash
python manage.py runserver 127.0.0.1:8000
```
成功
![image](https://hackmd.io/_uploads/HybUufxegg.png)

網頁中輸入`http://127.0.0.1:8000`
![image](https://hackmd.io/_uploads/rJkiaGgege.png)
網頁中輸入`http://127.0.0.1:8000/upload`
可以上傳、下載、查看圖片
![image](https://hackmd.io/_uploads/ByNHpGgxxx.png =500x)
# node red 雲端平台
下載完node之後下載node-red
```bash
sudo npm install -g --unsafe-perm node-red
```
執行node-red 然後到`http://127.0.0.1:1880/`（他給的網址）
```
node-red
```
![image](https://hackmd.io/_uploads/Hy3CYUllll.png)
![image](https://hackmd.io/_uploads/H1lr9Ugegl.png)
dashboard會出錯所以要用terminal裝
```
sudo npm cache clean --force
node-red admin install node-red-dashboard
```
![image](https://hackmd.io/_uploads/HJkK8Dgxeg.png)
![image](https://hackmd.io/_uploads/By-1wwexex.png)
Time
```javascript
msg.topic ="SELECT recorded_at FROM app_dht"
return msg;
```
getvalueT
```javascript
var test = msg.payload;
  test.forEach(function(e,i){        
      msg.payload=e['recorded_at'];});
  return msg;
```
![image](https://hackmd.io/_uploads/rklCz6xgex.png)

![image](https://hackmd.io/_uploads/BJq8Fwgxee.png)
![image](https://hackmd.io/_uploads/B10k7dlglx.png)
![image](https://hackmd.io/_uploads/rJSatvlxee.png)
![image](https://hackmd.io/_uploads/BkHJiwlegg.png)
```javascript
msg.topic ="SELECT id FROM app_dht"      
return msg;
```
```javascript
msg.topic ="SELECT temperature FROM app_dht"
return msg;
```
```javascript
msg.topic ="SELECT humidity FROM app_dht"
return msg;
```
原本的複製過去
![image](https://hackmd.io/_uploads/SkiZnPlxll.png)
多三個function
![image](https://hackmd.io/_uploads/SySng_glxe.png)
functionC
```javascript
var test = msg.payload;
test.forEach(function (e, i) {
    msg.payload = e['temperature'];
});
```
functionF
```javascript
var test = msg.payload;
test.forEach(function (e, i) {
    msg.payload = e['Fahrenheit'];
});
```
functionH
```javascript
var test = msg.payload;
test.forEach(function (e, i) {
    msg.payload = e['humidity'];
});
```
![image](https://hackmd.io/_uploads/SJvQbdxllx.png)

![image](https://hackmd.io/_uploads/Sk-d-_ellg.png)

![image](https://hackmd.io/_uploads/Sy4wzuxggg.png)

# 期末專題報告

### 學術文章 IOT

### 反應力測試：
要先逼rfid還會開始
開始後 led會倒數 321 倒數完之後led會亮 會計時當物體小於一定距離才會 傳資料上去資料庫 並且拍一張使用者的大頭照 然後用node red去呈現排名
- [x] 兩種以上感測元件：rfid、led、距離感測
- [x] 通訊網路：wifi
- [x] Mysql
- [x] api:django
- [x] node-red:呈現資料庫
- [x] cam:讓電腦可以看到畫面

![IMG_4373](https://hackmd.io/_uploads/B1ANPX7Zex.jpg =300x)

- [x] **step1：**
esp32:
led會倒數 321 倒數完之後會計時當物體小於一定距離才會 傳資料上去資料庫
- [x] **step2：**
倒數完傳資料要用wifi
- [x] **step3：**
用django接收並且傳到mysql
並且在拍一張照片
- [x] **step4：**
用node red去呈現排名與照片

#### 使用方法
先到xampp開啟mysql跟apache 
cd到django資料夾api_server 開啟虛擬環境
再run django的server
```bash
source .venv/bin/activate
cd ./game/gameApi
python manage.py runserver 172.20.10.2:8000
```
手機開啟網路讓電腦與esp32連上之後
到終端機開啟node-red
```
node-red
```
然後開啟第一個
* http://127.0.0.1:1880/ranking　➡︎ 整頁 5 秒刷新（高級 UI）
* http://127.0.0.1:1880/ui　　　 ➡︎ Dashboard 版

#### 成果報告
:::spoiler
# 反應時間測試遊戲系統

### 姓名：王冠傑 學號：D1149768

## 1. 動機與目的

### 動機

觀察到在現代社會中，反應速度對於許多場景都至關重要，從運動競技到日常駕駛。本專題的宗旨為：

- 建立一個可量化的反應時間測試系統
- 提供即時的視覺回饋和數據記錄
- 透過遊戲化方式提高使用者參與度

### 目的與辦法

由使用者用磁扣逼rfid作為遊戲的開始，開始後led燈會先提示使用者開始倒數，並且選擇2~4秒的隨機倒數時間。倒數完之後led會亮，表示開始測反應時間。當物體接近距離感測小於一定距離才會增測到，並且把傳資料上去資料庫，同時間用手機拍一張使用者的大頭照作為參賽者的紀錄，除此之外這樣也比較好辨認數據是誰的。最後用node-red去以排行榜去呈現排名以及使用者的大頭照。

## 2. 現有方法與缺點

### 傳統測試方法：

- 簡單的按鈕測試
- 基礎電腦程式

### 現有方法的缺點：

- 缺乏實時數據收集
- 無法提供視覺化回饋
- 數據分析能力有限
- 不有趣使用者參與度低
- 測試場景單一


## 3. 系統架構與情境圖

### 系統架構：

```mermaid
graph TD
    A[硬體感測器] -->|距離數據| B[ESP32 控制器]
    B -->|HTTP POST| C[Django API 服務器]
    C -->|存儲反應數據| D[資料庫]
    C -->|拍攝| E[攝像頭]
    E -->|照片| D
    F[使用者] -->|互動| A
    D -->|數據分析| G[網頁成果展示]
```

### 主要元件：

1. **硬體層**

   - ESP32 微控制器
   - 手機攝像頭系統
   - 超聲波距離感測器
   - led燈
   - rfid偵測器
2. **後端服務**

   - Django REST API
   - 資料庫系統 mysql
   - 影像處理模組 opencv
   - node-red 排行榜呈現


## 4. 成果展示

### 硬體實現





### 數據分析

![image](https://hackmd.io/_uploads/BkRbZY7bgg.png)


### 系統特點：

1. **即時反應測試**

   - 精確的距離感測
   - 毫秒級響應時間記錄
   - 自動影像捕捉
2. **數據收集與分析**

   - 自動化數據存儲
   - 多維度數據分析
   - 視覺化結果呈現
3. **系統優勢**

   - 高精度測量
   - 自動化數據收集
   - 完整的數據記錄
   - 視覺化成果展示
   - 擴展性強

### 實際應用場景：

- 運動員反應能力訓練
- 駕駛員反應測試
- 遊戲玩家反應力評估
- 醫療康復訓練
- 教育研究使用

:::