Поскольку я становлюсь старшим разработчиком по возрасту, я перешел с одного языка на другой. Одним из моих главных интересов всегда были чистые, простые для понимания пользовательские интерфейсы (пользовательский интерфейс). Это путешествие началось для меня с Director (для создания мультимедийных компакт-дисков), Flash анимации веб-сайтов и Flex многофункциональных интернет-приложений (= “Flash на стероидах»).
Когда я начал разработку с Java более 10 лет назад, у нас было несколько проектов с ранними версиями Vaadin и JavaFX. . Когда я перешел к серверным приложениям, я продолжил использовать JavaFX только для некоторых личных и побочных проектов, и мне понравилось, как вы можете создавать пользовательский интерфейс как с XML (на самом деле FXML), так и с кодом, точно такой же подход, который мне нравился с Flex. С тех пор моя любовь к Java и JavaFX только росла, и это по-прежнему моя основная среда программирования.
Но у JavaFX недостает одной детали: запуска в браузере… Да, JPRO может это сделать, но для этого нужна лицензия и выделенный сервер. И да, есть некоторые текущие проекты, чтобы полностью перенести JavaFX в браузер, но они продолжаются и еще не созрели… Давайте рассмотрим другой подход: Vaadin Flow и запустим его на Raspberry Pi для управления светодиодом и отображения состояния кнопка.
О Ваадин
Только недавно я заново открыл для себя Vaadin для создания пользовательских веб-интерфейсов, и это действительно похоже на JavaFX в браузере. Полный код Java для создания ваших представлений и запуска его в браузере без необходимости писать ни одного файла HTML, CSS, JavaScript или TypeScript! Vaadin Flow – это уникальная платформа, позволяющая создавать современные (отзывчивые!) веб-приложения на 100 % на Java без написания кода HTML или JavaScript.
В этом руководстве я использую этот Vaadin Flow в сочетании с Pi4J для создания веб-интерфейса для взаимодействия со светодиодом и кнопкой, подключенными к контактам GPIO (ввод/вывод общего назначения) Raspberry Pi. Готовый проект доступен на GitHub.
О Pi4J
Pi4J, библиотека ввода-вывода Java для Raspberry Pi призвана объединить программирование на Java с электроникой. Проект стартовал в 2012 г. Версия 1.3 была выпущена в феврале 2021 г. для поддержки новейших плат Raspberry Pi (4, 400 и Compute 4) и является последней версией, основанной на Java 8. Летом 2021 г. Запущена версия 2, основанная на модулях Java 11, PiGpio, Java и т. д.
Используя Pi4J-зависимость в проекте, управление электронными компонентами, подключенными к контактам GPIO (ввод/вывод общего назначения) Raspberry Pi, можно контролировать как объекты в коде Java. Pi4J использует собственные библиотеки для управления GPIO, поэтому вам, как программисту, не нужно полностью осознавать всю магию, связанную с аппаратной связью.
Зачем запускать Java на Raspberry Pi?!
Целью проекта Raspberry Pi было создание недорогого ПК (от 15 долларов), доступного для всех. Вы можете подключить его к телевизору, если у вас нет экрана компьютера и доступны разные версии, в зависимости от вашего бюджета. Raspberry Pi — это полный Linux-ПК, предлагающий различные версии операционной системы Raspberry Pi, чтобы вы могли легко начать работу со всеми языками программирования, а также с Java.
Но главная причина, по которой я влюбился в Raspberry Pi, — это GPIO. Когда вы посмотрите на плату Raspberry Pi, вы обнаружите 40 контактов (2 ряда по 20). Это контакты GPIO (= ввод/вывод общего назначения). Некоторые из них могут использоваться как цифровой вход или выход, то есть 0 вольт отключен, 3,3 вольт включен. И количество вещей, которые вы можете сделать с ним, поражает! Эти контакты GPIO являются основными факторами, которые сделали Raspberry Pi таким успешным! Вы действительно можете найти дешевый подержанный ноутбук с аккумулятором, экраном и т. д., но вы никогда не сможете проводить эксперименты, сочетающие программное и аппаратное обеспечение, таким простым способом, как вы можете сделать это с Raspberry Pi.
И последний плюс Raspberry Pi: его мощность! 4-версия быстрая и имеет много памяти. Я написал об этом целую книгу и создал с ее помощью множество различных Java-приложений. Вы можете подключить до двух мониторов 4K, что означает много места для одновременного открытия разных приложений, терминалов, файлового менеджера и т. д.
Пример приложения Spring + Vaadin + Pi4J
Давайте создадим демонстрационный проект на основе Spring, чтобы проиллюстрировать, как пользовательский интерфейс Vaadin (веб-сайт) может взаимодействовать с GPIO Raspberry Pi с помощью библиотеки Pi4J. Взаимодействие GPIO основано на минимальном примере приложения Pi4J и использует кнопку и светодиод для демонстрации взаимодействия цифрового ввода и вывода. Проводка очень проста и требует всего несколько компонентов.
Базовый код был сгенерирован на start.vaadin.com/app со следующими параметрами:
- Ваадин 23
- Ява 17
- Поток (только Java)
- Дополнительная страница и пользовательские значки
Скачайте исходники, разархивируйте и откройте в предпочитаемой вами среде IDE. Это проект Maven, поэтому вы можете сразу же запустить его и проверить код, который был сгенерирован автоматически. При запуске приложение будет доступно по адресу https://localhost:8080 на самом устройстве.
Дополнительные зависимости
Поскольку мы хотим взаимодействовать с GPIO с помощью Pi4J, нам нужно добавить некоторые дополнительные зависимости в pom.xml:
- pi4j-ядро
- pi4j-плагин-raspberrypi
- pi4j-плагин-pigpio
Сервис Pi4J
Поскольку у нас уже есть предварительно созданное приложение Spring, мы можем легко расширить его с помощью службы для управления всеми методами, связанными с Pi4J. Начнем с инициализации контекста Pi4J, который отвечает за все взаимодействия GPIO.
private final Context pi4j; private static final int PIN_BUTTON = 24; // PIN 18 = BCM 24 private static final int PIN_LED = 22; // PIN 15 = BCM 22 private final Queue<ButtonListener> buttonListeners; private DigitalOutput led; public Pi4JService() { pi4j = Pi4J.newAutoContext(); buttonListeners = new ConcurrentLinkedQueue<>(); initLed(); initButton(); }
Получив контекст, мы можем использовать его для настройки и инициализации DigitalOutput для светодиода и DigitalInput для кнопки. В нашем примерном приложении у нас есть только один компонент, который изменяется в зависимости от состояния кнопки, но с помощью интерфейса ButtonListener и списка реализаций мы можем связать неограниченное количество компонентов с состоянием кнопки.
private void initLed() { try { var ledConfig = DigitalOutput.newConfigBuilder(pi4j) .id("led") .name("LED") .address(PIN_LED) .shutdown(DigitalState.LOW) .initial(DigitalState.LOW) .provider("pigpio-digital-output"); led = pi4j.create(ledConfig); logger.info("The LED has been initialized on pin {}", PIN_LED); } catch (Exception ex) { logger.error("Error while initializing the LED: {}", ex.getMessage()); } } /** * Toggle the LED on or off. * * @param on */ public void setLedState(boolean on) { led.setState(on); } private void initButton() { try { var buttonConfig = DigitalInput.newConfigBuilder(pi4j) .id("button") .name("Button") .address(PIN_BUTTON) .pull(PullResistance.PULL_DOWN) .debounce(3000L) .provider("pigpio-digital-input"); var button = pi4j.create(buttonConfig); button.addListener(e -> { logger.info("Button state changed to {}", e.state()); buttonListeners.forEach(bl -> bl.onButtonEvent(e.state())); }); logger.info("The button has been initialized on pin {}", PIN_BUTTON); } catch (Exception ex) { logger.error("Error while initializing the button: {}", ex.getMessage()); } } /** * Add a button listener which will get all state changes of the button DigitalInput * * @param buttonListener */ public void addButtonListener(ButtonListener buttonListener) { buttonListeners.add(buttonListener); }
Кроме того, есть несколько вспомогательных методов для отображения состояния библиотеки Pi4J. Это только один из них, пожалуйста, проверьте источники для полных реализаций:
/** * Providers are intended to represent I/O implementations and provide access to the I/O interfaces available on * the system. Providers 'provide' concrete runtime implementations of I/O interfaces. */ public String getProviders() { if (pi4j == null || pi4j.providers() == null) { return "None"; } return pi4j.providers().all().entrySet().stream() .map(e -> e.getKey() + ": " + e.getValue()) .collect(Collectors.joining(",")); }
Пользовательский интерфейс для переключения светодиода
Теперь давайте изменим первую страницу для управления светодиодом. Код на самом деле очень ограничен, так как мы используем Checkbox
и добавляем прослушиватель значений, чтобы указать нашему Pi4JService изменить состояние светодиода.
public class ButtonView extends HorizontalLayout implements ButtonListener { Logger logger = LoggerFactory.getLogger(ButtonView.class); private final UI ui; private final Label lbl; public ButtonView(@Autowired Pi4JService pi4JService) { ui = UI.getCurrent(); lbl = new Label("Waiting for button change..."); add(lbl); setMargin(true); setVerticalComponentAlignment(Alignment.END, lbl); pi4JService.addButtonListener(this); } @Override public void onButtonEvent(DigitalState state) { var isPressed = state.equals(DigitalState.HIGH); logger.info("Button event in listener: {} - Is on: {}", state, isPressed); ui.accessSynchronously(() -> lbl.setText(isPressed ? "Button is pressed" : "Button is released")); } }
Пользовательский интерфейс для просмотра состояния кнопки
Этот пользовательский интерфейс имеет немного больше кода. Он расширяет ButtonListener
, поэтому необходимо переопределить onButtonEvent
, но другие части кода говорят сами за себя. Здесь следует отметить еще одну вещь: нам нужно использовать ui.accessSynchronously
для изменения компонента, который является частью представления, поскольку Pi4JService и пользовательский интерфейс работают в отдельных потоках.
public class ButtonView extends HorizontalLayout implements ButtonListener { Logger logger = LoggerFactory.getLogger(ButtonView.class); private final UI ui; private final Label lbl; public ButtonView(@Autowired Pi4JService pi4JService) { ui = UI.getCurrent(); lbl = new Label("Waiting for button change..."); add(lbl); setMargin(true); setVerticalComponentAlignment(Alignment.END, lbl); pi4JService.addButtonListener(this); } @Override public void onButtonEvent(DigitalState state) { var isPressed = state.equals(DigitalState.HIGH); logger.info("Button event in listener: {} - Is on: {}", state, isPressed); ui.accessSynchronously(() -> lbl.setText(isPressed ? "Button is pressed" : "Button is released")); } }
Необходимо дополнительное изменение в основном классе. Поскольку мы хотим отправить изменения из backend
в user interface
, нам нужно добавить атрибут @Push. Это крошечное изменение позволяет вам обновлять пользовательский интерфейс с сервера без явного запроса пользователем обновлений. Это основано на соединении клиент-сервер (WebSocket, если поддерживается, или альтернативном), которое клиент устанавливает, а сервер может затем использовать для отправки обновлений клиенту.
@SpringBootApplication @Theme(value = "pi4jdemo") @NpmPackage(value = "line-awesome", version = "1.3.0") @Push public class Application extends SpringBootServletInitializer implements AppShellConfigurator {
Пользовательский интерфейс с информацией Pi4J
Чтобы иметь возможность отлаживать, как Pi4J взаимодействует с GPIO, на экран «О программе» добавляется некоторая дополнительная информация, чтобы показать загруженные платформы и провайдеры. Реестр содержит список всех инициализированных входов и выходов, поэтому мы ожидаем увидеть здесь светодиод и кнопку.
public class AboutView extends VerticalLayout { public AboutView(@Autowired Pi4JService pi4JService) { setSpacing(false); add( new H2("Java"), new Paragraph("Version: " + System.getProperty("java.version")), new H2("Pi4J"), new Paragraph("Default platform: " + pi4JService.getDefaultPlatform()), new Paragraph("Loaded platforms: " + pi4JService.getLoadedPlatforms()), new Paragraph("Providers: " + pi4JService.getProviders()), new Paragraph("Registry: " + pi4JService.getRegistry()) ); setSizeFull(); getStyle().set("text-align", "left"); } }
Сборка и загрузка на Raspberry Pi
Если вы разрабатываете на ПК, вы можете собрать приложение с помощью следующей команды
- Windows 7_
- Mac и Linux:
./mvnw clean package -Pproduction
и загрузите на свой Raspberry Pi с помощью следующей команды (замените логин pi
и IP-адрес на адрес вашей платы):
$ scp target/pi4jdemo-1.0-SNAPSHOT.jar [email protected]://home/pi/
Запуск на Raspberry Pi
Теперь мы можем запустить приложение на нашем Raspberry Pi, но нам нужно запустить его с sudo
, поскольку Pi4J (все еще) нуждается в этом, чтобы иметь возможность взаимодействовать с GPIO, используя PiGpio в качестве родной библиотеки.
$ sudo java -jar /home/pi/pi4jdemo-1.0-SNAPSHOT.jar
Если вы установили Java с помощью SDKMAN как обычный пользователь, вы, вероятно, получите эту ошибку:
sudo: java: command not found
Один из способов исправить это — позволить самой команде искать, где установлена Java, с помощью `which java`
:
$ sudo `which java` -jar /home/pi/pi4jdemo-1.0-SNAPSHOT.jar ____ _ _ _ _ ____ | _ \ (_)| || | | | | _ \ ___ _ __ ___ ___ | |_) || || || |_ _ | | | | | | / _ \| '_ ` _ \ / _ \ | __/ | ||__ _|| |_| | | |_| || __/| | | | | || (_) | |_| |_| |_| \___/ |____/ \___||_| |_| |_| \___/ INFO 1807 --- [ main] be.webtechie.vaadin.pi4j.Application : Starting Application v1.0-SNAPSHOT using Java 17.0.2 on 64bit with PID 1807 (/home/pi/pi4jdemo-1.0-SNAPSHOT.jar started by root in /home/pi) INFO 1807 --- [ main] be.webtechie.vaadin.pi4j.Application : No active profile set, falling back to 1 default profile: "default" INFO 1807 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) INFO 1807 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] INFO 1807 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.58] INFO 1807 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext INFO 1807 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 6486 ms INFO 1807 --- [ main] c.v.f.s.VaadinServletContextInitializer : Search for VaadinAppShell took 125 ms INFO 1807 --- [ main] com.pi4j.Pi4J : New auto context INFO 1807 --- [ main] com.pi4j.Pi4J : New context builder INFO 1807 --- [ main] c.p.p.impl.DefaultRuntimePlatforms : adding platform to managed platform map [id=raspberrypi; name=RaspberryPi Platform; priority=5; class=com.pi4j.plugin.raspberrypi.platform.RaspberryPiPlatform] INFO 1807 --- [ main] b.w.vaadin.pi4j.service.Pi4JService : The LED has been initialized on pin 22 INFO 1807 --- [ main] b.w.vaadin.pi4j.service.Pi4JService : The button has been initialized on pin 24 INFO 1807 --- [ main] c.v.f.s.DefaultDeploymentConfiguration : Vaadin is running in production mode. INFO 1807 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' INFO 1807 --- [ main] be.webtechie.vaadin.pi4j.Application : Started Application in 14.089 seconds (JVM running for 17.643) INFO 1807 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' INFO 1807 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' INFO 1807 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 3 ms INFO 1807 --- [nio-8080-exec-1] c.vaadin.flow.spring.SpringInstantiator : The number of beans implementing 'I18NProvider' is 0. Cannot use Spring beans for I18N, falling back to the default behavior INFO 1807 --- [ Thread-7] b.w.vaadin.pi4j.service.Pi4JService : Button state changed to LOW INFO 1807 --- [ Thread-7] b.w.vaadin.pi4j.views.button.ButtonView : Button event in listener: LOW - Is on: false INFO 1807 --- [ Thread-8] b.w.vaadin.pi4j.service.Pi4JService : Button state changed to HIGH INFO 1807 --- [ Thread-8] b.w.vaadin.pi4j.views.button.ButtonView : Button event in listener: HIGH - Is on: true
В этом видео вы можете увидеть журналы работающего приложения на Raspberry Pi, браузера на ПК в той же сети, чтобы проиллюстрировать отзывчивый веб-интерфейс, а также вид камеры на макетную плату и Raspberry Pi со светодиодом и кнопкой:
Заключение
Благодаря стартовому сайту, предоставленному Vaadin, вы можете получить полностью работающее приложение в несколько кликов. Добавление GPIO-взаимодействия легко с библиотекой Pi4J и позволяет вам полностью взаимодействовать с электронными компонентами, подключенными к контактам GPIO Raspberry Pi. Используя исходные коды этой статьи, вы можете легко начать работу и расширить ее с помощью других типов компонентов.
Первоначально опубликовано на https://dzone.com.