實時溫濕度監測系統:Micropython編碼ESP32與DHT22模塊的無線數據傳輸與PC端接收項目

實時溫濕度監測系統

  • 前言
  • 項目目的
  • 項目材料
  • 項目步驟
    • 模擬ESP32接線連接測試
    • 搭建PC端ESP32拷錄環境
    • 對ESP32進行拷錄
    • PC端搭建桌面組件
      • 本地數據接收
      • 桌面小組件部分
  • 實驗總結

前言

人生苦短,我用Python。

由于我在日常工作中經常使用Python,因此在進行該項目時,我首先考慮使用Python進行實現。在搜索電路板編程相關內容時,我發現Micropython是一個非常好的選擇,因為它使用簡單的語法能夠幫助新手快速掌握。因此,我決定使用Micropython來實現該項目。
請添加圖片描述

項目目的

實時監控房間溫度,可以將其用作實時溫濕度查看的桌面插件,也可以將其用作溫濕度監控裝置。

要求ESP32所處房間需要有可連接的wifi。

項目材料

  1. ESP32 wifi 模塊
  2. HDT22 溫濕度傳感器
  3. 母對母接頭(買HDT22會送)

項目步驟

模擬ESP32接線連接測試

可使用我進行模擬的網站進行學習,點擊boot.py再點擊播放鍵即可運行:“Wokwi測試項目”

這個測試網站可以使用“Wokwi-GUEST”開放式wifi進行測試,實際使用中將wifi改為房間中的wifi和密碼即可。
并且該項目的兩個py文件就是我本地拷錄并且運行的代碼,代碼可以實現持續連接wifi和MQTT的功能,并且有呼吸燈和指示燈(這部分實際連接的時候可以注意到),還有一些數據傳輸的部分修飾。

網站的開放式wifi

能夠看到當前的結果就是代碼可以正常實現將溫濕度以及時間數據傳輸到MQTT公共服務端:MQTT開放端口

測試結果

動手實踐時可以按照模擬的方式進行實際連接:

模擬連接

搭建PC端ESP32拷錄環境

安裝tonny并且快速入門可看這個前幾集和課件。
【Python+ESP32 快速上手(持續更新中)【 通俗易懂 】】 https://www.bilibili.com/video/BV1G34y1E7tE/?share_source=copy_web&vd_source=0d6fb1bf666097a8d32dc1f77cf20826

注意事項:

  1. 安裝驅動之后連接ESP32到電腦可能不顯示端口COM,可能是使用的數據線類型過舊,盡量更換數據線進行使用;
  2. Tonny運行的時候可能出現未連接情況,只需要點擊重啟后端,或者拔出等幾秒重新插入即可。

在這里插入圖片描述

對ESP32進行拷錄

  1. 將模擬網站上的兩個代碼拷貝下來,修改TOPIC(盡量是唯一的,因為是公共端口,同時記得修改本地接收代碼里面的信息)以及wifi部分,上傳至ESP32中;
  2. 正確連接HDT22和ESP32;
  3. 給ESP32進行供電,當連接之后藍燈閃爍就是在上傳實時溫濕度,藍燈常亮就是MQTT端口暫時端口,藍燈不亮就是wifi也沒連上;

PC端搭建桌面組件

這部分是主要使用MQTTpython包進行本地數據接收以及tkinter創建桌面組件實現實時展示并且可以繪制折線圖。

本地數據接收

MQTT本地包進行實時數據接收,保存到當前目錄下的data.txt,可以自行修改,同時記得修改桌面組件讀取路徑。

import paho.mqtt.client as mqtt
import json# 當收到連接時的回調函數
def on_connect(client, userdata, flags, rc):print("Connected with result code " + str(rc))# 訂閱主題client.subscribe(topic)# 當接收到消息時的回調函數
def on_message(client, userdata, msg):print("Received message: " + msg.payload.decode())dict = json.loads(msg.payload.decode())# 將消息保存到文件、數據庫等with open("data.txt", "a") as file:file.write('\t'.join([dict["time"].replace("_"," "),str(dict["temp"]),str(dict["humidity"])])+"\n")# MQTT Broker的連接參數
broker = "broker.hivemq.com"
port = 1883  # 端口號
topic = "wokwi-weather"  # 訂閱的主題,記得修改這里
# 創建一個MQTT客戶端
client = mqtt.Client()# 設置回調函數
client.on_connect = on_connect
client.on_message = on_message# 連接到MQTT Broker
client.connect(broker, port, 60)# 開始循環,處理網絡流量和調用回調函數
client.loop_forever()

