程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

身在北方的朋友福音來了。用Python寫了個鍋爐溫控系統,再也不用時刻擔心了。

編輯:Python

1.前言

我家在東北農村,冬天很冷,買了一個鍋爐,需要循環泵的。簡單來說就是鍋爐水熱了之後循環泵自動開啟,然後將熱水輸送走,送到暖氣,熱水抽走,涼水進入鍋爐,溫度降低,循環泵關閉,等待下一次水燒熱。因為需要取暖的房子距離燒鍋爐的地方比較遠,所以需要循環泵,如果距離近的話水燒熱後利用熱水上流冷水回流的原理會自動完成循環。當然目前市場上有這種利用溫度自動控制循環泵開啟關閉的設備:

原理就是有一個熱敏電阻探頭(帶有磁鐵吸附,可以吸附到鍋爐壁上),然後一個繼電器控制的。當溫度達到設定的值後,繼電器開啟,循環泵啟動,循環過後鍋爐壁溫度下降,繼電器關閉,循環泵關閉。

循環泵:

由於我家的循環泵功率較大,小繼電器啟動幾次就燒壞了,所以中間又接入了一層交流接觸器(我的老父親加的)。

這種市面上的設備可以大致解決水循環的問題,但是也有一些細節問題解決不了,例如:當回流管水溫度也達到設定的溫度值後,循環泵就會一直開啟狀態,這時候就需要手動去調節旋鈕溫度(調高),讓循環泵停下來(大功率循環泵很耗電)。又如 當爐子內部煤漸漸燒結束後(東北話叫澇了),鍋爐溫度達不到設定值,這個時候就需要降低旋鈕,讓循環泵啟動,讓這些煤產生的余溫不浪費掉。

由於以上的不足,就需要有人隔幾個小時去調節旋鈕。這部分工作都是我老父親在做,所以我想做一個自動的東西降低一下老父親的工作量。

2.項目結構

我最初的構思是通過手機可以遠程調節溫度,這樣至少不用手動去調節旋鈕了,我半夜起來在被窩裡用手機調節一下就可以了。基於最初的構思設計的結構:

3.硬件搭建

esp01模塊+繼電器模塊,220v轉5v模塊 + 插排 = 聯網插座

esp8226 + 溫度傳感器 + 數碼管 = 實時溫度檢測顯示聯網模塊

我的控制系統都寫在 esp8226中。

溫度傳感器探頭制作:要驅動ds18b20傳感器,需要在數據總線(DQ)與 VDD 引腳之間加入一個4.7k歐姆的電阻(上拉電阻),這個是必須有的,用一個鐵紐扣包裹起來,裡面放了兩個小塊的钕磁鐵,最後用哥倆好膠水灌滿,這樣就形成了一個溫度傳感器探頭,可以吸附在鍋爐壁上:

這一步我犯了一個錯誤,將ds18b20傳感器和上拉電阻都放入到探頭裡面,測溫的時候發現溫度升高以後溫度傳感器讀取溫度失敗,是因為溫度升高導致電阻阻值增大。後來我將其電阻放到尾部就解決了這個問題。

封裝後的探頭:

esp8226 + 數碼管模塊:

殼子是我用3D打印機打印的,裡面放著esp8226模塊。

4.軟件編寫

固件

esp8226 和 esp01 我都是燒錄的micropython固件。

通信

通信開始我想的是用socket實現,簡單弄個HTTP協議進行通信,但是手機上這樣控制是比較麻煩的,沒有好用的軟件,我還要做個web界面,另外socket會阻塞線程。

後來我發現了MQTT這種協議,這是一種針對物聯網的硬件網絡通信協議,可以應對高延遲的網絡環境。簡單介紹一下MQTT協議,首先需要一個服務端(也有叫代{過}{濾}理服務器的,運行在電腦上,這裡我運行在樹莓派上),然後你的所有物聯網硬件設備(如esp8226)都是客戶端,物聯網硬件設備(客戶端)之間不會直接進行通信,它們都是與服務端直接進行通信,硬件設備之間如果需要進行通信是需要發布或者訂閱主題的。例如:如果硬件A 和硬件B 之間需要進行通信,那麼首先它們都需要連接到 服務端,然後 A 發布一個名為topic的主題,如果硬件B要接受A的信息就需要訂閱 topic這個主題,這樣就實現了 A ---> B 的通信,那麼反向通信也是一個原理,B 發布主題,A來訂閱。這個協議還有一個優勢,可以一個主題多個客戶端去訂閱,這樣就能實現多端通信。mqtt協議通信中每個客戶端不知道其它客戶端的存在,他們都是與服務端進行直接通信。

mqtt協議在micropython固件(我不知道安信可默認固件是不是也有)通信有一個很大的問題:LmacRxBlk:1報錯。例如,如果你在開始的時候訂閱了一個主題,然後你使用非阻塞的方法check_msg()通過回調函數處理 訂閱的主題消息,這個時候如果你訂閱的主題發布的次數超過你 check_msg()的次數,那麼就會在micropython固件底層報一個錯誤 LmacRxBlk:1,然後通信中斷,簡單來說就是你訂閱一個主題後進入事件循環,來不及處理訂閱主題的消息,就會導致這個錯誤。這個錯誤官方解釋是tcp buffer資源沒有釋放導致的,因為esp8226可用資源非常有限,但是在mqtt通信中我不可能每次循環後都去釋放連接,然後每次都重新建立連接,並且這個報錯惡心的地方在於,try except 是捕獲不到的,那麼對於處理這個錯誤只能通過設計上來解決了,也就是一開始就保證其訂閱的主題發布的間隔遠小於其循環的間隔,保證每次都能及時的處理訂閱的消息。這個問題我解決了很久,因為try except 捕獲不到,並且不常出現,無法定位,完全不知道為什麼通信會中斷。

代碼核心邏輯編寫

最核心的控制邏輯我都寫在esp8226模塊中,循環采集溫度,然後將溫度作為一個主題發布出來,手機可以訂閱主題就可以實時查看鍋爐溫度了,在循環中有一個設定溫度的變量 ,如果采集的溫度高於 設定溫度 那麼就發布一個 開關主題開啟的主題,如果溫度低於 設定溫度,那麼就發布一個 開關主題關閉,並且每次訂閱 設置主題,用於修改設定溫度變量。

esp01 控制繼電器,每次訂閱 開關主題 就可以了,然後每次再把 開關當前狀態 作為一個主題發布出來。

這個代碼邏輯最開始我是這樣寫的,手機上只需要發布設置溫度主題就可以了,但是也是需要人一段時間用手機去調節一次,還是不能實現自動化,後來我又修改的 esp8226 中的代碼邏輯,復雜了一些。

esp8226 核心邏輯還是和上述一樣,每次檢測溫度高於設定溫度後還會開啟開關,但是啟動後檢測到溫度低於設定溫度不會立刻關閉開關,而是等待檢測溫度低於設定溫度減去一個變量再發布一個開關關閉的主題。這是為了解決 檢測溫度在設定溫度臨界反復跳變,從而導致開關在短時間內反復的開啟關閉,這裡引入了一個減去變量,我將其叫做溫度步長 。這裡就有一個問題,如果鍋爐一直在升溫,那麼就算循環泵一直開啟 ,鍋爐溫度也不會低於設定溫度(回流管溫度已經高於設定溫度了),那麼循環泵就會處於一直開啟狀態,所以這裡我引入了第二個變量:啟動最大時長,每次啟動後我開啟一個計時器,如果計時器時間超過 啟動最大時長,那麼無論此時的溫度是否低於設定溫度,都會關閉循環泵,並且將設定溫度提高,提高溫度為溫度步長加當前溫度,這裡就實現了 自動調節 設定溫度的(升高方向)。還有一個問題:當爐子煤燃燒殆盡的時候,溫度逐漸降低,檢測到的溫度肯定會遠低於設定溫度,循環泵就永遠不會開啟了。所以這裡我引入了第三個變量(回調檢測時間),當esp8226 上電啟動時,啟動一個計時器,如果計時時間 等於回調檢測時間,那麼將設定溫度 調節到當前溫度,用於設定溫度 自動回調,假設回調檢測時間 為 30分鐘,那麼即使設定溫度 高於 檢測溫度,也會實現30分鐘一啟動循環泵,和剩余邏輯實現一個閉環,這樣就能實現 設定溫度,上下自動調節。這裡我還加入了一個變量最低溫度,如果當前溫度低於最低溫度,那麼就算 當前溫度高於設定溫度 也不會啟動循環泵,用於沒燒煤炭的時候,這樣就防止了無論什麼時候都30分鐘啟動一次循環泵,最低溫度 設置成高於環境溫度一些就可以了。

這裡 設定溫度溫度步長啟動最大時長回調檢測時間最低溫度,都可以通過手機端進行設置,我讓esp8226訂閱這些主題用於設置這些變量。

