//

Kaffee und Kuchen – Projekte mit Java Embedded 8 auf dem Raspberry Pi

6.9.2015 | 10 Minuten Lesezeit

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

Video laden

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}
184

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}
190

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

Video laden

YouTube immer entsperren

Mit dem Laden des Videos akzeptieren Sie die Datenschutzerklärung von YouTube.
Mehr erfahren

Video laden

YouTube immer entsperren

Mit dem Laden des Videos akzeptieren Sie die Datenschutzerklärung von YouTube.
Mehr erfahren

Video laden

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)

Beitrag teilen

Gefällt mir

0

//

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.