Загрузка данных


#!/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()