Wer vielleicht auch wie der Autor in den frühen 1980er Jahren mit dem Home-Computing eingestiegen ist und auch damals bereits gerne mit Elektronik gebastelt und programmiert hat, wird sich bestimmt auch über die kleinen preiswerten Minicomputer gefreut haben, die seit einiger Zeit auf dem Markt sind.
Ursprünglich war der Raspberry Pi (RPi), der im Jahr 2012 auf den Markt kam, als Lehrplattform für digitale Elektronik auf Basis der Sprache Python konzipiert (daher das “Pi“ Namen, „Python interpreter“). Der Python interpreter sollt ursprünglich fest eingebaut sein, aber am Ende wurde es dann doch eine sehr offene Architektur und so hat sich um dieses Gerät sehr schnell eine recht große Community gebildet.
Bis heute wurden bereits mehr als 5 Millionen Stück verkauft. Dieser hohe Verbreitungsgrad und die damit verbundene sehr breite Unterstützung machen den Einstieg ins digitale Experimentieren sehr leicht und vor allem preiswert: Ein aktueller Raspberry Pi 2 Model B mit Gehäuse, Netzteil, WLAN-Stick und SD-Karte liegt bei 70-80 Euro.
Auch Java-Entwickler kommen hier inzwischen voll auf ihre Kosten, denn seit geraumer Zeit bringt Oracle regelmäßig parallel zu den „normalen“ JDK 8 Builds jeweils ein JDK 8 for ARM , dass speziell für ARM basierte Plattformen, wie den Raspberry Pi gedacht ist.
Anfangs wurde die Unterstützung von JavaFX für den Einsatz von grafischen Elementen explizit von Oracle beworben. Mit dem aktuellen „Update 33“-Release wurde aber „klamm und heimlich“ JavaFX aus den offiziellen Builds ausgeklammert und dem OpenJDK Projekt übergeben. Johan Vos (Gluon) hat glücklicherweise gleich ein Paket zusammengestellt, dass vom „javafxports“-Repository herunterladen und extrahieren werden kann. Die darin verpackten Bibliotheken lassen sich einfach in entsprechende Verzeichnisse eines installierten ARM-JDKs von Oracle kopieren. Damit steht JavaFX für Embedded wieder zur Verfügung.
Bisher waren die Gestaltungsmöglichkeiten von JavaFX-Oberflächen auf dem RPi Aufgrund eingeschränkter Leistung überschaubar. Vor kurzem ist jedoch der Raspberry Pi 2 mit QuadCore Prozessor und 1GB RAM erschienenen und damit wird es wieder interessanter, UIs für den RPi zu entwickeln:
Mit dem Laden des Videos akzeptieren Sie die Datenschutzerklärung von YouTube.
Mehr erfahren
YouTube immer entsperren
Da JavaFX als Besonderheit direkt im Framebuffer läuft, ist kein laufender X-Server erforderlich und es können deutlich Ressourcen gespart werden.
Dieser Artikel zeigt exemplarisch, wie sich Eingangs- und Ausgangszustände eines Raspberry Pi über eine JavaFX basierte Touch-Oberfläche manipulieren und visualisieren lassen.
NetBeans 8
NetBeans 8 hält für Internet-of-Things-Java-Entwickler ein sehr beachtenswertes Feature bereit, das bereits in der Standardinstallation voll integriert zur Verfügung steht:
die Möglichkeit, direkt aus der IDE ein Projekt auf ein Embedded Device, wie einen Raspberry Pi einzurichten und dort remote auszuführen. Dabei kann das Projekt auch im Debug-Modus ausführt oder mit dem Profiler zur Laufzeit überwacht werden.
Vorausgesetzt auf dem entfernten Gerät läuft ein SSH-Dienst, kann eine neue Plattform vom Typ „Remote Java Standard Edition“ erstellt werden. Hier sind dann im folgenden Dialog Informationen wie Adresse des Gerätes, Login Credentials und Pfad zum zu benutzenden JRE/JDK hinterlegt. Danach kann diese in den Projekteinstellungen diese dann als Zielplattform ausgewählt und dann das Projekt laufen gelassen werden.
Abbildung 1:
Remote Platform in NetBeans 8.0.2
Dabei wird das Programm mit allen Abhängigkeiten lokal gebaut, per SCP übertragen und remote via SSHExec ausgeführt. Die Ausgaben von stdout und stderr werden schließlich in die Konsole von NetBeans umgeleitet. Das Remote-Deployment dient nicht nur zur entfernten Ausführung sondern kann eben auch als Auslieferung verstanden werden, denn die Applikation wird anschließend nicht vom RPi gelöscht.
Besonders betont sei hier noch das Setzen von „sudo“ als „Exec Prefix“ Property. Diese Einstellung sorgt dafür, dass das Java-Programm mit „Root“-Rechten auf dem entfernten Gerät ausgeführt wird. Wenn keine anderweitigen Gruppen- und Rechteumstellungen auf dem Raspberry Pi einrichtet werden sollen, ist dies unbedingt nötig, um auf die GPIO-Schnittstelle zugreifen zu dürfen.
Da all diese Schritte ANT basiert sind, kann hier leider nicht auf Maven-Repositories zugegriffen werden. Es ist also erforderlich, Bibliotheken und Abhängigkeiten manuell mit NetBeans-Bordmitteln zu verwalten.
GPIO und Pi4J
Zunächst ein Blick auf die General Purpose Input/Output (GPIO) Schnittstelle des Raspberry Pi. Sie ist das IO-Interface des RPi deren Funktionen sich sehr leicht mit wiringPi (eine C basierte API von Gordon Henderson (@drogon)) nutzen lassen. Praktischer Weise gibt es mit Pi4J eine Java API, das genau auf wiringPi aufsetzt und dieses auch noch passend automatisch mitbringt.
Der GPIO-Header steht im Mittelpunkt der ausgehenden und eingehenden Kommunikation. Beim Modell B können insgesamt 17 Pins zum I/O angefordert werden: 8 Pins GPIO Pins, 2 Pins vom I2C Interface, 5 Pins Serial Peripheral Interface, 2 Pins Serial UART (+ 4 weitere via P5 Connector (nur Rev. 2.0)). Das neuere Modell B+ hält noch weitere 9 GPIOs bereit.
Pi4J unterstützt alle RPi Modelle vom einfachem I/O über PWM und SPI bis I2C bleiben keine Wünsche offen.
Pi4J als Bibliothek einrichten
Zunächst lädt und entpackt man ein aktuelles Build, z.B. den Pi4J 1.0 Release Candidate von der Pi4J Site. Anschließend kann dann in NetBeans eine Bibliothek eingerichtet werden.
Beispiel-Projekt
Für das folgende Beispiel-Projekt „8 Kanal I/O Interface mit JavaFX basierter Touch-Oberfläche“ werden folgende Bauteil benötigt:
- Raspberry Pi (B oder B+)
- Breakout-Kit
- Zwei Breadboards
- Steckverbinder
- 7“ Touch-Display von Chalk-Elec [10]
- Acht LEDs
- Acht 330 Ω Vorwiderstände
- Acht Taster
Abbildung 2:
Schaltungsaufbau der 8 Kanal I/O Steckplatine
Die Zustände für die Ausgänge werden über ToggleButtons der grafischen Oberfläche manipuliert, die Eingänge über Taster getriggert und die Zustände in beiden Fällen an der UI angezeigt. Der I/O-Modus (Eingang/Ausgang) kann jeweils pro Kanal zur Laufzeit umgeschaltet werden (Abbildung 3 bis 5).
Zum besseren Testen der Oberflache kann zudem der GPIO Controller wahlweise aktiviert oder deaktiviert werden. Außerdem darf ein „Exit“-Button nicht vergessen werden: JavaFX kann direkt aus der Konsole den FrameBuffer übernehmen und die Anwendung kann dann nicht ohne Weiteres beendet werden (CTRL-D ist wirkungslos, weil JavaFX Keyboard-Events abfängt).
Abbildung 3:
Das JavaFX UI zum 8 Kanal I/O
Abbildung 4:
Über das UI kann zum Beispiel auch der I/O Modus pro Kanal (IN/OUT) gewählt werden
Abbildung 5:
Ein Kanal der Schaltung wird in der UI durch eine Spalteneinheit repräsentiert
Für die UI soll die eigentliche GPIO Kommunikation transparent sein. Daher werden alle Zustände über einen JavaFX-Properties-Adapter gekapselt. Diese Zwischenschicht bildet die Verbindung von UI und einer Einheit, die über Pi4J das GPIO Interface anspricht.
Abbildung 5:
Die Architektur des Datenflusses vom Taster zur UI und zurück
Etwas gekürzter Auszug der Klasse GpioAdapter.java
(es wurde der Code zu Kanal 1-7 ausgeblendet):
1public class GpioAdapter {
2
3 private GpioController gpio;
4 private GpioPinDigitalMultipurpose pin0;
5 […]
6 private ObjectProperty gpio0ModeProperty;
7 […]
8 private BooleanProperty gpio0StateProperty;
9 […]
10 private BooleanProperty connectedProperty;
11 private Timeline testTimeline;
12 private GpioPinDigitalMultipurpose[] pins;
13 private BooleanProperty[] stateProperties;
14 private ObjectProperty[] modeProperties;
15 private final static Logger LOGGER = Logger.getLogger(GpioAdapter.class.getName());
16
17 public GpioAdapter() {
18 init();
19 }
20
21 private void init() {
22 connectedProperty = new SimpleBooleanProperty(Boolean.FALSE);
23 gpio0StateProperty = new SimpleBooleanProperty(Boolean.FALSE);
24 […]
25 stateProperties = new BooleanProperty[]{gpio0StateProperty,
26 gpio1StateProperty,
27 […]
28 };
29 gpio0ModeProperty = new SimpleObjectProperty<>(PinMode.DIGITAL_OUTPUT);
30 […]
31 modeProperties = new ObjectProperty[]{
32 gpio0ModeProperty,
33 […]
34 };
35 }
36
37 private ChangeListener createPinStatePropertyListener(final GpioPinDigitalMultipurpose pin) {
38 return (ObservableValue<? extends Boolean> ov, Boolean oldValue, Boolean newValue) -> {
39 LOGGER.log(Level.INFO, "pinPropertyChanged: {0} {1}", new Object[]{pin.getName(), newValue});
40 if (pin.getMode() != PinMode.DIGITAL_INPUT) {
41 if (newValue) {
42 pin.high();
43 } else {
44 pin.low();
45 }
46 }
47 };
48 }
49
50 private void addGpioInputListener(final GpioPinDigitalMultipurpose pin, final BooleanProperty gpioStateProperty) {
51 pin.addListener((GpioPinListenerDigital) new GpioPinListenerDigital() {
52 @Override
53 public void handleGpioPinDigitalStateChangeEvent(final GpioPinDigitalStateChangeEvent event) {
54 LOGGER.log(Level.INFO, "pinstateChanged: {0} {1}", new Object[]{pin.getName(), event.getState()});
55 Platform.runLater(() -> {
56 gpioStateProperty.set(event.getState().
57 isHigh());
58 });
59 }
60 });
61 }
62
63 /*
64 * -------------------------- ACTIONS --------------------------
65 */
66 public void connect() {
67 LOGGER.log(Level.INFO, "connect...");
68
69 gpio = GpioFactory.getInstance();
70 pin0 = gpio.provisionDigitalMultipurposePin(RaspiPin.GPIO_00, gpio0ModeProperty.get(), PinPullResistance.PULL_DOWN);
71 […]
72
73 pins = new GpioPinDigitalMultipurpose[]{
74 pin0, pin1, pin2, pin3, pin4, pin5, pin6, pin7
75 };
76 gpio.setShutdownOptions(true, PinState.LOW, pins);
77
78 addGpioInputListener(pin0, gpio0StateProperty);
79 […]
80
81 gpio0StateProperty.addListener(createPinStatePropertyListener(pin0));
82 […]
83
84 reset();
85 setConnectedPropertyValue(Boolean.TRUE);
86 LOGGER.log(Level.INFO, "connected.");
87
88 }
89
90 public void setOnAllPins() {
91 LOGGER.log(Level.INFO, "setOnAllPins()");
92 for (int i = 0; i <= 7; i++) {
93 stateProperties[i].setValue(Boolean.TRUE);
94 }
95 }
96
97 public void setAllPinsLow() {
98 LOGGER.log(Level.INFO, "setOffAllPins()");
99 for (int i = 0; i <= 7; i++) {
100 stateProperties[i].setValue(Boolean.FALSE);
101 }
102 }
103
104 public void disconnect() {
105 LOGGER.log(Level.INFO, "disconnect()");
106 if (gpio != null) {
107 gpio.shutdown();
108 }
109 setConnectedPropertyValue(Boolean.FALSE);
110
111 }
112
113 public void resetIOModes() {
114 LOGGER.log(Level.INFO, "resetIOModes()");
115 for (int i = 0; i <= 7; i++) { setGpioMode(i, PinMode.DIGITAL_OUTPUT); } } public void reset() { LOGGER.log(Level.INFO, "reset()"); if (testTimeline != null) { testTimeline.stop(); } setAllPinsLow(); resetIOModes(); } public void connectTest() { LOGGER.log(Level.INFO, "connectTest()"); if (testTimeline != null) { testTimeline.stop(); } reset(); testTimeline = new Timeline(new KeyFrame(Duration.seconds(1), (ActionEvent event) -> {
116 setOnAllPins();
117 }), new KeyFrame(Duration.seconds(0.1), (ActionEvent event) -> {
118 setAllPinsLow();
119 }));
120 testTimeline.play();
121 }
122
123 public void test(double millis) {
124 LOGGER.log(Level.INFO, "test()");
125 reset();
126 testTimeline = new Timeline(new KeyFrame(Duration.millis(millis), (ActionEvent event) -> {
127 gpio0StateProperty.setValue(Boolean.TRUE);
128 }), new KeyFrame(Duration.millis(millis * 2), (ActionEvent event) -> {
129 gpio1StateProperty.setValue(Boolean.TRUE);
130 }), new KeyFrame(Duration.millis(millis * 3), (ActionEvent event) -> {
131 gpio2StateProperty.setValue(Boolean.TRUE);
132 }), new KeyFrame(Duration.millis(millis * 4), (ActionEvent event) -> {
133 gpio3StateProperty.setValue(Boolean.TRUE);
134 }), new KeyFrame(Duration.millis(millis * 5), (ActionEvent event) -> {
135 gpio4StateProperty.setValue(Boolean.TRUE);
136 }), new KeyFrame(Duration.millis(millis * 6), (ActionEvent event) -> {
137 gpio5StateProperty.setValue(Boolean.TRUE);
138 }), new KeyFrame(Duration.millis(millis * 7), (ActionEvent event) -> {
139 gpio6StateProperty.setValue(Boolean.TRUE);
140 }), new KeyFrame(Duration.millis(millis * 8), (ActionEvent event) -> {
141 gpio7StateProperty.setValue(Boolean.TRUE);
142 }));
143 testTimeline.play();
144 }
145
146 /*
147 * -------------------------- PROPERTY METHODS --------------------------
148 */
149 public void setGpioStateValue(int pinNumber, Boolean state) {
150 stateProperties[pinNumber].setValue(state);
151 }
152
153 public void setGpioMode(int pinNumber, PinMode mode) {
154 modeProperties[pinNumber].setValue(mode);
155 if (isConnected()) {
156 pins[pinNumber].setMode(mode);
157 if (PinMode.DIGITAL_OUTPUT.equals(mode)) {
158 pins[pinNumber].setState(PinState.LOW);
159 }
160 }
161 }
162
163 public void setConnectedPropertyValue(Boolean connected) {
164 this.connectedProperty.setValue(connected);
165 }
166
167 public boolean isConnected() {
168 return connectedProperty.get();
169 }
170
171 public BooleanProperty connectedProperty() {
172 return connectedProperty;
173 }
174
175 public BooleanProperty gpio0StateProperty() {
176 return gpio0StateProperty;
177 }
178 […]
179 public ObjectProperty gpio0ModeProperty() {
180 return gpio0ModeProperty;
181 }
182 […]
183}
Etwas gekürzter Auszug der Klasse IOBoard.java
(der Gpio-UI-Controller, (es wurde der Code zu Kanal 1-7 ausgeblendet):
1public class IOBoard extends VBox {
2
3 private final static Logger LOGGER = Logger.getLogger(IOBoard.class.getName());
4 @FXML
5 private GridPane buttonGridPane;
6 @FXML
7 private ToggleButton toogleGPIO0;
8 @FXML
9 […]
10 @FXML
11 private ToggleButton toggleModeGPIO0;
12 […]
13 @FXML
14 private Button exitButton;
15 @FXML
16 private VBox indicatorBox0;
17 @FXML
18 […]
19 @FXML
20 private ToggleButton gpioConnectToggleButton;
21 @FXML
22 private ResourceBundle resources;
23
24 private GpioAdapter gpioAdapter;
25 private Indicator indicatorGPIO0;
26 […]
27 private ToggleButton[] toggleGPIOButtons;
28 private ToggleButton[] toggleGPIOModeButtons;
29
30 public IOBoard() {
31 init();
32 }
33
34 private void init() {
35 ResourceBundle resourceBundle = ResourceBundle.getBundle(getClass().getPackage().getName() + ".ioboard");
36 FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("ioboard.fxml"));
37 fxmlLoader.setResources(resourceBundle);
38 fxmlLoader.setRoot(this);
39 fxmlLoader.setController(this);
40 try {
41 fxmlLoader.load();
42 } catch (IOException ex) {
43 LOGGER.log(Level.SEVERE, null, ex);
44 }
45 AwesomeDude.setIcon(exitButton, AwesomeIcon.POWER_OFF, "2em");
46 gpioAdapter = new GpioAdapter();
47 indicatorGPIO0 = createIndicator();
48 […]
49
50 indicatorBox0.getChildren().add(indicatorGPIO0);
51 […]
52
53 indicatorGPIO0.passProperty().bindBidirectional(gpioAdapter.gpio0StateProperty());
54 […]
55
56 gpioAdapter.gpio0ModeProperty().addListener(new IOModeChangeEventHandler(toggleModeGPIO0));
57 […]
58
59 toggleModeGPIO0.setOnAction(new ToggleModeEventHandler(toggleModeGPIO0, 0));
60 […]
61
62 toogleGPIO0.visibleProperty().bind(toggleModeGPIO0.selectedProperty().not());
63 […]
64
65 toogleGPIO0.selectedProperty().bindBidirectional(gpioAdapter.gpio0StateProperty());
66 […]
67
68 toggleGPIOButtons = new ToggleButton[]{
69 toogleGPIO0,
70 […]
71 };
72
73 toggleGPIOModeButtons = new ToggleButton[]{
74 toggleModeGPIO0,
75 […]
76 };
77
78 gpioConnectToggleButton.selectedProperty()
79 .addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean selected) -> {
80 if (!OS.isLinux()) {
81 LOGGER.info("Obviously not running on a Raspberry Pi. GPIO is not going to be connected.");
82 gpioConnectToggleButton.setSelected(false);
83 return;
84 }
85 if (selected) {
86 LOGGER.info("GPIO Connect");
87 gpioConnectToggleButton.setText(resources.getString("button.gpio.connected"));
88 onGpioConnect();
89 } else {
90 LOGGER.info("GPIO Disconnect");
91 gpioConnectToggleButton.setText(resources.getString("button.gpio.disconnected"));
92 onGpioDisconnect();
93 }
94 }
95 );
96
97 onReset();
98 }
99
100 private Indicator createIndicator() {
101 Indicator indicator = new Indicator();
102 indicator.setResult(Indicator.Result.FAIL);
103 indicator.setPrefSize(100.0, 100.0);
104 return indicator;
105 }
106
107 /*
108 * -------------------------- ACTIONS --------------------------
109 */
110 @FXML
111 public void onTest() {
112 LOGGER.info("onTest");
113 for (ToggleButton toggleGPIOModeButton : toggleGPIOModeButtons) {
114 toggleGPIOModeButton.setSelected(false);
115 }
116 gpioAdapter.test(1000);
117 }
118
119 @FXML
120 public void onReset() {
121 LOGGER.info("onReset");
122 for (ToggleButton toggleGPIOModeButton : toggleGPIOModeButtons) {
123 toggleGPIOModeButton.setSelected(false);
124 }
125 gpioAdapter.reset();
126 }
127
128 public void onGpioDisconnect() {
129 LOGGER.info("onGpioDisconnect");
130 gpioAdapter.disconnect();
131 }
132
133 public void onGpioConnect() {
134 LOGGER.info("onGpioConnect");
135 gpioAdapter.connect();
136 }
137
138 @FXML
139 public void onExit() {
140 LOGGER.info("onExit");
141 Platform.exit();
142 System.exit(0);
143
144 }
145
146 private class ToggleModeEventHandler implements EventHandler {
147
148 private final ToggleButton button;
149 private final int pinNumber;
150
151 public ToggleModeEventHandler(final ToggleButton button, final int pinNumber) {
152 this.button = button;
153 this.pinNumber = pinNumber;
154 }
155
156 @Override
157 public void handle(ActionEvent t) {
158 toggleGPIOButtons[pinNumber].setSelected(false);
159 if (button.isSelected()) {
160 LOGGER.log(Level.INFO, "set Pin: {0} Mode: {1}", new Object[]{pinNumber, PinMode.DIGITAL_INPUT});
161 gpioAdapter.setGpioMode(pinNumber, PinMode.DIGITAL_INPUT);
162
163 } else {
164 LOGGER.log(Level.INFO, "set Pin: {0} Mode: {1}", new Object[]{pinNumber, PinMode.DIGITAL_OUTPUT});
165 gpioAdapter.setGpioMode(pinNumber, PinMode.DIGITAL_OUTPUT);
166 }
167 }
168 }
169
170 private class IOModeChangeEventHandler implements ChangeListener {
171
172 private final ToggleButton modeToggleButton;
173
174 public IOModeChangeEventHandler(final ToggleButton modeToggleButton) {
175 this.modeToggleButton = modeToggleButton;
176 modeToggleButton.setText("OUT");
177 }
178
179 @Override
180 public void changed(ObservableValue<? extends PinMode> ov, PinMode t, PinMode newMode) {
181 if (newMode.equals(PinMode.DIGITAL_OUTPUT)) {
182 modeToggleButton.setText("OUT");
183 } else {
184 modeToggleButton.setText("IN");
185 }
186 }
187 }
188
189}
Der Code des Projektes (RaspiGPIOControllerFX) kann via BitBucket bezogen werden.
Zudem gibt es noch folgende YouTube Videos zum Thema NetBeans Remote Deployment, die auch für den Oracle Virtual Developer Day verwendet wurden:
Mit dem Laden des Videos akzeptieren Sie die Datenschutzerklärung von YouTube.
Mehr erfahren
YouTube immer entsperren
Mit dem Laden des Videos akzeptieren Sie die Datenschutzerklärung von YouTube.
Mehr erfahren
YouTube immer entsperren
Mit dem Laden des Videos akzeptieren Sie die Datenschutzerklärung von YouTube.
Mehr erfahren
YouTube immer entsperren
Weitere Links zum Thema:
Die Printversion dieses Artikels ist in der JavaAktuell 04-2015 erschienen.
JavaOne 2014:
James Gosling, Robots, the Raspberry Pi, and Small Devices [UGF8907] (NetBeans Day)
(James Gosling, Jose Pereda, Shai Almog, Johannes Weigend, Jens Deters)
Debugging and Profiling Robots with James Gosling [CON6699] (James Gosling, Mark, Heckler, Jose Pereda, Geertjan Wielenga, Jens Deters)
Weitere Beiträge
von Jens Deters
Dein Job bei codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Weitere Artikel in diesem Themenbereich
Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog-Autor*in
Jens Deters
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.