桌面小組件部分

還在不斷完善,因為也是剛學tkinter幾天沒有太掌握。
在這里插入圖片描述

暫時可以實現實時讀取data數據最后并讀取全部數據繪制折線圖。

import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import pandas as pddef line_plot():# Read the data from the filedata = pd.read_csv('data.txt', sep='\t', header=None, names=['Timestamp', 'Temperature', 'Humidity'])print("Data loaded for plotting.")# Create the figure with a single subplotfig, ax = plt.subplots(figsize=(12, 6))# Plot the temperaturetemperature_line, = ax.plot(data['Timestamp'], data['Temperature'], color='blue', label='Temperature')ax.set_xlabel('Timestamp')ax.set_ylim(20, 40)  # Set the y-axis limits for temperature to 20-40ax.set_ylabel('Temperature (°C)', color='blue')ax.tick_params('y', colors='blue')# Create a twin y-axis for the humidityax2 = ax.twinx()humidity_line, = ax2.plot(data['Timestamp'], data['Humidity'], color='green', label='Humidity')ax2.set_ylabel('Humidity (%)', color='green')ax2.set_ylim(20, 80)  # Set the y-axis limits for humidity to 20-80ax2.tick_params('y', colors='green')# Set the title and gridax.set_title('Temperature and Humidity over Time')ax.grid()# Add a legendlines = [temperature_line, humidity_line]labels = [l.get_label() for l in lines]ax.legend(lines, labels, loc='upper left')# Display 20 evenly spaced x-axis labelsnum_ticks = 20start = 0end = len(data['Timestamp'])tick_locations = [start + i * (end - start) / (num_ticks - 1) for i in range(num_ticks)]# def split_timestamp(ts):#     return "-".join(":".join(ts.split(":")[:-1]).split("-")[:])# tick_locations = tick_locations.apply(split_timestamp)tick_locations = [int(loc) for loc in tick_locations]ax.set_xticks(tick_locations)plt.setp(ax.get_xticklabels(), rotation=30)plt.tight_layout()# Display the plotreturn figclass AutoHideWindow:def __init__(self, root):self.root = rootself.root.geometry("320x130-100+100")self.root.overrideredirect(True)self.root.wm_attributes("-topmost", True)self.root.wm_attributes("-alpha", 0.9)self.is_hidden = Falseself.screen_width = self.root.winfo_screenwidth()self.screen_height = self.root.winfo_screenheight()self.hidden_window = Noneself.line_chart_window = Noneself.line_chart_open = False  # Track if the line chart window is openself.create_main_interface()self.create_line_chart_window()self.root.bind("<Configure>", self.check_position)self.root.bind("<Enter>", self.show_full_window)self.root.bind("<Escape>", self.hide_window)self.root.bind("<Return>", self.show_full_window)self.root.bind("<ButtonPress-1>", self.start_move)self.root.bind("<B1-Motion>", self.on_move)self.x_offset = 0self.y_offset = 0self.update_data()def create_main_interface(self):self.main_frame = ttk.Frame(self.root)self.main_frame.pack(fill=tk.BOTH, expand=True)self.gif_label = tk.Label(self.main_frame)self.gif_label.grid(row=0, column=1, rowspan=4, padx=5, pady=5, sticky=tk.W)self.load_gif("功德加一+(1).gif")self.numbers_label = ttk.Frame(self.main_frame)self.numbers_label.grid(row=0, column=0, rowspan=3, padx=10, pady=10)self.number0_label = tk.Label(self.numbers_label, width=20, height=1, bg='green', fg='white', font="Arial 10 bold", text=" ", relief=tk.FLAT, anchor=tk.W)self.number0_label.grid(column=0, row=0, sticky=tk.E)self.number1_label = tk.Label(self.numbers_label, width=20, height=1, bg='white', fg='black', font="Arial 10", text="溫度:", relief=tk.FLAT, anchor=tk.W)self.number1_label.grid(column=0, row=1, sticky=tk.E, ipady=3)self.number2_label = tk.Label(self.numbers_label, width=20, height=1, bg='white', fg='black', font="Arial 10", text="濕度:", relief=tk.FLAT, anchor=tk.W)self.number2_label.grid(column=0, row=2, sticky=tk.E, ipady=3)self.button = ttk.Button(self.main_frame, text="溫濕度折線圖", command=self.show_line_chart_window)self.button.grid(column=0, row=3, sticky=tk.E)def load_gif(self, path):self.gif = Image.open(path)self.gif_frames = []try:while True:self.gif_frames.append(ImageTk.PhotoImage(self.gif.copy()))self.gif.seek(len(self.gif_frames))except EOFError:passself.current_frame = 0self.update_gif()def update_gif(self):self.gif_label.configure(image=self.gif_frames[self.current_frame])self.current_frame = (self.current_frame + 1) % len(self.gif_frames)self.root.after(100, self.update_gif)def create_line_chart_window(self):x, y = self.root.winfo_x(), self.root.winfo_y()width, height = 10, self.root.winfo_height()self.line_chart_window = tk.Toplevel(self.root)self.line_chart_window.geometry(f"320x500+{x}+{y}")self.line_chart_window.withdraw()# Bind the close event of the window to a method that resets the open statusself.line_chart_window.protocol("WM_DELETE_WINDOW", self.close_line_chart_window)def check_position(self, event=None):if self.is_hidden:returnx, y = self.root.winfo_x(), self.root.winfo_y()width, height = self.root.winfo_width(), self.root.winfo_height()if x <= 0 or x + width >= self.screen_width:self.hide_window()def hide_window(self, event=None):if self.hidden_window or self.is_hidden:returnx, y = self.root.winfo_x(), self.root.winfo_y()width, height = 10, self.root.winfo_height()self.hidden_window = tk.Toplevel(self.root)self.hidden_window.geometry(f"{width}x{height}+{x}+{y}")self.hidden_window.overrideredirect(True)self.hidden_window.bind("<Enter>", self.show_full_window)def show_full_window(self, event=None):if self.hidden_window:self.hidden_window.destroy()self.hidden_window = Noneself.root.deiconify()self.is_hidden = Falsedef show_line_chart_window(self):if self.line_chart_open:self.line_chart_window.deiconify()  # Show existing windowself.create_line_chart(self.line_chart_window)  # Redraw the chartelse:self.create_line_chart(self.line_chart_window)self.line_chart_window.deiconify()self.line_chart_open = True  # Update the open statusdef close_line_chart_window(self):if self.line_chart_open:self.line_chart_window.withdraw()  # Hide the windowself.line_chart_open = False  # Update the open statusdef start_move(self, event):self.x_offset = event.xself.y_offset = event.ydef on_move(self, event):x = self.root.winfo_pointerx() - self.x_offsety = self.root.winfo_pointery() - self.y_offsetself.root.geometry(f"+{x}+{y}")def update_data(self, file="data.txt"):try:with open(file, "r") as file:lines = file.readlines()if lines:last_line = lines[-1]lasttime, temperate0, humi = last_line.split('\t')temperate = temperate0.strip("℃ ")self.number0_label.config(text=f"時間:{' '.join(lasttime.split('_'))}")self.number1_label.config(text=f"溫度:{temperate}℃")self.number2_label.config(text=f"濕度:{humi.strip()}%")except Exception as e:print(f"讀取文件出錯: {e}")self.root.after(10000, self.update_data)def create_line_chart(self, window):fig = line_plot()canvas = FigureCanvasTkAgg(fig, master=window)canvas.draw()canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)if __name__ == "__main__":root = tk.Tk()app = AutoHideWindow(root)root.mainloop()

這兩個代碼要同時運行就可以實現實時接收數據和實時組件展示,只開第一個就可以實時接收數據。

實驗總結

在這里插入圖片描述

是一次很好的學習電路板模塊的小項目,也可作為中學生實踐課程項目。
希望大家多多交流討論啊,本人也是新手,希望有更簡單高效的解決方案。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/43987.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/43987.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/43987.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于java+springboot+vue實現的校園二手書交易平臺(文末源碼+Lw)287

摘 要 信息數據從傳統到當代&#xff0c;是一直在變革當中&#xff0c;突如其來的互聯網讓傳統的信息管理看到了革命性的曙光&#xff0c;因為傳統信息管理從時效性&#xff0c;還是安全性&#xff0c;還是可操作性等各個方面來講&#xff0c;遇到了互聯網時代才發現能補上自…

Vue中v-for和v-if優先級(2、3)

Vue中v-for和v-if優先級&#xff08;2、3&#xff09; Vue2 在Vue2當中,v-for優先級要優于v-if,也就是說&#xff0c;當它倆同時沿用時&#xff0c;v-for先遍歷&#xff0c;v-if再判斷。 Vue2源碼位置 \vue-dev\src\compiler\codegen\index.js export function genElement…

如何構建數據驅動的企業?爬蟲管理平臺是關鍵橋梁嗎?

