You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的全局监听器才能正常捕获按键事件。

另外原代码存在两个潜在问题:

  1. ROS2节点未执行spin操作,可能导致节点内部消息处理异常
  2. 使用全局变量传递按键状态,线程安全没有保障

解决方法

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 14:32:38