Загрузка данных
#!/usr/bin/env python3
import time
import sys
import serial
from evdev import UInput, ecodes as e
SERIAL_PORT = '/dev/ttyUSB0'
BAUD_RATE = 115200
# Настройки направления стика
SWAP_AXES = False # True, если перепутаны горизонталь и вертикаль
INVERT_X = False # Инвертировать лево/право
INVERT_Y = True # Инвертировать верх/низ
DEADZONE = 35 # Мёртвая зона (покой)
MAX_SPEED = 16 # Скорость курсора
CURVE_EXP = 1.6 # Плавность разгона
# Регистрируем виртуальное устройство (мышь + клавиша Q)
capabilities = {
e.EV_REL: [e.REL_X, e.REL_Y],
e.EV_KEY: [
e.BTN_LEFT,
e.BTN_RIGHT,
e.BTN_MIDDLE,
e.KEY_Q
]
}
try:
ui = UInput(capabilities, name='Arduino-Custom-Controller')
except PermissionError:
print("[ОШИБКА] Нет прав доступа к /dev/uinput!")
print("Выполни: sudo chmod 666 /dev/uinput")
sys.exit(1)
try:
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
ser.reset_input_buffer()
except serial.SerialException as err:
print(f"[ОШИБКА] Не удалось открыть {SERIAL_PORT}: {err}")
print("Выполни: sudo chmod 666 /dev/ttyUSB0")
sys.exit(1)
print("[OK] Виртуальное устройство ядра создано!")
print("Калибровка центра стика... Не трогай джойстик 1 сек...")
# Автокалибровка покоя стика
x_sum, y_sum, samples = 0, 0, 40
valid = 0
while valid < samples:
line = ser.readline().decode('utf-8', errors='ignore').strip()
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}")
# Состояния кнопок (1 - отпущено, 0 - нажато из-за INPUT_PULLUP)
prev_states = {
'middle': 1,
'lmb': 1,
'rmb': 1,
'q': 1
}
def handle_btn(code, cur_state, key_name):
global prev_states
if cur_state == 0 and prev_states[key_name] == 1:
ui.write(e.EV_KEY, code, 1) # Зажать
ui.syn()
elif cur_state == 1 and prev_states[key_name] == 0:
ui.write(e.EV_KEY, code, 0) # Отпустить
ui.syn()
prev_states[key_name] = cur_state
def apply_curve(val):
abs_v = abs(val)
if abs_v <= DEADZONE:
return 0
norm = (abs_v - DEADZONE) / (512.0 - DEADZONE)
norm = min(max(norm, 0.0), 1.0)
spd = int((norm ** CURVE_EXP) * MAX_SPEED)
return spd if val > 0 else -spd
print("\nДрайвер запущен под Wayland! Нажми Ctrl+C для выхода.")
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_btn = 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 = apply_curve(dx_raw)
dy = apply_curve(dy_raw)
# Движение мыши
if dx != 0 or dy != 0:
ui.write(e.EV_REL, e.REL_X, dx)
ui.write(e.EV_REL, e.REL_Y, dy)
ui.syn()
# Обработка кнопок
handle_btn(e.BTN_MIDDLE, stick_btn, 'middle')
handle_btn(e.BTN_LEFT, btn_lmb, 'lmb')
handle_btn(e.BTN_RIGHT, btn_rmb, 'rmb')
handle_btn(e.KEY_Q, btn_q, 'q')
except ValueError:
pass
except KeyboardInterrupt:
print("\nОстановка...")
finally:
ser.close()
ui.close()