一、數據驅動時代&#xff1a;為何選擇爬蟲管理平臺&#xff1f; 在信息爆炸的今天&#xff0c;數據驅動已成為企業發展的核心戰略之一。爬蟲管理平臺&#xff0c;作為數據采集的第一站&#xff0c;它的重要性不言而喻。這類平臺通過自動化手段&#xff0c;從互聯網的各個角落…

windows的遠程桌面連接docker

1. Docker容器中運行遠程桌面服務 (RDP)&#xff1a;您的Docker容器需要安裝和運行遠程桌面服務。通常&#xff0c;遠程桌面服務在Windows操作系統上可用。如果您使用的是Linux容器&#xff0c;則需要安裝一個支持RDP協議的桌面環境和RDP服務器。 2. 開放RDP端口&#xff1a;通…

什么是RPC?有哪些RPC框架?

定義 RPC&#xff08;Remote Procedure Call&#xff0c;遠程過程調用&#xff09;是一種允許運行在一臺計算機上的程序調用另一臺計算機上子程序的技術。這種技術屏蔽了底層的網絡通信細節&#xff0c;使得程序間的遠程通信如同本地調用一樣簡單。RPC機制使得開發者能夠構建分…

【常見開源庫的二次開發】一文學懂CJSON

簡介&#xff1a; JSON&#xff08;JavaScript Object Notation&#xff09;是一種輕量級的數據交換格式。它基于JavaScript的一個子集&#xff0c;但是JSON是獨立于語言的&#xff0c;這意味著盡管JSON是由JavaScript語法衍生出來的&#xff0c;它可以被任何編程語言讀取和生成…

Django 實現子模版繼承父模板

背景 Django的占位符&#xff0c;如果不繼承父模板的內容&#xff0c;會被子模版所覆蓋&#xff0c;有些業務場景子模版也需要使用到父模板中的內容 可以使用Django自帶的標簽{% block super %}來實現此效果 base.html 最基礎html&#xff0c;相當于第一層html&#xff0c;bl…

代碼隨想錄算法訓練營day76 | Floyd 算法精講、A * 算法精講

本次題目來自于卡碼網 ??97. 小明逛公園 &#xff08;Floyd 算法精講&#xff09; 1、確定dp數組以及下標的含義 grid[i][j][k] m&#xff0c;表示 節點i 到 節點j 以[1...k] 集合為中間節點的最短距離為m 2、確定遞推公式 分兩種情況&#xff1a; 節點i 到 節點j 的最…

01 | 基礎架構:一條SQL查詢語句是如何執行的?

此系列文章為極客時間課程《MySQL 實戰 45 講》的學習筆記&#xff01; 引言 在了解 SQL 查詢語句如何執行之前&#xff0c;先了解下MySQL 的基本架構示意圖。 MySQL 分為 Server 層和引擎層。 Server 層包括連接器、查詢緩存、分析器、優化器、執行器等&#xff0c;涵蓋 M…

微球無菌篩分技術的巔峰之作:納維加特PV系列

在醫藥行業中&#xff0c;對微球的制備和篩分要求極高&#xff0c;納維加特&#xff08;Navector&#xff09;憑借其自主創新的PV系列微球無菌旋振篩&#xff0c;成功突破這一領域的技術壁壘。該產品不僅擁有高效率、高精度的篩分能力&#xff0c;同時還兼顧了高衛生級別的要求…

