Начало работы с JavaFX на Raspberry Pi
Raspberry Pi идеально подходит для любителей поэкспериментировать с электронными компонентами. Если объединить его с инструментами Java-разработчика, вы откроете для себя новый мир. Обсудить Недорогой одноплатный компьютер Raspberry Pi отлично работает с Java, отчего hardware-разработка становится не сложнее классической разработки ПО. *** Из этой статьи вы узнаете, как разработать приложение на JavaFX в стиле дашборд, используя библиотеку TilesFX. На рис. 1 изображен пользовательский интерфейс (далее UI). Рис. 1 UI приложения Также вы можете ознакомиться с видео, на котором показана работа приложения на Raspberry Pi 3B+ и функциональность интерфейса с тачскрином. Приведенный код и рассмотренные методы применимы только к микрокомпьютерам Raspberry Pi на чипах ARM v7 и ARM v8. В разделе спецификаций плат Raspberry Pi на Википедии вы найдете типы поставляющихся с этими процессорами плат: ● Модель A+, версия 3; ● Модель B, версии 2, 3 и 4; ● Вычислительный модуль (Compute Module), версия 3. Прочие используемые в проекте электронные компоненты вы найдете в большинстве стартовых наборов Arduino/Pi. Если вы хотите использовать другие элементы, можете начать с компонентов из проекта, и подстроить их под свои нужны. Мой комплект выглядит следующим образом: ● Raspberry Pi 3 Модель B+; ● SD карта 32 GB (или более) на ОС Raspberry Pi (бывш. Raspbian); ● Монитор, клавиатура и мышь; ● Светодиод и резистор (для большинства подойдет 330 Ом); ● Кнопочный переключатель; ● Датчик расстояния HC-SR04; ● Беспаечная макетная плата и провода. Если вы работаете Raspberry Pi впервые, подготовьте карту SD с операционной системой. В проекте используется Full ОС Raspbian (полная версия). Загрузите инструмент Imager. Для своей работы я взял версию Raspberry Pi Imager 1.2 от марта 2020 г. (рис. 2 и рис. 3). Убедитесь, что устанавливаете полную версию. Рис. 2 Страница скачивания инструмента Imager Рис.3 Выберите полную версию (Raspbian Full) Как только SD карта будет готова, вставьте ее в плату Raspberry Pi, запустите операционную систему и выполните действия по настройке и подключению к вашей сети Wi-Fi. В примечаниях к релизу Raspbian указано, что используемая мной версия ОС включает OpenJDK 11. Java-версия: Теперь плата готова к запуску любых приложений на основе Java 11. Однако JavaFX больше не является частью JDK (начиная с Java 11), и запустить JavaFX-программу на Raspberry Pi «из коробки» не получится. К счастью, BellSoft разработала Liberica JDK. Предназначенная для Raspberry Pi версия включает JavaFX, поэтому вы сможете запускать упакованное приложение JavaFX с помощью команды Когда инсталляция будет завершена, проверьте версию снова. Она должна выглядеть следующим образом: На моей тестовой плате хранятся различные версии Liberica JDK. Переключаться между ними несложно с помощью команды Рис. 4 Переключение между версиями Liberica JDK В папке исходного кода Рис. 5 Установочные скрипты различных версий Liberica JDK Прежде чем подключать компоненты к разъемам GPIO (Интерфейс ввода/вывода общего назначения) на плате, изучите три схемы нумерации, используемые для идентификации пинов. Вас может запутать работа с разъемами GPIO. Детальную информацию можно найти в подробном руководстве по распиновке GPIO. Ниже приведена краткая сводка. Нумерация Header Pin. Это логическая нумерация заголовка платы. Один ряд содержит четные пины, другой – нечетные.(см. Рис. 6) Рис. 6 Нумерация пинов заголовка платы Нумерация BCM. Относится к номеру канала Broadcom – нумерации внутри микросхемы, используемой на Raspberry Pi. Нумерация WiringPi. Wiring Pi – основной фреймворк, используемый Pi4J (в проекте он применяется в качестве библиотеки в Java) для управления GPIO. Причина другой схемы нумерации кроется в истории платы. Когда еще велась разработка первых моделей Raspberry Pi, предполагалось всего восемь контактов. В результате дальнейшего развития микрокомпьютера и добавления дополнительных контактов, нумерацию в WiringPi расширили, чтобы можно было на них ссылаться. Чтобы Java разработчикам было проще понять разницу между различными типами заголовков, пинами и функциями, я разработал небольшую библиотеку, расположенную в репозитории Maven по адресу be.webtechie.pi-headers. Используя ее и небольшое приложение JavaFX, я сделал вспомогательную таблицу (см. Рис. 7), которая упрощает поиск и сопоставление номеров с соответствующими им пинами на плате. Более подробную информацию вы найдете по ссылке “Raspberry Pi history, versions, pins and headers as a Java Maven library.” Рис. 7 Сопоставление номеров с пинами на плате Давайте подключим оборудование, чтобы использовать часть мощностей платы Pi: светодиод, кнопку и датчик расстояния. См. Табл. 1, Рис. 8, Рис. 9. Табл. 1 Сопоставление пинов с соответствующими устройствами Рис. 8 Подключение проводов Рис. 9 Схема подключения На рис. 10 изображена система с использованием моста на макетной плате RasPiO, что упрощает поиск правильного пина. Разъем моста помещает номера BCM в логическом порядке, но я все еще использую отдельную макетную плату, чтобы было немного больше места. Portsplus предлагает аналогичное удобное решение. Рис. 10 Фото системы с использованием моста макетной платы RasPiO Чтобы проверить, подключен ли светодиод в правильном положении с учетом его полярности, отсоедините кабель между светодиодом и пином GPIO (оранжевый кабель на рис. 10) и подключите его непосредственно к контакту 3,3 В (или к плюсу на плате). Если светодиод не загорится, вам нужно поменять его положение. Чтобы проверить соединение, запустите команду Примечание: Если вы работаете с платой Raspberry Pi 4, обязательно используйте версию 2.52 утилиты gpio. Поскольку внутренняя проводка процессора на плате Pi 4 отличается от предыдущих моделей, при необходимости доступно обновление для утилиты. Проверьте свою версию с помощью команды gpio -v через терминал и, если нужно, установите новую с помощью следующих команд: Включить (1) и выключить (0) светодиод: Состояние кнопки (1=нажата, 2=не нажата) 27 пина WiringPi Начинается самое интересное! Следующий код настраивает 29 пин WiringPi и 10 раз переключает его между включением и выключением с интервалом в 500 мс, используя ту же команду, которую я применял в терминале. Создайте файл под именем HelloGpio.java со следующим содержанием: Поскольку код будет использовать Java 11 (или выше), файл Java может быть выполнен без компиляции: Написанное для этого блока приложение использует обычный ультразвуковой датчик расстояния, который можно найти во многих стартовых наборах Arduino и Pi. Это модуль под названием HC-SR04; дополнительную информацию и примеры вы можете найти в интернете. Модулю требуются входные и выходные GPIO-соединения. Датчик работает по той же схеме, что и система, используемая летучей мышью для полета в темноте: он использует отражение ультразвука для расчета расстояния до объекта. Для измерения расстояния требуется, чтобы приложение и модуль выполнили следующие действия: Запуск одного файла Java хорошо для тестирования системы, но это только первый шаг. В приложении используется библиотека Pi4J для объединения Java и портов GPIO на плате Raspberry Pi. Pi4J должен быть установлен на Raspberry Pi и может быть интегрирован в приложение Java с Maven. Полный исходный код примера вы можете найти на GitHub. Следующие зависимости Maven указаны в файле POM: ● Для JavaFX, расширенная зависимость javafx-web, которая включает: ○ javafx-controls – основу для приложения JavaFX; ○ Веб-компоненты, необходимые для TilesFX. ● Логирование; ● TilesFX для панели инструментов Tiles; ● Pi4J для использования портов GPIO. Вот классы для взаимодействия с оборудованием. Класс GpioHelper. В нем сгруппированы функции, связанные с портами GPIO. Код начинается с определения контактов, к которым подключены компоненты оборудования, и инициализации Pi4J GpioController. Расширенная функциональность обрабатывается в отдельных классах: Класс ButtonChangeEventListener. Поскольку этот класс реализует Pi4J GpioPinListenerDigital, он может обрабатывать изменение кнопки и сохранять изменение в XYChart.Series с отметкой времени. Класс DistanceSensorMeasurement. Этот класс является Runnable. Он добавляет отмеренное расстояние к аналогичной серии данных с отметкой времени для каждого пробега. UI. Благодаря TilesFX приложение в стиле дашборда может быть создано быстро. Полное построение интерфейса выполняется в классе Чтобы показать измерение расстояния, я использую tile типа Класс приложения. Теперь все элементы готовы к объединению в класс приложения JavaFX. Ниже описано, как инициализировать GpioHelper и использовать его для инициализации DashboardScreen, а также есть дополнительный код, позволяющий красиво закрыть приложение. Вы можете запустить приложение на своем ПК через среду IDE, и UI отобразится, но большинство возможностей будут недоступны, потому что необходимы Pi4J и аппаратные компоненты. Перейдем к Raspberry Pi! Сначала вам нужно установить Pi4J. Это можно сделать с помощью однострочной команды: Финальный шаг – загрузить скомпилированный файл JAR с вашего ПК на плату Pi, что можно сделать с помощью SSH, USB-накопителя, загрузки или SD-карты. Поместите файл в /home/pi и запустите его с помощью Сначала на экране отобразятся логи, а чуть позже откроется экран JavaFX. Диаграмма расстояний получает новое значение каждую секунду, а диаграмма кнопки обновляется при каждом ее нажатии. Светодиод можно переключать с помощью кнопки переключения на экране в одной из плиток. (см. Рис. 11) Рис. 11 Фото запущенного приложения вместе с Raspberry Pi и другими компонентами Как только вы узнаете, какую версию Java нужно использовать на Pi, вы можете очень быстро начать работу с простыми тестовыми программами и расширить их с помощью пользовательского интерфейса JavaFX. Создание сложного приложения требует дополнительных усилий, чтобы настроить все в вашей среде IDE и иметь возможность тестового запуска. Это позволяет вам легко разрабатывать программы на ПК и выполнять их на Pi. Текст опубликован в переводе. Автор оригинальной статьи Frank Delporte. *** Изучение Java – непростая задача. Можно освоить необходимые знания самостоятельно, но если вы только начинаете путь в профессии, стоит обратить внимание на курс Факультета Java-разработки онлайн-университета GeekBrains. Занятия ведут опытные преподаватели, а студенты во время обучения создают проекты, которые будет не стыдно показать потенциальным работодателям. Кроме того онлайн-университет помогает выпускникам с трудоустройством. Интересно, хочу попробоватьПодготовка платы Raspberry Pi
Установка JDK (Java Development Kit) с помощью JavaFX
2019-06-20: * Based on Debian Buster * Oracle Java 7 and 8 replaced with OpenJDK 11
$ java -version openjdk version "11.0.3" 2019-04-16 OpenJDK Runtime Environment (build 11.0.3+7-post-Raspbian-5) OpenJDK Server VM (build 11.0.3+7-post-Raspbian-5, mixed mode)
java -jar yourapp.jar
. Используйте ссылку для загрузки от BellSoft, чтобы установить альтернативную JDK:
$ cd /home/pi $ wget https://download.bell-sw.com/java/13/bellsoft-jdk13-linux-arm32-vfp-hflt.deb $ sudo apt-get install ./bellsoft-jdk13-linux-arm32-vfp-hflt.deb $ sudo update-alternatives --config javac $ sudo update-alternatives --config java
$ java --version openjdk version "13-BellSoft" 2019-09-17 OpenJDK Runtime Environment (build 13-BellSoft+33) OpenJDK Server VM (build 13-BellSoft+33, mixed mode)
update-alternatives
. (см. Рис.4)Chapter_04_Java/scripts
на GitHub расположены установочные скрипты различных версий Liberica JDK. Они содержат ссылки для скачивания. (см. Рис. 5)Различные схемы нумерации Raspberry Pi
Подключение оборудования
Проверьте светодиод и кнопку через терминал
gpio
через терминал.
$ gpio -v gpio version: 2.50 $ cd /tmp $ wget https://project-downloads.drogon.net/wiringpi-latest.deb $ sudo dpkg -i wiringpi-latest.deb $ gpio -v gpio version: 2.52
$ gpio mode 29 out $ gpio write 29 1 $ gpio write 29 0
$ gpio mode 27 in $ gpio read 27 1
Переключение светодиода с помощью Java
public class HelloGpio { public static void main (String[] args) { System.out.println("Hello Gpio"); try { Runtime.getRuntime().exec("gpio mode 29 out"); var loopCounter = 0; var on = true; while (loopCounter < 10) { System.out.println("Changing LED to " + (on ? "on" : "off")); Runtime.getRuntime().exec("gpio write 29 " + (on ? "1" : "0")); on = !on; Thread.sleep(500); loopCounter++; } } catch (Exception ex) { System.err.println("Exception from Runtime: " + ex.getMessage()); } } }
$ java HelloGpio.java Hello Gpio Changing LED to on Changing LED to off Changing LED to on …
Использование датчика движения
Готовое приложение
<dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-web</artifactId> <version>11.0.2</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.13.1</version> </dependency> <dependency> <groupId>eu.hansolo</groupId> <artifactId>tilesfx</artifactId> <version>11.13</version> </dependency> <dependency> <groupId>com.pi4j</groupId> <artifactId>pi4j-core</artifactId> <version>1.2</version> </dependency>
ButtonChangeEventListener
и DistanceSensorMeasurement
(описаны вкратце). Дополнительные методы и геттеры будут использоваться в UI позже.
public class GpioHelper { private static final Logger logger = LogManager.getLogger(GpioHelper.class); /** * The pins being used in the example. */ private static final Pin PIN_LED = RaspiPin.GPIO_29; // BCM 21, Header pin 40 private static final Pin PIN_BUTTON = RaspiPin.GPIO_27; // BCM 16, Header pin 36 private static final Pin PIN_ECHO = RaspiPin.GPIO_05; // BCM 24, Header pin 18 private static final Pin PIN_TRIGGER = RaspiPin.GPIO_01; // BCM 18, Header pin 12 /** * The connected hardware components. */ private GpioController gpioController; /** * The Pi4J GPIO input and outputs. */ private GpioPinDigitalOutput led = null; /** * The GPIO handlers. */ private ButtonChangeEventListener buttonChangeEventListener = null; private DistanceSensorMeasurement distanceSensorMeasurement = null; /** * Constructor. */ public GpioHelper() { try { // Initialize the GPIO controller this.gpioController = GpioFactory.getInstance(); // Initialize the LED pin as a digital output pin with an initial low state this.led = gpioController.provisionDigitalOutputPin(PIN_LED, "RED", PinState.LOW); this.led.setShutdownOptions(true, PinState.LOW); // Initialize the input pin with pulldown resistor GpioPinDigitalInput button = gpioController .provisionDigitalInputPin(PIN_BUTTON, "Button", PinPullResistance.PULL_DOWN); // Initialize the pins for the distance sensor and start thread GpioPinDigitalOutput trigger = gpioController.provisionDigitalOutputPin(PIN_TRIGGER, "Trigger", PinState.LOW); GpioPinDigitalInput echo = gpioController.provisionDigitalInputPin(PIN_ECHO, "Echo", PinPullResistance.PULL_UP); this.distanceSensorMeasurement = new DistanceSensorMeasurement(trigger, echo); ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); executorService.scheduleAtFixedRate(this.distanceSensorMeasurement, 1, 1, TimeUnit.SECONDS); // Attach an event listener this.buttonChangeEventListener = new ButtonChangeEventListener(); button.addListener(this.buttonChangeEventListener); } catch (UnsatisfiedLinkError | IllegalArgumentException ex) { logger.error("Problem with Pi4J! Probably running on non-Pi-device or Pi4J not installed. Error: {}", ex.getMessage()); } } public GpioController getGpioController() { return this.gpioController; } /** * Set the state of the LED. * * @param on Flag true if the LED must be switched on */ public void setLed(boolean on) { if (this.led != null) { if (on) { this.led.high(); } else { this.led.low(); } } } /** * Get the data from the button. * * @return {@link XYChart.Series} */ public XYChart.Series<String, Number> getButtonEvents() { if (this.buttonChangeEventListener != null) { return this.buttonChangeEventListener.getData(); } else { return new Series<>(); } } /** * Get the data from the distance measurement. * * @return {@link XYChart.Series} */ public XYChart.Series<String, Number> getDistanceMeasurements() { if (this.distanceSensorMeasurement != null) { return this.distanceSensorMeasurement.getData(); } else { return new Series<>(); } } }
@Override public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event) { var timeStamp = LocalTime.now().format(DateTimeFormatter.ofPattern("HH.mm.ss")); this.data.getData().add(new XYChart.Data<>(timeStamp, event.getState().isHigh() ? 1 : 0)); logger.info("Button state changed to {}", event.getState().isHigh() ? "high" : "low"); }
@Override public void run() { // Set trigger high for 0.01 ms this.trigger.pulse(10, PinState.HIGH, true, TimeUnit.NANOSECONDS); // Start the measurement while (this.echo.isLow()) { // Wait until the echo pin is high, indicating the ultrasound was sent } long start = System.nanoTime(); // Wait until measurement is finished while (this.echo.isHigh()) { // Wait until the echo pin is low, indicating the ultrasound was received back } long end = System.nanoTime(); // Output the distance float measuredSeconds = getSecondsDifference(start, end); int distance = getDistance(measuredSeconds); logger.info("Distance is: {}cm for {}s ", distance, measuredSeconds); var timeStamp = new SimpleDateFormat("HH.mm.ss").format(new Date()); this.data.getData().add(new XYChart.Data<>(timeStamp, distance)); }
DashboardScreen.java
. В следующем фрагменте кода показана кнопка переключателя для включения и выключения светодиода:
var ledSwitchTile = TileBuilder.create() .skinType(SkinType.SWITCH) .prefSize(200, 200) .title("LED") .roundedCorners(false) .build(); ledSwitchTile.setOnSwitchReleased(e -> gpioHelper.setLed(ledSwitchTile.isActive()));
SMOOTHED_CHART
, который в свою очередь использует XYChart.Series
из доступного через GpioHelper DistanceSensorMeasurement
.
var distanceChart = TileBuilder.create() .skinType(SkinType.SMOOTHED_CHART) .prefSize(500, 280) .title("Distance measurement") //.animated(true) .smoothing(false) .series(gpioHelper.getDistanceMeasurements()) .build();
public class DashboardApp extends Application { private GpioHelper gpioHelper; @Override public void start(Stage stage) { Platform.setImplicitExit(true); this.gpioHelper = new GpioHelper(); var scene = new Scene(new DashboardScreen(this.gpioHelper), 640, 480); stage.setScene(scene); stage.setTitle("JavaFX demo application on Raspberry Pi"); stage.show(); // Make sure the application quits completely on close stage.setOnCloseRequest(t -> CleanExit.doExit(this.gpioHelper.getGpioController())); } public static void main(String[] args) { launch(); } }
Запуск приложения на одноплатном компьютере Raspberry Pi
$ curl -sSL https://pi4j.com/install | sudo bash
java -jar
:
$ cd /home/pi $ ls *.jar javamagazine-javafx-example-0.0.1-jar-with-dependencies.jar $ java -jar javamagazine-javafx-example-0.0.1-jar-with-dependencies.jar INFO be.webtechie.gpio.DistanceSensorMeasurement - Distance is: 103cm for 0.006021977s INFO be.webtechie.gpio.DistanceSensorMeasurement - Distance is: 265cm for 0.01544218s INFO be.webtechie.gpio.DistanceSensorMeasurement - Distance is: 198cm for 0.011520567s INFO be.webtechie.gpio.ButtonChangeEventListener - Button state changed to high INFO be.webtechie.gpio.ButtonChangeEventListener - Button state changed to low
Заключение
- 11 views
- 0 Comment