Как я создал устройство, которое может удаленно эмулировать мышку и клавиатуру на stm32

от автора

1. Как появилась идея?

Всем привет, совсем недавно я начал изучать протокол USB на STM32F103C8, а именно HID-устройства. Я такой человек, который не сильно любит теорию, но обожает учиться всему на практике, поэтому я тут же начал думать над будущим проектом. И я вспомнил, что совсем недавно заказал себе wifi модуль — ESP8266.

ESP8266

ESP8266

И тут ко мне пришла идея: почему бы не объединить STM32f103c8 и ESP8266 в одно устройство — UsbDeviceHacker (так я его назвал). Моя идея заключалось в том, чтобы управлять мышкой и клавиатурой дистанционно по api, который будет работать на веб сервере, запущенном на ESP. Соответственно, ESP будет выступать в роли master устройства, а STM32 в роли slave.

Многие могут спросить: почему не сделать все сильно проще и вместо wifi использовать bluetooth, однако использование wifi будет намного интереснее, потому что наш веб сервер будет разворачиваться в локальной сети, и, соответственно, каждое устройство в ней сможет управлять девайсом просто отправив HTTP запрос на нужный ip.

2. Модель проекта

Перед началом реализации нужно описать модель работы устройства, начну с ESP8266. Wifi модуль подключается к внешней точке доступа, и создает веб сервер в ее локальной сети. В его api 8 endpoint (POST), каждый их которых отвечает за свой функционал.

Модель ESP8266

Модель ESP8266

Точки /mouse и /keyboard позволяют напрямую обращаться к мышке и клавиатуре, т.е. использовать согласно их официальным документациям. Точка /animation уже использует готовый функционал, который облегчает работу с устройством.

После отправки корректного запроса ESP8266 формирует посылку и отправляет ее по UART прямо к STM32F103C8. Blue pill принимает данные и проверяет их на корректность, если все хорошо, то она отправляет report в USB.

Модель STM32F103C8

Модель STM32F103C8

Полную архитектуру проекта можно посмотреть в miro.

3. Реализация ESP8266

Прошивку для ESP8266 я писал, используя PlathormIO, в Visual Studio Code. Я не буду сильно останавливаться на объяснении кода, а просто расскажу все в общем виде, иначе статья может сильно растянуться. В любом случае вы сможете все сами просмотреть, репозиторий публичный, вот ссылка.

В целом структура проекта выглядит так :

main.cpp

main.cpp

Рабочую папку я проектировал следующим образом: она состоит из 4 папок: configs, controllers, modules, routes, и из файлов main.h и main.cpp. Функция InitRouter() создает эндпоинты с помощью библиотеки ESP8266WebServer.h, и перенаправляет их на контроллеры. Для контроллеров вместо классов я решил использовать namespace, просто потому что так понятнее и удобнее.

 void InitRouter(){      server.on("/mouse/set", Mouse::set);      server.on("/mouse/remove", Mouse::remove);          server.on("/mouse/click", Mouse::click);          server.on("/keyboard/set", Keyboard::set);          server.on("/keyboard/remove", Keyboard::remove);          server.on("/keyboard/click", Keyboard::click);          server.on("/animation/set", Animation::set);          server.on("/animation/remove", Animation::remove);  }

И далее все основные действия происходят в контроллерах. Вот пример контроллера Animation.h:

Animation.h

Animation.h

Для парсинга JSON я использую библиотеку ArduinoJson.h. Она очень удобная и сильно облегчает работу, с ней можно: легко парсить, фильтровать данные и самому создавать JSON объекты.

Посылка для STM32 формируется в массиве data и отправляется с помощью функции SendDataWithWait(uin8_t *data, uint8_t len) класса serial. Для проверки корректности данных в последний байт посылки добавляется контрольная сумма crc8.

string SendDataWithWait(uint8_t *data, uint16_t len){     uint8_t data_out[len+2] = {HEADER,};             for(int i = 0; i<len; i++){                     data_out[i+1] = data[i];             }             data_out[len+1] = crc8(data_out, len+1);             Serial.write(data_out, len+2);             if(AwaitResponse()){                     if (status){                     status = false;                             return "ok";                     }                     return "error";             }             return "timeout";     }

ESP8266 ждет ответа от STM32 в функции AwaitResponse(), после чего отправляет ответ клиенту. Вся работа с клиентской частью реализована в файле ClientProcessingModule.h.

Одна из проблем при создании веб сервера — это его динамический ip. При подключении к роутеру или модему, устройству выдается ip, который меняется со временем. Из-за этого приходится постоянно подключать ESP8266 к сериал порту и смотреть его вручную. Решением проблемы может быть два варианта: 1-ый, если устройство подключается к роутеру можно настроить статический ip; 2-ой отправлять динамический ip на вебсервер со статическим ip.

Я же на данный момент не пользуюсь ни одним из решений, просто потому что пока мне это не нужно.

4. Подключение

STM32F103C8

STM32F103C8

Для начала работы с STM32 нужно разобраться с подключением ESP8266 к ней. Т.к blue pill будет подключаться по USB, то питаться ESP будет от нее. Пины VCC и СH_EN подключаются к пину 3.3V на STM32, GND к GND, RX к B10, TX к B11 и на этом все.

На STM32 пины B10 и B11 являются TX и RX USART3 соответственно.

5. Реализация STM32f103C8

UsbDeviceHacker

UsbDeviceHacker

Прошивку для STM я писал в CubeIDE. Код для нее весьма простой, он написан полностью на С. Из протоколов подключены UASRT3(HAL), по которому принимаются запросы от ESP, и, собственно, USB, который настроен как HID-device. Также добавлен светодиод на PC13.

Структура проекта состоит из 3 папок: inits, modules, controllers. Данные с USART3 принимаются в файле UsartController.c, в HAL_UART_RxCpltCallback, причем принимаются они по одному и собираются в массиве buffer, после чего вызывается функция ParsingData, которая проверяет на корректность, с помощью crc8, и заполняет структуру Action проверенными данными.

UsartController.c

UsartController.c

После приема посылки срабатывает нужный case в main.c, в котором запускается функция обработки данных, где и происходит основная работа. Далее данные в зависимости от девайса и команды формируют report, который отправляется по USB.

main.c

main.c

Для того чтобы отправлять репорты и мышки, и клавиатуры одновременно нужно в функцию HID_MOUSE_ReportDesc, которая находиться в файле usbd_hid.c, прописать следующие инструкции:

0x05, 0x01, /* Usage Page (Generic Desktop) */ 0x09, 0x02, /* Usage (Mouse) */ 0xA1, 0x01, /* Collection (Application) */ 0x09, 0x01, /* Usage (Pointer) */ 0xA1, 0x00, /* Collection (Physical) */ 0x85, 0x01, /* Report ID */ 0x05, 0x09, /* Usage Page (Buttons) */ 0x19, 0x01, /* Usage Minimum (01) */ 0x29, 0x03, /* Usage Maximum (03) */ 0x15, 0x00, /* Logical Minimum (0) */ 0x25, 0x01, /* Logical Maximum (0) */ 0x95, 0x03, /* Report Count (3) */ 0x75, 0x01, /* Report Size (1) */ 0x81, 0x02, /* Input (Data, Variable, Absolute) */ 0x95, 0x01, /* Report Count (1) */ 0x75, 0x05, /* Report Size (5) */ 0x81, 0x01, /* Input (Constant) ;5 bit padding */ 0x05, 0x01, /* Usage Page (Generic Desktop) */ 0x09, 0x30, /* Usage (X) */ 0x09, 0x31, /* Usage (Y) */ 0x15, 0x81, /* Logical Minimum (-127) */ 0x25, 0x7F, /* Logical Maximum (127) */ 0x75, 0x08, /* Report Size (8) */ 0x95, 0x02, /* Report Count (2) */ 0x81, 0x06, /* Input (Data, Variable, Relative) */ 0xC0, 0xC0,/* End Collection,End Collection */ // 0x09, 0x06, /* Usage (Keyboard) */ 0xA1, 0x01, /* Collection (Application) */ 0x85, 0x02, /* Report ID */ 0x05, 0x07, /* Usage (Key codes) */ 0x19, 0xE0, /* Usage Minimum (224) */ 0x29, 0xE7, /* Usage Maximum (231) */ 0x15, 0x00, /* Logical Minimum (0) */ 0x25, 0x01, /* Logical Maximum (1) */ 0x75, 0x01, /* Report Size (1) */ 0x95, 0x08, /* Report Count (8) */ 0x81, 0x02, /* Input (Data, Variable, Absolute) */ 0x95, 0x01, /* Report Count (1) */ 0x75, 0x08, /* Report Size (8) */ 0x81, 0x01, /* Input (Constant) ;5 bit padding */ 0x95, 0x05, /* Report Count (5) */ 0x75, 0x01, /* Report Size (1) */ 0x05, 0x08, /* Usage Page (Page# for LEDs) */ 0x19, 0x01, /* Usage Minimum (01) */ 0x29, 0x05, /* Usage Maximum (05) */ 0x91, 0x02, /* Output (Data, Variable, Absolute) */ 0x95, 0x01, /* Report Count (1) */ 0x75, 0x03, /* Report Size (3) */ 0x91, 0x01, /* Output (Constant) */ 0x95, 0x06, /* Report Count (1) */ 0x75, 0x08, /* Report Size (3) */ 0x15, 0x00, /* Logical Minimum (0) */ 0x25, 0x65, /* Logical Maximum (101) */ 0x05, 0x07, /* Usage (Key codes) */ 0x19, 0x00, /* Usage Minimum (00) */ 0x29, 0x65, /* Usage Maximum (101) */ 0x81, 0x00, /* Input (Data, Array) */ 0xC0 /* End Collection,End Collection */

Где report id мышки будет 0x01, клавиатуры — 0x02, т.е. данные например для мыши надо будет отправлять в таком формате:

 uint8_t data_out[5] = {0x01, 0, 0, 0, 0}; // первый байт - report id  USBD_HID_SendReport(&hUsbDeviceFS, data_out, 5);

Отдельное внимание нужно уделить анимациям, потому что они добавляют готовый функционал в устройство. На данный момент доступно 3 анимации:

1) плавное движение мышки — мышка плавно двигается по оси x или y, можно задать направление и скорость движения от 1 до 10 включительно;

2)автоматический набор текста (работают только латинские символы, цифры и пробел) — поочередный набор текста, можно задать текст и повторы его написания, например набирать «Hello World» каждые 10 секунд.

3)движение мышки по кругу просто мышка движется по кругу, можно задать скорость от 1 до 10 включительно и радиус движения.

Также 1 и 3 анимациям можно задавать статусы нажатия кнопок мыши, например пусть мышка движется по кругу с нажатой ПКМ или ЛКМ, или все сразу. Всего доступно три статуса: ЛКМ, ПКМ и кнопка у колесика мыши.

Документация по api тут.

6. Тесты

Теперь дошли до самого интересного, а именно к тестам. Подключим устройство к пк (ноутбуку) и дождемся пока ESP подключиться к модему. Я раздал точку доступа с телефона, и также еще подключился к ней с ноутбука, чтобы отправлять запросы с него. В итоге запускать я буду с ноутбука, а останавливать с телефона, отправляя запрос на endpoint /remove.

Вот ссылка видео с тестами:


На этом и все, как я считаю, получился очень интересный проект, который точно заслуживал траты сил и времени на него. Оставляю ссылку на публичный git репозиторий, в котором вы сможете ознакомиться со всеми деталями. Подписывайтесь на мой тг канал, в котором я публикую информацию о своих проектах и о жизни в целом. Всем желаю удачи, надеюсь, еще увидимся.


ссылка на оригинал статьи https://habr.com/ru/articles/855750/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *