用ai来写一个CO2传感器检测

1、目标

打算做一个二氧化碳检测,买的传感器是非分散红外吸收法(NDIR)。

硬件,esp-32S v1.1,是一个nodemcu。软件系统,micropython

  • wifi
  • webserver: microdot框架
  • mqtt: GLM 4.6 socket实现非阻塞
    MicroPython v1.26.1 on 2025-09-11; Generic ESP32 module with ESP32 Type "help()" for more information.
bash 复制代码
MPY: soft reboot
自动连接失败,启动配置模式
AP模式已启动,SSID: ESP3230aea41c6d19, IP: 192.168.4.1
[MQTT] Attempting to connect...
[MQTT] Resolving hostname: 172.29.168.230...
[MQTT] Connecting to 172.29.168.230:1883...
启动Microdot服务,访问: http://192.168.4.1
Starting async server on 0.0.0.0:80...
main: MQTT loop
[MQTT DEBUG] Sending CONNECT packet (hex): 00044d5154540402003c0006657370313233
response:b' \x02\x00\x00'
[MQTT] Connected to broker.
main: MQTT loop
[MQTT] Successfully subscribed to topic: demo2/led
订阅成功
[Main] Publishing: 'Hello from ESP32: 0'
[MQTT Callback] Received message '{
  "msg": "ON"
}' on topic 'demo2/led'
Command received: Turn ON LED
main: MQTT loop
[Main] Publishing: 'Hello from ESP32: 1'
main: MQTT loop
2、wifi连接
python 复制代码
import network
import socket
import json
import os
import uasyncio

# WiFi配置存储文件
CONFIG_FILE = 'wifi_config.json'

class WiFiConfigEnhanced:
    def __init__(self):
        self.ap_ssid = "ESP32"
        self.ap_password = "12345678"
        self.sta_if = network.WLAN(network.STA_IF)
        self.ap_if = network.WLAN(network.AP_IF)
        self.ip = ""
        
    def start_ap(self):
        """启动AP模式"""
        self.ap_if.active(True)
        self.ap_ssid=self.ap_ssid+''.join([f'{i:02x}' for i in self.ap_if.config('mac')])
        self.ap_if.config(essid=self.ap_ssid, password=self.ap_password, authmode=network.AUTH_WPA_WPA2_PSK)
        print(f"AP模式已启动,SSID: {self.ap_ssid}, IP: {self.ap_if.ifconfig()[0]}")
        return self.ap_if.ifconfig()[0]
    
    async def connect_sta(self, ssid, password):
        """连接STA模式"""
        self.sta_if.active(True)
        self.sta_if.connect(ssid, password)
        
        # 等待连接
        for i in range(20):
            if self.sta_if.isconnected():
                print(f"已连接到WiFi: {ssid}")
                print(f"IP地址: {self.sta_if.ifconfig()[0]}")
                #self.save_config(ssid, password)
                self.ip=self.sta_if.ifconfig()[0]
                return True
            await uasyncio.sleep(1)
        return False
    
    def save_config(self, ssid, password):
        """保存WiFi配置到文件"""
        config = {"ssid": ssid, "password": password}
        with open(CONFIG_FILE, 'w') as f:
            json.dump(config, f)
        print("WiFi配置已保存")
    
    def load_config(self):
        """从文件加载WiFi配置"""
        try:
            with open(CONFIG_FILE, 'r') as f:
                config = json.load(f)
            return config.get("ssid"), config.get("password")
        except:
            return None, None
    
    def try_auto_connect(self):
        """尝试自动连接已保存的WiFi"""
        ssid, password = self.load_config()
        if ssid and password:
            print(f"尝试自动连接: {ssid}")
            return self.connect_sta(ssid, password)
        return False
    
    def scan_networks(self):
        """扫描可用WiFi网络"""
        self.sta_if.active(True)
        networks = self.sta_if.scan()
        result = []
        for net in networks:
            result.append({
                'ssid': net[0].decode('utf-8'),
                'signal': net[3],
                'security': net[4]
            })
        return result
    
    def get_ip(self):
        """获得的ip"""
        return self.ip
    
3、web服务

使用7zip压缩css,js文件,文件尺寸缩小明显。

注意send_filed选项 compressed,microdot会自动生成正确的header.

python 复制代码
from microdot import Microdot, send_file
from websocket import with_websocket
import network
import json
import uasyncio


class WebServerMicrodot:
    def __init__(self, wifi_config,mqtt_client):
        self.wifi_config = wifi_config
        self.mqtt_client=mqtt_client
        self.app = Microdot()
        self.setup_routes()
    
    def setup_routes(self):
        """设置路由"""
        @self.app.route('/')
        async def index(request):
            return send_file('index.html')
        
        @self.app.route('/milligram.min.css')
        async def style_css(request):
            return send_file('milligram.min.css.gz', compressed=True)
        
        @self.app.route('/zepto.min.js')
        async def script_js(request):
            return send_file('zepto.min.js.gz', compressed=True  )
        
        @self.app.route('/config', methods=['POST'])
        async def handle_config(request):
            try:
                config = request.json
                ssid = config.get('ssid', '')
                password = config.get('password', '')
                success = await self.wifi_config.connect_sta(ssid, password)
                if(success):
                    sta_if = network.WLAN(network.STA_IF)
                    return {'success': True, 'ip': sta_if.ifconfig()}, 200
                else:
                    return {'success': False}, 200
            except Exception as e:
                return {'success': False, 'error': str(e)}, 400
        
        @self.app.route('/status')
        async def get_status(request):
            """获取连接状态"""
            sta_if = network.WLAN(network.STA_IF)
            status = {
                'sta_connected': sta_if.isconnected(),
                'sta_config': sta_if.ifconfig() if sta_if.isconnected() else None,
                'ap_active': self.wifi_config.ap_if.active(),
                'ap_config': self.wifi_config.ap_if.ifconfig() if self.wifi_config.ap_if.active() else None
            }
            return status
        
        @self.app.errorhandler(404)
        async def not_found(request):
            return '页面未找到', 404
        
        @self.app.route('/connect_mqtt', methods=['POST'])
        async def connect_mqtt(request):
            """手动连接mqtt broker"""
            self.mqtt_client.connect()
            return '', 200
        
        @self.app.route('/ws')
        @with_websocket
        async def read_data(request, ws):
            """ websocket IO  """
            while True:
                ip = self.wifi_config.get_ip()
                # send
                await ws.send(ip)
                
                await uasyncio.sleep_ms(1000)
    
    async def start_server(self):
        """启动Microdot服务"""
        print(f"启动Microdot服务,访问: http://{self.wifi_config.ap_if.ifconfig()[0]}")
        await self.app.start_server(port=80, debug=True)
4、MQTTClient类

wifi未能成功取得IP时,使用umqtt类连接,会阻塞webserver进程。

用llama.cpp+jinx-gpt-oss-20b-mxfp4.gguf,代码进入循环,始终报错。问智谱的GLM 4.6代码,得到能执行的代码。

python 复制代码
# mqtt_async.py
import uasyncio as asyncio
import usocket as socket
import ustruct as struct
import utime as time
import urandom as random

# MQTT Packet Types
CONNECT = 0x10
CONNACK = 0x20
PUBLISH = 0x30
PUBACK = 0x40
SUBSCRIBE = 0x82
SUBACK = 0x90
PINGREQ = 0xC0
PINGRESP = 0xD0
DISCONNECT = 0xE0

