Лабораторная работа №2 Применение высокоуровневой обертки libusb на языке Java Цель работы: Получение навыка работы с высокоуровневой оберткой libusb на Java, создание кроссплатформенного приложения для работы с USB с использованием средств Javax-обертки нативной библиотеки libusb - usb4java. Javax - пакет, стандартизирующий интерфейс использования расширенных возможностей Java. Пакет javax.usb в свою очередь определяет интерфейс взаимодействия с USB. Однако кроссплатформенная реализация классов, реализующих интерфейс взаимодействия с USB не включена в стандартный набор классов Java Development Kit (JDK). Для подстановки сторонних классов, реализующих взаимодействие USB, в javax.usb используется специальный файл настройки javax.usb.properties. Это текстовый файл, в котором указывается класс реализации USB сервисов. Для использования библиотеки usb4java в качестве реализации javax.usb необходимо в файле javax.usb.properties разместить следующую строку: javax.usb.services = org.usb4java.javax.Services При запуске Java программы, файл javax.usb.properties должен находиться в одной из директорий, указанных в classpath. В данной лабораторной работе необходимо выполнить информационный обмен с USB мышью по методу Interrupt с помощью javax.usb реализации libusb. Необходимо выводить сообщения о текущем направлении движения мыши до тех пор, пока не будет нажата правая кнопка мыши. При движении/нажатии колеса и нажатии левой кнопки мыши программа не должна завершать работу. План: 1. Подготовка окружения Java для usb4java 2. Сканирование USB устройств и USB хостов в системе 3. Поиск USB устройства 4. Получение и захват информационного интерфейса 5. Получение потока данных USB устройства 6. Считывание данных 7. Освобождение интерфейса 8. Компиляция и запуск 1. Подготовка окружения Java для usb4java Для использования библиотеки usb4java необходимо получить архивы необходимых библиотек, разместить их в classpath. Необходимые библиотеки: usb4java-javax - https://repo1.maven.org/maven2/org/usb4java/usb4java-javax/1.3.0/usb4java-javax-1.3.0.jar usb4java - https://repo1.maven.org/maven2/org/usb4java/usb4java/1.3.0/usb4java-1.3.0.jar usb-api - https://repo1.maven.org/maven2/javax/usb/usb-api/1.0.2/usb-api-1.0.2.jar commons-lang3 - https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1.jar libusb4java - https://repo1.maven.org/maven2/org/usb4java/libusb4java/1.3.0/libusb4java-1.3.0-linux-x86-64.jar Для обеспечения кроссплатформенности библиотека libusb4java поставляется для набора архитектур. В случае данной работы, как минимум необходимо включить в сборку linux-x86-64. Полный список доступен по адресу: https://repo1.maven.org/maven2/org/usb4java/libusb4java/1.3.0/ Также для осуществления связки javax.usb - usb4java необходимо указать подсистеме javax.usb класс, реализующий USB сервис. Для этого в classpath необходимо разместить файл javax.usb.properties с указанием класса в следующем виде: javax.usb.services = org.usb4java.javax.Services 2. Сканирование USB устройств и USB хостов в системе Требуется вывести в терминал список USB хостов и USB устройств подключенных к шине. Для этого необходимо выполнить следующие действия: 2.1 Получить объект класса UsbServices при помощи статического метода UsbHostManager.getUsbServices() Объект класса UsbServices предоставляет общую информацию о шине USB на текущей машине. В том числе предоставляет доступ к корневому USB хосту, которому через промежуточные USB хосты подключены все USB устройства. 2.2 Получить корневой хост от объекта класса UsbServices при помощи метода getRootUsbHub(). Корневой (как и любой другой) USB хост представлен объектом класса UsbHub. 2.3 При помощи функции getAttachedUsbDevices() корневого хоста получить USB устройства, подключенные к корневому хосту. Каждое из этих устройств реализует интерфейс UsbDevice и может оказаться либо конечным USB устройством, либо промежуточным USB хостом. Метод isUsbHub() объекта UsbDevice возвращает true если устройство оказалось хостом. 2.4 Если устройство является USB хостом, необходимо также вывести все дочерние устройства, подключенные к этому хосту. Так как среди этих устройств также могут оказаться USB хосты, предлагается использовать рекурсию для древовидной структуры USB устройств. Пример рекурсивной функции вывода устройств, подключенных к хосту: public static void printDevices(UsbHub hub, String prefix) throws Exception{ for(Object o : hub.getAttachedUsbDevices()){ UsbDevice usbDev = (UsbDevice) o; UsbDeviceDescriptor devDesc = usbDev.getUsbDeviceDescriptor(); System.out.println(String.format("%s%s (VID: %04x PID: %04x)", prefix, usbDev.getProductString(), devDesc.idVendor(), devDesc.idProduct() )); if(usbDev.isUsbHub()){ printDevices((UsbHub)usbDev, String.format(" %s", prefix)); } } } 3. Поиск USB устройства Для осуществления информационного обмена с USB устройством необходимо получить объект класса UsbDevice, описывающий требуемое устройство. Данный объект обеспечивает доступ к USB интерфейсу по его номеру. Для идентификации USB устройства предлагается идентификатор производителя устройства и идентификатор продукта (VID:PID), которые представляют собой 2 числа по 2 байта каждый и хранятся в дескрипторе устройства. Для осуществления поиска устройства по его VID:PID необходимо: 3.1 Получить объект корневого USB хоста на шине (UsbHostManager.getUsbServices();) 3.2 Используя рекурсию, перебрать все USB устройства на всех USB хостах до тех пор, пока не встретиться устройство с требуемым VID:PID. Для получения VID:PID из объекта класса UsbDevice необходимо получить дескриптор устройства при помощи метода getUsbDeviceDescriptor(). В возвращаемом объекте класса UsbDeviceDescriptor методы idProduct() и idVendor() указывают на PID и VID устройства соответственно. 3.3 Сохранить объект класса UsbDevice указывающий на USB мышь. 4. Получение и захват информационного интерфейса Для получения интерфейса по номеру от объекта USB устройства (UsbDevice) необходимо определить активную конфигурацию устройства. Активная конфигурация возвращается методом getActiveUsbConfiguration() класса UsbDevice. Возвращаемая активная конфигурация имеет класс UsbConfiguration и содержит метод доступа к интерфейсам данной конфигурации. Для получения конкретного интерфейса по номеру требуется вызвать метод getUsbInterface(byte numInterface) класса UsbConfiguration (активная конфигурация). Метод возвращает объект класса UsbInterface, описывающий интерфейс устройства. Номер требуемого интерфейса необходимо получить из дескриптора конфигурации устройства. Для просмотра всех дескрипторов USB устройства можно воспользоваться утилитой lsusb: lsusb -d VID:PID -v где вместо VID:PID указать номер производителя и номер изделия исследуемого устройства. Например: lsusb -d 0424:ec00 -v Пример получения интерфейса с номером 2: UsbDevice uDev; .... UsbInterface iface = uDev.getActiveUsbConfiguration().getUsbInterface((byte)2); Для осуществления информационного обмена по этому интерфейсу необходимо его занять (claim). Для этого в классе UsbInterface имеется метод claim(UsbInterfacePolicy policy). В него передается объект с интерфейсом UsbInterfacePolicy, который предоставляет информацию о способе захвата интерфейса - с отсоединением от ядра операционной системы или без отсоединения от ядра (kernel deattach). Если метод forceClaim интерфейса UsbInterfacePolicy возвращает true, то будет осуществлено отсоединение от ядра ОС. В рамках данной лабораторной работы необходимо выполнить отсоединение интерфейса от ядра. Пример захвата интерфейса с отсоединением от ядра ОС: UsbInterface iface; .... iface.claim(new UsbInterfacePolicy() { @Override public boolean forceClaim(UsbInterface ui) { return true; } }); 5. Получение потока данных USB устройства Обмен между USB устройством с терминах javax.usb осуществляется через потоки (Pipes). Каждый поток связан с конкретной конечной точной (Endpoint), которая определяет направление передачи данных в потоке. В данной лабораторной работе осуществляется считывание данных с USB устройства, поэтому поток будет входным. Для получения потока из захваченного интерфейса необходимо: 5.1 Получить объект требуемой конечной точки устройства из захваченного интерфейса. Получение осуществляется через метод getUsbEndpoint(byte ID_конечной_точки), который возвращает объект класса UsbEndpoint; 5.2 Получить USB поток из объекта конечной точки при помощи метода getUsbPipe(); 6. Считывание данных Для считывания данных из потока USB по методам Bulk, Control и Interrupt используется метод int syncSubmit(byte[] data) класса UsbPipe. Перед осуществлением информационного обмена и после него необходимо открыть и закрыть USB поток при помощи методов open() и close() соответственно. Так как конечная точка является входной (IN), то в метод syncSubmit необходимо передать байтовый буфер, в который будут записаны принятые данные. Длина данного буфера должна быть не менее максимальной длины пакета конечной точки (в дескрипторе конечной точки параметр wMaxPacketSize). Метод syncSubmit возвращает длину фактически переданных данных. Обычно USB мышь передает посылки по 4 байта следующего формата: Байт 0 (битовая маска): второй бит - нажата средняя кнопка мыши первый бит - нажата правая кнопка мыши нулевой бит - нажата левая кнопка мыши Байт 1: относительное перемещение по оси X (знаковый байт) Байт 2: относительное перемещение по оси Y (знаковый байт) Байт 3: относительное перемещение колеса мыши (знаковый байт) Если формат данных USB мыши, используемой для выполнения лабораторной работы, отличается от описанного выше, необходимо определить формат самостоятельно путем периодического вывода дынных от мыши и генерации событий нажатия, движения и вращения колесика. 7. Освобождение интерфейса После выполнения информационного обмена с устройством и закрытия соответствующего потока (UsbPipe), необходимо освободить интерфейс. Для освобождения ранее занятого интерфейса используется метод release() класса UsbInterface. 8. Компиляция и запуск Компиляция java приложения выполняется при помощи вызова java компилятора (команда javac) с указанием перечня директорий и jar, входящих в classpath и списка файлов с исходным кодом (*.java). В результате успешной компиляции javac генерирует файлы классов (*.class), содержащие байт-код jvm соответствующих классов. Запуск программы осуществляется при помощи вызова java с указанием перечня директорий и jar, входящих в classpath и имени основного класса с функцией public static void main(String[] args). Пример компиляции программы: javac -cp lib/commons-lang3-3.8.1.jar:lib/libusb4java-1.3.0-linux-x86-64.jar:lib/usb4java-1.3.0.jar:lib/usb4java-javax-1.3.0.jar:lib/usb-api-1.0.2.jar:. Main.java Пример запуска: java -cp lib/commons-lang3-3.8.1.jar:lib/libusb4java-1.3.0-linux-x86-64.jar:lib/usb4java-1.3.0.jar:lib/usb4java-javax-1.3.0.jar:lib/usb-api-1.0.2.jar:. Main Полная справочная информация по javax.usb доступна по адресу: http://javax-usb.sourceforge.net/jdoc/