Pynput键盘监听器仅在终端最小化时生效的ROS2问题
问题:Pynput键盘监听在ROS2终端运行时仅在终端隐藏/最小化时生效
我通过Pynput读取键盘事件,在ROS2中发布底盘控制消息,代码如下:
import rclpy from my_interfaces.msg import Chassis import threading import time from pynput import keyboard import sys import rclpy.logging left = 0 right = 0 rclpy.init(args=sys.argv) node = rclpy.create_node('controller') pub = node.create_publisher(Chassis, 'cmd_vel', 10) def publisher(): global left, right msgToSend = Chassis() while True: msgToSend.left = left msgToSend.right = right pub.publish(msgToSend) time.sleep(0.1) def on_key_press(key): global left, right try: # Handle key press event print('Key is pressed') if(key.char == 'w'): left = 255 right = 255 elif(key.char == 's'): left = -255 right = -255 elif(key.char == 'a'): left = -255 right = 255 elif(key.char == 'd'): left = 255 right = -255 except AttributeError: # Some special keys don't have a `char` attribute pass def on_key_release(key): # Handle key release event global left, right try: if(key.char == 'w' or key.char =='a' or key.char =='s' or key.char =='d'): left = 0 right = 0 except AttributeError: pass def listener(): with keyboard.Listener( on_press=on_key_press, on_release=on_key_release) as listener: listener.join() def main(): publisher_thread = threading.Thread(target=publisher) publisher_thread.start() keyboard_thread = threading.Thread(target=listener) keyboard_thread.start() if __name__ == '__main__': main()
运行问题:通过ros2 run my_package controller在终端启动时,只有当所有终端(包括订阅/cmd_vel话题的终端)隐藏或最小化时,键盘事件才会被识别;在VS Code调试器/终端运行,或是开启终端“始终置顶”后切换到其他窗口时功能正常。尝试将键盘监听移到主线程,问题依旧。
原因分析
这是因为Pynput默认的Listener在终端窗口处于前台时,无法捕获全局键盘事件——终端作为前台窗口会优先捕获键盘输入,Pynput的监听器只能获取未被窗口拦截的事件。当终端隐藏/最小化,或设置为始终置顶但切换到其他窗口时,终端不再是键盘焦点,Pynput的全局监听器才能正常捕获按键事件。
另外原代码存在两个潜在问题:
- ROS2节点未执行
spin操作,可能导致节点内部消息处理异常 - 使用全局变量传递按键状态,线程安全没有保障
解决方法
1. 确保Pynput监听器捕获全局事件
显式配置监听器为全局钩子模式,同时确保系统权限足够(Linux下需确保用户有权限读取输入事件)。修改listener函数:
def listener(): # 显式设置全局监听,suppress=False表示不拦截事件(可根据需求调整) listener = keyboard.Listener( on_press=on_key_press, on_release=on_key_release, suppress=False) listener.start() listener.join()
2. 修复ROS2节点的spin逻辑
ROS2节点需要通过spin来维持节点生命周期和处理内部事件,添加spin线程:
def ros_spin(): rclpy.spin(node) def main(): # 启动ROS2 spin线程 spin_thread = threading.Thread(target=ros_spin) spin_thread.start() publisher_thread = threading.Thread(target=publisher) publisher_thread.start() keyboard_thread = threading.Thread(target=listener) keyboard_thread.start()
3. 优化线程安全(可选)
用threading.Lock保护全局变量的读写,避免多线程冲突:
import threading left = 0 right = 0 lock = threading.Lock() # 修改publisher函数 def publisher(): global left, right msgToSend = Chassis() while True: with lock: msgToSend.left = left msgToSend.right = right pub.publish(msgToSend) time.sleep(0.1) # 修改on_key_press函数 def on_key_press(key): global left, right try: print('Key is pressed') with lock: if(key.char == 'w'): left = 255 right = 255 elif(key.char == 's'): left = -255 right = -255 elif(key.char == 'a'): left = -255 right = 255 elif(key.char == 'd'): left = 255 right = -255 except AttributeError: pass # 修改on_key_release函数 def on_key_release(key): global left, right try: if(key.char in ['w','a','s','d']): with lock: left = 0 right = 0 except AttributeError: pass
4. 替代方案(推荐)
如果不需要自定义复杂的键盘逻辑,直接使用ROS2官方的键盘控制包teleop_twist_keyboard,安装后通过以下命令启动:
ros2 run teleop_twist_keyboard teleop_twist_keyboard
内容的提问来源于stack exchange,提问作者Somil Agrawal
相关产品推荐
相关产品推荐

