# 嵌入式系統 # 設定 要記得調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. **系統優勢** - 高精度測量 - 自動化數據收集 - 完整的數據記錄 - 視覺化成果展示 - 擴展性強 ### 實際應用場景: - 運動員反應能力訓練 - 駕駛員反應測試 - 遊戲玩家反應力評估 - 醫療康復訓練 - 教育研究使用 :::