Tkinter整合Asyncio与Telemetrix-AIO连接ESP32报错求助
使用Telemetrix AIO ESP32结合Tkinter+Asyncio时连接失败的问题
环境信息
- Windows 11
- Python 3.11.5
背景
我用Python Telemetrix库控制ESP32,做了一个带Tkinter界面的应用,想实现运行时连接/断开ESP32的功能。官方WiFi闪烁示例可以正常运行,能成功连接并控制LED闪烁。
我写了一个纯Asyncio(未用threading)的示例代码,用自定义事件循环处理Tk窗口更新和用户操作触发的异步方法,这种方式在websockets库中运行正常,但连接ESP32时出错。
示例代码
import asyncio import tkinter as tk from telemetrix_aio_esp32 import telemetrix_aio_esp32 DIGITAL_PIN = 2 class App: def __init__(self): self.root = tk.Tk() self.root.title("TelemetrixAioEsp32 Blink Example") self.board = None self.ip_address_frame = tk.Frame(self.root) self.ip_address_frame.pack() self.ip_address_label = tk.Label(self.ip_address_frame, text="IP Address:") self.ip_address_label.pack(side="left") self.ip_address_entry = tk.Entry(self.ip_address_frame) self.ip_address_entry.pack(side="left", padx=5) self.ip_address_entry.insert("end",'192.168.1.196') self.button_frame = tk.Frame(self.root) self.button_frame.pack() self.connect_button = tk.Button(self.button_frame, text="Connect", command=self.start_connect) self.connect_button.pack(side="left", padx=5) self.start_button = tk.Button(self.button_frame, text="Start Blink", command=self.start_blink) self.start_button.pack(side="left") self.start_button['state'] = tk.DISABLED self.request_blink = False self.request_connect = False self.event_loop = asyncio.get_event_loop() asyncio.set_event_loop(self.event_loop) self.showing = True self.root.protocol("WM_DELETE_WINDOW", self.on_closing) async def connect(self): if self.board is None: ip_address = self.ip_address_entry.get() self.board = telemetrix_aio_esp32.TelemetrixAioEsp32(transport_address=ip_address, loop=self.event_loop, autostart=False) await self.board.start_aio() self.connect_button["text"] = "Disconnect" self.start_button['state'] = tk.NORMAL else: await self.board.shutdown() self.board = None self.connect_button["text"] = "Connect" self.start_button['state'] = tk.DISABLED async def show(self): while self.showing: self.root.update() if self.request_blink: self.request_blink = False await self.blink(DIGITAL_PIN) if self.request_connect: self.request_connect = False await self.connect() if self.board is not None: await self.connect() async def blink(self, pin): try: await self.board.set_pin_mode_digital_output(pin) for x in range(4): print('ON') await self.board.digital_write(pin, 0) await asyncio.sleep(1) print('OFF') await self.board.digital_write(pin, 1) await asyncio.sleep(1) except Exception as e: print(f"Error during blink: {e}") def start_blink(self): self.request_blink = True def start_connect(self): self.request_connect = True async def run_tk(self): try: await self.show(); finally: self.event_loop.close() def run(self): asyncio.run(self.run_tk()) def on_closing(self): self.showing = False self.root.destroy() if __name__ == "__main__": app = App() app.run()
错误信息
PS C:\Users\lokno\Desktop\Scratch\Code> python .\blink_wifi_app.py TelemetrixAioEsp32 Version: 1.3 Copyright (c) 2022 Alan Yorinks All rights reserved. Successfully connected to: 192.168.1.196:31336 Traceback (most recent call last): File "C:\Users\lokno\Desktop\Scratch\Code\blink_wifi_app.py", line 110, in <module> app.run() File "C:\Users\lokno\Desktop\Scratch\Code\blink_wifi_app.py", line 102, in run asyncio.run(self.run_tk()) File "C:\Users\lokno\scoop\apps\python\current\Lib\asyncio\runners.py", line 190, in run return runner.run(main) ^^^^^^^^^^^^^^^^ File "C:\Users\lokno\scoop\apps\python\current\Lib\asyncio\runners.py", line 118, in run return self._loop.run_until_complete(task) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\lokno\scoop\apps\python\current\Lib\asyncio\base_events.py", line 653, in run_until_complete return future.result() ^^^^^^^^^^^^^^^ File "C:\Users\lokno\Desktop\Scratch\Code\blink_wifi_app.py", line 97, in run_tk await self.show(); ^^^^^^^^^^^^^^^^^ File "C:\Users\lokno\Desktop\Scratch\Code\blink_wifi_app.py", line 69, in show await self.connect() File "C:\Users\lokno\Desktop\Scratch\Code\blink_wifi_app.py", line 48, in connect await self.board.start_aio() File "C:\Users\lokno\scoop\apps\python\current\Lib\site-packages\telemetrix_aio_esp32\telemetrix_aio_esp32.py", line 282, in start_aio raise RuntimeError('Could not retrieve server firmware version') RuntimeError: Could not retrieve server firmware version
问题分析
我认为问题出在Asyncio和Telemetrix库的结合使用上。我传入了自定义事件循环并设置autostart=False,官方库中该参数的逻辑是:
if autostart: self.loop.run_until_complete(self.start_aio())
现在卡在了如何正确调用start_aio()上,需要调试建议。
解决方案
问题根源
start_aio()内部需要等待ESP32返回固件版本信息,但你的Tkinter更新逻辑(self.root.update())是同步阻塞调用,会抢占事件循环的执行时间,导致Telemetrix无法及时接收ESP32的响应,最终超时抛出错误。
修复步骤
- 替换同步的Tk更新方式:用
asyncio.create_task让Tk的事件循环和Asyncio事件循环协同工作,避免用self.root.update()死循环阻塞。 - 调整事件循环的管理:避免手动创建/设置事件循环,让
asyncio.run()自动管理,同时用asyncio.to_thread运行Tk的主循环。 - 用Asyncio原生事件标记请求:替代布尔变量,更符合异步编程的通知机制。
修复后的代码
import asyncio import tkinter as tk from telemetrix_aio_esp32 import telemetrix_aio_esp32 DIGITAL_PIN = 2 class App: def __init__(self): self.root = tk.Tk() self.root.title("TelemetrixAioEsp32 Blink Example") self.board = None self.request_blink = asyncio.Event() self.request_connect = asyncio.Event() # UI布局 ip_address_frame = tk.Frame(self.root) ip_address_frame.pack() tk.Label(ip_address_frame, text="IP Address:").pack(side="left") self.ip_address_entry = tk.Entry(ip_address_frame) self.ip_address_entry.pack(side="left", padx=5) self.ip_address_entry.insert("end", '192.168.1.196') button_frame = tk.Frame(self.root) button_frame.pack() self.connect_button = tk.Button(button_frame, text="Connect", command=self.trigger_connect) self.connect_button.pack(side="left", padx=5) self.start_button = tk.Button(button_frame, text="Start Blink", command=self.trigger_blink, state=tk.DISABLED) self.start_button.pack(side="left") self.root.protocol("WM_DELETE_WINDOW", self.on_closing) self.running = True def trigger_connect(self): self.request_connect.set() def trigger_blink(self): self.request_blink.set() async def connect(self): if self.board is None: ip_address = self.ip_address_entry.get() # 不手动指定loop,让库自动使用当前事件循环 self.board = telemetrix_aio_esp32.TelemetrixAioEsp32(transport_address=ip_address, autostart=False) await self.board.start_aio() self.connect_button["text"] = "Disconnect" self.start_button['state'] = tk.NORMAL else: await self.board.shutdown() self.board = None self.connect_button["text"] = "Connect" self.start_button['state'] = tk.DISABLED async def blink(self, pin): if not self.board: return try: await self.board.set_pin_mode_digital_output(pin) for _ in range(4): print('ON') await self.board.digital_write(pin, 0) await asyncio.sleep(1) print('OFF') await self.board.digital_write(pin, 1) await asyncio.sleep(1) except Exception as e: print(f"Error during blink: {e}") async def handle_events(self): while self.running: await asyncio.sleep(0.01) # 主动让出事件循环时间 if self.request_connect.is_set(): self.request_connect.clear() await self.connect() if self.request_blink.is_set(): self.request_blink.clear() await self.blink(DIGITAL_PIN) async def run_tk(self): # 用线程运行Tk主循环,避免阻塞Asyncio事件循环 await asyncio.to_thread(self.root.mainloop) async def run(self): # 同时运行Tk主循环和事件处理任务 task_tk = asyncio.create_task(self.run_tk()) task_events = asyncio.create_task(self.handle_events()) await asyncio.gather(task_tk, task_events) def on_closing(self): self.running = False self.root.destroy() if __name__ == "__main__": app = App() asyncio.run(app.run())
关键改动说明
- 用
asyncio.Event替代布尔变量标记用户请求,更符合Asyncio的异步通知机制,避免竞态条件。 - 将Tk的
mainloop放在单独线程中运行,通过asyncio.to_thread让它和Asyncio事件循环并行,彻底解决阻塞问题。 - 移除手动创建/设置事件循环的代码,让
asyncio.run()自动管理当前事件循环,Telemetrix库会自动绑定这个循环。 - 事件处理逻辑中加入
await asyncio.sleep(0.01),主动让出事件循环时间,确保Telemetrix的网络通信任务能及时执行,接收ESP32的响应。
内容的提问来源于stack exchange,提问作者Lokno
相关产品推荐
相关产品推荐

