Загрузка данных
#!/usr/bin/env python3
"""
Драйвер для управления мышью и клавиатурой через Arduino Uno на Arch Linux.
Поддерживает автокалибровку нуля, мёртвую зону и плавное ускорение.
"""
import time
import sys
import serial
from pynput.mouse import Controller as MouseController, Button
from pynput.keyboard import Controller as KeyController
# === НАСТРОЙКИ УСТРОЙСТВА ===
SERIAL_PORT = '/dev/ttyACM0' # Если не находит, проверьте /dev/ttyUSB0
BAUD_RATE = 115200
# === НАСТРОЙКИ ДВИЖЕНИЯ МЫШИ ===
# Для горизонтального положения (пины смотрят вниз к кнопкам):
SWAP_AXES = False # Поменяйте на True, если горизонталь и вертикаль перепутаны
INVERT_X = False # Инвертировать движение влево/вправо
INVERT_Y = True # Инвертировать движение вверх/вниз (обычно True для экрана)
DEADZONE = 35 # Мертвая зона стика (чтобы курсор не дрожал в покое)
MAX_SPEED = 18 # Максимальная скорость курсора при крайнем отклонении
CURVE_EXP = 1.6 # Степень нелинейности (плавное прицеливание + быстрый рывок)
mouse = MouseController()
keyboard = KeyController()
# Состояния кнопок (1 - отпущено, 0 - зажато)
prev_states = {
'middle': 1,
'lmb': 1,
'rmb': 1,
'q': 1
}
def connect_serial():
try:
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
# Очищаем буфер после открытия порта
ser.reset_input_buffer()
print(f"[OK] Подключено к {SERIAL_PORT}")
return ser
except serial.SerialException as e:
print(f"[ОШИБКА] Не удалось открыть порт {SERIAL_PORT}: {e}")
print("Проверьте подключение платы и права пользователя (группа uucp).")
sys.exit(1)
def calibrate_center(ser, samples=40):
"""Калибрует нулевое положение стика при старте."""
print("Калибровка центра стика... Не трогайте джойстик 1 секунду.")
x_sum = 0
y_sum = 0
valid = 0
while valid < samples:
line = ser.readline().decode('utf-8', errors='ignore').strip()
if not line:
continue
parts = line.split(',')
if len(parts) == 6:
try:
x_sum += int(parts[0])
y_sum += int(parts[1])
valid += 1
except ValueError:
pass
center_x = x_sum / samples
center_y = y_sum / samples
print(f"[OK] Калибровка завершена! Центр: X={center_x:.1f}, Y={center_y:.1f}")
return center_x, center_y
def handle_mouse_button(button_obj, current_state, state_key):
global prev_states
if current_state == 0 and prev_states[state_key] == 1:
mouse.press(button_obj)
elif current_state == 1 and prev_states[state_key] == 0:
mouse.release(button_obj)
prev_states[state_key] = current_state
def handle_keyboard_key(key_char, current_state, state_key):
global prev_states
if current_state == 0 and prev_states[state_key] == 1:
keyboard.press(key_char)
elif current_state == 1 and prev_states[state_key] == 0:
keyboard.release(key_char)
prev_states[state_key] = current_state
def apply_curve(val, deadzone, max_speed):
"""Вычисляет смещение с учётом мёртвой зоны и кривой ускорения."""
abs_val = abs(val)
if abs_val <= deadzone:
return 0.0
# Нормализуем диапазон от 0.0 до 1.0
normalized = (abs_val - deadzone) / (512.0 - deadzone)
normalized = min(max(normalized, 0.0), 1.0)
# Экспоненциальное масштабирование скорости
speed = (normalized ** CURVE_EXP) * max_speed
return speed if val > 0 else -speed
def main():
ser = connect_serial()
time.sleep(1.5) # Ждем перезагрузку платы после Serial DTR
center_x, center_y = calibrate_center(ser)
print("\nДрайвер запущен! Управление:")
print(" - Стик: Движение курсора")
print(" - Нажатие на стик: СКМ (Колёсико)")
print(" - Кнопка D5: ЛКМ")
print(" - Кнопка D4: ПКМ")
print(" - Кнопка D3: Клавиша 'Q'")
print("Для выхода нажмите Ctrl + C\n")
try:
while True:
line = ser.readline().decode('utf-8', errors='ignore').strip()
if not line:
continue
parts = line.split(',')
if len(parts) == 6:
try:
raw_x = int(parts[0])
raw_y = int(parts[1])
stick_middle = int(parts[2])
btn_lmb = int(parts[3])
btn_rmb = int(parts[4])
btn_q = int(parts[5])
# Вычисляем отклонение относительно откалиброванного центра
dx_raw = raw_x - center_x
dy_raw = raw_y - center_y
if SWAP_AXES:
dx_raw, dy_raw = dy_raw, dx_raw
if INVERT_X:
dx_raw = -dx_raw
if INVERT_Y:
dy_raw = -dy_raw
# Расчет движения с кривой
dx = int(apply_curve(dx_raw, DEADZONE, MAX_SPEED))
dy = int(apply_curve(dy_raw, DEADZONE, MAX_SPEED))
if dx != 0 or dy != 0:
mouse.move(dx, dy)
# Обработка кликов мыши
handle_mouse_button(Button.middle, stick_middle, 'middle')
handle_mouse_button(Button.left, btn_lmb, 'lmb')
handle_mouse_button(Button.right, btn_rmb, 'rmb')
# Обработка клавиши клавиатуры 'q'
handle_keyboard_key('q', btn_q, 'q')
except ValueError:
pass
except KeyboardInterrupt:
print("\nЗавершение работы драйвера.")
finally:
ser.close()
if __name__ == '__main__':
main()
/*
* Arduino Uno - Контроллер мыши и кнопок
*
* Подключение джойстика:
* VRx -> A0
* VRy -> A1
* SW -> D2 (Клик стика - Средняя кнопка мыши СКМ)
*
* Подключение кнопок (второй вывод всех кнопок идет на GND):
* D3 -> Клавиша Q
* D4 -> Правая кнопка мыши (ПКМ)
* D5 -> Левая кнопка мыши (ЛКМ)
*/
const int pinX = A0;
const int pinY = A1;
const int pinStickBtn = 2; // СКМ
const int pinBtnQ = 3; // Клавиша 'Q'
const int pinBtnRMB = 4; // ПКМ
const int pinBtnLMB = 5; // ЛКМ
void setup() {
// Включаем внутреннюю подтяжку к 5V (INPUT_PULLUP)
// При нажатии на кнопку на пине будет LOW (0), при отпускании - HIGH (1)
pinMode(pinStickBtn, INPUT_PULLUP);
pinMode(pinBtnQ, INPUT_PULLUP);
pinMode(pinBtnRMB, INPUT_PULLUP);
pinMode(pinBtnLMB, INPUT_PULLUP);
Serial.begin(115200);
}
void loop() {
// Считываем аналоговые значения со стика (диапазон 0..1023)
int rawX = analogRead(pinX);
int rawY = analogRead(pinY);
// Считываем состояния кнопок
int stickBtn = digitalRead(pinStickBtn);
int btnLMB = digitalRead(pinBtnLMB);
int btnRMB = digitalRead(pinBtnRMB);
int btnQ = digitalRead(pinBtnQ);
// Передаем данные в формате CSV: X,Y,STICK_BTN,LMB,RMB,Q
Serial.print(rawX);
Serial.print(",");
Serial.print(rawY);
Serial.print(",");
Serial.print(stickBtn);
Serial.print(",");
Serial.print(btnLMB);
Serial.print(",");
Serial.print(btnRMB);
Serial.print(",");
Serial.println(btnQ);
// Частота опроса ~100 Гц для плавного движения
delay(10);
}