閒來無事-樹莓派控制風扇啟停

2023-06-27 12:01:51

扯淡時間

端午放假,本想註冊個美團眾包騎自行車送外賣體驗一下生活,奈何這幾天北京熱的要死,只能作罷,還是苟在屋裡空調續命吧。
無事幹的時候,想著給我花盆監控升個級,換個電容的土壤檢測(之前的腐蝕了gg了)但是電容的是3v的,esp8266只能檢測1v的,所以買了一個新的esp32-cam,正好帶個攝像頭,間隔5分鐘拍個照片,一天下來還能拼接一個延時攝影的效果,奈何這個板子有點毛病,跑不起來,如下圖(商家也無法解決)

無奈退貨(有大哥知道這個怎麼解決麻煩指導一下小弟),想著下雨天打孩子,閒著也是閒著,這條路走不通了,換個其他的玩玩了。
我的樹莓派有個風扇,成天在轉,給他加一個控制,超出設定的溫度再啟動,最好在給個頁面去控制一下,開搞
還有就是這個樹莓派我買的時候400,我靠這玩意還漲價了,無語了,當時400我都覺得貴,還不如買個微控制器30塊就能玩

思路

  1. 裝置控制-遠端控制啟停
  2. 後臺定時任務-定時記錄溫度,根據設定的溫度控制風扇啟停
  3. 服務處理請求-展示溫度曲線

來個流程圖吧

graph TB subgraph 伺服器 id1[頁面控制指令]-.http請求.->id2{開關控制/最大溫度} id2-.開關控制.->id3[控制風扇] id3-.風扇狀態寫入.->id4 id2-.最大溫度寫入.->id4[資料庫] end subgraph 定時任務 id5[定時任務]<-.讀取資訊.->id4 id5-.定時任務.->id7[控制啟停] end

需要考慮的問題

  1. 當前的實現是用python來寫的,我不想每次重啟裝置後都要ssh上去再使用命令列啟動,問了公司大神給了個解決方案:systemctl,可以實現開機自啟,使用方式如下,想了解更多可以看:https://ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html
    當人為了防止程式掛掉,或者我退出以後停了,使用了nohup讓python指令碼一直在後太執行
  2. 如何控制風扇啟停呢?樹莓派上帶了不少Gpio的介面,但是輸出的電流只能點亮個led帶不動風扇,所以我們需要一個三極體,我買的是s8050NPN的有兩種別買錯了,pnp是接在地線上的,用流程圖畫一個接線圖吧,意思一下得了markdown不知道怎麼畫電路圖

1. 設定方式和使用方法(當前的解決方案)

/etc/systemd/system 增加對應的檔案

fanServer.service

[Unit]
Description=test deamon
After=rc-local.service

[Service]
Type=simple
User=root
Group=root
WorkingDirectory=檔案的路徑
ExecStart=nohup python3 -u processRequest.py > service.log 2>&1 &
Restart=always

[Install]
WantedBy=multi-user.target

fan.service

[Unit]
Description=test deamon
After=rc-local.service

[Service]
Type=simple
User=root
Group=root
WorkingDirectory=檔案所在的路徑
ExecStart=nohup python3 -u index.py > fan.log 2>&1 &
Restart=always

[Install]
WantedBy=multi-user.target

啟動

sudo systemctl start  fan.service
sudo systemctl start  fanServer.service

新增到開機自啟

sudo systemctl enable fan.service
sudo systemctl enable fanServer.service

2. 接線圖

graph LR zheng[正極] zheng-.正極.->yongdianqi yongdianqi[風扇] yongdianqi-.地線.->sanjiguan sanjiguan[三極體] sanjiguan-.地線.->dixian dixian[接地] gpio[gpio]-.高電開/低電關.->sanjiguan

意思一下得了哈,效果看圖片

現在看一下效果吧

圖片名稱 圖片名稱 > 接線的效果圖如圖二

正片乾貨結束無聊的實現時間

全部程式碼地址:https://github.com/dadademo/piFan

裝置控制(網頁端)

裝置控制主要就是:1. 展示資料 2. 傳送裝置控制指令

寫個vue的頁面,用腦子設計一下:開關按鈕得有,溫度設定得有,還得有個圖表,還得加個篩選時間,根據時間來篩選資料。就醬式的吧

程式碼如下(太多了給個連結吧)

https://github.com/dadademo/piFan/blob/main/fanUi/src/components/home.vue

後臺定時任務

後臺定時任務:1. 定時讀取任務,根據設定的溫度決定是否開/關風扇 2. 定時記錄資訊,將資訊存在資料裡面

這些服務本來想用node寫,奈何node操作gpio的庫一直不能下載,只能用python來搞了
注意這個程式碼我是直接複製的,依賴一些我寫的其他的python指令碼,如果想跑起來的話還直接拉全部程式碼去看吧
這裡就是用python啟動一個服務,接收網頁傳送的請求,例如設定溫度,還有啟停的製冷

定時任務python程式碼

