实时温湿度监测系统: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 pd

def line_plot():
    # Read the data from the file
    data = pd.read_csv('data.txt', sep='\t', header=None, names=['Timestamp', 'Temperature', 'Humidity'])
    print("Data loaded for plotting.")

    # Create the figure with a single subplot
    fig, ax = plt.subplots(figsize=(12, 6))

    # Plot the temperature
    temperature_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-40
    ax.set_ylabel('Temperature (°C)', color='blue')
    ax.tick_params('y', colors='blue')

    
    # Create a twin y-axis for the humidity
    ax2 = 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-80
    ax2.tick_params('y', colors='green')

    # Set the title and grid
    ax.set_title('Temperature and Humidity over Time')
    ax.grid()

    # Add a legend
    lines = [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 labels
    num_ticks = 20
    start = 0
    end = 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 plot
    return fig

class AutoHideWindow:
    def __init__(self, root):
        self.root = root
        self.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 = False

        self.screen_width = self.root.winfo_screenwidth()
        self.screen_height = self.root.winfo_screenheight()

        self.hidden_window = None
        self.line_chart_window = None
        self.line_chart_open = False  # Track if the line chart window is open

        self.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 = 0
        self.y_offset = 0

        self.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:
            pass

        self.current_frame = 0
        self.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 status
        self.line_chart_window.protocol("WM_DELETE_WINDOW", self.close_line_chart_window)

    def check_position(self, event=None):
        if self.is_hidden:
            return
        x, 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:
            return

        x, 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 = None
            self.root.deiconify()
            self.is_hidden = False

    def show_line_chart_window(self):
        if self.line_chart_open:
            self.line_chart_window.deiconify()  # Show existing window
            self.create_line_chart(self.line_chart_window)  # Redraw the chart
        else:
            self.create_line_chart(self.line_chart_window)
            self.line_chart_window.deiconify()
            self.line_chart_open = True  # Update the open status

    def close_line_chart_window(self):
        if self.line_chart_open:
            self.line_chart_window.withdraw()  # Hide the window
            self.line_chart_open = False  # Update the open status

    def start_move(self, event):
        self.x_offset = event.x
        self.y_offset = event.y

    def on_move(self, event):
        x = self.root.winfo_pointerx() - self.x_offset
        y = self.root.winfo_pointery() - self.y_offset
        self.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.mfbz.cn/a/788081.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

核密度估计KDE和概率密度函数PDF(深入浅出)

目录 1. 和密度估计&#xff08;KDE&#xff09;核密度估计的基本原理核密度估计的公式核密度估计的应用Python中的KDE实现示例代码 结果解释解释结果 总结 2. 概率密度函数&#xff08;PDF&#xff09;概率密度函数&#xff08;PDF&#xff09;是怎么工作的&#xff1a;用图画…

澳大利亚TikTok直播为什么需要海外直播专线?

近年来&#xff0c;许多卖家为了解决澳大利亚TikTok直播中的卡顿和高延迟问题&#xff0c;纷纷选择使用海外直播专线。这种专线服务是一种高效、低延迟的数据传输解决方案&#xff0c;专为需要高质量网络连接的场合设计。 与公共互联网相比&#xff0c;海外直播专线提供更稳定、…

在Linux下直接修改磁盘镜像文件的内容

背景 嵌入式Linux系统通常在调试稳定后&#xff0c;会对磁盘&#xff08;SSD、NVME、SD卡、TF卡&#xff09;做个镜像&#xff0c;通常是.img后缀的文件&#xff0c;以后组装新设备时&#xff0c;就将镜像文件烧录到新磁盘即可&#xff0c;非常简单。 这种方法有个不便之处&a…

99%的人忽视了这一点:活着本身就是人生的意义,别让抑郁和内耗成为你的枷锁!

人没必要抑郁和精神内耗&#xff0c;不结婚&#xff0c;不生子&#xff0c;赚不到钱&#xff0c;没考上名牌大学&#xff0c;没有好工作等等&#xff0c;其实都没什么关系。 因为大多数人生是没有什么意义&#xff0c;或者说&#xff0c;活着就是最大的意义。 抑郁和精神内耗…

接口测试(2)

单接口测试 CtrlD 复制 因为单接口的时候主要改变测试用例数据 自动判定响应结果 postman断言 //断言响应状态码为200 pm.test("Status code is 200", function () {pm.response.to.have.status(200); }); //断言返回数据中包括&#xff08;成功&#xff09; //预期结…

14.x86游戏实战-汇编指令cmp test

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

移除元素的讲解,看这篇就够了!

一&#xff1a;题目 博主本文将用指向来形象的表示下标位的移动。 二&#xff1a;思路 1&#xff1a;两个整形&#xff0c;一个start&#xff0c;一个end&#xff0c;在一开始都 0&#xff0c;即这里都指向第一个元素。 2&#xff1a;在查到val之前&#xff0c;查一个&…

策略路由和路由策略的区别详解

先说策略路由也就是 PBR&#xff1a; 它不会影响路由表的生成&#xff0c;设备的路由表是已经存在而且稳定的。 举个例子&#xff1a; 用 TCP/IP 路由技术一书的表述就是&#xff1a;策略路由就是一个复杂的静态路由。 总结&#xff1a;策略路由是一个基于路由表的影响特定数…

Linux | 安装lb-toolkits 1.2.4库

Linux | 安装 lb-toolkits 最近又需要下载葵花的数据&#xff0c;之前分享过一次代码。今天发现之前的环境不小心被我删了&#xff0c;而运行相关的代码需要安装lb-toolkits这个库&#xff0c;今天正好记录了一下安装lb-toolkits的过程。 这里安装的版本是1.2.4&#xff0c;别…

兼容性报错--调整字符集解决

文章目录 错误解决办法Unicode 字符集(两个字节来表示一个字符)多字节字符集(一个字节来表示一个字符)如何选择字符集char与wchar_t的区别LPCSTR与LPCWSTR的区别 错误 解决办法 切换字符集类型 Unicode 字符集(两个字节来表示一个字符) 优点&#xff1a; 支持更多的字符集…

高效前端开发:解密pnpm的存储与链接

什么是pnpm PNPM&#xff08;Performant NPM&#xff09;是一种快速且节省磁盘空间的包管理工具。相较于其他包管理器如NPM和Yarn&#xff0c;PNPM通过独特的存储机制和链接技术解决了许多常见的问题。以下是PNPM如何避免这些问题以及其关键技术的详细介绍。 特性 PNPM Store…

6.Python学习:异常和日志

1.异常的抓取 1.1异常的概念 使用异常前&#xff1a; print(1/0)使用异常后&#xff1a;错误提示更加友好&#xff0c;不影响程序继续往下运行 try:print(10/0) except ZeroDivisionError:print("0不能作为分母")1.2异常的抓取 第一种&#xff1a;如果提前知道可…

[C++] 由C语言过渡到C++的敲门砖

命名空间 在C/C中&#xff0c;变量、函数和后⾯要学到的类都是⼤量存在的&#xff0c;这些变量、函数和类的名称将都存在于全局作⽤域中&#xff0c;可能会导致很多冲突。使⽤命名空间的⽬的是对标识符的名称进⾏本地化&#xff0c;以避免命名冲突或名字污染 。 在同一个工程中…

可视采耳仪器什么牌子好?年度必备五大可视耳勺品牌分享

无线可视挖耳勺作为近年来新兴的个护健康产品&#xff0c;受到了越来越多消费者的关注和喜爱。这种挖耳勺采用了先进的无线技术和高清摄像头&#xff0c;能够让人们更加清晰地观察自己耳内的状况&#xff0c;从而更加安全、有效地清洁耳朵。 但随着可视挖耳勺市场扩大&#xff…

老师怎样提高学生的听课效率?

在课堂上&#xff0c;我们常常面临一个问题&#xff1a;如何提高学生的听课效率&#xff1f;这是一个让无数教师头疼的问题。学生是否全神贯注&#xff0c;是否能够吸收和理解课堂上的知识&#xff0c;这直接关系到教学的成败。那么&#xff0c;作为教师&#xff0c;我们能做些…

可以添加todo清单桌面小组件的便签哪个好?

在我们快节奏的生活中&#xff0c;有效的时间管理和任务追踪是必不可少的。为了实现这一目标&#xff0c;许多人选择使用桌面便签&#xff0c;尤其是那些具有Todo清单桌面小组件的便签。但是&#xff0c;面对市场上众多选择&#xff0c;可以添加todo清单桌面小组件的便签哪个好…

STM32的SPI接口详解

目录 1.SPI简介 2.SPI工作原理 3.SPI时序 3.1 CPOL&#xff08;Clock Polarity&#xff0c;时钟极性&#xff09;&#xff1a; 3.2 CPHA&#xff08;Clock Phase&#xff0c;时钟相位&#xff09;&#xff1a; 3.3 四种工作模式 4.相关代码 4.1使能片选信号 4.2使能通…

vue学习day01-vue的概念、创建Vue实例、插值表达式、响应式、安装Vue开发者工具

1、vue的概念 Vue是一个用于构建用户界面的渐进式 框架 &#xff08;1&#xff09;构建用户界面&#xff1a;基于数据动态渲染页面 &#xff08;2&#xff09;渐进式&#xff1a;循序渐进的学习 &#xff08;3&#xff09;框架&#xff1a;一条完整的项目解决方案&#xff…

回溯算法-以医院信息管理系统为例

1.回溯算法介绍 1.来源 回溯算法也叫试探法&#xff0c;它是一种系统地搜索问题的解的方法。 用回溯算法解决问题的一般步骤&#xff1a; 1、 针对所给问题&#xff0c;定义问题的解空间&#xff0c;它至少包含问题的一个&#xff08;最优&#xff09;解。 2 、确定易于搜…

Redis代替Session实现共享

集群的session共享问题 session共享问题&#xff1a;多台tomcat并不共享session存储空间&#xff0c;当请求切换到不同的tomcat服务时导致数据丢失的问题。 session的替代方案&#xff1a; 数据共享内存存储key、value结构 将redis替换session可以解决session共享问题