Создание новых контроллеров Linux для переносимых устройств
Встраиваемые и износостойкие системы требуют новых идей для аппаратного обеспечения контроллера ввода. Но более старое программное обеспечение все еще ожидает клавиатуры и мыши. Как мы преодолеем разрыв без необходимости писать драйверы ядра?
Несколько месяцев назад я построил компьютер с Linux, достаточно маленький, чтобы поместиться на моем запястье, который работал в полной среде рабочего стола X. Проблема заключалась не только в установке компьютера, но и в определении того, как его контролировать. Сенсорные экраны становятся проблематичными, когда экран только чуть больше пальца.
Конечно, мышь и клавиатура не могут быть и речи. Увы, все хорошее программное обеспечение все еще ожидает их. Поэтому важно иметь возможность подделывать эти стандартные входы контроллера, используя любое безумное оборудование, которое вы создаете. Это то, что мы собираемся сделать в этой статье: научиться создавать «виртуальные» устройства ввода для операционной системы (как только электроника работает).

Gyromouse работает на своих смартфонах Linux
Управление изношенным устройством
Изношенные устройства отчаянно нуждаются в новых идеях для способов их контроля. Хорошей новостью является то, что Linux упрощает работу. Нет необходимости писать специальные драйверы мыши или модули ядра, и вы можете использовать любой язык, который вы хотите (хотя C все же лучше).
Чтобы сделать машину на запястье, я попробовал несколько систем ввода, в том числе:
- Модуль магнитометра (или цифровой компас) и «3D-стилус» (магнит) для перемещения указателя мыши и имитации кликов.
- Режим акселерометра, при котором опрокидывание модуля в любом направлении заставляло указатель мыши «катиться» в этом направлении, как мяч на столе. Это было трудно контролировать.
- Режим, в котором скорость (интегрированное ускорение) в направлениях X и Y перемещает указатель мыши, как обычная мышь. Проблема здесь заключалась в том, что вы не можете использовать ее во время движения.
- Режим, при котором «нажатие» модуля вызывало щелчки мыши. Это была отличная идея для тонкого контроля.
- Режим гироскопа, где вращение модуля напрямую переводится в движение мыши.
Это был последний метод, гироскоп, который был самым приятным в использовании, наряду с физической кнопкой для щелчка. Оказывается, люди могут воспринимать и контролировать гораздо меньшие угловые «скручивающие» движения по сравнению с боковыми, «покачивающимися» ускорениями. Вот почему ручки существуют.
Многие люди спрашивали, почему я не сделал это «очевидным» способом скорости, точно копируя работу настольной мыши. Важно отметить, что метод гироскопа не был моим первым выбором или моим первоначальным намерением. Конечно, у меня были сильные идеи, но если бы я застрял с ними, я бы построил плохой интерфейс.
Эксперимент. Итерация. См. Это в контексте. Пусть реальность имеет право голоса в этом вопросе.
Система на основе гироскопа дала лучшее сочетание быстрого движения и тонкого контроля, и именно этого я и искал, поэтому я написал «гиромаз» и использовал его в качестве основы для манипулятора.
Подключение ИДУ и магнитометра
В наши дни очень легко подключить датчик I 2 C к встроенной машине Linux, такой как Raspberry Pi. Ground, VCC, SDA и SCL. Два провода питания, два провода данных - и все. Работа выполнена.
Когда физически подключен, поддержка Pi's I 2 C превосходна. Вам нужно включить его через raspi-config, по крайней мере для Wheezy и Jessie. (Я надеюсь, что в будущем Raspbian шины I 2 C, SPI и I 2 S могут быть включены по умолчанию.) Связанный с командной строкой инструмент i2cdetect может перечислять устройства на шине, поэтому вы можете проверить, в основном работая без написания строки кода.
Оттуда, прямолинейно использовать свой язык, чтобы поговорить с чипом с его предпочтительным протоколом. У большинства чипов I 2 C и SPI есть несколько конфигурационных регистров, которые вы пишете, чтобы включить их, и поместить их в правильном режиме, а также несколько регистров, которые вы читаете непрерывно, чтобы получить значения датчика.
Я использовал инструменты командной строки, Python, C, C + и даже Javascript для чтения датчика. Большинство датчиков имеют скорость обновления в диапазоне 10-1000 Гц, которые в целом совместимы с миллисекундными точками синхронизации в Linux.
Мы собираемся пропустить специфика общения с чипом и предположить, что у вас есть программа, которая может читать ваш датчик и, например, писать поток чисел в stdout, представляющий образцы.
Вот код, который я вымыл вместе для Инерциального измерительного блока MPU6050, используемого в гиромассе:
Код гироскопа ИДУ
И вот аналогичная задача для работы с магнитометром HMC5883:
Код гироскопического магнитометра
Теперь важная часть: использование этих необработанных данных для «подделки» типичного устройства ввода, такого как мышь, тачпад или клавиатура. Мы хотим написать вторую программу, которая берет поток образцов датчиков, обрабатывает его и возвращается обратно в операционную систему, такую как драйвер устройства.
И здесь Linux сияет, потому что у него есть устройства ввода для пользовательского пространства. «Пользовательское пространство» означает, что вам не нужно писать драйвер устройства ядра, и это отличная вещь. Вы действительно хотите избежать написания модулей ядра. Это займет вашу жизнь. Спросите Линус Торвальдс.
Вместо этого мы используем / dev / uinput, который является потоком, который подключается к существующему модулю ядра. Написав командные сообщения в этот специальный файл, ваш код может попросить API зарегистрировать «виртуальное» устройство от вашего имени и назначить ему возможности.