uniapp自動升級

一、創建云服務空間&#xff08;https://unicloud.dcloud.net.cn&#xff09; 云空間用于關聯需要版本控制升級的項目&#xff0c;如果已擁有云空間則省略此步驟。 二、搭建 uni升級中心 - 后臺管理系統&#xff08;升級中心 uni-upgrade-center - Admin&#xff09; uni-adm…

Linux調試器-gdb使用以及Linux項目自動化構建工具-make/Makefile

目錄 1.gdb背景2.開始使用gdb3.make/makefile 背景4.實例代碼5.依賴關系6.依賴方法7.原理8.項目清理 1.gdb背景 程序的發布方式有兩種&#xff0c;debug模式和release模式 Linux gcc/g出來的二進制程序&#xff0c;默認是release模式 要使用gdb調試&#xff0c;必須在源代碼生…

c++的makeFile怎么做

makeFile30分鐘 1 介紹&#xff08;makeFile是什么&#xff0c;30分鐘入門搞懂&#xff09;2 為什么要用makeFile3 如何制作makeFile文件&#xff1f;4 參考 makeFile真的很簡單&#xff0c;不要想的一下子全都學懂了&#xff0c;先入門了&#xff0c;然后在實踐中去使用&#…

Apache部署與配置

概述 介紹 Apache HTTP Server(簡稱Apache)是Apache的一個開源的網頁服務器&#xff0c;它源自NCSAhttpd服務器&#xff0c;并經過多次修改和發展&#xff0c;如今已經成為全球范圍內廣泛使用的Web服務器軟件之一 特點 跨平臺&#xff1a;可以運行在幾乎所有廣泛使用的計算機平…

36 特殊類設計

類&#xff0c;不能被拷貝 拷貝只會放生在兩個場景中&#xff1a;拷貝構造函數以及賦值運算符重載&#xff0c;因此想要讓一個類禁止拷貝。 c98 將拷貝構造函數與賦值云懸浮重載只聲明不定義&#xff0c;并且將其訪問權限設置為私有 class CopyBan{// ...private:CopyBan(co…

Apache中使用SSI設置

先停服務在修改httpd.conf&#xff0c;備份下 Apache\Apache24\conf 設置httpd.conf LoadModule ssl_module modules/mod_ssl.so 取消該命令前的注釋符# AddType text/html .shtml AddOutputFilter INCLUDES .shtml 取消該命令前的注釋符# 加入html AddType text/html .…

在 Kotlin 中,`@JvmOverloads` 注解用于為具有默認參數值的函數生成重載方法

在 Kotlin 中&#xff0c;JvmOverloads 注解用于為具有默認參數值的函數生成重載方法。這個注解在你需要從 Java 代碼調用 Kotlin 函數時特別有用&#xff0c;因為 Java 不支持默認參數值。 下面是一個例子&#xff0c;說明 JvmOverloads 的工作原理&#xff1a; Kotlin 代碼…

前端javascript中的排序算法之插入排序

插入排序&#xff08;Selection Sort&#xff09;基本思想&#xff1a; 插入排序每次排一個數組項&#xff0c;以此方式構建最后的排序數組。假定第一項已經排序了&#xff0c;接著&#xff0c; 它和第二項進行比較&#xff0c;第二項是應該待在原位還是插到第一項之前呢&#…

軟件工具網站推薦

1.菜鳥工具 菜鳥工具 - 不止于工具菜鳥工具&#xff0c;為開發設計人員提供在線工具&#xff0c;網址導航&#xff0c;提供在線PHP、Python、 CSS、JS 調試&#xff0c;中文簡繁體轉換&#xff0c;進制轉換等工具。致力于打造國內專業WEB開發工具&#xff0c;集成開發環境&…

詳細談談負載均衡的startupProbe探針、livenessProbe探針、readnessProbe探針如何使用以及使用差異化

文章目錄 startupProbe探針startupProbe說明示例配置參數解釋 使用場景說明實例——要求&#xff1a; 容器在8秒內完成啟動&#xff0c;否則殺死對應容器工作流程說明timeoutSeconds: 和 periodSeconds: 參數順序說明 livenessProbe探針livenessProbe說明示例配置參數解釋 使用…