class MQTTClient:
    def __init__(self, client_id, server, port=1883, user=None, password=None, keepalive=60, ssl=False):
        self.client_id = client_id
        self.server = server
        self.port = port
        self.user = user
        self.password = password
        self.keepalive = keepalive
        self.ssl = ssl
        
        self.reader = None
        self.writer = None
        self._lock = asyncio.Lock()
        self._pid = 0
        self._message_task = None
        
        self.is_connected = False
        self._callback = None
        
        # Events for QoS 1 publish and subscribe
        self._puback_events = {}
        self._suback_events = {}

    def _next_pid(self):
        self._pid = (self._pid + 1) & 0xFFFF
        if self._pid == 0:
            self._pid = 1
        return self._pid

    def _pack_remaining_length(self, length):
        s = b''
        while True:
            byte = length & 0x7F
            length >>= 7
            if length > 0:
                byte |= 0x80
            s += struct.pack('B', byte)
            if length == 0:
                break
        return s

    # --- 新增的健壮读取函数 ---
    async def _readexactly(self, n):
        """Read exactly n bytes from the stream or raise an error."""
        data = b''
        while len(data) < n:
            packet = await self.reader.read(n - len(data))
            if not packet:
                # Connection closed before all data was received
                raise OSError("Connection closed unexpectedly")
            data += packet
        return data

    async def _send_packet(self, cmd, data=b''):
        await self._lock.acquire()
        try:
            pkt = cmd.to_bytes(1, 'big') + self._pack_remaining_length(len(data)) + data
            self.writer.write(pkt)
            await self.writer.drain()
        finally:
            self._lock.release()

    def set_callback(self, f):
        self._callback = f

    async def connect(self):
        if self.is_connected:
            return
        
        try:
            # DNS 查询 (阻塞操作)
            print(f"[MQTT] Resolving hostname: {self.server}...")
            addr_info = socket.getaddrinfo(self.server, self.port)
            if not addr_info:
                raise OSError("DNS resolution failed")
            addr = addr_info[0][-1]
            print(f"[MQTT] Connecting to {addr[0]}:{addr[1]}...")

            self.reader, self.writer = await asyncio.open_connection(addr[0], addr[1])

            # Construct CONNECT packet
            data = bytearray()
            data += struct.pack('>H', 4) + b'MQTT'  # Protocol Name Length + "MQTT"
            data += struct.pack('B', 4)            # Protocol Level 4
            flags = 0x02  # Clean Session
            if self.user:
                flags |= 0x80
            if self.password:
                flags |= 0x40
            data += struct.pack('B', flags)
            data += struct.pack('>H', self.keepalive)  # Keep Alive
            data += struct.pack('>H', len(self.client_id)) + self.client_id.encode()
            if self.user:
                data += struct.pack('>H', len(self.user)) + self.user.encode()
            if self.password:
                data += struct.pack('>H', len(self.password)) + self.password.encode()
            
            print(f"[MQTT DEBUG] Sending CONNECT packet (hex): {data.hex()}")
            await self._send_packet(CONNECT, data)

            # --- 修改点: 使用 _readexactly ---
            # Wait for CONNACK
            resp = await self._readexactly(4)
            resp_str=str(resp)
            print(f'response:{resp_str}')
            if resp[0] != CONNACK:
                raise OSError(f"Expected CONNACK, got {resp[0]:#x}")
            if resp[3] != 0:
                raise OSError(f"Connection failed with code {resp[3]}")
            
            self.is_connected = True
            print("[MQTT] Connected to broker.")
            
            # Start the message handling loop
            if self._message_task is None or self._message_task.done():
                self._message_task = asyncio.create_task(self._message_loop())

        except OSError as e:
            print(f"[MQTT] Connection failed: {e}")
            self.is_connected = False
            if self.writer:
                self.writer.close()
                await self.writer.wait_closed()
                self.reader = None
                self.writer = None
            raise

    async def disconnect(self):
        if not self.is_connected:
            return
        try:
            await self._send_packet(DISCONNECT)
        finally:
            self.is_connected = False
            if self.writer:
                self.writer.close()
                await self.writer.wait_closed()
                self.reader = None
                self.writer = None
            if self._message_task:
                self._message_task.cancel()
                self._message_task = None
            print("[MQTT] Disconnected.")

    # 在 mqtt_async.py 文件中,找到 publish 方法并替换为以下版本:

    async def publish(self, topic, msg, qos=0, retain=False):
        if not self.is_connected:
            raise OSError("Not connected")
        
        pid = 0
        if qos > 0:
            pid = self._next_pid()
            self._puback_events[pid] = asyncio.Event()

        pkt = bytearray()
        # --- 修改点 1 ---
        pkt += struct.pack('>H', len(topic)) + topic.encode()
        if qos > 0:
            # --- 修改点 2 ---
            pkt += struct.pack('>H', pid)
        pkt += msg.encode()

        cmd = PUBLISH | (qos << 1) | (retain << 0)
        await self._send_packet(cmd, pkt)

        if qos > 0:
            try:
                await asyncio.wait_for(self._puback_events[pid].wait(), timeout=5)
            except asyncio.TimeoutError:
                print(f"[MQTT] PUBACK timeout for PID {pid}")
                del self._puback_events[pid]
                raise OSError("Publish QoS 1 failed (timeout)")
            finally:
                if pid in self._puback_events:
                    del self._puback_events[pid]
    
    # 在 mqtt_async.py 文件中,找到 subscribe 方法并替换为以下版本:

    async def subscribe(self, topic, qos=1):
        """
        订阅一个主题。
        
        Args:
            topic (str): 要订阅的主题。
            qos (int): 服务质量等级 (0 或 1)。

        Returns:
            bool: 订阅成功返回 True,否则返回 False。
        """
        if not self.is_connected:
            print("[MQTT] Cannot subscribe, not connected.")
            return False

        pid = self._next_pid()
        self._suback_events[pid] = asyncio.Event()
        
        data = bytearray()
        data += struct.pack('>H', pid)
        data += struct.pack('>H', len(topic)) + topic.encode()
        data += struct.pack('B', qos)
        
        await self._send_packet(SUBSCRIBE, data)
        
        try:
            # 等待服务器的 SUBACK 确认
            await asyncio.wait_for(self._suback_events[pid].wait(), timeout=5)
            print(f"[MQTT] Successfully subscribed to topic: {topic}")
            return True
        except asyncio.TimeoutError:
            print(f"[MQTT] SUBACK timeout for PID {pid}. Subscription failed.")
            return False
        finally:
            # 无论成功还是失败,都要清理事件
            if pid in self._suback_events:
                del self._suback_events[pid]

    async def _message_loop(self):
        while self.is_connected:
            try:
                # --- 修改点: 在消息循环中也使用 _readexactly ---
                fixed_header = await asyncio.wait_for(self._readexactly(1), timeout=self.keepalive)
                packet_type = fixed_header[0] & 0xF0
                
                remaining_len = 0
                multiplier = 1
                while True:
                    b = await self._readexactly(1)
                    remaining_len += (b[0] & 0x7F) * multiplier
                    if b[0] & 0x80 == 0:
                        break
                    multiplier <<= 7

                payload = await self._readexactly(remaining_len)

                if packet_type == PUBLISH:
                    self._handle_publish(payload)
                elif packet_type == PUBACK:
                    self._handle_puback(payload)
                elif packet_type == SUBACK:
                    self._handle_suback(payload)
                elif packet_type == PINGRESP:
                    pass
                else:
                    print(f"[MQTT] Received unhandled packet type: {packet_type:#x}")

            except asyncio.TimeoutError:
                await self._send_packet(PINGREQ)
                print("[MQTT] Sent PINGREQ")
            except OSError as e:
                print(f"[MQTT] Message loop error: {e}. Reconnecting...")
                self.is_connected = False
                break
        if self.writer:
            self.writer.close()
            await self.writer.wait_closed()
            self.reader = None
            self.writer = None

    def _handle_publish(self, payload):
        i = 0
        topic_len = struct.unpack_from('>H', payload, i)[0]
        i += 2
        topic = payload[i:i+topic_len].decode()
        i += topic_len
        
        pid = 0
        if (payload[0] & 0x06) != 0: # QoS > 0
            pid = struct.unpack_from('>H', payload, i)[0]
            i += 2
        
        msg = payload[i:].decode()
        
        if self._callback:
            asyncio.create_task(self._callback(topic, msg))
            
        if (payload[0] & 0x06) == 0x02: # QoS 1, send PUBACK
            asyncio.create_task(self._send_packet(PUBACK, struct.pack('>H', pid)))

    def _handle_puback(self, payload):
        pid = struct.unpack('>H', payload)[0]
        if pid in self._puback_events:
            self._puback_events[pid].set()

    def _handle_suback(self, payload):
        pid = struct.unpack('>H', payload)[0]
        if pid in self._suback_events:
            self._suback_events[pid].set()

    async def run_forever(self):
        """Main loop with auto-reconnect."""
        while True:
            try:
                if not self.is_connected:
                    print("[MQTT] Attempting to connect...")
                    await self.connect()
                await asyncio.sleep(1)
            except (OSError, asyncio.TimeoutError) as e:
                print(f"[MQTT] Connection attempt failed: {e}. Retrying in 10 seconds...")
            await asyncio.sleep(10)

完善onconnect和ondisconnect后的代码,会导致内存不够。

python 复制代码
import uasyncio as asyncio
import usocket as socket
import ustruct as struct
import utime as time
import urandom as random

# MQTT Packet Types
CONNECT = 0x10
CONNACK = 0x20
PUBLISH = 0x30
PUBACK = 0x40
SUBSCRIBE = 0x82
SUBACK = 0x90
PINGREQ = 0xC0
PINGRESP = 0xD0
DISCONNECT = 0xE0

class MQTTClient:
    def __init__(self, client_id, server, port=1883, user=None, password=None, keepalive=60, ssl=False):
        self.client_id = client_id
        self.server = server
        self.port = port
        self.user = user
        self.password = password
        self.keepalive = keepalive
        self.ssl = ssl
        
        self.reader = None
        self.writer = None
        self._lock = asyncio.Lock()
        self._pid = 0
        self._message_task = None
        
        self.is_connected = False
        
        # --- 新增:回调函数 ---
        self._callback = None
        self.on_connect = None
        self.on_disconnect = None
        
        # Events for QoS 1 publish and subscribe
        self._puback_events = {}
        self._suback_events = {}

    def _next_pid(self):
        self._pid = (self._pid + 1) & 0xFFFF
        if self._pid == 0:
            self._pid = 1
        return self._pid

    def _pack_remaining_length(self, length):
        s = b''
        while True:
            byte = length & 0x7F
            length >>= 7
            if length > 0:
                byte |= 0x80
            s += struct.pack('B', byte)
            if length == 0:
                break
        return s

    async def _readexactly(self, n):
        """Read exactly n bytes from the stream or raise an error."""
        data = b''
        while len(data) < n:
            packet = await self.reader.read(n - len(data))
            if not packet:
                raise OSError("Connection closed unexpectedly")
            data += packet
        return data

    async def _send_packet(self, cmd, data=b''):
        await self._lock.acquire()
        try:
            pkt = cmd.to_bytes(1, 'big') + self._pack_remaining_length(len(data)) + data
            self.writer.write(pkt)
            await self.writer.drain()
        finally:
            self._lock.release()

    # --- 修改:新增设置回调的方法 ---
    def set_callback(self, f):
        """设置接收 PUBLISH 消息的回调函数"""
        self._callback = f

    def set_on_connect(self, f):
        """设置连接成功后的回调函数"""
        self.on_connect = f

    def set_on_disconnect(self, f):
        """设置断开连接后的回调函数"""
        self.on_disconnect = f

    async def connect(self):
        if self.is_connected:
            return
        
        try:
            print(f"[MQTT] Resolving hostname: {self.server}...")
            addr_info = socket.getaddrinfo(self.server, self.port)
            if not addr_info:
                raise OSError("DNS resolution failed")
            addr = addr_info[0][-1]
            print(f"[MQTT] Connecting to {addr[0]}:{addr[1]}...")

            self.reader, self.writer = await asyncio.open_connection(addr[0], addr[1])

            # Construct CONNECT packet
            data = bytearray()
            data += struct.pack('>H', 4) + b'MQTT'  # Protocol Name
            data += struct.pack('B', 4)            # Protocol Level 4
            flags = 0x02  # Clean Session
            if self.user:
                flags |= 0x80
            if self.password:
                flags |= 0x40
            data += struct.pack('B', flags)
            data += struct.pack('>H', self.keepalive)  # Keep Alive
            data += struct.pack('>H', len(self.client_id)) + self.client_id.encode()
            if self.user:
                data += struct.pack('>H', len(self.user)) + self.user.encode()
            if self.password:
                data += struct.pack('>H', len(self.password)) + self.password.encode()
            
            await self._send_packet(CONNECT, data)

            resp = await self._readexactly(4)
            if resp[0] != CONNACK:
                raise OSError(f"Expected CONNACK, got {resp[0]:#x}")
            if resp[3] != 0:
                raise OSError(f"Connection failed with code {resp[3]}")
            
            self.is_connected = True
            print("[MQTT] Connected to broker.")
            
            # --- 新增:触发 on_connect 回调 ---
            if self.on_connect:
                asyncio.create_task(self.on_connect())
            
            # Start the message handling loop
            if self._message_task is None or self._message_task.done():
                self._message_task = asyncio.create_task(self._message_loop())

        except OSError as e:
            print(f"[MQTT] Connection failed: {e}")
            self.is_connected = False
            if self.writer:
                self.writer.close()
                await self.writer.wait_closed()
                self.reader = None
                self.writer = None
            raise

    async def disconnect(self):
        if not self.is_connected:
            return
        try:
            await self._send_packet(DISCONNECT)
        finally:
            self.is_connected = False
            if self.writer:
                self.writer.close()
                await self.writer.wait_closed()
                self.reader = None
                self.writer = None
            if self._message_task:
                self._message_task.cancel()
                self._message_task = None
            print("[MQTT] Disconnected.")
            # --- 新增:触发 on_disconnect 回调 ---
            if self.on_disconnect:
                asyncio.create_task(self.on_disconnect(reason="clean"))

    async def publish(self, topic, msg, qos=0, retain=False):
        if not self.is_connected:
            raise OSError("Not connected")
        
        pid = 0
        if qos > 0:
            pid = self._next_pid()
            self._puback_events[pid] = asyncio.Event()

        pkt = bytearray()
        pkt += struct.pack('>H', len(topic)) + topic.encode()
        if qos > 0:
            pkt += struct.pack('>H', pid)
        pkt += msg.encode()

        cmd = PUBLISH | (qos << 1) | (retain << 0)
        await self._send_packet(cmd, pkt)

        if qos > 0:
            try:
                await asyncio.wait_for(self._puback_events[pid].wait(), timeout=5)
            except asyncio.TimeoutError:
                print(f"[MQTT] PUBACK timeout for PID {pid}")
                del self._puback_events[pid]
                raise OSError("Publish QoS 1 failed (timeout)")
            finally:
                if pid in self._puback_events:
                    del self._puback_events[pid]
    
    async def subscribe(self, topic, qos=1):
        if not self.is_connected:
            print("[MQTT] Cannot subscribe, not connected.")
            return False

        pid = self._next_pid()
        self._suback_events[pid] = asyncio.Event()
        
        data = bytearray()
        data += struct.pack('>H', pid)
        data += struct.pack('>H', len(topic)) + topic.encode()
        data += struct.pack('B', qos)
        
        await self._send_packet(SUBSCRIBE, data)
        
        try:
            await asyncio.wait_for(self._suback_events[pid].wait(), timeout=5)
            print(f"[MQTT] Successfully subscribed to topic: {topic}")
            return True
        except asyncio.TimeoutError:
            print(f"[MQTT] SUBACK timeout for PID {pid}. Subscription failed.")
            return False
        finally:
            if pid in self._suback_events:
                del self._suback_events[pid]

    async def _message_loop(self):
        while self.is_connected:
            try:
                fixed_header = await asyncio.wait_for(self._readexactly(1), timeout=self.keepalive)
                packet_type = fixed_header[0] & 0xF0
                
                remaining_len = 0
                multiplier = 1
                while True:
                    b = await self._readexactly(1)
                    remaining_len += (b[0] & 0x7F) * multiplier
                    if b[0] & 0x80 == 0:
                        break
                    multiplier <<= 7

                payload = await self._readexactly(remaining_len)

                if packet_type == PUBLISH:
                    self._handle_publish(payload)
                elif packet_type == PUBACK:
                    self._handle_puback(payload)
                elif packet_type == SUBACK:
                    self._handle_suback(payload)
                elif packet_type == PINGRESP:
                    pass
                else:
                    print(f"[MQTT] Received unhandled packet type: {packet_type:#x}")

            except asyncio.TimeoutError:
                await self._send_packet(PINGREQ)
                print("[MQTT] Sent PINGREQ")
            except OSError as e:
                print(f"[MQTT] Message loop error: {e}. Reconnecting...")
                self.is_connected = False
                # --- 新增:触发 on_disconnect 回调 ---
                if self.on_disconnect:
                    asyncio.create_task(self.on_disconnect(reason="unexpected"))
                break
        if self.writer:
            self.writer.close()
            await self.writer.wait_closed()
            self.reader = None
            self.writer = None

    def _handle_publish(self, payload):
        i = 0
        topic_len = struct.unpack_from('>H', payload, i)[0]
        i += 2
        topic = payload[i:i+topic_len].decode()
        i += topic_len
        
        pid = 0
        if (payload[0] & 0x06) != 0: # QoS > 0
            pid = struct.unpack_from('>H', payload, i)[0]
            i += 2
        
        msg = payload[i:].decode()
        
        if self._callback:
            asyncio.create_task(self._callback(topic, msg))
            
        if (payload[0] & 0x06) == 0x02: # QoS 1, send PUBACK
            asyncio.create_task(self._send_packet(PUBACK, struct.pack('>H', pid)))

    def _handle_puback(self, payload):
        pid = struct.unpack('>H', payload)[0]
        if pid in self._puback_events:
            self._puback_events[pid].set()

    def _handle_suback(self, payload):
        pid = struct.unpack('>H', payload)[0]
        if pid in self._suback_events:
            self._suback_events[pid].set()

    async def run_forever(self):
        """Main loop with auto-reconnect."""
        while True:
            try:
                if not self.is_connected:
                    print("[MQTT] Attempting to connect...")
                    await self.connect()
                await asyncio.sleep(1)
            except (OSError, asyncio.TimeoutError) as e:
                print(f"[MQTT] Connection attempt failed: {e}. Retrying in 10 seconds...")
                await asyncio.sleep(10)