Оригинальный прототип Gyromouse, изображенный здесь рядом с устройством, которое он притворяется
Это работает, потому что UNIX действительно не волнует, сколько клавиатур или мышей вы подключили. Это никогда не было. Какая бы ни была перемещена мышь, толкает указатель, и любые кнопки, которые нажаты, принимаются как клавиши.
Существует три основных «класса» uinput-устройства, о которых мы поговорим (хотя их еще много), и одна программа может использовать любое подмножество:
- Ключевые события
- Относительные события
- Абсолютные события
Ключевые события
Это включает в себя кнопки клавиатуры и мыши. На этом уровне события кнопки мыши не имеют привязанных координат. На самом деле, они не могут, потому что это подразумевает, что устройство с низким уровнем ввода знает, что должен был делать указатель X-windows. Таким образом, кнопки мыши в основном рассматриваются как крошечные клавиатуры в системе uinput.
Ключевые события выходят в две половины, «вниз» и «вверх», и вы должны отправлять их в аккуратные пары. (Я уже больше не разгибал это правило, так что это не принудительно. Это просто сбивает с толку X.) Вы можете мягко подключить это прямо к выходу GPIO, подключенному к коммутатору, до тех пор, пока вы немного его отбросите.
Во время настройки вы должны указать API, какие коды клавиш вы планируете отправлять. Если вы реализуете полную клавиатуру, это становится утомительным, но это необходимое зло, так что Linux или X могут профилировать устройство и потенциально изменять поведение, если ключи разные (например, клавиши Meta / Windows / Mac, многоязычные клавиатуры, третья мышь кнопки).
Я не думаю, что после установки можно динамически добавлять или удалять ключи с виртуального устройства, но вы всегда можете уничтожить и воссоздать виртуальное устройство с новыми определениями.

Элементы управления, такие как поворотные ручки и емкостные сенсорные датчики, могут быть «клавиатурами»
Относительные события
Движение мыши является классическим примером относительного события. Мышь не знает, где она находится - она просто знает, что она повернута влево. Каждая ось получает свое собственное сообщение о событии, поэтому события REL_X могут быть отправлены с другой скоростью, чем события REL_Y, а затем вы отправляете сообщения EV_SYN после каждого «набора» обновлений. (По сути, это «фиксация», как только вы сделали все X и Y и обновления колес за общий период времени.)
Координаты оси отправляются как дельта в собственной системе координат устройства с таким количеством DPI, сколько вы хотите (в разумных пределах). (DPI, кстати, это злоупотребление точками на дюйм, которое является сокращением для приращений координат на пройденное расстояние. Не напечатанные точки.) В X-окнах есть панель предпочтений «скорость мыши / ускорение», которая регулирует, как X поворачивает эти значения событий в стрелочное движение указателя (что нас здесь не беспокоит).
Слабость в этой системе - если у вас есть устройства с совершенно разными DPI-эквивалентами, нет оптимальной «скорости мыши», которая будет работать для всех. Вот почему мыши с высоким разрешением имеют этот маленький переключатель на дне, чтобы сделать их обычными. Вместо того, чтобы определять свой собственный масштаб, вы хотите отправить значения событий, которые сортируются в соответствии с дельтами, которые будет генерировать мышь. Вы хотите, чтобы было легко управлять указателем на среднем экране (скажем, на 1000 точек).

Инерционные измерительные устройства, такие как MPU6050, обнаруживают физическое движение и обеспечивают гладкую и эффективную механическую интеграцию в носимые и другие устройства малого форм-фактора
Абсолютные события
Это устройства с ограниченным диапазоном входных данных, например сенсорными экранами или регуляторами громкости. Во время инициализации вы должны отправлять определения о диапазоне каждой оси, поэтому вы можете сказать, что ваш сенсорный экран (0 … 1023) × (0 … 1023), если у вас есть 10-разрядные АЦП, которые дают полный диапазон значений выборки. Это чья-то проблема, чтобы превратить это в координаты экрана при текущем разрешении. Вы имеете дело исключительно с вашими значениями выборки.
Это не означает, что вы должны напрямую копировать необработанные значения датчика в значения событий. Это приводит к очень резкому указателю, так как все шумы четко отображаются пользователю. Обычно требуется некоторая форма фильтрации пробы (если это еще не сделано датчиком), поэтому выбранный вами диапазон вывода будет больше связан с вашей математикой фильтра, чем с характеристиками устройства I 2 C.
Просто помните, что X-windows не может просто создать абсолютное разрешение, поэтому, если вы ожидаете управлять указателем на экране 4K с индивидуальной точностью пикселей, используя стандартный 8-битный резистивный сенсорный экран … ну, это не идти на работу. Меню прекрасно, если вы пропускаете 7 из 8 пикселей, но не Photoshop.
Насколько я знаю, нет явного запрета отправлять как относительные, так и абсолютные события из одного источника. Тем не менее, не так много обстоятельств, когда это имеет смысл. Даже если входы вашего сенсора поступают от чего-то вроде комбинации GPS / инерциального датчика (которая имеет, возможно, 10 Гц «абсолютных» обновлений против 1000 Гц «относительной» от ИДУ), вы сплавляете эти входы в своем коде и представляете один поток в / dev / uinput. В противном случае вы не представляете, как ускорение мыши X улучшает масштабирование между двумя режимами.

