当前位置: 首页 > news >正文

【python】pyserial 在windows 下卡住的bug

使用当前pyserial 在Windwos 下做的项目,移交到客户案场后, 客户发现程序会卡住,经过最终调查,是卡在了pyserial 的write 方法中了。

具体现象

程序卡在write 方法中,长时间无法返回,即使设置了write_timeout 也无法返回。

分析

下来我们将Send 方法的代码切出来:

   def write(self, data):"""Output the given byte string over the serial port."""if not self.is_open:raise PortNotOpenError()#~ if not isinstance(data, (bytes, bytearray)):#~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))# convert data (needed in case of memoryview instance: Py 3.1 io lib), ctypes doesn't like memoryviewdata = to_bytes(data)if data:#~ win32event.ResetEvent(self._overlapped_write.hEvent)n = win32.DWORD()success = win32.WriteFile(self._port_handle, data, len(data), ctypes.byref(n), self._overlapped_write)if self._write_timeout != 0:  # if blocking (None) or w/ write timeout (>0)if not success and win32.GetLastError() not in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING):raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError()))# Wait for the write to complete.#~ win32.WaitForSingleObject(self._overlapped_write.hEvent, win32.INFINITE)win32.GetOverlappedResult(self._port_handle, self._overlapped_write, ctypes.byref(n), True)if win32.GetLastError() == win32.ERROR_OPERATION_ABORTED:return n.value  # canceled IO is no errorif n.value != len(data):raise SerialTimeoutException('Write timeout')return n.valueelse:errorcode = win32.ERROR_SUCCESS if success else win32.GetLastError()if errorcode in (win32.ERROR_INVALID_USER_BUFFER, win32.ERROR_NOT_ENOUGH_MEMORY,win32.ERROR_OPERATION_ABORTED):return 0elif errorcode in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING):# no info on true length provided by OS function in async modereturn len(data)else:raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError()))else:return 0

这里有一个 Windows 串口编程的“坑”:即使你设置了 write_timeout,某些情况下,WriteFile + GetOverlappedResult() 仍可能卡住不返回。下面我从操作系统底层机制解释这个行为。
在 PySerial 的 Windows 实现中,串口写操作等价于如下 WinAPI 调用流程:

OVERLAPPED ov = {...};  // 重叠结构体(异步IO用)
DWORD written = 0;
BOOL success = WriteFile(hSerial, buffer, len, &written, &ov);if (!success && GetLastError() == ERROR_IO_PENDING) {// 等待异步写完成GetOverlappedResult(hSerial, &ov, &written, TRUE);  // TRUE = 阻塞等待
}

问题发生点:GetOverlappedResult(…, TRUE)
这个调用是阻塞等待写完成。理论上,当写操作超过 WriteTotalTimeoutConstant + len * WriteTotalTimeoutMultiplier 时,系统应返回 SerialTimeoutException,但实际并不总是这样。

原因解释
✅ 驱动未正确处理超时某些串口驱动(尤其是 USB 转串口)忽略了 SetCommTimeouts 中的写超时设置。
✅ 缓冲区已满写缓冲区写不进去,但驱动仍让 WriteFile 挂起等待,超时机制失效。
✅ GetOverlappedResult 不检查超时它本身不带超时参数,除非你用 WaitForSingleObject 另设超时,pyserial 没这么做。
✅ 虚拟串口/硬件死锁某些虚拟串口设备(如蓝牙串口、USB转串口)会因设备掉线导致永远不返回。

举个例子:

ser.write_timeout = 2.0
ser.write(b'X' * 102400)  # 写一个很大的包

如果目标串口设备已断开/没响应:

  • WriteFile() 会返回 ERROR_IO_PENDING
  • 然后 GetOverlappedResult(…, TRUE) 就会 永久阻塞
  • 此时 write_timeout 设置毫无作用

如果串口写缓冲区已满,驱动并不会立即失败, 而是等待硬件清空缓冲区(例如对端设备接受), 如果硬件永远不读(比如死机了),那它就永远挂住了。
WriteFile() 是异步的,但 GetOverlappedResult(…, TRUE) 是阻塞的。它等的是 overlapped 事件完成,但串口驱动只有在写成功/失败时才触发事件。

当使用 OVERLAPPED 结构进行串口异步写时,WriteFile() 返回后,并不会立刻知道是否成功,而是通过一个事件句柄 hEvent 在未来某个时间点通知“写操作完成”。