5、Main程序执行
python 复制代码
from config_wifi import WiFiConfigEnhanced
from web_server import WebServerMicrodot
from MqttClient import MQTTClient

import uasyncio
import json

# --- Callback function for received messages ---
async def mqtt_callback(topic, msg):
    print(f"[MQTT Callback] Received message '{msg}' on topic '{topic}'")
    # You can add logic here to react to received commands
    data=json.loads(msg)
    if data['msg'] == "ON":
        print("Command received: Turn ON LED")
        # Add your code to turn on an LED, e.g., led.on()
    elif ['msg'] == "OFF":
        print("Command received: Turn OFF LED")
        # Add your code to turn off an LED, e.g., led.off()
        
# publish topic
async def mqtt_loop(client: MQTTClient):    
    counter = 0
    subscribed=False 
    while True:
        print('main: MQTT loop')
        if client.is_connected:
            if not subscribed:
                subscribed=await client.subscribe('demo2/led')
                if subscribed:
                    print(f'订阅成功')
                
            try:
                message = f"Hello from ESP32: {counter}"
                print(f"[Main] Publishing: '{message}'")
                await client.publish('demo/counter', message, qos=1)
                counter += 1
            except OSError as e:
                print(f"[Main] Failed to publish: {e}")

        # Publish every 10 seconds
        await uasyncio.sleep(10)

async def main():
    # wifi
    wifi = WiFiConfigEnhanced()
    if not wifi.try_auto_connect():
        print("自动连接失败,启动配置模式")
        wifi.start_ap()
    else:
        print("已成功连接到WiFi")
        print(f"IP地址: {wifi.sta_if.ifconfig()[0]}")
  
    # mqtt
    mqtt_client = MQTTClient(client_id='esp123',server='172.29.168.230')
    mqtt_client.set_callback(mqtt_callback)
    task_mqtt=uasyncio.create_task(mqtt_client.run_forever())
  
    # web
    server = WebServerMicrodot(wifi,mqtt_client)
    task_web=uasyncio.create_task(server.start_server())
        
    # publish
    task_put=uasyncio.create_task(mqtt_loop(mqtt_client))

    tasks = [task_web,task_mqtt,task_put]
    await uasyncio.gather(*tasks)


if __name__ == "__main__":
    uasyncio.run(main())
6、index.html

用websocket更新页面。

helpers.py,websocket.py从microdot-2.3.5\src\microdot上传到根目录。

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ESP32 WiFi配置</title>
    <link rel="stylesheet" href="/milligram.min.css">
    <style>
        .message {
            margin-top: 20px;
            padding: 15px;
            border-radius: 5px;
        }
        .success {
            background-color: #d4edda;
            color: #155724;
        }
        .error {
            background-color: #f8d7da;
            color: #721c24;
        }
    </style>
</head>
<body>
<div class="container"> 
  <div class="row">
   <div class="column">
    <h6>ESP32 WiFi配置</h6>
   </div> 
  </div>
  <div class="row">
   <div class="column">
    <form id="wifiForm">
        <fieldset>
            <div class="form-group">
                <label for="ssid">WiFi名称 (SSID)</label>
                <input class="input" type="text" id="ssid" name="ssid" required>
            </div>
            <div class="form-group">
                <label for="password">WiFi密码</label>
                <input class="input" type="text" id="password" name="password">
            </div>
            <button class="button button-primary" type="submit">连接</button>
        </fieldset>
    </form>
     </div>
    </div>
    <div class="row">
       <div class="column">
       <form id="mqttForm">
           <label>Mqtt</label>
           <button class="button button-primary" type="submit">连接broker</button>
       </form>
       </div>
     </div>
       
    <div class="row">
     <div class="column message" id="wifi_satus"></div>
    </div> 
   <div class="row">
     <div class="column message" id="message" style="display: none;"></div>
    </div> 
    
    <script src="/zepto.min.js"></script>
    <script>
        var websocket=new WebSocket(`ws://${location.host}/ws`);
        websocket.onmessage = onMessage;
        function onMessage(event) {
            $('#wifi_satus').text(event.data);
        }
        $(function() {
            $('#mqttForm').on('submit', function(e) {
                e.preventDefault();
                $.ajax({
                    url: '/connect_mqtt',
                    type: 'POST'
                    });
                });
        
            $('#wifiForm').on('submit', function(e) {
                e.preventDefault();
                
                const ssid = $('#ssid').val();
                const password = $('#password').val();

                // Disable form during submission
                const submitButton = $('button[type="submit"]');
                submitButton.prop('disabled', true).addClass('button-disabled');
                
                // Show loading message
                $('#message').removeClass('error').addClass('success')
                    .text('正在连接...').show();
                
                $.ajax({
                    url: '/config',
                    type: 'POST',
                    contentType: 'application/json',
                    data: JSON.stringify({ ssid: ssid, password: password }),
                    timeout: 10000, // 10 second timeout
                    success: function(response) {
                        if (response.success) {
                            $('#message').removeClass('error').addClass('success')
                                .text('连接成功!IP:'+response.ip);
                        } else {
                        $('#message').removeClass('success').addClass('error')
                                .text('连接失败。');
                        
                        }
                    },
                    error: function(xhr, status, error) {
                        let errorMsg = '连接失败';
                        
                        if (xhr.status === 0) {
                            errorMsg = '无法连接到服务器,请检查网络';
                        } else {
                            try {
                                const response = JSON.parse(xhr.responseText);
                                errorMsg = response.error || errorMsg;
                            } catch (e) {
                                
                            }
                        }
                        
                        $('#message').removeClass('success').addClass('error')
                            .text(errorMsg);
                    },
                    complete: function() {
                        // Re-enable form
                        submitButton.prop('disabled', false).removeClass('button-disabled');
                    }
                });
            });
        });
    </script>
</div>    
</body>
</html>
相关推荐
我先去打把游戏先4 天前
ESP32C3开发指南(基于IDF):console控制台命令行交互功能
笔记·嵌入式硬件·mcu·物联网·学习·esp32·交互
superior tigre12 天前
esp32学习随笔文档1
学习·esp32
特立独行的猫a15 天前
ESP32使用笔记(基于ESP-IDF):小智AI的ESP32项目架构与启动流程全面解析
人工智能·架构·esp32·小智ai
MThinker18 天前
03-Machine-3-display_and_touch.py K230外接液晶显示屏与电容触摸屏功能演示
micropython·触摸屏·canmv·k230
我先去打把游戏先18 天前
ESP32学习笔记(基于IDF):ESP32连接MQTT服务器
服务器·笔记·单片机·嵌入式硬件·学习·esp32
MThinker19 天前
03-Machine-2-dht.py K230外接数字温湿度传感器DHT11模块演示
智能硬件·micropython·canmv·k230
我先去打把游戏先19 天前
ESP32学习笔记(基于IDF):SmartConfig一键配网
笔记·嵌入式硬件·mcu·物联网·学习·esp32·硬件工程
我先去打把游戏先22 天前
ESP32学习笔记(基于IDF):IOT应用——WIFI连接
笔记·单片机·嵌入式硬件·mcu·物联网·学习·esp32
星野云联AIoT技术洞察1 个月前
OpenMQTTGateway 技术全解:统一多协议到 MQTT 的开源网关
lora·esp32·智能家居·ble·ir·iot网关·openmqttgateway