Я подумал о создании «GPS-сенсорного экрана», который использовал всю Землю в качестве входной поверхности, но Pokemon Go избил меня
Обычно вы смешиваете либо относительные, либо абсолютные события с ключевыми событиями. Стандартная мышь - классический случай относительных + ключевых событий.
Код
Следующий пример написан в чистом C, но те же самые понятия могут применяться на любом языке. Если вы используете Python или Javascript, есть библиотеки, которые аккуратно завершают эту функциональность, но это то, что они все делают внизу.
Я узнал, что C - очень эффективный язык для работы с низким уровнем I 2 C и uinput, а отсутствие зависимости от любого интерпретируемого языка или библиотек означает, что моя мышь / клавиатура не перестанет работать, потому что мой Python / Обновление Javascript сломалось.
Пример кода C
инициализация
Для установки нет конкретной библиотеки - вы просто открываете / dev / uinput, как если бы это был обычный файл unix, и используйте функции ioctl () и write () для получения / отправки команд и данных в API. Формат и коды блока сообщений определены в стандартном Linux, который уже должен быть в вашей системе.
#include#include#include#include#include#include#includeint fd; // file handle void initialize_uinput() { // open the uinput fifo fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); if(fd < 0) { printf("could not open /dev/uinput\n"); return 1; } // enable the message types we're going to send if(ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0) die("error: ioctl"); if(ioctl(fd, UI_SET_KEYBIT, BTN_LEFT) < 0) die("error: ioctl"); if(ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT) < 0) die("error: ioctl"); if(ioctl(fd, UI_SET_KEYBIT, BTN_MIDDLE) < 0) die("error: ioctl"); if(ioctl(fd, UI_SET_EVBIT, EV_REL) < 0) die("error: ioctl"); if(ioctl(fd, UI_SET_RELBIT, REL_X) < 0) die("error: ioctl"); if(ioctl(fd, UI_SET_RELBIT, REL_Y) < 0) die("error: ioctl"); // create our virtual input device struct uinput_user_dev uidev; memset(&uidev, 0, sizeof(uidev)); snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "gyromouse"); uidev.id.bustype = BUS_VIRTUAL; uidev.id.vendor = 0x1; uidev.id.product = 0x2; uidev.id.version = 1; if(write(fd, &uidev, sizeof(uidev)) < 0) die("error: write"); if(ioctl(fd, UI_DEV_CREATE) < 0) die("error: ioctl"); }
Во время инициализации мы также предоставляем метаданные, такие как имя устройства («гиромания» в этом примере, но вы должны изменить его на свой). Однако, если мы пытаемся подражать определенному устройству (например, для устаревшего программного обеспечения, которое нуждается в чем-то конкретном), мы могли бы олицетворять это устройство, используя его идентификаторы шины / поставщика / продукта, а не общие значения по умолчанию.
Пресс-формы
По завершении настройки мы можем записать блоки сообщений в файл, который отключает события пользовательского интерфейса.
void send_button(int btn_code, int value) { struct input_event btn_ev; // button event memset(&btn_ev, 0, sizeof(struct input_event)); btn_ev.type = EV_KEY; btn_ev.code = btn_code; btn_ev.value = value; if(write(fd, &btn_ev, sizeof(struct input_event)) < 0) die("error: write"); // syn event send_syn(); } void send_syn() { struct input_event syn_ev; // syn event memset(&syn_ev, 0, sizeof(struct input_event)); syn_ev.type = EV_SYN; syn_ev.code = 0; syn_ev.value = 0; if(write(fd, &syn_ev, sizeof(struct input_event)) < 0) die("error: write"); }
Код кнопки / клавиши - это то же самое, что мы сказали во время инициализации. Значение равно 1 для «вниз» или 0 для «вверх» (или 2 для аппаратного автоповтора). Чтобы имитировать полный пресс-релиз, вы должны отправить оба события:
send_button(BTN_LEFT, 1); // send left mouse button down send_button(BTN_LEFT, 0); // send left mouse button up
Движение указателя
На этот раз мы отправляем пару событий для дельта X и Y (даже если дельта равна нулю, что теоретически можно пропустить). Затем мы отправляем синхронизацию.
void send_mouse(int dx, int dy) { struct input_event ev; // mouse X movement memset(&ev, 0, sizeof(struct input_event)); ev.type = EV_REL; ev.code = REL_X; ev.value = dx; if(write(fd, &ev, sizeof(struct input_event)) < 0) die("error: write"); // mouse Y movement memset(&ev, 0, sizeof(struct input_event)); ev.type = EV_REL; ev.code = REL_Y; ev.value = dy; if(write(fd, &ev, sizeof(struct input_event)) < 0) die("error: write"); // syn event send_syn(); }
Выключение
По мере закрытия нашей программы мы должны отправить окончательное сообщение API для уничтожения нашего устройства - логическим способом. (Если ваше аппаратное устройство физически уничтожено, это еще одна проблема).
void shutdown() { // destroy our mouse device if(ioctl(fd, UI_DEV_DESTROY) < 0) die("error: ioctl"); // close the file close(fd); }
В дополнение к выполнению этой функции, когда программа закрывается, вы должны попытаться захватить события SIGTERM, чтобы эта функция могла вызываться, когда пользователь нажимает (Ctrl + C) или запускает команду « kill -9 ». Но если вы этого не сделаете, это не конец системы - устройство ввода зависает (возможно, до перезагрузки компьютера), но на самом деле не мешает.
Если вы многократно запускаете программу без чистое уничтожение устройства, у вас могут возникнуть проблемы из-за того, что старые исходные данные накапливаются в ядре. Так что не делай этого. Но наличие десятков подвесных устройств во время разработки не вызывало у меня никакого горя, поэтому не чувствуйте, что вам нужно перезагружаться после каждого segfault.
Другие события и возможности
Эта статья просто царапает поверхность uinput системы. Существуют также события EV_SW с «stateful switch», предназначенные для обнаружения открывания / закрывания крышки ноутбука. Существуют EV_LED-события для запроса / управления мигающими огнями, такими как блокировка затвора, EV_SND для звуковых сигналов, EV_FF для управления пакетами с силовыми обратными связями (да, действительно), модификаторами для включения обнаружения нескольких касаний и нескольких инструментов (если у вас есть метка стилусом с ластиком на другом конце) и даже поддержкой инструмента (расстояние) для квази-3D-интерфейсов. Таким образом, существуют огромные возможности для создания стандартных устройств ввода UNIX с гораздо более сложными способностями, чем у 2D-мыши.
Здесь представлен хороший обзор всего спектра устройств.

Необычные устройства ввода, которые можно использовать в качестве новых интерфейсов: датчики влажности, ультразвука, сердечного ритма и света
Постоянное
Разделение задачи на две программы и использование соединительного потока - это «путь UNIX». Это позволяет нам делать трюки позже, как поток данных датчиков, в дополнительные программы (поскольку только одна программа может получить доступ к оборудованию) или даже отдельные программы по сети.
Поскольку они обычные программы UNIX (а не специальные драйверы устройств), мы должны убедиться, что они запускаются во время загрузки. Я сделал это быстро и грязно с помощью скрипта init.d, который запускает две программы вместе, но продвинутые гуру UNIX смогут демонтировать программы в правильных путях FIFO.
Перейти к началу страницы
Итак, теперь вы знаете, что это не так сложно подделать нажатия кнопок. Любая программа может это сделать, если у нее есть доступ к файлу / dev / uinput.
Большинство языков имеют библиотеку-оболочку с некоторым вариантом uinput, что делает процесс еще проще. Если у вас уже есть навыки, чтобы написать код, чтобы поговорить с вашим датчиком, это не будет сложно. Трудная часть - это «клей» посередине, который переводит необработанные данные в действия пользователя. Это сложно, потому что нет правильного ответа.
Например, код, предоставленный для Gyromouse, имеет различное масштабирование для осей X и Y, потому что легче перекрутить запястье, чем наклон всей руки. Было просто приятно, когда скорость Y была смягчена. Конечно, это относится только к устройствам на вашем запястье. Это довольно специфично для задачи (badum-tsh!).
Итак, теперь ваша работа: подключение аппаратного и программного обеспечения к новым идеям. Сделать это правильно. Инструменты для вас!