Funpack3-1 使用XG24-EK2703A制作可编程鼠标
本项目使用了 Silicon Labs 单片机制作了一款可编程的鼠标,并提供了其对应的 Python 控制代码。设备从电脑上接收控制信号,并将控制信号转化为HID报文并通过蓝牙发送到任意设备上。
标签
BLE
Funpack活动
鼠标
2x3j
更新2024-03-12
182

项目介绍

本项目使用了 Silicon Labs 单片机制作了一款可编程的鼠标,并提供了其对应的 Python 控制代码。设备从电脑上接收控制信号,并将控制信号转化为HID报文并通过蓝牙发送到任意设备上。通过设置不同的报文信息,理论上可以实现任意的鼠标操作,甚至包含人类不可做到的毫秒级点击、移动。

该项目的背景是目前一部分应用程序对鼠标自动操作进行了拦截,导致无法使用Python进行自动化控制。但是可编程鼠标作为一个外界的物理硬件不会被应用程序所拦截,可以正常的使用。

开发平台

EFR32xG24 Explorer套件是一个基于EFR32MG24片上系统的超低成本、小尺寸开发和评估平台,该套件专注于物联网应用的快速原型化和概念创建2.4 GHz无线协议的IoT应用程序,包括蓝牙LE、蓝牙Mesh、Zigbee、Thread 和 Matter。它是围绕EFR32MG24 SoC设计的,而EFR32MG24 SoC是开发能源友好型联网物联网应用的理想器件系列。

设计思路

在这个项目中,我在硬件方面实现了串口信息的接收、转化和提取,同时还将上位机部分的鼠标控制代码抽象为了一个 Python 类,可以插入到任意现有的 Python 程序中,以替代 PyautoGUI 等框架。目前已经实现了单击、移动等常见的鼠标操作。与此同时,单片机部分还额外实现了键盘的控制代码,可以自行进行补充键盘部分的控制功能。

硬件部分使用了 Silicon Labs 官方的 IDE 进行开发。在现有的 Bluetooth - HID Keyboard 模板上,增加了关于HID鼠标相关的报文。同时额外增加了一个状态变量,使用BTN0按钮中断改变收发状态。总体的流程图如下图所示:

当单片机启动之后会进入一个输入字符的循环,接收到结束符后会将接收的信息设置为 HID report,并通过蓝牙发送到已连接的设备上。一个按键中断。板上的 BTN0 按钮按下就会切换系统的控制状态。当控制状态为 false 的时候,将不会响应任何来自电脑的操作请求。

上位机部分使用 Python 代码实现。功能设计思路如下:

  • 要打开串口
  • 将用户的抽象操作转化为HID报文
  • 通过串口将报文发送到单片机上

设计要点

在MCU端,由于目前使用的程序修改自官方的 Bluetooth - HID Keyboard 示例上,其本身不具备鼠标的功能。我参照博客实现了键鼠复合设备的HID reportmap。


    0x05, 0x01,   // Usage Page (Generic Desktop)
    0x09, 0x06,   // Usage (Keyboard)
    0xa1, 0x01,   // Collection (Application)
    0x85, 0x01,   //   Report ID (1)
    0x05, 0x07,   //   Usage Page (Keyboard)
    0x19, 0xe0,   //   Usage Minimum (Keyboard LeftControl)
    0x29, 0xe7,   //   Usage Maximum (Keyboard Right GUI)
    0x15, 0x00,   //   Logical Minimum (0)
    0x25, 0x01,   //   Logical Maximum (1)
    0x75, 0x01,   //   Report Size (1)
    0x95, 0x08,   //   Report Count (8)
    0x81, 0x02,   //   Input (Data, Variable, Absolute) Modifier byte
    0x95, 0x01,   //   Report Count (1)
    0x75, 0x08,   //   Report Size (8)
    0x81, 0x01,   //   Input (Constant) Reserved byte
    0x95, 0x06,   //   Report Count (6)
    0x75, 0x08,   //   Report Size (8)
    0x15, 0x00,   //   Logical Minimum (0)
    0x25, 0x65,   //   Logical Maximum (101)
    0x05, 0x07,   //   Usage Page (Key Codes)
    0x19, 0x00,   //   Usage Minimum (Reserved (no event indicated))
    0x29, 0x65,   //   Usage Maximum (Keyboard Application)
    0x81, 0x00,   //   Input (Data, Array) Key arrays (6 bytes)
    0xc0          // End Collection
    0x05, 0x01,   // USAGE_PAGE (Generic Desktop)
    0x09, 0x02,   // USAGE (Mouse)
    0xa1, 0x01,   // COLLECTION (Application)
    0x85, 0x02,   //   REPORT_ID (2)
    0x09, 0x01,   //   USAGE (Pointer)
    0xa1, 0x00,   //   COLLECTION (Physical)
    0x05, 0x09,   //     Usage Page (Buttons)
    0x19, 0x01,   //     Usage Minimum (1)
    0x29, 0x03,   //     Usage Maximum (3)
    0x15, 0x00,   //     Logical Minimum (0)
    0x25, 0x01,   //     Logical Maximum (1)
    0x95, 0x03,   //     Report Count (3)
    0x75, 0x01,   //     Report Size (1)
    0x81, 0x02,   //     Input(Data, Variable, Absolute); 3 button bits
    0x95, 0x01,   //     Report Count(1)
    0x75, 0x05,   //     Report Size(5)
    0x81, 0x03,   //     Input(Constant);                 5 bit padding
    0x05, 0x01,   //     Usage Page (Generic Desktop)
    0x09, 0x30,   //     Usage (X)
    0x09, 0x31,   //     Usage (Y)
    0x09, 0x38,   //     Usage (Wheel)
    0x15, 0x81,   //     Logical Minimum (-127)
    0x25, 0x7F,   //     Logical Maximum (127)
    0x75, 0x08,   //     Report Size (8)
    0x95, 0x03,   //     Report Count (3)
    0x81, 0x06,   //     Input(Data, Variable, Relative); 3 position bytes (X,Y,Wheel)
    0xc0,         //   END_COLLECTION
    0xc0          // END_COLLECTION

