硬件模拟鼠标键盘(Arduino)
我的需求
利用硬件模拟鼠标键盘,并可用另一个台电脑通过网络发送指令操作游戏电脑,防止游戏反外挂程序检测封号。
我知道的模拟方法还有 Win32 SendMessage api
;驱动级模拟。总的来说还是用硬件来实现,简单并且稳如老狗。
俺并不是要做游戏外挂,只是目前AI太火,想着训练一个深度学习模型玩玩游戏。模拟鼠标键盘只是前提工作,额,下一步弄张好显卡(压上全部身家)。
N年前,Alpha Go
玩围棋谁还记得。好家伙,现在生成式AI又大火。
话说,大语言模型还是不是基于Attention模型?不知道我out没。
最近,马斯克的大语言模型和生成视频的Sora都放出来。
我说,AI大佬们,步子迈大了容易扯到蛋。
每周这么多模型,我都懒的点开看了。
实现
扯远了,回到正题:使用Arduino
模拟鼠标键盘。
如果你没有Arduino
,直接某宝成品几十块,比Arduino好使(貌似他们没有基于网络的)。
闲置若干年的Arduino
拿出来,吹吹灰先,型号MKR WIFI 1010
。

首先Arduino开发工具准备好,把驱动等依赖包装好,然后顺便点个LED灯压压惊。
由于这个Arduino支持wifi,我就做了一个通过网络发指令给Arduino,让它充当游戏PC的键盘鼠标。
直接上C代码 – 你想知道大雄和静香最后结局吗?
#include <WiFiNINA.h>
#include <WiFiUdp.h>
#include <Keyboard.h>
#include <Mouse.h>
#include <ArduinoQueue.h>
#include "arduino_secrets.h"
WiFiUDP Udp;
#define UDP_PORT 6789
extern void setupWifi();
extern void printWifiStatus();
extern void wind_mouse(int start_x, int start_y, int dest_x, int dest_y);
void setup() {
// put your setup code here, to run once:
//Initialize serial and wait for port to open:
pinMode(LED_BUILTIN, OUTPUT);
//Serial.begin(9600);
//while (!Serial) {}
setupWifi();
printWifiStatus();
Udp.begin(UDP_PORT);
randomSeed(analogRead(0));
Keyboard.begin();
Mouse.begin();
//Serial.println("Setup Done!");
}
void loop() {
// put your main code here, to run repeatedly:
digitalWrite(LED_BUILTIN, LOW);
getDataFromUTP();
execCommand();
delay(1);
}
/*
* source: 1 keyboard; 2 mouse;
* event: keyboard: 1 press, 2 release, 3 write; mouse 1 press, 2 release, 3 click, 4 move
* key: keyboard: keycode; mouse 1 MOUSE_LEFT, 2 MOUSE_RIGHT 3 MOUSE_MIDDLE
*/
struct Command {
uint8_t source;
uint8_t event;
uint16_t key;
uint16_t ext[4]; // start_x, start_y, end_x, end_y
};
int PACKAGE_SIZE = sizeof(struct Command);
ArduinoQueue<Command> CommandQ(16);
bool moving = false;
void execCommand() {
if (CommandQ.isEmpty()) {
return;
}
struct Command cmd = CommandQ.dequeue();
// Serial.println(cmd.source);
// Serial.println(cmd.event);
// Serial.println(cmd.key);
// Serial.println(cmd.ext[0]);
// Serial.println(cmd.ext[1]);
// Serial.println(cmd.ext[2]);
// Serial.println(cmd.ext[3]);
// Keyboard
if (cmd.source == 1) {
if (cmd.event == 1) {
Keyboard.press(cmd.key);
}
else if (cmd.event == 2) {
Keyboard.release(cmd.key);
}
else if (cmd.event == 3) {
Keyboard.write(cmd.key);
}
}
// Mouse
else if (cmd.source == 2) {
if (cmd.event == 1) {
switch(cmd.key) {
case 1:
Mouse.press(MOUSE_LEFT);
break;
case 2:
Mouse.press(MOUSE_RIGHT);
break;
case 3:
Mouse.press(MOUSE_MIDDLE);
break;
}
}
else if (cmd.event == 2) {
switch(cmd.key) {
case 1:
Mouse.release(MOUSE_LEFT);
break;
case 2:
Mouse.release(MOUSE_RIGHT);
break;
case 3:
Mouse.release(MOUSE_MIDDLE);
break;
}
}
else if (cmd.event == 3) {
switch(cmd.key) {
case 1:
Mouse.click(MOUSE_LEFT);
break;
case 2:
Mouse.click(MOUSE_RIGHT);
break;
case 3:
Mouse.click(MOUSE_MIDDLE);
break;
}
}
else if (cmd.event == 4) {
if (moving == false) {
moving = true;
wind_mouse(cmd.ext[0], cmd.ext[1], cmd.ext[2], cmd.ext[3]);
moving = false;
}
}
else if (cmd.event == 5) {
Mouse.move((short)cmd.ext[0], (short)cmd.ext[1], (short)cmd.ext[2]);
}
}
}
void getDataFromUTP() {
// if there's data available, read a packet
int packetSize = Udp.parsePacket();
if (packetSize) {
digitalWrite(LED_BUILTIN, HIGH);
//Serial.print("Received packet of size ");
//Serial.println(packetSize);
if (packetSize > PACKAGE_SIZE) {
return;
}
struct Command cmd;
memset((void*)&cmd, 0, PACKAGE_SIZE);
Udp.read((char*)&cmd, PACKAGE_SIZE);
if (CommandQ.isFull()) {
return;
}
CommandQ.enqueue(cmd);
}
}
/*
WindMouse algorithm. Calls the move_mouse kwarg with each new step.
Released under the terms of the GPLv3 license.
*/
#define sqrt3 1.7320508075688772
#define sqrt5 2.23606797749979
void wind_mouse(int start_x, int start_y, int dest_x, int dest_y) {
const int G_0=9; // magnitude of the gravitational fornce
const int W_0=3; // magnitude of the wind force fluctuations
float M_0=12; // maximum step size (velocity clip threshold)
const int D_0=12; // distance where wind behavior changes from random to damped
int current_x = start_x;
int current_y = start_y;
float v_x = 0, v_y = 0 , W_x = 0 , W_y = 0;
float dist = sqrt((dest_x-start_x)*(dest_x-start_x) + (dest_y-start_y)*(dest_y-start_y));
while (dist >= 1) {
float W_mag = min(W_0, dist);
if (dist >= D_0) {
W_x = W_x / sqrt3 + (2*(random(0, 1000) / 1000.0) - 1) * W_mag / sqrt5;
W_y = W_y / sqrt3 + (2*(random(0, 1000) / 1000.0) - 1) * W_mag / sqrt5;
}
else {
W_x /= sqrt3;
W_y /= sqrt3;
if (M_0 < 3)
M_0 = (random(0, 1000) / 1000.0) * 3 + 3;
else
M_0 /= sqrt5;
}
v_x += W_x + G_0 * (dest_x-start_x) / dist;
v_y += W_y + G_0 * (dest_y-start_y) / dist;
float v_mag = sqrt(v_x*v_x + v_y*v_y);
if (v_mag > M_0) {
float v_clip = M_0 / 2 + (random(0, 1000) / 1000.0) * M_0 / 2;
v_x = (v_x/v_mag) * v_clip;
v_y = (v_y/v_mag) * v_clip;
}
start_x += v_x;
start_y += v_y;
Mouse.move(v_x, v_y, 0);
unsigned long time = millis();
while(millis()-time < random(6, 10)) {
digitalWrite(LED_BUILTIN, LOW);
getDataFromUTP();
execCommand();
delay(1);
}
int move_x = (int) round(start_x);
int move_y = (int) round(start_y);
if (current_x != move_x || current_y != move_y) {
current_x = move_x;
current_y = move_y;
//move_mouse(current_x,current_y);
}
dist = sqrt((dest_x-start_x)*(dest_x-start_x) + (dest_y-start_y)*(dest_y-start_y));
}
}
void setupWifi() {
// check for the WiFi module:
if (WiFi.status() == WL_NO_MODULE) {
//Serial.println("Communication with WiFi module failed!");
// don't continue
while (true) {
digitalWrite(LED_BUILTIN, HIGH);
delay(300);
digitalWrite(LED_BUILTIN, LOW);
delay(300);
}
}
String fv = WiFi.firmwareVersion();
if (fv < WIFI_FIRMWARE_LATEST_VERSION) {
//Serial.println("Please upgrade the firmware");
}
// attempt to connect to WiFi network:
int status = WL_IDLE_STATUS;
while (status != WL_CONNECTED) {
//Serial.print("Attempting to connect to SSID: ");
//Serial.println(SECRET_SSID);
// Connect to WPA/WPA2 network. Change this line if using open or WEP network:
status = WiFi.begin(SECRET_SSID, SECRET_PASS);
// wait 5 seconds for connection:
delay(5000);
}
//Serial.println("Connected to WiFi");
}
void printWifiStatus() {
// print the SSID of the network you're attached to:
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
// print your board's IP address:
IPAddress ip = WiFi.localIP();
Serial.print("IP Address: ");
Serial.println(ip);
// print the received signal strength:
long rssi = WiFi.RSSI();
Serial.print("signal strength (RSSI):");
Serial.print(rssi);
Serial.println(" dBm");
}
arduino_secrets.h 定义wifi连接信息
#define SECRET_SSID "your wifi 2.4G"
#define SECRET_PASS "your wifi password"
理解代码的关键是构造了一个Command结构体,并用消息队列存储收到的事件。
Arduino相关文档
然后俺用python写了一个库,用来测试,如下
import socket
import struct
class Udp:
def __init__(self) -> None:
self.addr = ("192.168.89.162", 6789) # arduino地址
self.socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
def send(self, message):
self.socket.sendto(message, self.addr)
class Keyboard(Udp):
CODE = {
' ': 32,
'!': 33,
'"': 34,
'#': 35,
'$': 36,
'%': 37,
'&': 38,
"'": 39,
'(': 40,
')': 41,
'*': 42,
'+': 43,
',': 44,
'-': 45,
'.': 46,
'/': 47,
'0': 48,
'1': 49,
'2': 50,
'3': 51,
'4': 52,
'5': 53,
'6': 54,
'7': 55,
'8': 56,
'9': 57,
':': 58,
';': 59,
'<': 60,
'=': 61,
'>': 62,
'?': 63,
'@': 64,
'A': 65,
'B': 66,
'C': 67,
'D': 68,
'E': 69,
'F': 70,
'G': 71,
'H': 72,
'I': 73,
'J': 74,
'K': 75,
'L': 76,
'M': 77,
'N': 78,
'O': 79,
'P': 80,
'Q': 81,
'R': 82,
'S': 83,
'T': 84,
'U': 85,
'V': 86,
'W': 87,
'X': 88,
'Y': 89,
'Z': 90,
"[": 91,
'\\': 92,
']': 93,
'^': 94,
'_': 95,
'`': 96,
'a': 97,
'b': 98,
'c': 99,
'd': 100,
'e': 101,
'f': 102,
'g': 103,
'h': 104,
'i': 105,
'j': 106,
'k': 107,
'l': 108,
'm': 109,
'n': 110,
'o': 111,
'p': 112,
'q': 113,
'r': 114,
's': 115,
't': 116,
'u': 117,
'v': 118,
'w': 119,
'x': 120,
'y': 121,
'z': 122,
'{': 123,
'|': 124,
'}': 125,
'~': 126,
"KEY_LEFT_CTRL" : 128,
"KEY_LEFT_SHIFT" : 129,
"KEY_LEFT_ALT" : 130,
"KEY_LEFT_GUI" : 131,
"KEY_RIGHT_CTRL" : 132,
"KEY_RIGHT_SHIFT" : 133,
"KEY_RIGHT_ALT" : 134,
"KEY_RIGHT_GUI" : 135,
"KEY_TAB": 179,
"KEY_CAPS_LOCK": 193,
"KEY_BACKSPACE": 178,
"KEY_RETURN": 176,
"KEY_MENU": 237,
"KEY_INSERT": 209,
"KEY_DELETE": 212,
"KEY_HOME": 210,
"KEY_END": 213,
"KEY_PAGE_UP": 211,
"KEY_PAGE_DOWN": 214,
"KEY_UP_ARROW": 218,
"KEY_DOWN_ARROW":217,
"KEY_LEFT_ARROW":216,
"KEY_RIGHT_ARROW": 215,
"KEY_NUM_LOCK": 219,
"KEY_KP_SLASH": 220,
"KEY_KP_ASTERISK": 221,
"KEY_KP_MINUS": 222,
"KEY_KP_PLUS": 223,
"KEY_KP_ENTER": 224,
"KEY_KP_1": 225,
"KEY_KP_2": 226,
"KEY_KP_3": 227,
"KEY_KP_4": 228,
"KEY_KP_5": 229,
"KEY_KP_6": 230,
"KEY_KP_7": 231,
"KEY_KP_8": 232,
"KEY_KP_9": 233,
"KEY_KP_0": 234,
"KEY_KP_DOT": 235,
"KEY_ESC": 177,
"KEY_F1": 194,
"KEY_F2": 195,
"KEY_F3": 196,
"KEY_F4": 197,
"KEY_F5": 198,
"KEY_F6": 199,
"KEY_F7": 200,
"KEY_F8": 201,
"KEY_F9": 202,
"KEY_F10": 203,
"KEY_F11": 204,
"KEY_F12": 205,
"KEY_F13": 240,
"KEY_F14": 241,
"KEY_F15": 242,
"KEY_F16": 243,
"KEY_F17": 244,
"KEY_F18": 245,
"KEY_F19": 246,
"KEY_F20": 247,
"KEY_F21": 248,
"KEY_F22": 249,
"KEY_F23": 250,
"KEY_F24": 251,
"KEY_PRINT_SCREEN": 206,
"KEY_SCROLL_LOCK": 207,
"KEY_PAUSE": 208
}
def __init__(self) -> None:
super().__init__()
def press(self, key_code):
if type(key_code) == int:
self.send(struct.pack("BBH", 1, 1, key_code))
elif type(key_code) == str and len(key_code) == 1:
self.send(struct.pack("BBH", 1, 1, ord(key_code)))
else:
raise Exception("key_code must by int or char")
def release(self, key_code):
if type(key_code) == int:
self.send(struct.pack("BBH", 1, 2, key_code))
elif type(key_code) == str and len(key_code) == 1:
self.send(struct.pack("BBH", 1, 2, ord(key_code)))
else:
raise Exception("key_code must by int or char")
def write(self, key_code):
if type(key_code) == int:
self.send(struct.pack("BBH", 1, 3, key_code))
elif type(key_code) == str and len(key_code) == 1:
self.send(struct.pack("BBH", 1, 3, ord(key_code)))
else:
raise Exception("key_code must by int or char")
class Mouse(Udp):
MOUSE_LEFT = 1
MOUSE_RIGHT = 2
MOUSE_MIDDLE = 3
def __init__(self) -> None:
super().__init__()
def press(self, key=MOUSE_LEFT):
self.send(struct.pack("BBH", 2, 1, key))
def release(self, key=MOUSE_LEFT):
self.send(struct.pack("BBH", 2, 2, key))
def click(self, key=MOUSE_LEFT):
self.send(struct.pack("BBH", 2, 3, key))
def move(self, start_x, start_y, end_x, end_y):
self.send(struct.pack("BBHHHHH", 2, 4, 0, start_x, start_y, end_x, end_y))
def move_abs(self, x, y, wheel=0): # -127 128
self.send(struct.pack("BBHhhh", 2, 5, 0, x, y, wheel))
在另一台机器上测试执行。
keyboard = Keyboard()
keyboard.write("Hello")
mouse = Mouse()
mouse.move(100,100, 800,800)
mouse.click()
每收到一个事件,Arduino自带的灯都会闪一下并执行相关指令。
完工。
PS.网络毕竟有延迟,但是经过测试,基本远程操作无感。也许应该在Arduino上连个遥杆玩玩。手边就有摇杆,算了,闲的蛋疼的时候在折腾。
小PS.没想到啊没想到,大水博主C语言的功力竟然还没退化。
小小PS.爷们知道下载模型也没用,但是还是忍不住下了。
大佬,放开那个B200,让我来。
小小小PS.今天晚间看了一部布拉德皮特演的早期电影《Cool World》,挺烂的,可以用来催眠。