from machine import Pin,reset
import onewire
from ds18x20 import DS18X20
import time
import tm1637
from umqtt.simple import MQTTClient
def main():
client_id = "esp_temperature"
mserver = '192.168.0.99'
#mserver = '192.168.3.200'
#mserver = 'mq.tongxinmao.com'
tm = tm1637.TM1637(clk=Pin(14), dio=Pin(12))
ow=onewire.OneWire(Pin(4))
d = DS18X20(ow)
rom = d.scan()
def sub_callback(topic, msg):
# print((topic, msg))
nonlocal setTemperature
nonlocal lowTemperature
nonlocal startTime
nonlocal scanTime
nonlocal step
nonlocal startTimeTemp
nonlocal scanTimeTemp
nonlocal switchStatus
data = int(msg.decode())
if topic == b'setTemperature':
setTemperature = data
elif topic == b"setLowTemperature":
lowTemperature = data
elif topic == b"setStartTime":
startTime = data
startTimeTemp = startTime
elif topic == b"setScanTime":
scanTime = data*60
scanTimeTemp = scanTime
elif topic == b"setStep":
step = data
elif topic == b"switchWell":
switchStatus = data
else:
print("錯誤")
def publishInfo():
nonlocal client
client.publish("setTemperatureR",str(setTemperature),retain=True)
client.publish("setLowTemperatureR",str(lowTemperature),retain=True)
client.publish("setStartTimeR",str(startTime),retain=True)
client.publish("setScanTimeR",str(int(scanTime/60)),retain=True)
client.publish("setStepR",str(step),retain=True)
client = MQTTClient(client_id, mserver, 0)
client.set_callback(sub_callback)
client.connect()
client.subscribe(b'setTemperature')
client.subscribe(b'setLowTemperature')
client.subscribe(b'setStartTime')
client.subscribe(b'setScanTime')
client.subscribe(b"setStep")
client.subscribe(b"switchWell")
showSet = True
setTemperature = 40
lowTemperature = 40
startTime = 120 #啟動時間
scanTime = 1800 #30分鐘
step = 7
switchStatus = 0 # 0表示關閉,1表示開啟
startTimeTemp = startTime
scanTimeTemp = scanTime
while True:
try:
d.convert_temp()
# 顯示溫度和設定溫度
nowTemperature = d.read_temp(rom[0])
if showSet:
tm.temperature(int(setTemperature))
else:
tm.number(int(nowTemperature*10))
if int(nowTemperature) >= setTemperature and (not switchStatus) and int(nowTemperature) > lowTemperature:#高於設定溫度啟動
client.publish("switch",'1',retain=True)
startTimeTemp = startTime
if (int(nowTemperature) <= setTemperature - step) and switchStatus:
client.publish("switch",'0',retain=True)
if startTimeTemp <= 0: #如果時間超過3分鐘,自動停止,並提高設定溫度
client.publish("switch",'0',retain=True)
setTemperature = int(nowTemperature + step)
startTimeTemp = startTime
if switchStatus:
startTimeTemp -= 1
client.check_msg()
client.publish("temperature",str(round(nowTemperature,2)),retain=True)
publishInfo()
if scanTimeTemp <= 0:
setTemperature = int(nowTemperature - step) #回調降溫
scanTimeTemp = scanTime
scanTimeTemp -= 1
except Exception as e:
reset()
time.sleep(1)
showSet = not showSet

 esp01中代碼:

from machine import Pin
from umqtt.simple import MQTTClient
import time
def main():
def sub_callback(topic, msg):
nonlocal client
nonlocal pin
"""
收到訂閱消息回調
"""
if msg == b'0':
pin.off()
else:
pin.on()
client.publish("switchWell",str(pin.value()),retain=True)
client_id = "switch_id"
mserver = '192.168.0.99'
#mserver = 'mq.tongxinmao.com'
pin = Pin(0,Pin.OUT)
client = MQTTClient(client_id, mserver, 0)
client.set_callback(sub_callback)
client.connect()
client.subscribe(b'switch')
while True:
client.check_msg()
client.publish("switchStatus",str(pin.value()),retain=True)

5.客戶端監控調節軟件

1.手機端:

MQTT Dash:

IoTMQTTPanel:

手機上我還是推薦IoTMQTTPanel,因為將一套面板的配置發布到另一台手機很方便,只需要發布一個主題,然後接收端訂閱一個同名主題就可以把整個面板發布過去。MQTT Dash 也具有這個功能,但是發布過去後會卡死,不知道為什麼。

2.電腦端:

電腦端我還不知道有什麼好用的軟件,所以我用 PyQt5 簡單寫了一個監控的軟件,沒有調節功能,因為當時設備還不是很穩定,所以我一直在監控運行狀態。

6.後記

目前,整個東西已經穩定運行二周了,再也不需要人進行調節了。我反反復復弄了挺長時間,才把所有問題都解決,特別是那個LmacRxBlk:1報錯,花費了我很長時間。從零開始做一個能實際有用的東西還是挺困難的,因為有些問題只能通過實際的環境才能暴露出來,例如上拉電阻升溫後導致阻值變化。還有很多細節我沒提及,例如燒錄固件,3d建模外殼,mosquitto服務在樹莓派上部署,端口轉發,內容太多不便贅述,只能把主要內容和邏輯進行簡單敘述。

關注公眾號:Python源碼

領取完整代碼


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved