D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Предисловие:
В данной статье будет рассказано, как реализовать эмуляцию мыши, используя Arduino, но перед тем, как начать делать эмуляцию через Arduino, хочется рассказать о некоторых поправках в проекте, которые были сделаны вне статей. Правки небольшие, но всё же считаю нужным кратко рассказать о них для полного понимания того, как устроен проект от начала и до конца.Разделение проекта на разные файлы:
Первое, что было сделано, — это разделение логики каждой из версий нейронной сети на несколько файлов. Например, раньше в файле cs2.py были функции детекции, создания скриншотов, антиотдачи двух типов, аима двух типов, триггербота. Теперь же всё, что связано с эмуляцией, находится в отдельном файле. Аналогичная ситуация и для версии нейронной сети default.py.Также функция load_config использовалась практически в каждом файле проекта, и в каждом файле она была продублирована. Было принято решение сделать отдельный файл с функцией для загрузки конфига и во всех остальных файлах проекта просто её вызывать. Таким образом, суммарно стало меньше строк примерно на 100.
Если читали предыдущие статьи, то могли знать, что визуализацию объектов на экране я делал только для версии нейронной сети для игры CS2. Вне статьи был добавлен аналогичный функционал и к нейронной сети default.
Исправление ошибок в интерфейсе:
Также была проблема в веб-интерфейсе: при загрузке страницы не отображались данные из объектов true/false, например, включения и выключения триггербота для CS2. Проблема заключалась в JS-функции принятия данных из конфига и записи данных в объект. А если точнее, то из конфига брались логические данные, а объект на странице принимает строковые. Чтобы это исправить, нужно принятые из конфига данные конвертировать в строковые.JavaScript: Скопировать в буфер обмена
document.getElementById('cs2_trigger_bot').value = config.cs2_trigger_bot ? 'true' : 'false';
? 'true' : 'false'; конвертирует логическое значение в строковое. Так необходимо сделать для всех объектов, принимающих true или false.
Вот как выглядит структура проекта на данный момент:
Редизайн панели:
Также был полностью переделан дизайн веб-интерфейса. В самом дизайне ничего особенного и сложного нет, но пару моментов объяснить всё же стоит.Как видно на скриншоте, была добавлена боковая панель. Боковая панель является отдельным HTML-файлом, который вызывается в HTML-файле с основными настройками нейронной сети. Кнопка смены игры расположена именно в файле с боковой панелью, а не в основном файле с настройками. Как могли некоторые подумать, функцию для принятия данных из конфига и отправки данных из объекта выбора игры также нужно будет разместить в файле с боковой панелью, где находится сам объект. Но это не так. По сути, оба эти файла как бы соединяются в один, и функция из файла с основными настройками спокойно будет обновлять данные в объекте из файла с боковой панелью. Но для этого нужно немного изменить код. А для начала рассмотрим файл боковой панели, а точнее, объект с выбором игры:
HTML: Скопировать в буфер обмена
Код:
<form id="config-form_game">
<select class="game_selector" id="game" name="game">
<option value="cs2">CS2</option>
<option value="default">DEFAULT</option>
</select><br>
</form>
Как видно, селектор, то есть объект с выбором игры, находится внутри формы с id config-form_game. Теперь рассмотрим JS-код из файла с основными настройками, где происходит загрузка данных из конфига при открытии и обновлении страницы:
JavaScript: Скопировать в буфер обмена
Код:
document.addEventListener("DOMContentLoaded", function() {
loadConfig();
const form = document.getElementById("config-form");
form.addEventListener('input', updateConfig);
});
Как видно, первым делом запускается функция загрузки данных из конфига. Затем создается константа (переменная), в которую записывается объект. В данном случае это объект форма, в котором находятся основные объекты с настройками.
form.addEventListener('input', updateConfig); — данная строка отвечает за вызов ивента, который будет срабатывать при взаимодействии с объектом из ранее записанной переменной (form), и после взаимодействия вызовется функция updateConfig (которая берет данные из объектов, и дальше уже данные попадают в конфиг). Но, как стало понятно, взаимодействие происходит с формой, где находятся основные настройки из основного файла, а не из файла боковой панели. Чтобы это исправить, нужно добавить еще одну константу, но записать в нее форму config-form_game, в которой находится объект выбора игры.
JavaScript: Скопировать в буфер обмена
const formGame = document.getElementById("config-form_game");
Затем нужно вызвать ивент, который будет срабатывать при взаимодействии с объектом, но на этот раз указать переменную не form, а formGame. Таким образом, ивент будет срабатывать при взаимодействии с объектом выбора игры и затем будет вызываться функция для обновления данных в конфиге.
JavaScript: Скопировать в буфер обмена
formGame.addEventListener('input', updateConfig);
Возможно, некоторые могут задаться вопросом, почему бы форму с объектом смены игры просто не сделать с таким же id, как и форма, в которой находятся основные настройки. Но так делать не желательно: указывать одинаковые id у нескольких объектов считается грубой ошибкой, и такой фокус попросту не сработает.
С тем, как обновлять данные из объекта другой страницы, закончено. Теперь можно рассмотреть, как собственно добавляются данные одной страницы на другую.
В данном случае у нас есть два HTML-файла: первый файл — это боковая панель, второй файл — это все настройки нейронной сети. В HTML-файле боковой панели нужно указать это:
HTML: Скопировать в буфер обмена
{% block content %}{% endblock %}
Указать это нужно там, где должны быть данные из второго файла, то есть файла с основными настройками. В данном случае — просто внутри body.
Затем, внутри HTML-файла с настройками нужно указать эту строку:
HTML: Скопировать в буфер обмена
{% extends "base.html" %}
Указать её нужно выше всех объектов, которые должны быть использованы.
Затем нужно указать эту строку:
HTML: Скопировать в буфер обмена
{% block content %}
Указать её нужно под строкой, которая была указана выше. Она означает начало блока с контентом для переноса.
В конце файла с настройками, а именно там, где заканчиваются объекты и JS-функции, нужно указать эту строку:
HTML: Скопировать в буфер обмена
{% endblock %}
Эта строка означает конец блока с контентом, который будет перенесён.
В итоге, когда используется {% extends "base.html" %} в файле с настройками, это означает, что он будет наследовать файл с боковой панелью и вставлять свои объекты и другой код, находящийся внутри {% block content %}{% endblock %}, в место, указанное в base.html, то есть в {% block content %}{% endblock %}, который определён в файле с боковой панелью.
С использованием контента из разных файлов в одном разобрались. Теперь считаю нужным рассказать, как сделать в боковой панели кнопки, при нажатии на которые открываются страницы с настройками разных нейросетей. Как уже стало понятно, есть несколько страниц: страница боковой панели, страница с настройками нейросети для CS и страница с настройками нейросети default.
В Python-файле, в котором находится инициализация Flask, нужно указать маршруты до этих страниц.
Python: Скопировать в буфер обмена
Код:
@app.route('/cs2')
def cs2():
return render_template('cs2.html')
@app.route('/default')
def default():
return render_template('default.html')
Далее нужно перейти в HTML-файл боковой панели и создать объект <a></a>. Это объект для создания гиперссылок, то есть при нажатии на странице на этот объект будет происходить переход на ссылку, указанную внутри этого объекта.
HTML: Скопировать в буфер обмена
Код:
<a href="{{ url_for('default') }}" class="sidebar_item">
<h4 class="sidebar_text">Default</h4>
</a>
<a href="{{ url_for('cs2') }}" class="sidebar_item">
<h4 class="sidebar_text">CS2</h4>
</a>
На этом разбор всех изменений, сделанных вне статьи, закончен. Старался сделать его как можно компактнее, но при этом понятным, если вдруг кому-то интересен абсолютно каждый шаг при разработке нейронной сети.
Работа с Arduino и обновление Python кода:
Теперь можно приступить к работе с Arduino и к обновлению Python-логики. Для начала нужно выяснить, какая Arduino подойдет.Какой Arduino подойдет:
Подходящими Arduino являются те, которые поддерживают подключение к компьютеру как HID-устройство (Human Interface Device). То есть мышка, клавиатура, руль и т.д. А способны на такое платы с микроконтроллером ATmega32u4. В списке таких плат находятся Leonardo, Pro Micro, Micro. Также есть возможность некоторые другие версии Arduino перепрошить на работу как HID-устройство, но в данном случае будет использоваться Arduino Leonardo.После того как было разобрано, какой Arduino потребуется для реализации эмуляции, можно приступать к написанию кода.
Инициализация Arduino в Python коде:
Первым делом нужно создать в проекте новый файл, который будет называться arduino_initialization.py. В данном файле будут находиться две функции: функция поиска подключенного Arduino и функция подключения к ArduinoФункция поиска Arduino:
Первой в списке на реализацию будет функция поиска подключенного к ПК Arduino. Функция будет называться find_arduino_port().Для того чтобы найти подключенный Arduino, нужно пройтись по всем портам компьютера и найти тот, к которому подключено устройство с названием Arduino. Чтобы реализовать данную механику проверки портов, нужно импортировать библиотеку pyserial. Эта библиотека как раз и предназначена для работы с портами.
Python: Скопировать в буфер обмена
Код:
import serial
import serial.tools.list_ports
Теперь можно приступить к функции, и первое, что в ней нужно сделать, так это добавить переменную, в которой будет вызываться функция из библиотеки pyserial.
Python: Скопировать в буфер обмена
ports = serial.tools.list_ports.comports()
Данная функция получает список всех доступных на ПК портов.
Затем нужно создать цикл for, который будет перебирать каждый из портов и проверять его на то, чтобы подключенное к порту устройство было с названием Arduino.
Python: Скопировать в буфер обмена
Код:
for port in ports:
if "Arduino" in port.description or "Arduino" in port.manufacturer:
return port.device
port.manufacturer содержит информацию о производителе подключенного к порту устройства.
return port.device возвращает название подключенного к порту устройства.
Далее нужно вне цикла for указать возврат None, если подходящих портов не найдено.
Python: Скопировать в буфер обмена
Код:
print("Не удалось найти Arduino.")
return None
Затем нужно вызывать эту функцию. Сделать это нужно просто в файле вне какой-либо функции.
Python: Скопировать в буфер обмена
arduino_port = find_arduino_port()
Функция подключения к Arduino:
Теперь можно начать реализовывать функцию подключения к найденному Arduino. Функция будет называться connect_to_arduino, в аргументах которой будет указан baud_rate=115200. Данное значение является скоростью, с которой будет реализована передача данных, а именно количеством битов, которые могут быть переданы в секунду.Python: Скопировать в буфер обмена
def connect_to_arduino(baud_rate=115200):
Затем, внутри функции нужно добавить проверку if, на то чтобы, если arduino_port равняется None, то возвращать None.
Python: Скопировать в буфер обмена
Код:
if not arduino_port:
return None
Затем нужно внутри функции указать try except. Внутри try будет код для подключения к Arduino, а в except — уведомление, если подключиться не получилось. В try нужно создать объект Serial с такими параметрами, как: название подключенного к порту устройства (Arduino), скорость передачи данных и время ожидания записи данных в Arduino.
Python: Скопировать в буфер обмена
ser = serial.Serial(arduino_port, baud_rate, timeout=1, write_timeout=5)
Таким образом, результат вызова объекта записывается в переменную ser.
Далее можно добавить паузу, для того чтобы соединение успело произойти:
Python: Скопировать в буфер обмена
time.sleep(1)
Затем нужно добавить return, который будет возвращать переменную ser, в которой находится результат вызова объекта Serial.
Python: Скопировать в буфер обмена
Код:
print(f"Подключено к {arduino_port} со скоростью {baud_rate}")
return ser
Теперь в except нужно указать возврат None (except — это обработка ошибок, если код внутри try не был выполнен).
Python: Скопировать в буфер обмена
Код:
except serial.SerialException as e:
print(f"Не удалось открыть порт {arduino_port}: {e}")
return None
С функцией подключения к Arduino закончено, теперь эту функцию нужно вызвать:
Python: Скопировать в буфер обмена
ser = connect_to_arduino()
Изменение логики нейросети CS2:
Теперь можно изменить логику наводки и триггербота для нейросети CS2. Т.к. логика нейросети была разделена на несколько файлов, все функции, работающие с эмуляцией мыши, были вынесены в отдельный Python файл, если точнее, в cs2_mouse_emulated.py.Изменение функция наведения:
Первой будет изменена функция aim для наведения на врагов. Нужно найти внутри функции строку с эмуляцией мыши.Python: Скопировать в буфер обмена
win32api.mouse_event(win32con.MOUSEEVENTF_MOVE, int(aim_step_x), int(aim_step_y), 0, 0)
Затем нужно заменить ее на эти строки:
Python: Скопировать в буфер обмена
Код:
command = f"{aim_step_x},{aim_step_y}\n"
ser.write(command.encode())
ser.write — это метод объекта ser (экземпляра класса serial.Serial), который был создан ранее в Python файле инициализации Arduino внутри функции подключения к Arduino.
command.encode() означает, что берется строка из переменной command и конвертируется в байты.
Таким образом, ser.write берет данные из переменной с данными для перемещения и отправляет на Arduino.
На этом можно было бы уже закончить работу с функцией наводки и начать писать код для самого Arduino, но есть одно НО. Дело в том, что данные на Arduino будут поступать очень быстро и очень много, в связи с чем буфер Arduino будет заполнен, если на него будут беспрерывно поступать команды в течение секунд 5-10, то есть если вы нажмете ЛКМ и не будете ее отпускать долгое время. Из-за этого Arduino не сможет считывать новые данные, и это вызовет краш программы. Чтобы этого избежать, нужно обрабатывать ошибку таймаута записи данных на Arduino и при ее возникновении просто очищать буфер. Чтобы это сделать, нужно добавить код, который был написан ранее для замены эмуляции на отправку команды на Arduino, внутрь try.
Python: Скопировать в буфер обмена
Код:
try:
command = f"{aim_step_x},{aim_step_y}\n"
ser.write(command.encode())
time.sleep(load_config("cs2_aim_time_sleep"))
А внутрь except нужно добавить это:
Python: Скопировать в буфер обмена
Код:
except serial.SerialTimeoutException:
print("Превышено время ожидания для записи данных на Arduino")
ser.reset_output_buffer()
ser.reset_input_buffer() сбрасывает входной буфер, чтобы освободить место для новых данных, которые могут поступать на Arduino.
Теперь с функцией наведения закончено, и можно переделать функцию триггербота. Но перед этим хочется уточнить, почему не была изменена функция антиотдачи. Если вы читали предыдущие статьи, то можете помнить, что функция антиотдачи не производит эмуляцию, а лишь берет координаты макроса и передает их в функцию наведения. То есть наведение происходит относительно координат антиотдачи прямо в функции aim, в связи с этим функция антиотдачи не использует эмуляцию, поэтому и редактировать ее не требуется.
После разъяснения можно приступать к изменению функции триггербота.
Изменение функции триггербота:
Внутри функции нужно найти эти строки эмуляции нажатия и отпускания левой кнопки мыши:Python: Скопировать в буфер обмена
Код:
# Нажатие левой кнопки мыши
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)
# Отпускание левой кнопки мыши
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)
И заменить их на одну маленькую строчку, отправляющую команду на Arduino.
Python: Скопировать в буфер обмена
ser.write(b'CLICK\n')
В дальнейшем внутри кода Arduino будет проверка на данную команду. Если данная команда получена, то внутри кода Arduino будет происходить эмуляция нажатия и отпускания кнопки.
С Python-кодом закончено, и теперь можно начать писать на самом Arduino.
Подготовка к работе с Arduino:
Для того чтобы начать писать код, потребуется Скачать
View hidden content is available for registered users!