若有興趣土炮一個機房自動發報系統,可參考下方式,當機房過熱/過濕,會自動發送,商家(機房)頻道的LINE訊息。
簡易流程:
一、購買零件材料
1.DHT22 模組 (三腳)
2.杜邦線 (母-母)
3. Arduino Nano 開發板 (已焊好針腳)
4.Mini USB 轉 USB TYPE A (此開發板的輸出為mini USB)
二、連接感測器 > Arduino > 電腦
1. 紅線 3.3V
2. 黑線接地
3. 使用 Arduino Nano D2 接收數位訊號
三、安裝 Arduino IDE
1.需確定有驅動到連接埠 (相容nano開發板可能需安裝 CH340驅動)
2.選擇使用的開發板跟埠
3.範例電腦用NANO相容板與COM3 (依據實際變動)
四、安裝 Arduino 的 Libraries
1.工具 > 管理程式庫
2.輸入找 DTH sensor library 找到後安裝
3.輸入找 Adafruit DHT sensor library 找到後安裝
五、Arduino 運行程式
1.複製下方程式碼 到 Arduino IDE
2.檢查後上傳
3.若上傳出現錯誤,請改選擇一下所使用的開發板晶片
4.開啟連接埠監測視窗
#include <DHT.h>
// 定義 DHT22 感測器型號與連接腳位
#define DHTPIN 2 // DHT22 的數據腳接 Arduino 的 D2 腳位
#define DHTTYPE DHT22 // 感測器型號為 DHT22
// 初始化 DHT22 感測器
DHT dht(DHTPIN, DHTTYPE);
void setup() {
// 設定串列埠速率為 9600
Serial.begin(9600);
// 啟動 DHT22 感測器
dht.begin();
}
void loop() {
// 延遲以確保感測器穩定 (至少 2 秒)
delay(2000);
// 讀取溫度與濕度
float temperature = dht.readTemperature(); // 攝氏溫度
float humidity = dht.readHumidity(); // 濕度百分比
// 確認讀取是否成功
if (isnan(temperature) || isnan(humidity)) {
return;
}
// 只輸出溫度與濕度數值
Serial.print(temperature);
Serial.print(",");
Serial.println(humidity);
}
六、測試連接埠監測視窗是否正常回傳
1.有回傳 溫度 與 濕度數值
2.關閉 Arduino IDE
七、Line Developers 註冊後要加入成為商家 (才會有 200則/月 免費訊息)
Line Developers 網址連結
建立頻道,使用 Messaging API
建立供應商
1.建好頻道後 在 Basic settings 分頁可以看到 Your user ID: 『記下:等等PY程式要填入』
2.在 Messaging API 分頁可以看到 Channel access token (long-lived) 『記下:等等PY程式要填入』
3.其他用QR CODE加入機房群的同仁 user ID 必須過 webhook.site 監聽 LINE user ID
在 Messaging API 分頁可以加入你的 webhook.site 透過訊息傳送記錄,抓取 LINE user ID
關於LINE USER ID-1
關於LINE USER ID-2
關於Webhook.site
八、機房電腦安裝 Python (不想安裝也可以透過打包成exe,在機房電腦上執行)
GUI畫面如下,執行若出現錯誤則可能需加裝缺少python程式庫。
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
import threading
import datetime
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import os
import requests
import time
import os
# 設置目錄
base_path = "C:\\Temperature"
# 檢查目錄是否存在,如果不存在則創建
if not os.path.exists(base_path):
os.makedirs(base_path)
# 設置工作目錄
os.chdir(base_path)
print("目前工作目錄:", os.getcwd())
# LINE Messaging API 設定
LINE_CHANNEL_ACCESS_TOKEN = "輸入自己申請的LINEACCESS_TOKEN"
user_ids = [
"輸入傳送LINE user ID1",
"輸入傳送LINE user ID2",
"輸入傳送LINE user ID3",
"輸入傳送LINE user ID4"
]
# 初始化全域變數
serial_port = None
data_lock = threading.Lock()
data_points = []
is_reading = False
last_alert_time = 0 # 追蹤最後一次發送LINE通知的時間
# GUI 設定
root = tk.Tk()
root.title("串列埠監控工具")
root.option_add("*Font", "max.ttf")
# 實時數據顯示
current_temp = tk.StringVar(value="N/A")
current_humid = tk.StringVar(value="N/A")
# 創建圖表
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.set_xlabel("TIME ( 3600 POINT )")
ax1.set_ylabel("Temperature (°C)", color="red")
ax2.set_ylabel("Humidity (%)", color="blue")
lines_temp, = ax1.plot([], [], "r-", label="Temperature")
lines_humid, = ax2.plot([], [], "b-", label="Humidity")
# 添加畫布到 GUI
canvas = FigureCanvasTkAgg(fig, master=root)
canvas_widget = canvas.get_tk_widget()
canvas_widget.grid(row=5, column=0, columnspan=3, padx=5, pady=5)
# 幫助函式
def scan_ports():
"""掃描可用的COM埠"""
ports = serial.tools.list_ports.comports()
return [port.device for port in ports]
def connect():
"""連接串列埠"""
global serial_port, is_reading
com = com_combobox.get()
baudrate = baudrate_combobox.get()
if not com or not baudrate:
messagebox.showerror("錯誤", "請選擇COM埠與交涉率")
return
try:
serial_port = serial.Serial(com, int(baudrate), timeout=1)
connect_button.config(state=tk.DISABLED)
disconnect_button.config(state=tk.NORMAL)
status_label.config(text=f"已連接到 {com} @ {baudrate}bps", fg="green")
is_reading = True
threading.Thread(target=read_serial, daemon=True).start()
except Exception as e:
messagebox.showerror("錯誤", f"無法連接:{e}")
def disconnect():
"""斷開串列埠"""
global serial_port, is_reading
if serial_port and serial_port.is_open:
serial_port.close()
serial_port = None
is_reading = False
connect_button.config(state=tk.NORMAL)
disconnect_button.config(state=tk.DISABLED)
status_label.config(text="已斷開連接", fg="red")
def read_serial():
"""讀取串列埠資料"""
global serial_port, data_points, is_reading, last_alert_time
while is_reading and serial_port and serial_port.is_open:
try:
line = serial_port.readline().decode().strip()
if line:
temp, humid = map(float, line.split(","))
current_temp.set(f"{temp:.1f} °C")
current_humid.set(f"{humid:.1f} %")
record_data(temp, humid)
with data_lock:
data_points.append((temp, humid))
if len(data_points) > 3600: # 修改為3600個點
data_points.pop(0)
update_plot()
# 檢查過高溫度或濕度,並間隔10分鐘才發送一次LINE
current_time = time.time()
if (temp > 30 or humid > 80) and (current_time - last_alert_time > 600): # 600秒 = 10分鐘
send_line_alert(temp, humid)
last_alert_time = current_time
except Exception as e:
print(f"讀取錯誤:{e}")
break
def record_data(temp, humid):
"""紀錄數據到每日檔案"""
date_str = datetime.date.today().strftime("%Y-%m-%d")
filename = f"{date_str}.txt"
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
with open(filename, "a") as f:
f.write(f"{timestamp}\t{temp}\t{humid}\n")
def update_plot():
"""更新圖表"""
with data_lock:
temps = [point[0] for point in data_points]
humids = [point[1] for point in data_points]
times = list(range(len(data_points)))
lines_temp.set_data(times, temps)
lines_humid.set_data(times, humids)
ax1.set_xlim(max(0, len(times) - 3600), len(times)) # X軸顯示最多3600個點
ax1.set_ylim(min(temps, default=0) - 5, max(temps, default=40) + 5)
ax2.set_ylim(min(humids, default=0) - 5, max(humids, default=100) + 5)
canvas.draw()
def send_line_alert(temp, humid):
"""發送LINE通知"""
message = f"警告!溫度: {temp:.1f}°C, 濕度: {humid:.1f}%"
headers = {
"Authorization": f"Bearer {LINE_CHANNEL_ACCESS_TOKEN}",
"Content-Type": "application/json"
}
for user_id in user_ids:
payload = {
"to": user_id,
"messages": [{"type": "text", "text": message}]
}
try:
response = requests.post("https://api.line.me/v2/bot/message/push", headers=headers, json=payload)
if response.status_code != 200:
print(f"LINE 通知錯誤: {response.status_code}, {response.text}")
except Exception as e:
print(f"LINE 通知錯誤: {e}")
# GUI 元素
com_label = tk.Label(root, text="COM埠:")
com_label.grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
com_combobox = ttk.Combobox(root, values=scan_ports(), state="readonly")
com_combobox.grid(row=0, column=1, padx=5, pady=5)
baudrate_label = tk.Label(root, text="交涉率:")
baudrate_label.grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
baudrate_combobox = ttk.Combobox(root, values=["9600", "115200"], state="readonly")
baudrate_combobox.grid(row=1, column=1, padx=5, pady=5)
baudrate_combobox.set("9600")
connect_button = tk.Button(root, text="連接", command=connect)
connect_button.grid(row=2, column=0, padx=5, pady=5)
disconnect_button = tk.Button(root, text="斷開", command=disconnect, state=tk.DISABLED)
disconnect_button.grid(row=2, column=1, padx=5, pady=5)
status_label = tk.Label(root, text="未連接", fg="red")
status_label.grid(row=3, column=0, columnspan=2, pady=5)
refresh_button = tk.Button(root, text="刷新COM埠", command=lambda: com_combobox.config(values=scan_ports()))
refresh_button.grid(row=0, column=2, padx=5, pady=5)
real_time_label_temp = tk.Label(root, textvariable=current_temp, fg="red")
real_time_label_temp.grid(row=4, column=0, padx=5, pady=5)
real_time_label_humid = tk.Label(root, textvariable=current_humid, fg="blue")
real_time_label_humid.grid(row=4, column=1, padx=5, pady=5)
# 開始 GUI 事件循環
root.mainloop()