在实践之后,我发现官方提供的蓝牙发送代码中不包含对 Report ID 进行设置的功能。探索未果后,在群友的指导下完成了 ReportID 的设置。在图形化设置界面中新增一个 Report 并修改 Report ID,即可在自动生成的代码中找到其对应的索引值。

串口接收部分通过不断的读取字符实现用户输入字符的存储功能,当检测到结束字符时会发送对应的结果。这一过程中存在的问题是,当用户没有输入时,getchar 返回的值为 0xff,这导致的直接问题是,在发送鼠标和键盘的移动距离时无法发送 0xff。因此在上位机部分对这部分功能进行了一些调整,当输入数值的范围超过上限时,会自动进行调整。


上位机代码部分中,有两个点需要注意。第一点是默认的鼠标 HID ReportMap 中,X轴和Y轴的移动距离是使用八位有符号数进行表示的。在上位机发送对应的数值时,由于Python的编码的原因,需要将有符号输入其对应的补码转换成无符号数的形式,以保证发送的正确性。

第二点是 windows 本身允许调整鼠标移动速度带来的移动数值误差。在编写移动到绝对坐标功能时,我本来的实现思路是使用 PyautoGUI 库获取鼠标的当前位置,再与目标位置计算得到X轴和Y轴的相对移动距离,之后进行移动。但实际上,由于 Windows 平台允许用户调整鼠标的移动速度,设置的数值并不能对应实际移动的像素值。为此我在移动之后会检查当前位置是否与目标位置仍有偏差,如果偏差大于某个值,就继续重复移动过程。由于每次移动之后等待的间隔只有0.1秒,所以上述过程对于用户来说其实是不可感知的。

def move(self, x, y):
        """Move the mouse to the specified position."""
        while True:
            cur_x, cur_y = pyautogui.position()
            x_move = x - cur_x
            y_move = y - cur_y
            # calculate the real move distance based on the screen resolution and the system mouse speed setting.
            x_move = int(x_move / self.windows_mouse_speed)
            y_move = int(y_move / self.windows_mouse_speed)

            print(f" cur pos {cur_x} {cur_y}, diff {x_move} {y_move}")

            if abs(x_move) < self.move_threshold and abs(y_move) < self.move_threshold:
                print("break")
                break

            # The max distance of a move event is 126 units in any direction. To guarantee the uart transmission.
            # We need to split the move event into several smaller events.
            while abs(x_move) > 0 or abs(y_move) > 0:
                print(f"move {x_move} {y_move}")
                if abs(x_move) > 126:
                    self.set_x(126 if x_move > 0 else -126)
                    x_move -= 126 if x_move > 0 else -126
                else:
                    self.set_x(x_move)
                    x_move = 0

                if abs(y_move) > 126:
                    self.set_y(126 if y_move > 0 else -126)
                    y_move -= 126 if y_move > 0 else -126
                else:
                    self.set_y(y_move)
                    y_move = 0

                print(self.x, self.y)
                self.send()
            time.sleep(0.1)

最后为了测试这部分代码的普适性,我下载了一个来自 GitHub 的开源 PVZ-OpenCV 项目来测试鼠标的移动效果。具体来说,我把原来项目当中使用 PyautoGUI 进行移动和点击的代码替换成了自己的鼠标类。实际效果如视频所示,鼠标有着较快的反应速度。

总结

关于这个项目的总结是,目前 SiliconLabs 官方的一些教程并不是很完整。在尝试修改 HID report map 的时候,大部分教程都是根据 STM32 进行迁移的,实现起来有些麻烦。同时 SiliconLabs 的蓝牙的配置是完全图形化的,但是我没有找到相关的文档。就导致更改 HID report ID 的时候出现了很多问题,不过好在有群友的帮助。

自己实现一个蓝牙键盘鼠标是自接触硬件以来一直的一个小目标,有机会能够借着这次项目进行实现,整体还是比较开心的。

参考:

附件下载
serial-mouse.zip
团队介绍
Individual team
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号