這裡就是每隔5分鐘讀取一次溫度,超過了就啟動風扇,沒超過就停止


# 使用內建的signal庫捕獲Ctrl+C
import signal
# 風扇模組
import controlFan
# 退出程式
import sys
# 參照db模組
import db
import time


# 開始時間
beginTimeHour = 7
# 結束時間
endTimeHour = 20

# 每隔5分鐘判斷一次溫度
timeOut = 60*5



# 程式停止
def stop():
    # 停止gpio操作
    controlFan.quit()
    # 退出程式
    sys.exit()

controlFan.init_gpio()

# 監聽Ctr+C
signal.signal(signal.SIGINT, stop)
while True:
    # 0/1 當前風扇狀態
    fanStatus = 0
    # 設定狀態,預設40度
    setTemp = 0
    # 獲取狀態
    lastData = db.getLastData()
    fanStatus =  lastData.get('fanStatus')
    setTemp = lastData.get('setTemp')

    # 獲取當前溫度
    currentTemp = controlFan.read_cpu_temperature()
    # 時間
    currentTime = time.localtime()
    dt = time.strftime('%Y:%m:%d %H:%M:%S', currentTime)
    print(dt)
    # 當前溫度大於設定溫度
    ctrlVal = currentTemp > setTemp
    # 判斷邏輯
    if ctrlVal:
        fanStatus = 1
    else:
        fanStatus = 0
    # 初始化後追加狀態
    db.addInfo(fanStatus, currentTemp, setTemp)
    if ctrlVal:
        # 夜間模式
        if currentTime.tm_hour < beginTimeHour and currentTime.tm_hour > endTimeHour:
            controlFan.set_fan(False)
        else:
            controlFan.set_fan(True)
    else:
        controlFan.set_fan(False)
    time.sleep(timeOut)
伺服器端python的程式碼

這裡就是用python啟動一個服務,接收網頁傳送的請求,例如設定溫度,還有啟停的風扇指令,寫這個程式碼還了解了一下python的資料型別,賺到了
程式碼如下

# 匯入http.server模組
import http.server
import socketserver

# json物件
import json

# 引數解析
from urllib.parse import urlparse, parse_qs, parse_qsl

import db
import controlFan

# 定義埠號
HTTP_PORT = 3001

# 獲取query資訊並轉為obj


def getQureyData(url):
    retObj = {}
    currentQuery = parse_qs(urlparse(url).query)
    for key in currentQuery:
        if len(currentQuery[key]) == 1:
            retObj[key] = currentQuery[key][0]
        else:
            retObj[key] = currentQuery[key]
    return retObj


# 處理相應
class httpResponse(http.server.SimpleHTTPRequestHandler):
    # 處理get請求
    def do_GET(self):
        # 當前存取的連結決定
        currentUrl = urlparse(self.path).path
        print(currentUrl)
        if currentUrl == '/getFan':
            self.send_response(200)
            self.send_header('Content-type', 'application/json')
            self.end_headers()
            # 讀取資訊
            query = getQureyData(self.path)
            jsonInfo = db.getInfo(
                query.get('startTimestamp'), query.get('endTimestamp'), query.get('pageIndex'), query.get('pageSize'), query.get('all'))

            retStr = json.dumps(jsonInfo)
            self.wfile.write(bytes(retStr, "utf8"))

        if currentUrl == '/getLast':
            self.send_response(200)
            self.send_header('Content-type', 'application/json')
            self.end_headers()
            jsonInfo = db.getLastData()
            retStr = json.dumps(jsonInfo)
            self.wfile.write(bytes(retStr, "utf8"))
        return

    # 處理post請求
    def do_POST(self):
        # 獲取POST請求的資料
        currentUrl = urlparse(self.path).path
        if currentUrl == '/setFan':
            self.send_response(200)
            self.send_header('Content-type', 'application/json')
            self.end_headers()
            # 當前資訊
            query = getQureyData(self.path)
            # 獲取最後的資訊
            lastData = db.getLastData()
            fanStatus = int(query.get('fanStatus')
                            or lastData.get('fanStatus'))
            setTemp = int(query.get('setTemp') or lastData.get('setTemp'))
            print(fanStatus, fanStatus == 1)
            # 寫入狀態
            db.addInfo(fanStatus, controlFan.read_cpu_temperature(), setTemp)

            if fanStatus == 1:
                controlFan.set_fan(True)
            else:
                controlFan.set_fan(False)

            self.wfile.write(bytes("{status:200}", "utf8"))
        return


# 啟動http服務
def beginServer():
    with socketserver.TCPServer(("", HTTP_PORT), httpResponse) as httpd:
        print("serving at port", HTTP_PORT)
        httpd.serve_forever()

controlFan.init_gpio()
# 開啟服務
beginServer()

主要邏輯就這麼多,全部的程式碼看git的地址吧,readme裡面也有對應的操作,想網存取的話可以看我上一篇文章,有對應的連結,就不再這裡嗶嗶了