所以我们给出最终解决方案如下:

def write(self, data):"""Output the given byte string over the serial port."""if not self.is_open:raise PortNotOpenError()#~ if not isinstance(data, (bytes, bytearray)):#~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))# convert data (needed in case of memoryview instance: Py 3.1 io lib), ctypes doesn't like memoryviewdata = to_bytes(data)if data:#~ win32event.ResetEvent(self._overlapped_write.hEvent)n = win32.DWORD()success = win32.WriteFile(self._port_handle, data, len(data), ctypes.byref(n), self._overlapped_write)if self._write_timeout != 0:  # if blocking (None) or w/ write timeout (>0)if not success and win32.GetLastError() not in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING):raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError()))# Wait for the write to complete.WAIT_TIMEOUT = 0x00000102if self._write_timeout is None:timeout_ms = win32.INFINITEelse:timeout_ms = int(self._write_timeout * 1000)rc = win32.WaitForSingleObject(self._overlapped_write.hEvent, timeout_ms)if rc == WAIT_TIMEOUT:self.cancel_write()raise SerialTimeoutException('Write timeout due to device blocking.')win32.GetOverlappedResult(self._port_handle, self._overlapped_write, ctypes.byref(n), False)if win32.GetLastError() == win32.ERROR_OPERATION_ABORTED:return n.value  # canceled IO is no errorif n.value != len(data):raise SerialTimeoutException('Write timeout')return n.valueelse:errorcode = win32.ERROR_SUCCESS if success else win32.GetLastError()if errorcode in (win32.ERROR_INVALID_USER_BUFFER, win32.ERROR_NOT_ENOUGH_MEMORY,win32.ERROR_OPERATION_ABORTED):return 0elif errorcode in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING):# no info on true length provided by OS function in async modereturn len(data)else:raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError()))else:return 0

使用WaitForSingleObject:

  • 等待 overlapped 写事件完成
  • 最多等待 timeout_in_ms 毫秒
  • 返回值 rc 用于判断是成功、超时,还是其他错误
  • 如果超时,调用cancel_write() 掉取消一个挂起的 I/O 操作(如 ReadFile、WriteFile 等)(特别是在使用 重叠(Overlapped)I/O 时非常有用。)

如此,就完美的解决掉pyserial 在windows 下卡住的现象了。

http://www.lryc.cn/news/582518.html

相关文章:

  • 在PPT的文本框中,解决一打字,英文双引号就变成中文了
  • 4.权重衰减(weight decay)
  • NumPy-随机数生成详解
  • 初识单例模式
  • 【网络安全】服务间身份认证与授权模式
  • 【Flutter】面试记录
  • Next.js 实战笔记 2.0:深入 App Router 高阶特性与布局解构
  • 算法训练营DAY29 第八章 贪心算法 part02
  • ubuntu 操作记录
  • Python语言+pytest框架+allure报告+log日志+yaml文件+mysql断言实现接口自动化框架
  • 机制、形式、周期、内容:算法备案抽检复审政策讲解
  • 探索下一代云存储技术:对象存储、文件存储与块存储的区别与选择
  • 光流 | 当前光流算法还存在哪些缺点及难题?
  • ReactNative【实战系列教程】我的小红书 4 -- 首页(含顶栏tab切换,横向滚动频道,频道编辑弹窗,瀑布流布局列表等)
  • 闲庭信步使用图像验证平台加速FPGA的开发:第五课——HSV转RGB的FPGA实现
  • Java连接Emqx实现订阅发布消息
  • 恒创科技:香港站群服务器做seo站群优化效果如何
  • ReactNative【实战】瀑布流布局列表(含图片自适应、点亮红心动画)
  • Rust DevOps框架管理实例
  • ffmpeg下编译tsan
  • iOS 性能测试工具全流程:主流工具实战对比与适用场景
  • cocos2dx3.x项目升级到xcode15以上的iconv与duplicate symbols报错问题
  • CSP-S模拟赛二总结(实际难度大于CSP-S)
  • 力扣 239 题:滑动窗口最大值的两种高效解法
  • Android kotlin 协程的详细使用指南
  • C++--AVL树
  • 微前端框架对比
  • (16)Java+Playwright自动化测试-iframe操作-监听事件和执行js脚本
  • 精益管理与数字化转型的融合:中小制造企业降本增效的双重引擎
  • Nexus zkVM 3.0 及未来:迈向模块化、分布式的零知识证明